diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
commit | 57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch) | |
tree | 5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/iio |
Initial import
Diffstat (limited to 'drivers/iio')
217 files changed, 79269 insertions, 0 deletions
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig new file mode 100644 index 000000000..4011effe4 --- /dev/null +++ b/drivers/iio/Kconfig @@ -0,0 +1,80 @@ +# +# Industrial I/O subsystem configuration +# + +menuconfig IIO + tristate "Industrial I/O support" + select ANON_INODES + help + The industrial I/O subsystem provides a unified framework for + drivers for many different types of embedded sensors using a + number of different physical interfaces (i2c, spi, etc). + +if IIO + +config IIO_BUFFER + bool "Enable buffer support within IIO" + help + Provide core support for various buffer based data + acquisition methods. + +if IIO_BUFFER + +config IIO_BUFFER_CB + bool "IIO callback buffer used for push in-kernel interfaces" + help + Should be selected by any drivers that do in-kernel push + usage. That is, those where the data is pushed to the consumer. + +config IIO_KFIFO_BUF + tristate "Industrial I/O buffering based on kfifo" + help + A simple fifo based on kfifo. Note that this currently provides + no buffer events so it is up to userspace to work out how + often to read from the buffer. + +config IIO_TRIGGERED_BUFFER + tristate + select IIO_TRIGGER + select IIO_KFIFO_BUF + help + Provides helper functions for setting up triggered buffers. + +endif # IIO_BUFFER + +config IIO_TRIGGER + bool "Enable triggered sampling support" + help + Provides IIO core support for triggers. Currently these + are used to initialize capture of samples to push into + buffers. The triggers are effectively a 'capture + data now' interrupt. + +config IIO_CONSUMERS_PER_TRIGGER + int "Maximum number of consumers per trigger" + depends on IIO_TRIGGER + default "2" + help + This value controls the maximum number of consumers that a + given trigger may handle. Default is 2. + +source "drivers/iio/accel/Kconfig" +source "drivers/iio/adc/Kconfig" +source "drivers/iio/amplifiers/Kconfig" +source "drivers/iio/common/Kconfig" +source "drivers/iio/dac/Kconfig" +source "drivers/iio/frequency/Kconfig" +source "drivers/iio/gyro/Kconfig" +source "drivers/iio/humidity/Kconfig" +source "drivers/iio/imu/Kconfig" +source "drivers/iio/light/Kconfig" +source "drivers/iio/magnetometer/Kconfig" +source "drivers/iio/orientation/Kconfig" +if IIO_TRIGGER + source "drivers/iio/trigger/Kconfig" +endif #IIO_TRIGGER +source "drivers/iio/pressure/Kconfig" +source "drivers/iio/proximity/Kconfig" +source "drivers/iio/temperature/Kconfig" + +endif # IIO diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile new file mode 100644 index 000000000..698afc2d1 --- /dev/null +++ b/drivers/iio/Makefile @@ -0,0 +1,29 @@ +# +# Makefile for the industrial I/O core. +# + +obj-$(CONFIG_IIO) += industrialio.o +industrialio-y := industrialio-core.o industrialio-event.o inkern.o +industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o +industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o +industrialio-$(CONFIG_IIO_BUFFER_CB) += buffer_cb.o + +obj-$(CONFIG_IIO_TRIGGERED_BUFFER) += industrialio-triggered-buffer.o +obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o + +obj-y += accel/ +obj-y += adc/ +obj-y += amplifiers/ +obj-y += common/ +obj-y += dac/ +obj-y += gyro/ +obj-y += frequency/ +obj-y += humidity/ +obj-y += imu/ +obj-y += light/ +obj-y += magnetometer/ +obj-y += orientation/ +obj-y += pressure/ +obj-y += proximity/ +obj-y += temperature/ +obj-y += trigger/ diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig new file mode 100644 index 000000000..7c9a9a94a --- /dev/null +++ b/drivers/iio/accel/Kconfig @@ -0,0 +1,139 @@ +# +# Accelerometer drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Accelerometers" + +config BMA180 + tristate "Bosch BMA180/BMA250 3-Axis Accelerometer Driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for the Bosch BMA180 or + BMA250 triaxial acceleration sensor. + + To compile this driver as a module, choose M here: the + module will be called bma180. + +config BMC150_ACCEL + tristate "Bosch BMC150 Accelerometer Driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the following Bosch accelerometers: + BMC150, BMI055, BMA250E, BMA222E, BMA255, BMA280. + + Currently this only supports the device via an i2c interface. + + This is a combo module with both accelerometer and magnetometer. + This driver is only implementing accelerometer part, which has + its own address and register map. + +config HID_SENSOR_ACCEL_3D + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID Accelerometers 3D" + help + Say yes here to build support for the HID SENSOR + accelerometers 3D. + + To compile this driver as a module, choose M here: the + module will be called hid-sensor-accel-3d. + +config IIO_ST_ACCEL_3AXIS + tristate "STMicroelectronics accelerometers 3-Axis Driver" + depends on (I2C || SPI_MASTER) && SYSFS + select IIO_ST_SENSORS_CORE + select IIO_ST_ACCEL_I2C_3AXIS if (I2C) + select IIO_ST_ACCEL_SPI_3AXIS if (SPI_MASTER) + select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) + help + Say yes here to build support for STMicroelectronics accelerometers: + LSM303DLH, LSM303DLHC, LIS3DH, LSM330D, LSM330DL, LSM330DLC, + LIS331DLH, LSM303DL, LSM303DLM, LSM330. + + This driver can also be built as a module. If so, these modules + will be created: + - st_accel (core functions for the driver [it is mandatory]); + - st_accel_i2c (necessary for the I2C devices [optional*]); + - st_accel_spi (necessary for the SPI devices [optional*]); + + (*) one of these is necessary to do something. + +config IIO_ST_ACCEL_I2C_3AXIS + tristate + depends on IIO_ST_ACCEL_3AXIS + depends on IIO_ST_SENSORS_I2C + +config IIO_ST_ACCEL_SPI_3AXIS + tristate + depends on IIO_ST_ACCEL_3AXIS + depends on IIO_ST_SENSORS_SPI + +config KXSD9 + tristate "Kionix KXSD9 Accelerometer Driver" + depends on SPI + help + Say yes here to build support for the Kionix KXSD9 accelerometer. + Currently this only supports the device via an SPI interface. + + To compile this driver as a module, choose M here: the module + will be called kxsd9. + +config MMA8452 + tristate "Freescale MMA8452Q Accelerometer Driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Freescale MMA8452Q 3-axis + accelerometer. + + To compile this driver as a module, choose M here: the module + will be called mma8452. + +config KXCJK1013 + tristate "Kionix 3-Axis Accelerometer Driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for the Kionix KXCJK-1013 + triaxial acceleration sensor. This driver also supports KXCJ9-1008 + and KXTJ2-1009. + + To compile this driver as a module, choose M here: the module will + be called kxcjk-1013. + +config MMA9551_CORE + tristate + +config MMA9551 + tristate "Freescale MMA9551L Intelligent Motion-Sensing Platform Driver" + depends on I2C + select MMA9551_CORE + + help + Say yes here to build support for the Freescale MMA9551L + Intelligent Motion-Sensing Platform Driver. + + To compile this driver as a module, choose M here: the module + will be called mma9551. + +config MMA9553 + tristate "Freescale MMA9553L Intelligent Pedometer Platform Driver" + depends on I2C + select MMA9551_CORE + help + Say yes here to build support for the Freescale MMA9553L + Intelligent Pedometer Platform Driver. + + To compile this driver as a module, choose M here: the module + will be called mma9553. +endmenu diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile new file mode 100644 index 000000000..99d89e46c --- /dev/null +++ b/drivers/iio/accel/Makefile @@ -0,0 +1,24 @@ +# +# Makefile for industrial I/O accelerometer drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_BMA180) += bma180.o +obj-$(CONFIG_BMC150_ACCEL) += bmc150-accel.o +obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o +obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o +obj-$(CONFIG_KXSD9) += kxsd9.o +obj-$(CONFIG_MMA8452) += mma8452.o + +obj-$(CONFIG_MMA9551_CORE) += mma9551_core.o +obj-$(CONFIG_MMA9551) += mma9551.o +obj-$(CONFIG_MMA9553) += mma9553.o + +obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_accel_sensor.o + +obj-$(CONFIG_IIO_ST_ACCEL_3AXIS) += st_accel.o +st_accel-y := st_accel_core.o +st_accel-$(CONFIG_IIO_BUFFER) += st_accel_buffer.o + +obj-$(CONFIG_IIO_ST_ACCEL_I2C_3AXIS) += st_accel_i2c.o +obj-$(CONFIG_IIO_ST_ACCEL_SPI_3AXIS) += st_accel_spi.o diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c new file mode 100644 index 000000000..75c6d2103 --- /dev/null +++ b/drivers/iio/accel/bma180.c @@ -0,0 +1,862 @@ +/* + * bma180.c - IIO driver for Bosch BMA180 triaxial acceleration sensor + * + * Copyright 2013 Oleksandr Kravchenko <x0199363@ti.com> + * + * Support for BMA250 (c) Peter Meerwald <pmeerw@pmeerw.net> + * + * 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. + * + * SPI is not supported by driver + * BMA180: 7-bit I2C slave address 0x40 or 0x41 + * BMA250: 7-bit I2C slave address 0x18 or 0x19 + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/bitops.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define BMA180_DRV_NAME "bma180" +#define BMA180_IRQ_NAME "bma180_event" + +enum { + BMA180, + BMA250, +}; + +struct bma180_data; + +struct bma180_part_info { + const struct iio_chan_spec *channels; + unsigned num_channels; + const int *scale_table; + unsigned num_scales; + const int *bw_table; + unsigned num_bw; + + u8 int_reset_reg, int_reset_mask; + u8 sleep_reg, sleep_mask; + u8 bw_reg, bw_mask; + u8 scale_reg, scale_mask; + u8 power_reg, power_mask, lowpower_val; + u8 int_enable_reg, int_enable_mask; + u8 softreset_reg; + + int (*chip_config)(struct bma180_data *data); + void (*chip_disable)(struct bma180_data *data); +}; + +/* Register set */ +#define BMA180_CHIP_ID 0x00 /* Need to distinguish BMA180 from other */ +#define BMA180_ACC_X_LSB 0x02 /* First of 6 registers of accel data */ +#define BMA180_TEMP 0x08 +#define BMA180_CTRL_REG0 0x0d +#define BMA180_RESET 0x10 +#define BMA180_BW_TCS 0x20 +#define BMA180_CTRL_REG3 0x21 +#define BMA180_TCO_Z 0x30 +#define BMA180_OFFSET_LSB1 0x35 + +/* BMA180_CTRL_REG0 bits */ +#define BMA180_DIS_WAKE_UP BIT(0) /* Disable wake up mode */ +#define BMA180_SLEEP BIT(1) /* 1 - chip will sleep */ +#define BMA180_EE_W BIT(4) /* Unlock writing to addr from 0x20 */ +#define BMA180_RESET_INT BIT(6) /* Reset pending interrupts */ + +/* BMA180_CTRL_REG3 bits */ +#define BMA180_NEW_DATA_INT BIT(1) /* Intr every new accel data is ready */ + +/* BMA180_OFFSET_LSB1 skipping mode bit */ +#define BMA180_SMP_SKIP BIT(0) + +/* Bit masks for registers bit fields */ +#define BMA180_RANGE 0x0e /* Range of measured accel values */ +#define BMA180_BW 0xf0 /* Accel bandwidth */ +#define BMA180_MODE_CONFIG 0x03 /* Config operation modes */ + +/* We have to write this value in reset register to do soft reset */ +#define BMA180_RESET_VAL 0xb6 + +#define BMA180_ID_REG_VAL 0x03 + +/* Chip power modes */ +#define BMA180_LOW_POWER 0x03 + +#define BMA250_RANGE_REG 0x0f +#define BMA250_BW_REG 0x10 +#define BMA250_POWER_REG 0x11 +#define BMA250_RESET_REG 0x14 +#define BMA250_INT_ENABLE_REG 0x17 +#define BMA250_INT_MAP_REG 0x1a +#define BMA250_INT_RESET_REG 0x21 + +#define BMA250_RANGE_MASK GENMASK(3, 0) /* Range of accel values */ +#define BMA250_BW_MASK GENMASK(4, 0) /* Accel bandwidth */ +#define BMA250_SUSPEND_MASK BIT(7) /* chip will sleep */ +#define BMA250_LOWPOWER_MASK BIT(6) +#define BMA250_DATA_INTEN_MASK BIT(4) +#define BMA250_INT1_DATA_MASK BIT(0) +#define BMA250_INT_RESET_MASK BIT(7) /* Reset pending interrupts */ + +struct bma180_data { + struct i2c_client *client; + struct iio_trigger *trig; + const struct bma180_part_info *part_info; + struct mutex mutex; + bool sleep_state; + int scale; + int bw; + bool pmode; + u8 buff[16]; /* 3x 16-bit + 8-bit + padding + timestamp */ +}; + +enum bma180_chan { + AXIS_X, + AXIS_Y, + AXIS_Z, + TEMP +}; + +static int bma180_bw_table[] = { 10, 20, 40, 75, 150, 300 }; /* Hz */ +static int bma180_scale_table[] = { 1275, 1863, 2452, 3727, 4903, 9709, 19417 }; + +static int bma250_bw_table[] = { 8, 16, 31, 63, 125, 250 }; /* Hz */ +static int bma250_scale_table[] = { 0, 0, 0, 38344, 0, 76590, 0, 0, 153180, 0, + 0, 0, 306458 }; + +static int bma180_get_data_reg(struct bma180_data *data, enum bma180_chan chan) +{ + int ret; + + if (data->sleep_state) + return -EBUSY; + + switch (chan) { + case TEMP: + ret = i2c_smbus_read_byte_data(data->client, BMA180_TEMP); + if (ret < 0) + dev_err(&data->client->dev, "failed to read temp register\n"); + break; + default: + ret = i2c_smbus_read_word_data(data->client, + BMA180_ACC_X_LSB + chan * 2); + if (ret < 0) + dev_err(&data->client->dev, + "failed to read accel_%c register\n", + 'x' + chan); + } + + return ret; +} + +static int bma180_set_bits(struct bma180_data *data, u8 reg, u8 mask, u8 val) +{ + int ret = i2c_smbus_read_byte_data(data->client, reg); + u8 reg_val = (ret & ~mask) | (val << (ffs(mask) - 1)); + + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(data->client, reg, reg_val); +} + +static int bma180_reset_intr(struct bma180_data *data) +{ + int ret = bma180_set_bits(data, data->part_info->int_reset_reg, + data->part_info->int_reset_mask, 1); + + if (ret) + dev_err(&data->client->dev, "failed to reset interrupt\n"); + + return ret; +} + +static int bma180_set_new_data_intr_state(struct bma180_data *data, bool state) +{ + int ret = bma180_set_bits(data, data->part_info->int_enable_reg, + data->part_info->int_enable_mask, state); + if (ret) + goto err; + ret = bma180_reset_intr(data); + if (ret) + goto err; + + return 0; + +err: + dev_err(&data->client->dev, + "failed to set new data interrupt state %d\n", state); + return ret; +} + +static int bma180_set_sleep_state(struct bma180_data *data, bool state) +{ + int ret = bma180_set_bits(data, data->part_info->sleep_reg, + data->part_info->sleep_mask, state); + + if (ret) { + dev_err(&data->client->dev, + "failed to set sleep state %d\n", state); + return ret; + } + data->sleep_state = state; + + return 0; +} + +static int bma180_set_ee_writing_state(struct bma180_data *data, bool state) +{ + int ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_EE_W, state); + + if (ret) + dev_err(&data->client->dev, + "failed to set ee writing state %d\n", state); + + return ret; +} + +static int bma180_set_bw(struct bma180_data *data, int val) +{ + int ret, i; + + if (data->sleep_state) + return -EBUSY; + + for (i = 0; i < data->part_info->num_bw; ++i) { + if (data->part_info->bw_table[i] == val) { + ret = bma180_set_bits(data, data->part_info->bw_reg, + data->part_info->bw_mask, i); + if (ret) { + dev_err(&data->client->dev, + "failed to set bandwidth\n"); + return ret; + } + data->bw = val; + return 0; + } + } + + return -EINVAL; +} + +static int bma180_set_scale(struct bma180_data *data, int val) +{ + int ret, i; + + if (data->sleep_state) + return -EBUSY; + + for (i = 0; i < data->part_info->num_scales; ++i) + if (data->part_info->scale_table[i] == val) { + ret = bma180_set_bits(data, data->part_info->scale_reg, + data->part_info->scale_mask, i); + if (ret) { + dev_err(&data->client->dev, + "failed to set scale\n"); + return ret; + } + data->scale = val; + return 0; + } + + return -EINVAL; +} + +static int bma180_set_pmode(struct bma180_data *data, bool mode) +{ + u8 reg_val = mode ? data->part_info->lowpower_val : 0; + int ret = bma180_set_bits(data, data->part_info->power_reg, + data->part_info->power_mask, reg_val); + + if (ret) { + dev_err(&data->client->dev, "failed to set power mode\n"); + return ret; + } + data->pmode = mode; + + return 0; +} + +static int bma180_soft_reset(struct bma180_data *data) +{ + int ret = i2c_smbus_write_byte_data(data->client, + data->part_info->softreset_reg, BMA180_RESET_VAL); + + if (ret) + dev_err(&data->client->dev, "failed to reset the chip\n"); + + return ret; +} + +static int bma180_chip_init(struct bma180_data *data) +{ + /* Try to read chip_id register. It must return 0x03. */ + int ret = i2c_smbus_read_byte_data(data->client, BMA180_CHIP_ID); + + if (ret < 0) + return ret; + if (ret != BMA180_ID_REG_VAL) + return -ENODEV; + + ret = bma180_soft_reset(data); + if (ret) + return ret; + /* + * No serial transaction should occur within minimum 10 us + * after soft_reset command + */ + msleep(20); + + ret = bma180_set_new_data_intr_state(data, false); + if (ret) + return ret; + + return bma180_set_pmode(data, false); +} + +static int bma180_chip_config(struct bma180_data *data) +{ + int ret = bma180_chip_init(data); + + if (ret) + goto err; + ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_DIS_WAKE_UP, 1); + if (ret) + goto err; + ret = bma180_set_ee_writing_state(data, true); + if (ret) + goto err; + ret = bma180_set_bits(data, BMA180_OFFSET_LSB1, BMA180_SMP_SKIP, 1); + if (ret) + goto err; + ret = bma180_set_bw(data, 20); /* 20 Hz */ + if (ret) + goto err; + ret = bma180_set_scale(data, 2452); /* 2 G */ + if (ret) + goto err; + + return 0; + +err: + dev_err(&data->client->dev, "failed to config the chip\n"); + return ret; +} + +static int bma250_chip_config(struct bma180_data *data) +{ + int ret = bma180_chip_init(data); + + if (ret) + goto err; + ret = bma180_set_bw(data, 16); /* 16 Hz */ + if (ret) + goto err; + ret = bma180_set_scale(data, 38344); /* 2 G */ + if (ret) + goto err; + ret = bma180_set_bits(data, BMA250_INT_MAP_REG, + BMA250_INT1_DATA_MASK, 1); + if (ret) + goto err; + + return 0; + +err: + dev_err(&data->client->dev, "failed to config the chip\n"); + return ret; +} + +static void bma180_chip_disable(struct bma180_data *data) +{ + if (bma180_set_new_data_intr_state(data, false)) + goto err; + if (bma180_set_ee_writing_state(data, false)) + goto err; + if (bma180_set_sleep_state(data, true)) + goto err; + + return; + +err: + dev_err(&data->client->dev, "failed to disable the chip\n"); +} + +static void bma250_chip_disable(struct bma180_data *data) +{ + if (bma180_set_new_data_intr_state(data, false)) + goto err; + if (bma180_set_sleep_state(data, true)) + goto err; + + return; + +err: + dev_err(&data->client->dev, "failed to disable the chip\n"); +} + +static ssize_t bma180_show_avail(char *buf, const int *vals, unsigned n, + bool micros) +{ + size_t len = 0; + int i; + + for (i = 0; i < n; i++) { + if (!vals[i]) + continue; + len += scnprintf(buf + len, PAGE_SIZE - len, + micros ? "0.%06d " : "%d ", vals[i]); + } + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t bma180_show_filter_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bma180_data *data = iio_priv(dev_to_iio_dev(dev)); + + return bma180_show_avail(buf, data->part_info->bw_table, + data->part_info->num_bw, false); +} + +static ssize_t bma180_show_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bma180_data *data = iio_priv(dev_to_iio_dev(dev)); + + return bma180_show_avail(buf, data->part_info->scale_table, + data->part_info->num_scales, true); +} + +static IIO_DEVICE_ATTR(in_accel_filter_low_pass_3db_frequency_available, + S_IRUGO, bma180_show_filter_freq_avail, NULL, 0); + +static IIO_DEVICE_ATTR(in_accel_scale_available, + S_IRUGO, bma180_show_scale_avail, NULL, 0); + +static struct attribute *bma180_attributes[] = { + &iio_dev_attr_in_accel_filter_low_pass_3db_frequency_available. + dev_attr.attr, + &iio_dev_attr_in_accel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bma180_attrs_group = { + .attrs = bma180_attributes, +}; + +static int bma180_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->mutex); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&data->mutex); + return -EBUSY; + } + ret = bma180_get_data_reg(data, chan->scan_index); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *val = data->bw; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + *val = 0; + *val2 = data->scale; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 500; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = 48; /* 0 LSB @ 24 degree C */ + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int bma180_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val) + return -EINVAL; + mutex_lock(&data->mutex); + ret = bma180_set_scale(data, val2); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + if (val2) + return -EINVAL; + mutex_lock(&data->mutex); + ret = bma180_set_bw(data, val); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info bma180_info = { + .attrs = &bma180_attrs_group, + .read_raw = bma180_read_raw, + .write_raw = bma180_write_raw, + .driver_module = THIS_MODULE, +}; + +static const char * const bma180_power_modes[] = { "low_noise", "low_power" }; + +static int bma180_get_power_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct bma180_data *data = iio_priv(indio_dev); + + return data->pmode; +} + +static int bma180_set_power_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = bma180_set_pmode(data, mode); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_enum bma180_power_mode_enum = { + .items = bma180_power_modes, + .num_items = ARRAY_SIZE(bma180_power_modes), + .get = bma180_get_power_mode, + .set = bma180_set_power_mode, +}; + +static const struct iio_chan_spec_ext_info bma180_ext_info[] = { + IIO_ENUM("power_mode", true, &bma180_power_mode_enum), + IIO_ENUM_AVAILABLE("power_mode", &bma180_power_mode_enum), + { }, +}; + +#define BMA180_ACC_CHANNEL(_axis, _bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _bits, \ + .storagebits = 16, \ + .shift = 16 - _bits, \ + }, \ + .ext_info = bma180_ext_info, \ +} + +#define BMA180_TEMP_CHANNEL { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = TEMP, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 8, \ + .storagebits = 16, \ + }, \ +} + +static const struct iio_chan_spec bma180_channels[] = { + BMA180_ACC_CHANNEL(X, 14), + BMA180_ACC_CHANNEL(Y, 14), + BMA180_ACC_CHANNEL(Z, 14), + BMA180_TEMP_CHANNEL, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_chan_spec bma250_channels[] = { + BMA180_ACC_CHANNEL(X, 10), + BMA180_ACC_CHANNEL(Y, 10), + BMA180_ACC_CHANNEL(Z, 10), + BMA180_TEMP_CHANNEL, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct bma180_part_info bma180_part_info[] = { + [BMA180] = { + bma180_channels, ARRAY_SIZE(bma180_channels), + bma180_scale_table, ARRAY_SIZE(bma180_scale_table), + bma180_bw_table, ARRAY_SIZE(bma180_bw_table), + BMA180_CTRL_REG0, BMA180_RESET_INT, + BMA180_CTRL_REG0, BMA180_SLEEP, + BMA180_BW_TCS, BMA180_BW, + BMA180_OFFSET_LSB1, BMA180_RANGE, + BMA180_TCO_Z, BMA180_MODE_CONFIG, BMA180_LOW_POWER, + BMA180_CTRL_REG3, BMA180_NEW_DATA_INT, + BMA180_RESET, + bma180_chip_config, + bma180_chip_disable, + }, + [BMA250] = { + bma250_channels, ARRAY_SIZE(bma250_channels), + bma250_scale_table, ARRAY_SIZE(bma250_scale_table), + bma250_bw_table, ARRAY_SIZE(bma250_bw_table), + BMA250_INT_RESET_REG, BMA250_INT_RESET_MASK, + BMA250_POWER_REG, BMA250_SUSPEND_MASK, + BMA250_BW_REG, BMA250_BW_MASK, + BMA250_RANGE_REG, BMA250_RANGE_MASK, + BMA250_POWER_REG, BMA250_LOWPOWER_MASK, 1, + BMA250_INT_ENABLE_REG, BMA250_DATA_INTEN_MASK, + BMA250_RESET_REG, + bma250_chip_config, + bma250_chip_disable, + }, +}; + +static irqreturn_t bma180_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bma180_data *data = iio_priv(indio_dev); + int64_t time_ns = iio_get_time_ns(); + int bit, ret, i = 0; + + mutex_lock(&data->mutex); + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = bma180_get_data_reg(data, bit); + if (ret < 0) { + mutex_unlock(&data->mutex); + goto err; + } + ((s16 *)data->buff)[i++] = ret; + } + + mutex_unlock(&data->mutex); + + iio_push_to_buffers_with_timestamp(indio_dev, data->buff, time_ns); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bma180_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bma180_data *data = iio_priv(indio_dev); + + return bma180_set_new_data_intr_state(data, state); +} + +static int bma180_trig_try_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bma180_data *data = iio_priv(indio_dev); + + return bma180_reset_intr(data); +} + +static const struct iio_trigger_ops bma180_trigger_ops = { + .set_trigger_state = bma180_data_rdy_trigger_set_state, + .try_reenable = bma180_trig_try_reen, + .owner = THIS_MODULE, +}; + +static int bma180_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bma180_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->part_info = &bma180_part_info[id->driver_data]; + + ret = data->part_info->chip_config(data); + if (ret < 0) + goto err_chip_disable; + + mutex_init(&data->mutex); + indio_dev->dev.parent = &client->dev; + indio_dev->channels = data->part_info->channels; + indio_dev->num_channels = data->part_info->num_channels; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bma180_info; + + if (client->irq > 0) { + data->trig = iio_trigger_alloc("%s-dev%d", indio_dev->name, + indio_dev->id); + if (!data->trig) { + ret = -ENOMEM; + goto err_chip_disable; + } + + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, IRQF_TRIGGER_RISING, + "bma180_event", data->trig); + if (ret) { + dev_err(&client->dev, "unable to request IRQ\n"); + goto err_trigger_free; + } + + data->trig->dev.parent = &client->dev; + data->trig->ops = &bma180_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + indio_dev->trig = iio_trigger_get(data->trig); + + ret = iio_trigger_register(data->trig); + if (ret) + goto err_trigger_free; + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + bma180_trigger_handler, NULL); + if (ret < 0) { + dev_err(&client->dev, "unable to setup iio triggered buffer\n"); + goto err_trigger_unregister; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + + return 0; + +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: + if (data->trig) + iio_trigger_unregister(data->trig); +err_trigger_free: + iio_trigger_free(data->trig); +err_chip_disable: + data->part_info->chip_disable(data); + + return ret; +} + +static int bma180_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bma180_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (data->trig) { + iio_trigger_unregister(data->trig); + iio_trigger_free(data->trig); + } + + mutex_lock(&data->mutex); + data->part_info->chip_disable(data); + mutex_unlock(&data->mutex); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bma180_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = bma180_set_sleep_state(data, true); + mutex_unlock(&data->mutex); + + return ret; +} + +static int bma180_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bma180_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = bma180_set_sleep_state(data, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(bma180_pm_ops, bma180_suspend, bma180_resume); +#define BMA180_PM_OPS (&bma180_pm_ops) +#else +#define BMA180_PM_OPS NULL +#endif + +static struct i2c_device_id bma180_ids[] = { + { "bma180", BMA180 }, + { "bma250", BMA250 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, bma180_ids); + +static struct i2c_driver bma180_driver = { + .driver = { + .name = "bma180", + .owner = THIS_MODULE, + .pm = BMA180_PM_OPS, + }, + .probe = bma180_probe, + .remove = bma180_remove, + .id_table = bma180_ids, +}; + +module_i2c_driver(bma180_driver); + +MODULE_AUTHOR("Kravchenko Oleksandr <x0199363@ti.com>"); +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("Bosch BMA180/BMA250 triaxial acceleration sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/bmc150-accel.c b/drivers/iio/accel/bmc150-accel.c new file mode 100644 index 000000000..bf827d012 --- /dev/null +++ b/drivers/iio/accel/bmc150-accel.c @@ -0,0 +1,1853 @@ +/* + * 3-axis accelerometer driver supporting following Bosch-Sensortec chips: + * - BMC150 + * - BMI055 + * - BMA255 + * - BMA250E + * - BMA222E + * - BMA280 + * + * Copyright (c) 2014, 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. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/gpio/consumer.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define BMC150_ACCEL_DRV_NAME "bmc150_accel" +#define BMC150_ACCEL_IRQ_NAME "bmc150_accel_event" +#define BMC150_ACCEL_GPIO_NAME "bmc150_accel_int" + +#define BMC150_ACCEL_REG_CHIP_ID 0x00 + +#define BMC150_ACCEL_REG_INT_STATUS_2 0x0B +#define BMC150_ACCEL_ANY_MOTION_MASK 0x07 +#define BMC150_ACCEL_ANY_MOTION_BIT_X BIT(0) +#define BMC150_ACCEL_ANY_MOTION_BIT_Y BIT(1) +#define BMC150_ACCEL_ANY_MOTION_BIT_Z BIT(2) +#define BMC150_ACCEL_ANY_MOTION_BIT_SIGN BIT(3) + +#define BMC150_ACCEL_REG_PMU_LPW 0x11 +#define BMC150_ACCEL_PMU_MODE_MASK 0xE0 +#define BMC150_ACCEL_PMU_MODE_SHIFT 5 +#define BMC150_ACCEL_PMU_BIT_SLEEP_DUR_MASK 0x17 +#define BMC150_ACCEL_PMU_BIT_SLEEP_DUR_SHIFT 1 + +#define BMC150_ACCEL_REG_PMU_RANGE 0x0F + +#define BMC150_ACCEL_DEF_RANGE_2G 0x03 +#define BMC150_ACCEL_DEF_RANGE_4G 0x05 +#define BMC150_ACCEL_DEF_RANGE_8G 0x08 +#define BMC150_ACCEL_DEF_RANGE_16G 0x0C + +/* Default BW: 125Hz */ +#define BMC150_ACCEL_REG_PMU_BW 0x10 +#define BMC150_ACCEL_DEF_BW 125 + +#define BMC150_ACCEL_REG_INT_MAP_0 0x19 +#define BMC150_ACCEL_INT_MAP_0_BIT_SLOPE BIT(2) + +#define BMC150_ACCEL_REG_INT_MAP_1 0x1A +#define BMC150_ACCEL_INT_MAP_1_BIT_DATA BIT(0) +#define BMC150_ACCEL_INT_MAP_1_BIT_FWM BIT(1) +#define BMC150_ACCEL_INT_MAP_1_BIT_FFULL BIT(2) + +#define BMC150_ACCEL_REG_INT_RST_LATCH 0x21 +#define BMC150_ACCEL_INT_MODE_LATCH_RESET 0x80 +#define BMC150_ACCEL_INT_MODE_LATCH_INT 0x0F +#define BMC150_ACCEL_INT_MODE_NON_LATCH_INT 0x00 + +#define BMC150_ACCEL_REG_INT_EN_0 0x16 +#define BMC150_ACCEL_INT_EN_BIT_SLP_X BIT(0) +#define BMC150_ACCEL_INT_EN_BIT_SLP_Y BIT(1) +#define BMC150_ACCEL_INT_EN_BIT_SLP_Z BIT(2) + +#define BMC150_ACCEL_REG_INT_EN_1 0x17 +#define BMC150_ACCEL_INT_EN_BIT_DATA_EN BIT(4) +#define BMC150_ACCEL_INT_EN_BIT_FFULL_EN BIT(5) +#define BMC150_ACCEL_INT_EN_BIT_FWM_EN BIT(6) + +#define BMC150_ACCEL_REG_INT_OUT_CTRL 0x20 +#define BMC150_ACCEL_INT_OUT_CTRL_INT1_LVL BIT(0) + +#define BMC150_ACCEL_REG_INT_5 0x27 +#define BMC150_ACCEL_SLOPE_DUR_MASK 0x03 + +#define BMC150_ACCEL_REG_INT_6 0x28 +#define BMC150_ACCEL_SLOPE_THRES_MASK 0xFF + +/* Slope duration in terms of number of samples */ +#define BMC150_ACCEL_DEF_SLOPE_DURATION 1 +/* in terms of multiples of g's/LSB, based on range */ +#define BMC150_ACCEL_DEF_SLOPE_THRESHOLD 1 + +#define BMC150_ACCEL_REG_XOUT_L 0x02 + +#define BMC150_ACCEL_MAX_STARTUP_TIME_MS 100 + +/* Sleep Duration values */ +#define BMC150_ACCEL_SLEEP_500_MICRO 0x05 +#define BMC150_ACCEL_SLEEP_1_MS 0x06 +#define BMC150_ACCEL_SLEEP_2_MS 0x07 +#define BMC150_ACCEL_SLEEP_4_MS 0x08 +#define BMC150_ACCEL_SLEEP_6_MS 0x09 +#define BMC150_ACCEL_SLEEP_10_MS 0x0A +#define BMC150_ACCEL_SLEEP_25_MS 0x0B +#define BMC150_ACCEL_SLEEP_50_MS 0x0C +#define BMC150_ACCEL_SLEEP_100_MS 0x0D +#define BMC150_ACCEL_SLEEP_500_MS 0x0E +#define BMC150_ACCEL_SLEEP_1_SEC 0x0F + +#define BMC150_ACCEL_REG_TEMP 0x08 +#define BMC150_ACCEL_TEMP_CENTER_VAL 24 + +#define BMC150_ACCEL_AXIS_TO_REG(axis) (BMC150_ACCEL_REG_XOUT_L + (axis * 2)) +#define BMC150_AUTO_SUSPEND_DELAY_MS 2000 + +#define BMC150_ACCEL_REG_FIFO_STATUS 0x0E +#define BMC150_ACCEL_REG_FIFO_CONFIG0 0x30 +#define BMC150_ACCEL_REG_FIFO_CONFIG1 0x3E +#define BMC150_ACCEL_REG_FIFO_DATA 0x3F +#define BMC150_ACCEL_FIFO_LENGTH 32 + +enum bmc150_accel_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + +enum bmc150_power_modes { + BMC150_ACCEL_SLEEP_MODE_NORMAL, + BMC150_ACCEL_SLEEP_MODE_DEEP_SUSPEND, + BMC150_ACCEL_SLEEP_MODE_LPM, + BMC150_ACCEL_SLEEP_MODE_SUSPEND = 0x04, +}; + +struct bmc150_scale_info { + int scale; + u8 reg_range; +}; + +struct bmc150_accel_chip_info { + u8 chip_id; + const struct iio_chan_spec *channels; + int num_channels; + const struct bmc150_scale_info scale_table[4]; +}; + +struct bmc150_accel_interrupt { + const struct bmc150_accel_interrupt_info *info; + atomic_t users; +}; + +struct bmc150_accel_trigger { + struct bmc150_accel_data *data; + struct iio_trigger *indio_trig; + int (*setup)(struct bmc150_accel_trigger *t, bool state); + int intr; + bool enabled; +}; + +enum bmc150_accel_interrupt_id { + BMC150_ACCEL_INT_DATA_READY, + BMC150_ACCEL_INT_ANY_MOTION, + BMC150_ACCEL_INT_WATERMARK, + BMC150_ACCEL_INTERRUPTS, +}; + +enum bmc150_accel_trigger_id { + BMC150_ACCEL_TRIGGER_DATA_READY, + BMC150_ACCEL_TRIGGER_ANY_MOTION, + BMC150_ACCEL_TRIGGERS, +}; + +struct bmc150_accel_data { + struct i2c_client *client; + struct bmc150_accel_interrupt interrupts[BMC150_ACCEL_INTERRUPTS]; + atomic_t active_intr; + struct bmc150_accel_trigger triggers[BMC150_ACCEL_TRIGGERS]; + struct mutex mutex; + u8 fifo_mode, watermark; + s16 buffer[8]; + u8 bw_bits; + u32 slope_dur; + u32 slope_thres; + u32 range; + int ev_enable_state; + int64_t timestamp, old_timestamp; + const struct bmc150_accel_chip_info *chip_info; +}; + +static const struct { + int val; + int val2; + u8 bw_bits; +} bmc150_accel_samp_freq_table[] = { {15, 620000, 0x08}, + {31, 260000, 0x09}, + {62, 500000, 0x0A}, + {125, 0, 0x0B}, + {250, 0, 0x0C}, + {500, 0, 0x0D}, + {1000, 0, 0x0E}, + {2000, 0, 0x0F} }; + +static const struct { + int bw_bits; + int msec; +} bmc150_accel_sample_upd_time[] = { {0x08, 64}, + {0x09, 32}, + {0x0A, 16}, + {0x0B, 8}, + {0x0C, 4}, + {0x0D, 2}, + {0x0E, 1}, + {0x0F, 1} }; + +static const struct { + int sleep_dur; + u8 reg_value; +} bmc150_accel_sleep_value_table[] = { {0, 0}, + {500, BMC150_ACCEL_SLEEP_500_MICRO}, + {1000, BMC150_ACCEL_SLEEP_1_MS}, + {2000, BMC150_ACCEL_SLEEP_2_MS}, + {4000, BMC150_ACCEL_SLEEP_4_MS}, + {6000, BMC150_ACCEL_SLEEP_6_MS}, + {10000, BMC150_ACCEL_SLEEP_10_MS}, + {25000, BMC150_ACCEL_SLEEP_25_MS}, + {50000, BMC150_ACCEL_SLEEP_50_MS}, + {100000, BMC150_ACCEL_SLEEP_100_MS}, + {500000, BMC150_ACCEL_SLEEP_500_MS}, + {1000000, BMC150_ACCEL_SLEEP_1_SEC} }; + + +static int bmc150_accel_set_mode(struct bmc150_accel_data *data, + enum bmc150_power_modes mode, + int dur_us) +{ + int i; + int ret; + u8 lpw_bits; + int dur_val = -1; + + if (dur_us > 0) { + for (i = 0; i < ARRAY_SIZE(bmc150_accel_sleep_value_table); + ++i) { + if (bmc150_accel_sleep_value_table[i].sleep_dur == + dur_us) + dur_val = + bmc150_accel_sleep_value_table[i].reg_value; + } + } else + dur_val = 0; + + if (dur_val < 0) + return -EINVAL; + + lpw_bits = mode << BMC150_ACCEL_PMU_MODE_SHIFT; + lpw_bits |= (dur_val << BMC150_ACCEL_PMU_BIT_SLEEP_DUR_SHIFT); + + dev_dbg(&data->client->dev, "Set Mode bits %x\n", lpw_bits); + + ret = i2c_smbus_write_byte_data(data->client, + BMC150_ACCEL_REG_PMU_LPW, lpw_bits); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_pmu_lpw\n"); + return ret; + } + + return 0; +} + +static int bmc150_accel_set_bw(struct bmc150_accel_data *data, int val, + int val2) +{ + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(bmc150_accel_samp_freq_table); ++i) { + if (bmc150_accel_samp_freq_table[i].val == val && + bmc150_accel_samp_freq_table[i].val2 == val2) { + ret = i2c_smbus_write_byte_data( + data->client, + BMC150_ACCEL_REG_PMU_BW, + bmc150_accel_samp_freq_table[i].bw_bits); + if (ret < 0) + return ret; + + data->bw_bits = + bmc150_accel_samp_freq_table[i].bw_bits; + return 0; + } + } + + return -EINVAL; +} + +static int bmc150_accel_update_slope(struct bmc150_accel_data *data) +{ + int ret, val; + + ret = i2c_smbus_write_byte_data(data->client, BMC150_ACCEL_REG_INT_6, + data->slope_thres); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_6\n"); + return ret; + } + + ret = i2c_smbus_read_byte_data(data->client, BMC150_ACCEL_REG_INT_5); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_5\n"); + return ret; + } + + val = (ret & ~BMC150_ACCEL_SLOPE_DUR_MASK) | data->slope_dur; + ret = i2c_smbus_write_byte_data(data->client, BMC150_ACCEL_REG_INT_5, + val); + if (ret < 0) { + dev_err(&data->client->dev, "Error write reg_int_5\n"); + return ret; + } + + dev_dbg(&data->client->dev, "%s: %x %x\n", __func__, data->slope_thres, + data->slope_dur); + + return ret; +} + +static int bmc150_accel_any_motion_setup(struct bmc150_accel_trigger *t, + bool state) +{ + if (state) + return bmc150_accel_update_slope(t->data); + + return 0; +} + +static int bmc150_accel_chip_init(struct bmc150_accel_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, BMC150_ACCEL_REG_CHIP_ID); + if (ret < 0) { + dev_err(&data->client->dev, + "Error: Reading chip id\n"); + return ret; + } + + dev_dbg(&data->client->dev, "Chip Id %x\n", ret); + if (ret != data->chip_info->chip_id) { + dev_err(&data->client->dev, "Invalid chip %x\n", ret); + return -ENODEV; + } + + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); + if (ret < 0) + return ret; + + /* Set Bandwidth */ + ret = bmc150_accel_set_bw(data, BMC150_ACCEL_DEF_BW, 0); + if (ret < 0) + return ret; + + /* Set Default Range */ + ret = i2c_smbus_write_byte_data(data->client, + BMC150_ACCEL_REG_PMU_RANGE, + BMC150_ACCEL_DEF_RANGE_4G); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_pmu_range\n"); + return ret; + } + + data->range = BMC150_ACCEL_DEF_RANGE_4G; + + /* Set default slope duration and thresholds */ + data->slope_thres = BMC150_ACCEL_DEF_SLOPE_THRESHOLD; + data->slope_dur = BMC150_ACCEL_DEF_SLOPE_DURATION; + ret = bmc150_accel_update_slope(data); + if (ret < 0) + return ret; + + /* Set default as latched interrupts */ + ret = i2c_smbus_write_byte_data(data->client, + BMC150_ACCEL_REG_INT_RST_LATCH, + BMC150_ACCEL_INT_MODE_LATCH_INT | + BMC150_ACCEL_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_int_rst_latch\n"); + return ret; + } + + return 0; +} + +static int bmc150_accel_get_bw(struct bmc150_accel_data *data, int *val, + int *val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bmc150_accel_samp_freq_table); ++i) { + if (bmc150_accel_samp_freq_table[i].bw_bits == data->bw_bits) { + *val = bmc150_accel_samp_freq_table[i].val; + *val2 = bmc150_accel_samp_freq_table[i].val2; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +#ifdef CONFIG_PM +static int bmc150_accel_get_startup_times(struct bmc150_accel_data *data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bmc150_accel_sample_upd_time); ++i) { + if (bmc150_accel_sample_upd_time[i].bw_bits == data->bw_bits) + return bmc150_accel_sample_upd_time[i].msec; + } + + return BMC150_ACCEL_MAX_STARTUP_TIME_MS; +} + +static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on) +{ + int ret; + + if (on) + ret = pm_runtime_get_sync(&data->client->dev); + else { + pm_runtime_mark_last_busy(&data->client->dev); + ret = pm_runtime_put_autosuspend(&data->client->dev); + } + if (ret < 0) { + dev_err(&data->client->dev, + "Failed: bmc150_accel_set_power_state for %d\n", on); + if (on) + pm_runtime_put_noidle(&data->client->dev); + + return ret; + } + + return 0; +} +#else +static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on) +{ + return 0; +} +#endif + +static const struct bmc150_accel_interrupt_info { + u8 map_reg; + u8 map_bitmask; + u8 en_reg; + u8 en_bitmask; +} bmc150_accel_interrupts[BMC150_ACCEL_INTERRUPTS] = { + { /* data ready interrupt */ + .map_reg = BMC150_ACCEL_REG_INT_MAP_1, + .map_bitmask = BMC150_ACCEL_INT_MAP_1_BIT_DATA, + .en_reg = BMC150_ACCEL_REG_INT_EN_1, + .en_bitmask = BMC150_ACCEL_INT_EN_BIT_DATA_EN, + }, + { /* motion interrupt */ + .map_reg = BMC150_ACCEL_REG_INT_MAP_0, + .map_bitmask = BMC150_ACCEL_INT_MAP_0_BIT_SLOPE, + .en_reg = BMC150_ACCEL_REG_INT_EN_0, + .en_bitmask = BMC150_ACCEL_INT_EN_BIT_SLP_X | + BMC150_ACCEL_INT_EN_BIT_SLP_Y | + BMC150_ACCEL_INT_EN_BIT_SLP_Z + }, + { /* fifo watermark interrupt */ + .map_reg = BMC150_ACCEL_REG_INT_MAP_1, + .map_bitmask = BMC150_ACCEL_INT_MAP_1_BIT_FWM, + .en_reg = BMC150_ACCEL_REG_INT_EN_1, + .en_bitmask = BMC150_ACCEL_INT_EN_BIT_FWM_EN, + }, +}; + +static void bmc150_accel_interrupts_setup(struct iio_dev *indio_dev, + struct bmc150_accel_data *data) +{ + int i; + + for (i = 0; i < BMC150_ACCEL_INTERRUPTS; i++) + data->interrupts[i].info = &bmc150_accel_interrupts[i]; +} + +static int bmc150_accel_set_interrupt(struct bmc150_accel_data *data, int i, + bool state) +{ + struct bmc150_accel_interrupt *intr = &data->interrupts[i]; + const struct bmc150_accel_interrupt_info *info = intr->info; + int ret; + + if (state) { + if (atomic_inc_return(&intr->users) > 1) + return 0; + } else { + if (atomic_dec_return(&intr->users) > 0) + return 0; + } + + /* + * We will expect the enable and disable to do operation in + * in reverse order. This will happen here anyway as our + * resume operation uses sync mode runtime pm calls, the + * suspend operation will be delayed by autosuspend delay + * So the disable operation will still happen in reverse of + * enable operation. When runtime pm is disabled the mode + * is always on so sequence doesn't matter + */ + ret = bmc150_accel_set_power_state(data, state); + if (ret < 0) + return ret; + + /* map the interrupt to the appropriate pins */ + ret = i2c_smbus_read_byte_data(data->client, info->map_reg); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_map\n"); + goto out_fix_power_state; + } + if (state) + ret |= info->map_bitmask; + else + ret &= ~info->map_bitmask; + + ret = i2c_smbus_write_byte_data(data->client, info->map_reg, + ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_map\n"); + goto out_fix_power_state; + } + + /* enable/disable the interrupt */ + ret = i2c_smbus_read_byte_data(data->client, info->en_reg); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_en\n"); + goto out_fix_power_state; + } + + if (state) + ret |= info->en_bitmask; + else + ret &= ~info->en_bitmask; + + ret = i2c_smbus_write_byte_data(data->client, info->en_reg, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_en\n"); + goto out_fix_power_state; + } + + if (state) + atomic_inc(&data->active_intr); + else + atomic_dec(&data->active_intr); + + return 0; + +out_fix_power_state: + bmc150_accel_set_power_state(data, false); + return ret; +} + + +static int bmc150_accel_set_scale(struct bmc150_accel_data *data, int val) +{ + int ret, i; + + for (i = 0; i < ARRAY_SIZE(data->chip_info->scale_table); ++i) { + if (data->chip_info->scale_table[i].scale == val) { + ret = i2c_smbus_write_byte_data( + data->client, + BMC150_ACCEL_REG_PMU_RANGE, + data->chip_info->scale_table[i].reg_range); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing pmu_range\n"); + return ret; + } + + data->range = data->chip_info->scale_table[i].reg_range; + return 0; + } + } + + return -EINVAL; +} + +static int bmc150_accel_get_temp(struct bmc150_accel_data *data, int *val) +{ + int ret; + + mutex_lock(&data->mutex); + + ret = i2c_smbus_read_byte_data(data->client, BMC150_ACCEL_REG_TEMP); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_temp\n"); + mutex_unlock(&data->mutex); + return ret; + } + *val = sign_extend32(ret, 7); + + mutex_unlock(&data->mutex); + + return IIO_VAL_INT; +} + +static int bmc150_accel_get_axis(struct bmc150_accel_data *data, + struct iio_chan_spec const *chan, + int *val) +{ + int ret; + int axis = chan->scan_index; + + mutex_lock(&data->mutex); + ret = bmc150_accel_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = i2c_smbus_read_word_data(data->client, + BMC150_ACCEL_AXIS_TO_REG(axis)); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading axis %d\n", axis); + bmc150_accel_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + ret = bmc150_accel_set_power_state(data, false); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + + return IIO_VAL_INT; +} + +static int bmc150_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + return bmc150_accel_get_temp(data, val); + case IIO_ACCEL: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + else + return bmc150_accel_get_axis(data, chan, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = BMC150_ACCEL_TEMP_CENTER_VAL; + return IIO_VAL_INT; + } else + return -EINVAL; + case IIO_CHAN_INFO_SCALE: + *val = 0; + switch (chan->type) { + case IIO_TEMP: + *val2 = 500000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + { + int i; + const struct bmc150_scale_info *si; + int st_size = ARRAY_SIZE(data->chip_info->scale_table); + + for (i = 0; i < st_size; ++i) { + si = &data->chip_info->scale_table[i]; + if (si->reg_range == data->range) { + *val2 = si->scale; + return IIO_VAL_INT_PLUS_MICRO; + } + } + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + ret = bmc150_accel_get_bw(data, val, val2); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static int bmc150_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + ret = bmc150_accel_set_bw(data, val, val2); + mutex_unlock(&data->mutex); + break; + case IIO_CHAN_INFO_SCALE: + if (val) + return -EINVAL; + + mutex_lock(&data->mutex); + ret = bmc150_accel_set_scale(data, val2); + mutex_unlock(&data->mutex); + return ret; + default: + ret = -EINVAL; + } + + return ret; +} + +static int bmc150_accel_read_event(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 bmc150_accel_data *data = iio_priv(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + *val = data->slope_thres; + break; + case IIO_EV_INFO_PERIOD: + *val = data->slope_dur; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int bmc150_accel_write_event(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 bmc150_accel_data *data = iio_priv(indio_dev); + + if (data->ev_enable_state) + return -EBUSY; + + switch (info) { + case IIO_EV_INFO_VALUE: + data->slope_thres = val & 0xFF; + break; + case IIO_EV_INFO_PERIOD: + data->slope_dur = val & BMC150_ACCEL_SLOPE_DUR_MASK; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bmc150_accel_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 bmc150_accel_data *data = iio_priv(indio_dev); + + return data->ev_enable_state; +} + +static int bmc150_accel_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 bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + if (state == data->ev_enable_state) + return 0; + + mutex_lock(&data->mutex); + + ret = bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_ANY_MOTION, + state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + data->ev_enable_state = state; + mutex_unlock(&data->mutex); + + return 0; +} + +static int bmc150_accel_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int i; + + for (i = 0; i < BMC150_ACCEL_TRIGGERS; i++) { + if (data->triggers[i].indio_trig == trig) + return 0; + } + + return -EINVAL; +} + +static ssize_t bmc150_accel_get_fifo_watermark(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + int wm; + + mutex_lock(&data->mutex); + wm = data->watermark; + mutex_unlock(&data->mutex); + + return sprintf(buf, "%d\n", wm); +} + +static ssize_t bmc150_accel_get_fifo_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmc150_accel_data *data = iio_priv(indio_dev); + bool state; + + mutex_lock(&data->mutex); + state = data->fifo_mode; + mutex_unlock(&data->mutex); + + return sprintf(buf, "%d\n", state); +} + +static IIO_CONST_ATTR(hwfifo_watermark_min, "1"); +static IIO_CONST_ATTR(hwfifo_watermark_max, + __stringify(BMC150_ACCEL_FIFO_LENGTH)); +static IIO_DEVICE_ATTR(hwfifo_enabled, S_IRUGO, + bmc150_accel_get_fifo_state, NULL, 0); +static IIO_DEVICE_ATTR(hwfifo_watermark, S_IRUGO, + bmc150_accel_get_fifo_watermark, NULL, 0); + +static const struct attribute *bmc150_accel_fifo_attributes[] = { + &iio_const_attr_hwfifo_watermark_min.dev_attr.attr, + &iio_const_attr_hwfifo_watermark_max.dev_attr.attr, + &iio_dev_attr_hwfifo_watermark.dev_attr.attr, + &iio_dev_attr_hwfifo_enabled.dev_attr.attr, + NULL, +}; + +static int bmc150_accel_set_watermark(struct iio_dev *indio_dev, unsigned val) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + if (val > BMC150_ACCEL_FIFO_LENGTH) + val = BMC150_ACCEL_FIFO_LENGTH; + + mutex_lock(&data->mutex); + data->watermark = val; + mutex_unlock(&data->mutex); + + return 0; +} + +/* + * We must read at least one full frame in one burst, otherwise the rest of the + * frame data is discarded. + */ +static int bmc150_accel_fifo_transfer(const struct i2c_client *client, + char *buffer, int samples) +{ + int sample_length = 3 * 2; + u8 reg_fifo_data = BMC150_ACCEL_REG_FIFO_DATA; + int ret = -EIO; + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = 0, + .buf = ®_fifo_data, + .len = sizeof(reg_fifo_data), + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .buf = (u8 *)buffer, + .len = samples * sample_length, + } + }; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret != 2) + ret = -EIO; + else + ret = 0; + } else { + int i, step = I2C_SMBUS_BLOCK_MAX / sample_length; + + for (i = 0; i < samples * sample_length; i += step) { + ret = i2c_smbus_read_i2c_block_data(client, + reg_fifo_data, step, + &buffer[i]); + if (ret != step) { + ret = -EIO; + break; + } + + ret = 0; + } + } + + if (ret) + dev_err(&client->dev, "Error transferring data from fifo\n"); + + return ret; +} + +static int __bmc150_accel_fifo_flush(struct iio_dev *indio_dev, + unsigned samples, bool irq) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret, i; + u8 count; + u16 buffer[BMC150_ACCEL_FIFO_LENGTH * 3]; + int64_t tstamp; + uint64_t sample_period; + ret = i2c_smbus_read_byte_data(data->client, + BMC150_ACCEL_REG_FIFO_STATUS); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_fifo_status\n"); + return ret; + } + + count = ret & 0x7F; + + if (!count) + return 0; + + /* + * If we getting called from IRQ handler we know the stored timestamp is + * fairly accurate for the last stored sample. Otherwise, if we are + * called as a result of a read operation from userspace and hence + * before the watermark interrupt was triggered, take a timestamp + * now. We can fall anywhere in between two samples so the error in this + * case is at most one sample period. + */ + if (!irq) { + data->old_timestamp = data->timestamp; + data->timestamp = iio_get_time_ns(); + } + + /* + * Approximate timestamps for each of the sample based on the sampling + * frequency, timestamp for last sample and number of samples. + * + * Note that we can't use the current bandwidth settings to compute the + * sample period because the sample rate varies with the device + * (e.g. between 31.70ms to 32.20ms for a bandwidth of 15.63HZ). That + * small variation adds when we store a large number of samples and + * creates significant jitter between the last and first samples in + * different batches (e.g. 32ms vs 21ms). + * + * To avoid this issue we compute the actual sample period ourselves + * based on the timestamp delta between the last two flush operations. + */ + sample_period = (data->timestamp - data->old_timestamp); + do_div(sample_period, count); + tstamp = data->timestamp - (count - 1) * sample_period; + + if (samples && count > samples) + count = samples; + + ret = bmc150_accel_fifo_transfer(data->client, (u8 *)buffer, count); + if (ret) + return ret; + + /* + * Ideally we want the IIO core to handle the demux when running in fifo + * mode but not when running in triggered buffer mode. Unfortunately + * this does not seem to be possible, so stick with driver demux for + * now. + */ + for (i = 0; i < count; i++) { + u16 sample[8]; + int j, bit; + + j = 0; + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) + memcpy(&sample[j++], &buffer[i * 3 + bit], 2); + + iio_push_to_buffers_with_timestamp(indio_dev, sample, tstamp); + + tstamp += sample_period; + } + + return count; +} + +static int bmc150_accel_fifo_flush(struct iio_dev *indio_dev, unsigned samples) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = __bmc150_accel_fifo_flush(indio_dev, samples, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "15.620000 31.260000 62.50000 125 250 500 1000 2000"); + +static struct attribute *bmc150_accel_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bmc150_accel_attrs_group = { + .attrs = bmc150_accel_attributes, +}; + +static const struct iio_event_spec bmc150_accel_event = { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD) +}; + +#define BMC150_ACCEL_CHANNEL(_axis, bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 16 - (bits), \ + }, \ + .event_spec = &bmc150_accel_event, \ + .num_event_specs = 1 \ +} + +#define BMC150_ACCEL_CHANNELS(bits) { \ + { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = -1, \ + }, \ + BMC150_ACCEL_CHANNEL(X, bits), \ + BMC150_ACCEL_CHANNEL(Y, bits), \ + BMC150_ACCEL_CHANNEL(Z, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(3), \ +} + +static const struct iio_chan_spec bma222e_accel_channels[] = + BMC150_ACCEL_CHANNELS(8); +static const struct iio_chan_spec bma250e_accel_channels[] = + BMC150_ACCEL_CHANNELS(10); +static const struct iio_chan_spec bmc150_accel_channels[] = + BMC150_ACCEL_CHANNELS(12); +static const struct iio_chan_spec bma280_accel_channels[] = + BMC150_ACCEL_CHANNELS(14); + +enum { + bmc150, + bmi055, + bma255, + bma250e, + bma222e, + bma280, +}; + +static const struct bmc150_accel_chip_info bmc150_accel_chip_info_tbl[] = { + [bmc150] = { + .chip_id = 0xFA, + .channels = bmc150_accel_channels, + .num_channels = ARRAY_SIZE(bmc150_accel_channels), + .scale_table = { {9610, BMC150_ACCEL_DEF_RANGE_2G}, + {19122, BMC150_ACCEL_DEF_RANGE_4G}, + {38344, BMC150_ACCEL_DEF_RANGE_8G}, + {76590, BMC150_ACCEL_DEF_RANGE_16G} }, + }, + [bmi055] = { + .chip_id = 0xFA, + .channels = bmc150_accel_channels, + .num_channels = ARRAY_SIZE(bmc150_accel_channels), + .scale_table = { {9610, BMC150_ACCEL_DEF_RANGE_2G}, + {19122, BMC150_ACCEL_DEF_RANGE_4G}, + {38344, BMC150_ACCEL_DEF_RANGE_8G}, + {76590, BMC150_ACCEL_DEF_RANGE_16G} }, + }, + [bma255] = { + .chip_id = 0xFA, + .channels = bmc150_accel_channels, + .num_channels = ARRAY_SIZE(bmc150_accel_channels), + .scale_table = { {9610, BMC150_ACCEL_DEF_RANGE_2G}, + {19122, BMC150_ACCEL_DEF_RANGE_4G}, + {38344, BMC150_ACCEL_DEF_RANGE_8G}, + {76590, BMC150_ACCEL_DEF_RANGE_16G} }, + }, + [bma250e] = { + .chip_id = 0xF9, + .channels = bma250e_accel_channels, + .num_channels = ARRAY_SIZE(bma250e_accel_channels), + .scale_table = { {38344, BMC150_ACCEL_DEF_RANGE_2G}, + {76590, BMC150_ACCEL_DEF_RANGE_4G}, + {153277, BMC150_ACCEL_DEF_RANGE_8G}, + {306457, BMC150_ACCEL_DEF_RANGE_16G} }, + }, + [bma222e] = { + .chip_id = 0xF8, + .channels = bma222e_accel_channels, + .num_channels = ARRAY_SIZE(bma222e_accel_channels), + .scale_table = { {153277, BMC150_ACCEL_DEF_RANGE_2G}, + {306457, BMC150_ACCEL_DEF_RANGE_4G}, + {612915, BMC150_ACCEL_DEF_RANGE_8G}, + {1225831, BMC150_ACCEL_DEF_RANGE_16G} }, + }, + [bma280] = { + .chip_id = 0xFB, + .channels = bma280_accel_channels, + .num_channels = ARRAY_SIZE(bma280_accel_channels), + .scale_table = { {2392, BMC150_ACCEL_DEF_RANGE_2G}, + {4785, BMC150_ACCEL_DEF_RANGE_4G}, + {9581, BMC150_ACCEL_DEF_RANGE_8G}, + {19152, BMC150_ACCEL_DEF_RANGE_16G} }, + }, +}; + +static const struct iio_info bmc150_accel_info = { + .attrs = &bmc150_accel_attrs_group, + .read_raw = bmc150_accel_read_raw, + .write_raw = bmc150_accel_write_raw, + .read_event_value = bmc150_accel_read_event, + .write_event_value = bmc150_accel_write_event, + .write_event_config = bmc150_accel_write_event_config, + .read_event_config = bmc150_accel_read_event_config, + .validate_trigger = bmc150_accel_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info bmc150_accel_info_fifo = { + .attrs = &bmc150_accel_attrs_group, + .read_raw = bmc150_accel_read_raw, + .write_raw = bmc150_accel_write_raw, + .read_event_value = bmc150_accel_read_event, + .write_event_value = bmc150_accel_write_event, + .write_event_config = bmc150_accel_write_event_config, + .read_event_config = bmc150_accel_read_event_config, + .validate_trigger = bmc150_accel_validate_trigger, + .hwfifo_set_watermark = bmc150_accel_set_watermark, + .hwfifo_flush_to_buffer = bmc150_accel_fifo_flush, + .driver_module = THIS_MODULE, +}; + +static irqreturn_t bmc150_accel_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmc150_accel_data *data = iio_priv(indio_dev); + int bit, ret, i = 0; + + mutex_lock(&data->mutex); + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = i2c_smbus_read_word_data(data->client, + BMC150_ACCEL_AXIS_TO_REG(bit)); + if (ret < 0) { + mutex_unlock(&data->mutex); + goto err_read; + } + data->buffer[i++] = ret; + } + mutex_unlock(&data->mutex); + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + data->timestamp); +err_read: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bmc150_accel_trig_try_reen(struct iio_trigger *trig) +{ + struct bmc150_accel_trigger *t = iio_trigger_get_drvdata(trig); + struct bmc150_accel_data *data = t->data; + int ret; + + /* new data interrupts don't need ack */ + if (t == &t->data->triggers[BMC150_ACCEL_TRIGGER_DATA_READY]) + return 0; + + mutex_lock(&data->mutex); + /* clear any latched interrupt */ + ret = i2c_smbus_write_byte_data(data->client, + BMC150_ACCEL_REG_INT_RST_LATCH, + BMC150_ACCEL_INT_MODE_LATCH_INT | + BMC150_ACCEL_INT_MODE_LATCH_RESET); + mutex_unlock(&data->mutex); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_int_rst_latch\n"); + return ret; + } + + return 0; +} + +static int bmc150_accel_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bmc150_accel_trigger *t = iio_trigger_get_drvdata(trig); + struct bmc150_accel_data *data = t->data; + int ret; + + mutex_lock(&data->mutex); + + if (t->enabled == state) { + mutex_unlock(&data->mutex); + return 0; + } + + if (t->setup) { + ret = t->setup(t, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + } + + ret = bmc150_accel_set_interrupt(data, t->intr, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + t->enabled = state; + + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_trigger_ops bmc150_accel_trigger_ops = { + .set_trigger_state = bmc150_accel_trigger_set_state, + .try_reenable = bmc150_accel_trig_try_reen, + .owner = THIS_MODULE, +}; + +static int bmc150_accel_handle_roc_event(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int dir; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, + BMC150_ACCEL_REG_INT_STATUS_2); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_status_2\n"); + return ret; + } + + if (ret & BMC150_ACCEL_ANY_MOTION_BIT_SIGN) + dir = IIO_EV_DIR_FALLING; + else + dir = IIO_EV_DIR_RISING; + + if (ret & BMC150_ACCEL_ANY_MOTION_BIT_X) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_ROC, + dir), + data->timestamp); + if (ret & BMC150_ACCEL_ANY_MOTION_BIT_Y) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_ROC, + dir), + data->timestamp); + if (ret & BMC150_ACCEL_ANY_MOTION_BIT_Z) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_ROC, + dir), + data->timestamp); + return ret; +} + +static irqreturn_t bmc150_accel_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmc150_accel_data *data = iio_priv(indio_dev); + bool ack = false; + int ret; + + mutex_lock(&data->mutex); + + if (data->fifo_mode) { + ret = __bmc150_accel_fifo_flush(indio_dev, + BMC150_ACCEL_FIFO_LENGTH, true); + if (ret > 0) + ack = true; + } + + if (data->ev_enable_state) { + ret = bmc150_accel_handle_roc_event(indio_dev); + if (ret > 0) + ack = true; + } + + if (ack) { + ret = i2c_smbus_write_byte_data(data->client, + BMC150_ACCEL_REG_INT_RST_LATCH, + BMC150_ACCEL_INT_MODE_LATCH_INT | + BMC150_ACCEL_INT_MODE_LATCH_RESET); + if (ret) + dev_err(&data->client->dev, "Error writing reg_int_rst_latch\n"); + ret = IRQ_HANDLED; + } else { + ret = IRQ_NONE; + } + + mutex_unlock(&data->mutex); + + return ret; +} + +static irqreturn_t bmc150_accel_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmc150_accel_data *data = iio_priv(indio_dev); + bool ack = false; + int i; + + data->old_timestamp = data->timestamp; + data->timestamp = iio_get_time_ns(); + + for (i = 0; i < BMC150_ACCEL_TRIGGERS; i++) { + if (data->triggers[i].enabled) { + iio_trigger_poll(data->triggers[i].indio_trig); + ack = true; + break; + } + } + + if (data->ev_enable_state || data->fifo_mode) + return IRQ_WAKE_THREAD; + + if (ack) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static const char *bmc150_accel_match_acpi_device(struct device *dev, int *data) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + + if (!id) + return NULL; + + *data = (int) id->driver_data; + + return dev_name(dev); +} + +static int bmc150_accel_gpio_probe(struct i2c_client *client, + struct bmc150_accel_data *data) +{ + struct device *dev; + struct gpio_desc *gpio; + int ret; + + if (!client) + return -EINVAL; + + dev = &client->dev; + + /* data ready gpio interrupt pin */ + gpio = devm_gpiod_get_index(dev, BMC150_ACCEL_GPIO_NAME, 0, GPIOD_IN); + if (IS_ERR(gpio)) { + dev_err(dev, "Failed: gpio get index\n"); + return PTR_ERR(gpio); + } + + ret = gpiod_to_irq(gpio); + + dev_dbg(dev, "GPIO resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret); + + return ret; +} + +static const struct { + int intr; + const char *name; + int (*setup)(struct bmc150_accel_trigger *t, bool state); +} bmc150_accel_triggers[BMC150_ACCEL_TRIGGERS] = { + { + .intr = 0, + .name = "%s-dev%d", + }, + { + .intr = 1, + .name = "%s-any-motion-dev%d", + .setup = bmc150_accel_any_motion_setup, + }, +}; + +static void bmc150_accel_unregister_triggers(struct bmc150_accel_data *data, + int from) +{ + int i; + + for (i = from; i >= 0; i--) { + if (data->triggers[i].indio_trig) { + iio_trigger_unregister(data->triggers[i].indio_trig); + data->triggers[i].indio_trig = NULL; + } + } +} + +static int bmc150_accel_triggers_setup(struct iio_dev *indio_dev, + struct bmc150_accel_data *data) +{ + int i, ret; + + for (i = 0; i < BMC150_ACCEL_TRIGGERS; i++) { + struct bmc150_accel_trigger *t = &data->triggers[i]; + + t->indio_trig = devm_iio_trigger_alloc(&data->client->dev, + bmc150_accel_triggers[i].name, + indio_dev->name, + indio_dev->id); + if (!t->indio_trig) { + ret = -ENOMEM; + break; + } + + t->indio_trig->dev.parent = &data->client->dev; + t->indio_trig->ops = &bmc150_accel_trigger_ops; + t->intr = bmc150_accel_triggers[i].intr; + t->data = data; + t->setup = bmc150_accel_triggers[i].setup; + iio_trigger_set_drvdata(t->indio_trig, t); + + ret = iio_trigger_register(t->indio_trig); + if (ret) + break; + } + + if (ret) + bmc150_accel_unregister_triggers(data, i - 1); + + return ret; +} + +#define BMC150_ACCEL_FIFO_MODE_STREAM 0x80 +#define BMC150_ACCEL_FIFO_MODE_FIFO 0x40 +#define BMC150_ACCEL_FIFO_MODE_BYPASS 0x00 + +static int bmc150_accel_fifo_set_mode(struct bmc150_accel_data *data) +{ + u8 reg = BMC150_ACCEL_REG_FIFO_CONFIG1; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, reg, data->fifo_mode); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_fifo_config1\n"); + return ret; + } + + if (!data->fifo_mode) + return 0; + + ret = i2c_smbus_write_byte_data(data->client, + BMC150_ACCEL_REG_FIFO_CONFIG0, + data->watermark); + if (ret < 0) + dev_err(&data->client->dev, "Error writing reg_fifo_config0\n"); + + return ret; +} + +static int bmc150_accel_buffer_postenable(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret = 0; + + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) + return iio_triggered_buffer_postenable(indio_dev); + + mutex_lock(&data->mutex); + + if (!data->watermark) + goto out; + + ret = bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_WATERMARK, + true); + if (ret) + goto out; + + data->fifo_mode = BMC150_ACCEL_FIFO_MODE_FIFO; + + ret = bmc150_accel_fifo_set_mode(data); + if (ret) { + data->fifo_mode = 0; + bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_WATERMARK, + false); + } + +out: + mutex_unlock(&data->mutex); + + return ret; +} + +static int bmc150_accel_buffer_predisable(struct iio_dev *indio_dev) +{ + struct bmc150_accel_data *data = iio_priv(indio_dev); + + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) + return iio_triggered_buffer_predisable(indio_dev); + + mutex_lock(&data->mutex); + + if (!data->fifo_mode) + goto out; + + bmc150_accel_set_interrupt(data, BMC150_ACCEL_INT_WATERMARK, false); + __bmc150_accel_fifo_flush(indio_dev, BMC150_ACCEL_FIFO_LENGTH, false); + data->fifo_mode = 0; + bmc150_accel_fifo_set_mode(data); + +out: + mutex_unlock(&data->mutex); + + return 0; +} + +static const struct iio_buffer_setup_ops bmc150_accel_buffer_ops = { + .postenable = bmc150_accel_buffer_postenable, + .predisable = bmc150_accel_buffer_predisable, +}; + +static int bmc150_accel_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bmc150_accel_data *data; + struct iio_dev *indio_dev; + int ret; + const char *name = NULL; + int chip_id = 0; + + 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; + + if (id) { + name = id->name; + chip_id = id->driver_data; + } + + if (ACPI_HANDLE(&client->dev)) + name = bmc150_accel_match_acpi_device(&client->dev, &chip_id); + + data->chip_info = &bmc150_accel_chip_info_tbl[chip_id]; + + ret = bmc150_accel_chip_init(data); + if (ret < 0) + return ret; + + mutex_init(&data->mutex); + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = data->chip_info->channels; + indio_dev->num_channels = data->chip_info->num_channels; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmc150_accel_info; + + if (client->irq < 0) + client->irq = bmc150_accel_gpio_probe(client, data); + + if (client->irq >= 0) { + ret = devm_request_threaded_irq( + &client->dev, client->irq, + bmc150_accel_irq_handler, + bmc150_accel_irq_thread_handler, + IRQF_TRIGGER_RISING, + BMC150_ACCEL_IRQ_NAME, + indio_dev); + if (ret) + return ret; + + /* + * Set latched mode interrupt. While certain interrupts are + * non-latched regardless of this settings (e.g. new data) we + * want to use latch mode when we can to prevent interrupt + * flooding. + */ + ret = i2c_smbus_write_byte_data(data->client, + BMC150_ACCEL_REG_INT_RST_LATCH, + BMC150_ACCEL_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_rst_latch\n"); + return ret; + } + + bmc150_accel_interrupts_setup(indio_dev, data); + + ret = bmc150_accel_triggers_setup(indio_dev, data); + if (ret) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, + &iio_pollfunc_store_time, + bmc150_accel_trigger_handler, + &bmc150_accel_buffer_ops); + if (ret < 0) { + dev_err(&client->dev, + "Failed: iio triggered buffer setup\n"); + goto err_trigger_unregister; + } + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C) || + i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + indio_dev->modes |= INDIO_BUFFER_SOFTWARE; + indio_dev->info = &bmc150_accel_info_fifo; + indio_dev->buffer->attrs = bmc150_accel_fifo_attributes; + } + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "Unable to register iio device\n"); + goto err_buffer_cleanup; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret) + goto err_iio_unregister; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + BMC150_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + return 0; + +err_iio_unregister: + iio_device_unregister(indio_dev); +err_buffer_cleanup: + if (indio_dev->pollfunc) + iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: + bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1); + + return ret; +} + +static int bmc150_accel_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bmc150_accel_data *data = iio_priv(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + iio_device_unregister(indio_dev); + + bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1); + + mutex_lock(&data->mutex); + bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_DEEP_SUSPEND, 0); + mutex_unlock(&data->mutex); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bmc150_accel_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bmc150_accel_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); + mutex_unlock(&data->mutex); + + return 0; +} + +static int bmc150_accel_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bmc150_accel_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + if (atomic_read(&data->active_intr)) + bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); + bmc150_accel_fifo_set_mode(data); + mutex_unlock(&data->mutex); + + return 0; +} +#endif + +#ifdef CONFIG_PM +static int bmc150_accel_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + + dev_dbg(&data->client->dev, __func__); + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); + if (ret < 0) + return -EAGAIN; + + return 0; +} + +static int bmc150_accel_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bmc150_accel_data *data = iio_priv(indio_dev); + int ret; + int sleep_val; + + dev_dbg(&data->client->dev, __func__); + + ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); + if (ret < 0) + return ret; + ret = bmc150_accel_fifo_set_mode(data); + if (ret < 0) + return ret; + + sleep_val = bmc150_accel_get_startup_times(data); + if (sleep_val < 20) + usleep_range(sleep_val * 1000, 20000); + else + msleep_interruptible(sleep_val); + + return 0; +} +#endif + +static const struct dev_pm_ops bmc150_accel_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(bmc150_accel_suspend, bmc150_accel_resume) + SET_RUNTIME_PM_OPS(bmc150_accel_runtime_suspend, + bmc150_accel_runtime_resume, NULL) +}; + +static const struct acpi_device_id bmc150_accel_acpi_match[] = { + {"BSBA0150", bmc150}, + {"BMC150A", bmc150}, + {"BMI055A", bmi055}, + {"BMA0255", bma255}, + {"BMA250E", bma250e}, + {"BMA222E", bma222e}, + {"BMA0280", bma280}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, bmc150_accel_acpi_match); + +static const struct i2c_device_id bmc150_accel_id[] = { + {"bmc150_accel", bmc150}, + {"bmi055_accel", bmi055}, + {"bma255", bma255}, + {"bma250e", bma250e}, + {"bma222e", bma222e}, + {"bma280", bma280}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, bmc150_accel_id); + +static struct i2c_driver bmc150_accel_driver = { + .driver = { + .name = BMC150_ACCEL_DRV_NAME, + .acpi_match_table = ACPI_PTR(bmc150_accel_acpi_match), + .pm = &bmc150_accel_pm_ops, + }, + .probe = bmc150_accel_probe, + .remove = bmc150_accel_remove, + .id_table = bmc150_accel_id, +}; +module_i2c_driver(bmc150_accel_driver); + +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMC150 accelerometer driver"); diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c new file mode 100644 index 000000000..2b4fad699 --- /dev/null +++ b/drivers/iio/accel/hid-sensor-accel-3d.c @@ -0,0 +1,425 @@ +/* + * HID Sensors Driver + * Copyright (c) 2012, 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; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum accel_3d_channel { + CHANNEL_SCAN_INDEX_X, + CHANNEL_SCAN_INDEX_Y, + CHANNEL_SCAN_INDEX_Z, + ACCEL_3D_CHANNEL_MAX, +}; + +struct accel_3d_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info accel[ACCEL_3D_CHANNEL_MAX]; + u32 accel_val[ACCEL_3D_CHANNEL_MAX]; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +static const u32 accel_3d_addresses[ACCEL_3D_CHANNEL_MAX] = { + HID_USAGE_SENSOR_ACCEL_X_AXIS, + HID_USAGE_SENSOR_ACCEL_Y_AXIS, + HID_USAGE_SENSOR_ACCEL_Z_AXIS +}; + +/* Channel definitions */ +static const struct iio_chan_spec accel_3d_channels[] = { + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .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), + .scan_index = CHANNEL_SCAN_INDEX_X, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .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), + .scan_index = CHANNEL_SCAN_INDEX_Y, + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .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), + .scan_index = CHANNEL_SCAN_INDEX_Z, + } +}; + +/* Adjust channel real bits based on report descriptor */ +static void accel_3d_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 u32 */ + channels[channel].scan_type.storagebits = sizeof(u32) * 8; +} + +/* Channel read_raw handler */ +static int accel_3d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct accel_3d_state *accel_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + + *val = 0; + *val2 = 0; + switch (mask) { + case 0: + hid_sensor_power_state(&accel_state->common_attributes, true); + report_id = accel_state->accel[chan->scan_index].report_id; + address = accel_3d_addresses[chan->scan_index]; + if (report_id >= 0) + *val = sensor_hub_input_attr_get_raw_value( + accel_state->common_attributes.hsdev, + HID_USAGE_SENSOR_ACCEL_3D, address, + report_id, + SENSOR_HUB_SYNC); + else { + *val = 0; + hid_sensor_power_state(&accel_state->common_attributes, + false); + return -EINVAL; + } + hid_sensor_power_state(&accel_state->common_attributes, false); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = accel_state->scale_pre_decml; + *val2 = accel_state->scale_post_decml; + ret_type = accel_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = accel_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &accel_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &accel_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int accel_3d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct accel_3d_state *accel_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &accel_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &accel_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info accel_3d_info = { + .driver_module = THIS_MODULE, + .read_raw = &accel_3d_read_raw, + .write_raw = &accel_3d_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data, + int len) +{ + dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); + iio_push_to_buffers(indio_dev, data); +} + +/* Callback handler to send event after all samples are received and captured */ +static int accel_3d_proc_event(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct accel_3d_state *accel_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "accel_3d_proc_event\n"); + if (atomic_read(&accel_state->common_attributes.data_ready)) + hid_sensor_push_data(indio_dev, + accel_state->accel_val, + sizeof(accel_state->accel_val)); + + return 0; +} + +/* Capture samples in local storage */ +static int accel_3d_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct accel_3d_state *accel_state = iio_priv(indio_dev); + int offset; + int ret = -EINVAL; + + switch (usage_id) { + case HID_USAGE_SENSOR_ACCEL_X_AXIS: + case HID_USAGE_SENSOR_ACCEL_Y_AXIS: + case HID_USAGE_SENSOR_ACCEL_Z_AXIS: + offset = usage_id - HID_USAGE_SENSOR_ACCEL_X_AXIS; + accel_state->accel_val[CHANNEL_SCAN_INDEX_X + offset] = + *(u32 *)raw_data; + ret = 0; + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int accel_3d_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + unsigned usage_id, + struct accel_3d_state *st) +{ + int ret; + int i; + + for (i = 0; i <= CHANNEL_SCAN_INDEX_Z; ++i) { + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ACCEL_X_AXIS + i, + &st->accel[CHANNEL_SCAN_INDEX_X + i]); + if (ret < 0) + break; + accel_3d_adjust_channel_bit_mask(channels, + CHANNEL_SCAN_INDEX_X + i, + st->accel[CHANNEL_SCAN_INDEX_X + i].size); + } + dev_dbg(&pdev->dev, "accel_3d %x:%x, %x:%x, %x:%x\n", + st->accel[0].index, + st->accel[0].report_id, + st->accel[1].index, st->accel[1].report_id, + st->accel[2].index, st->accel[2].report_id); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_ACCEL_3D, + &st->accel[CHANNEL_SCAN_INDEX_X], + &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_ACCELERATION, + &st->common_attributes.sensitivity); + dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", + st->common_attributes.sensitivity.index, + st->common_attributes.sensitivity.report_id); + } + + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_accel_3d_probe(struct platform_device *pdev) +{ + int ret = 0; + static const char *name = "accel_3d"; + struct iio_dev *indio_dev; + struct accel_3d_state *accel_state; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_chan_spec *channels; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct accel_3d_state)); + if (indio_dev == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + + accel_state = iio_priv(indio_dev); + accel_state->common_attributes.hsdev = hsdev; + accel_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_ACCEL_3D, + &accel_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + channels = kmemdup(accel_3d_channels, sizeof(accel_3d_channels), + GFP_KERNEL); + if (!channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = accel_3d_parse_report(pdev, hsdev, channels, + HID_USAGE_SENSOR_ACCEL_3D, accel_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + goto error_free_dev_mem; + } + + indio_dev->channels = channels; + indio_dev->num_channels = ARRAY_SIZE(accel_3d_channels); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &accel_3d_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to initialize trigger buffer\n"); + goto error_free_dev_mem; + } + atomic_set(&accel_state->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, name, + &accel_state->common_attributes); + if (ret < 0) { + dev_err(&pdev->dev, "trigger setup failed\n"); + goto error_unreg_buffer_funcs; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_trigger; + } + + accel_state->callbacks.send_event = accel_3d_proc_event; + accel_state->callbacks.capture_sample = accel_3d_capture_sample; + accel_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_ACCEL_3D, + &accel_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_iio_unreg; + } + + return ret; + +error_iio_unreg: + iio_device_unregister(indio_dev); +error_remove_trigger: + hid_sensor_remove_trigger(&accel_state->common_attributes); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: + kfree(indio_dev->channels); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_accel_3d_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct accel_3d_state *accel_state = iio_priv(indio_dev); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ACCEL_3D); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(&accel_state->common_attributes); + iio_triggered_buffer_cleanup(indio_dev); + kfree(indio_dev->channels); + + return 0; +} + +static struct platform_device_id hid_accel_3d_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200073", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_accel_3d_ids); + +static struct platform_driver hid_accel_3d_platform_driver = { + .id_table = hid_accel_3d_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_accel_3d_probe, + .remove = hid_accel_3d_remove, +}; +module_platform_driver(hid_accel_3d_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Accel 3D"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c new file mode 100644 index 000000000..5b7a860df --- /dev/null +++ b/drivers/iio/accel/kxcjk-1013.c @@ -0,0 +1,1452 @@ +/* + * KXCJK-1013 3-axis accelerometer driver + * Copyright (c) 2014, 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. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/acpi.h> +#include <linux/gpio/consumer.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/events.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/accel/kxcjk_1013.h> + +#define KXCJK1013_DRV_NAME "kxcjk1013" +#define KXCJK1013_IRQ_NAME "kxcjk1013_event" + +#define KXCJK1013_REG_XOUT_L 0x06 +/* + * From low byte X axis register, all the other addresses of Y and Z can be + * obtained by just applying axis offset. The following axis defines are just + * provide clarity, but not used. + */ +#define KXCJK1013_REG_XOUT_H 0x07 +#define KXCJK1013_REG_YOUT_L 0x08 +#define KXCJK1013_REG_YOUT_H 0x09 +#define KXCJK1013_REG_ZOUT_L 0x0A +#define KXCJK1013_REG_ZOUT_H 0x0B + +#define KXCJK1013_REG_DCST_RESP 0x0C +#define KXCJK1013_REG_WHO_AM_I 0x0F +#define KXCJK1013_REG_INT_SRC1 0x16 +#define KXCJK1013_REG_INT_SRC2 0x17 +#define KXCJK1013_REG_STATUS_REG 0x18 +#define KXCJK1013_REG_INT_REL 0x1A +#define KXCJK1013_REG_CTRL1 0x1B +#define KXCJK1013_REG_CTRL2 0x1D +#define KXCJK1013_REG_INT_CTRL1 0x1E +#define KXCJK1013_REG_INT_CTRL2 0x1F +#define KXCJK1013_REG_DATA_CTRL 0x21 +#define KXCJK1013_REG_WAKE_TIMER 0x29 +#define KXCJK1013_REG_SELF_TEST 0x3A +#define KXCJK1013_REG_WAKE_THRES 0x6A + +#define KXCJK1013_REG_CTRL1_BIT_PC1 BIT(7) +#define KXCJK1013_REG_CTRL1_BIT_RES BIT(6) +#define KXCJK1013_REG_CTRL1_BIT_DRDY BIT(5) +#define KXCJK1013_REG_CTRL1_BIT_GSEL1 BIT(4) +#define KXCJK1013_REG_CTRL1_BIT_GSEL0 BIT(3) +#define KXCJK1013_REG_CTRL1_BIT_WUFE BIT(1) +#define KXCJK1013_REG_INT_REG1_BIT_IEA BIT(4) +#define KXCJK1013_REG_INT_REG1_BIT_IEN BIT(5) + +#define KXCJK1013_DATA_MASK_12_BIT 0x0FFF +#define KXCJK1013_MAX_STARTUP_TIME_US 100000 + +#define KXCJK1013_SLEEP_DELAY_MS 2000 + +#define KXCJK1013_REG_INT_SRC2_BIT_ZP BIT(0) +#define KXCJK1013_REG_INT_SRC2_BIT_ZN BIT(1) +#define KXCJK1013_REG_INT_SRC2_BIT_YP BIT(2) +#define KXCJK1013_REG_INT_SRC2_BIT_YN BIT(3) +#define KXCJK1013_REG_INT_SRC2_BIT_XP BIT(4) +#define KXCJK1013_REG_INT_SRC2_BIT_XN BIT(5) + +#define KXCJK1013_DEFAULT_WAKE_THRES 1 + +enum kx_chipset { + KXCJK1013, + KXCJ91008, + KXTJ21009, + KX_MAX_CHIPS /* this must be last */ +}; + +struct kxcjk1013_data { + struct i2c_client *client; + struct iio_trigger *dready_trig; + struct iio_trigger *motion_trig; + struct mutex mutex; + s16 buffer[8]; + u8 odr_bits; + u8 range; + int wake_thres; + int wake_dur; + bool active_high_intr; + bool dready_trigger_on; + int ev_enable_state; + bool motion_trigger_on; + int64_t timestamp; + enum kx_chipset chipset; + bool is_smo8500_device; +}; + +enum kxcjk1013_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + +enum kxcjk1013_mode { + STANDBY, + OPERATION, +}; + +enum kxcjk1013_range { + KXCJK1013_RANGE_2G, + KXCJK1013_RANGE_4G, + KXCJK1013_RANGE_8G, +}; + +static const struct { + int val; + int val2; + int odr_bits; +} samp_freq_table[] = { {0, 781000, 0x08}, {1, 563000, 0x09}, + {3, 125000, 0x0A}, {6, 250000, 0x0B}, {12, 500000, 0}, + {25, 0, 0x01}, {50, 0, 0x02}, {100, 0, 0x03}, + {200, 0, 0x04}, {400, 0, 0x05}, {800, 0, 0x06}, + {1600, 0, 0x07} }; + +/* Refer to section 4 of the specification */ +static const struct { + int odr_bits; + int usec; +} odr_start_up_times[KX_MAX_CHIPS][12] = { + /* KXCJK-1013 */ + { + {0x08, 100000}, + {0x09, 100000}, + {0x0A, 100000}, + {0x0B, 100000}, + {0, 80000}, + {0x01, 41000}, + {0x02, 21000}, + {0x03, 11000}, + {0x04, 6400}, + {0x05, 3900}, + {0x06, 2700}, + {0x07, 2100}, + }, + /* KXCJ9-1008 */ + { + {0x08, 100000}, + {0x09, 100000}, + {0x0A, 100000}, + {0x0B, 100000}, + {0, 80000}, + {0x01, 41000}, + {0x02, 21000}, + {0x03, 11000}, + {0x04, 6400}, + {0x05, 3900}, + {0x06, 2700}, + {0x07, 2100}, + }, + /* KXCTJ2-1009 */ + { + {0x08, 1240000}, + {0x09, 621000}, + {0x0A, 309000}, + {0x0B, 151000}, + {0, 80000}, + {0x01, 41000}, + {0x02, 21000}, + {0x03, 11000}, + {0x04, 6000}, + {0x05, 4000}, + {0x06, 3000}, + {0x07, 2000}, + }, +}; + +static const struct { + u16 scale; + u8 gsel_0; + u8 gsel_1; +} KXCJK1013_scale_table[] = { {9582, 0, 0}, + {19163, 1, 0}, + {38326, 0, 1} }; + +static const struct { + int val; + int val2; + int odr_bits; +} wake_odr_data_rate_table[] = { {0, 781000, 0x00}, + {1, 563000, 0x01}, + {3, 125000, 0x02}, + {6, 250000, 0x03}, + {12, 500000, 0x04}, + {25, 0, 0x05}, + {50, 0, 0x06}, + {100, 0, 0x06}, + {200, 0, 0x06}, + {400, 0, 0x06}, + {800, 0, 0x06}, + {1600, 0, 0x06} }; + +static int kxcjk1013_set_mode(struct kxcjk1013_data *data, + enum kxcjk1013_mode mode) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (mode == STANDBY) + ret &= ~KXCJK1013_REG_CTRL1_BIT_PC1; + else + ret |= KXCJK1013_REG_CTRL1_BIT_PC1; + + ret = i2c_smbus_write_byte_data(data->client, + KXCJK1013_REG_CTRL1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + return 0; +} + +static int kxcjk1013_get_mode(struct kxcjk1013_data *data, + enum kxcjk1013_mode *mode) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (ret & KXCJK1013_REG_CTRL1_BIT_PC1) + *mode = OPERATION; + else + *mode = STANDBY; + + return 0; +} + +static int kxcjk1013_set_range(struct kxcjk1013_data *data, int range_index) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + ret &= ~(KXCJK1013_REG_CTRL1_BIT_GSEL0 | + KXCJK1013_REG_CTRL1_BIT_GSEL1); + ret |= (KXCJK1013_scale_table[range_index].gsel_0 << 3); + ret |= (KXCJK1013_scale_table[range_index].gsel_1 << 4); + + ret = i2c_smbus_write_byte_data(data->client, + KXCJK1013_REG_CTRL1, + ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + data->range = range_index; + + return 0; +} + +static int kxcjk1013_chip_init(struct kxcjk1013_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_WHO_AM_I); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading who_am_i\n"); + return ret; + } + + dev_dbg(&data->client->dev, "KXCJK1013 Chip Id %x\n", ret); + + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + /* Set 12 bit mode */ + ret |= KXCJK1013_REG_CTRL1_BIT_RES; + + ret = i2c_smbus_write_byte_data(data->client, KXCJK1013_REG_CTRL1, + ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl\n"); + return ret; + } + + /* Setting range to 4G */ + ret = kxcjk1013_set_range(data, KXCJK1013_RANGE_4G); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_DATA_CTRL); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_data_ctrl\n"); + return ret; + } + + data->odr_bits = ret; + + /* Set up INT polarity */ + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_INT_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_ctrl1\n"); + return ret; + } + + if (data->active_high_intr) + ret |= KXCJK1013_REG_INT_REG1_BIT_IEA; + else + ret &= ~KXCJK1013_REG_INT_REG1_BIT_IEA; + + ret = i2c_smbus_write_byte_data(data->client, KXCJK1013_REG_INT_CTRL1, + ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_ctrl1\n"); + return ret; + } + + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret < 0) + return ret; + + data->wake_thres = KXCJK1013_DEFAULT_WAKE_THRES; + + return 0; +} + +#ifdef CONFIG_PM +static int kxcjk1013_get_startup_times(struct kxcjk1013_data *data) +{ + int i; + int idx = data->chipset; + + for (i = 0; i < ARRAY_SIZE(odr_start_up_times[idx]); ++i) { + if (odr_start_up_times[idx][i].odr_bits == data->odr_bits) + return odr_start_up_times[idx][i].usec; + } + + return KXCJK1013_MAX_STARTUP_TIME_US; +} +#endif + +static int kxcjk1013_set_power_state(struct kxcjk1013_data *data, bool on) +{ +#ifdef CONFIG_PM + int ret; + + if (on) + ret = pm_runtime_get_sync(&data->client->dev); + else { + pm_runtime_mark_last_busy(&data->client->dev); + ret = pm_runtime_put_autosuspend(&data->client->dev); + } + if (ret < 0) { + dev_err(&data->client->dev, + "Failed: kxcjk1013_set_power_state for %d\n", on); + if (on) + pm_runtime_put_noidle(&data->client->dev); + return ret; + } +#endif + + return 0; +} + +static int kxcjk1013_chip_update_thresholds(struct kxcjk1013_data *data) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, + KXCJK1013_REG_WAKE_TIMER, + data->wake_dur); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_wake_timer\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(data->client, + KXCJK1013_REG_WAKE_THRES, + data->wake_thres); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_wake_thres\n"); + return ret; + } + + return 0; +} + +static int kxcjk1013_setup_any_motion_interrupt(struct kxcjk1013_data *data, + bool status) +{ + int ret; + enum kxcjk1013_mode store_mode; + + ret = kxcjk1013_get_mode(data, &store_mode); + if (ret < 0) + return ret; + + /* This is requirement by spec to change state to STANDBY */ + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) + return ret; + + ret = kxcjk1013_chip_update_thresholds(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_INT_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_ctrl1\n"); + return ret; + } + + if (status) + ret |= KXCJK1013_REG_INT_REG1_BIT_IEN; + else + ret &= ~KXCJK1013_REG_INT_REG1_BIT_IEN; + + ret = i2c_smbus_write_byte_data(data->client, KXCJK1013_REG_INT_CTRL1, + ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_ctrl1\n"); + return ret; + } + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (status) + ret |= KXCJK1013_REG_CTRL1_BIT_WUFE; + else + ret &= ~KXCJK1013_REG_CTRL1_BIT_WUFE; + + ret = i2c_smbus_write_byte_data(data->client, + KXCJK1013_REG_CTRL1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + if (store_mode == OPERATION) { + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret < 0) + return ret; + } + + return 0; +} + +static int kxcjk1013_setup_new_data_interrupt(struct kxcjk1013_data *data, + bool status) +{ + int ret; + enum kxcjk1013_mode store_mode; + + ret = kxcjk1013_get_mode(data, &store_mode); + if (ret < 0) + return ret; + + /* This is requirement by spec to change state to STANDBY */ + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_INT_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_ctrl1\n"); + return ret; + } + + if (status) + ret |= KXCJK1013_REG_INT_REG1_BIT_IEN; + else + ret &= ~KXCJK1013_REG_INT_REG1_BIT_IEN; + + ret = i2c_smbus_write_byte_data(data->client, KXCJK1013_REG_INT_CTRL1, + ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_ctrl1\n"); + return ret; + } + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (status) + ret |= KXCJK1013_REG_CTRL1_BIT_DRDY; + else + ret &= ~KXCJK1013_REG_CTRL1_BIT_DRDY; + + ret = i2c_smbus_write_byte_data(data->client, + KXCJK1013_REG_CTRL1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + if (store_mode == OPERATION) { + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret < 0) + return ret; + } + + return 0; +} + +static int kxcjk1013_convert_freq_to_bit(int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(samp_freq_table); ++i) { + if (samp_freq_table[i].val == val && + samp_freq_table[i].val2 == val2) { + return samp_freq_table[i].odr_bits; + } + } + + return -EINVAL; +} + +static int kxcjk1013_convert_wake_odr_to_bit(int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wake_odr_data_rate_table); ++i) { + if (wake_odr_data_rate_table[i].val == val && + wake_odr_data_rate_table[i].val2 == val2) { + return wake_odr_data_rate_table[i].odr_bits; + } + } + + return -EINVAL; +} + +static int kxcjk1013_set_odr(struct kxcjk1013_data *data, int val, int val2) +{ + int ret; + int odr_bits; + enum kxcjk1013_mode store_mode; + + ret = kxcjk1013_get_mode(data, &store_mode); + if (ret < 0) + return ret; + + odr_bits = kxcjk1013_convert_freq_to_bit(val, val2); + if (odr_bits < 0) + return odr_bits; + + /* To change ODR, the chip must be set to STANDBY as per spec */ + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, KXCJK1013_REG_DATA_CTRL, + odr_bits); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing data_ctrl\n"); + return ret; + } + + data->odr_bits = odr_bits; + + odr_bits = kxcjk1013_convert_wake_odr_to_bit(val, val2); + if (odr_bits < 0) + return odr_bits; + + ret = i2c_smbus_write_byte_data(data->client, KXCJK1013_REG_CTRL2, + odr_bits); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl2\n"); + return ret; + } + + if (store_mode == OPERATION) { + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret < 0) + return ret; + } + + return 0; +} + +static int kxcjk1013_get_odr(struct kxcjk1013_data *data, int *val, int *val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(samp_freq_table); ++i) { + if (samp_freq_table[i].odr_bits == data->odr_bits) { + *val = samp_freq_table[i].val; + *val2 = samp_freq_table[i].val2; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +static int kxcjk1013_get_acc_reg(struct kxcjk1013_data *data, int axis) +{ + u8 reg = KXCJK1013_REG_XOUT_L + axis * 2; + int ret; + + ret = i2c_smbus_read_word_data(data->client, reg); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to read accel_%c registers\n", 'x' + axis); + return ret; + } + + return ret; +} + +static int kxcjk1013_set_scale(struct kxcjk1013_data *data, int val) +{ + int ret, i; + enum kxcjk1013_mode store_mode; + + + for (i = 0; i < ARRAY_SIZE(KXCJK1013_scale_table); ++i) { + if (KXCJK1013_scale_table[i].scale == val) { + + ret = kxcjk1013_get_mode(data, &store_mode); + if (ret < 0) + return ret; + + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) + return ret; + + ret = kxcjk1013_set_range(data, i); + if (ret < 0) + return ret; + + if (store_mode == OPERATION) { + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret) + return ret; + } + + return 0; + } + } + + return -EINVAL; +} + +static int kxcjk1013_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->mutex); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else { + ret = kxcjk1013_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + ret = kxcjk1013_get_acc_reg(data, chan->scan_index); + if (ret < 0) { + kxcjk1013_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + *val = sign_extend32(ret >> 4, 11); + ret = kxcjk1013_set_power_state(data, false); + } + mutex_unlock(&data->mutex); + + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = KXCJK1013_scale_table[data->range].scale; + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + ret = kxcjk1013_get_odr(data, val, val2); + mutex_unlock(&data->mutex); + return ret; + + default: + return -EINVAL; + } +} + +static int kxcjk1013_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + ret = kxcjk1013_set_odr(data, val, val2); + mutex_unlock(&data->mutex); + break; + case IIO_CHAN_INFO_SCALE: + if (val) + return -EINVAL; + + mutex_lock(&data->mutex); + ret = kxcjk1013_set_scale(data, val2); + mutex_unlock(&data->mutex); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int kxcjk1013_read_event(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 kxcjk1013_data *data = iio_priv(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + *val = data->wake_thres; + break; + case IIO_EV_INFO_PERIOD: + *val = data->wake_dur; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int kxcjk1013_write_event(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 kxcjk1013_data *data = iio_priv(indio_dev); + + if (data->ev_enable_state) + return -EBUSY; + + switch (info) { + case IIO_EV_INFO_VALUE: + data->wake_thres = val; + break; + case IIO_EV_INFO_PERIOD: + data->wake_dur = val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int kxcjk1013_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 kxcjk1013_data *data = iio_priv(indio_dev); + + return data->ev_enable_state; +} + +static int kxcjk1013_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 kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + if (state && data->ev_enable_state) + return 0; + + mutex_lock(&data->mutex); + + if (!state && data->motion_trigger_on) { + data->ev_enable_state = 0; + mutex_unlock(&data->mutex); + return 0; + } + + /* + * We will expect the enable and disable to do operation in + * in reverse order. This will happen here anyway as our + * resume operation uses sync mode runtime pm calls, the + * suspend operation will be delayed by autosuspend delay + * So the disable operation will still happen in reverse of + * enable operation. When runtime pm is disabled the mode + * is always on so sequence doesn't matter + */ + ret = kxcjk1013_set_power_state(data, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = kxcjk1013_setup_any_motion_interrupt(data, state); + if (ret < 0) { + kxcjk1013_set_power_state(data, false); + data->ev_enable_state = 0; + mutex_unlock(&data->mutex); + return ret; + } + + data->ev_enable_state = state; + mutex_unlock(&data->mutex); + + return 0; +} + +static int kxcjk1013_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct kxcjk1013_data *data = iio_priv(indio_dev); + + if (data->dready_trig != trig && data->motion_trig != trig) + return -EINVAL; + + return 0; +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "0.781000 1.563000 3.125000 6.250000 12.500000 25 50 100 200 400 800 1600"); + +static IIO_CONST_ATTR(in_accel_scale_available, "0.009582 0.019163 0.038326"); + +static struct attribute *kxcjk1013_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_accel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group kxcjk1013_attrs_group = { + .attrs = kxcjk1013_attributes, +}; + +static const struct iio_event_spec kxcjk1013_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD) +}; + +#define KXCJK1013_CHANNEL(_axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_CPU, \ + }, \ + .event_spec = &kxcjk1013_event, \ + .num_event_specs = 1 \ +} + +static const struct iio_chan_spec kxcjk1013_channels[] = { + KXCJK1013_CHANNEL(X), + KXCJK1013_CHANNEL(Y), + KXCJK1013_CHANNEL(Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_info kxcjk1013_info = { + .attrs = &kxcjk1013_attrs_group, + .read_raw = kxcjk1013_read_raw, + .write_raw = kxcjk1013_write_raw, + .read_event_value = kxcjk1013_read_event, + .write_event_value = kxcjk1013_write_event, + .write_event_config = kxcjk1013_write_event_config, + .read_event_config = kxcjk1013_read_event_config, + .validate_trigger = kxcjk1013_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static irqreturn_t kxcjk1013_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct kxcjk1013_data *data = iio_priv(indio_dev); + int bit, ret, i = 0; + + mutex_lock(&data->mutex); + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = kxcjk1013_get_acc_reg(data, bit); + if (ret < 0) { + mutex_unlock(&data->mutex); + goto err; + } + data->buffer[i++] = ret; + } + mutex_unlock(&data->mutex); + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + data->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int kxcjk1013_trig_try_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_INT_REL); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_rel\n"); + return ret; + } + + return 0; +} + +static int kxcjk1013_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + + if (!state && data->ev_enable_state && data->motion_trigger_on) { + data->motion_trigger_on = false; + mutex_unlock(&data->mutex); + return 0; + } + + ret = kxcjk1013_set_power_state(data, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + if (data->motion_trig == trig) + ret = kxcjk1013_setup_any_motion_interrupt(data, state); + else + ret = kxcjk1013_setup_new_data_interrupt(data, state); + if (ret < 0) { + kxcjk1013_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + if (data->motion_trig == trig) + data->motion_trigger_on = state; + else + data->dready_trigger_on = state; + + mutex_unlock(&data->mutex); + + return 0; +} + +static const struct iio_trigger_ops kxcjk1013_trigger_ops = { + .set_trigger_state = kxcjk1013_data_rdy_trigger_set_state, + .try_reenable = kxcjk1013_trig_try_reen, + .owner = THIS_MODULE, +}; + +static irqreturn_t kxcjk1013_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_INT_SRC1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_src1\n"); + goto ack_intr; + } + + if (ret & 0x02) { + ret = i2c_smbus_read_byte_data(data->client, + KXCJK1013_REG_INT_SRC2); + if (ret < 0) { + dev_err(&data->client->dev, + "Error reading reg_int_src2\n"); + goto ack_intr; + } + + if (ret & KXCJK1013_REG_INT_SRC2_BIT_XN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + data->timestamp); + if (ret & KXCJK1013_REG_INT_SRC2_BIT_XP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + data->timestamp); + + + if (ret & KXCJK1013_REG_INT_SRC2_BIT_YN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + data->timestamp); + if (ret & KXCJK1013_REG_INT_SRC2_BIT_YP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + data->timestamp); + + if (ret & KXCJK1013_REG_INT_SRC2_BIT_ZN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + data->timestamp); + if (ret & KXCJK1013_REG_INT_SRC2_BIT_ZP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + data->timestamp); + } + +ack_intr: + if (data->dready_trigger_on) + return IRQ_HANDLED; + + ret = i2c_smbus_read_byte_data(data->client, KXCJK1013_REG_INT_REL); + if (ret < 0) + dev_err(&data->client->dev, "Error reading reg_int_rel\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t kxcjk1013_data_rdy_trig_poll(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct kxcjk1013_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(); + + if (data->dready_trigger_on) + iio_trigger_poll(data->dready_trig); + else if (data->motion_trigger_on) + iio_trigger_poll(data->motion_trig); + + if (data->ev_enable_state) + return IRQ_WAKE_THREAD; + else + return IRQ_HANDLED; +} + +static const char *kxcjk1013_match_acpi_device(struct device *dev, + enum kx_chipset *chipset, + bool *is_smo8500_device) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + if (strcmp(id->id, "SMO8500") == 0) + *is_smo8500_device = true; + *chipset = (enum kx_chipset)id->driver_data; + + return dev_name(dev); +} + +static int kxcjk1013_gpio_probe(struct i2c_client *client, + struct kxcjk1013_data *data) +{ + struct device *dev; + struct gpio_desc *gpio; + int ret; + + if (!client) + return -EINVAL; + if (data->is_smo8500_device) + return -ENOTSUPP; + + dev = &client->dev; + + /* data ready gpio interrupt pin */ + gpio = devm_gpiod_get_index(dev, "kxcjk1013_int", 0, GPIOD_IN); + if (IS_ERR(gpio)) { + dev_err(dev, "acpi gpio get index failed\n"); + return PTR_ERR(gpio); + } + + ret = gpiod_to_irq(gpio); + + dev_dbg(dev, "GPIO resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret); + + return ret; +} + +static int kxcjk1013_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct kxcjk1013_data *data; + struct iio_dev *indio_dev; + struct kxcjk_1013_platform_data *pdata; + const char *name; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + pdata = dev_get_platdata(&client->dev); + if (pdata) + data->active_high_intr = pdata->active_high_intr; + else + data->active_high_intr = true; /* default polarity */ + + if (id) { + data->chipset = (enum kx_chipset)(id->driver_data); + name = id->name; + } else if (ACPI_HANDLE(&client->dev)) { + name = kxcjk1013_match_acpi_device(&client->dev, + &data->chipset, + &data->is_smo8500_device); + } else + return -ENODEV; + + ret = kxcjk1013_chip_init(data); + if (ret < 0) + return ret; + + mutex_init(&data->mutex); + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = kxcjk1013_channels; + indio_dev->num_channels = ARRAY_SIZE(kxcjk1013_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &kxcjk1013_info; + + if (client->irq < 0) + client->irq = kxcjk1013_gpio_probe(client, data); + + if (client->irq >= 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + kxcjk1013_data_rdy_trig_poll, + kxcjk1013_event_handler, + IRQF_TRIGGER_RISING, + KXCJK1013_IRQ_NAME, + indio_dev); + if (ret) + goto err_poweroff; + + data->dready_trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", + indio_dev->name, + indio_dev->id); + if (!data->dready_trig) { + ret = -ENOMEM; + goto err_poweroff; + } + + data->motion_trig = devm_iio_trigger_alloc(&client->dev, + "%s-any-motion-dev%d", + indio_dev->name, + indio_dev->id); + if (!data->motion_trig) { + ret = -ENOMEM; + goto err_poweroff; + } + + data->dready_trig->dev.parent = &client->dev; + data->dready_trig->ops = &kxcjk1013_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + indio_dev->trig = data->dready_trig; + iio_trigger_get(indio_dev->trig); + ret = iio_trigger_register(data->dready_trig); + if (ret) + goto err_poweroff; + + data->motion_trig->dev.parent = &client->dev; + data->motion_trig->ops = &kxcjk1013_trigger_ops; + iio_trigger_set_drvdata(data->motion_trig, indio_dev); + ret = iio_trigger_register(data->motion_trig); + if (ret) { + data->motion_trig = NULL; + goto err_trigger_unregister; + } + + ret = iio_triggered_buffer_setup(indio_dev, + &iio_pollfunc_store_time, + kxcjk1013_trigger_handler, + NULL); + if (ret < 0) { + dev_err(&client->dev, + "iio triggered buffer setup failed\n"); + goto err_trigger_unregister; + } + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret) + goto err_iio_unregister; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + KXCJK1013_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + return 0; + +err_iio_unregister: + iio_device_unregister(indio_dev); +err_buffer_cleanup: + if (data->dready_trig) + iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: + if (data->dready_trig) + iio_trigger_unregister(data->dready_trig); + if (data->motion_trig) + iio_trigger_unregister(data->motion_trig); +err_poweroff: + kxcjk1013_set_mode(data, STANDBY); + + return ret; +} + +static int kxcjk1013_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct kxcjk1013_data *data = iio_priv(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + iio_device_unregister(indio_dev); + + if (data->dready_trig) { + iio_triggered_buffer_cleanup(indio_dev); + iio_trigger_unregister(data->dready_trig); + iio_trigger_unregister(data->motion_trig); + } + + mutex_lock(&data->mutex); + kxcjk1013_set_mode(data, STANDBY); + mutex_unlock(&data->mutex); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int kxcjk1013_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = kxcjk1013_set_mode(data, STANDBY); + mutex_unlock(&data->mutex); + + return ret; +} + +static int kxcjk1013_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&data->mutex); + ret = kxcjk1013_set_mode(data, OPERATION); + mutex_unlock(&data->mutex); + + return ret; +} +#endif + +#ifdef CONFIG_PM +static int kxcjk1013_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + + ret = kxcjk1013_set_mode(data, STANDBY); + if (ret < 0) { + dev_err(&data->client->dev, "powering off device failed\n"); + return -EAGAIN; + } + return 0; +} + +static int kxcjk1013_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct kxcjk1013_data *data = iio_priv(indio_dev); + int ret; + int sleep_val; + + ret = kxcjk1013_set_mode(data, OPERATION); + if (ret < 0) + return ret; + + sleep_val = kxcjk1013_get_startup_times(data); + if (sleep_val < 20000) + usleep_range(sleep_val, 20000); + else + msleep_interruptible(sleep_val/1000); + + return 0; +} +#endif + +static const struct dev_pm_ops kxcjk1013_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(kxcjk1013_suspend, kxcjk1013_resume) + SET_RUNTIME_PM_OPS(kxcjk1013_runtime_suspend, + kxcjk1013_runtime_resume, NULL) +}; + +static const struct acpi_device_id kx_acpi_match[] = { + {"KXCJ1013", KXCJK1013}, + {"KXCJ1008", KXCJ91008}, + {"KXCJ9000", KXCJ91008}, + {"KXTJ1009", KXTJ21009}, + {"SMO8500", KXCJ91008}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, kx_acpi_match); + +static const struct i2c_device_id kxcjk1013_id[] = { + {"kxcjk1013", KXCJK1013}, + {"kxcj91008", KXCJ91008}, + {"kxtj21009", KXTJ21009}, + {"SMO8500", KXCJ91008}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, kxcjk1013_id); + +static struct i2c_driver kxcjk1013_driver = { + .driver = { + .name = KXCJK1013_DRV_NAME, + .acpi_match_table = ACPI_PTR(kx_acpi_match), + .pm = &kxcjk1013_pm_ops, + }, + .probe = kxcjk1013_probe, + .remove = kxcjk1013_remove, + .id_table = kxcjk1013_id, +}; +module_i2c_driver(kxcjk1013_driver); + +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("KXCJK1013 accelerometer driver"); diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c new file mode 100644 index 000000000..98ba761cb --- /dev/null +++ b/drivers/iio/accel/kxsd9.c @@ -0,0 +1,276 @@ +/* + * kxsd9.c simple support for the Kionix KXSD9 3D + * accelerometer. + * + * Copyright (c) 2008-2009 Jonathan Cameron <jic23@kernel.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The i2c interface is very similar, so shouldn't be a problem once + * I have a suitable wire made up. + * + * TODO: Support the motion detector + * Uses register address incrementing so could have a + * heavily optimized ring buffer access function. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define KXSD9_REG_X 0x00 +#define KXSD9_REG_Y 0x02 +#define KXSD9_REG_Z 0x04 +#define KXSD9_REG_AUX 0x06 +#define KXSD9_REG_RESET 0x0a +#define KXSD9_REG_CTRL_C 0x0c + +#define KXSD9_FS_MASK 0x03 + +#define KXSD9_REG_CTRL_B 0x0d +#define KXSD9_REG_CTRL_A 0x0e + +#define KXSD9_READ(a) (0x80 | (a)) +#define KXSD9_WRITE(a) (a) + +#define KXSD9_STATE_RX_SIZE 2 +#define KXSD9_STATE_TX_SIZE 2 +/** + * struct kxsd9_state - device related storage + * @buf_lock: protect the rx and tx buffers. + * @us: spi device + * @rx: single rx buffer storage + * @tx: single tx buffer storage + **/ +struct kxsd9_state { + struct mutex buf_lock; + struct spi_device *us; + u8 rx[KXSD9_STATE_RX_SIZE] ____cacheline_aligned; + u8 tx[KXSD9_STATE_TX_SIZE]; +}; + +#define KXSD9_SCALE_2G "0.011978" +#define KXSD9_SCALE_4G "0.023927" +#define KXSD9_SCALE_6G "0.035934" +#define KXSD9_SCALE_8G "0.047853" + +/* reverse order */ +static const int kxsd9_micro_scales[4] = { 47853, 35934, 23927, 11978 }; + +static int kxsd9_write_scale(struct iio_dev *indio_dev, int micro) +{ + int ret, i; + struct kxsd9_state *st = iio_priv(indio_dev); + bool foundit = false; + + for (i = 0; i < 4; i++) + if (micro == kxsd9_micro_scales[i]) { + foundit = true; + break; + } + if (!foundit) + return -EINVAL; + + mutex_lock(&st->buf_lock); + ret = spi_w8r8(st->us, KXSD9_READ(KXSD9_REG_CTRL_C)); + if (ret) + goto error_ret; + st->tx[0] = KXSD9_WRITE(KXSD9_REG_CTRL_C); + st->tx[1] = (ret & ~KXSD9_FS_MASK) | i; + + ret = spi_write(st->us, st->tx, 2); +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int kxsd9_read(struct iio_dev *indio_dev, u8 address) +{ + int ret; + struct kxsd9_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .bits_per_word = 8, + .len = 1, + .delay_usecs = 200, + .tx_buf = st->tx, + }, { + .bits_per_word = 8, + .len = 2, + .rx_buf = st->rx, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = KXSD9_READ(address); + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (!ret) + ret = (((u16)(st->rx[0])) << 8) | (st->rx[1] & 0xF0); + mutex_unlock(&st->buf_lock); + return ret; +} + +static IIO_CONST_ATTR(accel_scale_available, + KXSD9_SCALE_2G " " + KXSD9_SCALE_4G " " + KXSD9_SCALE_6G " " + KXSD9_SCALE_8G); + +static struct attribute *kxsd9_attributes[] = { + &iio_const_attr_accel_scale_available.dev_attr.attr, + NULL, +}; + +static int kxsd9_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret = -EINVAL; + + if (mask == IIO_CHAN_INFO_SCALE) { + /* Check no integer component */ + if (val) + return -EINVAL; + ret = kxsd9_write_scale(indio_dev, val2); + } + + return ret; +} + +static int kxsd9_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret = -EINVAL; + struct kxsd9_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = kxsd9_read(indio_dev, chan->address); + if (ret < 0) + goto error_ret; + *val = ret; + break; + case IIO_CHAN_INFO_SCALE: + ret = spi_w8r8(st->us, KXSD9_READ(KXSD9_REG_CTRL_C)); + if (ret) + goto error_ret; + *val2 = kxsd9_micro_scales[ret & KXSD9_FS_MASK]; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + } + +error_ret: + return ret; +}; +#define KXSD9_ACCEL_CHAN(axis) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = KXSD9_REG_##axis, \ + } + +static const struct iio_chan_spec kxsd9_channels[] = { + KXSD9_ACCEL_CHAN(X), KXSD9_ACCEL_CHAN(Y), KXSD9_ACCEL_CHAN(Z), + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .indexed = 1, + .address = KXSD9_REG_AUX, + } +}; + +static const struct attribute_group kxsd9_attribute_group = { + .attrs = kxsd9_attributes, +}; + +static int kxsd9_power_up(struct kxsd9_state *st) +{ + int ret; + + st->tx[0] = 0x0d; + st->tx[1] = 0x40; + ret = spi_write(st->us, st->tx, 2); + if (ret) + return ret; + + st->tx[0] = 0x0c; + st->tx[1] = 0x9b; + return spi_write(st->us, st->tx, 2); +}; + +static const struct iio_info kxsd9_info = { + .read_raw = &kxsd9_read_raw, + .write_raw = &kxsd9_write_raw, + .attrs = &kxsd9_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int kxsd9_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct kxsd9_state *st; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->us = spi; + mutex_init(&st->buf_lock); + indio_dev->channels = kxsd9_channels; + indio_dev->num_channels = ARRAY_SIZE(kxsd9_channels); + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &kxsd9_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + spi->mode = SPI_MODE_0; + spi_setup(spi); + kxsd9_power_up(st); + + return iio_device_register(indio_dev); +} + +static int kxsd9_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id kxsd9_id[] = { + {"kxsd9", 0}, + { }, +}; +MODULE_DEVICE_TABLE(spi, kxsd9_id); + +static struct spi_driver kxsd9_driver = { + .driver = { + .name = "kxsd9", + .owner = THIS_MODULE, + }, + .probe = kxsd9_probe, + .remove = kxsd9_remove, + .id_table = kxsd9_id, +}; +module_spi_driver(kxsd9_driver); + +MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); +MODULE_DESCRIPTION("Kionix KXSD9 SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c new file mode 100644 index 000000000..5b8065788 --- /dev/null +++ b/drivers/iio/accel/mma8452.c @@ -0,0 +1,451 @@ +/* + * mma8452.c - Support for Freescale MMA8452Q 3-axis 12-bit accelerometer + * + * Copyright 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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 0x1c/0x1d (pin selectable) + * + * TODO: interrupt, thresholding, orientation / freefall events, autosleep + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/delay.h> + +#define MMA8452_STATUS 0x00 +#define MMA8452_OUT_X 0x01 /* MSB first, 12-bit */ +#define MMA8452_OUT_Y 0x03 +#define MMA8452_OUT_Z 0x05 +#define MMA8452_WHO_AM_I 0x0d +#define MMA8452_DATA_CFG 0x0e +#define MMA8452_OFF_X 0x2f +#define MMA8452_OFF_Y 0x30 +#define MMA8452_OFF_Z 0x31 +#define MMA8452_CTRL_REG1 0x2a +#define MMA8452_CTRL_REG2 0x2b + +#define MMA8452_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0)) + +#define MMA8452_CTRL_DR_MASK (BIT(5) | BIT(4) | BIT(3)) +#define MMA8452_CTRL_DR_SHIFT 3 +#define MMA8452_CTRL_DR_DEFAULT 0x4 /* 50 Hz sample frequency */ +#define MMA8452_CTRL_ACTIVE BIT(0) + +#define MMA8452_DATA_CFG_FS_MASK (BIT(1) | BIT(0)) +#define MMA8452_DATA_CFG_FS_2G 0 +#define MMA8452_DATA_CFG_FS_4G 1 +#define MMA8452_DATA_CFG_FS_8G 2 + +#define MMA8452_DEVICE_ID 0x2a + +struct mma8452_data { + struct i2c_client *client; + struct mutex lock; + u8 ctrl_reg1; + u8 data_cfg; +}; + +static int mma8452_drdy(struct mma8452_data *data) +{ + int tries = 150; + + while (tries-- > 0) { + int ret = i2c_smbus_read_byte_data(data->client, + MMA8452_STATUS); + if (ret < 0) + return ret; + if ((ret & MMA8452_STATUS_DRDY) == MMA8452_STATUS_DRDY) + return 0; + msleep(20); + } + + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; +} + +static int mma8452_read(struct mma8452_data *data, __be16 buf[3]) +{ + int ret = mma8452_drdy(data); + if (ret < 0) + return ret; + return i2c_smbus_read_i2c_block_data(data->client, + MMA8452_OUT_X, 3 * sizeof(__be16), (u8 *) buf); +} + +static ssize_t mma8452_show_int_plus_micros(char *buf, + const int (*vals)[2], int n) +{ + size_t len = 0; + + while (n-- > 0) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%d.%06d ", vals[n][0], vals[n][1]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static int mma8452_get_int_plus_micros_index(const int (*vals)[2], int n, + int val, int val2) +{ + while (n-- > 0) + if (val == vals[n][0] && val2 == vals[n][1]) + return n; + + return -EINVAL; +} + +static const int mma8452_samp_freq[8][2] = { + {800, 0}, {400, 0}, {200, 0}, {100, 0}, {50, 0}, {12, 500000}, + {6, 250000}, {1, 560000} +}; + +/* + * Hardware has fullscale of -2G, -4G, -8G corresponding to raw value -2048 + * The userspace interface uses m/s^2 and we declare micro units + * So scale factor is given by: + * g * N * 1000000 / 2048 for N = 2, 4, 8 and g=9.80665 + */ +static const int mma8452_scales[3][2] = { + {0, 9577}, {0, 19154}, {0, 38307} +}; + +static ssize_t mma8452_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return mma8452_show_int_plus_micros(buf, mma8452_samp_freq, + ARRAY_SIZE(mma8452_samp_freq)); +} + +static ssize_t mma8452_show_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return mma8452_show_int_plus_micros(buf, mma8452_scales, + ARRAY_SIZE(mma8452_scales)); +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(mma8452_show_samp_freq_avail); +static IIO_DEVICE_ATTR(in_accel_scale_available, S_IRUGO, + mma8452_show_scale_avail, NULL, 0); + +static int mma8452_get_samp_freq_index(struct mma8452_data *data, + int val, int val2) +{ + return mma8452_get_int_plus_micros_index(mma8452_samp_freq, + ARRAY_SIZE(mma8452_samp_freq), val, val2); +} + +static int mma8452_get_scale_index(struct mma8452_data *data, + int val, int val2) +{ + return mma8452_get_int_plus_micros_index(mma8452_scales, + ARRAY_SIZE(mma8452_scales), val, val2); +} + +static int mma8452_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma8452_data *data = iio_priv(indio_dev); + __be16 buffer[3]; + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + mutex_lock(&data->lock); + ret = mma8452_read(data, buffer); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = sign_extend32( + be16_to_cpu(buffer[chan->scan_index]) >> 4, 11); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + i = data->data_cfg & MMA8452_DATA_CFG_FS_MASK; + *val = mma8452_scales[i][0]; + *val2 = mma8452_scales[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + i = (data->ctrl_reg1 & MMA8452_CTRL_DR_MASK) >> + MMA8452_CTRL_DR_SHIFT; + *val = mma8452_samp_freq[i][0]; + *val2 = mma8452_samp_freq[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + ret = i2c_smbus_read_byte_data(data->client, MMA8452_OFF_X + + chan->scan_index); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 7); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int mma8452_standby(struct mma8452_data *data) +{ + return i2c_smbus_write_byte_data(data->client, MMA8452_CTRL_REG1, + data->ctrl_reg1 & ~MMA8452_CTRL_ACTIVE); +} + +static int mma8452_active(struct mma8452_data *data) +{ + return i2c_smbus_write_byte_data(data->client, MMA8452_CTRL_REG1, + data->ctrl_reg1); +} + +static int mma8452_change_config(struct mma8452_data *data, u8 reg, u8 val) +{ + int ret; + + mutex_lock(&data->lock); + + /* config can only be changed when in standby */ + ret = mma8452_standby(data); + if (ret < 0) + goto fail; + + ret = i2c_smbus_write_byte_data(data->client, reg, val); + if (ret < 0) + goto fail; + + ret = mma8452_active(data); + if (ret < 0) + goto fail; + + ret = 0; +fail: + mutex_unlock(&data->lock); + return ret; +} + +static int mma8452_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mma8452_data *data = iio_priv(indio_dev); + int i; + + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + i = mma8452_get_samp_freq_index(data, val, val2); + if (i < 0) + return -EINVAL; + + data->ctrl_reg1 &= ~MMA8452_CTRL_DR_MASK; + data->ctrl_reg1 |= i << MMA8452_CTRL_DR_SHIFT; + return mma8452_change_config(data, MMA8452_CTRL_REG1, + data->ctrl_reg1); + case IIO_CHAN_INFO_SCALE: + i = mma8452_get_scale_index(data, val, val2); + if (i < 0) + return -EINVAL; + data->data_cfg &= ~MMA8452_DATA_CFG_FS_MASK; + data->data_cfg |= i; + return mma8452_change_config(data, MMA8452_DATA_CFG, + data->data_cfg); + case IIO_CHAN_INFO_CALIBBIAS: + if (val < -128 || val > 127) + return -EINVAL; + return mma8452_change_config(data, MMA8452_OFF_X + + chan->scan_index, val); + default: + return -EINVAL; + } +} + +static irqreturn_t mma8452_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mma8452_data *data = iio_priv(indio_dev); + u8 buffer[16]; /* 3 16-bit channels + padding + ts */ + int ret; + + ret = mma8452_read(data, (__be16 *) buffer); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, buffer, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +#define MMA8452_CHANNEL(axis, idx) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec mma8452_channels[] = { + MMA8452_CHANNEL(X, 0), + MMA8452_CHANNEL(Y, 1), + MMA8452_CHANNEL(Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static struct attribute *mma8452_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_accel_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mma8452_group = { + .attrs = mma8452_attributes, +}; + +static const struct iio_info mma8452_info = { + .attrs = &mma8452_group, + .read_raw = &mma8452_read_raw, + .write_raw = &mma8452_write_raw, + .driver_module = THIS_MODULE, +}; + +static const unsigned long mma8452_scan_masks[] = {0x7, 0}; + +static int mma8452_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mma8452_data *data; + struct iio_dev *indio_dev; + int ret; + + ret = i2c_smbus_read_byte_data(client, MMA8452_WHO_AM_I); + if (ret < 0) + return ret; + if (ret != MMA8452_DEVICE_ID) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &mma8452_info; + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mma8452_channels; + indio_dev->num_channels = ARRAY_SIZE(mma8452_channels); + indio_dev->available_scan_masks = mma8452_scan_masks; + + data->ctrl_reg1 = MMA8452_CTRL_ACTIVE | + (MMA8452_CTRL_DR_DEFAULT << MMA8452_CTRL_DR_SHIFT); + ret = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG1, + data->ctrl_reg1); + if (ret < 0) + return ret; + + data->data_cfg = MMA8452_DATA_CFG_FS_2G; + ret = i2c_smbus_write_byte_data(client, MMA8452_DATA_CFG, + data->data_cfg); + if (ret < 0) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + mma8452_trigger_handler, NULL); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +static int mma8452_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + mma8452_standby(iio_priv(indio_dev)); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mma8452_suspend(struct device *dev) +{ + return mma8452_standby(iio_priv(i2c_get_clientdata( + to_i2c_client(dev)))); +} + +static int mma8452_resume(struct device *dev) +{ + return mma8452_active(iio_priv(i2c_get_clientdata( + to_i2c_client(dev)))); +} + +static SIMPLE_DEV_PM_OPS(mma8452_pm_ops, mma8452_suspend, mma8452_resume); +#define MMA8452_PM_OPS (&mma8452_pm_ops) +#else +#define MMA8452_PM_OPS NULL +#endif + +static const struct i2c_device_id mma8452_id[] = { + { "mma8452", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mma8452_id); + +static const struct of_device_id mma8452_dt_ids[] = { + { .compatible = "fsl,mma8452" }, + { } +}; + +static struct i2c_driver mma8452_driver = { + .driver = { + .name = "mma8452", + .of_match_table = of_match_ptr(mma8452_dt_ids), + .pm = MMA8452_PM_OPS, + }, + .probe = mma8452_probe, + .remove = mma8452_remove, + .id_table = mma8452_id, +}; +module_i2c_driver(mma8452_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Freescale MMA8452 accelerometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/mma9551.c b/drivers/iio/accel/mma9551.c new file mode 100644 index 000000000..7db7cc0bf --- /dev/null +++ b/drivers/iio/accel/mma9551.c @@ -0,0 +1,638 @@ +/* + * Freescale MMA9551L Intelligent Motion-Sensing Platform driver + * Copyright (c) 2014, 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. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/pm_runtime.h> +#include "mma9551_core.h" + +#define MMA9551_DRV_NAME "mma9551" +#define MMA9551_IRQ_NAME "mma9551_event" +#define MMA9551_GPIO_NAME "mma9551_int" +#define MMA9551_GPIO_COUNT 4 + +/* Tilt application (inclination in IIO terms). */ +#define MMA9551_TILT_XZ_ANG_REG 0x00 +#define MMA9551_TILT_YZ_ANG_REG 0x01 +#define MMA9551_TILT_XY_ANG_REG 0x02 +#define MMA9551_TILT_ANGFLG BIT(7) +#define MMA9551_TILT_QUAD_REG 0x03 +#define MMA9551_TILT_XY_QUAD_SHIFT 0 +#define MMA9551_TILT_YZ_QUAD_SHIFT 2 +#define MMA9551_TILT_XZ_QUAD_SHIFT 4 +#define MMA9551_TILT_CFG_REG 0x01 +#define MMA9551_TILT_ANG_THRESH_MASK GENMASK(3, 0) + +#define MMA9551_DEFAULT_SAMPLE_RATE 122 /* Hz */ + +/* Tilt events are mapped to the first three GPIO pins. */ +enum mma9551_tilt_axis { + mma9551_x = 0, + mma9551_y, + mma9551_z, +}; + +struct mma9551_data { + struct i2c_client *client; + struct mutex mutex; + int event_enabled[3]; + int irqs[MMA9551_GPIO_COUNT]; +}; + +static int mma9551_read_incli_chan(struct i2c_client *client, + const struct iio_chan_spec *chan, + int *val) +{ + u8 quad_shift, angle, quadrant; + u16 reg_addr; + int ret; + + switch (chan->channel2) { + case IIO_MOD_X: + reg_addr = MMA9551_TILT_YZ_ANG_REG; + quad_shift = MMA9551_TILT_YZ_QUAD_SHIFT; + break; + case IIO_MOD_Y: + reg_addr = MMA9551_TILT_XZ_ANG_REG; + quad_shift = MMA9551_TILT_XZ_QUAD_SHIFT; + break; + case IIO_MOD_Z: + reg_addr = MMA9551_TILT_XY_ANG_REG; + quad_shift = MMA9551_TILT_XY_QUAD_SHIFT; + break; + default: + return -EINVAL; + } + + ret = mma9551_set_power_state(client, true); + if (ret < 0) + return ret; + + ret = mma9551_read_status_byte(client, MMA9551_APPID_TILT, + reg_addr, &angle); + if (ret < 0) + goto out_poweroff; + + ret = mma9551_read_status_byte(client, MMA9551_APPID_TILT, + MMA9551_TILT_QUAD_REG, &quadrant); + if (ret < 0) + goto out_poweroff; + + angle &= ~MMA9551_TILT_ANGFLG; + quadrant = (quadrant >> quad_shift) & 0x03; + + if (quadrant == 1 || quadrant == 3) + *val = 90 * (quadrant + 1) - angle; + else + *val = angle + 90 * quadrant; + + ret = IIO_VAL_INT; + +out_poweroff: + mma9551_set_power_state(client, false); + return ret; +} + +static int mma9551_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_INCLI: + mutex_lock(&data->mutex); + ret = mma9551_read_incli_chan(data->client, chan, val); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_ACCEL: + mutex_lock(&data->mutex); + ret = mma9551_read_accel_chan(data->client, + chan, val, val2); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + return mma9551_read_accel_scale(val, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9551_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 mma9551_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_INCLI: + /* IIO counts axes from 1, because IIO_NO_MOD is 0. */ + return data->event_enabled[chan->channel2 - 1]; + default: + return -EINVAL; + } +} + +static int mma9551_config_incli_event(struct iio_dev *indio_dev, + enum iio_modifier axis, + int state) +{ + struct mma9551_data *data = iio_priv(indio_dev); + enum mma9551_tilt_axis mma_axis; + int ret; + + /* IIO counts axes from 1, because IIO_NO_MOD is 0. */ + mma_axis = axis - 1; + + if (data->event_enabled[mma_axis] == state) + return 0; + + if (state == 0) { + ret = mma9551_gpio_config(data->client, + (enum mma9551_gpio_pin)mma_axis, + MMA9551_APPID_NONE, 0, 0); + if (ret < 0) + return ret; + + ret = mma9551_set_power_state(data->client, false); + if (ret < 0) + return ret; + } else { + int bitnum; + + /* Bit 7 of each angle register holds the angle flag. */ + switch (axis) { + case IIO_MOD_X: + bitnum = 7 + 8 * MMA9551_TILT_YZ_ANG_REG; + break; + case IIO_MOD_Y: + bitnum = 7 + 8 * MMA9551_TILT_XZ_ANG_REG; + break; + case IIO_MOD_Z: + bitnum = 7 + 8 * MMA9551_TILT_XY_ANG_REG; + break; + default: + return -EINVAL; + } + + + ret = mma9551_set_power_state(data->client, true); + if (ret < 0) + return ret; + + ret = mma9551_gpio_config(data->client, + (enum mma9551_gpio_pin)mma_axis, + MMA9551_APPID_TILT, bitnum, 0); + if (ret < 0) { + mma9551_set_power_state(data->client, false); + return ret; + } + } + + data->event_enabled[mma_axis] = state; + + return ret; +} + +static int mma9551_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 mma9551_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_INCLI: + mutex_lock(&data->mutex); + ret = mma9551_config_incli_event(indio_dev, + chan->channel2, state); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static int mma9551_write_event_value(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 mma9551_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_INCLI: + if (val2 != 0 || val < 1 || val > 10) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9551_update_config_bits(data->client, + MMA9551_APPID_TILT, + MMA9551_TILT_CFG_REG, + MMA9551_TILT_ANG_THRESH_MASK, + val); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static int mma9551_read_event_value(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 mma9551_data *data = iio_priv(indio_dev); + int ret; + u8 tmp; + + switch (chan->type) { + case IIO_INCLI: + mutex_lock(&data->mutex); + ret = mma9551_read_config_byte(data->client, + MMA9551_APPID_TILT, + MMA9551_TILT_CFG_REG, &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = tmp & MMA9551_TILT_ANG_THRESH_MASK; + *val2 = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_event_spec mma9551_incli_event = { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), +}; + +#define MMA9551_INCLI_CHANNEL(axis) { \ + .type = IIO_INCLI, \ + .modified = 1, \ + .channel2 = axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .event_spec = &mma9551_incli_event, \ + .num_event_specs = 1, \ +} + +static const struct iio_chan_spec mma9551_channels[] = { + MMA9551_ACCEL_CHANNEL(IIO_MOD_X), + MMA9551_ACCEL_CHANNEL(IIO_MOD_Y), + MMA9551_ACCEL_CHANNEL(IIO_MOD_Z), + + MMA9551_INCLI_CHANNEL(IIO_MOD_X), + MMA9551_INCLI_CHANNEL(IIO_MOD_Y), + MMA9551_INCLI_CHANNEL(IIO_MOD_Z), +}; + +static const struct iio_info mma9551_info = { + .driver_module = THIS_MODULE, + .read_raw = mma9551_read_raw, + .read_event_config = mma9551_read_event_config, + .write_event_config = mma9551_write_event_config, + .read_event_value = mma9551_read_event_value, + .write_event_value = mma9551_write_event_value, +}; + +static irqreturn_t mma9551_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mma9551_data *data = iio_priv(indio_dev); + int i, ret, mma_axis = -1; + u16 reg; + u8 val; + + mutex_lock(&data->mutex); + + for (i = 0; i < 3; i++) + if (irq == data->irqs[i]) { + mma_axis = i; + break; + } + + if (mma_axis == -1) { + /* IRQ was triggered on 4th line, which we don't use. */ + dev_warn(&data->client->dev, + "irq triggered on unused line %d\n", data->irqs[3]); + goto out; + } + + switch (mma_axis) { + case mma9551_x: + reg = MMA9551_TILT_YZ_ANG_REG; + break; + case mma9551_y: + reg = MMA9551_TILT_XZ_ANG_REG; + break; + case mma9551_z: + reg = MMA9551_TILT_XY_ANG_REG; + break; + } + + /* + * Read the angle even though we don't use it, otherwise we + * won't get any further interrupts. + */ + ret = mma9551_read_status_byte(data->client, MMA9551_APPID_TILT, + reg, &val); + if (ret < 0) { + dev_err(&data->client->dev, + "error %d reading tilt register in IRQ\n", ret); + goto out; + } + + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_INCLI, 0, (mma_axis + 1), + IIO_EV_TYPE_ROC, IIO_EV_DIR_RISING), + iio_get_time_ns()); + +out: + mutex_unlock(&data->mutex); + + return IRQ_HANDLED; +} + +static int mma9551_init(struct mma9551_data *data) +{ + int ret; + + ret = mma9551_read_version(data->client); + if (ret) + return ret; + + return mma9551_set_device_state(data->client, true); +} + +static int mma9551_gpio_probe(struct iio_dev *indio_dev) +{ + struct gpio_desc *gpio; + int i, ret; + struct mma9551_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + for (i = 0; i < MMA9551_GPIO_COUNT; i++) { + gpio = devm_gpiod_get_index(dev, MMA9551_GPIO_NAME, i, + GPIOD_IN); + if (IS_ERR(gpio)) { + dev_err(dev, "acpi gpio get index failed\n"); + return PTR_ERR(gpio); + } + + ret = gpiod_to_irq(gpio); + if (ret < 0) + return ret; + + data->irqs[i] = ret; + ret = devm_request_threaded_irq(dev, data->irqs[i], + NULL, mma9551_event_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + MMA9551_IRQ_NAME, indio_dev); + if (ret < 0) { + dev_err(dev, "request irq %d failed\n", data->irqs[i]); + return ret; + } + + dev_dbg(dev, "gpio resource, no:%d irq:%d\n", + desc_to_gpio(gpio), data->irqs[i]); + } + + return 0; +} + +static const char *mma9551_match_acpi_device(struct device *dev) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + + return dev_name(dev); +} + +static int mma9551_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mma9551_data *data; + struct iio_dev *indio_dev; + const char *name = NULL; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + if (id) + name = id->name; + else if (ACPI_HANDLE(&client->dev)) + name = mma9551_match_acpi_device(&client->dev); + + ret = mma9551_init(data); + if (ret < 0) + return ret; + + mutex_init(&data->mutex); + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = mma9551_channels; + indio_dev->num_channels = ARRAY_SIZE(mma9551_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mma9551_info; + + ret = mma9551_gpio_probe(indio_dev); + if (ret < 0) + goto out_poweroff; + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto out_poweroff; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_iio_unregister; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + MMA9551_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + return 0; + +out_iio_unregister: + iio_device_unregister(indio_dev); +out_poweroff: + mma9551_set_device_state(client, false); + + return ret; +} + +static int mma9551_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mma9551_data *data = iio_priv(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + iio_device_unregister(indio_dev); + mutex_lock(&data->mutex); + mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + + return 0; +} + +#ifdef CONFIG_PM +static int mma9551_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + if (ret < 0) { + dev_err(&data->client->dev, "powering off device failed\n"); + return -EAGAIN; + } + + return 0; +} + +static int mma9551_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + ret = mma9551_set_device_state(data->client, true); + if (ret < 0) + return ret; + + mma9551_sleep(MMA9551_DEFAULT_SAMPLE_RATE); + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int mma9551_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static int mma9551_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9551_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, true); + mutex_unlock(&data->mutex); + + return ret; +} +#endif + +static const struct dev_pm_ops mma9551_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mma9551_suspend, mma9551_resume) + SET_RUNTIME_PM_OPS(mma9551_runtime_suspend, + mma9551_runtime_resume, NULL) +}; + +static const struct acpi_device_id mma9551_acpi_match[] = { + {"MMA9551", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(acpi, mma9551_acpi_match); + +static const struct i2c_device_id mma9551_id[] = { + {"mma9551", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, mma9551_id); + +static struct i2c_driver mma9551_driver = { + .driver = { + .name = MMA9551_DRV_NAME, + .acpi_match_table = ACPI_PTR(mma9551_acpi_match), + .pm = &mma9551_pm_ops, + }, + .probe = mma9551_probe, + .remove = mma9551_remove, + .id_table = mma9551_id, +}; + +module_i2c_driver(mma9551_driver); + +MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>"); +MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MMA9551L motion-sensing platform driver"); diff --git a/drivers/iio/accel/mma9551_core.c b/drivers/iio/accel/mma9551_core.c new file mode 100644 index 000000000..c6d5a3a40 --- /dev/null +++ b/drivers/iio/accel/mma9551_core.c @@ -0,0 +1,813 @@ +/* + * Common code for Freescale MMA955x Intelligent Sensor Platform drivers + * Copyright (c) 2014, 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. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/pm_runtime.h> +#include "mma9551_core.h" + +/* Command masks for mailbox write command */ +#define MMA9551_CMD_READ_VERSION_INFO 0x00 +#define MMA9551_CMD_READ_CONFIG 0x10 +#define MMA9551_CMD_WRITE_CONFIG 0x20 +#define MMA9551_CMD_READ_STATUS 0x30 + +/* Mailbox read command */ +#define MMA9551_RESPONSE_COCO BIT(7) + +/* Error-Status codes returned in mailbox read command */ +#define MMA9551_MCI_ERROR_NONE 0x00 +#define MMA9551_MCI_ERROR_PARAM 0x04 +#define MMA9551_MCI_INVALID_COUNT 0x19 +#define MMA9551_MCI_ERROR_COMMAND 0x1C +#define MMA9551_MCI_ERROR_INVALID_LENGTH 0x21 +#define MMA9551_MCI_ERROR_FIFO_BUSY 0x22 +#define MMA9551_MCI_ERROR_FIFO_ALLOCATED 0x23 +#define MMA9551_MCI_ERROR_FIFO_OVERSIZE 0x24 + +/* GPIO Application */ +#define MMA9551_GPIO_POL_MSB 0x08 +#define MMA9551_GPIO_POL_LSB 0x09 + +/* Sleep/Wake application */ +#define MMA9551_SLEEP_CFG 0x06 +#define MMA9551_SLEEP_CFG_SNCEN BIT(0) +#define MMA9551_SLEEP_CFG_FLEEN BIT(1) +#define MMA9551_SLEEP_CFG_SCHEN BIT(2) + +/* AFE application */ +#define MMA9551_AFE_X_ACCEL_REG 0x00 +#define MMA9551_AFE_Y_ACCEL_REG 0x02 +#define MMA9551_AFE_Z_ACCEL_REG 0x04 + +/* Reset/Suspend/Clear application */ +#define MMA9551_RSC_RESET 0x00 +#define MMA9551_RSC_OFFSET(mask) (3 - (ffs(mask) - 1) / 8) +#define MMA9551_RSC_VAL(mask) (mask >> (((ffs(mask) - 1) / 8) * 8)) + +/* + * A response is composed of: + * - control registers: MB0-3 + * - data registers: MB4-31 + * + * A request is composed of: + * - mbox to write to (always 0) + * - control registers: MB1-4 + * - data registers: MB5-31 + */ +#define MMA9551_MAILBOX_CTRL_REGS 4 +#define MMA9551_MAX_MAILBOX_DATA_REGS 28 +#define MMA9551_MAILBOX_REGS 32 + +#define MMA9551_I2C_READ_RETRIES 5 +#define MMA9551_I2C_READ_DELAY 50 /* us */ + +struct mma9551_mbox_request { + u8 start_mbox; /* Always 0. */ + u8 app_id; + /* + * See Section 5.3.1 of the MMA955xL Software Reference Manual. + * + * Bit 7: reserved, always 0 + * Bits 6-4: command + * Bits 3-0: upper bits of register offset + */ + u8 cmd_off; + u8 lower_off; + u8 nbytes; + u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS - 1]; +} __packed; + +struct mma9551_mbox_response { + u8 app_id; + /* + * See Section 5.3.3 of the MMA955xL Software Reference Manual. + * + * Bit 7: COCO + * Bits 6-0: Error code. + */ + u8 coco_err; + u8 nbytes; + u8 req_bytes; + u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS]; +} __packed; + +struct mma9551_version_info { + __be32 device_id; + u8 rom_version[2]; + u8 fw_version[2]; + u8 hw_version[2]; + u8 fw_build[2]; +}; + +static int mma9551_transfer(struct i2c_client *client, + u8 app_id, u8 command, u16 offset, + u8 *inbytes, int num_inbytes, + u8 *outbytes, int num_outbytes) +{ + struct mma9551_mbox_request req; + struct mma9551_mbox_response rsp; + struct i2c_msg in, out; + u8 req_len, err_code; + int ret, retries; + + if (offset >= 1 << 12) { + dev_err(&client->dev, "register offset too large\n"); + return -EINVAL; + } + + req_len = 1 + MMA9551_MAILBOX_CTRL_REGS + num_inbytes; + req.start_mbox = 0; + req.app_id = app_id; + req.cmd_off = command | (offset >> 8); + req.lower_off = offset; + + if (command == MMA9551_CMD_WRITE_CONFIG) + req.nbytes = num_inbytes; + else + req.nbytes = num_outbytes; + if (num_inbytes) + memcpy(req.buf, inbytes, num_inbytes); + + out.addr = client->addr; + out.flags = 0; + out.len = req_len; + out.buf = (u8 *)&req; + + ret = i2c_transfer(client->adapter, &out, 1); + if (ret < 0) { + dev_err(&client->dev, "i2c write failed\n"); + return ret; + } + + retries = MMA9551_I2C_READ_RETRIES; + do { + udelay(MMA9551_I2C_READ_DELAY); + + in.addr = client->addr; + in.flags = I2C_M_RD; + in.len = sizeof(rsp); + in.buf = (u8 *)&rsp; + + ret = i2c_transfer(client->adapter, &in, 1); + if (ret < 0) { + dev_err(&client->dev, "i2c read failed\n"); + return ret; + } + + if (rsp.coco_err & MMA9551_RESPONSE_COCO) + break; + } while (--retries > 0); + + if (retries == 0) { + dev_err(&client->dev, + "timed out while waiting for command response\n"); + return -ETIMEDOUT; + } + + if (rsp.app_id != app_id) { + dev_err(&client->dev, + "app_id mismatch in response got %02x expected %02x\n", + rsp.app_id, app_id); + return -EINVAL; + } + + err_code = rsp.coco_err & ~MMA9551_RESPONSE_COCO; + if (err_code != MMA9551_MCI_ERROR_NONE) { + dev_err(&client->dev, "read returned error %x\n", err_code); + return -EINVAL; + } + + if (rsp.nbytes != rsp.req_bytes) { + dev_err(&client->dev, + "output length mismatch got %d expected %d\n", + rsp.nbytes, rsp.req_bytes); + return -EINVAL; + } + + if (num_outbytes) + memcpy(outbytes, rsp.buf, num_outbytes); + + return 0; +} + +/** + * mma9551_read_config_byte() - read 1 configuration byte + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one configuration byte from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed + * by one or more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val) +{ + return mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG, + reg, NULL, 0, val, 1); +} +EXPORT_SYMBOL(mma9551_read_config_byte); + +/** + * mma9551_write_config_byte() - write 1 configuration byte + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Value to write + * + * Write one configuration byte from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_write_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 val) +{ + return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg, + &val, 1, NULL, 0); +} +EXPORT_SYMBOL(mma9551_write_config_byte); + +/** + * mma9551_read_status_byte() - read 1 status byte + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one status byte from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_status_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val) +{ + return mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, + reg, NULL, 0, val, 1); +} +EXPORT_SYMBOL(mma9551_read_status_byte); + +/** + * mma9551_read_config_word() - read 1 config word + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one configuration word from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val) +{ + int ret; + __be16 v; + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG, + reg, NULL, 0, (u8 *)&v, 2); + *val = be16_to_cpu(v); + + return ret; +} +EXPORT_SYMBOL(mma9551_read_config_word); + +/** + * mma9551_write_config_word() - write 1 config word + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Value to write + * + * Write one configuration word from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_write_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 val) +{ + __be16 v = cpu_to_be16(val); + + return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg, + (u8 *) &v, 2, NULL, 0); +} +EXPORT_SYMBOL(mma9551_write_config_word); + +/** + * mma9551_read_status_word() - read 1 status word + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @val: Pointer to store value read + * + * Read one status word from the device using MMA955xL command format. + * Commands to the MMA955xL platform consist of a write followed by one or + * more reads. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_status_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val) +{ + int ret; + __be16 v; + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, + reg, NULL, 0, (u8 *)&v, 2); + *val = be16_to_cpu(v); + + return ret; +} +EXPORT_SYMBOL(mma9551_read_status_word); + +/** + * mma9551_read_config_words() - read multiple config words + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @len: Length of array to read in bytes + * @val: Array of words to read + * + * Read multiple configuration registers (word-sized registers). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf) +{ + int ret, i; + int len_words = len / sizeof(u16); + __be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS / 2]; + + if (len_words > ARRAY_SIZE(be_buf)) { + dev_err(&client->dev, "Invalid buffer size %d\n", len); + return -EINVAL; + } + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG, + reg, NULL, 0, (u8 *) be_buf, len); + if (ret < 0) + return ret; + + for (i = 0; i < len_words; i++) + buf[i] = be16_to_cpu(be_buf[i]); + + return 0; +} +EXPORT_SYMBOL(mma9551_read_config_words); + +/** + * mma9551_read_status_words() - read multiple status words + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @len: Length of array to read in bytes + * @val: Array of words to read + * + * Read multiple status registers (word-sized registers). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_status_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf) +{ + int ret, i; + int len_words = len / sizeof(u16); + __be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS / 2]; + + if (len_words > ARRAY_SIZE(be_buf)) { + dev_err(&client->dev, "Invalid buffer size %d\n", len); + return -EINVAL; + } + + ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS, + reg, NULL, 0, (u8 *) be_buf, len); + if (ret < 0) + return ret; + + for (i = 0; i < len_words; i++) + buf[i] = be16_to_cpu(be_buf[i]); + + return 0; +} +EXPORT_SYMBOL(mma9551_read_status_words); + +/** + * mma9551_write_config_words() - write multiple config words + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @len: Length of array to write in bytes + * @val: Array of words to write + * + * Write multiple configuration registers (word-sized registers). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_write_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf) +{ + int i; + int len_words = len / sizeof(u16); + __be16 be_buf[(MMA9551_MAX_MAILBOX_DATA_REGS - 1) / 2]; + + if (len_words > ARRAY_SIZE(be_buf)) { + dev_err(&client->dev, "Invalid buffer size %d\n", len); + return -EINVAL; + } + + for (i = 0; i < len_words; i++) + be_buf[i] = cpu_to_be16(buf[i]); + + return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, + reg, (u8 *) be_buf, len, NULL, 0); +} +EXPORT_SYMBOL(mma9551_write_config_words); + +/** + * mma9551_update_config_bits() - update bits in register + * @client: I2C client + * @app_id: Application ID + * @reg: Application register + * @mask: Mask for the bits to update + * @val: Value of the bits to update + * + * Update bits in the given register using a bit mask. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_update_config_bits(struct i2c_client *client, u8 app_id, + u16 reg, u8 mask, u8 val) +{ + int ret; + u8 tmp, orig; + + ret = mma9551_read_config_byte(client, app_id, reg, &orig); + if (ret < 0) + return ret; + + tmp = orig & ~mask; + tmp |= val & mask; + + if (tmp == orig) + return 0; + + return mma9551_write_config_byte(client, app_id, reg, tmp); +} +EXPORT_SYMBOL(mma9551_update_config_bits); + +/** + * mma9551_gpio_config() - configure gpio + * @client: I2C client + * @pin: GPIO pin to configure + * @app_id: Application ID + * @bitnum: Bit number of status register being assigned to the GPIO pin. + * @polarity: The polarity parameter is described in section 6.2.2, page 66, + * of the Software Reference Manual. Basically, polarity=0 means + * the interrupt line has the same value as the selected bit, + * while polarity=1 means the line is inverted. + * + * Assign a bit from an application’s status register to a specific GPIO pin. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_gpio_config(struct i2c_client *client, enum mma9551_gpio_pin pin, + u8 app_id, u8 bitnum, int polarity) +{ + u8 reg, pol_mask, pol_val; + int ret; + + if (pin > mma9551_gpio_max) { + dev_err(&client->dev, "bad GPIO pin\n"); + return -EINVAL; + } + + /* + * Pin 6 is configured by regs 0x00 and 0x01, pin 7 by 0x02 and + * 0x03, and so on. + */ + reg = pin * 2; + + ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO, + reg, app_id); + if (ret < 0) { + dev_err(&client->dev, "error setting GPIO app_id\n"); + return ret; + } + + ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO, + reg + 1, bitnum); + if (ret < 0) { + dev_err(&client->dev, "error setting GPIO bit number\n"); + return ret; + } + + switch (pin) { + case mma9551_gpio6: + reg = MMA9551_GPIO_POL_LSB; + pol_mask = 1 << 6; + break; + case mma9551_gpio7: + reg = MMA9551_GPIO_POL_LSB; + pol_mask = 1 << 7; + break; + case mma9551_gpio8: + reg = MMA9551_GPIO_POL_MSB; + pol_mask = 1 << 0; + break; + case mma9551_gpio9: + reg = MMA9551_GPIO_POL_MSB; + pol_mask = 1 << 1; + break; + } + pol_val = polarity ? pol_mask : 0; + + ret = mma9551_update_config_bits(client, MMA9551_APPID_GPIO, reg, + pol_mask, pol_val); + if (ret < 0) + dev_err(&client->dev, "error setting GPIO polarity\n"); + + return ret; +} +EXPORT_SYMBOL(mma9551_gpio_config); + +/** + * mma9551_read_version() - read device version information + * @client: I2C client + * + * Read version information and print device id and firmware version. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_read_version(struct i2c_client *client) +{ + struct mma9551_version_info info; + int ret; + + ret = mma9551_transfer(client, MMA9551_APPID_VERSION, 0x00, 0x00, + NULL, 0, (u8 *)&info, sizeof(info)); + if (ret < 0) + return ret; + + dev_info(&client->dev, "device ID 0x%x, firmware version %02x.%02x\n", + be32_to_cpu(info.device_id), info.fw_version[0], + info.fw_version[1]); + + return 0; +} +EXPORT_SYMBOL(mma9551_read_version); + +/** + * mma9551_set_device_state() - sets HW power mode + * @client: I2C client + * @enable: Use true to power on device, false to cause the device + * to enter sleep. + * + * Set power on/off for device using the Sleep/Wake Application. + * When enable is true, power on chip and enable doze mode. + * When enable is false, enter sleep mode (device remains in the + * lowest-power mode). + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_set_device_state(struct i2c_client *client, bool enable) +{ + return mma9551_update_config_bits(client, MMA9551_APPID_SLEEP_WAKE, + MMA9551_SLEEP_CFG, + MMA9551_SLEEP_CFG_SNCEN | + MMA9551_SLEEP_CFG_FLEEN | + MMA9551_SLEEP_CFG_SCHEN, + enable ? MMA9551_SLEEP_CFG_SCHEN | + MMA9551_SLEEP_CFG_FLEEN : + MMA9551_SLEEP_CFG_SNCEN); +} +EXPORT_SYMBOL(mma9551_set_device_state); + +/** + * mma9551_set_power_state() - sets runtime PM state + * @client: I2C client + * @on: Use true to power on device, false to power off + * + * Resume or suspend the device using Runtime PM. + * The device will suspend after the autosuspend delay. + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_set_power_state(struct i2c_client *client, bool on) +{ +#ifdef CONFIG_PM + int ret; + + if (on) + ret = pm_runtime_get_sync(&client->dev); + else { + pm_runtime_mark_last_busy(&client->dev); + ret = pm_runtime_put_autosuspend(&client->dev); + } + + if (ret < 0) { + dev_err(&client->dev, + "failed to change power state to %d\n", on); + if (on) + pm_runtime_put_noidle(&client->dev); + + return ret; + } +#endif + + return 0; +} +EXPORT_SYMBOL(mma9551_set_power_state); + +/** + * mma9551_sleep() - sleep + * @freq: Application frequency + * + * Firmware applications run at a certain frequency on the + * device. Sleep for one application cycle to make sure the + * application had time to run once and initialize set values. + */ +void mma9551_sleep(int freq) +{ + int sleep_val = 1000 / freq; + + if (sleep_val < 20) + usleep_range(sleep_val * 1000, 20000); + else + msleep_interruptible(sleep_val); +} +EXPORT_SYMBOL(mma9551_sleep); + +/** + * mma9551_read_accel_chan() - read accelerometer channel + * @client: I2C client + * @chan: IIO channel + * @val: Pointer to the accelerometer value read + * @val2: Unused + * + * Read accelerometer value for the specified channel. + * + * Locking note: This function must be called with the device lock held. + * Locking is not handled inside the function. Callers should ensure they + * serialize access to the HW. + * + * Returns: IIO_VAL_INT on success, negative value on failure. + */ +int mma9551_read_accel_chan(struct i2c_client *client, + const struct iio_chan_spec *chan, + int *val, int *val2) +{ + u16 reg_addr; + s16 raw_accel; + int ret; + + switch (chan->channel2) { + case IIO_MOD_X: + reg_addr = MMA9551_AFE_X_ACCEL_REG; + break; + case IIO_MOD_Y: + reg_addr = MMA9551_AFE_Y_ACCEL_REG; + break; + case IIO_MOD_Z: + reg_addr = MMA9551_AFE_Z_ACCEL_REG; + break; + default: + return -EINVAL; + } + + ret = mma9551_set_power_state(client, true); + if (ret < 0) + return ret; + + ret = mma9551_read_status_word(client, MMA9551_APPID_AFE, + reg_addr, &raw_accel); + if (ret < 0) + goto out_poweroff; + + *val = raw_accel; + + ret = IIO_VAL_INT; + +out_poweroff: + mma9551_set_power_state(client, false); + return ret; +} +EXPORT_SYMBOL(mma9551_read_accel_chan); + +/** + * mma9551_read_accel_scale() - read accelerometer scale + * @val: Pointer to the accelerometer scale (int value) + * @val2: Pointer to the accelerometer scale (micro value) + * + * Read accelerometer scale. + * + * Returns: IIO_VAL_INT_PLUS_MICRO. + */ +int mma9551_read_accel_scale(int *val, int *val2) +{ + *val = 0; + *val2 = 2440; + + return IIO_VAL_INT_PLUS_MICRO; +} +EXPORT_SYMBOL(mma9551_read_accel_scale); + +/** + * mma9551_app_reset() - reset application + * @client: I2C client + * @app_mask: Application to reset + * + * Reset the given application (using the Reset/Suspend/Clear + * Control Application) + * + * Returns: 0 on success, negative value on failure. + */ +int mma9551_app_reset(struct i2c_client *client, u32 app_mask) +{ + return mma9551_write_config_byte(client, MMA9551_APPID_RCS, + MMA9551_RSC_RESET + + MMA9551_RSC_OFFSET(app_mask), + MMA9551_RSC_VAL(app_mask)); +} +EXPORT_SYMBOL(mma9551_app_reset); + +MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>"); +MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MMA955xL sensors core"); diff --git a/drivers/iio/accel/mma9551_core.h b/drivers/iio/accel/mma9551_core.h new file mode 100644 index 000000000..edaa56b10 --- /dev/null +++ b/drivers/iio/accel/mma9551_core.h @@ -0,0 +1,81 @@ +/* + * Common code for Freescale MMA955x Intelligent Sensor Platform drivers + * Copyright (c) 2014, 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. + */ + +#ifndef _MMA9551_CORE_H_ +#define _MMA9551_CORE_H_ + +/* Applications IDs */ +#define MMA9551_APPID_VERSION 0x00 +#define MMA9551_APPID_GPIO 0x03 +#define MMA9551_APPID_AFE 0x06 +#define MMA9551_APPID_TILT 0x0B +#define MMA9551_APPID_SLEEP_WAKE 0x12 +#define MMA9551_APPID_PEDOMETER 0x15 +#define MMA9551_APPID_RCS 0x17 +#define MMA9551_APPID_NONE 0xff + +/* Reset/Suspend/Clear application app masks */ +#define MMA9551_RSC_PED BIT(21) + +#define MMA9551_AUTO_SUSPEND_DELAY_MS 2000 + +enum mma9551_gpio_pin { + mma9551_gpio6 = 0, + mma9551_gpio7, + mma9551_gpio8, + mma9551_gpio9, + mma9551_gpio_max = mma9551_gpio9, +}; + +#define MMA9551_ACCEL_CHANNEL(axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +int mma9551_read_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val); +int mma9551_write_config_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 val); +int mma9551_read_status_byte(struct i2c_client *client, u8 app_id, + u16 reg, u8 *val); +int mma9551_read_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val); +int mma9551_write_config_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 val); +int mma9551_read_status_word(struct i2c_client *client, u8 app_id, + u16 reg, u16 *val); +int mma9551_read_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf); +int mma9551_read_status_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf); +int mma9551_write_config_words(struct i2c_client *client, u8 app_id, + u16 reg, u8 len, u16 *buf); +int mma9551_update_config_bits(struct i2c_client *client, u8 app_id, + u16 reg, u8 mask, u8 val); +int mma9551_gpio_config(struct i2c_client *client, enum mma9551_gpio_pin pin, + u8 app_id, u8 bitnum, int polarity); +int mma9551_read_version(struct i2c_client *client); +int mma9551_set_device_state(struct i2c_client *client, bool enable); +int mma9551_set_power_state(struct i2c_client *client, bool on); +void mma9551_sleep(int freq); +int mma9551_read_accel_chan(struct i2c_client *client, + const struct iio_chan_spec *chan, + int *val, int *val2); +int mma9551_read_accel_scale(int *val, int *val2); +int mma9551_app_reset(struct i2c_client *client, u32 app_mask); + +#endif /* _MMA9551_CORE_H_ */ diff --git a/drivers/iio/accel/mma9553.c b/drivers/iio/accel/mma9553.c new file mode 100644 index 000000000..365a109aa --- /dev/null +++ b/drivers/iio/accel/mma9553.c @@ -0,0 +1,1332 @@ +/* + * Freescale MMA9553L Intelligent Pedometer driver + * Copyright (c) 2014, 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. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/pm_runtime.h> +#include "mma9551_core.h" + +#define MMA9553_DRV_NAME "mma9553" +#define MMA9553_IRQ_NAME "mma9553_event" +#define MMA9553_GPIO_NAME "mma9553_int" + +/* Pedometer configuration registers (R/W) */ +#define MMA9553_REG_CONF_SLEEPMIN 0x00 +#define MMA9553_REG_CONF_SLEEPMAX 0x02 +#define MMA9553_REG_CONF_SLEEPTHD 0x04 +#define MMA9553_MASK_CONF_WORD GENMASK(15, 0) + +#define MMA9553_REG_CONF_CONF_STEPLEN 0x06 +#define MMA9553_MASK_CONF_CONFIG BIT(15) +#define MMA9553_MASK_CONF_ACT_DBCNTM BIT(14) +#define MMA9553_MASK_CONF_SLP_DBCNTM BIT(13) +#define MMA9553_MASK_CONF_STEPLEN GENMASK(7, 0) + +#define MMA9553_REG_CONF_HEIGHT_WEIGHT 0x08 +#define MMA9553_MASK_CONF_HEIGHT GENMASK(15, 8) +#define MMA9553_MASK_CONF_WEIGHT GENMASK(7, 0) + +#define MMA9553_REG_CONF_FILTER 0x0A +#define MMA9553_MASK_CONF_FILTSTEP GENMASK(15, 8) +#define MMA9553_MASK_CONF_MALE BIT(7) +#define MMA9553_MASK_CONF_FILTTIME GENMASK(6, 0) + +#define MMA9553_REG_CONF_SPEED_STEP 0x0C +#define MMA9553_MASK_CONF_SPDPRD GENMASK(15, 8) +#define MMA9553_MASK_CONF_STEPCOALESCE GENMASK(7, 0) + +#define MMA9553_REG_CONF_ACTTHD 0x0E +#define MMA9553_MAX_ACTTHD GENMASK(15, 0) + +/* Pedometer status registers (R-only) */ +#define MMA9553_REG_STATUS 0x00 +#define MMA9553_MASK_STATUS_MRGFL BIT(15) +#define MMA9553_MASK_STATUS_SUSPCHG BIT(14) +#define MMA9553_MASK_STATUS_STEPCHG BIT(13) +#define MMA9553_MASK_STATUS_ACTCHG BIT(12) +#define MMA9553_MASK_STATUS_SUSP BIT(11) +#define MMA9553_MASK_STATUS_ACTIVITY (BIT(10) | BIT(9) | BIT(8)) +#define MMA9553_MASK_STATUS_VERSION 0x00FF + +#define MMA9553_REG_STEPCNT 0x02 +#define MMA9553_REG_DISTANCE 0x04 +#define MMA9553_REG_SPEED 0x06 +#define MMA9553_REG_CALORIES 0x08 +#define MMA9553_REG_SLEEPCNT 0x0A + +/* Pedometer events are always mapped to this pin. */ +#define MMA9553_DEFAULT_GPIO_PIN mma9551_gpio6 +#define MMA9553_DEFAULT_GPIO_POLARITY 0 + +/* Bitnum used for gpio configuration = bit number in high status byte */ +#define STATUS_TO_BITNUM(bit) (ffs(bit) - 9) + +#define MMA9553_DEFAULT_SAMPLE_RATE 30 /* Hz */ + +/* + * The internal activity level must be stable for ACTTHD samples before + * ACTIVITY is updated.The ACTIVITY variable contains the current activity + * level and is updated every time a step is detected or once a second + * if there are no steps. + */ +#define MMA9553_ACTIVITY_THD_TO_SEC(thd) ((thd) / MMA9553_DEFAULT_SAMPLE_RATE) +#define MMA9553_ACTIVITY_SEC_TO_THD(sec) ((sec) * MMA9553_DEFAULT_SAMPLE_RATE) + +/* + * Autonomously suspend pedometer if acceleration vector magnitude + * is near 1g (4096 at 0.244 mg/LSB resolution) for 30 seconds. + */ +#define MMA9553_DEFAULT_SLEEPMIN 3688 /* 0,9 g */ +#define MMA9553_DEFAULT_SLEEPMAX 4508 /* 1,1 g */ +#define MMA9553_DEFAULT_SLEEPTHD (MMA9553_DEFAULT_SAMPLE_RATE * 30) + +#define MMA9553_CONFIG_RETRIES 2 + +/* Status register - activity field */ +enum activity_level { + ACTIVITY_UNKNOWN, + ACTIVITY_REST, + ACTIVITY_WALKING, + ACTIVITY_JOGGING, + ACTIVITY_RUNNING, +}; + +static struct mma9553_event_info { + enum iio_chan_type type; + enum iio_modifier mod; + enum iio_event_direction dir; +} mma9553_events_info[] = { + { + .type = IIO_STEPS, + .mod = IIO_NO_MOD, + .dir = IIO_EV_DIR_NONE, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_STILL, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_STILL, + .dir = IIO_EV_DIR_FALLING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_WALKING, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_WALKING, + .dir = IIO_EV_DIR_FALLING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_JOGGING, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_JOGGING, + .dir = IIO_EV_DIR_FALLING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_RUNNING, + .dir = IIO_EV_DIR_RISING, + }, + { + .type = IIO_ACTIVITY, + .mod = IIO_MOD_RUNNING, + .dir = IIO_EV_DIR_FALLING, + }, +}; + +#define MMA9553_EVENTS_INFO_SIZE ARRAY_SIZE(mma9553_events_info) + +struct mma9553_event { + struct mma9553_event_info *info; + bool enabled; +}; + +struct mma9553_conf_regs { + u16 sleepmin; + u16 sleepmax; + u16 sleepthd; + u16 config; + u16 height_weight; + u16 filter; + u16 speed_step; + u16 actthd; +} __packed; + +struct mma9553_data { + struct i2c_client *client; + struct mutex mutex; + struct mma9553_conf_regs conf; + struct mma9553_event events[MMA9553_EVENTS_INFO_SIZE]; + int num_events; + u8 gpio_bitnum; + /* + * This is used for all features that depend on step count: + * step count, distance, speed, calories. + */ + bool stepcnt_enabled; + u16 stepcnt; + u8 activity; + s64 timestamp; +}; + +static u8 mma9553_get_bits(u16 val, u16 mask) +{ + return (val & mask) >> (ffs(mask) - 1); +} + +static u16 mma9553_set_bits(u16 current_val, u16 val, u16 mask) +{ + return (current_val & ~mask) | (val << (ffs(mask) - 1)); +} + +static enum iio_modifier mma9553_activity_to_mod(enum activity_level activity) +{ + switch (activity) { + case ACTIVITY_RUNNING: + return IIO_MOD_RUNNING; + case ACTIVITY_JOGGING: + return IIO_MOD_JOGGING; + case ACTIVITY_WALKING: + return IIO_MOD_WALKING; + case ACTIVITY_REST: + return IIO_MOD_STILL; + case ACTIVITY_UNKNOWN: + default: + return IIO_NO_MOD; + } +} + +static void mma9553_init_events(struct mma9553_data *data) +{ + int i; + + data->num_events = MMA9553_EVENTS_INFO_SIZE; + for (i = 0; i < data->num_events; i++) { + data->events[i].info = &mma9553_events_info[i]; + data->events[i].enabled = false; + } +} + +static struct mma9553_event *mma9553_get_event(struct mma9553_data *data, + enum iio_chan_type type, + enum iio_modifier mod, + enum iio_event_direction dir) +{ + int i; + + for (i = 0; i < data->num_events; i++) + if (data->events[i].info->type == type && + data->events[i].info->mod == mod && + data->events[i].info->dir == dir) + return &data->events[i]; + + return NULL; +} + +static bool mma9553_is_any_event_enabled(struct mma9553_data *data, + bool check_type, + enum iio_chan_type type) +{ + int i; + + for (i = 0; i < data->num_events; i++) + if ((check_type && data->events[i].info->type == type && + data->events[i].enabled) || + (!check_type && data->events[i].enabled)) + return true; + + return false; +} + +static int mma9553_set_config(struct mma9553_data *data, u16 reg, + u16 *p_reg_val, u16 val, u16 mask) +{ + int ret, retries; + u16 reg_val, config; + + reg_val = *p_reg_val; + if (val == mma9553_get_bits(reg_val, mask)) + return 0; + + reg_val = mma9553_set_bits(reg_val, val, mask); + ret = mma9551_write_config_word(data->client, MMA9551_APPID_PEDOMETER, + reg, reg_val); + if (ret < 0) { + dev_err(&data->client->dev, + "error writing config register 0x%x\n", reg); + return ret; + } + + *p_reg_val = reg_val; + + /* Reinitializes the pedometer with current configuration values */ + config = mma9553_set_bits(data->conf.config, 1, + MMA9553_MASK_CONF_CONFIG); + + ret = mma9551_write_config_word(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_CONF_STEPLEN, config); + if (ret < 0) { + dev_err(&data->client->dev, + "error writing config register 0x%x\n", + MMA9553_REG_CONF_CONF_STEPLEN); + return ret; + } + + retries = MMA9553_CONFIG_RETRIES; + do { + mma9551_sleep(MMA9553_DEFAULT_SAMPLE_RATE); + ret = mma9551_read_config_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_CONF_STEPLEN, + &config); + if (ret < 0) + return ret; + } while (mma9553_get_bits(config, MMA9553_MASK_CONF_CONFIG) && + --retries > 0); + + return 0; +} + +static int mma9553_read_activity_stepcnt(struct mma9553_data *data, + u8 *activity, u16 *stepcnt) +{ + u16 buf[2]; + int ret; + + ret = mma9551_read_status_words(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_STATUS, sizeof(u32), buf); + if (ret < 0) { + dev_err(&data->client->dev, + "error reading status and stepcnt\n"); + return ret; + } + + *activity = mma9553_get_bits(buf[0], MMA9553_MASK_STATUS_ACTIVITY); + *stepcnt = buf[1]; + + return 0; +} + +static int mma9553_conf_gpio(struct mma9553_data *data) +{ + u8 bitnum = 0, appid = MMA9551_APPID_PEDOMETER; + int ret; + struct mma9553_event *ev_step_detect; + bool activity_enabled; + + activity_enabled = + mma9553_is_any_event_enabled(data, true, IIO_ACTIVITY); + ev_step_detect = + mma9553_get_event(data, IIO_STEPS, IIO_NO_MOD, IIO_EV_DIR_NONE); + + /* + * If both step detector and activity are enabled, use the MRGFL bit. + * This bit is the logical OR of the SUSPCHG, STEPCHG, and ACTCHG flags. + */ + if (activity_enabled && ev_step_detect->enabled) + bitnum = STATUS_TO_BITNUM(MMA9553_MASK_STATUS_MRGFL); + else if (ev_step_detect->enabled) + bitnum = STATUS_TO_BITNUM(MMA9553_MASK_STATUS_STEPCHG); + else if (activity_enabled) + bitnum = STATUS_TO_BITNUM(MMA9553_MASK_STATUS_ACTCHG); + else /* Reset */ + appid = MMA9551_APPID_NONE; + + if (data->gpio_bitnum == bitnum) + return 0; + + /* Save initial values for activity and stepcnt */ + if (activity_enabled || ev_step_detect->enabled) + mma9553_read_activity_stepcnt(data, &data->activity, + &data->stepcnt); + + ret = mma9551_gpio_config(data->client, + MMA9553_DEFAULT_GPIO_PIN, + appid, bitnum, MMA9553_DEFAULT_GPIO_POLARITY); + if (ret < 0) + return ret; + data->gpio_bitnum = bitnum; + + return 0; +} + +static int mma9553_init(struct mma9553_data *data) +{ + int ret; + + ret = mma9551_read_version(data->client); + if (ret) + return ret; + + /* + * Read all the pedometer configuration registers. This is used as + * a device identification command to differentiate the MMA9553L + * from the MMA9550L. + */ + ret = + mma9551_read_config_words(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_SLEEPMIN, + sizeof(data->conf), (u16 *) &data->conf); + if (ret < 0) { + dev_err(&data->client->dev, + "device is not MMA9553L: failed to read cfg regs\n"); + return ret; + } + + + /* Reset gpio */ + data->gpio_bitnum = -1; + ret = mma9553_conf_gpio(data); + if (ret < 0) + return ret; + + ret = mma9551_app_reset(data->client, MMA9551_RSC_PED); + if (ret < 0) + return ret; + + /* Init config registers */ + data->conf.sleepmin = MMA9553_DEFAULT_SLEEPMIN; + data->conf.sleepmax = MMA9553_DEFAULT_SLEEPMAX; + data->conf.sleepthd = MMA9553_DEFAULT_SLEEPTHD; + data->conf.config = + mma9553_set_bits(data->conf.config, 1, MMA9553_MASK_CONF_CONFIG); + /* + * Clear the activity debounce counter when the activity level changes, + * so that the confidence level applies for any activity level. + */ + data->conf.config = mma9553_set_bits(data->conf.config, 1, + MMA9553_MASK_CONF_ACT_DBCNTM); + ret = + mma9551_write_config_words(data->client, MMA9551_APPID_PEDOMETER, + MMA9553_REG_CONF_SLEEPMIN, + sizeof(data->conf), (u16 *) &data->conf); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to write configuration registers\n"); + return ret; + } + + return mma9551_set_device_state(data->client, true); +} + +static int mma9553_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + u16 tmp; + u8 activity; + bool powered_on; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + /* + * The HW only counts steps and other dependent + * parameters (speed, distance, calories, activity) + * if power is on (from enabling an event or the + * step counter */ + powered_on = + mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, + "No channels enabled\n"); + return -EINVAL; + } + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_STEPCNT, + &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_DISTANCE: + powered_on = + mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, + "No channels enabled\n"); + return -EINVAL; + } + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_DISTANCE, + &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_ACTIVITY: + powered_on = + mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, + "No channels enabled\n"); + return -EINVAL; + } + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_STATUS, + &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + + activity = + mma9553_get_bits(tmp, MMA9553_MASK_STATUS_ACTIVITY); + + /* + * The device does not support confidence value levels, + * so we will always have 100% for current activity and + * 0% for the others. + */ + if (chan->channel2 == mma9553_activity_to_mod(activity)) + *val = 100; + else + *val = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VELOCITY: /* m/h */ + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + powered_on = + mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, + "No channels enabled\n"); + return -EINVAL; + } + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_SPEED, &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_ENERGY: /* Cal or kcal */ + powered_on = + mma9553_is_any_event_enabled(data, false, 0) || + data->stepcnt_enabled; + if (!powered_on) { + dev_err(&data->client->dev, + "No channels enabled\n"); + return -EINVAL; + } + mutex_lock(&data->mutex); + ret = mma9551_read_status_word(data->client, + MMA9551_APPID_PEDOMETER, + MMA9553_REG_CALORIES, + &tmp); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + *val = tmp; + return IIO_VAL_INT; + case IIO_ACCEL: + mutex_lock(&data->mutex); + ret = mma9551_read_accel_chan(data->client, + chan, val, val2); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VELOCITY: /* m/h to m/s */ + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + *val = 0; + *val2 = 277; /* 0.000277 */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ENERGY: /* Cal or kcal to J */ + *val = 4184; + return IIO_VAL_INT; + case IIO_ACCEL: + return mma9551_read_accel_scale(val, val2); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_ENABLE: + *val = data->stepcnt_enabled; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBHEIGHT: + tmp = mma9553_get_bits(data->conf.height_weight, + MMA9553_MASK_CONF_HEIGHT); + *val = tmp / 100; /* cm to m */ + *val2 = (tmp % 100) * 10000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBWEIGHT: + *val = mma9553_get_bits(data->conf.height_weight, + MMA9553_MASK_CONF_WEIGHT); + return IIO_VAL_INT; + case IIO_CHAN_INFO_DEBOUNCE_COUNT: + switch (chan->type) { + case IIO_STEPS: + *val = mma9553_get_bits(data->conf.filter, + MMA9553_MASK_CONF_FILTSTEP); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_DEBOUNCE_TIME: + switch (chan->type) { + case IIO_STEPS: + *val = mma9553_get_bits(data->conf.filter, + MMA9553_MASK_CONF_FILTTIME); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_VELOCITY: + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + *val = mma9553_get_bits(data->conf.speed_step, + MMA9553_MASK_CONF_SPDPRD); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mma9553_data *data = iio_priv(indio_dev); + int ret, tmp; + + switch (mask) { + case IIO_CHAN_INFO_ENABLE: + if (data->stepcnt_enabled == !!val) + return 0; + mutex_lock(&data->mutex); + ret = mma9551_set_power_state(data->client, val); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + data->stepcnt_enabled = val; + mutex_unlock(&data->mutex); + return 0; + case IIO_CHAN_INFO_CALIBHEIGHT: + /* m to cm */ + tmp = val * 100 + val2 / 10000; + if (tmp < 0 || tmp > 255) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_HEIGHT_WEIGHT, + &data->conf.height_weight, + tmp, MMA9553_MASK_CONF_HEIGHT); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_CALIBWEIGHT: + if (val < 0 || val > 255) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_HEIGHT_WEIGHT, + &data->conf.height_weight, + val, MMA9553_MASK_CONF_WEIGHT); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_DEBOUNCE_COUNT: + switch (chan->type) { + case IIO_STEPS: + /* + * Set to 0 to disable step filtering. If the value + * specified is greater than 6, then 6 will be used. + */ + if (val < 0) + return -EINVAL; + if (val > 6) + val = 6; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER, + &data->conf.filter, val, + MMA9553_MASK_CONF_FILTSTEP); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_DEBOUNCE_TIME: + switch (chan->type) { + case IIO_STEPS: + if (val < 0 || val > 127) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER, + &data->conf.filter, val, + MMA9553_MASK_CONF_FILTTIME); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + switch (chan->type) { + case IIO_VELOCITY: + if (chan->channel2 != IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z) + return -EINVAL; + /* + * If set to a value greater than 5, then 5 will be + * used. Warning: Do not set SPDPRD to 0 or 1 as + * this may cause undesirable behavior. + */ + if (val < 2) + return -EINVAL; + if (val > 5) + val = 5; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_SPEED_STEP, + &data->conf.speed_step, val, + MMA9553_MASK_CONF_SPDPRD); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_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 mma9553_data *data = iio_priv(indio_dev); + struct mma9553_event *event; + + event = mma9553_get_event(data, chan->type, chan->channel2, dir); + if (!event) + return -EINVAL; + + return event->enabled; +} + +static int mma9553_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 mma9553_data *data = iio_priv(indio_dev); + struct mma9553_event *event; + int ret; + + event = mma9553_get_event(data, chan->type, chan->channel2, dir); + if (!event) + return -EINVAL; + + if (event->enabled == state) + return 0; + + mutex_lock(&data->mutex); + + ret = mma9551_set_power_state(data->client, state); + if (ret < 0) + goto err_out; + event->enabled = state; + + ret = mma9553_conf_gpio(data); + if (ret < 0) + goto err_conf_gpio; + + mutex_unlock(&data->mutex); + + return ret; + +err_conf_gpio: + if (state) { + event->enabled = false; + mma9551_set_power_state(data->client, false); + } +err_out: + mutex_unlock(&data->mutex); + return ret; +} + +static int mma9553_read_event_value(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 mma9553_data *data = iio_priv(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + switch (chan->type) { + case IIO_STEPS: + *val = mma9553_get_bits(data->conf.speed_step, + MMA9553_MASK_CONF_STEPCOALESCE); + return IIO_VAL_INT; + case IIO_ACTIVITY: + /* + * The device does not support confidence value levels. + * We set an average of 50%. + */ + *val = 50; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + switch (chan->type) { + case IIO_ACTIVITY: + *val = MMA9553_ACTIVITY_THD_TO_SEC(data->conf.actthd); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_write_event_value(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 mma9553_data *data = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (chan->type) { + case IIO_STEPS: + if (val < 0 || val > 255) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, + MMA9553_REG_CONF_SPEED_STEP, + &data->conf.speed_step, val, + MMA9553_MASK_CONF_STEPCOALESCE); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + switch (chan->type) { + case IIO_ACTIVITY: + if (val < 0 || val > MMA9553_ACTIVITY_THD_TO_SEC( + MMA9553_MAX_ACTTHD)) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_ACTTHD, + &data->conf.actthd, + MMA9553_ACTIVITY_SEC_TO_THD + (val), MMA9553_MASK_CONF_WORD); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int mma9553_get_calibgender_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mma9553_data *data = iio_priv(indio_dev); + u8 gender; + + gender = mma9553_get_bits(data->conf.filter, MMA9553_MASK_CONF_MALE); + /* + * HW expects 0 for female and 1 for male, + * while iio index is 0 for male and 1 for female + */ + return !gender; +} + +static int mma9553_set_calibgender_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct mma9553_data *data = iio_priv(indio_dev); + u8 gender = !mode; + int ret; + + if ((mode != 0) && (mode != 1)) + return -EINVAL; + mutex_lock(&data->mutex); + ret = mma9553_set_config(data, MMA9553_REG_CONF_FILTER, + &data->conf.filter, gender, + MMA9553_MASK_CONF_MALE); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_event_spec mma9553_step_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), +}; + +static const struct iio_event_spec mma9553_activity_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, +}; + +static const char * const calibgender_modes[] = { "male", "female" }; + +static const struct iio_enum mma9553_calibgender_enum = { + .items = calibgender_modes, + .num_items = ARRAY_SIZE(calibgender_modes), + .get = mma9553_get_calibgender_mode, + .set = mma9553_set_calibgender_mode, +}; + +static const struct iio_chan_spec_ext_info mma9553_ext_info[] = { + IIO_ENUM("calibgender", IIO_SHARED_BY_TYPE, &mma9553_calibgender_enum), + IIO_ENUM_AVAILABLE("calibgender", &mma9553_calibgender_enum), + {}, +}; + +#define MMA9553_PEDOMETER_CHANNEL(_type, _mask) { \ + .type = _type, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) | \ + BIT(IIO_CHAN_INFO_CALIBHEIGHT) | \ + _mask, \ + .ext_info = mma9553_ext_info, \ +} + +#define MMA9553_ACTIVITY_CHANNEL(_chan2) { \ + .type = IIO_ACTIVITY, \ + .modified = 1, \ + .channel2 = _chan2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBHEIGHT) | \ + BIT(IIO_CHAN_INFO_ENABLE), \ + .event_spec = mma9553_activity_events, \ + .num_event_specs = ARRAY_SIZE(mma9553_activity_events), \ + .ext_info = mma9553_ext_info, \ +} + +static const struct iio_chan_spec mma9553_channels[] = { + MMA9551_ACCEL_CHANNEL(IIO_MOD_X), + MMA9551_ACCEL_CHANNEL(IIO_MOD_Y), + MMA9551_ACCEL_CHANNEL(IIO_MOD_Z), + + { + .type = IIO_STEPS, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_DEBOUNCE_COUNT) | + BIT(IIO_CHAN_INFO_DEBOUNCE_TIME), + .event_spec = &mma9553_step_event, + .num_event_specs = 1, + }, + + MMA9553_PEDOMETER_CHANNEL(IIO_DISTANCE, BIT(IIO_CHAN_INFO_PROCESSED)), + { + .type = IIO_VELOCITY, + .modified = 1, + .channel2 = IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_ENABLE), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBHEIGHT), + .ext_info = mma9553_ext_info, + }, + MMA9553_PEDOMETER_CHANNEL(IIO_ENERGY, BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBWEIGHT)), + + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_RUNNING), + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_JOGGING), + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_WALKING), + MMA9553_ACTIVITY_CHANNEL(IIO_MOD_STILL), +}; + +static const struct iio_info mma9553_info = { + .driver_module = THIS_MODULE, + .read_raw = mma9553_read_raw, + .write_raw = mma9553_write_raw, + .read_event_config = mma9553_read_event_config, + .write_event_config = mma9553_write_event_config, + .read_event_value = mma9553_read_event_value, + .write_event_value = mma9553_write_event_value, +}; + +static irqreturn_t mma9553_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mma9553_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(); + /* + * Since we only configure the interrupt pin when an + * event is enabled, we are sure we have at least + * one event enabled at this point. + */ + return IRQ_WAKE_THREAD; +} + +static irqreturn_t mma9553_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mma9553_data *data = iio_priv(indio_dev); + u16 stepcnt; + u8 activity; + struct mma9553_event *ev_activity, *ev_prev_activity, *ev_step_detect; + int ret; + + mutex_lock(&data->mutex); + ret = mma9553_read_activity_stepcnt(data, &activity, &stepcnt); + if (ret < 0) { + mutex_unlock(&data->mutex); + return IRQ_HANDLED; + } + + ev_prev_activity = + mma9553_get_event(data, IIO_ACTIVITY, + mma9553_activity_to_mod(data->activity), + IIO_EV_DIR_FALLING); + ev_activity = + mma9553_get_event(data, IIO_ACTIVITY, + mma9553_activity_to_mod(activity), + IIO_EV_DIR_RISING); + ev_step_detect = + mma9553_get_event(data, IIO_STEPS, IIO_NO_MOD, IIO_EV_DIR_NONE); + + if (ev_step_detect->enabled && (stepcnt != data->stepcnt)) { + data->stepcnt = stepcnt; + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_STEPS, 0, IIO_NO_MOD, + IIO_EV_DIR_NONE, IIO_EV_TYPE_CHANGE, 0, 0, 0), + data->timestamp); + } + + if (activity != data->activity) { + data->activity = activity; + /* ev_activity can be NULL if activity == ACTIVITY_UNKNOWN */ + if (ev_prev_activity && ev_prev_activity->enabled) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_ACTIVITY, 0, + ev_prev_activity->info->mod, + IIO_EV_DIR_FALLING, + IIO_EV_TYPE_THRESH, 0, 0, 0), + data->timestamp); + + if (ev_activity && ev_activity->enabled) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_ACTIVITY, 0, + ev_activity->info->mod, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, 0, 0, 0), + data->timestamp); + } + mutex_unlock(&data->mutex); + + return IRQ_HANDLED; +} + +static int mma9553_gpio_probe(struct i2c_client *client) +{ + struct device *dev; + struct gpio_desc *gpio; + int ret; + + if (!client) + return -EINVAL; + + dev = &client->dev; + + /* data ready gpio interrupt pin */ + gpio = devm_gpiod_get_index(dev, MMA9553_GPIO_NAME, 0, GPIOD_IN); + if (IS_ERR(gpio)) { + dev_err(dev, "acpi gpio get index failed\n"); + return PTR_ERR(gpio); + } + + ret = gpiod_to_irq(gpio); + + dev_dbg(dev, "gpio resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret); + + return ret; +} + +static const char *mma9553_match_acpi_device(struct device *dev) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + + return dev_name(dev); +} + +static int mma9553_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mma9553_data *data; + struct iio_dev *indio_dev; + const char *name = NULL; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + if (id) + name = id->name; + else if (ACPI_HANDLE(&client->dev)) + name = mma9553_match_acpi_device(&client->dev); + else + return -ENOSYS; + + mutex_init(&data->mutex); + mma9553_init_events(data); + + ret = mma9553_init(data); + if (ret < 0) + return ret; + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = mma9553_channels; + indio_dev->num_channels = ARRAY_SIZE(mma9553_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mma9553_info; + + if (client->irq < 0) + client->irq = mma9553_gpio_probe(client); + + if (client->irq >= 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + mma9553_irq_handler, + mma9553_event_handler, + IRQF_TRIGGER_RISING, + MMA9553_IRQ_NAME, indio_dev); + if (ret < 0) { + dev_err(&client->dev, "request irq %d failed\n", + client->irq); + goto out_poweroff; + } + + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto out_poweroff; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_iio_unregister; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + MMA9551_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + dev_dbg(&indio_dev->dev, "Registered device %s\n", name); + + return 0; + +out_iio_unregister: + iio_device_unregister(indio_dev); +out_poweroff: + mma9551_set_device_state(client, false); + return ret; +} + +static int mma9553_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct mma9553_data *data = iio_priv(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + iio_device_unregister(indio_dev); + mutex_lock(&data->mutex); + mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + + return 0; +} + +#ifdef CONFIG_PM +static int mma9553_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + if (ret < 0) { + dev_err(&data->client->dev, "powering off device failed\n"); + return -EAGAIN; + } + + return 0; +} + +static int mma9553_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + ret = mma9551_set_device_state(data->client, true); + if (ret < 0) + return ret; + + mma9551_sleep(MMA9553_DEFAULT_SAMPLE_RATE); + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int mma9553_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, false); + mutex_unlock(&data->mutex); + + return ret; +} + +static int mma9553_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma9553_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = mma9551_set_device_state(data->client, true); + mutex_unlock(&data->mutex); + + return ret; +} +#endif + +static const struct dev_pm_ops mma9553_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mma9553_suspend, mma9553_resume) + SET_RUNTIME_PM_OPS(mma9553_runtime_suspend, + mma9553_runtime_resume, NULL) +}; + +static const struct acpi_device_id mma9553_acpi_match[] = { + {"MMA9553", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(acpi, mma9553_acpi_match); + +static const struct i2c_device_id mma9553_id[] = { + {"mma9553", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mma9553_id); + +static struct i2c_driver mma9553_driver = { + .driver = { + .name = MMA9553_DRV_NAME, + .acpi_match_table = ACPI_PTR(mma9553_acpi_match), + .pm = &mma9553_pm_ops, + }, + .probe = mma9553_probe, + .remove = mma9553_remove, + .id_table = mma9553_id, +}; + +module_i2c_driver(mma9553_driver); + +MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MMA9553L pedometer platform driver"); diff --git a/drivers/iio/accel/ssp_accel_sensor.c b/drivers/iio/accel/ssp_accel_sensor.c new file mode 100644 index 000000000..4ae05fce9 --- /dev/null +++ b/drivers/iio/accel/ssp_accel_sensor.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * 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. + * + */ + +#include <linux/iio/common/ssp_sensors.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include "../common/ssp_sensors/ssp_iio_sensor.h" + +#define SSP_CHANNEL_COUNT 3 + +#define SSP_ACCEL_NAME "ssp-accelerometer" +static const char ssp_accel_device_name[] = SSP_ACCEL_NAME; + +enum ssp_accel_3d_channel { + SSP_CHANNEL_SCAN_INDEX_X, + SSP_CHANNEL_SCAN_INDEX_Y, + SSP_CHANNEL_SCAN_INDEX_Z, + SSP_CHANNEL_SCAN_INDEX_TIME, +}; + +static int ssp_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + u32 t; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + t = ssp_get_sensor_delay(data, SSP_ACCELEROMETER_SENSOR); + ssp_convert_to_freq(t, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + + return -EINVAL; +} + +static int ssp_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + int ret; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ssp_convert_to_time(val, val2); + ret = ssp_change_delay(data, SSP_ACCELEROMETER_SENSOR, ret); + if (ret < 0) + dev_err(&indio_dev->dev, "accel sensor enable fail\n"); + + return ret; + default: + break; + } + + return -EINVAL; +} + +static struct iio_info ssp_accel_iio_info = { + .read_raw = &ssp_accel_read_raw, + .write_raw = &ssp_accel_write_raw, +}; + +static const unsigned long ssp_accel_scan_mask[] = { 0x7, 0, }; + +static const struct iio_chan_spec ssp_acc_channels[] = { + SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_X, SSP_CHANNEL_SCAN_INDEX_X), + SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_Y, SSP_CHANNEL_SCAN_INDEX_Y), + SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_Z, SSP_CHANNEL_SCAN_INDEX_Z), + SSP_CHAN_TIMESTAMP(SSP_CHANNEL_SCAN_INDEX_TIME), +}; + +static int ssp_process_accel_data(struct iio_dev *indio_dev, void *buf, + int64_t timestamp) +{ + return ssp_common_process_data(indio_dev, buf, SSP_ACCELEROMETER_SIZE, + timestamp); +} + +static const struct iio_buffer_setup_ops ssp_accel_buffer_ops = { + .postenable = &ssp_common_buffer_postenable, + .postdisable = &ssp_common_buffer_postdisable, +}; + +static int ssp_accel_probe(struct platform_device *pdev) +{ + int ret; + struct iio_dev *indio_dev; + struct ssp_sensor_data *spd; + struct iio_buffer *buffer; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*spd)); + if (!indio_dev) + return -ENOMEM; + + spd = iio_priv(indio_dev); + + spd->process_data = ssp_process_accel_data; + spd->type = SSP_ACCELEROMETER_SENSOR; + + indio_dev->name = ssp_accel_device_name; + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &ssp_accel_iio_info; + indio_dev->modes = INDIO_BUFFER_SOFTWARE; + indio_dev->channels = ssp_acc_channels; + indio_dev->num_channels = ARRAY_SIZE(ssp_acc_channels); + indio_dev->available_scan_masks = ssp_accel_scan_mask; + + buffer = devm_iio_kfifo_allocate(&pdev->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->setup_ops = &ssp_accel_buffer_ops; + + platform_set_drvdata(pdev, indio_dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + return ret; + + /* ssp registering should be done after all iio setup */ + ssp_register_consumer(indio_dev, SSP_ACCELEROMETER_SENSOR); + + return 0; +} + +static int ssp_accel_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + + return 0; +} + +static struct platform_driver ssp_accel_driver = { + .driver = { + .name = SSP_ACCEL_NAME, + }, + .probe = ssp_accel_probe, + .remove = ssp_accel_remove, +}; + +module_platform_driver(ssp_accel_driver); + +MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>"); +MODULE_DESCRIPTION("Samsung sensorhub accelerometers driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/st_accel.h b/drivers/iio/accel/st_accel.h new file mode 100644 index 000000000..7ee9724b1 --- /dev/null +++ b/drivers/iio/accel/st_accel.h @@ -0,0 +1,56 @@ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * v. 1.0.0 + * Licensed under the GPL-2. + */ + +#ifndef ST_ACCEL_H +#define ST_ACCEL_H + +#include <linux/types.h> +#include <linux/iio/common/st_sensors.h> + +#define LIS3LV02DL_ACCEL_DEV_NAME "lis3lv02dl_accel" +#define LSM303DLHC_ACCEL_DEV_NAME "lsm303dlhc_accel" +#define LIS3DH_ACCEL_DEV_NAME "lis3dh" +#define LSM330D_ACCEL_DEV_NAME "lsm330d_accel" +#define LSM330DL_ACCEL_DEV_NAME "lsm330dl_accel" +#define LSM330DLC_ACCEL_DEV_NAME "lsm330dlc_accel" +#define LIS331DLH_ACCEL_DEV_NAME "lis331dlh" +#define LSM303DL_ACCEL_DEV_NAME "lsm303dl_accel" +#define LSM303DLH_ACCEL_DEV_NAME "lsm303dlh_accel" +#define LSM303DLM_ACCEL_DEV_NAME "lsm303dlm_accel" +#define LSM330_ACCEL_DEV_NAME "lsm330_accel" + +/** +* struct st_sensors_platform_data - default accel platform data +* @drdy_int_pin: default accel DRDY is available on INT1 pin. +*/ +static const struct st_sensors_platform_data default_accel_pdata = { + .drdy_int_pin = 1, +}; + +int st_accel_common_probe(struct iio_dev *indio_dev); +void st_accel_common_remove(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +int st_accel_allocate_ring(struct iio_dev *indio_dev); +void st_accel_deallocate_ring(struct iio_dev *indio_dev); +int st_accel_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_ACCEL_TRIGGER_SET_STATE (&st_accel_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_accel_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +static inline void st_accel_deallocate_ring(struct iio_dev *indio_dev) +{ +} +#define ST_ACCEL_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_ACCEL_H */ diff --git a/drivers/iio/accel/st_accel_buffer.c b/drivers/iio/accel/st_accel_buffer.c new file mode 100644 index 000000000..a1e642ee1 --- /dev/null +++ b/drivers/iio/accel/st_accel_buffer.c @@ -0,0 +1,105 @@ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_accel.h" + +int st_accel_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + + return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_accel_buffer_preenable(struct iio_dev *indio_dev) +{ + return st_sensors_set_enable(indio_dev, true); +} + +static int st_accel_buffer_postenable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *adata = iio_priv(indio_dev); + + adata->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (adata->buffer_data == NULL) { + err = -ENOMEM; + goto allocate_memory_error; + } + + err = st_sensors_set_axis_enable(indio_dev, + (u8)indio_dev->active_scan_mask[0]); + if (err < 0) + goto st_accel_buffer_postenable_error; + + err = iio_triggered_buffer_postenable(indio_dev); + if (err < 0) + goto st_accel_buffer_postenable_error; + + return err; + +st_accel_buffer_postenable_error: + kfree(adata->buffer_data); +allocate_memory_error: + return err; +} + +static int st_accel_buffer_predisable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *adata = iio_priv(indio_dev); + + err = iio_triggered_buffer_predisable(indio_dev); + if (err < 0) + goto st_accel_buffer_predisable_error; + + err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); + if (err < 0) + goto st_accel_buffer_predisable_error; + + err = st_sensors_set_enable(indio_dev, false); + +st_accel_buffer_predisable_error: + kfree(adata->buffer_data); + return err; +} + +static const struct iio_buffer_setup_ops st_accel_buffer_setup_ops = { + .preenable = &st_accel_buffer_preenable, + .postenable = &st_accel_buffer_postenable, + .predisable = &st_accel_buffer_predisable, +}; + +int st_accel_allocate_ring(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &st_sensors_trigger_handler, &st_accel_buffer_setup_ops); +} + +void st_accel_deallocate_ring(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics accelerometers buffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/st_accel_core.c b/drivers/iio/accel/st_accel_core.c new file mode 100644 index 000000000..211b13271 --- /dev/null +++ b/drivers/iio/accel/st_accel_core.c @@ -0,0 +1,622 @@ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_accel.h" + +#define ST_ACCEL_NUMBER_DATA_CHANNELS 3 + +/* DEFAULT VALUE FOR SENSORS */ +#define ST_ACCEL_DEFAULT_OUT_X_L_ADDR 0x28 +#define ST_ACCEL_DEFAULT_OUT_Y_L_ADDR 0x2a +#define ST_ACCEL_DEFAULT_OUT_Z_L_ADDR 0x2c + +/* FULLSCALE */ +#define ST_ACCEL_FS_AVL_2G 2 +#define ST_ACCEL_FS_AVL_4G 4 +#define ST_ACCEL_FS_AVL_6G 6 +#define ST_ACCEL_FS_AVL_8G 8 +#define ST_ACCEL_FS_AVL_16G 16 + +/* CUSTOM VALUES FOR SENSOR 1 */ +#define ST_ACCEL_1_WAI_EXP 0x33 +#define ST_ACCEL_1_ODR_ADDR 0x20 +#define ST_ACCEL_1_ODR_MASK 0xf0 +#define ST_ACCEL_1_ODR_AVL_1HZ_VAL 0x01 +#define ST_ACCEL_1_ODR_AVL_10HZ_VAL 0x02 +#define ST_ACCEL_1_ODR_AVL_25HZ_VAL 0x03 +#define ST_ACCEL_1_ODR_AVL_50HZ_VAL 0x04 +#define ST_ACCEL_1_ODR_AVL_100HZ_VAL 0x05 +#define ST_ACCEL_1_ODR_AVL_200HZ_VAL 0x06 +#define ST_ACCEL_1_ODR_AVL_400HZ_VAL 0x07 +#define ST_ACCEL_1_ODR_AVL_1600HZ_VAL 0x08 +#define ST_ACCEL_1_FS_ADDR 0x23 +#define ST_ACCEL_1_FS_MASK 0x30 +#define ST_ACCEL_1_FS_AVL_2_VAL 0x00 +#define ST_ACCEL_1_FS_AVL_4_VAL 0x01 +#define ST_ACCEL_1_FS_AVL_8_VAL 0x02 +#define ST_ACCEL_1_FS_AVL_16_VAL 0x03 +#define ST_ACCEL_1_FS_AVL_2_GAIN IIO_G_TO_M_S_2(1000) +#define ST_ACCEL_1_FS_AVL_4_GAIN IIO_G_TO_M_S_2(2000) +#define ST_ACCEL_1_FS_AVL_8_GAIN IIO_G_TO_M_S_2(4000) +#define ST_ACCEL_1_FS_AVL_16_GAIN IIO_G_TO_M_S_2(12000) +#define ST_ACCEL_1_BDU_ADDR 0x23 +#define ST_ACCEL_1_BDU_MASK 0x80 +#define ST_ACCEL_1_DRDY_IRQ_ADDR 0x22 +#define ST_ACCEL_1_DRDY_IRQ_INT1_MASK 0x10 +#define ST_ACCEL_1_DRDY_IRQ_INT2_MASK 0x08 +#define ST_ACCEL_1_MULTIREAD_BIT true + +/* CUSTOM VALUES FOR SENSOR 2 */ +#define ST_ACCEL_2_WAI_EXP 0x32 +#define ST_ACCEL_2_ODR_ADDR 0x20 +#define ST_ACCEL_2_ODR_MASK 0x18 +#define ST_ACCEL_2_ODR_AVL_50HZ_VAL 0x00 +#define ST_ACCEL_2_ODR_AVL_100HZ_VAL 0x01 +#define ST_ACCEL_2_ODR_AVL_400HZ_VAL 0x02 +#define ST_ACCEL_2_ODR_AVL_1000HZ_VAL 0x03 +#define ST_ACCEL_2_PW_ADDR 0x20 +#define ST_ACCEL_2_PW_MASK 0xe0 +#define ST_ACCEL_2_FS_ADDR 0x23 +#define ST_ACCEL_2_FS_MASK 0x30 +#define ST_ACCEL_2_FS_AVL_2_VAL 0X00 +#define ST_ACCEL_2_FS_AVL_4_VAL 0X01 +#define ST_ACCEL_2_FS_AVL_8_VAL 0x03 +#define ST_ACCEL_2_FS_AVL_2_GAIN IIO_G_TO_M_S_2(1000) +#define ST_ACCEL_2_FS_AVL_4_GAIN IIO_G_TO_M_S_2(2000) +#define ST_ACCEL_2_FS_AVL_8_GAIN IIO_G_TO_M_S_2(3900) +#define ST_ACCEL_2_BDU_ADDR 0x23 +#define ST_ACCEL_2_BDU_MASK 0x80 +#define ST_ACCEL_2_DRDY_IRQ_ADDR 0x22 +#define ST_ACCEL_2_DRDY_IRQ_INT1_MASK 0x02 +#define ST_ACCEL_2_DRDY_IRQ_INT2_MASK 0x10 +#define ST_ACCEL_2_MULTIREAD_BIT true + +/* CUSTOM VALUES FOR SENSOR 3 */ +#define ST_ACCEL_3_WAI_EXP 0x40 +#define ST_ACCEL_3_ODR_ADDR 0x20 +#define ST_ACCEL_3_ODR_MASK 0xf0 +#define ST_ACCEL_3_ODR_AVL_3HZ_VAL 0x01 +#define ST_ACCEL_3_ODR_AVL_6HZ_VAL 0x02 +#define ST_ACCEL_3_ODR_AVL_12HZ_VAL 0x03 +#define ST_ACCEL_3_ODR_AVL_25HZ_VAL 0x04 +#define ST_ACCEL_3_ODR_AVL_50HZ_VAL 0x05 +#define ST_ACCEL_3_ODR_AVL_100HZ_VAL 0x06 +#define ST_ACCEL_3_ODR_AVL_200HZ_VAL 0x07 +#define ST_ACCEL_3_ODR_AVL_400HZ_VAL 0x08 +#define ST_ACCEL_3_ODR_AVL_800HZ_VAL 0x09 +#define ST_ACCEL_3_ODR_AVL_1600HZ_VAL 0x0a +#define ST_ACCEL_3_FS_ADDR 0x24 +#define ST_ACCEL_3_FS_MASK 0x38 +#define ST_ACCEL_3_FS_AVL_2_VAL 0X00 +#define ST_ACCEL_3_FS_AVL_4_VAL 0X01 +#define ST_ACCEL_3_FS_AVL_6_VAL 0x02 +#define ST_ACCEL_3_FS_AVL_8_VAL 0x03 +#define ST_ACCEL_3_FS_AVL_16_VAL 0x04 +#define ST_ACCEL_3_FS_AVL_2_GAIN IIO_G_TO_M_S_2(61) +#define ST_ACCEL_3_FS_AVL_4_GAIN IIO_G_TO_M_S_2(122) +#define ST_ACCEL_3_FS_AVL_6_GAIN IIO_G_TO_M_S_2(183) +#define ST_ACCEL_3_FS_AVL_8_GAIN IIO_G_TO_M_S_2(244) +#define ST_ACCEL_3_FS_AVL_16_GAIN IIO_G_TO_M_S_2(732) +#define ST_ACCEL_3_BDU_ADDR 0x20 +#define ST_ACCEL_3_BDU_MASK 0x08 +#define ST_ACCEL_3_DRDY_IRQ_ADDR 0x23 +#define ST_ACCEL_3_DRDY_IRQ_INT1_MASK 0x80 +#define ST_ACCEL_3_DRDY_IRQ_INT2_MASK 0x00 +#define ST_ACCEL_3_IG1_EN_ADDR 0x23 +#define ST_ACCEL_3_IG1_EN_MASK 0x08 +#define ST_ACCEL_3_MULTIREAD_BIT false + +/* CUSTOM VALUES FOR SENSOR 4 */ +#define ST_ACCEL_4_WAI_EXP 0x3a +#define ST_ACCEL_4_ODR_ADDR 0x20 +#define ST_ACCEL_4_ODR_MASK 0x30 /* DF1 and DF0 */ +#define ST_ACCEL_4_ODR_AVL_40HZ_VAL 0x00 +#define ST_ACCEL_4_ODR_AVL_160HZ_VAL 0x01 +#define ST_ACCEL_4_ODR_AVL_640HZ_VAL 0x02 +#define ST_ACCEL_4_ODR_AVL_2560HZ_VAL 0x03 +#define ST_ACCEL_4_PW_ADDR 0x20 +#define ST_ACCEL_4_PW_MASK 0xc0 +#define ST_ACCEL_4_FS_ADDR 0x21 +#define ST_ACCEL_4_FS_MASK 0x80 +#define ST_ACCEL_4_FS_AVL_2_VAL 0X00 +#define ST_ACCEL_4_FS_AVL_6_VAL 0X01 +#define ST_ACCEL_4_FS_AVL_2_GAIN IIO_G_TO_M_S_2(1024) +#define ST_ACCEL_4_FS_AVL_6_GAIN IIO_G_TO_M_S_2(340) +#define ST_ACCEL_4_BDU_ADDR 0x21 +#define ST_ACCEL_4_BDU_MASK 0x40 +#define ST_ACCEL_4_DRDY_IRQ_ADDR 0x21 +#define ST_ACCEL_4_DRDY_IRQ_INT1_MASK 0x04 +#define ST_ACCEL_4_IG1_EN_ADDR 0x21 +#define ST_ACCEL_4_IG1_EN_MASK 0x08 +#define ST_ACCEL_4_MULTIREAD_BIT true + +static const struct iio_chan_spec st_accel_12bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 12, 16, + ST_ACCEL_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 12, 16, + ST_ACCEL_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 12, 16, + ST_ACCEL_DEFAULT_OUT_Z_L_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_accel_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16, + ST_ACCEL_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16, + ST_ACCEL_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16, + ST_ACCEL_DEFAULT_OUT_Z_L_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct st_sensor_settings st_accel_sensors_settings[] = { + { + .wai = ST_ACCEL_1_WAI_EXP, + .sensors_supported = { + [0] = LIS3DH_ACCEL_DEV_NAME, + [1] = LSM303DLHC_ACCEL_DEV_NAME, + [2] = LSM330D_ACCEL_DEV_NAME, + [3] = LSM330DL_ACCEL_DEV_NAME, + [4] = LSM330DLC_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = ST_ACCEL_1_ODR_ADDR, + .mask = ST_ACCEL_1_ODR_MASK, + .odr_avl = { + { 1, ST_ACCEL_1_ODR_AVL_1HZ_VAL, }, + { 10, ST_ACCEL_1_ODR_AVL_10HZ_VAL, }, + { 25, ST_ACCEL_1_ODR_AVL_25HZ_VAL, }, + { 50, ST_ACCEL_1_ODR_AVL_50HZ_VAL, }, + { 100, ST_ACCEL_1_ODR_AVL_100HZ_VAL, }, + { 200, ST_ACCEL_1_ODR_AVL_200HZ_VAL, }, + { 400, ST_ACCEL_1_ODR_AVL_400HZ_VAL, }, + { 1600, ST_ACCEL_1_ODR_AVL_1600HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_ACCEL_1_ODR_ADDR, + .mask = ST_ACCEL_1_ODR_MASK, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = ST_ACCEL_1_FS_ADDR, + .mask = ST_ACCEL_1_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = ST_ACCEL_1_FS_AVL_2_VAL, + .gain = ST_ACCEL_1_FS_AVL_2_GAIN, + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = ST_ACCEL_1_FS_AVL_4_VAL, + .gain = ST_ACCEL_1_FS_AVL_4_GAIN, + }, + [2] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = ST_ACCEL_1_FS_AVL_8_VAL, + .gain = ST_ACCEL_1_FS_AVL_8_GAIN, + }, + [3] = { + .num = ST_ACCEL_FS_AVL_16G, + .value = ST_ACCEL_1_FS_AVL_16_VAL, + .gain = ST_ACCEL_1_FS_AVL_16_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_ACCEL_1_BDU_ADDR, + .mask = ST_ACCEL_1_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_ACCEL_1_DRDY_IRQ_ADDR, + .mask_int1 = ST_ACCEL_1_DRDY_IRQ_INT1_MASK, + .mask_int2 = ST_ACCEL_1_DRDY_IRQ_INT2_MASK, + }, + .multi_read_bit = ST_ACCEL_1_MULTIREAD_BIT, + .bootime = 2, + }, + { + .wai = ST_ACCEL_2_WAI_EXP, + .sensors_supported = { + [0] = LIS331DLH_ACCEL_DEV_NAME, + [1] = LSM303DL_ACCEL_DEV_NAME, + [2] = LSM303DLH_ACCEL_DEV_NAME, + [3] = LSM303DLM_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = ST_ACCEL_2_ODR_ADDR, + .mask = ST_ACCEL_2_ODR_MASK, + .odr_avl = { + { 50, ST_ACCEL_2_ODR_AVL_50HZ_VAL, }, + { 100, ST_ACCEL_2_ODR_AVL_100HZ_VAL, }, + { 400, ST_ACCEL_2_ODR_AVL_400HZ_VAL, }, + { 1000, ST_ACCEL_2_ODR_AVL_1000HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_ACCEL_2_PW_ADDR, + .mask = ST_ACCEL_2_PW_MASK, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = ST_ACCEL_2_FS_ADDR, + .mask = ST_ACCEL_2_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = ST_ACCEL_2_FS_AVL_2_VAL, + .gain = ST_ACCEL_2_FS_AVL_2_GAIN, + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = ST_ACCEL_2_FS_AVL_4_VAL, + .gain = ST_ACCEL_2_FS_AVL_4_GAIN, + }, + [2] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = ST_ACCEL_2_FS_AVL_8_VAL, + .gain = ST_ACCEL_2_FS_AVL_8_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_ACCEL_2_BDU_ADDR, + .mask = ST_ACCEL_2_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_ACCEL_2_DRDY_IRQ_ADDR, + .mask_int1 = ST_ACCEL_2_DRDY_IRQ_INT1_MASK, + .mask_int2 = ST_ACCEL_2_DRDY_IRQ_INT2_MASK, + }, + .multi_read_bit = ST_ACCEL_2_MULTIREAD_BIT, + .bootime = 2, + }, + { + .wai = ST_ACCEL_3_WAI_EXP, + .sensors_supported = { + [0] = LSM330_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_16bit_channels, + .odr = { + .addr = ST_ACCEL_3_ODR_ADDR, + .mask = ST_ACCEL_3_ODR_MASK, + .odr_avl = { + { 3, ST_ACCEL_3_ODR_AVL_3HZ_VAL }, + { 6, ST_ACCEL_3_ODR_AVL_6HZ_VAL, }, + { 12, ST_ACCEL_3_ODR_AVL_12HZ_VAL, }, + { 25, ST_ACCEL_3_ODR_AVL_25HZ_VAL, }, + { 50, ST_ACCEL_3_ODR_AVL_50HZ_VAL, }, + { 100, ST_ACCEL_3_ODR_AVL_100HZ_VAL, }, + { 200, ST_ACCEL_3_ODR_AVL_200HZ_VAL, }, + { 400, ST_ACCEL_3_ODR_AVL_400HZ_VAL, }, + { 800, ST_ACCEL_3_ODR_AVL_800HZ_VAL, }, + { 1600, ST_ACCEL_3_ODR_AVL_1600HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_ACCEL_3_ODR_ADDR, + .mask = ST_ACCEL_3_ODR_MASK, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = ST_ACCEL_3_FS_ADDR, + .mask = ST_ACCEL_3_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = ST_ACCEL_3_FS_AVL_2_VAL, + .gain = ST_ACCEL_3_FS_AVL_2_GAIN, + }, + [1] = { + .num = ST_ACCEL_FS_AVL_4G, + .value = ST_ACCEL_3_FS_AVL_4_VAL, + .gain = ST_ACCEL_3_FS_AVL_4_GAIN, + }, + [2] = { + .num = ST_ACCEL_FS_AVL_6G, + .value = ST_ACCEL_3_FS_AVL_6_VAL, + .gain = ST_ACCEL_3_FS_AVL_6_GAIN, + }, + [3] = { + .num = ST_ACCEL_FS_AVL_8G, + .value = ST_ACCEL_3_FS_AVL_8_VAL, + .gain = ST_ACCEL_3_FS_AVL_8_GAIN, + }, + [4] = { + .num = ST_ACCEL_FS_AVL_16G, + .value = ST_ACCEL_3_FS_AVL_16_VAL, + .gain = ST_ACCEL_3_FS_AVL_16_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_ACCEL_3_BDU_ADDR, + .mask = ST_ACCEL_3_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_ACCEL_3_DRDY_IRQ_ADDR, + .mask_int1 = ST_ACCEL_3_DRDY_IRQ_INT1_MASK, + .mask_int2 = ST_ACCEL_3_DRDY_IRQ_INT2_MASK, + .ig1 = { + .en_addr = ST_ACCEL_3_IG1_EN_ADDR, + .en_mask = ST_ACCEL_3_IG1_EN_MASK, + }, + }, + .multi_read_bit = ST_ACCEL_3_MULTIREAD_BIT, + .bootime = 2, + }, + { + .wai = ST_ACCEL_4_WAI_EXP, + .sensors_supported = { + [0] = LIS3LV02DL_ACCEL_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = ST_ACCEL_4_ODR_ADDR, + .mask = ST_ACCEL_4_ODR_MASK, + .odr_avl = { + { 40, ST_ACCEL_4_ODR_AVL_40HZ_VAL }, + { 160, ST_ACCEL_4_ODR_AVL_160HZ_VAL, }, + { 640, ST_ACCEL_4_ODR_AVL_640HZ_VAL, }, + { 2560, ST_ACCEL_4_ODR_AVL_2560HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_ACCEL_4_PW_ADDR, + .mask = ST_ACCEL_4_PW_MASK, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = ST_ACCEL_4_FS_ADDR, + .mask = ST_ACCEL_4_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_2G, + .value = ST_ACCEL_4_FS_AVL_2_VAL, + .gain = ST_ACCEL_4_FS_AVL_2_GAIN, + }, + [1] = { + .num = ST_ACCEL_FS_AVL_6G, + .value = ST_ACCEL_4_FS_AVL_6_VAL, + .gain = ST_ACCEL_4_FS_AVL_6_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_ACCEL_4_BDU_ADDR, + .mask = ST_ACCEL_4_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_ACCEL_4_DRDY_IRQ_ADDR, + .mask_int1 = ST_ACCEL_4_DRDY_IRQ_INT1_MASK, + .ig1 = { + .en_addr = ST_ACCEL_4_IG1_EN_ADDR, + .en_mask = ST_ACCEL_4_IG1_EN_MASK, + }, + }, + .multi_read_bit = ST_ACCEL_4_MULTIREAD_BIT, + .bootime = 2, /* guess */ + }, +}; + +static int st_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + struct st_sensor_data *adata = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = st_sensors_read_info_raw(indio_dev, ch, val); + if (err < 0) + goto read_error; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = adata->current_fullscale->gain; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = adata->odr; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static int st_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + int err; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + err = st_sensors_set_fullscale_by_gain(indio_dev, val2); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + mutex_lock(&indio_dev->mlock); + err = st_sensors_set_odr(indio_dev, val); + mutex_unlock(&indio_dev->mlock); + return err; + default: + return -EINVAL; + } + + return err; +} + +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); +static ST_SENSORS_DEV_ATTR_SCALE_AVAIL(in_accel_scale_available); + +static struct attribute *st_accel_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_accel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_accel_attribute_group = { + .attrs = st_accel_attributes, +}; + +static const struct iio_info accel_info = { + .driver_module = THIS_MODULE, + .attrs = &st_accel_attribute_group, + .read_raw = &st_accel_read_raw, + .write_raw = &st_accel_write_raw, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_accel_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = ST_ACCEL_TRIGGER_SET_STATE, +}; +#define ST_ACCEL_TRIGGER_OPS (&st_accel_trigger_ops) +#else +#define ST_ACCEL_TRIGGER_OPS NULL +#endif + +int st_accel_common_probe(struct iio_dev *indio_dev) +{ + struct st_sensor_data *adata = iio_priv(indio_dev); + int irq = adata->get_irq_data_ready(indio_dev); + int err; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &accel_info; + mutex_init(&adata->tb.buf_lock); + + st_sensors_power_enable(indio_dev); + + err = st_sensors_check_device_support(indio_dev, + ARRAY_SIZE(st_accel_sensors_settings), + st_accel_sensors_settings); + if (err < 0) + return err; + + adata->num_data_channels = ST_ACCEL_NUMBER_DATA_CHANNELS; + adata->multiread_bit = adata->sensor_settings->multi_read_bit; + indio_dev->channels = adata->sensor_settings->ch; + indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + + adata->current_fullscale = (struct st_sensor_fullscale_avl *) + &adata->sensor_settings->fs.fs_avl[0]; + adata->odr = adata->sensor_settings->odr.odr_avl[0].hz; + + if (!adata->dev->platform_data) + adata->dev->platform_data = + (struct st_sensors_platform_data *)&default_accel_pdata; + + err = st_sensors_init_sensor(indio_dev, adata->dev->platform_data); + if (err < 0) + return err; + + err = st_accel_allocate_ring(indio_dev); + if (err < 0) + return err; + + if (irq > 0) { + err = st_sensors_allocate_trigger(indio_dev, + ST_ACCEL_TRIGGER_OPS); + if (err < 0) + goto st_accel_probe_trigger_error; + } + + err = iio_device_register(indio_dev); + if (err) + goto st_accel_device_register_error; + + dev_info(&indio_dev->dev, "registered accelerometer %s\n", + indio_dev->name); + + return 0; + +st_accel_device_register_error: + if (irq > 0) + st_sensors_deallocate_trigger(indio_dev); +st_accel_probe_trigger_error: + st_accel_deallocate_ring(indio_dev); + + return err; +} +EXPORT_SYMBOL(st_accel_common_probe); + +void st_accel_common_remove(struct iio_dev *indio_dev) +{ + struct st_sensor_data *adata = iio_priv(indio_dev); + + st_sensors_power_disable(indio_dev); + + iio_device_unregister(indio_dev); + if (adata->get_irq_data_ready(indio_dev) > 0) + st_sensors_deallocate_trigger(indio_dev); + + st_accel_deallocate_ring(indio_dev); +} +EXPORT_SYMBOL(st_accel_common_remove); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics accelerometers driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c new file mode 100644 index 000000000..6b720c190 --- /dev/null +++ b/drivers/iio/accel/st_accel_i2c.c @@ -0,0 +1,133 @@ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_i2c.h> +#include "st_accel.h" + +#ifdef CONFIG_OF +static const struct of_device_id st_accel_of_match[] = { + { + .compatible = "st,lis3lv02dl-accel", + .data = LIS3LV02DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303dlh-accel", + .data = LSM303DLH_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303dlhc-accel", + .data = LSM303DLHC_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis3dh-accel", + .data = LIS3DH_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330d-accel", + .data = LSM330D_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330dl-accel", + .data = LSM330DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330dlc-accel", + .data = LSM330DLC_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lis331dlh-accel", + .data = LIS331DLH_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303dl-accel", + .data = LSM303DL_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm303dlm-accel", + .data = LSM303DLM_ACCEL_DEV_NAME, + }, + { + .compatible = "st,lsm330-accel", + .data = LSM330_ACCEL_DEV_NAME, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_accel_of_match); +#else +#define st_accel_of_match NULL +#endif + +static int st_accel_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *adata; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adata)); + if (!indio_dev) + return -ENOMEM; + + adata = iio_priv(indio_dev); + st_sensors_of_i2c_probe(client, st_accel_of_match); + + st_sensors_i2c_configure(indio_dev, client, adata); + + err = st_accel_common_probe(indio_dev); + if (err < 0) + return err; + + return 0; +} + +static int st_accel_i2c_remove(struct i2c_client *client) +{ + st_accel_common_remove(i2c_get_clientdata(client)); + + return 0; +} + +static const struct i2c_device_id st_accel_id_table[] = { + { LSM303DLH_ACCEL_DEV_NAME }, + { LSM303DLHC_ACCEL_DEV_NAME }, + { LIS3DH_ACCEL_DEV_NAME }, + { LSM330D_ACCEL_DEV_NAME }, + { LSM330DL_ACCEL_DEV_NAME }, + { LSM330DLC_ACCEL_DEV_NAME }, + { LIS331DLH_ACCEL_DEV_NAME }, + { LSM303DL_ACCEL_DEV_NAME }, + { LSM303DLM_ACCEL_DEV_NAME }, + { LSM330_ACCEL_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, st_accel_id_table); + +static struct i2c_driver st_accel_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-accel-i2c", + .of_match_table = of_match_ptr(st_accel_of_match), + }, + .probe = st_accel_i2c_probe, + .remove = st_accel_i2c_remove, + .id_table = st_accel_id_table, +}; +module_i2c_driver(st_accel_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics accelerometers i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/st_accel_spi.c b/drivers/iio/accel/st_accel_spi.c new file mode 100644 index 000000000..12ec29389 --- /dev/null +++ b/drivers/iio/accel/st_accel_spi.c @@ -0,0 +1,77 @@ +/* + * STMicroelectronics accelerometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_spi.h> +#include "st_accel.h" + +static int st_accel_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *adata; + int err; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adata)); + if (!indio_dev) + return -ENOMEM; + + adata = iio_priv(indio_dev); + + st_sensors_spi_configure(indio_dev, spi, adata); + + err = st_accel_common_probe(indio_dev); + if (err < 0) + return err; + + return 0; +} + +static int st_accel_spi_remove(struct spi_device *spi) +{ + st_accel_common_remove(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id st_accel_id_table[] = { + { LSM303DLH_ACCEL_DEV_NAME }, + { LSM303DLHC_ACCEL_DEV_NAME }, + { LIS3DH_ACCEL_DEV_NAME }, + { LSM330D_ACCEL_DEV_NAME }, + { LSM330DL_ACCEL_DEV_NAME }, + { LSM330DLC_ACCEL_DEV_NAME }, + { LIS331DLH_ACCEL_DEV_NAME }, + { LSM303DL_ACCEL_DEV_NAME }, + { LSM303DLM_ACCEL_DEV_NAME }, + { LSM330_ACCEL_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(spi, st_accel_id_table); + +static struct spi_driver st_accel_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-accel-spi", + }, + .probe = st_accel_spi_probe, + .remove = st_accel_spi_remove, + .id_table = st_accel_id_table, +}; +module_spi_driver(st_accel_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics accelerometers spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig new file mode 100644 index 000000000..1bcb65b8d --- /dev/null +++ b/drivers/iio/adc/Kconfig @@ -0,0 +1,359 @@ +# +# ADC drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Analog to digital converters" + +config AD_SIGMA_DELTA + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config AD7266 + tristate "Analog Devices AD7265/AD7266 ADC driver" + depends on SPI_MASTER + select IIO_BUFFER + select IIO_TRIGGER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD7265 and AD7266 + ADCs. + +config AD7291 + tristate "Analog Devices AD7291 ADC driver" + depends on I2C + help + Say yes here to build support for Analog Devices AD7291 + 8 Channel ADC with temperature sensor. + + To compile this driver as a module, choose M here: the + module will be called ad7291. + +config AD7298 + tristate "Analog Devices AD7298 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD7298 + 8 Channel ADC with temperature sensor. + + To compile this driver as a module, choose M here: the + module will be called ad7298. + +config AD7476 + tristate "Analog Devices AD7476 and similar 1-channel ADCs driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD7273, AD7274, AD7276, + AD7277, AD7278, AD7475, AD7476, AD7477, AD7478, AD7466, AD7467, AD7468, + AD7495, AD7910, AD7920, AD7920 SPI analog to digital converters (ADC). + + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ad7476. + +config AD7791 + tristate "Analog Devices AD7791 ADC driver" + depends on SPI + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7787, AD7788, AD7789, + AD7790 and AD7791 SPI analog to digital converters (ADC). If unsure, say + N (but it is safe to say "Y"). + + To compile this driver as a module, choose M here: the module will be + called ad7791. + +config AD7793 + tristate "Analog Devices AD7793 and similar ADCs driver" + depends on SPI + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7785, AD7792, AD7793, + AD7794 and AD7795 SPI analog to digital converters (ADC). + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called AD7793. + +config AD7887 + tristate "Analog Devices AD7887 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices + AD7887 SPI analog to digital converter (ADC). + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ad7887. + +config AD7923 + tristate "Analog Devices AD7923 and similar ADCs driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices + AD7904, AD7914, AD7923, AD7924 4 Channel ADCs. + + To compile this driver as a module, choose M here: the + module will be called ad7923. + +config AD799X + tristate "Analog Devices AD799x ADC driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices: + ad7991, ad7995, ad7999, ad7992, ad7993, ad7994, ad7997, ad7998 + i2c analog to digital converters (ADC). Provides direct access + via sysfs. + +config AT91_ADC + tristate "Atmel AT91 ADC" + depends on ARCH_AT91 + depends on INPUT + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select SYSFS + help + Say yes here to build support for Atmel AT91 ADC. + +config AXP288_ADC + tristate "X-Powers AXP288 ADC driver" + depends on MFD_AXP20X + help + Say yes here to have support for X-Powers power management IC (PMIC) ADC + device. Depending on platform configuration, this general purpose ADC can + be used for sampling sensors such as thermal resistors. + +config DA9150_GPADC + tristate "Dialog DA9150 GPADC driver support" + depends on MFD_DA9150 + help + Say yes here to build support for Dialog DA9150 GPADC. + + This driver can also be built as a module. If chosen, the module name + will be da9150-gpadc. + +config CC10001_ADC + tristate "Cosmic Circuits 10001 ADC driver" + depends on HAS_IOMEM && HAVE_CLK && REGULATOR + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Cosmic Circuits 10001 ADC. + + This driver can also be built as a module. If so, the module will be + called cc10001_adc. + +config EXYNOS_ADC + tristate "Exynos ADC driver support" + depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST) + help + Core support for the ADC block found in the Samsung EXYNOS series + of SoCs for drivers such as the touchscreen and hwmon to use to share + this resource. + +config LP8788_ADC + tristate "LP8788 ADC driver" + depends on MFD_LP8788 + help + Say yes here to build support for TI LP8788 ADC. + +config MAX1027 + tristate "Maxim max1027 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Maxim SPI ADC models + max1027, max1029 and max1031. + +config MAX1363 + tristate "Maxim max1363 ADC driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for many Maxim i2c analog to digital + converters (ADC). (max1361, max1362, max1363, max1364, max1036, + max1037, max1038, max1039, max1136, max1136, max1137, max1138, + max1139, max1236, max1237, max11238, max1239, max11600, max11601, + max11602, max11603, max11604, max11605, max11606, max11607, + max11608, max11609, max11610, max11611, max11612, max11613, + max11614, max11615, max11616, max11617, max11644, max11645, + max11646, max11647) Provides direct access via sysfs and buffered + data via the iio dev interface. + +config MCP320X + tristate "Microchip Technology MCP3x01/02/04/08" + depends on SPI + help + Say yes here to build support for Microchip Technology's + MCP3001, MCP3002, MCP3004, MCP3008, MCP3201, MCP3202, MCP3204 or + MCP3208 analog to digital converter. + + This driver can also be built as a module. If so, the module will be + called mcp320x. + +config MCP3422 + tristate "Microchip Technology MCP3422/3/4/6/7/8 driver" + depends on I2C + help + Say yes here to build support for Microchip Technology's + MCP3422, MCP3423, MCP3424, MCP3426, MCP3427 or MCP3428 + analog to digital converters. + + This driver can also be built as a module. If so, the module will be + called mcp3422. + +config MEN_Z188_ADC + tristate "MEN 16z188 ADC IP Core support" + depends on MCB + help + Say yes here to enable support for the MEN 16z188 ADC IP-Core on a MCB + carrier. + + This driver can also be built as a module. If so, the module will be + called men_z188_adc. + +config NAU7802 + tristate "Nuvoton NAU7802 ADC driver" + depends on I2C + help + Say yes here to build support for Nuvoton NAU7802 ADC. + + To compile this driver as a module, choose M here: the + module will be called nau7802. + +config QCOM_SPMI_IADC + tristate "Qualcomm SPMI PMIC current ADC" + depends on SPMI + select REGMAP_SPMI + help + This is the IIO Current ADC driver for Qualcomm QPNP IADC Chip. + + The driver supports single mode operation to read from one of two + channels (external or internal). Hardware have additional + channels internally used for gain and offset calibration. + + To compile this driver as a module, choose M here: the module will + be called qcom-spmi-iadc. + +config QCOM_SPMI_VADC + tristate "Qualcomm SPMI PMIC voltage ADC" + depends on SPMI + select REGMAP_SPMI + help + This is the IIO Voltage ADC driver for Qualcomm QPNP VADC Chip. + + The driver supports multiple channels read. The VADC is a 15-bit + sigma-delta ADC. Some of the channels are internally used for + calibration. + + To compile this driver as a module, choose M here: the module will + be called qcom-spmi-vadc. + +config ROCKCHIP_SARADC + tristate "Rockchip SARADC driver" + depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST) + help + Say yes here to build support for the SARADC found in SoCs from + Rockchip. + + To compile this driver as a module, choose M here: the + module will be called rockchip_saradc. + +config TI_ADC081C + tristate "Texas Instruments ADC081C021/027" + depends on I2C + help + If you say yes here you get support for Texas Instruments ADC081C021 + and ADC081C027 ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-adc081c. + +config TI_ADC128S052 + tristate "Texas Instruments ADC128S052" + depends on SPI + help + If you say yes here you get support for Texas Instruments ADC128S052 + chip. + + This driver can also be built as a module. If so, the module will be + called ti-adc128s052. + +config TI_AM335X_ADC + tristate "TI's AM335X ADC driver" + depends on MFD_TI_AM335X_TSCADC + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for Texas Instruments ADC + driver which is also a MFD client. + +config TWL4030_MADC + tristate "TWL4030 MADC (Monitoring A/D Converter)" + depends on TWL4030_CORE + help + This driver provides support for Triton TWL4030-MADC. The + driver supports both RT and SW conversion methods. + + This driver can also be built as a module. If so, the module will be + called twl4030-madc. + +config TWL6030_GPADC + tristate "TWL6030 GPADC (General Purpose A/D Converter) Support" + depends on TWL4030_CORE + default n + help + Say yes here if you want support for the TWL6030/TWL6032 General + Purpose A/D Converter. This will add support for battery type + detection, battery voltage and temperature measurement, die + temperature measurement, system supply voltage, audio accessory, + USB ID detection. + + This driver can also be built as a module. If so, the module will be + called twl6030-gpadc. + +config VF610_ADC + tristate "Freescale vf610 ADC driver" + depends on OF + help + Say yes here to support for Vybrid board analog-to-digital converter. + Since the IP is used for i.MX6SLX, the driver also support i.MX6SLX. + + This driver can also be built as a module. If so, the module will be + called vf610_adc. + +config VIPERBOARD_ADC + tristate "Viperboard ADC support" + depends on MFD_VIPERBOARD && USB + help + Say yes here to access the ADC part of the Nano River + Technologies Viperboard. + +config XILINX_XADC + tristate "Xilinx XADC driver" + depends on ARCH_ZYNQ || MICROBLAZE || COMPILE_TEST + depends on HAS_IOMEM + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to have support for the Xilinx XADC. The driver does support + both the ZYNQ interface to the XADC as well as the AXI-XADC interface. + + The driver can also be build as a module. If so, the module will be called + xilinx-xadc. + +endmenu diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile new file mode 100644 index 000000000..3930e63e8 --- /dev/null +++ b/drivers/iio/adc/Makefile @@ -0,0 +1,39 @@ +# +# Makefile for IIO ADC drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o +obj-$(CONFIG_AD7266) += ad7266.o +obj-$(CONFIG_AD7291) += ad7291.o +obj-$(CONFIG_AD7298) += ad7298.o +obj-$(CONFIG_AD7923) += ad7923.o +obj-$(CONFIG_AD7476) += ad7476.o +obj-$(CONFIG_AD7791) += ad7791.o +obj-$(CONFIG_AD7793) += ad7793.o +obj-$(CONFIG_AD7887) += ad7887.o +obj-$(CONFIG_AD799X) += ad799x.o +obj-$(CONFIG_AT91_ADC) += at91_adc.o +obj-$(CONFIG_AXP288_ADC) += axp288_adc.o +obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o +obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o +obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o +obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o +obj-$(CONFIG_MAX1027) += max1027.o +obj-$(CONFIG_MAX1363) += max1363.o +obj-$(CONFIG_MCP320X) += mcp320x.o +obj-$(CONFIG_MCP3422) += mcp3422.o +obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o +obj-$(CONFIG_NAU7802) += nau7802.o +obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o +obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o +obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o +obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o +obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o +obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o +obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o +obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o +obj-$(CONFIG_VF610_ADC) += vf610_adc.o +obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o +xilinx-xadc-y := xilinx-xadc-core.o xilinx-xadc-events.o +obj-$(CONFIG_XILINX_XADC) += xilinx-xadc.o diff --git a/drivers/iio/adc/ad7266.c b/drivers/iio/adc/ad7266.c new file mode 100644 index 000000000..70f78c306 --- /dev/null +++ b/drivers/iio/adc/ad7266.c @@ -0,0 +1,522 @@ +/* + * AD7266/65 SPI ADC driver + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/module.h> + +#include <linux/interrupt.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/platform_data/ad7266.h> + +struct ad7266_state { + struct spi_device *spi; + struct regulator *reg; + unsigned long vref_mv; + + struct spi_transfer single_xfer[3]; + struct spi_message single_msg; + + enum ad7266_range range; + enum ad7266_mode mode; + bool fixed_addr; + struct gpio gpios[3]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + * The buffer needs to be large enough to hold two samples (4 bytes) and + * the naturally aligned timestamp (8 bytes). + */ + struct { + __be16 sample[2]; + s64 timestamp; + } data ____cacheline_aligned; +}; + +static int ad7266_wakeup(struct ad7266_state *st) +{ + /* Any read with >= 2 bytes will wake the device */ + return spi_read(st->spi, &st->data.sample[0], 2); +} + +static int ad7266_powerdown(struct ad7266_state *st) +{ + /* Any read with < 2 bytes will powerdown the device */ + return spi_read(st->spi, &st->data.sample[0], 1); +} + +static int ad7266_preenable(struct iio_dev *indio_dev) +{ + struct ad7266_state *st = iio_priv(indio_dev); + return ad7266_wakeup(st); +} + +static int ad7266_postdisable(struct iio_dev *indio_dev) +{ + struct ad7266_state *st = iio_priv(indio_dev); + return ad7266_powerdown(st); +} + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .preenable = &ad7266_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &ad7266_postdisable, +}; + +static irqreturn_t ad7266_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7266_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_read(st->spi, st->data.sample, 4); + if (ret == 0) { + iio_push_to_buffers_with_timestamp(indio_dev, &st->data, + pf->timestamp); + } + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void ad7266_select_input(struct ad7266_state *st, unsigned int nr) +{ + unsigned int i; + + if (st->fixed_addr) + return; + + switch (st->mode) { + case AD7266_MODE_SINGLE_ENDED: + nr >>= 1; + break; + case AD7266_MODE_PSEUDO_DIFF: + nr |= 1; + break; + case AD7266_MODE_DIFF: + nr &= ~1; + break; + } + + for (i = 0; i < 3; ++i) + gpio_set_value(st->gpios[i].gpio, (bool)(nr & BIT(i))); +} + +static int ad7266_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7266_state *st = iio_priv(indio_dev); + unsigned int nr = find_first_bit(scan_mask, indio_dev->masklength); + + ad7266_select_input(st, nr); + + return 0; +} + +static int ad7266_read_single(struct ad7266_state *st, int *val, + unsigned int address) +{ + int ret; + + ad7266_select_input(st, address); + + ret = spi_sync(st->spi, &st->single_msg); + *val = be16_to_cpu(st->data.sample[address % 2]); + + return ret; +} + +static int ad7266_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long m) +{ + struct ad7266_state *st = iio_priv(indio_dev); + unsigned long scale_mv; + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + ret = ad7266_read_single(st, val, chan->address); + if (ret) + return ret; + + *val = (*val >> 2) & 0xfff; + if (chan->scan_type.sign == 's') + *val = sign_extend32(*val, 11); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_mv = st->vref_mv; + if (st->mode == AD7266_MODE_DIFF) + scale_mv *= 2; + if (st->range == AD7266_RANGE_2VREF) + scale_mv *= 2; + + *val = scale_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + if (st->range == AD7266_RANGE_2VREF && + st->mode != AD7266_MODE_DIFF) + *val = 2048; + else + *val = 0; + return IIO_VAL_INT; + } + return -EINVAL; +} + +#define AD7266_CHAN(_chan, _sign) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_chan), \ + .address = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = (_chan), \ + .scan_type = { \ + .sign = (_sign), \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 2, \ + .endianness = IIO_BE, \ + }, \ +} + +#define AD7266_DECLARE_SINGLE_ENDED_CHANNELS(_name, _sign) \ +const struct iio_chan_spec ad7266_channels_##_name[] = { \ + AD7266_CHAN(0, (_sign)), \ + AD7266_CHAN(1, (_sign)), \ + AD7266_CHAN(2, (_sign)), \ + AD7266_CHAN(3, (_sign)), \ + AD7266_CHAN(4, (_sign)), \ + AD7266_CHAN(5, (_sign)), \ + AD7266_CHAN(6, (_sign)), \ + AD7266_CHAN(7, (_sign)), \ + AD7266_CHAN(8, (_sign)), \ + AD7266_CHAN(9, (_sign)), \ + AD7266_CHAN(10, (_sign)), \ + AD7266_CHAN(11, (_sign)), \ + IIO_CHAN_SOFT_TIMESTAMP(13), \ +} + +#define AD7266_DECLARE_SINGLE_ENDED_CHANNELS_FIXED(_name, _sign) \ +const struct iio_chan_spec ad7266_channels_##_name##_fixed[] = { \ + AD7266_CHAN(0, (_sign)), \ + AD7266_CHAN(1, (_sign)), \ + IIO_CHAN_SOFT_TIMESTAMP(2), \ +} + +static AD7266_DECLARE_SINGLE_ENDED_CHANNELS(u, 'u'); +static AD7266_DECLARE_SINGLE_ENDED_CHANNELS(s, 's'); +static AD7266_DECLARE_SINGLE_ENDED_CHANNELS_FIXED(u, 'u'); +static AD7266_DECLARE_SINGLE_ENDED_CHANNELS_FIXED(s, 's'); + +#define AD7266_CHAN_DIFF(_chan, _sign) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_chan) * 2, \ + .channel2 = (_chan) * 2 + 1, \ + .address = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_index = (_chan), \ + .scan_type = { \ + .sign = _sign, \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 2, \ + .endianness = IIO_BE, \ + }, \ + .differential = 1, \ +} + +#define AD7266_DECLARE_DIFF_CHANNELS(_name, _sign) \ +const struct iio_chan_spec ad7266_channels_diff_##_name[] = { \ + AD7266_CHAN_DIFF(0, (_sign)), \ + AD7266_CHAN_DIFF(1, (_sign)), \ + AD7266_CHAN_DIFF(2, (_sign)), \ + AD7266_CHAN_DIFF(3, (_sign)), \ + AD7266_CHAN_DIFF(4, (_sign)), \ + AD7266_CHAN_DIFF(5, (_sign)), \ + IIO_CHAN_SOFT_TIMESTAMP(6), \ +} + +static AD7266_DECLARE_DIFF_CHANNELS(s, 's'); +static AD7266_DECLARE_DIFF_CHANNELS(u, 'u'); + +#define AD7266_DECLARE_DIFF_CHANNELS_FIXED(_name, _sign) \ +const struct iio_chan_spec ad7266_channels_diff_fixed_##_name[] = { \ + AD7266_CHAN_DIFF(0, (_sign)), \ + AD7266_CHAN_DIFF(1, (_sign)), \ + IIO_CHAN_SOFT_TIMESTAMP(2), \ +} + +static AD7266_DECLARE_DIFF_CHANNELS_FIXED(s, 's'); +static AD7266_DECLARE_DIFF_CHANNELS_FIXED(u, 'u'); + +static const struct iio_info ad7266_info = { + .read_raw = &ad7266_read_raw, + .update_scan_mode = &ad7266_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static const unsigned long ad7266_available_scan_masks[] = { + 0x003, + 0x00c, + 0x030, + 0x0c0, + 0x300, + 0xc00, + 0x000, +}; + +static const unsigned long ad7266_available_scan_masks_diff[] = { + 0x003, + 0x00c, + 0x030, + 0x000, +}; + +static const unsigned long ad7266_available_scan_masks_fixed[] = { + 0x003, + 0x000, +}; + +struct ad7266_chan_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + const unsigned long *scan_masks; +}; + +#define AD7266_CHAN_INFO_INDEX(_differential, _signed, _fixed) \ + (((_differential) << 2) | ((_signed) << 1) | ((_fixed) << 0)) + +static const struct ad7266_chan_info ad7266_chan_infos[] = { + [AD7266_CHAN_INFO_INDEX(0, 0, 0)] = { + .channels = ad7266_channels_u, + .num_channels = ARRAY_SIZE(ad7266_channels_u), + .scan_masks = ad7266_available_scan_masks, + }, + [AD7266_CHAN_INFO_INDEX(0, 0, 1)] = { + .channels = ad7266_channels_u_fixed, + .num_channels = ARRAY_SIZE(ad7266_channels_u_fixed), + .scan_masks = ad7266_available_scan_masks_fixed, + }, + [AD7266_CHAN_INFO_INDEX(0, 1, 0)] = { + .channels = ad7266_channels_s, + .num_channels = ARRAY_SIZE(ad7266_channels_s), + .scan_masks = ad7266_available_scan_masks, + }, + [AD7266_CHAN_INFO_INDEX(0, 1, 1)] = { + .channels = ad7266_channels_s_fixed, + .num_channels = ARRAY_SIZE(ad7266_channels_s_fixed), + .scan_masks = ad7266_available_scan_masks_fixed, + }, + [AD7266_CHAN_INFO_INDEX(1, 0, 0)] = { + .channels = ad7266_channels_diff_u, + .num_channels = ARRAY_SIZE(ad7266_channels_diff_u), + .scan_masks = ad7266_available_scan_masks_diff, + }, + [AD7266_CHAN_INFO_INDEX(1, 0, 1)] = { + .channels = ad7266_channels_diff_fixed_u, + .num_channels = ARRAY_SIZE(ad7266_channels_diff_fixed_u), + .scan_masks = ad7266_available_scan_masks_fixed, + }, + [AD7266_CHAN_INFO_INDEX(1, 1, 0)] = { + .channels = ad7266_channels_diff_s, + .num_channels = ARRAY_SIZE(ad7266_channels_diff_s), + .scan_masks = ad7266_available_scan_masks_diff, + }, + [AD7266_CHAN_INFO_INDEX(1, 1, 1)] = { + .channels = ad7266_channels_diff_fixed_s, + .num_channels = ARRAY_SIZE(ad7266_channels_diff_fixed_s), + .scan_masks = ad7266_available_scan_masks_fixed, + }, +}; + +static void ad7266_init_channels(struct iio_dev *indio_dev) +{ + struct ad7266_state *st = iio_priv(indio_dev); + bool is_differential, is_signed; + const struct ad7266_chan_info *chan_info; + int i; + + is_differential = st->mode != AD7266_MODE_SINGLE_ENDED; + is_signed = (st->range == AD7266_RANGE_2VREF) | + (st->mode == AD7266_MODE_DIFF); + + i = AD7266_CHAN_INFO_INDEX(is_differential, is_signed, st->fixed_addr); + chan_info = &ad7266_chan_infos[i]; + + indio_dev->channels = chan_info->channels; + indio_dev->num_channels = chan_info->num_channels; + indio_dev->available_scan_masks = chan_info->scan_masks; + indio_dev->masklength = chan_info->num_channels - 1; +} + +static const char * const ad7266_gpio_labels[] = { + "AD0", "AD1", "AD2", +}; + +static int ad7266_probe(struct spi_device *spi) +{ + struct ad7266_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev; + struct ad7266_state *st; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get(&spi->dev, "vref"); + if (!IS_ERR_OR_NULL(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = regulator_get_voltage(st->reg); + if (ret < 0) + goto error_disable_reg; + + st->vref_mv = ret / 1000; + } else { + /* Use internal reference */ + st->vref_mv = 2500; + } + + if (pdata) { + st->fixed_addr = pdata->fixed_addr; + st->mode = pdata->mode; + st->range = pdata->range; + + if (!st->fixed_addr) { + for (i = 0; i < ARRAY_SIZE(st->gpios); ++i) { + st->gpios[i].gpio = pdata->addr_gpios[i]; + st->gpios[i].flags = GPIOF_OUT_INIT_LOW; + st->gpios[i].label = ad7266_gpio_labels[i]; + } + ret = gpio_request_array(st->gpios, + ARRAY_SIZE(st->gpios)); + if (ret) + goto error_disable_reg; + } + } else { + st->fixed_addr = true; + st->range = AD7266_RANGE_VREF; + st->mode = AD7266_MODE_DIFF; + } + + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad7266_info; + + ad7266_init_channels(indio_dev); + + /* wakeup */ + st->single_xfer[0].rx_buf = &st->data.sample[0]; + st->single_xfer[0].len = 2; + st->single_xfer[0].cs_change = 1; + /* conversion */ + st->single_xfer[1].rx_buf = st->data.sample; + st->single_xfer[1].len = 4; + st->single_xfer[1].cs_change = 1; + /* powerdown */ + st->single_xfer[2].tx_buf = &st->data.sample[0]; + st->single_xfer[2].len = 1; + + spi_message_init(&st->single_msg); + spi_message_add_tail(&st->single_xfer[0], &st->single_msg); + spi_message_add_tail(&st->single_xfer[1], &st->single_msg); + spi_message_add_tail(&st->single_xfer[2], &st->single_msg); + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &ad7266_trigger_handler, &iio_triggered_buffer_setup_ops); + if (ret) + goto error_free_gpios; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_buffer_cleanup; + + return 0; + +error_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +error_free_gpios: + if (!st->fixed_addr) + gpio_free_array(st->gpios, ARRAY_SIZE(st->gpios)); +error_disable_reg: + if (!IS_ERR_OR_NULL(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ad7266_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7266_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (!st->fixed_addr) + gpio_free_array(st->gpios, ARRAY_SIZE(st->gpios)); + if (!IS_ERR_OR_NULL(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7266_id[] = { + {"ad7265", 0}, + {"ad7266", 0}, + { } +}; +MODULE_DEVICE_TABLE(spi, ad7266_id); + +static struct spi_driver ad7266_driver = { + .driver = { + .name = "ad7266", + .owner = THIS_MODULE, + }, + .probe = ad7266_probe, + .remove = ad7266_remove, + .id_table = ad7266_id, +}; +module_spi_driver(ad7266_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices AD7266/65 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7291.c b/drivers/iio/adc/ad7291.c new file mode 100644 index 000000000..c0eabf156 --- /dev/null +++ b/drivers/iio/adc/ad7291.c @@ -0,0 +1,585 @@ +/* + * AD7291 8-Channel, I2C, 12-Bit SAR ADC with Temperature Sensor + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/sysfs.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> + +#include <linux/platform_data/ad7291.h> + +/* + * Simplified handling + * + * If no events enabled - single polled channel read + * If event enabled direct reads disable unless channel + * is in the read mask. + * + * The noise-delayed bit as per datasheet suggestion is always enabled. + */ + +/* + * AD7291 registers definition + */ +#define AD7291_COMMAND 0x00 +#define AD7291_VOLTAGE 0x01 +#define AD7291_T_SENSE 0x02 +#define AD7291_T_AVERAGE 0x03 +#define AD7291_DATA_HIGH(x) ((x) * 3 + 0x4) +#define AD7291_DATA_LOW(x) ((x) * 3 + 0x5) +#define AD7291_HYST(x) ((x) * 3 + 0x6) +#define AD7291_VOLTAGE_ALERT_STATUS 0x1F +#define AD7291_T_ALERT_STATUS 0x20 + +#define AD7291_BITS 12 +#define AD7291_VOLTAGE_LIMIT_COUNT 8 + + +/* + * AD7291 command + */ +#define AD7291_AUTOCYCLE BIT(0) +#define AD7291_RESET BIT(1) +#define AD7291_ALERT_CLEAR BIT(2) +#define AD7291_ALERT_POLARITY BIT(3) +#define AD7291_EXT_REF BIT(4) +#define AD7291_NOISE_DELAY BIT(5) +#define AD7291_T_SENSE_MASK BIT(7) +#define AD7291_VOLTAGE_MASK GENMASK(15, 8) +#define AD7291_VOLTAGE_OFFSET 8 + +/* + * AD7291 value masks + */ +#define AD7291_VALUE_MASK GENMASK(11, 0) + +/* + * AD7291 alert register bits + */ +#define AD7291_T_LOW BIT(0) +#define AD7291_T_HIGH BIT(1) +#define AD7291_T_AVG_LOW BIT(2) +#define AD7291_T_AVG_HIGH BIT(3) +#define AD7291_V_LOW(x) BIT((x) * 2) +#define AD7291_V_HIGH(x) BIT((x) * 2 + 1) + + +struct ad7291_chip_info { + struct i2c_client *client; + struct regulator *reg; + u16 command; + u16 c_mask; /* Active voltage channels for events */ + struct mutex state_lock; +}; + +static int ad7291_i2c_read(struct ad7291_chip_info *chip, u8 reg, u16 *data) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + ret = i2c_smbus_read_word_swapped(client, reg); + if (ret < 0) { + dev_err(&client->dev, "I2C read error\n"); + return ret; + } + + *data = ret; + + return 0; +} + +static int ad7291_i2c_write(struct ad7291_chip_info *chip, u8 reg, u16 data) +{ + return i2c_smbus_write_word_swapped(chip->client, reg, data); +} + +static irqreturn_t ad7291_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad7291_chip_info *chip = iio_priv(private); + u16 t_status, v_status; + u16 command; + int i; + s64 timestamp = iio_get_time_ns(); + + if (ad7291_i2c_read(chip, AD7291_T_ALERT_STATUS, &t_status)) + return IRQ_HANDLED; + + if (ad7291_i2c_read(chip, AD7291_VOLTAGE_ALERT_STATUS, &v_status)) + return IRQ_HANDLED; + + if (!(t_status || v_status)) + return IRQ_HANDLED; + + command = chip->command | AD7291_ALERT_CLEAR; + ad7291_i2c_write(chip, AD7291_COMMAND, command); + + command = chip->command & ~AD7291_ALERT_CLEAR; + ad7291_i2c_write(chip, AD7291_COMMAND, command); + + /* For now treat t_sense and t_sense_average the same */ + if ((t_status & AD7291_T_LOW) || (t_status & AD7291_T_AVG_LOW)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if ((t_status & AD7291_T_HIGH) || (t_status & AD7291_T_AVG_HIGH)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + for (i = 0; i < AD7291_VOLTAGE_LIMIT_COUNT; i++) { + if (v_status & AD7291_V_LOW(i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if (v_status & AD7291_V_HIGH(i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + i, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + } + + return IRQ_HANDLED; +} + +static unsigned int ad7291_threshold_reg(const struct iio_chan_spec *chan, + enum iio_event_direction dir, + enum iio_event_info info) +{ + unsigned int offset; + + switch (chan->type) { + case IIO_VOLTAGE: + offset = chan->channel; + break; + case IIO_TEMP: + offset = AD7291_VOLTAGE_OFFSET; + break; + default: + return 0; + } + + switch (info) { + case IIO_EV_INFO_VALUE: + if (dir == IIO_EV_DIR_FALLING) + return AD7291_DATA_HIGH(offset); + else + return AD7291_DATA_LOW(offset); + case IIO_EV_INFO_HYSTERESIS: + return AD7291_HYST(offset); + default: + break; + } + return 0; +} + +static int ad7291_read_event_value(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 ad7291_chip_info *chip = iio_priv(indio_dev); + int ret; + u16 uval; + + ret = ad7291_i2c_read(chip, ad7291_threshold_reg(chan, dir, info), + &uval); + if (ret < 0) + return ret; + + if (info == IIO_EV_INFO_HYSTERESIS || chan->type == IIO_VOLTAGE) + *val = uval & AD7291_VALUE_MASK; + + else + *val = sign_extend32(uval, 11); + + return IIO_VAL_INT; +} + +static int ad7291_write_event_value(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 ad7291_chip_info *chip = iio_priv(indio_dev); + + if (info == IIO_EV_INFO_HYSTERESIS || chan->type == IIO_VOLTAGE) { + if (val > AD7291_VALUE_MASK || val < 0) + return -EINVAL; + } else { + if (val > 2047 || val < -2048) + return -EINVAL; + } + + return ad7291_i2c_write(chip, ad7291_threshold_reg(chan, dir, info), + val); +} + +static int ad7291_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 ad7291_chip_info *chip = iio_priv(indio_dev); + /* + * To be enabled the channel must simply be on. If any are enabled + * we are in continuous sampling mode + */ + + switch (chan->type) { + case IIO_VOLTAGE: + return !!(chip->c_mask & BIT(15 - chan->channel)); + case IIO_TEMP: + /* always on */ + return 1; + default: + return -EINVAL; + } + +} + +static int ad7291_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) +{ + int ret = 0; + struct ad7291_chip_info *chip = iio_priv(indio_dev); + unsigned int mask; + u16 regval; + + mutex_lock(&chip->state_lock); + regval = chip->command; + /* + * To be enabled the channel must simply be on. If any are enabled + * use continuous sampling mode. + * Possible to disable temp as well but that makes single read tricky. + */ + + mask = BIT(15 - chan->channel); + + switch (chan->type) { + case IIO_VOLTAGE: + if ((!state) && (chip->c_mask & mask)) + chip->c_mask &= ~mask; + else if (state && (!(chip->c_mask & mask))) + chip->c_mask |= mask; + else + break; + + regval &= ~AD7291_AUTOCYCLE; + regval |= chip->c_mask; + if (chip->c_mask) /* Enable autocycle? */ + regval |= AD7291_AUTOCYCLE; + + ret = ad7291_i2c_write(chip, AD7291_COMMAND, regval); + if (ret < 0) + goto error_ret; + + chip->command = regval; + break; + default: + ret = -EINVAL; + } + +error_ret: + mutex_unlock(&chip->state_lock); + return ret; +} + +static int ad7291_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret; + struct ad7291_chip_info *chip = iio_priv(indio_dev); + u16 regval; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + mutex_lock(&chip->state_lock); + /* If in autocycle mode drop through */ + if (chip->command & AD7291_AUTOCYCLE) { + mutex_unlock(&chip->state_lock); + return -EBUSY; + } + /* Enable this channel alone */ + regval = chip->command & (~AD7291_VOLTAGE_MASK); + regval |= BIT(15 - chan->channel); + ret = ad7291_i2c_write(chip, AD7291_COMMAND, regval); + if (ret < 0) { + mutex_unlock(&chip->state_lock); + return ret; + } + /* Read voltage */ + ret = i2c_smbus_read_word_swapped(chip->client, + AD7291_VOLTAGE); + if (ret < 0) { + mutex_unlock(&chip->state_lock); + return ret; + } + *val = ret & AD7291_VALUE_MASK; + mutex_unlock(&chip->state_lock); + return IIO_VAL_INT; + case IIO_TEMP: + /* Assumes tsense bit of command register always set */ + ret = i2c_smbus_read_word_swapped(chip->client, + AD7291_T_SENSE); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 11); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_AVERAGE_RAW: + ret = i2c_smbus_read_word_swapped(chip->client, + AD7291_T_AVERAGE); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 11); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chip->reg) { + int vref; + + vref = regulator_get_voltage(chip->reg); + if (vref < 0) + return vref; + *val = vref / 1000; + } else { + *val = 2500; + } + *val2 = AD7291_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + /* + * One LSB of the ADC corresponds to 0.25 deg C. + * The temperature reading is in 12-bit twos + * complement format + */ + *val = 250; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_event_spec ad7291_events[] = { + { + .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), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +#define AD7291_VOLTAGE_CHAN(_chan) \ +{ \ + .type = IIO_VOLTAGE, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .indexed = 1, \ + .channel = _chan, \ + .event_spec = ad7291_events, \ + .num_event_specs = ARRAY_SIZE(ad7291_events), \ +} + +static const struct iio_chan_spec ad7291_channels[] = { + AD7291_VOLTAGE_CHAN(0), + AD7291_VOLTAGE_CHAN(1), + AD7291_VOLTAGE_CHAN(2), + AD7291_VOLTAGE_CHAN(3), + AD7291_VOLTAGE_CHAN(4), + AD7291_VOLTAGE_CHAN(5), + AD7291_VOLTAGE_CHAN(6), + AD7291_VOLTAGE_CHAN(7), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_AVERAGE_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + .channel = 0, + .event_spec = ad7291_events, + .num_event_specs = ARRAY_SIZE(ad7291_events), + } +}; + +static const struct iio_info ad7291_info = { + .read_raw = &ad7291_read_raw, + .read_event_config = &ad7291_read_event_config, + .write_event_config = &ad7291_write_event_config, + .read_event_value = &ad7291_read_event_value, + .write_event_value = &ad7291_write_event_value, + .driver_module = THIS_MODULE, +}; + +static int ad7291_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ad7291_platform_data *pdata = client->dev.platform_data; + struct ad7291_chip_info *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + chip = iio_priv(indio_dev); + + if (pdata && pdata->use_external_ref) { + chip->reg = devm_regulator_get(&client->dev, "vref"); + if (IS_ERR(chip->reg)) + return PTR_ERR(chip->reg); + + ret = regulator_enable(chip->reg); + if (ret) + return ret; + } + + mutex_init(&chip->state_lock); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + chip->client = client; + + chip->command = AD7291_NOISE_DELAY | + AD7291_T_SENSE_MASK | /* Tsense always enabled */ + AD7291_ALERT_POLARITY; /* set irq polarity low level */ + + if (pdata && pdata->use_external_ref) + chip->command |= AD7291_EXT_REF; + + indio_dev->name = id->name; + indio_dev->channels = ad7291_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7291_channels); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ad7291_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ad7291_i2c_write(chip, AD7291_COMMAND, AD7291_RESET); + if (ret) { + ret = -EIO; + goto error_disable_reg; + } + + ret = ad7291_i2c_write(chip, AD7291_COMMAND, chip->command); + if (ret) { + ret = -EIO; + goto error_disable_reg; + } + + if (client->irq > 0) { + ret = request_threaded_irq(client->irq, + NULL, + &ad7291_event_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + id->name, + indio_dev); + if (ret) + goto error_disable_reg; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_irq; + + return 0; + +error_unreg_irq: + if (client->irq) + free_irq(client->irq, indio_dev); +error_disable_reg: + if (chip->reg) + regulator_disable(chip->reg); + + return ret; +} + +static int ad7291_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ad7291_chip_info *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (client->irq) + free_irq(client->irq, indio_dev); + + if (chip->reg) + regulator_disable(chip->reg); + + return 0; +} + +static const struct i2c_device_id ad7291_id[] = { + { "ad7291", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad7291_id); + +static struct i2c_driver ad7291_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = ad7291_probe, + .remove = ad7291_remove, + .id_table = ad7291_id, +}; +module_i2c_driver(ad7291_driver); + +MODULE_AUTHOR("Sonic Zhang <sonic.zhang@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD7291 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7298.c b/drivers/iio/adc/ad7298.c new file mode 100644 index 000000000..4a8c0a2f4 --- /dev/null +++ b/drivers/iio/adc/ad7298.c @@ -0,0 +1,391 @@ +/* + * AD7298 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/bitops.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/platform_data/ad7298.h> + +#define AD7298_WRITE BIT(15) /* write to the control register */ +#define AD7298_REPEAT BIT(14) /* repeated conversion enable */ +#define AD7298_CH(x) BIT(13 - (x)) /* channel select */ +#define AD7298_TSENSE BIT(5) /* temperature conversion enable */ +#define AD7298_EXTREF BIT(2) /* external reference enable */ +#define AD7298_TAVG BIT(1) /* temperature sensor averaging enable */ +#define AD7298_PDD BIT(0) /* partial power down enable */ + +#define AD7298_MAX_CHAN 8 +#define AD7298_INTREF_mV 2500 + +#define AD7298_CH_TEMP 9 + +struct ad7298_state { + struct spi_device *spi; + struct regulator *reg; + unsigned ext_ref; + struct spi_transfer ring_xfer[10]; + struct spi_transfer scan_single_xfer[3]; + struct spi_message ring_msg; + struct spi_message scan_single_msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __be16 rx_buf[12] ____cacheline_aligned; + __be16 tx_buf[2]; +}; + +#define AD7298_V_CHAN(index) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec ad7298_channels[] = { + { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = AD7298_CH_TEMP, + .scan_index = -1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + }, + }, + AD7298_V_CHAN(0), + AD7298_V_CHAN(1), + AD7298_V_CHAN(2), + AD7298_V_CHAN(3), + AD7298_V_CHAN(4), + AD7298_V_CHAN(5), + AD7298_V_CHAN(6), + AD7298_V_CHAN(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +/** + * ad7298_update_scan_mode() setup the spi transfer buffer for the new scan mask + **/ +static int ad7298_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad7298_state *st = iio_priv(indio_dev); + int i, m; + unsigned short command; + int scan_count; + + /* Now compute overall size */ + scan_count = bitmap_weight(active_scan_mask, indio_dev->masklength); + + command = AD7298_WRITE | st->ext_ref; + + for (i = 0, m = AD7298_CH(0); i < AD7298_MAX_CHAN; i++, m >>= 1) + if (test_bit(i, active_scan_mask)) + command |= m; + + st->tx_buf[0] = cpu_to_be16(command); + + /* build spi ring message */ + st->ring_xfer[0].tx_buf = &st->tx_buf[0]; + st->ring_xfer[0].len = 2; + st->ring_xfer[0].cs_change = 1; + st->ring_xfer[1].tx_buf = &st->tx_buf[1]; + st->ring_xfer[1].len = 2; + st->ring_xfer[1].cs_change = 1; + + spi_message_init(&st->ring_msg); + spi_message_add_tail(&st->ring_xfer[0], &st->ring_msg); + spi_message_add_tail(&st->ring_xfer[1], &st->ring_msg); + + for (i = 0; i < scan_count; i++) { + st->ring_xfer[i + 2].rx_buf = &st->rx_buf[i]; + st->ring_xfer[i + 2].len = 2; + st->ring_xfer[i + 2].cs_change = 1; + spi_message_add_tail(&st->ring_xfer[i + 2], &st->ring_msg); + } + /* make sure last transfer cs_change is not set */ + st->ring_xfer[i + 1].cs_change = 0; + + return 0; +} + +/** + * ad7298_trigger_handler() bh of trigger launched polling to ring buffer + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + **/ +static irqreturn_t ad7298_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7298_state *st = iio_priv(indio_dev); + int b_sent; + + b_sent = spi_sync(st->spi, &st->ring_msg); + if (b_sent) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->rx_buf, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad7298_scan_direct(struct ad7298_state *st, unsigned ch) +{ + int ret; + st->tx_buf[0] = cpu_to_be16(AD7298_WRITE | st->ext_ref | + (AD7298_CH(0) >> ch)); + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + return be16_to_cpu(st->rx_buf[0]); +} + +static int ad7298_scan_temp(struct ad7298_state *st, int *val) +{ + int ret; + __be16 buf; + + buf = cpu_to_be16(AD7298_WRITE | AD7298_TSENSE | + AD7298_TAVG | st->ext_ref); + + ret = spi_write(st->spi, (u8 *)&buf, 2); + if (ret) + return ret; + + buf = cpu_to_be16(0); + + ret = spi_write(st->spi, (u8 *)&buf, 2); + if (ret) + return ret; + + usleep_range(101, 1000); /* sleep > 100us */ + + ret = spi_read(st->spi, (u8 *)&buf, 2); + if (ret) + return ret; + + *val = sign_extend32(be16_to_cpu(buf), 11); + + return 0; +} + +static int ad7298_get_ref_voltage(struct ad7298_state *st) +{ + int vref; + + if (st->ext_ref) { + vref = regulator_get_voltage(st->reg); + if (vref < 0) + return vref; + + return vref / 1000; + } else { + return AD7298_INTREF_mV; + } +} + +static int ad7298_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7298_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + ret = -EBUSY; + } else { + if (chan->address == AD7298_CH_TEMP) + ret = ad7298_scan_temp(st, val); + else + ret = ad7298_scan_direct(st, chan->address); + } + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + if (chan->address != AD7298_CH_TEMP) + *val = ret & GENMASK(chan->scan_type.realbits - 1, 0); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = ad7298_get_ref_voltage(st); + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + *val = ad7298_get_ref_voltage(st); + *val2 = 10; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = 1093 - 2732500 / ad7298_get_ref_voltage(st); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static const struct iio_info ad7298_info = { + .read_raw = &ad7298_read_raw, + .update_scan_mode = ad7298_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static int ad7298_probe(struct spi_device *spi) +{ + struct ad7298_platform_data *pdata = spi->dev.platform_data; + struct ad7298_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + if (pdata && pdata->ext_ref) + st->ext_ref = AD7298_EXTREF; + + if (st->ext_ref) { + st->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + } + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7298_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7298_channels); + indio_dev->info = &ad7298_info; + + /* Setup default message */ + + st->scan_single_xfer[0].tx_buf = &st->tx_buf[0]; + st->scan_single_xfer[0].len = 2; + st->scan_single_xfer[0].cs_change = 1; + st->scan_single_xfer[1].tx_buf = &st->tx_buf[1]; + st->scan_single_xfer[1].len = 2; + st->scan_single_xfer[1].cs_change = 1; + st->scan_single_xfer[2].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[2].len = 2; + + spi_message_init(&st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[0], &st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[1], &st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[2], &st->scan_single_msg); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &ad7298_trigger_handler, NULL); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_ring; + + return 0; + +error_cleanup_ring: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_reg: + if (st->ext_ref) + regulator_disable(st->reg); + + return ret; +} + +static int ad7298_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7298_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (st->ext_ref) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7298_id[] = { + {"ad7298", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7298_id); + +static struct spi_driver ad7298_driver = { + .driver = { + .name = "ad7298", + .owner = THIS_MODULE, + }, + .probe = ad7298_probe, + .remove = ad7298_remove, + .id_table = ad7298_id, +}; +module_spi_driver(ad7298_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD7298 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7476.c b/drivers/iio/adc/ad7476.c new file mode 100644 index 000000000..ce400ec17 --- /dev/null +++ b/drivers/iio/adc/ad7476.c @@ -0,0 +1,315 @@ +/* + * AD7466/7/8 AD7476/5/7/8 (A) SPI ADC driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/bitops.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +struct ad7476_state; + +struct ad7476_chip_info { + unsigned int int_vref_uv; + struct iio_chan_spec channel[2]; + void (*reset)(struct ad7476_state *); +}; + +struct ad7476_state { + struct spi_device *spi; + const struct ad7476_chip_info *chip_info; + struct regulator *reg; + struct spi_transfer xfer; + struct spi_message msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + * Make the buffer large enough for one 16 bit sample and one 64 bit + * aligned 64 bit timestamp. + */ + unsigned char data[ALIGN(2, sizeof(s64)) + sizeof(s64)] + ____cacheline_aligned; +}; + +enum ad7476_supported_device_ids { + ID_AD7091R, + ID_AD7276, + ID_AD7277, + ID_AD7278, + ID_AD7466, + ID_AD7467, + ID_AD7468, + ID_AD7495, + ID_AD7940, +}; + +static irqreturn_t ad7476_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7476_state *st = iio_priv(indio_dev); + int b_sent; + + b_sent = spi_sync(st->spi, &st->msg); + if (b_sent < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->data, + iio_get_time_ns()); +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void ad7091_reset(struct ad7476_state *st) +{ + /* Any transfers with 8 scl cycles will reset the device */ + spi_read(st->spi, st->data, 1); +} + +static int ad7476_scan_direct(struct ad7476_state *st) +{ + int ret; + + ret = spi_sync(st->spi, &st->msg); + if (ret) + return ret; + + return be16_to_cpup((__be16 *)st->data); +} + +static int ad7476_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7476_state *st = iio_priv(indio_dev); + int scale_uv; + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad7476_scan_direct(st); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + *val = (ret >> st->chip_info->channel[0].scan_type.shift) & + GENMASK(st->chip_info->channel[0].scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (!st->chip_info->int_vref_uv) { + scale_uv = regulator_get_voltage(st->reg); + if (scale_uv < 0) + return scale_uv; + } else { + scale_uv = st->chip_info->int_vref_uv; + } + *val = scale_uv / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +#define _AD7476_CHAN(bits, _shift, _info_mask_sep) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .info_mask_separate = _info_mask_sep, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = (_shift), \ + .endianness = IIO_BE, \ + }, \ +} + +#define AD7476_CHAN(bits) _AD7476_CHAN((bits), 13 - (bits), \ + BIT(IIO_CHAN_INFO_RAW)) +#define AD7940_CHAN(bits) _AD7476_CHAN((bits), 15 - (bits), \ + BIT(IIO_CHAN_INFO_RAW)) +#define AD7091R_CHAN(bits) _AD7476_CHAN((bits), 16 - (bits), 0) + +static const struct ad7476_chip_info ad7476_chip_info_tbl[] = { + [ID_AD7091R] = { + .channel[0] = AD7091R_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .reset = ad7091_reset, + }, + [ID_AD7276] = { + .channel[0] = AD7940_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7277] = { + .channel[0] = AD7940_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7278] = { + .channel[0] = AD7940_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7466] = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7467] = { + .channel[0] = AD7476_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7468] = { + .channel[0] = AD7476_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7495] = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .int_vref_uv = 2500000, + }, + [ID_AD7940] = { + .channel[0] = AD7940_CHAN(14), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, +}; + +static const struct iio_info ad7476_info = { + .driver_module = THIS_MODULE, + .read_raw = &ad7476_read_raw, +}; + +static int ad7476_probe(struct spi_device *spi) +{ + struct ad7476_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->chip_info = + &ad7476_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + st->reg = devm_regulator_get(&spi->dev, "vcc"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + /* Establish that the iio_dev is a child of the spi device */ + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channel; + indio_dev->num_channels = 2; + indio_dev->info = &ad7476_info; + /* Setup default message */ + + st->xfer.rx_buf = &st->data; + st->xfer.len = st->chip_info->channel[0].scan_type.storagebits / 8; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &ad7476_trigger_handler, NULL); + if (ret) + goto error_disable_reg; + + if (st->chip_info->reset) + st->chip_info->reset(st); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_ring_unregister; + return 0; + +error_ring_unregister: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_reg: + regulator_disable(st->reg); + + return ret; +} + +static int ad7476_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7476_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7476_id[] = { + {"ad7091r", ID_AD7091R}, + {"ad7273", ID_AD7277}, + {"ad7274", ID_AD7276}, + {"ad7276", ID_AD7276}, + {"ad7277", ID_AD7277}, + {"ad7278", ID_AD7278}, + {"ad7466", ID_AD7466}, + {"ad7467", ID_AD7467}, + {"ad7468", ID_AD7468}, + {"ad7475", ID_AD7466}, + {"ad7476", ID_AD7466}, + {"ad7476a", ID_AD7466}, + {"ad7477", ID_AD7467}, + {"ad7477a", ID_AD7467}, + {"ad7478", ID_AD7468}, + {"ad7478a", ID_AD7468}, + {"ad7495", ID_AD7495}, + {"ad7910", ID_AD7467}, + {"ad7920", ID_AD7466}, + {"ad7940", ID_AD7940}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7476_id); + +static struct spi_driver ad7476_driver = { + .driver = { + .name = "ad7476", + .owner = THIS_MODULE, + }, + .probe = ad7476_probe, + .remove = ad7476_remove, + .id_table = ad7476_id, +}; +module_spi_driver(ad7476_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD7476 and similar 1-channel ADCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7791.c b/drivers/iio/adc/ad7791.c new file mode 100644 index 000000000..c19f8fd1b --- /dev/null +++ b/drivers/iio/adc/ad7791.c @@ -0,0 +1,453 @@ +/* + * AD7787/AD7788/AD7789/AD7790/AD7791 SPI ADC driver + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/adc/ad_sigma_delta.h> + +#include <linux/platform_data/ad7791.h> + +#define AD7791_REG_COMM 0x0 /* For writes */ +#define AD7791_REG_STATUS 0x0 /* For reads */ +#define AD7791_REG_MODE 0x1 +#define AD7791_REG_FILTER 0x2 +#define AD7791_REG_DATA 0x3 + +#define AD7791_MODE_CONTINUOUS 0x00 +#define AD7791_MODE_SINGLE 0x02 +#define AD7791_MODE_POWERDOWN 0x03 + +#define AD7791_CH_AIN1P_AIN1N 0x00 +#define AD7791_CH_AIN2 0x01 +#define AD7791_CH_AIN1N_AIN1N 0x02 +#define AD7791_CH_AVDD_MONITOR 0x03 + +#define AD7791_FILTER_CLK_DIV_1 (0x0 << 4) +#define AD7791_FILTER_CLK_DIV_2 (0x1 << 4) +#define AD7791_FILTER_CLK_DIV_4 (0x2 << 4) +#define AD7791_FILTER_CLK_DIV_8 (0x3 << 4) +#define AD7791_FILTER_CLK_MASK (0x3 << 4) +#define AD7791_FILTER_RATE_120 0x0 +#define AD7791_FILTER_RATE_100 0x1 +#define AD7791_FILTER_RATE_33_3 0x2 +#define AD7791_FILTER_RATE_20 0x3 +#define AD7791_FILTER_RATE_16_6 0x4 +#define AD7791_FILTER_RATE_16_7 0x5 +#define AD7791_FILTER_RATE_13_3 0x6 +#define AD7791_FILTER_RATE_9_5 0x7 +#define AD7791_FILTER_RATE_MASK 0x7 + +#define AD7791_MODE_BUFFER BIT(1) +#define AD7791_MODE_UNIPOLAR BIT(2) +#define AD7791_MODE_BURNOUT BIT(3) +#define AD7791_MODE_SEL_MASK (0x3 << 6) +#define AD7791_MODE_SEL(x) ((x) << 6) + +#define DECLARE_AD7787_CHANNELS(name, bits, storagebits) \ +const struct iio_chan_spec name[] = { \ + AD_SD_DIFF_CHANNEL(0, 0, 0, AD7791_CH_AIN1P_AIN1N, \ + (bits), (storagebits), 0), \ + AD_SD_CHANNEL(1, 1, AD7791_CH_AIN2, (bits), (storagebits), 0), \ + AD_SD_SHORTED_CHANNEL(2, 0, AD7791_CH_AIN1N_AIN1N, \ + (bits), (storagebits), 0), \ + AD_SD_SUPPLY_CHANNEL(3, 2, AD7791_CH_AVDD_MONITOR, \ + (bits), (storagebits), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(4), \ +} + +#define DECLARE_AD7791_CHANNELS(name, bits, storagebits) \ +const struct iio_chan_spec name[] = { \ + AD_SD_DIFF_CHANNEL(0, 0, 0, AD7791_CH_AIN1P_AIN1N, \ + (bits), (storagebits), 0), \ + AD_SD_SHORTED_CHANNEL(1, 0, AD7791_CH_AIN1N_AIN1N, \ + (bits), (storagebits), 0), \ + AD_SD_SUPPLY_CHANNEL(2, 1, AD7791_CH_AVDD_MONITOR, \ + (bits), (storagebits), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(3), \ +} + +static DECLARE_AD7787_CHANNELS(ad7787_channels, 24, 32); +static DECLARE_AD7791_CHANNELS(ad7790_channels, 16, 16); +static DECLARE_AD7791_CHANNELS(ad7791_channels, 24, 32); + +enum { + AD7787, + AD7788, + AD7789, + AD7790, + AD7791, +}; + +enum ad7791_chip_info_flags { + AD7791_FLAG_HAS_FILTER = (1 << 0), + AD7791_FLAG_HAS_BUFFER = (1 << 1), + AD7791_FLAG_HAS_UNIPOLAR = (1 << 2), + AD7791_FLAG_HAS_BURNOUT = (1 << 3), +}; + +struct ad7791_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + enum ad7791_chip_info_flags flags; +}; + +static const struct ad7791_chip_info ad7791_chip_infos[] = { + [AD7787] = { + .channels = ad7787_channels, + .num_channels = ARRAY_SIZE(ad7787_channels), + .flags = AD7791_FLAG_HAS_FILTER | AD7791_FLAG_HAS_BUFFER | + AD7791_FLAG_HAS_UNIPOLAR | AD7791_FLAG_HAS_BURNOUT, + }, + [AD7788] = { + .channels = ad7790_channels, + .num_channels = ARRAY_SIZE(ad7790_channels), + .flags = AD7791_FLAG_HAS_UNIPOLAR, + }, + [AD7789] = { + .channels = ad7791_channels, + .num_channels = ARRAY_SIZE(ad7791_channels), + .flags = AD7791_FLAG_HAS_UNIPOLAR, + }, + [AD7790] = { + .channels = ad7790_channels, + .num_channels = ARRAY_SIZE(ad7790_channels), + .flags = AD7791_FLAG_HAS_FILTER | AD7791_FLAG_HAS_BUFFER | + AD7791_FLAG_HAS_BURNOUT, + }, + [AD7791] = { + .channels = ad7791_channels, + .num_channels = ARRAY_SIZE(ad7791_channels), + .flags = AD7791_FLAG_HAS_FILTER | AD7791_FLAG_HAS_BUFFER | + AD7791_FLAG_HAS_UNIPOLAR | AD7791_FLAG_HAS_BURNOUT, + }, +}; + +struct ad7791_state { + struct ad_sigma_delta sd; + uint8_t mode; + uint8_t filter; + + struct regulator *reg; + const struct ad7791_chip_info *info; +}; + +static struct ad7791_state *ad_sigma_delta_to_ad7791(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7791_state, sd); +} + +static int ad7791_set_channel(struct ad_sigma_delta *sd, unsigned int channel) +{ + ad_sd_set_comm(sd, channel); + + return 0; +} + +static int ad7791_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7791_state *st = ad_sigma_delta_to_ad7791(sd); + + switch (mode) { + case AD_SD_MODE_CONTINUOUS: + mode = AD7791_MODE_CONTINUOUS; + break; + case AD_SD_MODE_SINGLE: + mode = AD7791_MODE_SINGLE; + break; + case AD_SD_MODE_IDLE: + case AD_SD_MODE_POWERDOWN: + mode = AD7791_MODE_POWERDOWN; + break; + } + + st->mode &= ~AD7791_MODE_SEL_MASK; + st->mode |= AD7791_MODE_SEL(mode); + + return ad_sd_write_reg(sd, AD7791_REG_MODE, sizeof(st->mode), st->mode); +} + +static const struct ad_sigma_delta_info ad7791_sigma_delta_info = { + .set_channel = ad7791_set_channel, + .set_mode = ad7791_set_mode, + .has_registers = true, + .addr_shift = 4, + .read_mask = BIT(3), +}; + +static int ad7791_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ + struct ad7791_state *st = iio_priv(indio_dev); + bool unipolar = !!(st->mode & AD7791_MODE_UNIPOLAR); + + switch (info) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_OFFSET: + /** + * Unipolar: 0 to VREF + * Bipolar -VREF to VREF + **/ + if (unipolar) + *val = 0; + else + *val = -(1 << (chan->scan_type.realbits - 1)); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* The monitor channel uses an internal reference. */ + if (chan->address == AD7791_CH_AVDD_MONITOR) { + /* + * The signal is attenuated by a factor of 5 and + * compared against a 1.17V internal reference. + */ + *val = 1170 * 5; + } else { + int voltage_uv; + + voltage_uv = regulator_get_voltage(st->reg); + if (voltage_uv < 0) + return voltage_uv; + + *val = voltage_uv / 1000; + } + if (unipolar) + *val2 = chan->scan_type.realbits; + else + *val2 = chan->scan_type.realbits - 1; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static const char * const ad7791_sample_freq_avail[] = { + [AD7791_FILTER_RATE_120] = "120", + [AD7791_FILTER_RATE_100] = "100", + [AD7791_FILTER_RATE_33_3] = "33.3", + [AD7791_FILTER_RATE_20] = "20", + [AD7791_FILTER_RATE_16_6] = "16.6", + [AD7791_FILTER_RATE_16_7] = "16.7", + [AD7791_FILTER_RATE_13_3] = "13.3", + [AD7791_FILTER_RATE_9_5] = "9.5", +}; + +static ssize_t ad7791_read_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7791_state *st = iio_priv(indio_dev); + unsigned int rate = st->filter & AD7791_FILTER_RATE_MASK; + + return sprintf(buf, "%s\n", ad7791_sample_freq_avail[rate]); +} + +static ssize_t ad7791_write_frequency(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7791_state *st = iio_priv(indio_dev); + int i, ret; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + mutex_unlock(&indio_dev->mlock); + + ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(ad7791_sample_freq_avail); i++) { + if (sysfs_streq(ad7791_sample_freq_avail[i], buf)) { + + mutex_lock(&indio_dev->mlock); + st->filter &= ~AD7791_FILTER_RATE_MASK; + st->filter |= i; + ad_sd_write_reg(&st->sd, AD7791_REG_FILTER, + sizeof(st->filter), st->filter); + mutex_unlock(&indio_dev->mlock); + ret = 0; + break; + } + } + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ad7791_read_frequency, + ad7791_write_frequency); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("120 100 33.3 20 16.7 16.6 13.3 9.5"); + +static struct attribute *ad7791_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7791_attribute_group = { + .attrs = ad7791_attributes, +}; + +static const struct iio_info ad7791_info = { + .read_raw = &ad7791_read_raw, + .attrs = &ad7791_attribute_group, + .validate_trigger = ad_sd_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad7791_no_filter_info = { + .read_raw = &ad7791_read_raw, + .validate_trigger = ad_sd_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static int ad7791_setup(struct ad7791_state *st, + struct ad7791_platform_data *pdata) +{ + /* Set to poweron-reset default values */ + st->mode = AD7791_MODE_BUFFER; + st->filter = AD7791_FILTER_RATE_16_6; + + if (!pdata) + return 0; + + if ((st->info->flags & AD7791_FLAG_HAS_BUFFER) && !pdata->buffered) + st->mode &= ~AD7791_MODE_BUFFER; + + if ((st->info->flags & AD7791_FLAG_HAS_BURNOUT) && + pdata->burnout_current) + st->mode |= AD7791_MODE_BURNOUT; + + if ((st->info->flags & AD7791_FLAG_HAS_UNIPOLAR) && pdata->unipolar) + st->mode |= AD7791_MODE_UNIPOLAR; + + return ad_sd_write_reg(&st->sd, AD7791_REG_MODE, sizeof(st->mode), + st->mode); +} + +static int ad7791_probe(struct spi_device *spi) +{ + struct ad7791_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev; + struct ad7791_state *st; + int ret; + + if (!spi->irq) { + dev_err(&spi->dev, "Missing IRQ.\n"); + return -ENXIO; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get(&spi->dev, "refin"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + st->info = &ad7791_chip_infos[spi_get_device_id(spi)->driver_data]; + ad_sd_init(&st->sd, indio_dev, spi, &ad7791_sigma_delta_info); + + spi_set_drvdata(spi, indio_dev); + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->info->channels; + indio_dev->num_channels = st->info->num_channels; + if (st->info->flags & AD7791_FLAG_HAS_FILTER) + indio_dev->info = &ad7791_info; + else + indio_dev->info = &ad7791_no_filter_info; + + ret = ad_sd_setup_buffer_and_trigger(indio_dev); + if (ret) + goto error_disable_reg; + + ret = ad7791_setup(st, pdata); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + ad_sd_cleanup_buffer_and_trigger(indio_dev); +error_disable_reg: + regulator_disable(st->reg); + + return ret; +} + +static int ad7791_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7791_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + ad_sd_cleanup_buffer_and_trigger(indio_dev); + + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7791_spi_ids[] = { + { "ad7787", AD7787 }, + { "ad7788", AD7788 }, + { "ad7789", AD7789 }, + { "ad7790", AD7790 }, + { "ad7791", AD7791 }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7791_spi_ids); + +static struct spi_driver ad7791_driver = { + .driver = { + .name = "ad7791", + .owner = THIS_MODULE, + }, + .probe = ad7791_probe, + .remove = ad7791_remove, + .id_table = ad7791_spi_ids, +}; +module_spi_driver(ad7791_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Device AD7787/AD7788/AD7789/AD7790/AD7791 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7793.c b/drivers/iio/adc/ad7793.c new file mode 100644 index 000000000..b84922a4b --- /dev/null +++ b/drivers/iio/adc/ad7793.c @@ -0,0 +1,865 @@ +/* + * AD7785/AD7792/AD7793/AD7794/AD7795 SPI ADC driver + * + * Copyright 2011-2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/adc/ad_sigma_delta.h> +#include <linux/platform_data/ad7793.h> + +/* Registers */ +#define AD7793_REG_COMM 0 /* Communications Register (WO, 8-bit) */ +#define AD7793_REG_STAT 0 /* Status Register (RO, 8-bit) */ +#define AD7793_REG_MODE 1 /* Mode Register (RW, 16-bit */ +#define AD7793_REG_CONF 2 /* Configuration Register (RW, 16-bit) */ +#define AD7793_REG_DATA 3 /* Data Register (RO, 16-/24-bit) */ +#define AD7793_REG_ID 4 /* ID Register (RO, 8-bit) */ +#define AD7793_REG_IO 5 /* IO Register (RO, 8-bit) */ +#define AD7793_REG_OFFSET 6 /* Offset Register (RW, 16-bit + * (AD7792)/24-bit (AD7793)) */ +#define AD7793_REG_FULLSALE 7 /* Full-Scale Register + * (RW, 16-bit (AD7792)/24-bit (AD7793)) */ + +/* Communications Register Bit Designations (AD7793_REG_COMM) */ +#define AD7793_COMM_WEN (1 << 7) /* Write Enable */ +#define AD7793_COMM_WRITE (0 << 6) /* Write Operation */ +#define AD7793_COMM_READ (1 << 6) /* Read Operation */ +#define AD7793_COMM_ADDR(x) (((x) & 0x7) << 3) /* Register Address */ +#define AD7793_COMM_CREAD (1 << 2) /* Continuous Read of Data Register */ + +/* Status Register Bit Designations (AD7793_REG_STAT) */ +#define AD7793_STAT_RDY (1 << 7) /* Ready */ +#define AD7793_STAT_ERR (1 << 6) /* Error (Overrange, Underrange) */ +#define AD7793_STAT_CH3 (1 << 2) /* Channel 3 */ +#define AD7793_STAT_CH2 (1 << 1) /* Channel 2 */ +#define AD7793_STAT_CH1 (1 << 0) /* Channel 1 */ + +/* Mode Register Bit Designations (AD7793_REG_MODE) */ +#define AD7793_MODE_SEL(x) (((x) & 0x7) << 13) /* Operation Mode Select */ +#define AD7793_MODE_SEL_MASK (0x7 << 13) /* Operation Mode Select mask */ +#define AD7793_MODE_CLKSRC(x) (((x) & 0x3) << 6) /* ADC Clock Source Select */ +#define AD7793_MODE_RATE(x) ((x) & 0xF) /* Filter Update Rate Select */ + +#define AD7793_MODE_CONT 0 /* Continuous Conversion Mode */ +#define AD7793_MODE_SINGLE 1 /* Single Conversion Mode */ +#define AD7793_MODE_IDLE 2 /* Idle Mode */ +#define AD7793_MODE_PWRDN 3 /* Power-Down Mode */ +#define AD7793_MODE_CAL_INT_ZERO 4 /* Internal Zero-Scale Calibration */ +#define AD7793_MODE_CAL_INT_FULL 5 /* Internal Full-Scale Calibration */ +#define AD7793_MODE_CAL_SYS_ZERO 6 /* System Zero-Scale Calibration */ +#define AD7793_MODE_CAL_SYS_FULL 7 /* System Full-Scale Calibration */ + +#define AD7793_CLK_INT 0 /* Internal 64 kHz Clock not + * available at the CLK pin */ +#define AD7793_CLK_INT_CO 1 /* Internal 64 kHz Clock available + * at the CLK pin */ +#define AD7793_CLK_EXT 2 /* External 64 kHz Clock */ +#define AD7793_CLK_EXT_DIV2 3 /* External Clock divided by 2 */ + +/* Configuration Register Bit Designations (AD7793_REG_CONF) */ +#define AD7793_CONF_VBIAS(x) (((x) & 0x3) << 14) /* Bias Voltage + * Generator Enable */ +#define AD7793_CONF_BO_EN (1 << 13) /* Burnout Current Enable */ +#define AD7793_CONF_UNIPOLAR (1 << 12) /* Unipolar/Bipolar Enable */ +#define AD7793_CONF_BOOST (1 << 11) /* Boost Enable */ +#define AD7793_CONF_GAIN(x) (((x) & 0x7) << 8) /* Gain Select */ +#define AD7793_CONF_REFSEL(x) ((x) << 6) /* INT/EXT Reference Select */ +#define AD7793_CONF_BUF (1 << 4) /* Buffered Mode Enable */ +#define AD7793_CONF_CHAN(x) ((x) & 0xf) /* Channel select */ +#define AD7793_CONF_CHAN_MASK 0xf /* Channel select mask */ + +#define AD7793_CH_AIN1P_AIN1M 0 /* AIN1(+) - AIN1(-) */ +#define AD7793_CH_AIN2P_AIN2M 1 /* AIN2(+) - AIN2(-) */ +#define AD7793_CH_AIN3P_AIN3M 2 /* AIN3(+) - AIN3(-) */ +#define AD7793_CH_AIN1M_AIN1M 3 /* AIN1(-) - AIN1(-) */ +#define AD7793_CH_TEMP 6 /* Temp Sensor */ +#define AD7793_CH_AVDD_MONITOR 7 /* AVDD Monitor */ + +#define AD7795_CH_AIN4P_AIN4M 4 /* AIN4(+) - AIN4(-) */ +#define AD7795_CH_AIN5P_AIN5M 5 /* AIN5(+) - AIN5(-) */ +#define AD7795_CH_AIN6P_AIN6M 6 /* AIN6(+) - AIN6(-) */ +#define AD7795_CH_AIN1M_AIN1M 8 /* AIN1(-) - AIN1(-) */ + +/* ID Register Bit Designations (AD7793_REG_ID) */ +#define AD7785_ID 0xB +#define AD7792_ID 0xA +#define AD7793_ID 0xB +#define AD7794_ID 0xF +#define AD7795_ID 0xF +#define AD7796_ID 0xA +#define AD7797_ID 0xB +#define AD7798_ID 0x8 +#define AD7799_ID 0x9 +#define AD7793_ID_MASK 0xF + +/* IO (Excitation Current Sources) Register Bit Designations (AD7793_REG_IO) */ +#define AD7793_IO_IEXC1_IOUT1_IEXC2_IOUT2 0 /* IEXC1 connect to IOUT1, + * IEXC2 connect to IOUT2 */ +#define AD7793_IO_IEXC1_IOUT2_IEXC2_IOUT1 1 /* IEXC1 connect to IOUT2, + * IEXC2 connect to IOUT1 */ +#define AD7793_IO_IEXC1_IEXC2_IOUT1 2 /* Both current sources + * IEXC1,2 connect to IOUT1 */ +#define AD7793_IO_IEXC1_IEXC2_IOUT2 3 /* Both current sources + * IEXC1,2 connect to IOUT2 */ + +#define AD7793_IO_IXCEN_10uA (1 << 0) /* Excitation Current 10uA */ +#define AD7793_IO_IXCEN_210uA (2 << 0) /* Excitation Current 210uA */ +#define AD7793_IO_IXCEN_1mA (3 << 0) /* Excitation Current 1mA */ + +/* NOTE: + * The AD7792/AD7793 features a dual use data out ready DOUT/RDY output. + * In order to avoid contentions on the SPI bus, it's therefore necessary + * to use spi bus locking. + * + * The DOUT/RDY output must also be wired to an interrupt capable GPIO. + */ + +#define AD7793_FLAG_HAS_CLKSEL BIT(0) +#define AD7793_FLAG_HAS_REFSEL BIT(1) +#define AD7793_FLAG_HAS_VBIAS BIT(2) +#define AD7793_HAS_EXITATION_CURRENT BIT(3) +#define AD7793_FLAG_HAS_GAIN BIT(4) +#define AD7793_FLAG_HAS_BUFFER BIT(5) + +struct ad7793_chip_info { + unsigned int id; + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int flags; + + const struct iio_info *iio_info; + const u16 *sample_freq_avail; +}; + +struct ad7793_state { + const struct ad7793_chip_info *chip_info; + struct regulator *reg; + u16 int_vref_mv; + u16 mode; + u16 conf; + u32 scale_avail[8][2]; + + struct ad_sigma_delta sd; + +}; + +enum ad7793_supported_device_ids { + ID_AD7785, + ID_AD7792, + ID_AD7793, + ID_AD7794, + ID_AD7795, + ID_AD7796, + ID_AD7797, + ID_AD7798, + ID_AD7799, +}; + +static struct ad7793_state *ad_sigma_delta_to_ad7793(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7793_state, sd); +} + +static int ad7793_set_channel(struct ad_sigma_delta *sd, unsigned int channel) +{ + struct ad7793_state *st = ad_sigma_delta_to_ad7793(sd); + + st->conf &= ~AD7793_CONF_CHAN_MASK; + st->conf |= AD7793_CONF_CHAN(channel); + + return ad_sd_write_reg(&st->sd, AD7793_REG_CONF, 2, st->conf); +} + +static int ad7793_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7793_state *st = ad_sigma_delta_to_ad7793(sd); + + st->mode &= ~AD7793_MODE_SEL_MASK; + st->mode |= AD7793_MODE_SEL(mode); + + return ad_sd_write_reg(&st->sd, AD7793_REG_MODE, 2, st->mode); +} + +static const struct ad_sigma_delta_info ad7793_sigma_delta_info = { + .set_channel = ad7793_set_channel, + .set_mode = ad7793_set_mode, + .has_registers = true, + .addr_shift = 3, + .read_mask = BIT(6), +}; + +static const struct ad_sd_calib_data ad7793_calib_arr[6] = { + {AD7793_MODE_CAL_INT_ZERO, AD7793_CH_AIN1P_AIN1M}, + {AD7793_MODE_CAL_INT_FULL, AD7793_CH_AIN1P_AIN1M}, + {AD7793_MODE_CAL_INT_ZERO, AD7793_CH_AIN2P_AIN2M}, + {AD7793_MODE_CAL_INT_FULL, AD7793_CH_AIN2P_AIN2M}, + {AD7793_MODE_CAL_INT_ZERO, AD7793_CH_AIN3P_AIN3M}, + {AD7793_MODE_CAL_INT_FULL, AD7793_CH_AIN3P_AIN3M} +}; + +static int ad7793_calibrate_all(struct ad7793_state *st) +{ + return ad_sd_calibrate_all(&st->sd, ad7793_calib_arr, + ARRAY_SIZE(ad7793_calib_arr)); +} + +static int ad7793_check_platform_data(struct ad7793_state *st, + const struct ad7793_platform_data *pdata) +{ + if ((pdata->current_source_direction == AD7793_IEXEC1_IEXEC2_IOUT1 || + pdata->current_source_direction == AD7793_IEXEC1_IEXEC2_IOUT2) && + ((pdata->exitation_current != AD7793_IX_10uA) && + (pdata->exitation_current != AD7793_IX_210uA))) + return -EINVAL; + + if (!(st->chip_info->flags & AD7793_FLAG_HAS_CLKSEL) && + pdata->clock_src != AD7793_CLK_SRC_INT) + return -EINVAL; + + if (!(st->chip_info->flags & AD7793_FLAG_HAS_REFSEL) && + pdata->refsel != AD7793_REFSEL_REFIN1) + return -EINVAL; + + if (!(st->chip_info->flags & AD7793_FLAG_HAS_VBIAS) && + pdata->bias_voltage != AD7793_BIAS_VOLTAGE_DISABLED) + return -EINVAL; + + if (!(st->chip_info->flags & AD7793_HAS_EXITATION_CURRENT) && + pdata->exitation_current != AD7793_IX_DISABLED) + return -EINVAL; + + return 0; +} + +static int ad7793_setup(struct iio_dev *indio_dev, + const struct ad7793_platform_data *pdata, + unsigned int vref_mv) +{ + struct ad7793_state *st = iio_priv(indio_dev); + int i, ret = -1; + unsigned long long scale_uv; + u32 id; + + ret = ad7793_check_platform_data(st, pdata); + if (ret) + return ret; + + /* reset the serial interface */ + ret = spi_write(st->sd.spi, (u8 *)&ret, sizeof(ret)); + if (ret < 0) + goto out; + usleep_range(500, 2000); /* Wait for at least 500us */ + + /* write/read test for device presence */ + ret = ad_sd_read_reg(&st->sd, AD7793_REG_ID, 1, &id); + if (ret) + goto out; + + id &= AD7793_ID_MASK; + + if (id != st->chip_info->id) { + dev_err(&st->sd.spi->dev, "device ID query failed\n"); + goto out; + } + + st->mode = AD7793_MODE_RATE(1); + st->conf = 0; + + if (st->chip_info->flags & AD7793_FLAG_HAS_CLKSEL) + st->mode |= AD7793_MODE_CLKSRC(pdata->clock_src); + if (st->chip_info->flags & AD7793_FLAG_HAS_REFSEL) + st->conf |= AD7793_CONF_REFSEL(pdata->refsel); + if (st->chip_info->flags & AD7793_FLAG_HAS_VBIAS) + st->conf |= AD7793_CONF_VBIAS(pdata->bias_voltage); + if (pdata->buffered || !(st->chip_info->flags & AD7793_FLAG_HAS_BUFFER)) + st->conf |= AD7793_CONF_BUF; + if (pdata->boost_enable && + (st->chip_info->flags & AD7793_FLAG_HAS_VBIAS)) + st->conf |= AD7793_CONF_BOOST; + if (pdata->burnout_current) + st->conf |= AD7793_CONF_BO_EN; + if (pdata->unipolar) + st->conf |= AD7793_CONF_UNIPOLAR; + + if (!(st->chip_info->flags & AD7793_FLAG_HAS_GAIN)) + st->conf |= AD7793_CONF_GAIN(7); + + ret = ad7793_set_mode(&st->sd, AD_SD_MODE_IDLE); + if (ret) + goto out; + + ret = ad7793_set_channel(&st->sd, 0); + if (ret) + goto out; + + if (st->chip_info->flags & AD7793_HAS_EXITATION_CURRENT) { + ret = ad_sd_write_reg(&st->sd, AD7793_REG_IO, 1, + pdata->exitation_current | + (pdata->current_source_direction << 2)); + if (ret) + goto out; + } + + ret = ad7793_calibrate_all(st); + if (ret) + goto out; + + /* Populate available ADC input ranges */ + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) { + scale_uv = ((u64)vref_mv * 100000000) + >> (st->chip_info->channels[0].scan_type.realbits - + (!!(st->conf & AD7793_CONF_UNIPOLAR) ? 0 : 1)); + scale_uv >>= i; + + st->scale_avail[i][1] = do_div(scale_uv, 100000000) * 10; + st->scale_avail[i][0] = scale_uv; + } + + return 0; +out: + dev_err(&st->sd.spi->dev, "setup failed\n"); + return ret; +} + +static const u16 ad7793_sample_freq_avail[16] = {0, 470, 242, 123, 62, 50, 39, + 33, 19, 17, 16, 12, 10, 8, 6, 4}; + +static const u16 ad7797_sample_freq_avail[16] = {0, 0, 0, 123, 62, 50, 0, + 33, 0, 17, 16, 12, 10, 8, 6, 4}; + +static ssize_t ad7793_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7793_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", + st->chip_info->sample_freq_avail[AD7793_MODE_RATE(st->mode)]); +} + +static ssize_t ad7793_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7793_state *st = iio_priv(indio_dev); + long lval; + int i, ret; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + mutex_unlock(&indio_dev->mlock); + + ret = kstrtol(buf, 10, &lval); + if (ret) + return ret; + + if (lval == 0) + return -EINVAL; + + ret = -EINVAL; + + for (i = 0; i < 16; i++) + if (lval == st->chip_info->sample_freq_avail[i]) { + mutex_lock(&indio_dev->mlock); + st->mode &= ~AD7793_MODE_RATE(-1); + st->mode |= AD7793_MODE_RATE(i); + ad_sd_write_reg(&st->sd, AD7793_REG_MODE, + sizeof(st->mode), st->mode); + mutex_unlock(&indio_dev->mlock); + ret = 0; + } + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ad7793_read_frequency, + ad7793_write_frequency); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "470 242 123 62 50 39 33 19 17 16 12 10 8 6 4"); + +static IIO_CONST_ATTR_NAMED(sampling_frequency_available_ad7797, + sampling_frequency_available, "123 62 50 33 17 16 12 10 8 6 4"); + +static ssize_t ad7793_show_scale_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7793_state *st = iio_priv(indio_dev); + int i, len = 0; + + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + len += sprintf(buf + len, "%d.%09u ", st->scale_avail[i][0], + st->scale_avail[i][1]); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static IIO_DEVICE_ATTR_NAMED(in_m_in_scale_available, + in_voltage-voltage_scale_available, S_IRUGO, + ad7793_show_scale_available, NULL, 0); + +static struct attribute *ad7793_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_m_in_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7793_attribute_group = { + .attrs = ad7793_attributes, +}; + +static struct attribute *ad7797_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available_ad7797.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7797_attribute_group = { + .attrs = ad7797_attributes, +}; + +static int ad7793_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7793_state *st = iio_priv(indio_dev); + int ret; + unsigned long long scale_uv; + bool unipolar = !!(st->conf & AD7793_CONF_UNIPOLAR); + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad_sigma_delta_single_conversion(indio_dev, chan, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->differential) { + *val = st-> + scale_avail[(st->conf >> 8) & 0x7][0]; + *val2 = st-> + scale_avail[(st->conf >> 8) & 0x7][1]; + return IIO_VAL_INT_PLUS_NANO; + } else { + /* 1170mV / 2^23 * 6 */ + scale_uv = (1170ULL * 1000000000ULL * 6ULL); + } + break; + case IIO_TEMP: + /* 1170mV / 0.81 mV/C / 2^23 */ + scale_uv = 1444444444444444ULL; + break; + default: + return -EINVAL; + } + + scale_uv >>= (chan->scan_type.realbits - (unipolar ? 0 : 1)); + *val = 0; + *val2 = scale_uv; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + if (!unipolar) + *val = -(1 << (chan->scan_type.realbits - 1)); + else + *val = 0; + + /* Kelvin to Celsius */ + if (chan->type == IIO_TEMP) { + unsigned long long offset; + unsigned int shift; + + shift = chan->scan_type.realbits - (unipolar ? 0 : 1); + offset = 273ULL << shift; + do_div(offset, 1444); + *val -= offset; + } + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int ad7793_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7793_state *st = iio_priv(indio_dev); + int ret, i; + unsigned int tmp; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + if (val2 == st->scale_avail[i][1]) { + ret = 0; + tmp = st->conf; + st->conf &= ~AD7793_CONF_GAIN(-1); + st->conf |= AD7793_CONF_GAIN(i); + + if (tmp == st->conf) + break; + + ad_sd_write_reg(&st->sd, AD7793_REG_CONF, + sizeof(st->conf), st->conf); + ad7793_calibrate_all(st); + break; + } + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static int ad7793_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static const struct iio_info ad7793_info = { + .read_raw = &ad7793_read_raw, + .write_raw = &ad7793_write_raw, + .write_raw_get_fmt = &ad7793_write_raw_get_fmt, + .attrs = &ad7793_attribute_group, + .validate_trigger = ad_sd_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad7797_info = { + .read_raw = &ad7793_read_raw, + .write_raw = &ad7793_write_raw, + .write_raw_get_fmt = &ad7793_write_raw_get_fmt, + .attrs = &ad7793_attribute_group, + .validate_trigger = ad_sd_validate_trigger, + .driver_module = THIS_MODULE, +}; + +#define DECLARE_AD7793_CHANNELS(_name, _b, _sb, _s) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD_SD_DIFF_CHANNEL(0, 0, 0, AD7793_CH_AIN1P_AIN1M, (_b), (_sb), (_s)), \ + AD_SD_DIFF_CHANNEL(1, 1, 1, AD7793_CH_AIN2P_AIN2M, (_b), (_sb), (_s)), \ + AD_SD_DIFF_CHANNEL(2, 2, 2, AD7793_CH_AIN3P_AIN3M, (_b), (_sb), (_s)), \ + AD_SD_SHORTED_CHANNEL(3, 0, AD7793_CH_AIN1M_AIN1M, (_b), (_sb), (_s)), \ + AD_SD_TEMP_CHANNEL(4, AD7793_CH_TEMP, (_b), (_sb), (_s)), \ + AD_SD_SUPPLY_CHANNEL(5, 3, AD7793_CH_AVDD_MONITOR, (_b), (_sb), (_s)), \ + IIO_CHAN_SOFT_TIMESTAMP(6), \ +} + +#define DECLARE_AD7795_CHANNELS(_name, _b, _sb) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD_SD_DIFF_CHANNEL(0, 0, 0, AD7793_CH_AIN1P_AIN1M, (_b), (_sb), 0), \ + AD_SD_DIFF_CHANNEL(1, 1, 1, AD7793_CH_AIN2P_AIN2M, (_b), (_sb), 0), \ + AD_SD_DIFF_CHANNEL(2, 2, 2, AD7793_CH_AIN3P_AIN3M, (_b), (_sb), 0), \ + AD_SD_DIFF_CHANNEL(3, 3, 3, AD7795_CH_AIN4P_AIN4M, (_b), (_sb), 0), \ + AD_SD_DIFF_CHANNEL(4, 4, 4, AD7795_CH_AIN5P_AIN5M, (_b), (_sb), 0), \ + AD_SD_DIFF_CHANNEL(5, 5, 5, AD7795_CH_AIN6P_AIN6M, (_b), (_sb), 0), \ + AD_SD_SHORTED_CHANNEL(6, 0, AD7795_CH_AIN1M_AIN1M, (_b), (_sb), 0), \ + AD_SD_TEMP_CHANNEL(7, AD7793_CH_TEMP, (_b), (_sb), 0), \ + AD_SD_SUPPLY_CHANNEL(8, 3, AD7793_CH_AVDD_MONITOR, (_b), (_sb), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(9), \ +} + +#define DECLARE_AD7797_CHANNELS(_name, _b, _sb) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD_SD_DIFF_CHANNEL(0, 0, 0, AD7793_CH_AIN1P_AIN1M, (_b), (_sb), 0), \ + AD_SD_SHORTED_CHANNEL(1, 0, AD7793_CH_AIN1M_AIN1M, (_b), (_sb), 0), \ + AD_SD_TEMP_CHANNEL(2, AD7793_CH_TEMP, (_b), (_sb), 0), \ + AD_SD_SUPPLY_CHANNEL(3, 3, AD7793_CH_AVDD_MONITOR, (_b), (_sb), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(4), \ +} + +#define DECLARE_AD7799_CHANNELS(_name, _b, _sb) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD_SD_DIFF_CHANNEL(0, 0, 0, AD7793_CH_AIN1P_AIN1M, (_b), (_sb), 0), \ + AD_SD_DIFF_CHANNEL(1, 1, 1, AD7793_CH_AIN2P_AIN2M, (_b), (_sb), 0), \ + AD_SD_DIFF_CHANNEL(2, 2, 2, AD7793_CH_AIN3P_AIN3M, (_b), (_sb), 0), \ + AD_SD_SHORTED_CHANNEL(3, 0, AD7793_CH_AIN1M_AIN1M, (_b), (_sb), 0), \ + AD_SD_SUPPLY_CHANNEL(4, 3, AD7793_CH_AVDD_MONITOR, (_b), (_sb), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(5), \ +} + +static DECLARE_AD7793_CHANNELS(ad7785, 20, 32, 4); +static DECLARE_AD7793_CHANNELS(ad7792, 16, 32, 0); +static DECLARE_AD7793_CHANNELS(ad7793, 24, 32, 0); +static DECLARE_AD7795_CHANNELS(ad7794, 16, 32); +static DECLARE_AD7795_CHANNELS(ad7795, 24, 32); +static DECLARE_AD7797_CHANNELS(ad7796, 16, 16); +static DECLARE_AD7797_CHANNELS(ad7797, 24, 32); +static DECLARE_AD7799_CHANNELS(ad7798, 16, 16); +static DECLARE_AD7799_CHANNELS(ad7799, 24, 32); + +static const struct ad7793_chip_info ad7793_chip_info_tbl[] = { + [ID_AD7785] = { + .id = AD7785_ID, + .channels = ad7785_channels, + .num_channels = ARRAY_SIZE(ad7785_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL | + AD7793_FLAG_HAS_REFSEL | + AD7793_FLAG_HAS_VBIAS | + AD7793_HAS_EXITATION_CURRENT | + AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7792] = { + .id = AD7792_ID, + .channels = ad7792_channels, + .num_channels = ARRAY_SIZE(ad7792_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL | + AD7793_FLAG_HAS_REFSEL | + AD7793_FLAG_HAS_VBIAS | + AD7793_HAS_EXITATION_CURRENT | + AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7793] = { + .id = AD7793_ID, + .channels = ad7793_channels, + .num_channels = ARRAY_SIZE(ad7793_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL | + AD7793_FLAG_HAS_REFSEL | + AD7793_FLAG_HAS_VBIAS | + AD7793_HAS_EXITATION_CURRENT | + AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7794] = { + .id = AD7794_ID, + .channels = ad7794_channels, + .num_channels = ARRAY_SIZE(ad7794_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL | + AD7793_FLAG_HAS_REFSEL | + AD7793_FLAG_HAS_VBIAS | + AD7793_HAS_EXITATION_CURRENT | + AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7795] = { + .id = AD7795_ID, + .channels = ad7795_channels, + .num_channels = ARRAY_SIZE(ad7795_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL | + AD7793_FLAG_HAS_REFSEL | + AD7793_FLAG_HAS_VBIAS | + AD7793_HAS_EXITATION_CURRENT | + AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7796] = { + .id = AD7796_ID, + .channels = ad7796_channels, + .num_channels = ARRAY_SIZE(ad7796_channels), + .iio_info = &ad7797_info, + .sample_freq_avail = ad7797_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL, + }, + [ID_AD7797] = { + .id = AD7797_ID, + .channels = ad7797_channels, + .num_channels = ARRAY_SIZE(ad7797_channels), + .iio_info = &ad7797_info, + .sample_freq_avail = ad7797_sample_freq_avail, + .flags = AD7793_FLAG_HAS_CLKSEL, + }, + [ID_AD7798] = { + .id = AD7798_ID, + .channels = ad7798_channels, + .num_channels = ARRAY_SIZE(ad7798_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, + [ID_AD7799] = { + .id = AD7799_ID, + .channels = ad7799_channels, + .num_channels = ARRAY_SIZE(ad7799_channels), + .iio_info = &ad7793_info, + .sample_freq_avail = ad7793_sample_freq_avail, + .flags = AD7793_FLAG_HAS_GAIN | + AD7793_FLAG_HAS_BUFFER, + }, +}; + +static int ad7793_probe(struct spi_device *spi) +{ + const struct ad7793_platform_data *pdata = spi->dev.platform_data; + struct ad7793_state *st; + struct iio_dev *indio_dev; + int ret, vref_mv = 0; + + if (!pdata) { + dev_err(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + if (!spi->irq) { + dev_err(&spi->dev, "no IRQ?\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + ad_sd_init(&st->sd, indio_dev, spi, &ad7793_sigma_delta_info); + + if (pdata->refsel != AD7793_REFSEL_INTERNAL) { + st->reg = devm_regulator_get(&spi->dev, "refin"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + vref_mv = regulator_get_voltage(st->reg); + if (vref_mv < 0) { + ret = vref_mv; + goto error_disable_reg; + } + + vref_mv /= 1000; + } else { + vref_mv = 1170; /* Build-in ref */ + } + + st->chip_info = + &ad7793_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + spi_set_drvdata(spi, indio_dev); + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = st->chip_info->iio_info; + + ret = ad_sd_setup_buffer_and_trigger(indio_dev); + if (ret) + goto error_disable_reg; + + ret = ad7793_setup(indio_dev, pdata, vref_mv); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + ad_sd_cleanup_buffer_and_trigger(indio_dev); +error_disable_reg: + if (pdata->refsel != AD7793_REFSEL_INTERNAL) + regulator_disable(st->reg); + + return ret; +} + +static int ad7793_remove(struct spi_device *spi) +{ + const struct ad7793_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7793_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + ad_sd_cleanup_buffer_and_trigger(indio_dev); + + if (pdata->refsel != AD7793_REFSEL_INTERNAL) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7793_id[] = { + {"ad7785", ID_AD7785}, + {"ad7792", ID_AD7792}, + {"ad7793", ID_AD7793}, + {"ad7794", ID_AD7794}, + {"ad7795", ID_AD7795}, + {"ad7796", ID_AD7796}, + {"ad7797", ID_AD7797}, + {"ad7798", ID_AD7798}, + {"ad7799", ID_AD7799}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7793_id); + +static struct spi_driver ad7793_driver = { + .driver = { + .name = "ad7793", + .owner = THIS_MODULE, + }, + .probe = ad7793_probe, + .remove = ad7793_remove, + .id_table = ad7793_id, +}; +module_spi_driver(ad7793_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD7793 and similar ADCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7887.c b/drivers/iio/adc/ad7887.c new file mode 100644 index 000000000..2fd012ee9 --- /dev/null +++ b/drivers/iio/adc/ad7887.c @@ -0,0 +1,369 @@ +/* + * AD7887 SPI ADC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/bitops.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> + +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/platform_data/ad7887.h> + +#define AD7887_REF_DIS BIT(5) /* on-chip reference disable */ +#define AD7887_DUAL BIT(4) /* dual-channel mode */ +#define AD7887_CH_AIN1 BIT(3) /* convert on channel 1, DUAL=1 */ +#define AD7887_CH_AIN0 0 /* convert on channel 0, DUAL=0,1 */ +#define AD7887_PM_MODE1 0 /* CS based shutdown */ +#define AD7887_PM_MODE2 1 /* full on */ +#define AD7887_PM_MODE3 2 /* auto shutdown after conversion */ +#define AD7887_PM_MODE4 3 /* standby mode */ + +enum ad7887_channels { + AD7887_CH0, + AD7887_CH0_CH1, + AD7887_CH1, +}; + +/** + * struct ad7887_chip_info - chip specifc information + * @int_vref_mv: the internal reference voltage + * @channel: channel specification + */ +struct ad7887_chip_info { + u16 int_vref_mv; + struct iio_chan_spec channel[3]; +}; + +struct ad7887_state { + struct spi_device *spi; + const struct ad7887_chip_info *chip_info; + struct regulator *reg; + struct spi_transfer xfer[4]; + struct spi_message msg[3]; + struct spi_message *ring_msg; + unsigned char tx_cmd_buf[4]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + * Buffer needs to be large enough to hold two 16 bit samples and a + * 64 bit aligned 64 bit timestamp. + */ + unsigned char data[ALIGN(4, sizeof(s64)) + sizeof(s64)] + ____cacheline_aligned; +}; + +enum ad7887_supported_device_ids { + ID_AD7887 +}; + +static int ad7887_ring_preenable(struct iio_dev *indio_dev) +{ + struct ad7887_state *st = iio_priv(indio_dev); + + /* We know this is a single long so can 'cheat' */ + switch (*indio_dev->active_scan_mask) { + case (1 << 0): + st->ring_msg = &st->msg[AD7887_CH0]; + break; + case (1 << 1): + st->ring_msg = &st->msg[AD7887_CH1]; + /* Dummy read: push CH1 setting down to hardware */ + spi_sync(st->spi, st->ring_msg); + break; + case ((1 << 1) | (1 << 0)): + st->ring_msg = &st->msg[AD7887_CH0_CH1]; + break; + } + + return 0; +} + +static int ad7887_ring_postdisable(struct iio_dev *indio_dev) +{ + struct ad7887_state *st = iio_priv(indio_dev); + + /* dummy read: restore default CH0 settin */ + return spi_sync(st->spi, &st->msg[AD7887_CH0]); +} + +/** + * ad7887_trigger_handler() bh of trigger launched polling to ring buffer + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + **/ +static irqreturn_t ad7887_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7887_state *st = iio_priv(indio_dev); + int b_sent; + + b_sent = spi_sync(st->spi, st->ring_msg); + if (b_sent) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->data, + iio_get_time_ns()); +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops ad7887_ring_setup_ops = { + .preenable = &ad7887_ring_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &ad7887_ring_postdisable, +}; + +static int ad7887_scan_direct(struct ad7887_state *st, unsigned ch) +{ + int ret = spi_sync(st->spi, &st->msg[ch]); + if (ret) + return ret; + + return (st->data[(ch * 2)] << 8) | st->data[(ch * 2) + 1]; +} + +static int ad7887_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7887_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad7887_scan_direct(st, chan->address); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + *val = ret >> chan->scan_type.shift; + *val &= GENMASK(chan->scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (st->reg) { + *val = regulator_get_voltage(st->reg); + if (*val < 0) + return *val; + *val /= 1000; + } else { + *val = st->chip_info->int_vref_mv; + } + + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + + +static const struct ad7887_chip_info ad7887_chip_info_tbl[] = { + /* + * More devices added in future + */ + [ID_AD7887] = { + .channel[0] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .address = 1, + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + .shift = 0, + .endianness = IIO_BE, + }, + }, + .channel[1] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .address = 0, + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 12, + .storagebits = 16, + .shift = 0, + .endianness = IIO_BE, + }, + }, + .channel[2] = IIO_CHAN_SOFT_TIMESTAMP(2), + .int_vref_mv = 2500, + }, +}; + +static const struct iio_info ad7887_info = { + .read_raw = &ad7887_read_raw, + .driver_module = THIS_MODULE, +}; + +static int ad7887_probe(struct spi_device *spi) +{ + struct ad7887_platform_data *pdata = spi->dev.platform_data; + struct ad7887_state *st; + struct iio_dev *indio_dev; + uint8_t mode; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + if (!pdata || !pdata->use_onchip_ref) { + st->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + } + + st->chip_info = + &ad7887_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + + /* Estabilish that the iio_dev is a child of the spi device */ + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad7887_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Setup default message */ + + mode = AD7887_PM_MODE4; + if (!pdata || !pdata->use_onchip_ref) + mode |= AD7887_REF_DIS; + if (pdata && pdata->en_dual) + mode |= AD7887_DUAL; + + st->tx_cmd_buf[0] = AD7887_CH_AIN0 | mode; + + st->xfer[0].rx_buf = &st->data[0]; + st->xfer[0].tx_buf = &st->tx_cmd_buf[0]; + st->xfer[0].len = 2; + + spi_message_init(&st->msg[AD7887_CH0]); + spi_message_add_tail(&st->xfer[0], &st->msg[AD7887_CH0]); + + if (pdata && pdata->en_dual) { + st->tx_cmd_buf[2] = AD7887_CH_AIN1 | mode; + + st->xfer[1].rx_buf = &st->data[0]; + st->xfer[1].tx_buf = &st->tx_cmd_buf[2]; + st->xfer[1].len = 2; + + st->xfer[2].rx_buf = &st->data[2]; + st->xfer[2].tx_buf = &st->tx_cmd_buf[0]; + st->xfer[2].len = 2; + + spi_message_init(&st->msg[AD7887_CH0_CH1]); + spi_message_add_tail(&st->xfer[1], &st->msg[AD7887_CH0_CH1]); + spi_message_add_tail(&st->xfer[2], &st->msg[AD7887_CH0_CH1]); + + st->xfer[3].rx_buf = &st->data[2]; + st->xfer[3].tx_buf = &st->tx_cmd_buf[2]; + st->xfer[3].len = 2; + + spi_message_init(&st->msg[AD7887_CH1]); + spi_message_add_tail(&st->xfer[3], &st->msg[AD7887_CH1]); + + indio_dev->channels = st->chip_info->channel; + indio_dev->num_channels = 3; + } else { + indio_dev->channels = &st->chip_info->channel[1]; + indio_dev->num_channels = 2; + } + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &ad7887_trigger_handler, &ad7887_ring_setup_ops); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unregister_ring; + + return 0; +error_unregister_ring: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_reg: + if (st->reg) + regulator_disable(st->reg); + + return ret; +} + +static int ad7887_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7887_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (st->reg) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7887_id[] = { + {"ad7887", ID_AD7887}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7887_id); + +static struct spi_driver ad7887_driver = { + .driver = { + .name = "ad7887", + .owner = THIS_MODULE, + }, + .probe = ad7887_probe, + .remove = ad7887_remove, + .id_table = ad7887_id, +}; +module_spi_driver(ad7887_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD7887 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7923.c b/drivers/iio/adc/ad7923.c new file mode 100644 index 000000000..28732c28e --- /dev/null +++ b/drivers/iio/adc/ad7923.c @@ -0,0 +1,371 @@ +/* + * AD7904/AD7914/AD7923/AD7924 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc (from AD7923 Driver) + * Copyright 2012 CS Systemes d'Information + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/interrupt.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define AD7923_WRITE_CR (1 << 11) /* write control register */ +#define AD7923_RANGE (1 << 1) /* range to REFin */ +#define AD7923_CODING (1 << 0) /* coding is straight binary */ +#define AD7923_PM_MODE_AS (1) /* auto shutdown */ +#define AD7923_PM_MODE_FS (2) /* full shutdown */ +#define AD7923_PM_MODE_OPS (3) /* normal operation */ +#define AD7923_CHANNEL_0 (0) /* analog input 0 */ +#define AD7923_CHANNEL_1 (1) /* analog input 1 */ +#define AD7923_CHANNEL_2 (2) /* analog input 2 */ +#define AD7923_CHANNEL_3 (3) /* analog input 3 */ +#define AD7923_SEQUENCE_OFF (0) /* no sequence fonction */ +#define AD7923_SEQUENCE_PROTECT (2) /* no interrupt write cycle */ +#define AD7923_SEQUENCE_ON (3) /* continuous sequence */ + +#define AD7923_MAX_CHAN 4 + +#define AD7923_PM_MODE_WRITE(mode) (mode << 4) /* write mode */ +#define AD7923_CHANNEL_WRITE(channel) (channel << 6) /* write channel */ +#define AD7923_SEQUENCE_WRITE(sequence) (((sequence & 1) << 3) \ + + ((sequence & 2) << 9)) + /* write sequence fonction */ +/* left shift for CR : bit 11 transmit in first */ +#define AD7923_SHIFT_REGISTER 4 + +/* val = value, dec = left shift, bits = number of bits of the mask */ +#define EXTRACT(val, dec, bits) ((val >> dec) & ((1 << bits) - 1)) + +struct ad7923_state { + struct spi_device *spi; + struct spi_transfer ring_xfer[5]; + struct spi_transfer scan_single_xfer[2]; + struct spi_message ring_msg; + struct spi_message scan_single_msg; + + struct regulator *reg; + + unsigned int settings; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __be16 rx_buf[4] ____cacheline_aligned; + __be16 tx_buf[4]; +}; + +struct ad7923_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +enum ad7923_id { + AD7904, + AD7914, + AD7924, +}; + +#define AD7923_V_CHAN(index, bits) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +#define DECLARE_AD7923_CHANNELS(name, bits) \ +const struct iio_chan_spec name ## _channels[] = { \ + AD7923_V_CHAN(0, bits), \ + AD7923_V_CHAN(1, bits), \ + AD7923_V_CHAN(2, bits), \ + AD7923_V_CHAN(3, bits), \ + IIO_CHAN_SOFT_TIMESTAMP(4), \ +} + +static DECLARE_AD7923_CHANNELS(ad7904, 8); +static DECLARE_AD7923_CHANNELS(ad7914, 10); +static DECLARE_AD7923_CHANNELS(ad7924, 12); + +static const struct ad7923_chip_info ad7923_chip_info[] = { + [AD7904] = { + .channels = ad7904_channels, + .num_channels = ARRAY_SIZE(ad7904_channels), + }, + [AD7914] = { + .channels = ad7914_channels, + .num_channels = ARRAY_SIZE(ad7914_channels), + }, + [AD7924] = { + .channels = ad7924_channels, + .num_channels = ARRAY_SIZE(ad7924_channels), + }, +}; + +/** + * ad7923_update_scan_mode() setup the spi transfer buffer for the new scan mask + **/ +static int ad7923_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *active_scan_mask) +{ + struct ad7923_state *st = iio_priv(indio_dev); + int i, cmd, len; + + len = 0; + for_each_set_bit(i, active_scan_mask, AD7923_MAX_CHAN) { + cmd = AD7923_WRITE_CR | AD7923_CHANNEL_WRITE(i) | + AD7923_SEQUENCE_WRITE(AD7923_SEQUENCE_OFF) | + st->settings; + cmd <<= AD7923_SHIFT_REGISTER; + st->tx_buf[len++] = cpu_to_be16(cmd); + } + /* build spi ring message */ + st->ring_xfer[0].tx_buf = &st->tx_buf[0]; + st->ring_xfer[0].len = len; + st->ring_xfer[0].cs_change = 1; + + spi_message_init(&st->ring_msg); + spi_message_add_tail(&st->ring_xfer[0], &st->ring_msg); + + for (i = 0; i < len; i++) { + st->ring_xfer[i + 1].rx_buf = &st->rx_buf[i]; + st->ring_xfer[i + 1].len = 2; + st->ring_xfer[i + 1].cs_change = 1; + spi_message_add_tail(&st->ring_xfer[i + 1], &st->ring_msg); + } + /* make sure last transfer cs_change is not set */ + st->ring_xfer[i + 1].cs_change = 0; + + return 0; +} + +/** + * ad7923_trigger_handler() bh of trigger launched polling to ring buffer + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + **/ +static irqreturn_t ad7923_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7923_state *st = iio_priv(indio_dev); + int b_sent; + + b_sent = spi_sync(st->spi, &st->ring_msg); + if (b_sent) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, st->rx_buf, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad7923_scan_direct(struct ad7923_state *st, unsigned ch) +{ + int ret, cmd; + + cmd = AD7923_WRITE_CR | AD7923_CHANNEL_WRITE(ch) | + AD7923_SEQUENCE_WRITE(AD7923_SEQUENCE_OFF) | + st->settings; + cmd <<= AD7923_SHIFT_REGISTER; + st->tx_buf[0] = cpu_to_be16(cmd); + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + return be16_to_cpu(st->rx_buf[0]); +} + +static int ad7923_get_range(struct ad7923_state *st) +{ + int vref; + + vref = regulator_get_voltage(st->reg); + if (vref < 0) + return vref; + + vref /= 1000; + + if (!(st->settings & AD7923_RANGE)) + vref *= 2; + + return vref; +} + +static int ad7923_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7923_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad7923_scan_direct(st, chan->address); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + if (chan->address == EXTRACT(ret, 12, 4)) + *val = EXTRACT(ret, 0, 12); + else + return -EIO; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = ad7923_get_range(st); + if (ret < 0) + return ret; + *val = ret; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static const struct iio_info ad7923_info = { + .read_raw = &ad7923_read_raw, + .update_scan_mode = ad7923_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static int ad7923_probe(struct spi_device *spi) +{ + struct ad7923_state *st; + struct iio_dev *indio_dev; + const struct ad7923_chip_info *info; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + st->settings = AD7923_CODING | AD7923_RANGE | + AD7923_PM_MODE_WRITE(AD7923_PM_MODE_OPS); + + info = &ad7923_chip_info[spi_get_device_id(spi)->driver_data]; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + indio_dev->info = &ad7923_info; + + /* Setup default message */ + + st->scan_single_xfer[0].tx_buf = &st->tx_buf[0]; + st->scan_single_xfer[0].len = 2; + st->scan_single_xfer[0].cs_change = 1; + st->scan_single_xfer[1].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[1].len = 2; + + spi_message_init(&st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[0], &st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[1], &st->scan_single_msg); + + st->reg = devm_regulator_get(&spi->dev, "refin"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &ad7923_trigger_handler, NULL); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_ring; + + return 0; + +error_cleanup_ring: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_reg: + regulator_disable(st->reg); + + return ret; +} + +static int ad7923_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7923_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7923_id[] = { + {"ad7904", AD7904}, + {"ad7914", AD7914}, + {"ad7923", AD7924}, + {"ad7924", AD7924}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7923_id); + +static struct spi_driver ad7923_driver = { + .driver = { + .name = "ad7923", + .owner = THIS_MODULE, + }, + .probe = ad7923_probe, + .remove = ad7923_remove, + .id_table = ad7923_id, +}; +module_spi_driver(ad7923_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_AUTHOR("Patrick Vasseur <patrick.vasseur@c-s.fr>"); +MODULE_DESCRIPTION("Analog Devices AD7904/AD7914/AD7923/AD7924 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad799x.c b/drivers/iio/adc/ad799x.c new file mode 100644 index 000000000..b99de00e5 --- /dev/null +++ b/drivers/iio/adc/ad799x.c @@ -0,0 +1,905 @@ +/* + * iio/adc/ad799x.c + * Copyright (C) 2010-2011 Michael Hennerich, Analog Devices Inc. + * + * based on iio/adc/max1363 + * Copyright (C) 2008-2010 Jonathan Cameron + * + * based on linux/drivers/i2c/chips/max123x + * Copyright (C) 2002-2004 Stefan Eletzhofer + * + * based on linux/drivers/acron/char/pcf8583.c + * Copyright (C) 2000 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ad799x.c + * + * Support for ad7991, ad7995, ad7999, ad7992, ad7993, ad7994, ad7997, + * ad7998 and similar chips. + * + */ + +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/sysfs.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/bitops.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define AD799X_CHANNEL_SHIFT 4 + +/* + * AD7991, AD7995 and AD7999 defines + */ + +#define AD7991_REF_SEL 0x08 +#define AD7991_FLTR 0x04 +#define AD7991_BIT_TRIAL_DELAY 0x02 +#define AD7991_SAMPLE_DELAY 0x01 + +/* + * AD7992, AD7993, AD7994, AD7997 and AD7998 defines + */ + +#define AD7998_FLTR BIT(3) +#define AD7998_ALERT_EN BIT(2) +#define AD7998_BUSY_ALERT BIT(1) +#define AD7998_BUSY_ALERT_POL BIT(0) + +#define AD7998_CONV_RES_REG 0x0 +#define AD7998_ALERT_STAT_REG 0x1 +#define AD7998_CONF_REG 0x2 +#define AD7998_CYCLE_TMR_REG 0x3 + +#define AD7998_DATALOW_REG(x) ((x) * 3 + 0x4) +#define AD7998_DATAHIGH_REG(x) ((x) * 3 + 0x5) +#define AD7998_HYST_REG(x) ((x) * 3 + 0x6) + +#define AD7998_CYC_MASK GENMASK(2, 0) +#define AD7998_CYC_DIS 0x0 +#define AD7998_CYC_TCONF_32 0x1 +#define AD7998_CYC_TCONF_64 0x2 +#define AD7998_CYC_TCONF_128 0x3 +#define AD7998_CYC_TCONF_256 0x4 +#define AD7998_CYC_TCONF_512 0x5 +#define AD7998_CYC_TCONF_1024 0x6 +#define AD7998_CYC_TCONF_2048 0x7 + +#define AD7998_ALERT_STAT_CLEAR 0xFF + +/* + * AD7997 and AD7997 defines + */ + +#define AD7997_8_READ_SINGLE BIT(7) +#define AD7997_8_READ_SEQUENCE (BIT(6) | BIT(5) | BIT(4)) + +enum { + ad7991, + ad7995, + ad7999, + ad7992, + ad7993, + ad7994, + ad7997, + ad7998 +}; + +/** + * struct ad799x_chip_config - chip specific information + * @channel: channel specification + * @default_config: device default configuration + * @info: pointer to iio_info struct + */ +struct ad799x_chip_config { + const struct iio_chan_spec channel[9]; + u16 default_config; + const struct iio_info *info; +}; + +/** + * struct ad799x_chip_info - chip specific information + * @num_channels: number of channels + * @noirq_config: device configuration w/o IRQ + * @irq_config: device configuration w/IRQ + */ +struct ad799x_chip_info { + int num_channels; + const struct ad799x_chip_config noirq_config; + const struct ad799x_chip_config irq_config; +}; + +struct ad799x_state { + struct i2c_client *client; + const struct ad799x_chip_config *chip_config; + struct regulator *reg; + struct regulator *vref; + unsigned id; + u16 config; + + u8 *rx_buf; + unsigned int transfer_size; +}; + +static int ad799x_write_config(struct ad799x_state *st, u16 val) +{ + switch (st->id) { + case ad7997: + case ad7998: + return i2c_smbus_write_word_swapped(st->client, AD7998_CONF_REG, + val); + case ad7992: + case ad7993: + case ad7994: + return i2c_smbus_write_byte_data(st->client, AD7998_CONF_REG, + val); + default: + /* Will be written when doing a conversion */ + st->config = val; + return 0; + } +} + +static int ad799x_read_config(struct ad799x_state *st) +{ + switch (st->id) { + case ad7997: + case ad7998: + return i2c_smbus_read_word_swapped(st->client, AD7998_CONF_REG); + case ad7992: + case ad7993: + case ad7994: + return i2c_smbus_read_byte_data(st->client, AD7998_CONF_REG); + default: + /* No readback support */ + return st->config; + } +} + +/** + * ad799x_trigger_handler() bh of trigger launched polling to ring buffer + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + **/ +static irqreturn_t ad799x_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad799x_state *st = iio_priv(indio_dev); + int b_sent; + u8 cmd; + + switch (st->id) { + case ad7991: + case ad7995: + case ad7999: + cmd = st->config | + (*indio_dev->active_scan_mask << AD799X_CHANNEL_SHIFT); + break; + case ad7992: + case ad7993: + case ad7994: + cmd = (*indio_dev->active_scan_mask << AD799X_CHANNEL_SHIFT) | + AD7998_CONV_RES_REG; + break; + case ad7997: + case ad7998: + cmd = AD7997_8_READ_SEQUENCE | AD7998_CONV_RES_REG; + break; + default: + cmd = 0; + } + + b_sent = i2c_smbus_read_i2c_block_data(st->client, + cmd, st->transfer_size, st->rx_buf); + if (b_sent < 0) + goto out; + + iio_push_to_buffers_with_timestamp(indio_dev, st->rx_buf, + iio_get_time_ns()); +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ad799x_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad799x_state *st = iio_priv(indio_dev); + + kfree(st->rx_buf); + st->rx_buf = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!st->rx_buf) + return -ENOMEM; + + st->transfer_size = bitmap_weight(scan_mask, indio_dev->masklength) * 2; + + switch (st->id) { + case ad7992: + case ad7993: + case ad7994: + case ad7997: + case ad7998: + st->config &= ~(GENMASK(7, 0) << AD799X_CHANNEL_SHIFT); + st->config |= (*scan_mask << AD799X_CHANNEL_SHIFT); + return ad799x_write_config(st, st->config); + default: + return 0; + } +} + +static int ad799x_scan_direct(struct ad799x_state *st, unsigned ch) +{ + u8 cmd; + + switch (st->id) { + case ad7991: + case ad7995: + case ad7999: + cmd = st->config | (BIT(ch) << AD799X_CHANNEL_SHIFT); + break; + case ad7992: + case ad7993: + case ad7994: + cmd = BIT(ch) << AD799X_CHANNEL_SHIFT; + break; + case ad7997: + case ad7998: + cmd = (ch << AD799X_CHANNEL_SHIFT) | AD7997_8_READ_SINGLE; + break; + default: + return -EINVAL; + } + + return i2c_smbus_read_word_swapped(st->client, cmd); +} + +static int ad799x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad799x_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad799x_scan_direct(st, chan->scan_index); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + *val = (ret >> chan->scan_type.shift) & + GENMASK(chan->scan_type.realbits - 1, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(st->vref); + if (ret < 0) + return ret; + *val = ret / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} +static const unsigned int ad7998_frequencies[] = { + [AD7998_CYC_DIS] = 0, + [AD7998_CYC_TCONF_32] = 15625, + [AD7998_CYC_TCONF_64] = 7812, + [AD7998_CYC_TCONF_128] = 3906, + [AD7998_CYC_TCONF_512] = 976, + [AD7998_CYC_TCONF_1024] = 488, + [AD7998_CYC_TCONF_2048] = 244, +}; + +static ssize_t ad799x_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad799x_state *st = iio_priv(indio_dev); + + int ret = i2c_smbus_read_byte_data(st->client, AD7998_CYCLE_TMR_REG); + if (ret < 0) + return ret; + + return sprintf(buf, "%u\n", ad7998_frequencies[ret & AD7998_CYC_MASK]); +} + +static ssize_t ad799x_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad799x_state *st = iio_priv(indio_dev); + + long val; + int ret, i; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + ret = i2c_smbus_read_byte_data(st->client, AD7998_CYCLE_TMR_REG); + if (ret < 0) + goto error_ret_mutex; + /* Wipe the bits clean */ + ret &= ~AD7998_CYC_MASK; + + for (i = 0; i < ARRAY_SIZE(ad7998_frequencies); i++) + if (val == ad7998_frequencies[i]) + break; + if (i == ARRAY_SIZE(ad7998_frequencies)) { + ret = -EINVAL; + goto error_ret_mutex; + } + + ret = i2c_smbus_write_byte_data(st->client, AD7998_CYCLE_TMR_REG, + ret | i); + if (ret < 0) + goto error_ret_mutex; + ret = len; + +error_ret_mutex: + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad799x_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 ad799x_state *st = iio_priv(indio_dev); + + if (!(st->config & AD7998_ALERT_EN)) + return 0; + + if ((st->config >> AD799X_CHANNEL_SHIFT) & BIT(chan->scan_index)) + return 1; + + return 0; +} + +static int ad799x_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 ad799x_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto done; + } + + if (state) + st->config |= BIT(chan->scan_index) << AD799X_CHANNEL_SHIFT; + else + st->config &= ~(BIT(chan->scan_index) << AD799X_CHANNEL_SHIFT); + + if (st->config >> AD799X_CHANNEL_SHIFT) + st->config |= AD7998_ALERT_EN; + else + st->config &= ~AD7998_ALERT_EN; + + ret = ad799x_write_config(st, st->config); + +done: + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static unsigned int ad799x_threshold_reg(const struct iio_chan_spec *chan, + enum iio_event_direction dir, + enum iio_event_info info) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + if (dir == IIO_EV_DIR_FALLING) + return AD7998_DATALOW_REG(chan->channel); + else + return AD7998_DATAHIGH_REG(chan->channel); + case IIO_EV_INFO_HYSTERESIS: + return AD7998_HYST_REG(chan->channel); + default: + return -EINVAL; + } + + return 0; +} + +static int ad799x_write_event_value(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) +{ + int ret; + struct ad799x_state *st = iio_priv(indio_dev); + + if (val < 0 || val > GENMASK(chan->scan_type.realbits - 1, 0)) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = i2c_smbus_write_word_swapped(st->client, + ad799x_threshold_reg(chan, dir, info), + val << chan->scan_type.shift); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad799x_read_event_value(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) +{ + int ret; + struct ad799x_state *st = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + ret = i2c_smbus_read_word_swapped(st->client, + ad799x_threshold_reg(chan, dir, info)); + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + *val = (ret >> chan->scan_type.shift) & + GENMASK(chan->scan_type.realbits - 1 , 0); + + return IIO_VAL_INT; +} + +static irqreturn_t ad799x_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad799x_state *st = iio_priv(private); + int i, ret; + + ret = i2c_smbus_read_byte_data(st->client, AD7998_ALERT_STAT_REG); + if (ret <= 0) + goto done; + + if (i2c_smbus_write_byte_data(st->client, AD7998_ALERT_STAT_REG, + AD7998_ALERT_STAT_CLEAR) < 0) + goto done; + + for (i = 0; i < 8; i++) { + if (ret & BIT(i)) + iio_push_event(indio_dev, + i & 0x1 ? + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + (i >> 1), + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING) : + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + (i >> 1), + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns()); + } + +done: + return IRQ_HANDLED; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ad799x_read_frequency, + ad799x_write_frequency); +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("15625 7812 3906 1953 976 488 244 0"); + +static struct attribute *ad799x_event_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad799x_event_attrs_group = { + .attrs = ad799x_event_attributes, + .name = "events", +}; + +static const struct iio_info ad7991_info = { + .read_raw = &ad799x_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad7993_4_7_8_noirq_info = { + .read_raw = &ad799x_read_raw, + .driver_module = THIS_MODULE, + .update_scan_mode = ad799x_update_scan_mode, +}; + +static const struct iio_info ad7993_4_7_8_irq_info = { + .read_raw = &ad799x_read_raw, + .event_attrs = &ad799x_event_attrs_group, + .read_event_config = &ad799x_read_event_config, + .write_event_config = &ad799x_write_event_config, + .read_event_value = &ad799x_read_event_value, + .write_event_value = &ad799x_write_event_value, + .driver_module = THIS_MODULE, + .update_scan_mode = ad799x_update_scan_mode, +}; + +static const struct iio_event_spec ad799x_events[] = { + { + .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), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +#define _AD799X_CHANNEL(_index, _realbits, _ev_spec, _num_ev_spec) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_realbits), \ + .storagebits = 16, \ + .shift = 12 - (_realbits), \ + .endianness = IIO_BE, \ + }, \ + .event_spec = _ev_spec, \ + .num_event_specs = _num_ev_spec, \ +} + +#define AD799X_CHANNEL(_index, _realbits) \ + _AD799X_CHANNEL(_index, _realbits, NULL, 0) + +#define AD799X_CHANNEL_WITH_EVENTS(_index, _realbits) \ + _AD799X_CHANNEL(_index, _realbits, ad799x_events, \ + ARRAY_SIZE(ad799x_events)) + +static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { + [ad7991] = { + .num_channels = 5, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 12), + AD799X_CHANNEL(1, 12), + AD799X_CHANNEL(2, 12), + AD799X_CHANNEL(3, 12), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .info = &ad7991_info, + }, + }, + [ad7995] = { + .num_channels = 5, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 10), + AD799X_CHANNEL(1, 10), + AD799X_CHANNEL(2, 10), + AD799X_CHANNEL(3, 10), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .info = &ad7991_info, + }, + }, + [ad7999] = { + .num_channels = 5, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 8), + AD799X_CHANNEL(1, 8), + AD799X_CHANNEL(2, 8), + AD799X_CHANNEL(3, 8), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .info = &ad7991_info, + }, + }, + [ad7992] = { + .num_channels = 3, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 12), + AD799X_CHANNEL(1, 12), + IIO_CHAN_SOFT_TIMESTAMP(3), + }, + .info = &ad7993_4_7_8_noirq_info, + }, + .irq_config = { + .channel = { + AD799X_CHANNEL_WITH_EVENTS(0, 12), + AD799X_CHANNEL_WITH_EVENTS(1, 12), + IIO_CHAN_SOFT_TIMESTAMP(3), + }, + .default_config = AD7998_ALERT_EN | AD7998_BUSY_ALERT, + .info = &ad7993_4_7_8_irq_info, + }, + }, + [ad7993] = { + .num_channels = 5, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 10), + AD799X_CHANNEL(1, 10), + AD799X_CHANNEL(2, 10), + AD799X_CHANNEL(3, 10), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .info = &ad7993_4_7_8_noirq_info, + }, + .irq_config = { + .channel = { + AD799X_CHANNEL_WITH_EVENTS(0, 10), + AD799X_CHANNEL_WITH_EVENTS(1, 10), + AD799X_CHANNEL_WITH_EVENTS(2, 10), + AD799X_CHANNEL_WITH_EVENTS(3, 10), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .default_config = AD7998_ALERT_EN | AD7998_BUSY_ALERT, + .info = &ad7993_4_7_8_irq_info, + }, + }, + [ad7994] = { + .num_channels = 5, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 12), + AD799X_CHANNEL(1, 12), + AD799X_CHANNEL(2, 12), + AD799X_CHANNEL(3, 12), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .info = &ad7993_4_7_8_noirq_info, + }, + .irq_config = { + .channel = { + AD799X_CHANNEL_WITH_EVENTS(0, 12), + AD799X_CHANNEL_WITH_EVENTS(1, 12), + AD799X_CHANNEL_WITH_EVENTS(2, 12), + AD799X_CHANNEL_WITH_EVENTS(3, 12), + IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .default_config = AD7998_ALERT_EN | AD7998_BUSY_ALERT, + .info = &ad7993_4_7_8_irq_info, + }, + }, + [ad7997] = { + .num_channels = 9, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 10), + AD799X_CHANNEL(1, 10), + AD799X_CHANNEL(2, 10), + AD799X_CHANNEL(3, 10), + AD799X_CHANNEL(4, 10), + AD799X_CHANNEL(5, 10), + AD799X_CHANNEL(6, 10), + AD799X_CHANNEL(7, 10), + IIO_CHAN_SOFT_TIMESTAMP(8), + }, + .info = &ad7993_4_7_8_noirq_info, + }, + .irq_config = { + .channel = { + AD799X_CHANNEL_WITH_EVENTS(0, 10), + AD799X_CHANNEL_WITH_EVENTS(1, 10), + AD799X_CHANNEL_WITH_EVENTS(2, 10), + AD799X_CHANNEL_WITH_EVENTS(3, 10), + AD799X_CHANNEL(4, 10), + AD799X_CHANNEL(5, 10), + AD799X_CHANNEL(6, 10), + AD799X_CHANNEL(7, 10), + IIO_CHAN_SOFT_TIMESTAMP(8), + }, + .default_config = AD7998_ALERT_EN | AD7998_BUSY_ALERT, + .info = &ad7993_4_7_8_irq_info, + }, + }, + [ad7998] = { + .num_channels = 9, + .noirq_config = { + .channel = { + AD799X_CHANNEL(0, 12), + AD799X_CHANNEL(1, 12), + AD799X_CHANNEL(2, 12), + AD799X_CHANNEL(3, 12), + AD799X_CHANNEL(4, 12), + AD799X_CHANNEL(5, 12), + AD799X_CHANNEL(6, 12), + AD799X_CHANNEL(7, 12), + IIO_CHAN_SOFT_TIMESTAMP(8), + }, + .info = &ad7993_4_7_8_noirq_info, + }, + .irq_config = { + .channel = { + AD799X_CHANNEL_WITH_EVENTS(0, 12), + AD799X_CHANNEL_WITH_EVENTS(1, 12), + AD799X_CHANNEL_WITH_EVENTS(2, 12), + AD799X_CHANNEL_WITH_EVENTS(3, 12), + AD799X_CHANNEL(4, 12), + AD799X_CHANNEL(5, 12), + AD799X_CHANNEL(6, 12), + AD799X_CHANNEL(7, 12), + IIO_CHAN_SOFT_TIMESTAMP(8), + }, + .default_config = AD7998_ALERT_EN | AD7998_BUSY_ALERT, + .info = &ad7993_4_7_8_irq_info, + }, + }, +}; + +static int ad799x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct ad799x_state *st; + struct iio_dev *indio_dev; + const struct ad799x_chip_info *chip_info = + &ad799x_chip_info_tbl[id->driver_data]; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + st->id = id->driver_data; + if (client->irq > 0 && chip_info->irq_config.info) + st->chip_config = &chip_info->irq_config; + else + st->chip_config = &chip_info->noirq_config; + + /* TODO: Add pdata options for filtering and bit delay */ + + st->reg = devm_regulator_get(&client->dev, "vcc"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + ret = regulator_enable(st->reg); + if (ret) + return ret; + st->vref = devm_regulator_get(&client->dev, "vref"); + if (IS_ERR(st->vref)) { + ret = PTR_ERR(st->vref); + goto error_disable_reg; + } + ret = regulator_enable(st->vref); + if (ret) + goto error_disable_reg; + + st->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->info = st->chip_config->info; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_config->channel; + indio_dev->num_channels = chip_info->num_channels; + + ret = ad799x_write_config(st, st->chip_config->default_config); + if (ret < 0) + goto error_disable_reg; + ret = ad799x_read_config(st); + if (ret < 0) + goto error_disable_reg; + st->config = ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &ad799x_trigger_handler, NULL); + if (ret) + goto error_disable_vref; + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, + client->irq, + NULL, + ad799x_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + client->name, + indio_dev); + if (ret) + goto error_cleanup_ring; + } + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_ring; + + return 0; + +error_cleanup_ring: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_vref: + regulator_disable(st->vref); +error_disable_reg: + regulator_disable(st->reg); + + return ret; +} + +static int ad799x_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ad799x_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(st->vref); + regulator_disable(st->reg); + kfree(st->rx_buf); + + return 0; +} + +static const struct i2c_device_id ad799x_id[] = { + { "ad7991", ad7991 }, + { "ad7995", ad7995 }, + { "ad7999", ad7999 }, + { "ad7992", ad7992 }, + { "ad7993", ad7993 }, + { "ad7994", ad7994 }, + { "ad7997", ad7997 }, + { "ad7998", ad7998 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad799x_id); + +static struct i2c_driver ad799x_driver = { + .driver = { + .name = "ad799x", + }, + .probe = ad799x_probe, + .remove = ad799x_remove, + .id_table = ad799x_id, +}; +module_i2c_driver(ad799x_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD799x ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c new file mode 100644 index 000000000..d10bd0c97 --- /dev/null +++ b/drivers/iio/adc/ad_sigma_delta.c @@ -0,0 +1,553 @@ +/* + * Support code for Analog Devices Sigma-Delta ADCs + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/err.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/adc/ad_sigma_delta.h> + +#include <asm/unaligned.h> + + +#define AD_SD_COMM_CHAN_MASK 0x3 + +#define AD_SD_REG_COMM 0x00 +#define AD_SD_REG_DATA 0x03 + +/** + * ad_sd_set_comm() - Set communications register + * + * @sigma_delta: The sigma delta device + * @comm: New value for the communications register + */ +void ad_sd_set_comm(struct ad_sigma_delta *sigma_delta, uint8_t comm) +{ + /* Some variants use the lower two bits of the communications register + * to select the channel */ + sigma_delta->comm = comm & AD_SD_COMM_CHAN_MASK; +} +EXPORT_SYMBOL_GPL(ad_sd_set_comm); + +/** + * ad_sd_write_reg() - Write a register + * + * @sigma_delta: The sigma delta device + * @reg: Address of the register + * @size: Size of the register (0-3) + * @val: Value to write to the register + * + * Returns 0 on success, an error code otherwise. + **/ +int ad_sd_write_reg(struct ad_sigma_delta *sigma_delta, unsigned int reg, + unsigned int size, unsigned int val) +{ + uint8_t *data = sigma_delta->data; + struct spi_transfer t = { + .tx_buf = data, + .len = size + 1, + .cs_change = sigma_delta->bus_locked, + }; + struct spi_message m; + int ret; + + data[0] = (reg << sigma_delta->info->addr_shift) | sigma_delta->comm; + + switch (size) { + case 3: + data[1] = val >> 16; + data[2] = val >> 8; + data[3] = val; + break; + case 2: + put_unaligned_be16(val, &data[1]); + break; + case 1: + data[1] = val; + break; + case 0: + break; + default: + return -EINVAL; + } + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + if (sigma_delta->bus_locked) + ret = spi_sync_locked(sigma_delta->spi, &m); + else + ret = spi_sync(sigma_delta->spi, &m); + + return ret; +} +EXPORT_SYMBOL_GPL(ad_sd_write_reg); + +static int ad_sd_read_reg_raw(struct ad_sigma_delta *sigma_delta, + unsigned int reg, unsigned int size, uint8_t *val) +{ + uint8_t *data = sigma_delta->data; + int ret; + struct spi_transfer t[] = { + { + .tx_buf = data, + .len = 1, + }, { + .rx_buf = val, + .len = size, + .cs_change = sigma_delta->bus_locked, + }, + }; + struct spi_message m; + + spi_message_init(&m); + + if (sigma_delta->info->has_registers) { + data[0] = reg << sigma_delta->info->addr_shift; + data[0] |= sigma_delta->info->read_mask; + spi_message_add_tail(&t[0], &m); + } + spi_message_add_tail(&t[1], &m); + + if (sigma_delta->bus_locked) + ret = spi_sync_locked(sigma_delta->spi, &m); + else + ret = spi_sync(sigma_delta->spi, &m); + + return ret; +} + +/** + * ad_sd_read_reg() - Read a register + * + * @sigma_delta: The sigma delta device + * @reg: Address of the register + * @size: Size of the register (1-4) + * @val: Read value + * + * Returns 0 on success, an error code otherwise. + **/ +int ad_sd_read_reg(struct ad_sigma_delta *sigma_delta, + unsigned int reg, unsigned int size, unsigned int *val) +{ + int ret; + + ret = ad_sd_read_reg_raw(sigma_delta, reg, size, sigma_delta->data); + if (ret < 0) + goto out; + + switch (size) { + case 4: + *val = get_unaligned_be32(sigma_delta->data); + break; + case 3: + *val = (sigma_delta->data[0] << 16) | + (sigma_delta->data[1] << 8) | + sigma_delta->data[2]; + break; + case 2: + *val = get_unaligned_be16(sigma_delta->data); + break; + case 1: + *val = sigma_delta->data[0]; + break; + default: + ret = -EINVAL; + break; + } + +out: + return ret; +} +EXPORT_SYMBOL_GPL(ad_sd_read_reg); + +static int ad_sd_calibrate(struct ad_sigma_delta *sigma_delta, + unsigned int mode, unsigned int channel) +{ + int ret; + + ret = ad_sigma_delta_set_channel(sigma_delta, channel); + if (ret) + return ret; + + spi_bus_lock(sigma_delta->spi->master); + sigma_delta->bus_locked = true; + reinit_completion(&sigma_delta->completion); + + ret = ad_sigma_delta_set_mode(sigma_delta, mode); + if (ret < 0) + goto out; + + sigma_delta->irq_dis = false; + enable_irq(sigma_delta->spi->irq); + ret = wait_for_completion_timeout(&sigma_delta->completion, 2*HZ); + if (ret == 0) { + sigma_delta->irq_dis = true; + disable_irq_nosync(sigma_delta->spi->irq); + ret = -EIO; + } else { + ret = 0; + } +out: + sigma_delta->bus_locked = false; + spi_bus_unlock(sigma_delta->spi->master); + ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_IDLE); + + return ret; +} + +/** + * ad_sd_calibrate_all() - Performs channel calibration + * @sigma_delta: The sigma delta device + * @cb: Array of channels and calibration type to perform + * @n: Number of items in cb + * + * Returns 0 on success, an error code otherwise. + **/ +int ad_sd_calibrate_all(struct ad_sigma_delta *sigma_delta, + const struct ad_sd_calib_data *cb, unsigned int n) +{ + unsigned int i; + int ret; + + for (i = 0; i < n; i++) { + ret = ad_sd_calibrate(sigma_delta, cb[i].mode, cb[i].channel); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(ad_sd_calibrate_all); + +/** + * ad_sigma_delta_single_conversion() - Performs a single data conversion + * @indio_dev: The IIO device + * @chan: The conversion is done for this channel + * @val: Pointer to the location where to store the read value + * + * Returns: 0 on success, an error value otherwise. + */ +int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + unsigned int sample, raw_sample; + int ret = 0; + + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + mutex_lock(&indio_dev->mlock); + ad_sigma_delta_set_channel(sigma_delta, chan->address); + + spi_bus_lock(sigma_delta->spi->master); + sigma_delta->bus_locked = true; + reinit_completion(&sigma_delta->completion); + + ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_SINGLE); + + sigma_delta->irq_dis = false; + enable_irq(sigma_delta->spi->irq); + ret = wait_for_completion_interruptible_timeout( + &sigma_delta->completion, HZ); + + sigma_delta->bus_locked = false; + spi_bus_unlock(sigma_delta->spi->master); + + if (ret == 0) + ret = -EIO; + if (ret < 0) + goto out; + + ret = ad_sd_read_reg(sigma_delta, AD_SD_REG_DATA, + DIV_ROUND_UP(chan->scan_type.realbits + chan->scan_type.shift, 8), + &raw_sample); + +out: + if (!sigma_delta->irq_dis) { + disable_irq_nosync(sigma_delta->spi->irq); + sigma_delta->irq_dis = true; + } + + ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_IDLE); + mutex_unlock(&indio_dev->mlock); + + if (ret) + return ret; + + sample = raw_sample >> chan->scan_type.shift; + sample &= (1 << chan->scan_type.realbits) - 1; + *val = sample; + + ret = ad_sigma_delta_postprocess_sample(sigma_delta, raw_sample); + if (ret) + return ret; + + return IIO_VAL_INT; +} +EXPORT_SYMBOL_GPL(ad_sigma_delta_single_conversion); + +static int ad_sd_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + unsigned int channel; + int ret; + + ret = iio_triggered_buffer_postenable(indio_dev); + if (ret < 0) + return ret; + + channel = find_first_bit(indio_dev->active_scan_mask, + indio_dev->masklength); + ret = ad_sigma_delta_set_channel(sigma_delta, + indio_dev->channels[channel].address); + if (ret) + goto err_predisable; + + spi_bus_lock(sigma_delta->spi->master); + sigma_delta->bus_locked = true; + ret = ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_CONTINUOUS); + if (ret) + goto err_unlock; + + sigma_delta->irq_dis = false; + enable_irq(sigma_delta->spi->irq); + + return 0; + +err_unlock: + spi_bus_unlock(sigma_delta->spi->master); +err_predisable: + + return ret; +} + +static int ad_sd_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + + reinit_completion(&sigma_delta->completion); + wait_for_completion_timeout(&sigma_delta->completion, HZ); + + if (!sigma_delta->irq_dis) { + disable_irq_nosync(sigma_delta->spi->irq); + sigma_delta->irq_dis = true; + } + + ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_IDLE); + + sigma_delta->bus_locked = false; + return spi_bus_unlock(sigma_delta->spi->master); +} + +static irqreturn_t ad_sd_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + unsigned int reg_size; + uint8_t data[16]; + int ret; + + memset(data, 0x00, 16); + + reg_size = indio_dev->channels[0].scan_type.realbits + + indio_dev->channels[0].scan_type.shift; + reg_size = DIV_ROUND_UP(reg_size, 8); + + switch (reg_size) { + case 4: + case 2: + case 1: + ret = ad_sd_read_reg_raw(sigma_delta, AD_SD_REG_DATA, + reg_size, &data[0]); + break; + case 3: + /* We store 24 bit samples in a 32 bit word. Keep the upper + * byte set to zero. */ + ret = ad_sd_read_reg_raw(sigma_delta, AD_SD_REG_DATA, + reg_size, &data[1]); + break; + } + + iio_push_to_buffers_with_timestamp(indio_dev, data, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + sigma_delta->irq_dis = false; + enable_irq(sigma_delta->spi->irq); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops ad_sd_buffer_setup_ops = { + .postenable = &ad_sd_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &ad_sd_buffer_postdisable, + .validate_scan_mask = &iio_validate_scan_mask_onehot, +}; + +static irqreturn_t ad_sd_data_rdy_trig_poll(int irq, void *private) +{ + struct ad_sigma_delta *sigma_delta = private; + + complete(&sigma_delta->completion); + disable_irq_nosync(irq); + sigma_delta->irq_dis = true; + iio_trigger_poll(sigma_delta->trig); + + return IRQ_HANDLED; +} + +/** + * ad_sd_validate_trigger() - validate_trigger callback for ad_sigma_delta devices + * @indio_dev: The IIO device + * @trig: The new trigger + * + * Returns: 0 if the 'trig' matches the trigger registered by the ad_sigma_delta + * device, -EINVAL otherwise. + */ +int ad_sd_validate_trigger(struct iio_dev *indio_dev, struct iio_trigger *trig) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + + if (sigma_delta->trig != trig) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(ad_sd_validate_trigger); + +static const struct iio_trigger_ops ad_sd_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int ad_sd_probe_trigger(struct iio_dev *indio_dev) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + int ret; + + sigma_delta->trig = iio_trigger_alloc("%s-dev%d", indio_dev->name, + indio_dev->id); + if (sigma_delta->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + sigma_delta->trig->ops = &ad_sd_trigger_ops; + init_completion(&sigma_delta->completion); + + ret = request_irq(sigma_delta->spi->irq, + ad_sd_data_rdy_trig_poll, + IRQF_TRIGGER_LOW, + indio_dev->name, + sigma_delta); + if (ret) + goto error_free_trig; + + if (!sigma_delta->irq_dis) { + sigma_delta->irq_dis = true; + disable_irq_nosync(sigma_delta->spi->irq); + } + sigma_delta->trig->dev.parent = &sigma_delta->spi->dev; + iio_trigger_set_drvdata(sigma_delta->trig, sigma_delta); + + ret = iio_trigger_register(sigma_delta->trig); + if (ret) + goto error_free_irq; + + /* select default trigger */ + indio_dev->trig = iio_trigger_get(sigma_delta->trig); + + return 0; + +error_free_irq: + free_irq(sigma_delta->spi->irq, sigma_delta); +error_free_trig: + iio_trigger_free(sigma_delta->trig); +error_ret: + return ret; +} + +static void ad_sd_remove_trigger(struct iio_dev *indio_dev) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + + iio_trigger_unregister(sigma_delta->trig); + free_irq(sigma_delta->spi->irq, sigma_delta); + iio_trigger_free(sigma_delta->trig); +} + +/** + * ad_sd_setup_buffer_and_trigger() - + * @indio_dev: The IIO device + */ +int ad_sd_setup_buffer_and_trigger(struct iio_dev *indio_dev) +{ + int ret; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &ad_sd_trigger_handler, &ad_sd_buffer_setup_ops); + if (ret) + return ret; + + ret = ad_sd_probe_trigger(indio_dev); + if (ret) { + iio_triggered_buffer_cleanup(indio_dev); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(ad_sd_setup_buffer_and_trigger); + +/** + * ad_sd_cleanup_buffer_and_trigger() - + * @indio_dev: The IIO device + */ +void ad_sd_cleanup_buffer_and_trigger(struct iio_dev *indio_dev) +{ + ad_sd_remove_trigger(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); +} +EXPORT_SYMBOL_GPL(ad_sd_cleanup_buffer_and_trigger); + +/** + * ad_sd_init() - Initializes a ad_sigma_delta struct + * @sigma_delta: The ad_sigma_delta device + * @indio_dev: The IIO device which the Sigma Delta device is used for + * @spi: The SPI device for the ad_sigma_delta device + * @info: Device specific callbacks and options + * + * This function needs to be called before any other operations are performed on + * the ad_sigma_delta struct. + */ +int ad_sd_init(struct ad_sigma_delta *sigma_delta, struct iio_dev *indio_dev, + struct spi_device *spi, const struct ad_sigma_delta_info *info) +{ + sigma_delta->spi = spi; + sigma_delta->info = info; + iio_device_set_drvdata(indio_dev, sigma_delta); + + return 0; +} +EXPORT_SYMBOL_GPL(ad_sd_init); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices Sigma-Delta ADCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c new file mode 100644 index 000000000..7b40925dd --- /dev/null +++ b/drivers/iio/adc/at91_adc.c @@ -0,0 +1,1438 @@ +/* + * Driver for the ADC present in the Atmel AT91 evaluation boards. + * + * Copyright 2011 Free Electrons + * + * Licensed under the GPLv2 or later. + */ + +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/wait.h> + +#include <linux/platform_data/at91_adc.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* Registers */ +#define AT91_ADC_CR 0x00 /* Control Register */ +#define AT91_ADC_SWRST (1 << 0) /* Software Reset */ +#define AT91_ADC_START (1 << 1) /* Start Conversion */ + +#define AT91_ADC_MR 0x04 /* Mode Register */ +#define AT91_ADC_TSAMOD (3 << 0) /* ADC mode */ +#define AT91_ADC_TSAMOD_ADC_ONLY_MODE (0 << 0) /* ADC Mode */ +#define AT91_ADC_TSAMOD_TS_ONLY_MODE (1 << 0) /* Touch Screen Only Mode */ +#define AT91_ADC_TRGEN (1 << 0) /* Trigger Enable */ +#define AT91_ADC_TRGSEL (7 << 1) /* Trigger Selection */ +#define AT91_ADC_TRGSEL_TC0 (0 << 1) +#define AT91_ADC_TRGSEL_TC1 (1 << 1) +#define AT91_ADC_TRGSEL_TC2 (2 << 1) +#define AT91_ADC_TRGSEL_EXTERNAL (6 << 1) +#define AT91_ADC_LOWRES (1 << 4) /* Low Resolution */ +#define AT91_ADC_SLEEP (1 << 5) /* Sleep Mode */ +#define AT91_ADC_PENDET (1 << 6) /* Pen contact detection enable */ +#define AT91_ADC_PRESCAL_9260 (0x3f << 8) /* Prescalar Rate Selection */ +#define AT91_ADC_PRESCAL_9G45 (0xff << 8) +#define AT91_ADC_PRESCAL_(x) ((x) << 8) +#define AT91_ADC_STARTUP_9260 (0x1f << 16) /* Startup Up Time */ +#define AT91_ADC_STARTUP_9G45 (0x7f << 16) +#define AT91_ADC_STARTUP_9X5 (0xf << 16) +#define AT91_ADC_STARTUP_(x) ((x) << 16) +#define AT91_ADC_SHTIM (0xf << 24) /* Sample & Hold Time */ +#define AT91_ADC_SHTIM_(x) ((x) << 24) +#define AT91_ADC_PENDBC (0x0f << 28) /* Pen Debounce time */ +#define AT91_ADC_PENDBC_(x) ((x) << 28) + +#define AT91_ADC_TSR 0x0C +#define AT91_ADC_TSR_SHTIM (0xf << 24) /* Sample & Hold Time */ +#define AT91_ADC_TSR_SHTIM_(x) ((x) << 24) + +#define AT91_ADC_CHER 0x10 /* Channel Enable Register */ +#define AT91_ADC_CHDR 0x14 /* Channel Disable Register */ +#define AT91_ADC_CHSR 0x18 /* Channel Status Register */ +#define AT91_ADC_CH(n) (1 << (n)) /* Channel Number */ + +#define AT91_ADC_SR 0x1C /* Status Register */ +#define AT91_ADC_EOC(n) (1 << (n)) /* End of Conversion on Channel N */ +#define AT91_ADC_OVRE(n) (1 << ((n) + 8))/* Overrun Error on Channel N */ +#define AT91_ADC_DRDY (1 << 16) /* Data Ready */ +#define AT91_ADC_GOVRE (1 << 17) /* General Overrun Error */ +#define AT91_ADC_ENDRX (1 << 18) /* End of RX Buffer */ +#define AT91_ADC_RXFUFF (1 << 19) /* RX Buffer Full */ + +#define AT91_ADC_SR_9X5 0x30 /* Status Register for 9x5 */ +#define AT91_ADC_SR_DRDY_9X5 (1 << 24) /* Data Ready */ + +#define AT91_ADC_LCDR 0x20 /* Last Converted Data Register */ +#define AT91_ADC_LDATA (0x3ff) + +#define AT91_ADC_IER 0x24 /* Interrupt Enable Register */ +#define AT91_ADC_IDR 0x28 /* Interrupt Disable Register */ +#define AT91_ADC_IMR 0x2C /* Interrupt Mask Register */ +#define AT91RL_ADC_IER_PEN (1 << 20) +#define AT91RL_ADC_IER_NOPEN (1 << 21) +#define AT91_ADC_IER_PEN (1 << 29) +#define AT91_ADC_IER_NOPEN (1 << 30) +#define AT91_ADC_IER_XRDY (1 << 20) +#define AT91_ADC_IER_YRDY (1 << 21) +#define AT91_ADC_IER_PRDY (1 << 22) +#define AT91_ADC_ISR_PENS (1 << 31) + +#define AT91_ADC_CHR(n) (0x30 + ((n) * 4)) /* Channel Data Register N */ +#define AT91_ADC_DATA (0x3ff) + +#define AT91_ADC_CDR0_9X5 (0x50) /* Channel Data Register 0 for 9X5 */ + +#define AT91_ADC_ACR 0x94 /* Analog Control Register */ +#define AT91_ADC_ACR_PENDETSENS (0x3 << 0) /* pull-up resistor */ + +#define AT91_ADC_TSMR 0xB0 +#define AT91_ADC_TSMR_TSMODE (3 << 0) /* Touch Screen Mode */ +#define AT91_ADC_TSMR_TSMODE_NONE (0 << 0) +#define AT91_ADC_TSMR_TSMODE_4WIRE_NO_PRESS (1 << 0) +#define AT91_ADC_TSMR_TSMODE_4WIRE_PRESS (2 << 0) +#define AT91_ADC_TSMR_TSMODE_5WIRE (3 << 0) +#define AT91_ADC_TSMR_TSAV (3 << 4) /* Averages samples */ +#define AT91_ADC_TSMR_TSAV_(x) ((x) << 4) +#define AT91_ADC_TSMR_SCTIM (0x0f << 16) /* Switch closure time */ +#define AT91_ADC_TSMR_PENDBC (0x0f << 28) /* Pen Debounce time */ +#define AT91_ADC_TSMR_PENDBC_(x) ((x) << 28) +#define AT91_ADC_TSMR_NOTSDMA (1 << 22) /* No Touchscreen DMA */ +#define AT91_ADC_TSMR_PENDET_DIS (0 << 24) /* Pen contact detection disable */ +#define AT91_ADC_TSMR_PENDET_ENA (1 << 24) /* Pen contact detection enable */ + +#define AT91_ADC_TSXPOSR 0xB4 +#define AT91_ADC_TSYPOSR 0xB8 +#define AT91_ADC_TSPRESSR 0xBC + +#define AT91_ADC_TRGR_9260 AT91_ADC_MR +#define AT91_ADC_TRGR_9G45 0x08 +#define AT91_ADC_TRGR_9X5 0xC0 + +/* Trigger Register bit field */ +#define AT91_ADC_TRGR_TRGPER (0xffff << 16) +#define AT91_ADC_TRGR_TRGPER_(x) ((x) << 16) +#define AT91_ADC_TRGR_TRGMOD (0x7 << 0) +#define AT91_ADC_TRGR_NONE (0 << 0) +#define AT91_ADC_TRGR_MOD_PERIOD_TRIG (5 << 0) + +#define AT91_ADC_CHAN(st, ch) \ + (st->registers->channel_base + (ch * 4)) +#define at91_adc_readl(st, reg) \ + (readl_relaxed(st->reg_base + reg)) +#define at91_adc_writel(st, reg, val) \ + (writel_relaxed(val, st->reg_base + reg)) + +#define DRIVER_NAME "at91_adc" +#define MAX_POS_BITS 12 + +#define TOUCH_SAMPLE_PERIOD_US 2000 /* 2ms */ +#define TOUCH_PEN_DETECT_DEBOUNCE_US 200 + +#define MAX_RLPOS_BITS 10 +#define TOUCH_SAMPLE_PERIOD_US_RL 10000 /* 10ms, the SoC can't keep up with 2ms */ +#define TOUCH_SHTIM 0xa + +/** + * struct at91_adc_reg_desc - Various informations relative to registers + * @channel_base: Base offset for the channel data registers + * @drdy_mask: Mask of the DRDY field in the relevant registers + (Interruptions registers mostly) + * @status_register: Offset of the Interrupt Status Register + * @trigger_register: Offset of the Trigger setup register + * @mr_prescal_mask: Mask of the PRESCAL field in the adc MR register + * @mr_startup_mask: Mask of the STARTUP field in the adc MR register + */ +struct at91_adc_reg_desc { + u8 channel_base; + u32 drdy_mask; + u8 status_register; + u8 trigger_register; + u32 mr_prescal_mask; + u32 mr_startup_mask; +}; + +struct at91_adc_caps { + bool has_ts; /* Support touch screen */ + bool has_tsmr; /* only at91sam9x5, sama5d3 have TSMR reg */ + /* + * Numbers of sampling data will be averaged. Can be 0~3. + * Hardware can average (2 ^ ts_filter_average) sample data. + */ + u8 ts_filter_average; + /* Pen Detection input pull-up resistor, can be 0~3 */ + u8 ts_pen_detect_sensitivity; + + /* startup time calculate function */ + u32 (*calc_startup_ticks)(u32 startup_time, u32 adc_clk_khz); + + u8 num_channels; + struct at91_adc_reg_desc registers; +}; + +struct at91_adc_state { + struct clk *adc_clk; + u16 *buffer; + unsigned long channels_mask; + struct clk *clk; + bool done; + int irq; + u16 last_value; + int chnb; + struct mutex lock; + u8 num_channels; + void __iomem *reg_base; + struct at91_adc_reg_desc *registers; + u32 startup_time; + u8 sample_hold_time; + bool sleep_mode; + struct iio_trigger **trig; + struct at91_adc_trigger *trigger_list; + u32 trigger_number; + bool use_external; + u32 vref_mv; + u32 res; /* resolution used for convertions */ + bool low_res; /* the resolution corresponds to the lowest one */ + wait_queue_head_t wq_data_avail; + struct at91_adc_caps *caps; + + /* + * Following ADC channels are shared by touchscreen: + * + * CH0 -- Touch screen XP/UL + * CH1 -- Touch screen XM/UR + * CH2 -- Touch screen YP/LL + * CH3 -- Touch screen YM/Sense + * CH4 -- Touch screen LR(5-wire only) + * + * The bitfields below represents the reserved channel in the + * touchscreen mode. + */ +#define CHAN_MASK_TOUCHSCREEN_4WIRE (0xf << 0) +#define CHAN_MASK_TOUCHSCREEN_5WIRE (0x1f << 0) + enum atmel_adc_ts_type touchscreen_type; + struct input_dev *ts_input; + + u16 ts_sample_period_val; + u32 ts_pressure_threshold; + u16 ts_pendbc; + + bool ts_bufferedmeasure; + u32 ts_prev_absx; + u32 ts_prev_absy; +}; + +static irqreturn_t at91_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *idev = pf->indio_dev; + struct at91_adc_state *st = iio_priv(idev); + int i, j = 0; + + for (i = 0; i < idev->masklength; i++) { + if (!test_bit(i, idev->active_scan_mask)) + continue; + st->buffer[j] = at91_adc_readl(st, AT91_ADC_CHAN(st, i)); + j++; + } + + iio_push_to_buffers_with_timestamp(idev, st->buffer, pf->timestamp); + + iio_trigger_notify_done(idev->trig); + + /* Needed to ACK the DRDY interruption */ + at91_adc_readl(st, AT91_ADC_LCDR); + + enable_irq(st->irq); + + return IRQ_HANDLED; +} + +/* Handler for classic adc channel eoc trigger */ +static void handle_adc_eoc_trigger(int irq, struct iio_dev *idev) +{ + struct at91_adc_state *st = iio_priv(idev); + + if (iio_buffer_enabled(idev)) { + disable_irq_nosync(irq); + iio_trigger_poll(idev->trig); + } else { + st->last_value = at91_adc_readl(st, AT91_ADC_CHAN(st, st->chnb)); + st->done = true; + wake_up_interruptible(&st->wq_data_avail); + } +} + +static int at91_ts_sample(struct at91_adc_state *st) +{ + unsigned int xscale, yscale, reg, z1, z2; + unsigned int x, y, pres, xpos, ypos; + unsigned int rxp = 1; + unsigned int factor = 1000; + struct iio_dev *idev = iio_priv_to_dev(st); + + unsigned int xyz_mask_bits = st->res; + unsigned int xyz_mask = (1 << xyz_mask_bits) - 1; + + /* calculate position */ + /* x position = (x / xscale) * max, max = 2^MAX_POS_BITS - 1 */ + reg = at91_adc_readl(st, AT91_ADC_TSXPOSR); + xpos = reg & xyz_mask; + x = (xpos << MAX_POS_BITS) - xpos; + xscale = (reg >> 16) & xyz_mask; + if (xscale == 0) { + dev_err(&idev->dev, "Error: xscale == 0!\n"); + return -1; + } + x /= xscale; + + /* y position = (y / yscale) * max, max = 2^MAX_POS_BITS - 1 */ + reg = at91_adc_readl(st, AT91_ADC_TSYPOSR); + ypos = reg & xyz_mask; + y = (ypos << MAX_POS_BITS) - ypos; + yscale = (reg >> 16) & xyz_mask; + if (yscale == 0) { + dev_err(&idev->dev, "Error: yscale == 0!\n"); + return -1; + } + y /= yscale; + + /* calculate the pressure */ + reg = at91_adc_readl(st, AT91_ADC_TSPRESSR); + z1 = reg & xyz_mask; + z2 = (reg >> 16) & xyz_mask; + + if (z1 != 0) + pres = rxp * (x * factor / 1024) * (z2 * factor / z1 - factor) + / factor; + else + pres = st->ts_pressure_threshold; /* no pen contacted */ + + dev_dbg(&idev->dev, "xpos = %d, xscale = %d, ypos = %d, yscale = %d, z1 = %d, z2 = %d, press = %d\n", + xpos, xscale, ypos, yscale, z1, z2, pres); + + if (pres < st->ts_pressure_threshold) { + dev_dbg(&idev->dev, "x = %d, y = %d, pressure = %d\n", + x, y, pres / factor); + input_report_abs(st->ts_input, ABS_X, x); + input_report_abs(st->ts_input, ABS_Y, y); + input_report_abs(st->ts_input, ABS_PRESSURE, pres); + input_report_key(st->ts_input, BTN_TOUCH, 1); + input_sync(st->ts_input); + } else { + dev_dbg(&idev->dev, "pressure too low: not reporting\n"); + } + + return 0; +} + +static irqreturn_t at91_adc_rl_interrupt(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + u32 status = at91_adc_readl(st, st->registers->status_register); + unsigned int reg; + + status &= at91_adc_readl(st, AT91_ADC_IMR); + if (status & GENMASK(st->num_channels - 1, 0)) + handle_adc_eoc_trigger(irq, idev); + + if (status & AT91RL_ADC_IER_PEN) { + /* Disabling pen debounce is required to get a NOPEN irq */ + reg = at91_adc_readl(st, AT91_ADC_MR); + reg &= ~AT91_ADC_PENDBC; + at91_adc_writel(st, AT91_ADC_MR, reg); + + at91_adc_writel(st, AT91_ADC_IDR, AT91RL_ADC_IER_PEN); + at91_adc_writel(st, AT91_ADC_IER, AT91RL_ADC_IER_NOPEN + | AT91_ADC_EOC(3)); + /* Set up period trigger for sampling */ + at91_adc_writel(st, st->registers->trigger_register, + AT91_ADC_TRGR_MOD_PERIOD_TRIG | + AT91_ADC_TRGR_TRGPER_(st->ts_sample_period_val)); + } else if (status & AT91RL_ADC_IER_NOPEN) { + reg = at91_adc_readl(st, AT91_ADC_MR); + reg |= AT91_ADC_PENDBC_(st->ts_pendbc) & AT91_ADC_PENDBC; + at91_adc_writel(st, AT91_ADC_MR, reg); + at91_adc_writel(st, st->registers->trigger_register, + AT91_ADC_TRGR_NONE); + + at91_adc_writel(st, AT91_ADC_IDR, AT91RL_ADC_IER_NOPEN + | AT91_ADC_EOC(3)); + at91_adc_writel(st, AT91_ADC_IER, AT91RL_ADC_IER_PEN); + st->ts_bufferedmeasure = false; + input_report_key(st->ts_input, BTN_TOUCH, 0); + input_sync(st->ts_input); + } else if (status & AT91_ADC_EOC(3)) { + /* Conversion finished */ + if (st->ts_bufferedmeasure) { + /* + * Last measurement is always discarded, since it can + * be erroneous. + * Always report previous measurement + */ + input_report_abs(st->ts_input, ABS_X, st->ts_prev_absx); + input_report_abs(st->ts_input, ABS_Y, st->ts_prev_absy); + input_report_key(st->ts_input, BTN_TOUCH, 1); + input_sync(st->ts_input); + } else + st->ts_bufferedmeasure = true; + + /* Now make new measurement */ + st->ts_prev_absx = at91_adc_readl(st, AT91_ADC_CHAN(st, 3)) + << MAX_RLPOS_BITS; + st->ts_prev_absx /= at91_adc_readl(st, AT91_ADC_CHAN(st, 2)); + + st->ts_prev_absy = at91_adc_readl(st, AT91_ADC_CHAN(st, 1)) + << MAX_RLPOS_BITS; + st->ts_prev_absy /= at91_adc_readl(st, AT91_ADC_CHAN(st, 0)); + } + + return IRQ_HANDLED; +} + +static irqreturn_t at91_adc_9x5_interrupt(int irq, void *private) +{ + struct iio_dev *idev = private; + struct at91_adc_state *st = iio_priv(idev); + u32 status = at91_adc_readl(st, st->registers->status_register); + const uint32_t ts_data_irq_mask = + AT91_ADC_IER_XRDY | + AT91_ADC_IER_YRDY | + AT91_ADC_IER_PRDY; + + if (status & GENMASK(st->num_channels - 1, 0)) + handle_adc_eoc_trigger(irq, idev); + + if (status & AT91_ADC_IER_PEN) { + at91_adc_writel(st, AT91_ADC_IDR, AT91_ADC_IER_PEN); + at91_adc_writel(st, AT91_ADC_IER, AT91_ADC_IER_NOPEN | + ts_data_irq_mask); + /* Set up period trigger for sampling */ + at91_adc_writel(st, st->registers->trigger_register, + AT91_ADC_TRGR_MOD_PERIOD_TRIG | + AT91_ADC_TRGR_TRGPER_(st->ts_sample_period_val)); + } else if (status & AT91_ADC_IER_NOPEN) { + at91_adc_writel(st, st->registers->trigger_register, 0); + at91_adc_writel(st, AT91_ADC_IDR, AT91_ADC_IER_NOPEN | + ts_data_irq_mask); + at91_adc_writel(st, AT91_ADC_IER, AT91_ADC_IER_PEN); + + input_report_key(st->ts_input, BTN_TOUCH, 0); + input_sync(st->ts_input); + } else if ((status & ts_data_irq_mask) == ts_data_irq_mask) { + /* Now all touchscreen data is ready */ + + if (status & AT91_ADC_ISR_PENS) { + /* validate data by pen contact */ + at91_ts_sample(st); + } else { + /* triggered by event that is no pen contact, just read + * them to clean the interrupt and discard all. + */ + at91_adc_readl(st, AT91_ADC_TSXPOSR); + at91_adc_readl(st, AT91_ADC_TSYPOSR); + at91_adc_readl(st, AT91_ADC_TSPRESSR); + } + } + + return IRQ_HANDLED; +} + +static int at91_adc_channel_init(struct iio_dev *idev) +{ + struct at91_adc_state *st = iio_priv(idev); + struct iio_chan_spec *chan_array, *timestamp; + int bit, idx = 0; + unsigned long rsvd_mask = 0; + + /* If touchscreen is enable, then reserve the adc channels */ + if (st->touchscreen_type == ATMEL_ADC_TOUCHSCREEN_4WIRE) + rsvd_mask = CHAN_MASK_TOUCHSCREEN_4WIRE; + else if (st->touchscreen_type == ATMEL_ADC_TOUCHSCREEN_5WIRE) + rsvd_mask = CHAN_MASK_TOUCHSCREEN_5WIRE; + + /* set up the channel mask to reserve touchscreen channels */ + st->channels_mask &= ~rsvd_mask; + + idev->num_channels = bitmap_weight(&st->channels_mask, + st->num_channels) + 1; + + chan_array = devm_kzalloc(&idev->dev, + ((idev->num_channels + 1) * + sizeof(struct iio_chan_spec)), + GFP_KERNEL); + + if (!chan_array) + return -ENOMEM; + + for_each_set_bit(bit, &st->channels_mask, st->num_channels) { + struct iio_chan_spec *chan = chan_array + idx; + + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->channel = bit; + chan->scan_index = idx; + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = st->res; + chan->scan_type.storagebits = 16; + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + idx++; + } + timestamp = chan_array + idx; + + timestamp->type = IIO_TIMESTAMP; + timestamp->channel = -1; + timestamp->scan_index = idx; + timestamp->scan_type.sign = 's'; + timestamp->scan_type.realbits = 64; + timestamp->scan_type.storagebits = 64; + + idev->channels = chan_array; + return idev->num_channels; +} + +static int at91_adc_get_trigger_value_by_name(struct iio_dev *idev, + struct at91_adc_trigger *triggers, + const char *trigger_name) +{ + struct at91_adc_state *st = iio_priv(idev); + int i; + + for (i = 0; i < st->trigger_number; i++) { + char *name = kasprintf(GFP_KERNEL, + "%s-dev%d-%s", + idev->name, + idev->id, + triggers[i].name); + if (!name) + return -ENOMEM; + + if (strcmp(trigger_name, name) == 0) { + kfree(name); + if (triggers[i].value == 0) + return -EINVAL; + return triggers[i].value; + } + + kfree(name); + } + + return -EINVAL; +} + +static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) +{ + struct iio_dev *idev = iio_trigger_get_drvdata(trig); + struct at91_adc_state *st = iio_priv(idev); + struct at91_adc_reg_desc *reg = st->registers; + u32 status = at91_adc_readl(st, reg->trigger_register); + int value; + u8 bit; + + value = at91_adc_get_trigger_value_by_name(idev, + st->trigger_list, + idev->trig->name); + if (value < 0) + return value; + + if (state) { + st->buffer = kmalloc(idev->scan_bytes, GFP_KERNEL); + if (st->buffer == NULL) + return -ENOMEM; + + at91_adc_writel(st, reg->trigger_register, + status | value); + + for_each_set_bit(bit, idev->active_scan_mask, + st->num_channels) { + struct iio_chan_spec const *chan = idev->channels + bit; + at91_adc_writel(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + } + + at91_adc_writel(st, AT91_ADC_IER, reg->drdy_mask); + + } else { + at91_adc_writel(st, AT91_ADC_IDR, reg->drdy_mask); + + at91_adc_writel(st, reg->trigger_register, + status & ~value); + + for_each_set_bit(bit, idev->active_scan_mask, + st->num_channels) { + struct iio_chan_spec const *chan = idev->channels + bit; + at91_adc_writel(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + } + kfree(st->buffer); + } + + return 0; +} + +static const struct iio_trigger_ops at91_adc_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &at91_adc_configure_trigger, +}; + +static struct iio_trigger *at91_adc_allocate_trigger(struct iio_dev *idev, + struct at91_adc_trigger *trigger) +{ + struct iio_trigger *trig; + int ret; + + trig = iio_trigger_alloc("%s-dev%d-%s", idev->name, + idev->id, trigger->name); + if (trig == NULL) + return NULL; + + trig->dev.parent = idev->dev.parent; + iio_trigger_set_drvdata(trig, idev); + trig->ops = &at91_adc_trigger_ops; + + ret = iio_trigger_register(trig); + if (ret) + return NULL; + + return trig; +} + +static int at91_adc_trigger_init(struct iio_dev *idev) +{ + struct at91_adc_state *st = iio_priv(idev); + int i, ret; + + st->trig = devm_kzalloc(&idev->dev, + st->trigger_number * sizeof(*st->trig), + GFP_KERNEL); + + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + for (i = 0; i < st->trigger_number; i++) { + if (st->trigger_list[i].is_external && !(st->use_external)) + continue; + + st->trig[i] = at91_adc_allocate_trigger(idev, + st->trigger_list + i); + if (st->trig[i] == NULL) { + dev_err(&idev->dev, + "Could not allocate trigger %d\n", i); + ret = -ENOMEM; + goto error_trigger; + } + } + + return 0; + +error_trigger: + for (i--; i >= 0; i--) { + iio_trigger_unregister(st->trig[i]); + iio_trigger_free(st->trig[i]); + } +error_ret: + return ret; +} + +static void at91_adc_trigger_remove(struct iio_dev *idev) +{ + struct at91_adc_state *st = iio_priv(idev); + int i; + + for (i = 0; i < st->trigger_number; i++) { + iio_trigger_unregister(st->trig[i]); + iio_trigger_free(st->trig[i]); + } +} + +static int at91_adc_buffer_init(struct iio_dev *idev) +{ + return iio_triggered_buffer_setup(idev, &iio_pollfunc_store_time, + &at91_adc_trigger_handler, NULL); +} + +static void at91_adc_buffer_remove(struct iio_dev *idev) +{ + iio_triggered_buffer_cleanup(idev); +} + +static int at91_adc_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(idev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + + st->chnb = chan->channel; + at91_adc_writel(st, AT91_ADC_CHER, + AT91_ADC_CH(chan->channel)); + at91_adc_writel(st, AT91_ADC_IER, BIT(chan->channel)); + at91_adc_writel(st, AT91_ADC_CR, AT91_ADC_START); + + ret = wait_event_interruptible_timeout(st->wq_data_avail, + st->done, + msecs_to_jiffies(1000)); + if (ret == 0) + ret = -ETIMEDOUT; + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + + *val = st->last_value; + + at91_adc_writel(st, AT91_ADC_CHDR, + AT91_ADC_CH(chan->channel)); + at91_adc_writel(st, AT91_ADC_IDR, BIT(chan->channel)); + + st->last_value = 0; + st->done = false; + mutex_unlock(&st->lock); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + return -EINVAL; +} + +static int at91_adc_of_get_resolution(struct at91_adc_state *st, + struct platform_device *pdev) +{ + struct iio_dev *idev = iio_priv_to_dev(st); + struct device_node *np = pdev->dev.of_node; + int count, i, ret = 0; + char *res_name, *s; + u32 *resolutions; + + count = of_property_count_strings(np, "atmel,adc-res-names"); + if (count < 2) { + dev_err(&idev->dev, "You must specified at least two resolution names for " + "adc-res-names property in the DT\n"); + return count; + } + + resolutions = kmalloc(count * sizeof(*resolutions), GFP_KERNEL); + if (!resolutions) + return -ENOMEM; + + if (of_property_read_u32_array(np, "atmel,adc-res", resolutions, count)) { + dev_err(&idev->dev, "Missing adc-res property in the DT.\n"); + ret = -ENODEV; + goto ret; + } + + if (of_property_read_string(np, "atmel,adc-use-res", (const char **)&res_name)) + res_name = "highres"; + + for (i = 0; i < count; i++) { + if (of_property_read_string_index(np, "atmel,adc-res-names", i, (const char **)&s)) + continue; + + if (strcmp(res_name, s)) + continue; + + st->res = resolutions[i]; + if (!strcmp(res_name, "lowres")) + st->low_res = true; + else + st->low_res = false; + + dev_info(&idev->dev, "Resolution used: %u bits\n", st->res); + goto ret; + } + + dev_err(&idev->dev, "There is no resolution for %s\n", res_name); + +ret: + kfree(resolutions); + return ret; +} + +static u32 calc_startup_ticks_9260(u32 startup_time, u32 adc_clk_khz) +{ + /* + * Number of ticks needed to cover the startup time of the ADC + * as defined in the electrical characteristics of the board, + * divided by 8. The formula thus is : + * Startup Time = (ticks + 1) * 8 / ADC Clock + */ + return round_up((startup_time * adc_clk_khz / 1000) - 1, 8) / 8; +} + +static u32 calc_startup_ticks_9x5(u32 startup_time, u32 adc_clk_khz) +{ + /* + * For sama5d3x and at91sam9x5, the formula changes to: + * Startup Time = <lookup_table_value> / ADC Clock + */ + const int startup_lookup[] = { + 0 , 8 , 16 , 24 , + 64 , 80 , 96 , 112, + 512, 576, 640, 704, + 768, 832, 896, 960 + }; + int i, size = ARRAY_SIZE(startup_lookup); + unsigned int ticks; + + ticks = startup_time * adc_clk_khz / 1000; + for (i = 0; i < size; i++) + if (ticks < startup_lookup[i]) + break; + + ticks = i; + if (ticks == size) + /* Reach the end of lookup table */ + ticks = size - 1; + + return ticks; +} + +static const struct of_device_id at91_adc_dt_ids[]; + +static int at91_adc_probe_dt_ts(struct device_node *node, + struct at91_adc_state *st, struct device *dev) +{ + int ret; + u32 prop; + + ret = of_property_read_u32(node, "atmel,adc-ts-wires", &prop); + if (ret) { + dev_info(dev, "ADC Touch screen is disabled.\n"); + return 0; + } + + switch (prop) { + case 4: + case 5: + st->touchscreen_type = prop; + break; + default: + dev_err(dev, "Unsupported number of touchscreen wires (%d). Should be 4 or 5.\n", prop); + return -EINVAL; + } + + if (!st->caps->has_tsmr) + return 0; + prop = 0; + of_property_read_u32(node, "atmel,adc-ts-pressure-threshold", &prop); + st->ts_pressure_threshold = prop; + if (st->ts_pressure_threshold) { + return 0; + } else { + dev_err(dev, "Invalid pressure threshold for the touchscreen\n"); + return -EINVAL; + } +} + +static int at91_adc_probe_dt(struct at91_adc_state *st, + struct platform_device *pdev) +{ + struct iio_dev *idev = iio_priv_to_dev(st); + struct device_node *node = pdev->dev.of_node; + struct device_node *trig_node; + int i = 0, ret; + u32 prop; + + if (!node) + return -EINVAL; + + st->caps = (struct at91_adc_caps *) + of_match_device(at91_adc_dt_ids, &pdev->dev)->data; + + st->use_external = of_property_read_bool(node, "atmel,adc-use-external-triggers"); + + if (of_property_read_u32(node, "atmel,adc-channels-used", &prop)) { + dev_err(&idev->dev, "Missing adc-channels-used property in the DT.\n"); + ret = -EINVAL; + goto error_ret; + } + st->channels_mask = prop; + + st->sleep_mode = of_property_read_bool(node, "atmel,adc-sleep-mode"); + + if (of_property_read_u32(node, "atmel,adc-startup-time", &prop)) { + dev_err(&idev->dev, "Missing adc-startup-time property in the DT.\n"); + ret = -EINVAL; + goto error_ret; + } + st->startup_time = prop; + + prop = 0; + of_property_read_u32(node, "atmel,adc-sample-hold-time", &prop); + st->sample_hold_time = prop; + + if (of_property_read_u32(node, "atmel,adc-vref", &prop)) { + dev_err(&idev->dev, "Missing adc-vref property in the DT.\n"); + ret = -EINVAL; + goto error_ret; + } + st->vref_mv = prop; + + ret = at91_adc_of_get_resolution(st, pdev); + if (ret) + goto error_ret; + + st->registers = &st->caps->registers; + st->num_channels = st->caps->num_channels; + st->trigger_number = of_get_child_count(node); + st->trigger_list = devm_kzalloc(&idev->dev, st->trigger_number * + sizeof(struct at91_adc_trigger), + GFP_KERNEL); + if (!st->trigger_list) { + dev_err(&idev->dev, "Could not allocate trigger list memory.\n"); + ret = -ENOMEM; + goto error_ret; + } + + for_each_child_of_node(node, trig_node) { + struct at91_adc_trigger *trig = st->trigger_list + i; + const char *name; + + if (of_property_read_string(trig_node, "trigger-name", &name)) { + dev_err(&idev->dev, "Missing trigger-name property in the DT.\n"); + ret = -EINVAL; + goto error_ret; + } + trig->name = name; + + if (of_property_read_u32(trig_node, "trigger-value", &prop)) { + dev_err(&idev->dev, "Missing trigger-value property in the DT.\n"); + ret = -EINVAL; + goto error_ret; + } + trig->value = prop; + trig->is_external = of_property_read_bool(trig_node, "trigger-external"); + i++; + } + + /* Check if touchscreen is supported. */ + if (st->caps->has_ts) + return at91_adc_probe_dt_ts(node, st, &idev->dev); + else + dev_info(&idev->dev, "not support touchscreen in the adc compatible string.\n"); + + return 0; + +error_ret: + return ret; +} + +static int at91_adc_probe_pdata(struct at91_adc_state *st, + struct platform_device *pdev) +{ + struct at91_adc_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return -EINVAL; + + st->caps = (struct at91_adc_caps *) + platform_get_device_id(pdev)->driver_data; + + st->use_external = pdata->use_external_triggers; + st->vref_mv = pdata->vref; + st->channels_mask = pdata->channels_used; + st->num_channels = st->caps->num_channels; + st->startup_time = pdata->startup_time; + st->trigger_number = pdata->trigger_number; + st->trigger_list = pdata->trigger_list; + st->registers = &st->caps->registers; + st->touchscreen_type = pdata->touchscreen_type; + + return 0; +} + +static const struct iio_info at91_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &at91_adc_read_raw, +}; + +/* Touchscreen related functions */ +static int atmel_ts_open(struct input_dev *dev) +{ + struct at91_adc_state *st = input_get_drvdata(dev); + + if (st->caps->has_tsmr) + at91_adc_writel(st, AT91_ADC_IER, AT91_ADC_IER_PEN); + else + at91_adc_writel(st, AT91_ADC_IER, AT91RL_ADC_IER_PEN); + return 0; +} + +static void atmel_ts_close(struct input_dev *dev) +{ + struct at91_adc_state *st = input_get_drvdata(dev); + + if (st->caps->has_tsmr) + at91_adc_writel(st, AT91_ADC_IDR, AT91_ADC_IER_PEN); + else + at91_adc_writel(st, AT91_ADC_IDR, AT91RL_ADC_IER_PEN); +} + +static int at91_ts_hw_init(struct at91_adc_state *st, u32 adc_clk_khz) +{ + u32 reg = 0; + int i = 0; + + /* a Pen Detect Debounce Time is necessary for the ADC Touch to avoid + * pen detect noise. + * The formula is : Pen Detect Debounce Time = (2 ^ pendbc) / ADCClock + */ + st->ts_pendbc = round_up(TOUCH_PEN_DETECT_DEBOUNCE_US * adc_clk_khz / + 1000, 1); + + while (st->ts_pendbc >> ++i) + ; /* Empty! Find the shift offset */ + if (abs(st->ts_pendbc - (1 << i)) < abs(st->ts_pendbc - (1 << (i - 1)))) + st->ts_pendbc = i; + else + st->ts_pendbc = i - 1; + + if (!st->caps->has_tsmr) { + reg = at91_adc_readl(st, AT91_ADC_MR); + reg |= AT91_ADC_TSAMOD_TS_ONLY_MODE | AT91_ADC_PENDET; + + reg |= AT91_ADC_PENDBC_(st->ts_pendbc) & AT91_ADC_PENDBC; + at91_adc_writel(st, AT91_ADC_MR, reg); + + reg = AT91_ADC_TSR_SHTIM_(TOUCH_SHTIM) & AT91_ADC_TSR_SHTIM; + at91_adc_writel(st, AT91_ADC_TSR, reg); + + st->ts_sample_period_val = round_up((TOUCH_SAMPLE_PERIOD_US_RL * + adc_clk_khz / 1000) - 1, 1); + + return 0; + } + + if (st->touchscreen_type == ATMEL_ADC_TOUCHSCREEN_4WIRE) + reg = AT91_ADC_TSMR_TSMODE_4WIRE_PRESS; + else + reg = AT91_ADC_TSMR_TSMODE_5WIRE; + + reg |= AT91_ADC_TSMR_TSAV_(st->caps->ts_filter_average) + & AT91_ADC_TSMR_TSAV; + reg |= AT91_ADC_TSMR_PENDBC_(st->ts_pendbc) & AT91_ADC_TSMR_PENDBC; + reg |= AT91_ADC_TSMR_NOTSDMA; + reg |= AT91_ADC_TSMR_PENDET_ENA; + reg |= 0x03 << 8; /* TSFREQ, needs to be bigger than TSAV */ + + at91_adc_writel(st, AT91_ADC_TSMR, reg); + + /* Change adc internal resistor value for better pen detection, + * default value is 100 kOhm. + * 0 = 200 kOhm, 1 = 150 kOhm, 2 = 100 kOhm, 3 = 50 kOhm + * option only available on ES2 and higher + */ + at91_adc_writel(st, AT91_ADC_ACR, st->caps->ts_pen_detect_sensitivity + & AT91_ADC_ACR_PENDETSENS); + + /* Sample Period Time = (TRGPER + 1) / ADCClock */ + st->ts_sample_period_val = round_up((TOUCH_SAMPLE_PERIOD_US * + adc_clk_khz / 1000) - 1, 1); + + return 0; +} + +static int at91_ts_register(struct at91_adc_state *st, + struct platform_device *pdev) +{ + struct input_dev *input; + struct iio_dev *idev = iio_priv_to_dev(st); + int ret; + + input = input_allocate_device(); + if (!input) { + dev_err(&idev->dev, "Failed to allocate TS device!\n"); + return -ENOMEM; + } + + input->name = DRIVER_NAME; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; + input->open = atmel_ts_open; + input->close = atmel_ts_close; + + __set_bit(EV_ABS, input->evbit); + __set_bit(EV_KEY, input->evbit); + __set_bit(BTN_TOUCH, input->keybit); + if (st->caps->has_tsmr) { + input_set_abs_params(input, ABS_X, 0, (1 << MAX_POS_BITS) - 1, + 0, 0); + input_set_abs_params(input, ABS_Y, 0, (1 << MAX_POS_BITS) - 1, + 0, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, 0xffffff, 0, 0); + } else { + if (st->touchscreen_type != ATMEL_ADC_TOUCHSCREEN_4WIRE) { + dev_err(&pdev->dev, + "This touchscreen controller only support 4 wires\n"); + ret = -EINVAL; + goto err; + } + + input_set_abs_params(input, ABS_X, 0, (1 << MAX_RLPOS_BITS) - 1, + 0, 0); + input_set_abs_params(input, ABS_Y, 0, (1 << MAX_RLPOS_BITS) - 1, + 0, 0); + } + + st->ts_input = input; + input_set_drvdata(input, st); + + ret = input_register_device(input); + if (ret) + goto err; + + return ret; + +err: + input_free_device(st->ts_input); + return ret; +} + +static void at91_ts_unregister(struct at91_adc_state *st) +{ + input_unregister_device(st->ts_input); +} + +static int at91_adc_probe(struct platform_device *pdev) +{ + unsigned int prsc, mstrclk, ticks, adc_clk, adc_clk_khz, shtim; + int ret; + struct iio_dev *idev; + struct at91_adc_state *st; + struct resource *res; + u32 reg; + + idev = devm_iio_device_alloc(&pdev->dev, sizeof(struct at91_adc_state)); + if (!idev) + return -ENOMEM; + + st = iio_priv(idev); + + if (pdev->dev.of_node) + ret = at91_adc_probe_dt(st, pdev); + else + ret = at91_adc_probe_pdata(st, pdev); + + if (ret) { + dev_err(&pdev->dev, "No platform data available.\n"); + return -EINVAL; + } + + platform_set_drvdata(pdev, idev); + + idev->dev.parent = &pdev->dev; + idev->name = dev_name(&pdev->dev); + idev->modes = INDIO_DIRECT_MODE; + idev->info = &at91_adc_info; + + st->irq = platform_get_irq(pdev, 0); + if (st->irq < 0) { + dev_err(&pdev->dev, "No IRQ ID is designated\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + st->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(st->reg_base)) { + return PTR_ERR(st->reg_base); + } + + /* + * Disable all IRQs before setting up the handler + */ + at91_adc_writel(st, AT91_ADC_CR, AT91_ADC_SWRST); + at91_adc_writel(st, AT91_ADC_IDR, 0xFFFFFFFF); + + if (st->caps->has_tsmr) + ret = request_irq(st->irq, at91_adc_9x5_interrupt, 0, + pdev->dev.driver->name, idev); + else + ret = request_irq(st->irq, at91_adc_rl_interrupt, 0, + pdev->dev.driver->name, idev); + if (ret) { + dev_err(&pdev->dev, "Failed to allocate IRQ.\n"); + return ret; + } + + st->clk = devm_clk_get(&pdev->dev, "adc_clk"); + if (IS_ERR(st->clk)) { + dev_err(&pdev->dev, "Failed to get the clock.\n"); + ret = PTR_ERR(st->clk); + goto error_free_irq; + } + + ret = clk_prepare_enable(st->clk); + if (ret) { + dev_err(&pdev->dev, + "Could not prepare or enable the clock.\n"); + goto error_free_irq; + } + + st->adc_clk = devm_clk_get(&pdev->dev, "adc_op_clk"); + if (IS_ERR(st->adc_clk)) { + dev_err(&pdev->dev, "Failed to get the ADC clock.\n"); + ret = PTR_ERR(st->adc_clk); + goto error_disable_clk; + } + + ret = clk_prepare_enable(st->adc_clk); + if (ret) { + dev_err(&pdev->dev, + "Could not prepare or enable the ADC clock.\n"); + goto error_disable_clk; + } + + /* + * Prescaler rate computation using the formula from the Atmel's + * datasheet : ADC Clock = MCK / ((Prescaler + 1) * 2), ADC Clock being + * specified by the electrical characteristics of the board. + */ + mstrclk = clk_get_rate(st->clk); + adc_clk = clk_get_rate(st->adc_clk); + adc_clk_khz = adc_clk / 1000; + + dev_dbg(&pdev->dev, "Master clock is set as: %d Hz, adc_clk should set as: %d Hz\n", + mstrclk, adc_clk); + + prsc = (mstrclk / (2 * adc_clk)) - 1; + + if (!st->startup_time) { + dev_err(&pdev->dev, "No startup time available.\n"); + ret = -EINVAL; + goto error_disable_adc_clk; + } + ticks = (*st->caps->calc_startup_ticks)(st->startup_time, adc_clk_khz); + + /* + * a minimal Sample and Hold Time is necessary for the ADC to guarantee + * the best converted final value between two channels selection + * The formula thus is : Sample and Hold Time = (shtim + 1) / ADCClock + */ + if (st->sample_hold_time > 0) + shtim = round_up((st->sample_hold_time * adc_clk_khz / 1000) + - 1, 1); + else + shtim = 0; + + reg = AT91_ADC_PRESCAL_(prsc) & st->registers->mr_prescal_mask; + reg |= AT91_ADC_STARTUP_(ticks) & st->registers->mr_startup_mask; + if (st->low_res) + reg |= AT91_ADC_LOWRES; + if (st->sleep_mode) + reg |= AT91_ADC_SLEEP; + reg |= AT91_ADC_SHTIM_(shtim) & AT91_ADC_SHTIM; + at91_adc_writel(st, AT91_ADC_MR, reg); + + /* Setup the ADC channels available on the board */ + ret = at91_adc_channel_init(idev); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't initialize the channels.\n"); + goto error_disable_adc_clk; + } + + init_waitqueue_head(&st->wq_data_avail); + mutex_init(&st->lock); + + /* + * Since touch screen will set trigger register as period trigger. So + * when touch screen is enabled, then we have to disable hardware + * trigger for classic adc. + */ + if (!st->touchscreen_type) { + ret = at91_adc_buffer_init(idev); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't initialize the buffer.\n"); + goto error_disable_adc_clk; + } + + ret = at91_adc_trigger_init(idev); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't setup the triggers.\n"); + at91_adc_buffer_remove(idev); + goto error_disable_adc_clk; + } + } else { + ret = at91_ts_register(st, pdev); + if (ret) + goto error_disable_adc_clk; + + at91_ts_hw_init(st, adc_clk_khz); + } + + ret = iio_device_register(idev); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto error_iio_device_register; + } + + return 0; + +error_iio_device_register: + if (!st->touchscreen_type) { + at91_adc_trigger_remove(idev); + at91_adc_buffer_remove(idev); + } else { + at91_ts_unregister(st); + } +error_disable_adc_clk: + clk_disable_unprepare(st->adc_clk); +error_disable_clk: + clk_disable_unprepare(st->clk); +error_free_irq: + free_irq(st->irq, idev); + return ret; +} + +static int at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *idev = platform_get_drvdata(pdev); + struct at91_adc_state *st = iio_priv(idev); + + iio_device_unregister(idev); + if (!st->touchscreen_type) { + at91_adc_trigger_remove(idev); + at91_adc_buffer_remove(idev); + } else { + at91_ts_unregister(st); + } + clk_disable_unprepare(st->adc_clk); + clk_disable_unprepare(st->clk); + free_irq(st->irq, idev); + + return 0; +} + +static struct at91_adc_caps at91sam9260_caps = { + .calc_startup_ticks = calc_startup_ticks_9260, + .num_channels = 4, + .registers = { + .channel_base = AT91_ADC_CHR(0), + .drdy_mask = AT91_ADC_DRDY, + .status_register = AT91_ADC_SR, + .trigger_register = AT91_ADC_TRGR_9260, + .mr_prescal_mask = AT91_ADC_PRESCAL_9260, + .mr_startup_mask = AT91_ADC_STARTUP_9260, + }, +}; + +static struct at91_adc_caps at91sam9rl_caps = { + .has_ts = true, + .calc_startup_ticks = calc_startup_ticks_9260, /* same as 9260 */ + .num_channels = 6, + .registers = { + .channel_base = AT91_ADC_CHR(0), + .drdy_mask = AT91_ADC_DRDY, + .status_register = AT91_ADC_SR, + .trigger_register = AT91_ADC_TRGR_9G45, + .mr_prescal_mask = AT91_ADC_PRESCAL_9260, + .mr_startup_mask = AT91_ADC_STARTUP_9G45, + }, +}; + +static struct at91_adc_caps at91sam9g45_caps = { + .has_ts = true, + .calc_startup_ticks = calc_startup_ticks_9260, /* same as 9260 */ + .num_channels = 8, + .registers = { + .channel_base = AT91_ADC_CHR(0), + .drdy_mask = AT91_ADC_DRDY, + .status_register = AT91_ADC_SR, + .trigger_register = AT91_ADC_TRGR_9G45, + .mr_prescal_mask = AT91_ADC_PRESCAL_9G45, + .mr_startup_mask = AT91_ADC_STARTUP_9G45, + }, +}; + +static struct at91_adc_caps at91sam9x5_caps = { + .has_ts = true, + .has_tsmr = true, + .ts_filter_average = 3, + .ts_pen_detect_sensitivity = 2, + .calc_startup_ticks = calc_startup_ticks_9x5, + .num_channels = 12, + .registers = { + .channel_base = AT91_ADC_CDR0_9X5, + .drdy_mask = AT91_ADC_SR_DRDY_9X5, + .status_register = AT91_ADC_SR_9X5, + .trigger_register = AT91_ADC_TRGR_9X5, + /* prescal mask is same as 9G45 */ + .mr_prescal_mask = AT91_ADC_PRESCAL_9G45, + .mr_startup_mask = AT91_ADC_STARTUP_9X5, + }, +}; + +static const struct of_device_id at91_adc_dt_ids[] = { + { .compatible = "atmel,at91sam9260-adc", .data = &at91sam9260_caps }, + { .compatible = "atmel,at91sam9rl-adc", .data = &at91sam9rl_caps }, + { .compatible = "atmel,at91sam9g45-adc", .data = &at91sam9g45_caps }, + { .compatible = "atmel,at91sam9x5-adc", .data = &at91sam9x5_caps }, + {}, +}; +MODULE_DEVICE_TABLE(of, at91_adc_dt_ids); + +static const struct platform_device_id at91_adc_ids[] = { + { + .name = "at91sam9260-adc", + .driver_data = (unsigned long)&at91sam9260_caps, + }, { + .name = "at91sam9rl-adc", + .driver_data = (unsigned long)&at91sam9rl_caps, + }, { + .name = "at91sam9g45-adc", + .driver_data = (unsigned long)&at91sam9g45_caps, + }, { + .name = "at91sam9x5-adc", + .driver_data = (unsigned long)&at91sam9x5_caps, + }, { + /* terminator */ + } +}; +MODULE_DEVICE_TABLE(platform, at91_adc_ids); + +static struct platform_driver at91_adc_driver = { + .probe = at91_adc_probe, + .remove = at91_adc_remove, + .id_table = at91_adc_ids, + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(at91_adc_dt_ids), + }, +}; + +module_platform_driver(at91_adc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel AT91 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); diff --git a/drivers/iio/adc/axp288_adc.c b/drivers/iio/adc/axp288_adc.c new file mode 100644 index 000000000..56008a86b --- /dev/null +++ b/drivers/iio/adc/axp288_adc.c @@ -0,0 +1,261 @@ +/* + * axp288_adc.c - X-Powers AXP288 PMIC ADC Driver + * + * Copyright (C) 2014 Intel Corporation + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/regmap.h> +#include <linux/mfd/axp20x.h> +#include <linux/platform_device.h> + +#include <linux/iio/iio.h> +#include <linux/iio/machine.h> +#include <linux/iio/driver.h> + +#define AXP288_ADC_EN_MASK 0xF1 +#define AXP288_ADC_TS_PIN_GPADC 0xF2 +#define AXP288_ADC_TS_PIN_ON 0xF3 + +enum axp288_adc_id { + AXP288_ADC_TS, + AXP288_ADC_PMIC, + AXP288_ADC_GP, + AXP288_ADC_BATT_CHRG_I, + AXP288_ADC_BATT_DISCHRG_I, + AXP288_ADC_BATT_V, + AXP288_ADC_NR_CHAN, +}; + +struct axp288_adc_info { + int irq; + struct regmap *regmap; +}; + +static const struct iio_chan_spec const axp288_adc_channels[] = { + { + .indexed = 1, + .type = IIO_TEMP, + .channel = 0, + .address = AXP288_TS_ADC_H, + .datasheet_name = "TS_PIN", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = 1, + .address = AXP288_PMIC_ADC_H, + .datasheet_name = "PMIC_TEMP", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = 2, + .address = AXP288_GP_ADC_H, + .datasheet_name = "GPADC", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_CURRENT, + .channel = 3, + .address = AXP20X_BATT_CHRG_I_H, + .datasheet_name = "BATT_CHG_I", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_CURRENT, + .channel = 4, + .address = AXP20X_BATT_DISCHRG_I_H, + .datasheet_name = "BATT_DISCHRG_I", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_VOLTAGE, + .channel = 5, + .address = AXP20X_BATT_V_H, + .datasheet_name = "BATT_V", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, +}; + +#define AXP288_ADC_MAP(_adc_channel_label, _consumer_dev_name, \ + _consumer_channel) \ + { \ + .adc_channel_label = _adc_channel_label, \ + .consumer_dev_name = _consumer_dev_name, \ + .consumer_channel = _consumer_channel, \ + } + +/* for consumer drivers */ +static struct iio_map axp288_adc_default_maps[] = { + AXP288_ADC_MAP("TS_PIN", "axp288-batt", "axp288-batt-temp"), + AXP288_ADC_MAP("PMIC_TEMP", "axp288-pmic", "axp288-pmic-temp"), + AXP288_ADC_MAP("GPADC", "axp288-gpadc", "axp288-system-temp"), + AXP288_ADC_MAP("BATT_CHG_I", "axp288-chrg", "axp288-chrg-curr"), + AXP288_ADC_MAP("BATT_DISCHRG_I", "axp288-chrg", "axp288-chrg-d-curr"), + AXP288_ADC_MAP("BATT_V", "axp288-batt", "axp288-batt-volt"), + {}, +}; + +static int axp288_adc_read_channel(int *val, unsigned long address, + struct regmap *regmap) +{ + u8 buf[2]; + + if (regmap_bulk_read(regmap, address, buf, 2)) + return -EIO; + *val = (buf[0] << 4) + ((buf[1] >> 4) & 0x0F); + + return IIO_VAL_INT; +} + +static int axp288_adc_set_ts(struct regmap *regmap, unsigned int mode, + unsigned long address) +{ + /* channels other than GPADC do not need to switch TS pin */ + if (address != AXP288_GP_ADC_H) + return 0; + + return regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, mode); +} + +static int axp288_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct axp288_adc_info *info = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_GPADC, + chan->address)) { + dev_err(&indio_dev->dev, "GPADC mode\n"); + ret = -EINVAL; + break; + } + ret = axp288_adc_read_channel(val, chan->address, info->regmap); + if (axp288_adc_set_ts(info->regmap, AXP288_ADC_TS_PIN_ON, + chan->address)) + dev_err(&indio_dev->dev, "TS pin restore\n"); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int axp288_adc_set_state(struct regmap *regmap) +{ + /* ADC should be always enabled for internal FG to function */ + if (regmap_write(regmap, AXP288_ADC_TS_PIN_CTRL, AXP288_ADC_TS_PIN_ON)) + return -EIO; + + return regmap_write(regmap, AXP20X_ADC_EN1, AXP288_ADC_EN_MASK); +} + +static const struct iio_info axp288_adc_iio_info = { + .read_raw = &axp288_adc_read_raw, + .driver_module = THIS_MODULE, +}; + +static int axp288_adc_probe(struct platform_device *pdev) +{ + int ret; + struct axp288_adc_info *info; + struct iio_dev *indio_dev; + struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return info->irq; + } + platform_set_drvdata(pdev, indio_dev); + info->regmap = axp20x->regmap; + /* + * Set ADC to enabled state at all time, including system suspend. + * otherwise internal fuel gauge functionality may be affected. + */ + ret = axp288_adc_set_state(axp20x->regmap); + if (ret) { + dev_err(&pdev->dev, "unable to enable ADC device\n"); + return ret; + } + + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = pdev->name; + indio_dev->channels = axp288_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels); + indio_dev->info = &axp288_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + ret = iio_map_array_register(indio_dev, axp288_adc_default_maps); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&pdev->dev, "unable to register iio device\n"); + goto err_array_unregister; + } + return 0; + +err_array_unregister: + iio_map_array_unregister(indio_dev); + + return ret; +} + +static int axp288_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + iio_map_array_unregister(indio_dev); + + return 0; +} + +static struct platform_device_id axp288_adc_id_table[] = { + { .name = "axp288_adc" }, + {}, +}; + +static struct platform_driver axp288_adc_driver = { + .probe = axp288_adc_probe, + .remove = axp288_adc_remove, + .id_table = axp288_adc_id_table, + .driver = { + .name = "axp288_adc", + }, +}; + +MODULE_DEVICE_TABLE(platform, axp288_adc_id_table); + +module_platform_driver(axp288_adc_driver); + +MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>"); +MODULE_DESCRIPTION("X-Powers AXP288 ADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/cc10001_adc.c b/drivers/iio/adc/cc10001_adc.c new file mode 100644 index 000000000..115f6e99a --- /dev/null +++ b/drivers/iio/adc/cc10001_adc.c @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2014-2015 Imagination Technologies Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* Registers */ +#define CC10001_ADC_CONFIG 0x00 +#define CC10001_ADC_START_CONV BIT(4) +#define CC10001_ADC_MODE_SINGLE_CONV BIT(5) + +#define CC10001_ADC_DDATA_OUT 0x04 +#define CC10001_ADC_EOC 0x08 +#define CC10001_ADC_EOC_SET BIT(0) + +#define CC10001_ADC_CHSEL_SAMPLED 0x0c +#define CC10001_ADC_POWER_DOWN 0x10 +#define CC10001_ADC_POWER_DOWN_SET BIT(0) + +#define CC10001_ADC_DEBUG 0x14 +#define CC10001_ADC_DATA_COUNT 0x20 + +#define CC10001_ADC_DATA_MASK GENMASK(9, 0) +#define CC10001_ADC_NUM_CHANNELS 8 +#define CC10001_ADC_CH_MASK GENMASK(2, 0) + +#define CC10001_INVALID_SAMPLED 0xffff +#define CC10001_MAX_POLL_COUNT 20 + +/* + * As per device specification, wait six clock cycles after power-up to + * activate START. Since adding two more clock cycles delay does not + * impact the performance too much, we are adding two additional cycles delay + * intentionally here. + */ +#define CC10001_WAIT_CYCLES 8 + +struct cc10001_adc_device { + void __iomem *reg_base; + struct clk *adc_clk; + struct regulator *reg; + u16 *buf; + + struct mutex lock; + unsigned int start_delay_ns; + unsigned int eoc_delay_ns; +}; + +static inline void cc10001_adc_write_reg(struct cc10001_adc_device *adc_dev, + u32 reg, u32 val) +{ + writel(val, adc_dev->reg_base + reg); +} + +static inline u32 cc10001_adc_read_reg(struct cc10001_adc_device *adc_dev, + u32 reg) +{ + return readl(adc_dev->reg_base + reg); +} + +static void cc10001_adc_power_up(struct cc10001_adc_device *adc_dev) +{ + cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_DOWN, 0); + ndelay(adc_dev->start_delay_ns); +} + +static void cc10001_adc_power_down(struct cc10001_adc_device *adc_dev) +{ + cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_DOWN, + CC10001_ADC_POWER_DOWN_SET); +} + +static void cc10001_adc_start(struct cc10001_adc_device *adc_dev, + unsigned int channel) +{ + u32 val; + + /* Channel selection and mode of operation */ + val = (channel & CC10001_ADC_CH_MASK) | CC10001_ADC_MODE_SINGLE_CONV; + cc10001_adc_write_reg(adc_dev, CC10001_ADC_CONFIG, val); + + udelay(1); + val = cc10001_adc_read_reg(adc_dev, CC10001_ADC_CONFIG); + val = val | CC10001_ADC_START_CONV; + cc10001_adc_write_reg(adc_dev, CC10001_ADC_CONFIG, val); +} + +static u16 cc10001_adc_poll_done(struct iio_dev *indio_dev, + unsigned int channel, + unsigned int delay) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + unsigned int poll_count = 0; + + while (!(cc10001_adc_read_reg(adc_dev, CC10001_ADC_EOC) & + CC10001_ADC_EOC_SET)) { + + ndelay(delay); + if (poll_count++ == CC10001_MAX_POLL_COUNT) + return CC10001_INVALID_SAMPLED; + } + + poll_count = 0; + while ((cc10001_adc_read_reg(adc_dev, CC10001_ADC_CHSEL_SAMPLED) & + CC10001_ADC_CH_MASK) != channel) { + + ndelay(delay); + if (poll_count++ == CC10001_MAX_POLL_COUNT) + return CC10001_INVALID_SAMPLED; + } + + /* Read the 10 bit output register */ + return cc10001_adc_read_reg(adc_dev, CC10001_ADC_DDATA_OUT) & + CC10001_ADC_DATA_MASK; +} + +static irqreturn_t cc10001_adc_trigger_h(int irq, void *p) +{ + struct cc10001_adc_device *adc_dev; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev; + unsigned int delay_ns; + unsigned int channel; + unsigned int scan_idx; + bool sample_invalid; + u16 *data; + int i; + + indio_dev = pf->indio_dev; + adc_dev = iio_priv(indio_dev); + data = adc_dev->buf; + + mutex_lock(&adc_dev->lock); + + cc10001_adc_power_up(adc_dev); + + /* Calculate delay step for eoc and sampled data */ + delay_ns = adc_dev->eoc_delay_ns / CC10001_MAX_POLL_COUNT; + + i = 0; + sample_invalid = false; + for_each_set_bit(scan_idx, indio_dev->active_scan_mask, + indio_dev->masklength) { + + channel = indio_dev->channels[scan_idx].channel; + cc10001_adc_start(adc_dev, channel); + + data[i] = cc10001_adc_poll_done(indio_dev, channel, delay_ns); + if (data[i] == CC10001_INVALID_SAMPLED) { + dev_warn(&indio_dev->dev, + "invalid sample on channel %d\n", channel); + sample_invalid = true; + goto done; + } + i++; + } + +done: + cc10001_adc_power_down(adc_dev); + + mutex_unlock(&adc_dev->lock); + + if (!sample_invalid) + iio_push_to_buffers_with_timestamp(indio_dev, data, + iio_get_time_ns()); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static u16 cc10001_adc_read_raw_voltage(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + unsigned int delay_ns; + u16 val; + + cc10001_adc_power_up(adc_dev); + + /* Calculate delay step for eoc and sampled data */ + delay_ns = adc_dev->eoc_delay_ns / CC10001_MAX_POLL_COUNT; + + cc10001_adc_start(adc_dev, chan->channel); + + val = cc10001_adc_poll_done(indio_dev, chan->channel, delay_ns); + + cc10001_adc_power_down(adc_dev); + + return val; +} + +static int cc10001_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + mutex_lock(&adc_dev->lock); + *val = cc10001_adc_read_raw_voltage(indio_dev, chan); + mutex_unlock(&adc_dev->lock); + + if (*val == CC10001_INVALID_SAMPLED) + return -EIO; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(adc_dev->reg); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static int cc10001_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + + kfree(adc_dev->buf); + adc_dev->buf = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!adc_dev->buf) + return -ENOMEM; + + return 0; +} + +static const struct iio_info cc10001_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &cc10001_adc_read_raw, + .update_scan_mode = &cc10001_update_scan_mode, +}; + +static int cc10001_adc_channel_init(struct iio_dev *indio_dev, + unsigned long channel_map) +{ + struct iio_chan_spec *chan_array, *timestamp; + unsigned int bit, idx = 0; + + indio_dev->num_channels = bitmap_weight(&channel_map, + CC10001_ADC_NUM_CHANNELS) + 1; + + chan_array = devm_kcalloc(&indio_dev->dev, indio_dev->num_channels, + sizeof(struct iio_chan_spec), + GFP_KERNEL); + if (!chan_array) + return -ENOMEM; + + for_each_set_bit(bit, &channel_map, CC10001_ADC_NUM_CHANNELS) { + struct iio_chan_spec *chan = &chan_array[idx]; + + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->channel = bit; + chan->scan_index = idx; + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = 10; + chan->scan_type.storagebits = 16; + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + idx++; + } + + timestamp = &chan_array[idx]; + timestamp->type = IIO_TIMESTAMP; + timestamp->channel = -1; + timestamp->scan_index = idx; + timestamp->scan_type.sign = 's'; + timestamp->scan_type.realbits = 64; + timestamp->scan_type.storagebits = 64; + + indio_dev->channels = chan_array; + + return 0; +} + +static int cc10001_adc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct cc10001_adc_device *adc_dev; + unsigned long adc_clk_rate; + struct resource *res; + struct iio_dev *indio_dev; + unsigned long channel_map; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_dev)); + if (indio_dev == NULL) + return -ENOMEM; + + adc_dev = iio_priv(indio_dev); + + channel_map = GENMASK(CC10001_ADC_NUM_CHANNELS - 1, 0); + if (!of_property_read_u32(node, "adc-reserved-channels", &ret)) + channel_map &= ~ret; + + adc_dev->reg = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(adc_dev->reg)) + return PTR_ERR(adc_dev->reg); + + ret = regulator_enable(adc_dev->reg); + if (ret) + return ret; + + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = dev_name(&pdev->dev); + indio_dev->info = &cc10001_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + adc_dev->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(adc_dev->reg_base)) { + ret = PTR_ERR(adc_dev->reg_base); + goto err_disable_reg; + } + + adc_dev->adc_clk = devm_clk_get(&pdev->dev, "adc"); + if (IS_ERR(adc_dev->adc_clk)) { + dev_err(&pdev->dev, "failed to get the clock\n"); + ret = PTR_ERR(adc_dev->adc_clk); + goto err_disable_reg; + } + + ret = clk_prepare_enable(adc_dev->adc_clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable the clock\n"); + goto err_disable_reg; + } + + adc_clk_rate = clk_get_rate(adc_dev->adc_clk); + if (!adc_clk_rate) { + ret = -EINVAL; + dev_err(&pdev->dev, "null clock rate!\n"); + goto err_disable_clk; + } + + adc_dev->eoc_delay_ns = NSEC_PER_SEC / adc_clk_rate; + adc_dev->start_delay_ns = adc_dev->eoc_delay_ns * CC10001_WAIT_CYCLES; + + /* Setup the ADC channels available on the device */ + ret = cc10001_adc_channel_init(indio_dev, channel_map); + if (ret < 0) + goto err_disable_clk; + + mutex_init(&adc_dev->lock); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &cc10001_adc_trigger_h, NULL); + if (ret < 0) + goto err_disable_clk; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto err_cleanup_buffer; + + platform_set_drvdata(pdev, indio_dev); + + return 0; + +err_cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +err_disable_clk: + clk_disable_unprepare(adc_dev->adc_clk); +err_disable_reg: + regulator_disable(adc_dev->reg); + return ret; +} + +static int cc10001_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct cc10001_adc_device *adc_dev = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + clk_disable_unprepare(adc_dev->adc_clk); + regulator_disable(adc_dev->reg); + + return 0; +} + +static const struct of_device_id cc10001_adc_dt_ids[] = { + { .compatible = "cosmic,10001-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, cc10001_adc_dt_ids); + +static struct platform_driver cc10001_adc_driver = { + .driver = { + .name = "cc10001-adc", + .of_match_table = cc10001_adc_dt_ids, + }, + .probe = cc10001_adc_probe, + .remove = cc10001_adc_remove, +}; +module_platform_driver(cc10001_adc_driver); + +MODULE_AUTHOR("Phani Movva <Phani.Movva@imgtec.com>"); +MODULE_DESCRIPTION("Cosmic Circuits ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/da9150-gpadc.c b/drivers/iio/adc/da9150-gpadc.c new file mode 100644 index 000000000..3445107e1 --- /dev/null +++ b/drivers/iio/adc/da9150-gpadc.c @@ -0,0 +1,407 @@ +/* + * DA9150 GPADC Driver + * + * Copyright (c) 2014 Dialog Semiconductor + * + * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/completion.h> +#include <linux/iio/iio.h> +#include <linux/iio/machine.h> +#include <linux/iio/driver.h> +#include <linux/mfd/da9150/core.h> +#include <linux/mfd/da9150/registers.h> + +/* Channels */ +enum da9150_gpadc_hw_channel { + DA9150_GPADC_HW_CHAN_GPIOA_2V = 0, + DA9150_GPADC_HW_CHAN_GPIOA_2V_, + DA9150_GPADC_HW_CHAN_GPIOB_2V, + DA9150_GPADC_HW_CHAN_GPIOB_2V_, + DA9150_GPADC_HW_CHAN_GPIOC_2V, + DA9150_GPADC_HW_CHAN_GPIOC_2V_, + DA9150_GPADC_HW_CHAN_GPIOD_2V, + DA9150_GPADC_HW_CHAN_GPIOD_2V_, + DA9150_GPADC_HW_CHAN_IBUS_SENSE, + DA9150_GPADC_HW_CHAN_IBUS_SENSE_, + DA9150_GPADC_HW_CHAN_VBUS_DIV, + DA9150_GPADC_HW_CHAN_VBUS_DIV_, + DA9150_GPADC_HW_CHAN_ID, + DA9150_GPADC_HW_CHAN_ID_, + DA9150_GPADC_HW_CHAN_VSYS, + DA9150_GPADC_HW_CHAN_VSYS_, + DA9150_GPADC_HW_CHAN_GPIOA_6V, + DA9150_GPADC_HW_CHAN_GPIOA_6V_, + DA9150_GPADC_HW_CHAN_GPIOB_6V, + DA9150_GPADC_HW_CHAN_GPIOB_6V_, + DA9150_GPADC_HW_CHAN_GPIOC_6V, + DA9150_GPADC_HW_CHAN_GPIOC_6V_, + DA9150_GPADC_HW_CHAN_GPIOD_6V, + DA9150_GPADC_HW_CHAN_GPIOD_6V_, + DA9150_GPADC_HW_CHAN_VBAT, + DA9150_GPADC_HW_CHAN_VBAT_, + DA9150_GPADC_HW_CHAN_TBAT, + DA9150_GPADC_HW_CHAN_TBAT_, + DA9150_GPADC_HW_CHAN_TJUNC_CORE, + DA9150_GPADC_HW_CHAN_TJUNC_CORE_, + DA9150_GPADC_HW_CHAN_TJUNC_OVP, + DA9150_GPADC_HW_CHAN_TJUNC_OVP_, +}; + +enum da9150_gpadc_channel { + DA9150_GPADC_CHAN_GPIOA = 0, + DA9150_GPADC_CHAN_GPIOB, + DA9150_GPADC_CHAN_GPIOC, + DA9150_GPADC_CHAN_GPIOD, + DA9150_GPADC_CHAN_IBUS, + DA9150_GPADC_CHAN_VBUS, + DA9150_GPADC_CHAN_VSYS, + DA9150_GPADC_CHAN_VBAT, + DA9150_GPADC_CHAN_TBAT, + DA9150_GPADC_CHAN_TJUNC_CORE, + DA9150_GPADC_CHAN_TJUNC_OVP, +}; + +/* Private data */ +struct da9150_gpadc { + struct da9150 *da9150; + struct device *dev; + + struct mutex lock; + struct completion complete; +}; + + +static irqreturn_t da9150_gpadc_irq(int irq, void *data) +{ + + struct da9150_gpadc *gpadc = data; + + complete(&gpadc->complete); + + return IRQ_HANDLED; +} + +static int da9150_gpadc_read_adc(struct da9150_gpadc *gpadc, int hw_chan) +{ + u8 result_regs[2]; + int result; + + mutex_lock(&gpadc->lock); + + /* Set channel & enable measurement */ + da9150_reg_write(gpadc->da9150, DA9150_GPADC_MAN, + (DA9150_GPADC_EN_MASK | + hw_chan << DA9150_GPADC_MUX_SHIFT)); + + /* Consume left-over completion from a previous timeout */ + try_wait_for_completion(&gpadc->complete); + + /* Check for actual completion */ + wait_for_completion_timeout(&gpadc->complete, msecs_to_jiffies(5)); + + /* Read result and status from device */ + da9150_bulk_read(gpadc->da9150, DA9150_GPADC_RES_A, 2, result_regs); + + mutex_unlock(&gpadc->lock); + + /* Check to make sure device really has completed reading */ + if (result_regs[1] & DA9150_GPADC_RUN_MASK) { + dev_err(gpadc->dev, "Timeout on channel %d of GPADC\n", + hw_chan); + return -ETIMEDOUT; + } + + /* LSBs - 2 bits */ + result = (result_regs[1] & DA9150_GPADC_RES_L_MASK) >> + DA9150_GPADC_RES_L_SHIFT; + /* MSBs - 8 bits */ + result |= result_regs[0] << DA9150_GPADC_RES_L_BITS; + + return result; +} + +static inline int da9150_gpadc_gpio_6v_voltage_now(int raw_val) +{ + /* Convert to mV */ + return (6 * ((raw_val * 1000) + 500)) / 1024; +} + +static inline int da9150_gpadc_ibus_current_avg(int raw_val) +{ + /* Convert to mA */ + return (4 * ((raw_val * 1000) + 500)) / 2048; +} + +static inline int da9150_gpadc_vbus_21v_voltage_now(int raw_val) +{ + /* Convert to mV */ + return (21 * ((raw_val * 1000) + 500)) / 1024; +} + +static inline int da9150_gpadc_vsys_6v_voltage_now(int raw_val) +{ + /* Convert to mV */ + return (3 * ((raw_val * 1000) + 500)) / 512; +} + +static int da9150_gpadc_read_processed(struct da9150_gpadc *gpadc, int channel, + int hw_chan, int *val) +{ + int raw_val; + + raw_val = da9150_gpadc_read_adc(gpadc, hw_chan); + if (raw_val < 0) + return raw_val; + + switch (channel) { + case DA9150_GPADC_CHAN_GPIOA: + case DA9150_GPADC_CHAN_GPIOB: + case DA9150_GPADC_CHAN_GPIOC: + case DA9150_GPADC_CHAN_GPIOD: + *val = da9150_gpadc_gpio_6v_voltage_now(raw_val); + break; + case DA9150_GPADC_CHAN_IBUS: + *val = da9150_gpadc_ibus_current_avg(raw_val); + break; + case DA9150_GPADC_CHAN_VBUS: + *val = da9150_gpadc_vbus_21v_voltage_now(raw_val); + break; + case DA9150_GPADC_CHAN_VSYS: + *val = da9150_gpadc_vsys_6v_voltage_now(raw_val); + break; + default: + /* No processing for other channels so return raw value */ + *val = raw_val; + break; + } + + return IIO_VAL_INT; +} + +static int da9150_gpadc_read_scale(int channel, int *val, int *val2) +{ + switch (channel) { + case DA9150_GPADC_CHAN_VBAT: + *val = 2932; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case DA9150_GPADC_CHAN_TJUNC_CORE: + case DA9150_GPADC_CHAN_TJUNC_OVP: + *val = 1000000; + *val2 = 4420; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int da9150_gpadc_read_offset(int channel, int *val) +{ + switch (channel) { + case DA9150_GPADC_CHAN_VBAT: + *val = 1500000 / 2932; + return IIO_VAL_INT; + case DA9150_GPADC_CHAN_TJUNC_CORE: + case DA9150_GPADC_CHAN_TJUNC_OVP: + *val = -144; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int da9150_gpadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct da9150_gpadc *gpadc = iio_priv(indio_dev); + + if ((chan->channel < DA9150_GPADC_CHAN_GPIOA) || + (chan->channel > DA9150_GPADC_CHAN_TJUNC_OVP)) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + return da9150_gpadc_read_processed(gpadc, chan->channel, + chan->address, val); + case IIO_CHAN_INFO_SCALE: + return da9150_gpadc_read_scale(chan->channel, val, val2); + case IIO_CHAN_INFO_OFFSET: + return da9150_gpadc_read_offset(chan->channel, val); + default: + return -EINVAL; + } +} + +static const struct iio_info da9150_gpadc_info = { + .read_raw = &da9150_gpadc_read_raw, + .driver_module = THIS_MODULE, +}; + +#define DA9150_GPADC_CHANNEL(_id, _hw_id, _type, chan_info, \ + _ext_name) { \ + .type = _type, \ + .indexed = 1, \ + .channel = DA9150_GPADC_CHAN_##_id, \ + .address = DA9150_GPADC_HW_CHAN_##_hw_id, \ + .info_mask_separate = chan_info, \ + .extend_name = _ext_name, \ + .datasheet_name = #_id, \ +} + +#define DA9150_GPADC_CHANNEL_RAW(_id, _hw_id, _type, _ext_name) \ + DA9150_GPADC_CHANNEL(_id, _hw_id, _type, \ + BIT(IIO_CHAN_INFO_RAW), _ext_name) + +#define DA9150_GPADC_CHANNEL_SCALED(_id, _hw_id, _type, _ext_name) \ + DA9150_GPADC_CHANNEL(_id, _hw_id, _type, \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + _ext_name) + +#define DA9150_GPADC_CHANNEL_PROCESSED(_id, _hw_id, _type, _ext_name) \ + DA9150_GPADC_CHANNEL(_id, _hw_id, _type, \ + BIT(IIO_CHAN_INFO_PROCESSED), _ext_name) + +/* Supported channels */ +static const struct iio_chan_spec da9150_gpadc_channels[] = { + DA9150_GPADC_CHANNEL_PROCESSED(GPIOA, GPIOA_6V, IIO_VOLTAGE, NULL), + DA9150_GPADC_CHANNEL_PROCESSED(GPIOB, GPIOB_6V, IIO_VOLTAGE, NULL), + DA9150_GPADC_CHANNEL_PROCESSED(GPIOC, GPIOC_6V, IIO_VOLTAGE, NULL), + DA9150_GPADC_CHANNEL_PROCESSED(GPIOD, GPIOD_6V, IIO_VOLTAGE, NULL), + DA9150_GPADC_CHANNEL_PROCESSED(IBUS, IBUS_SENSE, IIO_CURRENT, "ibus"), + DA9150_GPADC_CHANNEL_PROCESSED(VBUS, VBUS_DIV_, IIO_VOLTAGE, "vbus"), + DA9150_GPADC_CHANNEL_PROCESSED(VSYS, VSYS, IIO_VOLTAGE, "vsys"), + DA9150_GPADC_CHANNEL_SCALED(VBAT, VBAT, IIO_VOLTAGE, "vbat"), + DA9150_GPADC_CHANNEL_RAW(TBAT, TBAT, IIO_VOLTAGE, "tbat"), + DA9150_GPADC_CHANNEL_SCALED(TJUNC_CORE, TJUNC_CORE, IIO_TEMP, + "tjunc_core"), + DA9150_GPADC_CHANNEL_SCALED(TJUNC_OVP, TJUNC_OVP, IIO_TEMP, + "tjunc_ovp"), +}; + +/* Default maps used by da9150-charger */ +static struct iio_map da9150_gpadc_default_maps[] = { + { + .consumer_dev_name = "da9150-charger", + .consumer_channel = "CHAN_IBUS", + .adc_channel_label = "IBUS", + }, + { + .consumer_dev_name = "da9150-charger", + .consumer_channel = "CHAN_VBUS", + .adc_channel_label = "VBUS", + }, + { + .consumer_dev_name = "da9150-charger", + .consumer_channel = "CHAN_TJUNC", + .adc_channel_label = "TJUNC_CORE", + }, + { + .consumer_dev_name = "da9150-charger", + .consumer_channel = "CHAN_VBAT", + .adc_channel_label = "VBAT", + }, + {}, +}; + +static int da9150_gpadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct da9150 *da9150 = dev_get_drvdata(dev->parent); + struct da9150_gpadc *gpadc; + struct iio_dev *indio_dev; + int irq, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gpadc)); + if (!indio_dev) { + dev_err(&pdev->dev, "Failed to allocate IIO device\n"); + return -ENOMEM; + } + gpadc = iio_priv(indio_dev); + + platform_set_drvdata(pdev, indio_dev); + gpadc->da9150 = da9150; + gpadc->dev = dev; + mutex_init(&gpadc->lock); + init_completion(&gpadc->complete); + + irq = platform_get_irq_byname(pdev, "GPADC"); + if (irq < 0) { + dev_err(dev, "Failed to get IRQ: %d\n", irq); + return irq; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, da9150_gpadc_irq, + IRQF_ONESHOT, "GPADC", gpadc); + if (ret) { + dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); + return ret; + } + + ret = iio_map_array_register(indio_dev, da9150_gpadc_default_maps); + if (ret) { + dev_err(dev, "Failed to register IIO maps: %d\n", ret); + return ret; + } + + indio_dev->name = dev_name(dev); + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &da9150_gpadc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = da9150_gpadc_channels; + indio_dev->num_channels = ARRAY_SIZE(da9150_gpadc_channels); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "Failed to register IIO device: %d\n", ret); + goto iio_map_unreg; + } + + return 0; + +iio_map_unreg: + iio_map_array_unregister(indio_dev); + + return ret; +} + +static int da9150_gpadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + iio_map_array_unregister(indio_dev); + + return 0; +} + +static struct platform_driver da9150_gpadc_driver = { + .driver = { + .name = "da9150-gpadc", + }, + .probe = da9150_gpadc_probe, + .remove = da9150_gpadc_remove, +}; + +module_platform_driver(da9150_gpadc_driver); + +MODULE_DESCRIPTION("GPADC Driver for DA9150"); +MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c new file mode 100644 index 000000000..3a2dbb3b4 --- /dev/null +++ b/drivers/iio/adc/exynos_adc.c @@ -0,0 +1,779 @@ +/* + * exynos_adc.c - Support for ADC in EXYNOS SoCs + * + * 8 ~ 10 channel, 10/12-bit ADC + * + * Copyright (C) 2013 Naveen Krishna Chatradhi <ch.naveen@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/regulator/consumer.h> +#include <linux/of_platform.h> +#include <linux/err.h> + +#include <linux/iio/iio.h> +#include <linux/iio/machine.h> +#include <linux/iio/driver.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> + +/* S3C/EXYNOS4412/5250 ADC_V1 registers definitions */ +#define ADC_V1_CON(x) ((x) + 0x00) +#define ADC_V1_DLY(x) ((x) + 0x08) +#define ADC_V1_DATX(x) ((x) + 0x0C) +#define ADC_V1_INTCLR(x) ((x) + 0x18) +#define ADC_V1_MUX(x) ((x) + 0x1c) + +/* S3C2410 ADC registers definitions */ +#define ADC_S3C2410_MUX(x) ((x) + 0x18) + +/* Future ADC_V2 registers definitions */ +#define ADC_V2_CON1(x) ((x) + 0x00) +#define ADC_V2_CON2(x) ((x) + 0x04) +#define ADC_V2_STAT(x) ((x) + 0x08) +#define ADC_V2_INT_EN(x) ((x) + 0x10) +#define ADC_V2_INT_ST(x) ((x) + 0x14) +#define ADC_V2_VER(x) ((x) + 0x20) + +/* Bit definitions for ADC_V1 */ +#define ADC_V1_CON_RES (1u << 16) +#define ADC_V1_CON_PRSCEN (1u << 14) +#define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6) +#define ADC_V1_CON_STANDBY (1u << 2) + +/* Bit definitions for S3C2410 ADC */ +#define ADC_S3C2410_CON_SELMUX(x) (((x) & 7) << 3) +#define ADC_S3C2410_DATX_MASK 0x3FF +#define ADC_S3C2416_CON_RES_SEL (1u << 3) + +/* Bit definitions for ADC_V2 */ +#define ADC_V2_CON1_SOFT_RESET (1u << 2) + +#define ADC_V2_CON2_OSEL (1u << 10) +#define ADC_V2_CON2_ESEL (1u << 9) +#define ADC_V2_CON2_HIGHF (1u << 8) +#define ADC_V2_CON2_C_TIME(x) (((x) & 7) << 4) +#define ADC_V2_CON2_ACH_SEL(x) (((x) & 0xF) << 0) +#define ADC_V2_CON2_ACH_MASK 0xF + +#define MAX_ADC_V2_CHANNELS 10 +#define MAX_ADC_V1_CHANNELS 8 +#define MAX_EXYNOS3250_ADC_CHANNELS 2 + +/* Bit definitions common for ADC_V1 and ADC_V2 */ +#define ADC_CON_EN_START (1u << 0) +#define ADC_CON_EN_START_MASK (0x3 << 0) +#define ADC_DATX_MASK 0xFFF + +#define EXYNOS_ADC_TIMEOUT (msecs_to_jiffies(100)) + +#define EXYNOS_ADCV1_PHY_OFFSET 0x0718 +#define EXYNOS_ADCV2_PHY_OFFSET 0x0720 + +struct exynos_adc { + struct exynos_adc_data *data; + struct device *dev; + void __iomem *regs; + struct regmap *pmu_map; + struct clk *clk; + struct clk *sclk; + unsigned int irq; + struct regulator *vdd; + + struct completion completion; + + u32 value; + unsigned int version; +}; + +struct exynos_adc_data { + int num_channels; + bool needs_sclk; + bool needs_adc_phy; + int phy_offset; + u32 mask; + + void (*init_hw)(struct exynos_adc *info); + void (*exit_hw)(struct exynos_adc *info); + void (*clear_irq)(struct exynos_adc *info); + void (*start_conv)(struct exynos_adc *info, unsigned long addr); +}; + +static void exynos_adc_unprepare_clk(struct exynos_adc *info) +{ + if (info->data->needs_sclk) + clk_unprepare(info->sclk); + clk_unprepare(info->clk); +} + +static int exynos_adc_prepare_clk(struct exynos_adc *info) +{ + int ret; + + ret = clk_prepare(info->clk); + if (ret) { + dev_err(info->dev, "failed preparing adc clock: %d\n", ret); + return ret; + } + + if (info->data->needs_sclk) { + ret = clk_prepare(info->sclk); + if (ret) { + clk_unprepare(info->clk); + dev_err(info->dev, + "failed preparing sclk_adc clock: %d\n", ret); + return ret; + } + } + + return 0; +} + +static void exynos_adc_disable_clk(struct exynos_adc *info) +{ + if (info->data->needs_sclk) + clk_disable(info->sclk); + clk_disable(info->clk); +} + +static int exynos_adc_enable_clk(struct exynos_adc *info) +{ + int ret; + + ret = clk_enable(info->clk); + if (ret) { + dev_err(info->dev, "failed enabling adc clock: %d\n", ret); + return ret; + } + + if (info->data->needs_sclk) { + ret = clk_enable(info->sclk); + if (ret) { + clk_disable(info->clk); + dev_err(info->dev, + "failed enabling sclk_adc clock: %d\n", ret); + return ret; + } + } + + return 0; +} + +static void exynos_adc_v1_init_hw(struct exynos_adc *info) +{ + u32 con1; + + if (info->data->needs_adc_phy) + regmap_write(info->pmu_map, info->data->phy_offset, 1); + + /* set default prescaler values and Enable prescaler */ + con1 = ADC_V1_CON_PRSCLV(49) | ADC_V1_CON_PRSCEN; + + /* Enable 12-bit ADC resolution */ + con1 |= ADC_V1_CON_RES; + writel(con1, ADC_V1_CON(info->regs)); +} + +static void exynos_adc_v1_exit_hw(struct exynos_adc *info) +{ + u32 con; + + if (info->data->needs_adc_phy) + regmap_write(info->pmu_map, info->data->phy_offset, 0); + + con = readl(ADC_V1_CON(info->regs)); + con |= ADC_V1_CON_STANDBY; + writel(con, ADC_V1_CON(info->regs)); +} + +static void exynos_adc_v1_clear_irq(struct exynos_adc *info) +{ + writel(1, ADC_V1_INTCLR(info->regs)); +} + +static void exynos_adc_v1_start_conv(struct exynos_adc *info, + unsigned long addr) +{ + u32 con1; + + writel(addr, ADC_V1_MUX(info->regs)); + + con1 = readl(ADC_V1_CON(info->regs)); + writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); +} + +static const struct exynos_adc_data exynos_adc_v1_data = { + .num_channels = MAX_ADC_V1_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + .needs_adc_phy = true, + .phy_offset = EXYNOS_ADCV1_PHY_OFFSET, + + .init_hw = exynos_adc_v1_init_hw, + .exit_hw = exynos_adc_v1_exit_hw, + .clear_irq = exynos_adc_v1_clear_irq, + .start_conv = exynos_adc_v1_start_conv, +}; + +static void exynos_adc_s3c2416_start_conv(struct exynos_adc *info, + unsigned long addr) +{ + u32 con1; + + /* Enable 12 bit ADC resolution */ + con1 = readl(ADC_V1_CON(info->regs)); + con1 |= ADC_S3C2416_CON_RES_SEL; + writel(con1, ADC_V1_CON(info->regs)); + + /* Select channel for S3C2416 */ + writel(addr, ADC_S3C2410_MUX(info->regs)); + + con1 = readl(ADC_V1_CON(info->regs)); + writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); +} + +static struct exynos_adc_data const exynos_adc_s3c2416_data = { + .num_channels = MAX_ADC_V1_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + + .init_hw = exynos_adc_v1_init_hw, + .exit_hw = exynos_adc_v1_exit_hw, + .start_conv = exynos_adc_s3c2416_start_conv, +}; + +static void exynos_adc_s3c2443_start_conv(struct exynos_adc *info, + unsigned long addr) +{ + u32 con1; + + /* Select channel for S3C2433 */ + writel(addr, ADC_S3C2410_MUX(info->regs)); + + con1 = readl(ADC_V1_CON(info->regs)); + writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); +} + +static struct exynos_adc_data const exynos_adc_s3c2443_data = { + .num_channels = MAX_ADC_V1_CHANNELS, + .mask = ADC_S3C2410_DATX_MASK, /* 10 bit ADC resolution */ + + .init_hw = exynos_adc_v1_init_hw, + .exit_hw = exynos_adc_v1_exit_hw, + .start_conv = exynos_adc_s3c2443_start_conv, +}; + +static void exynos_adc_s3c64xx_start_conv(struct exynos_adc *info, + unsigned long addr) +{ + u32 con1; + + con1 = readl(ADC_V1_CON(info->regs)); + con1 &= ~ADC_S3C2410_CON_SELMUX(0x7); + con1 |= ADC_S3C2410_CON_SELMUX(addr); + writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); +} + +static struct exynos_adc_data const exynos_adc_s3c24xx_data = { + .num_channels = MAX_ADC_V1_CHANNELS, + .mask = ADC_S3C2410_DATX_MASK, /* 10 bit ADC resolution */ + + .init_hw = exynos_adc_v1_init_hw, + .exit_hw = exynos_adc_v1_exit_hw, + .start_conv = exynos_adc_s3c64xx_start_conv, +}; + +static struct exynos_adc_data const exynos_adc_s3c64xx_data = { + .num_channels = MAX_ADC_V1_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + + .init_hw = exynos_adc_v1_init_hw, + .exit_hw = exynos_adc_v1_exit_hw, + .clear_irq = exynos_adc_v1_clear_irq, + .start_conv = exynos_adc_s3c64xx_start_conv, +}; + +static void exynos_adc_v2_init_hw(struct exynos_adc *info) +{ + u32 con1, con2; + + if (info->data->needs_adc_phy) + regmap_write(info->pmu_map, info->data->phy_offset, 1); + + con1 = ADC_V2_CON1_SOFT_RESET; + writel(con1, ADC_V2_CON1(info->regs)); + + con2 = ADC_V2_CON2_OSEL | ADC_V2_CON2_ESEL | + ADC_V2_CON2_HIGHF | ADC_V2_CON2_C_TIME(0); + writel(con2, ADC_V2_CON2(info->regs)); + + /* Enable interrupts */ + writel(1, ADC_V2_INT_EN(info->regs)); +} + +static void exynos_adc_v2_exit_hw(struct exynos_adc *info) +{ + u32 con; + + if (info->data->needs_adc_phy) + regmap_write(info->pmu_map, info->data->phy_offset, 0); + + con = readl(ADC_V2_CON1(info->regs)); + con &= ~ADC_CON_EN_START; + writel(con, ADC_V2_CON1(info->regs)); +} + +static void exynos_adc_v2_clear_irq(struct exynos_adc *info) +{ + writel(1, ADC_V2_INT_ST(info->regs)); +} + +static void exynos_adc_v2_start_conv(struct exynos_adc *info, + unsigned long addr) +{ + u32 con1, con2; + + con2 = readl(ADC_V2_CON2(info->regs)); + con2 &= ~ADC_V2_CON2_ACH_MASK; + con2 |= ADC_V2_CON2_ACH_SEL(addr); + writel(con2, ADC_V2_CON2(info->regs)); + + con1 = readl(ADC_V2_CON1(info->regs)); + writel(con1 | ADC_CON_EN_START, ADC_V2_CON1(info->regs)); +} + +static const struct exynos_adc_data exynos_adc_v2_data = { + .num_channels = MAX_ADC_V2_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + .needs_adc_phy = true, + .phy_offset = EXYNOS_ADCV2_PHY_OFFSET, + + .init_hw = exynos_adc_v2_init_hw, + .exit_hw = exynos_adc_v2_exit_hw, + .clear_irq = exynos_adc_v2_clear_irq, + .start_conv = exynos_adc_v2_start_conv, +}; + +static const struct exynos_adc_data exynos3250_adc_data = { + .num_channels = MAX_EXYNOS3250_ADC_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + .needs_sclk = true, + .needs_adc_phy = true, + .phy_offset = EXYNOS_ADCV1_PHY_OFFSET, + + .init_hw = exynos_adc_v2_init_hw, + .exit_hw = exynos_adc_v2_exit_hw, + .clear_irq = exynos_adc_v2_clear_irq, + .start_conv = exynos_adc_v2_start_conv, +}; + +static void exynos_adc_exynos7_init_hw(struct exynos_adc *info) +{ + u32 con1, con2; + + if (info->data->needs_adc_phy) + regmap_write(info->pmu_map, info->data->phy_offset, 1); + + con1 = ADC_V2_CON1_SOFT_RESET; + writel(con1, ADC_V2_CON1(info->regs)); + + con2 = readl(ADC_V2_CON2(info->regs)); + con2 &= ~ADC_V2_CON2_C_TIME(7); + con2 |= ADC_V2_CON2_C_TIME(0); + writel(con2, ADC_V2_CON2(info->regs)); + + /* Enable interrupts */ + writel(1, ADC_V2_INT_EN(info->regs)); +} + +static const struct exynos_adc_data exynos7_adc_data = { + .num_channels = MAX_ADC_V1_CHANNELS, + .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ + + .init_hw = exynos_adc_exynos7_init_hw, + .exit_hw = exynos_adc_v2_exit_hw, + .clear_irq = exynos_adc_v2_clear_irq, + .start_conv = exynos_adc_v2_start_conv, +}; + +static const struct of_device_id exynos_adc_match[] = { + { + .compatible = "samsung,s3c2410-adc", + .data = &exynos_adc_s3c24xx_data, + }, { + .compatible = "samsung,s3c2416-adc", + .data = &exynos_adc_s3c2416_data, + }, { + .compatible = "samsung,s3c2440-adc", + .data = &exynos_adc_s3c24xx_data, + }, { + .compatible = "samsung,s3c2443-adc", + .data = &exynos_adc_s3c2443_data, + }, { + .compatible = "samsung,s3c6410-adc", + .data = &exynos_adc_s3c64xx_data, + }, { + .compatible = "samsung,exynos-adc-v1", + .data = &exynos_adc_v1_data, + }, { + .compatible = "samsung,exynos-adc-v2", + .data = &exynos_adc_v2_data, + }, { + .compatible = "samsung,exynos3250-adc", + .data = &exynos3250_adc_data, + }, { + .compatible = "samsung,exynos7-adc", + .data = &exynos7_adc_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_adc_match); + +static struct exynos_adc_data *exynos_adc_get_data(struct platform_device *pdev) +{ + const struct of_device_id *match; + + match = of_match_node(exynos_adc_match, pdev->dev.of_node); + return (struct exynos_adc_data *)match->data; +} + +static int exynos_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct exynos_adc *info = iio_priv(indio_dev); + unsigned long timeout; + int ret; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + reinit_completion(&info->completion); + + /* Select the channel to be used and Trigger conversion */ + if (info->data->start_conv) + info->data->start_conv(info, chan->address); + + timeout = wait_for_completion_timeout + (&info->completion, EXYNOS_ADC_TIMEOUT); + if (timeout == 0) { + dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); + if (info->data->init_hw) + info->data->init_hw(info); + ret = -ETIMEDOUT; + } else { + *val = info->value; + *val2 = 0; + ret = IIO_VAL_INT; + } + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static irqreturn_t exynos_adc_isr(int irq, void *dev_id) +{ + struct exynos_adc *info = (struct exynos_adc *)dev_id; + u32 mask = info->data->mask; + + /* Read value */ + info->value = readl(ADC_V1_DATX(info->regs)) & mask; + + /* clear irq */ + if (info->data->clear_irq) + info->data->clear_irq(info); + + complete(&info->completion); + + return IRQ_HANDLED; +} + +static int exynos_adc_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct exynos_adc *info = iio_priv(indio_dev); + + if (readval == NULL) + return -EINVAL; + + *readval = readl(info->regs + reg); + + return 0; +} + +static const struct iio_info exynos_adc_iio_info = { + .read_raw = &exynos_read_raw, + .debugfs_reg_access = &exynos_adc_reg_access, + .driver_module = THIS_MODULE, +}; + +#define ADC_CHANNEL(_index, _id) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .address = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .datasheet_name = _id, \ +} + +static const struct iio_chan_spec exynos_adc_iio_channels[] = { + ADC_CHANNEL(0, "adc0"), + ADC_CHANNEL(1, "adc1"), + ADC_CHANNEL(2, "adc2"), + ADC_CHANNEL(3, "adc3"), + ADC_CHANNEL(4, "adc4"), + ADC_CHANNEL(5, "adc5"), + ADC_CHANNEL(6, "adc6"), + ADC_CHANNEL(7, "adc7"), + ADC_CHANNEL(8, "adc8"), + ADC_CHANNEL(9, "adc9"), +}; + +static int exynos_adc_remove_devices(struct device *dev, void *c) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static int exynos_adc_probe(struct platform_device *pdev) +{ + struct exynos_adc *info = NULL; + struct device_node *np = pdev->dev.of_node; + struct iio_dev *indio_dev = NULL; + struct resource *mem; + int ret = -ENODEV; + int irq; + + if (!np) + return ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct exynos_adc)); + if (!indio_dev) { + dev_err(&pdev->dev, "failed allocating iio device\n"); + return -ENOMEM; + } + + info = iio_priv(indio_dev); + + info->data = exynos_adc_get_data(pdev); + if (!info->data) { + dev_err(&pdev->dev, "failed getting exynos_adc_data\n"); + return -EINVAL; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + + if (info->data->needs_adc_phy) { + info->pmu_map = syscon_regmap_lookup_by_phandle( + pdev->dev.of_node, + "samsung,syscon-phandle"); + if (IS_ERR(info->pmu_map)) { + dev_err(&pdev->dev, "syscon regmap lookup failed.\n"); + return PTR_ERR(info->pmu_map); + } + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return irq; + } + + info->irq = irq; + info->dev = &pdev->dev; + + init_completion(&info->completion); + + info->clk = devm_clk_get(&pdev->dev, "adc"); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "failed getting clock, err = %ld\n", + PTR_ERR(info->clk)); + return PTR_ERR(info->clk); + } + + if (info->data->needs_sclk) { + info->sclk = devm_clk_get(&pdev->dev, "sclk"); + if (IS_ERR(info->sclk)) { + dev_err(&pdev->dev, + "failed getting sclk clock, err = %ld\n", + PTR_ERR(info->sclk)); + return PTR_ERR(info->sclk); + } + } + + info->vdd = devm_regulator_get(&pdev->dev, "vdd"); + if (IS_ERR(info->vdd)) { + dev_err(&pdev->dev, "failed getting regulator, err = %ld\n", + PTR_ERR(info->vdd)); + return PTR_ERR(info->vdd); + } + + ret = regulator_enable(info->vdd); + if (ret) + return ret; + + ret = exynos_adc_prepare_clk(info); + if (ret) + goto err_disable_reg; + + ret = exynos_adc_enable_clk(info); + if (ret) + goto err_unprepare_clk; + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &exynos_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = exynos_adc_iio_channels; + indio_dev->num_channels = info->data->num_channels; + + ret = request_irq(info->irq, exynos_adc_isr, + 0, dev_name(&pdev->dev), info); + if (ret < 0) { + dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", + info->irq); + goto err_disable_clk; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto err_irq; + + if (info->data->init_hw) + info->data->init_hw(info); + + ret = of_platform_populate(np, exynos_adc_match, NULL, &indio_dev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed adding child nodes\n"); + goto err_of_populate; + } + + return 0; + +err_of_populate: + device_for_each_child(&indio_dev->dev, NULL, + exynos_adc_remove_devices); + iio_device_unregister(indio_dev); +err_irq: + free_irq(info->irq, info); +err_disable_clk: + if (info->data->exit_hw) + info->data->exit_hw(info); + exynos_adc_disable_clk(info); +err_unprepare_clk: + exynos_adc_unprepare_clk(info); +err_disable_reg: + regulator_disable(info->vdd); + return ret; +} + +static int exynos_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct exynos_adc *info = iio_priv(indio_dev); + + device_for_each_child(&indio_dev->dev, NULL, + exynos_adc_remove_devices); + iio_device_unregister(indio_dev); + free_irq(info->irq, info); + if (info->data->exit_hw) + info->data->exit_hw(info); + exynos_adc_disable_clk(info); + exynos_adc_unprepare_clk(info); + regulator_disable(info->vdd); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int exynos_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct exynos_adc *info = iio_priv(indio_dev); + + if (info->data->exit_hw) + info->data->exit_hw(info); + exynos_adc_disable_clk(info); + regulator_disable(info->vdd); + + return 0; +} + +static int exynos_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct exynos_adc *info = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(info->vdd); + if (ret) + return ret; + + ret = exynos_adc_enable_clk(info); + if (ret) + return ret; + + if (info->data->init_hw) + info->data->init_hw(info); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(exynos_adc_pm_ops, + exynos_adc_suspend, + exynos_adc_resume); + +static struct platform_driver exynos_adc_driver = { + .probe = exynos_adc_probe, + .remove = exynos_adc_remove, + .driver = { + .name = "exynos-adc", + .of_match_table = exynos_adc_match, + .pm = &exynos_adc_pm_ops, + }, +}; + +module_platform_driver(exynos_adc_driver); + +MODULE_AUTHOR("Naveen Krishna Chatradhi <ch.naveen@samsung.com>"); +MODULE_DESCRIPTION("Samsung EXYNOS5 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/lp8788_adc.c b/drivers/iio/adc/lp8788_adc.c new file mode 100644 index 000000000..152cfc8e1 --- /dev/null +++ b/drivers/iio/adc/lp8788_adc.c @@ -0,0 +1,254 @@ +/* + * TI LP8788 MFD - ADC driver + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/driver.h> +#include <linux/iio/machine.h> +#include <linux/mfd/lp8788.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* register address */ +#define LP8788_ADC_CONF 0x60 +#define LP8788_ADC_RAW 0x61 +#define LP8788_ADC_DONE 0x63 + +#define ADC_CONV_START 1 + +struct lp8788_adc { + struct lp8788 *lp; + struct iio_map *map; + struct mutex lock; +}; + +static const int lp8788_scale[LPADC_MAX] = { + [LPADC_VBATT_5P5] = 1343101, + [LPADC_VIN_CHG] = 3052503, + [LPADC_IBATT] = 610500, + [LPADC_IC_TEMP] = 61050, + [LPADC_VBATT_6P0] = 1465201, + [LPADC_VBATT_5P0] = 1221001, + [LPADC_ADC1] = 610500, + [LPADC_ADC2] = 610500, + [LPADC_VDD] = 1025641, + [LPADC_VCOIN] = 757020, + [LPADC_ADC3] = 610500, + [LPADC_ADC4] = 610500, +}; + +static int lp8788_get_adc_result(struct lp8788_adc *adc, enum lp8788_adc_id id, + int *val) +{ + unsigned int msb; + unsigned int lsb; + unsigned int result; + u8 data; + u8 rawdata[2]; + int size = ARRAY_SIZE(rawdata); + int retry = 5; + int ret; + + data = (id << 1) | ADC_CONV_START; + ret = lp8788_write_byte(adc->lp, LP8788_ADC_CONF, data); + if (ret) + goto err_io; + + /* retry until adc conversion is done */ + data = 0; + while (retry--) { + usleep_range(100, 200); + + ret = lp8788_read_byte(adc->lp, LP8788_ADC_DONE, &data); + if (ret) + goto err_io; + + /* conversion done */ + if (data) + break; + } + + ret = lp8788_read_multi_bytes(adc->lp, LP8788_ADC_RAW, rawdata, size); + if (ret) + goto err_io; + + msb = (rawdata[0] << 4) & 0x00000ff0; + lsb = (rawdata[1] >> 4) & 0x0000000f; + result = msb | lsb; + *val = result; + + return 0; + +err_io: + return ret; +} + +static int lp8788_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lp8788_adc *adc = iio_priv(indio_dev); + enum lp8788_adc_id id = chan->channel; + int ret; + + mutex_lock(&adc->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = lp8788_get_adc_result(adc, id, val) ? -EIO : IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = lp8788_scale[id] / 1000000; + *val2 = lp8788_scale[id] % 1000000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&adc->lock); + + return ret; +} + +static const struct iio_info lp8788_adc_info = { + .read_raw = &lp8788_adc_read_raw, + .driver_module = THIS_MODULE, +}; + +#define LP8788_CHAN(_id, _type) { \ + .type = _type, \ + .indexed = 1, \ + .channel = LPADC_##_id, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = #_id, \ +} + +static const struct iio_chan_spec lp8788_adc_channels[] = { + [LPADC_VBATT_5P5] = LP8788_CHAN(VBATT_5P5, IIO_VOLTAGE), + [LPADC_VIN_CHG] = LP8788_CHAN(VIN_CHG, IIO_VOLTAGE), + [LPADC_IBATT] = LP8788_CHAN(IBATT, IIO_CURRENT), + [LPADC_IC_TEMP] = LP8788_CHAN(IC_TEMP, IIO_TEMP), + [LPADC_VBATT_6P0] = LP8788_CHAN(VBATT_6P0, IIO_VOLTAGE), + [LPADC_VBATT_5P0] = LP8788_CHAN(VBATT_5P0, IIO_VOLTAGE), + [LPADC_ADC1] = LP8788_CHAN(ADC1, IIO_VOLTAGE), + [LPADC_ADC2] = LP8788_CHAN(ADC2, IIO_VOLTAGE), + [LPADC_VDD] = LP8788_CHAN(VDD, IIO_VOLTAGE), + [LPADC_VCOIN] = LP8788_CHAN(VCOIN, IIO_VOLTAGE), + [LPADC_ADC3] = LP8788_CHAN(ADC3, IIO_VOLTAGE), + [LPADC_ADC4] = LP8788_CHAN(ADC4, IIO_VOLTAGE), +}; + +/* default maps used by iio consumer (lp8788-charger driver) */ +static struct iio_map lp8788_default_iio_maps[] = { + { + .consumer_dev_name = "lp8788-charger", + .consumer_channel = "lp8788_vbatt_5p0", + .adc_channel_label = "VBATT_5P0", + }, + { + .consumer_dev_name = "lp8788-charger", + .consumer_channel = "lp8788_adc1", + .adc_channel_label = "ADC1", + }, + { } +}; + +static int lp8788_iio_map_register(struct iio_dev *indio_dev, + struct lp8788_platform_data *pdata, + struct lp8788_adc *adc) +{ + struct iio_map *map; + int ret; + + map = (!pdata || !pdata->adc_pdata) ? + lp8788_default_iio_maps : pdata->adc_pdata; + + ret = iio_map_array_register(indio_dev, map); + if (ret) { + dev_err(&indio_dev->dev, "iio map err: %d\n", ret); + return ret; + } + + adc->map = map; + return 0; +} + +static int lp8788_adc_probe(struct platform_device *pdev) +{ + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); + struct iio_dev *indio_dev; + struct lp8788_adc *adc; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->lp = lp; + platform_set_drvdata(pdev, indio_dev); + + indio_dev->dev.of_node = pdev->dev.of_node; + ret = lp8788_iio_map_register(indio_dev, lp->pdata, adc); + if (ret) + return ret; + + mutex_init(&adc->lock); + + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &lp8788_adc_info; + indio_dev->channels = lp8788_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(lp8788_adc_channels); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "iio dev register err: %d\n", ret); + goto err_iio_device; + } + + return 0; + +err_iio_device: + iio_map_array_unregister(indio_dev); + return ret; +} + +static int lp8788_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + iio_map_array_unregister(indio_dev); + + return 0; +} + +static struct platform_driver lp8788_adc_driver = { + .probe = lp8788_adc_probe, + .remove = lp8788_adc_remove, + .driver = { + .name = LP8788_DEV_ADC, + }, +}; +module_platform_driver(lp8788_adc_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8788 ADC Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lp8788-adc"); diff --git a/drivers/iio/adc/max1027.c b/drivers/iio/adc/max1027.c new file mode 100644 index 000000000..44bf815ad --- /dev/null +++ b/drivers/iio/adc/max1027.c @@ -0,0 +1,521 @@ + /* + * iio/adc/max1027.c + * Copyright (C) 2014 Philippe Reynes + * + * based on linux/drivers/iio/ad7923.c + * Copyright 2011 Analog Devices Inc (from AD7923 Driver) + * Copyright 2012 CS Systemes d'Information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * max1027.c + * + * Partial support for max1027 and similar chips. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define MAX1027_CONV_REG BIT(7) +#define MAX1027_SETUP_REG BIT(6) +#define MAX1027_AVG_REG BIT(5) +#define MAX1027_RST_REG BIT(4) + +/* conversion register */ +#define MAX1027_TEMP BIT(0) +#define MAX1027_SCAN_0_N (0x00 << 1) +#define MAX1027_SCAN_N_M (0x01 << 1) +#define MAX1027_SCAN_N (0x02 << 1) +#define MAX1027_NOSCAN (0x03 << 1) +#define MAX1027_CHAN(n) ((n) << 3) + +/* setup register */ +#define MAX1027_UNIPOLAR 0x02 +#define MAX1027_BIPOLAR 0x03 +#define MAX1027_REF_MODE0 (0x00 << 2) +#define MAX1027_REF_MODE1 (0x01 << 2) +#define MAX1027_REF_MODE2 (0x02 << 2) +#define MAX1027_REF_MODE3 (0x03 << 2) +#define MAX1027_CKS_MODE0 (0x00 << 4) +#define MAX1027_CKS_MODE1 (0x01 << 4) +#define MAX1027_CKS_MODE2 (0x02 << 4) +#define MAX1027_CKS_MODE3 (0x03 << 4) + +/* averaging register */ +#define MAX1027_NSCAN_4 0x00 +#define MAX1027_NSCAN_8 0x01 +#define MAX1027_NSCAN_12 0x02 +#define MAX1027_NSCAN_16 0x03 +#define MAX1027_NAVG_4 (0x00 << 2) +#define MAX1027_NAVG_8 (0x01 << 2) +#define MAX1027_NAVG_16 (0x02 << 2) +#define MAX1027_NAVG_32 (0x03 << 2) +#define MAX1027_AVG_EN BIT(4) + +enum max1027_id { + max1027, + max1029, + max1031, +}; + +static const struct spi_device_id max1027_id[] = { + {"max1027", max1027}, + {"max1029", max1029}, + {"max1031", max1031}, + {} +}; +MODULE_DEVICE_TABLE(spi, max1027_id); + +#ifdef CONFIG_OF +static const struct of_device_id max1027_adc_dt_ids[] = { + { .compatible = "maxim,max1027" }, + { .compatible = "maxim,max1029" }, + { .compatible = "maxim,max1031" }, + {}, +}; +MODULE_DEVICE_TABLE(of, max1027_adc_dt_ids); +#endif + +#define MAX1027_V_CHAN(index) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = index + 1, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 10, \ + .storagebits = 16, \ + .shift = 2, \ + .endianness = IIO_BE, \ + }, \ + } + +#define MAX1027_T_CHAN \ + { \ + .type = IIO_TEMP, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = 0, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec max1027_channels[] = { + MAX1027_T_CHAN, + MAX1027_V_CHAN(0), + MAX1027_V_CHAN(1), + MAX1027_V_CHAN(2), + MAX1027_V_CHAN(3), + MAX1027_V_CHAN(4), + MAX1027_V_CHAN(5), + MAX1027_V_CHAN(6), + MAX1027_V_CHAN(7) +}; + +static const struct iio_chan_spec max1029_channels[] = { + MAX1027_T_CHAN, + MAX1027_V_CHAN(0), + MAX1027_V_CHAN(1), + MAX1027_V_CHAN(2), + MAX1027_V_CHAN(3), + MAX1027_V_CHAN(4), + MAX1027_V_CHAN(5), + MAX1027_V_CHAN(6), + MAX1027_V_CHAN(7), + MAX1027_V_CHAN(8), + MAX1027_V_CHAN(9), + MAX1027_V_CHAN(10), + MAX1027_V_CHAN(11) +}; + +static const struct iio_chan_spec max1031_channels[] = { + MAX1027_T_CHAN, + MAX1027_V_CHAN(0), + MAX1027_V_CHAN(1), + MAX1027_V_CHAN(2), + MAX1027_V_CHAN(3), + MAX1027_V_CHAN(4), + MAX1027_V_CHAN(5), + MAX1027_V_CHAN(6), + MAX1027_V_CHAN(7), + MAX1027_V_CHAN(8), + MAX1027_V_CHAN(9), + MAX1027_V_CHAN(10), + MAX1027_V_CHAN(11), + MAX1027_V_CHAN(12), + MAX1027_V_CHAN(13), + MAX1027_V_CHAN(14), + MAX1027_V_CHAN(15) +}; + +static const unsigned long max1027_available_scan_masks[] = { + 0x000001ff, + 0x00000000, +}; + +static const unsigned long max1029_available_scan_masks[] = { + 0x00001fff, + 0x00000000, +}; + +static const unsigned long max1031_available_scan_masks[] = { + 0x0001ffff, + 0x00000000, +}; + +struct max1027_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + const unsigned long *available_scan_masks; +}; + +static const struct max1027_chip_info max1027_chip_info_tbl[] = { + [max1027] = { + .channels = max1027_channels, + .num_channels = ARRAY_SIZE(max1027_channels), + .available_scan_masks = max1027_available_scan_masks, + }, + [max1029] = { + .channels = max1029_channels, + .num_channels = ARRAY_SIZE(max1029_channels), + .available_scan_masks = max1029_available_scan_masks, + }, + [max1031] = { + .channels = max1031_channels, + .num_channels = ARRAY_SIZE(max1031_channels), + .available_scan_masks = max1031_available_scan_masks, + }, +}; + +struct max1027_state { + const struct max1027_chip_info *info; + struct spi_device *spi; + struct iio_trigger *trig; + __be16 *buffer; + struct mutex lock; + + u8 reg ____cacheline_aligned; +}; + +static int max1027_read_single_value(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + int ret; + struct max1027_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) { + dev_warn(&indio_dev->dev, "trigger mode already enabled"); + return -EBUSY; + } + + /* Start acquisition on conversion register write */ + st->reg = MAX1027_SETUP_REG | MAX1027_REF_MODE2 | MAX1027_CKS_MODE2; + ret = spi_write(st->spi, &st->reg, 1); + if (ret < 0) { + dev_err(&indio_dev->dev, + "Failed to configure setup register\n"); + return ret; + } + + /* Configure conversion register with the requested chan */ + st->reg = MAX1027_CONV_REG | MAX1027_CHAN(chan->channel) | + MAX1027_NOSCAN | !!(chan->type == IIO_TEMP); + ret = spi_write(st->spi, &st->reg, 1); + if (ret < 0) { + dev_err(&indio_dev->dev, + "Failed to configure conversion register\n"); + return ret; + } + + /* + * For an unknown reason, when we use the mode "10" (write + * conversion register), the interrupt doesn't occur every time. + * So we just wait 1 ms. + */ + mdelay(1); + + /* Read result */ + ret = spi_read(st->spi, st->buffer, (chan->type == IIO_TEMP) ? 4 : 2); + if (ret < 0) + return ret; + + *val = be16_to_cpu(st->buffer[0]); + + return IIO_VAL_INT; +} + +static int max1027_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret = 0; + struct max1027_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = max1027_read_single_value(indio_dev, chan, val); + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 1; + *val2 = 8; + ret = IIO_VAL_FRACTIONAL; + break; + case IIO_VOLTAGE: + *val = 2500; + *val2 = 10; + ret = IIO_VAL_FRACTIONAL_LOG2; + break; + default: + ret = -EINVAL; + break; + } + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&st->lock); + + return ret; +} + +static int max1027_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct max1027_state *st = iio_priv(indio_dev); + u8 *val = (u8 *)st->buffer; + + if (readval != NULL) + return -EINVAL; + + *val = (u8)writeval; + return spi_write(st->spi, val, 1); +} + +static int max1027_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct max1027_state *st = iio_priv(indio_dev); + + if (st->trig != trig) + return -EINVAL; + + return 0; +} + +static int max1027_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct max1027_state *st = iio_priv(indio_dev); + int ret; + + if (state) { + /* Start acquisition on cnvst */ + st->reg = MAX1027_SETUP_REG | MAX1027_CKS_MODE0 | + MAX1027_REF_MODE2; + ret = spi_write(st->spi, &st->reg, 1); + if (ret < 0) + return ret; + + /* Scan from 0 to max */ + st->reg = MAX1027_CONV_REG | MAX1027_CHAN(0) | + MAX1027_SCAN_N_M | MAX1027_TEMP; + ret = spi_write(st->spi, &st->reg, 1); + if (ret < 0) + return ret; + } else { + /* Start acquisition on conversion register write */ + st->reg = MAX1027_SETUP_REG | MAX1027_CKS_MODE2 | + MAX1027_REF_MODE2; + ret = spi_write(st->spi, &st->reg, 1); + if (ret < 0) + return ret; + } + + return 0; +} + +static int max1027_validate_device(struct iio_trigger *trig, + struct iio_dev *indio_dev) +{ + struct iio_dev *indio = iio_trigger_get_drvdata(trig); + + if (indio != indio_dev) + return -EINVAL; + + return 0; +} + +static irqreturn_t max1027_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = (struct iio_poll_func *)private; + struct iio_dev *indio_dev = pf->indio_dev; + struct max1027_state *st = iio_priv(indio_dev); + + pr_debug("%s(irq=%d, private=0x%p)\n", __func__, irq, private); + + /* fill buffer with all channel */ + spi_read(st->spi, st->buffer, indio_dev->masklength * 2); + + iio_push_to_buffers(indio_dev, st->buffer); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops max1027_trigger_ops = { + .owner = THIS_MODULE, + .validate_device = &max1027_validate_device, + .set_trigger_state = &max1027_set_trigger_state, +}; + +static const struct iio_info max1027_info = { + .driver_module = THIS_MODULE, + .read_raw = &max1027_read_raw, + .validate_trigger = &max1027_validate_trigger, + .debugfs_reg_access = &max1027_debugfs_reg_access, +}; + +static int max1027_probe(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev; + struct max1027_state *st; + + pr_debug("%s: probe(spi = 0x%p)\n", __func__, spi); + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) { + pr_err("Can't allocate iio device\n"); + return -ENOMEM; + } + + spi_set_drvdata(spi, indio_dev); + + st = iio_priv(indio_dev); + st->spi = spi; + st->info = &max1027_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + mutex_init(&st->lock); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &max1027_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->info->channels; + indio_dev->num_channels = st->info->num_channels; + indio_dev->available_scan_masks = st->info->available_scan_masks; + + st->buffer = devm_kmalloc(&indio_dev->dev, + indio_dev->num_channels * 2, + GFP_KERNEL); + if (st->buffer == NULL) { + dev_err(&indio_dev->dev, "Can't allocate buffer\n"); + return -ENOMEM; + } + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &max1027_trigger_handler, NULL); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed to setup buffer\n"); + return ret; + } + + st->trig = devm_iio_trigger_alloc(&spi->dev, "%s-trigger", + indio_dev->name); + if (st->trig == NULL) { + ret = -ENOMEM; + dev_err(&indio_dev->dev, "Failed to allocate iio trigger\n"); + goto fail_trigger_alloc; + } + + st->trig->ops = &max1027_trigger_ops; + st->trig->dev.parent = &spi->dev; + iio_trigger_set_drvdata(st->trig, indio_dev); + iio_trigger_register(st->trig); + + ret = devm_request_threaded_irq(&spi->dev, spi->irq, + iio_trigger_generic_data_rdy_poll, + NULL, + IRQF_TRIGGER_FALLING, + spi->dev.driver->name, st->trig); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed to allocate IRQ.\n"); + goto fail_dev_register; + } + + /* Disable averaging */ + st->reg = MAX1027_AVG_REG; + ret = spi_write(st->spi, &st->reg, 1); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed to configure averaging register\n"); + goto fail_dev_register; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed to register iio device\n"); + goto fail_dev_register; + } + + return 0; + +fail_dev_register: +fail_trigger_alloc: + iio_triggered_buffer_cleanup(indio_dev); + + return ret; +} + +static int max1027_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + pr_debug("%s: remove(spi = 0x%p)\n", __func__, spi); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + return 0; +} + +static struct spi_driver max1027_driver = { + .driver = { + .name = "max1027", + .owner = THIS_MODULE, + }, + .probe = max1027_probe, + .remove = max1027_remove, + .id_table = max1027_id, +}; +module_spi_driver(max1027_driver); + +MODULE_AUTHOR("Philippe Reynes <tremyfr@yahoo.fr>"); +MODULE_DESCRIPTION("MAX1027/MAX1029/MAX1031 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/max1363.c b/drivers/iio/adc/max1363.c new file mode 100644 index 000000000..1b3b74be5 --- /dev/null +++ b/drivers/iio/adc/max1363.c @@ -0,0 +1,1701 @@ + /* + * iio/adc/max1363.c + * Copyright (C) 2008-2010 Jonathan Cameron + * + * based on linux/drivers/i2c/chips/max123x + * Copyright (C) 2002-2004 Stefan Eletzhofer + * + * based on linux/drivers/acron/char/pcf8583.c + * Copyright (C) 2000 Russell King + * + * Driver for max1363 and similar chips. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/sysfs.h> +#include <linux/list.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/buffer.h> +#include <linux/iio/driver.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define MAX1363_SETUP_BYTE(a) ((a) | 0x80) + +/* There is a fair bit more defined here than currently + * used, but the intention is to support everything these + * chips do in the long run */ + +/* see data sheets */ +/* max1363 and max1236, max1237, max1238, max1239 */ +#define MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_VDD 0x00 +#define MAX1363_SETUP_AIN3_IS_REF_EXT_TO_REF 0x20 +#define MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_INT 0x40 +#define MAX1363_SETUP_AIN3_IS_REF_REF_IS_INT 0x60 +#define MAX1363_SETUP_POWER_UP_INT_REF 0x10 +#define MAX1363_SETUP_POWER_DOWN_INT_REF 0x00 + +/* think about including max11600 etc - more settings */ +#define MAX1363_SETUP_EXT_CLOCK 0x08 +#define MAX1363_SETUP_INT_CLOCK 0x00 +#define MAX1363_SETUP_UNIPOLAR 0x00 +#define MAX1363_SETUP_BIPOLAR 0x04 +#define MAX1363_SETUP_RESET 0x00 +#define MAX1363_SETUP_NORESET 0x02 +/* max1363 only - though don't care on others. + * For now monitor modes are not implemented as the relevant + * line is not connected on my test board. + * The definitions are here as I intend to add this soon. + */ +#define MAX1363_SETUP_MONITOR_SETUP 0x01 + +/* Specific to the max1363 */ +#define MAX1363_MON_RESET_CHAN(a) (1 << ((a) + 4)) +#define MAX1363_MON_INT_ENABLE 0x01 + +/* defined for readability reasons */ +/* All chips */ +#define MAX1363_CONFIG_BYTE(a) ((a)) + +#define MAX1363_CONFIG_SE 0x01 +#define MAX1363_CONFIG_DE 0x00 +#define MAX1363_CONFIG_SCAN_TO_CS 0x00 +#define MAX1363_CONFIG_SCAN_SINGLE_8 0x20 +#define MAX1363_CONFIG_SCAN_MONITOR_MODE 0x40 +#define MAX1363_CONFIG_SCAN_SINGLE_1 0x60 +/* max123{6-9} only */ +#define MAX1236_SCAN_MID_TO_CHANNEL 0x40 + +/* max1363 only - merely part of channel selects or don't care for others */ +#define MAX1363_CONFIG_EN_MON_MODE_READ 0x18 + +#define MAX1363_CHANNEL_SEL(a) ((a) << 1) + +/* max1363 strictly 0x06 - but doesn't matter */ +#define MAX1363_CHANNEL_SEL_MASK 0x1E +#define MAX1363_SCAN_MASK 0x60 +#define MAX1363_SE_DE_MASK 0x01 + +#define MAX1363_MAX_CHANNELS 25 +/** + * struct max1363_mode - scan mode information + * @conf: The corresponding value of the configuration register + * @modemask: Bit mask corresponding to channels enabled in this mode + */ +struct max1363_mode { + int8_t conf; + DECLARE_BITMAP(modemask, MAX1363_MAX_CHANNELS); +}; + +/* This must be maintained along side the max1363_mode_table in max1363_core */ +enum max1363_modes { + /* Single read of a single channel */ + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, _s8, _s9, _s10, _s11, + /* Differential single read */ + d0m1, d2m3, d4m5, d6m7, d8m9, d10m11, + d1m0, d3m2, d5m4, d7m6, d9m8, d11m10, + /* Scan to channel and mid to channel where overlapping */ + s0to1, s0to2, s2to3, s0to3, s0to4, s0to5, s0to6, + s6to7, s0to7, s6to8, s0to8, s6to9, + s0to9, s6to10, s0to10, s6to11, s0to11, + /* Differential scan to channel and mid to channel where overlapping */ + d0m1to2m3, d0m1to4m5, d0m1to6m7, d6m7to8m9, + d0m1to8m9, d6m7to10m11, d0m1to10m11, d1m0to3m2, + d1m0to5m4, d1m0to7m6, d7m6to9m8, d1m0to9m8, + d7m6to11m10, d1m0to11m10, +}; + +/** + * struct max1363_chip_info - chip specifc information + * @info: iio core function callbacks structure + * @channels: channel specification + * @num_channels: number of channels + * @mode_list: array of available scan modes + * @default_mode: the scan mode in which the chip starts up + * @int_vref_mv: the internal reference voltage + * @num_modes: number of modes + * @bits: accuracy of the adc in bits + */ +struct max1363_chip_info { + const struct iio_info *info; + const struct iio_chan_spec *channels; + int num_channels; + const enum max1363_modes *mode_list; + enum max1363_modes default_mode; + u16 int_vref_mv; + u8 num_modes; + u8 bits; +}; + +/** + * struct max1363_state - driver instance specific data + * @client: i2c_client + * @setupbyte: cache of current device setup byte + * @configbyte: cache of current device config byte + * @chip_info: chip model specific constants, available modes, etc. + * @current_mode: the scan mode of this chip + * @requestedmask: a valid requested set of channels + * @reg: supply regulator + * @monitor_on: whether monitor mode is enabled + * @monitor_speed: parameter corresponding to device monitor speed setting + * @mask_high: bitmask for enabled high thresholds + * @mask_low: bitmask for enabled low thresholds + * @thresh_high: high threshold values + * @thresh_low: low threshold values + * @vref: Reference voltage regulator + * @vref_uv: Actual (external or internal) reference voltage + * @send: function used to send data to the chip + * @recv: function used to receive data from the chip + */ +struct max1363_state { + struct i2c_client *client; + u8 setupbyte; + u8 configbyte; + const struct max1363_chip_info *chip_info; + const struct max1363_mode *current_mode; + u32 requestedmask; + struct regulator *reg; + + /* Using monitor modes and buffer at the same time is + currently not supported */ + bool monitor_on; + unsigned int monitor_speed:3; + u8 mask_high; + u8 mask_low; + /* 4x unipolar first then the fours bipolar ones */ + s16 thresh_high[8]; + s16 thresh_low[8]; + struct regulator *vref; + u32 vref_uv; + int (*send)(const struct i2c_client *client, + const char *buf, int count); + int (*recv)(const struct i2c_client *client, + char *buf, int count); +}; + +#define MAX1363_MODE_SINGLE(_num, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_SINGLE_1 \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask, \ + } + +#define MAX1363_MODE_SCAN_TO_CHANNEL(_num, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_TO_CS \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask, \ + } + +/* note not available for max1363 hence naming */ +#define MAX1236_MODE_SCAN_MID_TO_CHANNEL(_mid, _num, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1236_SCAN_MID_TO_CHANNEL \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask \ +} + +#define MAX1363_MODE_DIFF_SINGLE(_nump, _numm, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_nump) \ + | MAX1363_CONFIG_SCAN_SINGLE_1 \ + | MAX1363_CONFIG_DE, \ + .modemask[0] = _mask \ + } + +/* Can't think how to automate naming so specify for now */ +#define MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(_num, _numvals, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_TO_CS \ + | MAX1363_CONFIG_DE, \ + .modemask[0] = _mask \ + } + +/* note only available for max1363 hence naming */ +#define MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(_num, _numvals, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1236_SCAN_MID_TO_CHANNEL \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask \ +} + +static const struct max1363_mode max1363_mode_table[] = { + /* All of the single channel options first */ + MAX1363_MODE_SINGLE(0, 1 << 0), + MAX1363_MODE_SINGLE(1, 1 << 1), + MAX1363_MODE_SINGLE(2, 1 << 2), + MAX1363_MODE_SINGLE(3, 1 << 3), + MAX1363_MODE_SINGLE(4, 1 << 4), + MAX1363_MODE_SINGLE(5, 1 << 5), + MAX1363_MODE_SINGLE(6, 1 << 6), + MAX1363_MODE_SINGLE(7, 1 << 7), + MAX1363_MODE_SINGLE(8, 1 << 8), + MAX1363_MODE_SINGLE(9, 1 << 9), + MAX1363_MODE_SINGLE(10, 1 << 10), + MAX1363_MODE_SINGLE(11, 1 << 11), + + MAX1363_MODE_DIFF_SINGLE(0, 1, 1 << 12), + MAX1363_MODE_DIFF_SINGLE(2, 3, 1 << 13), + MAX1363_MODE_DIFF_SINGLE(4, 5, 1 << 14), + MAX1363_MODE_DIFF_SINGLE(6, 7, 1 << 15), + MAX1363_MODE_DIFF_SINGLE(8, 9, 1 << 16), + MAX1363_MODE_DIFF_SINGLE(10, 11, 1 << 17), + MAX1363_MODE_DIFF_SINGLE(1, 0, 1 << 18), + MAX1363_MODE_DIFF_SINGLE(3, 2, 1 << 19), + MAX1363_MODE_DIFF_SINGLE(5, 4, 1 << 20), + MAX1363_MODE_DIFF_SINGLE(7, 6, 1 << 21), + MAX1363_MODE_DIFF_SINGLE(9, 8, 1 << 22), + MAX1363_MODE_DIFF_SINGLE(11, 10, 1 << 23), + + /* The multichannel scans next */ + MAX1363_MODE_SCAN_TO_CHANNEL(1, 0x003), + MAX1363_MODE_SCAN_TO_CHANNEL(2, 0x007), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(2, 3, 0x00C), + MAX1363_MODE_SCAN_TO_CHANNEL(3, 0x00F), + MAX1363_MODE_SCAN_TO_CHANNEL(4, 0x01F), + MAX1363_MODE_SCAN_TO_CHANNEL(5, 0x03F), + MAX1363_MODE_SCAN_TO_CHANNEL(6, 0x07F), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 7, 0x0C0), + MAX1363_MODE_SCAN_TO_CHANNEL(7, 0x0FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 8, 0x1C0), + MAX1363_MODE_SCAN_TO_CHANNEL(8, 0x1FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 9, 0x3C0), + MAX1363_MODE_SCAN_TO_CHANNEL(9, 0x3FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 10, 0x7C0), + MAX1363_MODE_SCAN_TO_CHANNEL(10, 0x7FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 11, 0xFC0), + MAX1363_MODE_SCAN_TO_CHANNEL(11, 0xFFF), + + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(2, 2, 0x003000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(4, 3, 0x007000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(6, 4, 0x00F000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(8, 2, 0x018000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(8, 5, 0x01F000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(10, 3, 0x038000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(10, 6, 0x3F000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(3, 2, 0x0C0000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(5, 3, 0x1C0000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(7, 4, 0x3C0000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(9, 2, 0x600000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(9, 5, 0x7C0000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(11, 3, 0xE00000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(11, 6, 0xFC0000), +}; + +static const struct max1363_mode +*max1363_match_mode(const unsigned long *mask, + const struct max1363_chip_info *ci) +{ + int i; + if (mask) + for (i = 0; i < ci->num_modes; i++) + if (bitmap_subset(mask, + max1363_mode_table[ci->mode_list[i]]. + modemask, + MAX1363_MAX_CHANNELS)) + return &max1363_mode_table[ci->mode_list[i]]; + return NULL; +} + +static int max1363_smbus_send(const struct i2c_client *client, const char *buf, + int count) +{ + int i, err; + + for (i = err = 0; err == 0 && i < count; ++i) + err = i2c_smbus_write_byte(client, buf[i]); + + return err ? err : count; +} + +static int max1363_smbus_recv(const struct i2c_client *client, char *buf, + int count) +{ + int i, ret; + + for (i = 0; i < count; ++i) { + ret = i2c_smbus_read_byte(client); + if (ret < 0) + return ret; + buf[i] = ret; + } + + return count; +} + +static int max1363_write_basic_config(struct max1363_state *st) +{ + u8 tx_buf[2] = { st->setupbyte, st->configbyte }; + + return st->send(st->client, tx_buf, 2); +} + +static int max1363_set_scan_mode(struct max1363_state *st) +{ + st->configbyte &= ~(MAX1363_CHANNEL_SEL_MASK + | MAX1363_SCAN_MASK + | MAX1363_SE_DE_MASK); + st->configbyte |= st->current_mode->conf; + + return max1363_write_basic_config(st); +} + +static int max1363_read_single_chan(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + long m) +{ + int ret = 0; + s32 data; + u8 rxbuf[2]; + struct max1363_state *st = iio_priv(indio_dev); + struct i2c_client *client = st->client; + + mutex_lock(&indio_dev->mlock); + /* + * If monitor mode is enabled, the method for reading a single + * channel will have to be rather different and has not yet + * been implemented. + * + * Also, cannot read directly if buffered capture enabled. + */ + if (st->monitor_on || iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto error_ret; + } + + /* Check to see if current scan mode is correct */ + if (st->current_mode != &max1363_mode_table[chan->address]) { + /* Update scan mode if needed */ + st->current_mode = &max1363_mode_table[chan->address]; + ret = max1363_set_scan_mode(st); + if (ret < 0) + goto error_ret; + } + if (st->chip_info->bits != 8) { + /* Get reading */ + data = st->recv(client, rxbuf, 2); + if (data < 0) { + ret = data; + goto error_ret; + } + data = (rxbuf[1] | rxbuf[0] << 8) & + ((1 << st->chip_info->bits) - 1); + } else { + /* Get reading */ + data = st->recv(client, rxbuf, 1); + if (data < 0) { + ret = data; + goto error_ret; + } + data = rxbuf[0]; + } + *val = data; +error_ret: + mutex_unlock(&indio_dev->mlock); + return ret; + +} + +static int max1363_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct max1363_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = max1363_read_single_chan(indio_dev, chan, val, m); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_uv / 1000; + *val2 = st->chip_info->bits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + return 0; +} + +/* Applies to max1363 */ +static const enum max1363_modes max1363_mode_list[] = { + _s0, _s1, _s2, _s3, + s0to1, s0to2, s0to3, + d0m1, d2m3, d1m0, d3m2, + d0m1to2m3, d1m0to3m2, +}; + +static const struct iio_event_spec max1363_events[] = { + { + .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), + }, +}; + +#define MAX1363_CHAN_U(num, addr, si, bits, ev_spec, num_ev_spec) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = num, \ + .address = addr, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "AIN"#num, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = bits, \ + .storagebits = (bits > 8) ? 16 : 8, \ + .endianness = IIO_BE, \ + }, \ + .scan_index = si, \ + .event_spec = ev_spec, \ + .num_event_specs = num_ev_spec, \ + } + +/* bipolar channel */ +#define MAX1363_CHAN_B(num, num2, addr, si, bits, ev_spec, num_ev_spec) \ + { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .channel = num, \ + .channel2 = num2, \ + .address = addr, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = "AIN"#num"-AIN"#num2, \ + .scan_type = { \ + .sign = 's', \ + .realbits = bits, \ + .storagebits = (bits > 8) ? 16 : 8, \ + .endianness = IIO_BE, \ + }, \ + .scan_index = si, \ + .event_spec = ev_spec, \ + .num_event_specs = num_ev_spec, \ + } + +#define MAX1363_4X_CHANS(bits, ev_spec, num_ev_spec) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_U(1, _s1, 1, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_U(2, _s2, 2, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_U(3, _s3, 3, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_B(0, 1, d0m1, 4, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_B(2, 3, d2m3, 5, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_B(1, 0, d1m0, 6, bits, ev_spec, num_ev_spec), \ + MAX1363_CHAN_B(3, 2, d3m2, 7, bits, ev_spec, num_ev_spec), \ + IIO_CHAN_SOFT_TIMESTAMP(8) \ + } + +static const struct iio_chan_spec max1036_channels[] = + MAX1363_4X_CHANS(8, NULL, 0); +static const struct iio_chan_spec max1136_channels[] = + MAX1363_4X_CHANS(10, NULL, 0); +static const struct iio_chan_spec max1236_channels[] = + MAX1363_4X_CHANS(12, NULL, 0); +static const struct iio_chan_spec max1361_channels[] = + MAX1363_4X_CHANS(10, max1363_events, ARRAY_SIZE(max1363_events)); +static const struct iio_chan_spec max1363_channels[] = + MAX1363_4X_CHANS(12, max1363_events, ARRAY_SIZE(max1363_events)); + +/* Applies to max1236, max1237 */ +static const enum max1363_modes max1236_mode_list[] = { + _s0, _s1, _s2, _s3, + s0to1, s0to2, s0to3, + d0m1, d2m3, d1m0, d3m2, + d0m1to2m3, d1m0to3m2, + s2to3, +}; + +/* Applies to max1238, max1239 */ +static const enum max1363_modes max1238_mode_list[] = { + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, _s8, _s9, _s10, _s11, + s0to1, s0to2, s0to3, s0to4, s0to5, s0to6, + s0to7, s0to8, s0to9, s0to10, s0to11, + d0m1, d2m3, d4m5, d6m7, d8m9, d10m11, + d1m0, d3m2, d5m4, d7m6, d9m8, d11m10, + d0m1to2m3, d0m1to4m5, d0m1to6m7, d0m1to8m9, d0m1to10m11, + d1m0to3m2, d1m0to5m4, d1m0to7m6, d1m0to9m8, d1m0to11m10, + s6to7, s6to8, s6to9, s6to10, s6to11, + d6m7to8m9, d6m7to10m11, d7m6to9m8, d7m6to11m10, +}; + +#define MAX1363_12X_CHANS(bits) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, NULL, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, NULL, 0), \ + MAX1363_CHAN_U(2, _s2, 2, bits, NULL, 0), \ + MAX1363_CHAN_U(3, _s3, 3, bits, NULL, 0), \ + MAX1363_CHAN_U(4, _s4, 4, bits, NULL, 0), \ + MAX1363_CHAN_U(5, _s5, 5, bits, NULL, 0), \ + MAX1363_CHAN_U(6, _s6, 6, bits, NULL, 0), \ + MAX1363_CHAN_U(7, _s7, 7, bits, NULL, 0), \ + MAX1363_CHAN_U(8, _s8, 8, bits, NULL, 0), \ + MAX1363_CHAN_U(9, _s9, 9, bits, NULL, 0), \ + MAX1363_CHAN_U(10, _s10, 10, bits, NULL, 0), \ + MAX1363_CHAN_U(11, _s11, 11, bits, NULL, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 12, bits, NULL, 0), \ + MAX1363_CHAN_B(2, 3, d2m3, 13, bits, NULL, 0), \ + MAX1363_CHAN_B(4, 5, d4m5, 14, bits, NULL, 0), \ + MAX1363_CHAN_B(6, 7, d6m7, 15, bits, NULL, 0), \ + MAX1363_CHAN_B(8, 9, d8m9, 16, bits, NULL, 0), \ + MAX1363_CHAN_B(10, 11, d10m11, 17, bits, NULL, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 18, bits, NULL, 0), \ + MAX1363_CHAN_B(3, 2, d3m2, 19, bits, NULL, 0), \ + MAX1363_CHAN_B(5, 4, d5m4, 20, bits, NULL, 0), \ + MAX1363_CHAN_B(7, 6, d7m6, 21, bits, NULL, 0), \ + MAX1363_CHAN_B(9, 8, d9m8, 22, bits, NULL, 0), \ + MAX1363_CHAN_B(11, 10, d11m10, 23, bits, NULL, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(24) \ + } +static const struct iio_chan_spec max1038_channels[] = MAX1363_12X_CHANS(8); +static const struct iio_chan_spec max1138_channels[] = MAX1363_12X_CHANS(10); +static const struct iio_chan_spec max1238_channels[] = MAX1363_12X_CHANS(12); + +static const enum max1363_modes max11607_mode_list[] = { + _s0, _s1, _s2, _s3, + s0to1, s0to2, s0to3, + s2to3, + d0m1, d2m3, d1m0, d3m2, + d0m1to2m3, d1m0to3m2, +}; + +static const enum max1363_modes max11608_mode_list[] = { + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, + s0to1, s0to2, s0to3, s0to4, s0to5, s0to6, s0to7, + s6to7, + d0m1, d2m3, d4m5, d6m7, + d1m0, d3m2, d5m4, d7m6, + d0m1to2m3, d0m1to4m5, d0m1to6m7, + d1m0to3m2, d1m0to5m4, d1m0to7m6, +}; + +#define MAX1363_8X_CHANS(bits) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, NULL, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, NULL, 0), \ + MAX1363_CHAN_U(2, _s2, 2, bits, NULL, 0), \ + MAX1363_CHAN_U(3, _s3, 3, bits, NULL, 0), \ + MAX1363_CHAN_U(4, _s4, 4, bits, NULL, 0), \ + MAX1363_CHAN_U(5, _s5, 5, bits, NULL, 0), \ + MAX1363_CHAN_U(6, _s6, 6, bits, NULL, 0), \ + MAX1363_CHAN_U(7, _s7, 7, bits, NULL, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 8, bits, NULL, 0), \ + MAX1363_CHAN_B(2, 3, d2m3, 9, bits, NULL, 0), \ + MAX1363_CHAN_B(4, 5, d4m5, 10, bits, NULL, 0), \ + MAX1363_CHAN_B(6, 7, d6m7, 11, bits, NULL, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 12, bits, NULL, 0), \ + MAX1363_CHAN_B(3, 2, d3m2, 13, bits, NULL, 0), \ + MAX1363_CHAN_B(5, 4, d5m4, 14, bits, NULL, 0), \ + MAX1363_CHAN_B(7, 6, d7m6, 15, bits, NULL, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(16) \ +} +static const struct iio_chan_spec max11602_channels[] = MAX1363_8X_CHANS(8); +static const struct iio_chan_spec max11608_channels[] = MAX1363_8X_CHANS(10); +static const struct iio_chan_spec max11614_channels[] = MAX1363_8X_CHANS(12); + +static const enum max1363_modes max11644_mode_list[] = { + _s0, _s1, s0to1, d0m1, d1m0, +}; + +#define MAX1363_2X_CHANS(bits) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, NULL, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, NULL, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 2, bits, NULL, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 3, bits, NULL, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(4) \ + } + +static const struct iio_chan_spec max11646_channels[] = MAX1363_2X_CHANS(10); +static const struct iio_chan_spec max11644_channels[] = MAX1363_2X_CHANS(12); + +enum { max1361, + max1362, + max1363, + max1364, + max1036, + max1037, + max1038, + max1039, + max1136, + max1137, + max1138, + max1139, + max1236, + max1237, + max1238, + max1239, + max11600, + max11601, + max11602, + max11603, + max11604, + max11605, + max11606, + max11607, + max11608, + max11609, + max11610, + max11611, + max11612, + max11613, + max11614, + max11615, + max11616, + max11617, + max11644, + max11645, + max11646, + max11647 +}; + +static const int max1363_monitor_speeds[] = { 133000, 665000, 33300, 16600, + 8300, 4200, 2000, 1000 }; + +static ssize_t max1363_monitor_show_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max1363_state *st = iio_priv(dev_to_iio_dev(dev)); + return sprintf(buf, "%d\n", max1363_monitor_speeds[st->monitor_speed]); +} + +static ssize_t max1363_monitor_store_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct max1363_state *st = iio_priv(indio_dev); + int i, ret; + unsigned long val; + bool found = false; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(max1363_monitor_speeds); i++) + if (val == max1363_monitor_speeds[i]) { + found = true; + break; + } + if (!found) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + st->monitor_speed = i; + mutex_unlock(&indio_dev->mlock); + + return 0; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, + max1363_monitor_show_freq, + max1363_monitor_store_freq); + +static IIO_CONST_ATTR(sampling_frequency_available, + "133000 665000 33300 16600 8300 4200 2000 1000"); + +static int max1363_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 max1363_state *st = iio_priv(indio_dev); + if (dir == IIO_EV_DIR_FALLING) + *val = st->thresh_low[chan->channel]; + else + *val = st->thresh_high[chan->channel]; + return IIO_VAL_INT; +} + +static int max1363_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 max1363_state *st = iio_priv(indio_dev); + /* make it handle signed correctly as well */ + switch (st->chip_info->bits) { + case 10: + if (val > 0x3FF) + return -EINVAL; + break; + case 12: + if (val > 0xFFF) + return -EINVAL; + break; + } + + switch (dir) { + case IIO_EV_DIR_FALLING: + st->thresh_low[chan->channel] = val; + break; + case IIO_EV_DIR_RISING: + st->thresh_high[chan->channel] = val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const u64 max1363_event_codes[] = { + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 1, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 2, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 3, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 1, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 2, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 3, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), +}; + +static irqreturn_t max1363_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct max1363_state *st = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(); + unsigned long mask, loc; + u8 rx; + u8 tx[2] = { st->setupbyte, + MAX1363_MON_INT_ENABLE | (st->monitor_speed << 1) | 0xF0 }; + + st->recv(st->client, &rx, 1); + mask = rx; + for_each_set_bit(loc, &mask, 8) + iio_push_event(indio_dev, max1363_event_codes[loc], timestamp); + st->send(st->client, tx, 2); + + return IRQ_HANDLED; +} + +static int max1363_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 max1363_state *st = iio_priv(indio_dev); + int val; + int number = chan->channel; + + mutex_lock(&indio_dev->mlock); + if (dir == IIO_EV_DIR_FALLING) + val = (1 << number) & st->mask_low; + else + val = (1 << number) & st->mask_high; + mutex_unlock(&indio_dev->mlock); + + return val; +} + +static int max1363_monitor_mode_update(struct max1363_state *st, int enabled) +{ + u8 *tx_buf; + int ret, i = 3, j; + unsigned long numelements; + int len; + const long *modemask; + + if (!enabled) { + /* transition to buffered capture is not currently supported */ + st->setupbyte &= ~MAX1363_SETUP_MONITOR_SETUP; + st->configbyte &= ~MAX1363_SCAN_MASK; + st->monitor_on = false; + return max1363_write_basic_config(st); + } + + /* Ensure we are in the relevant mode */ + st->setupbyte |= MAX1363_SETUP_MONITOR_SETUP; + st->configbyte &= ~(MAX1363_CHANNEL_SEL_MASK + | MAX1363_SCAN_MASK + | MAX1363_SE_DE_MASK); + st->configbyte |= MAX1363_CONFIG_SCAN_MONITOR_MODE; + if ((st->mask_low | st->mask_high) & 0x0F) { + st->configbyte |= max1363_mode_table[s0to3].conf; + modemask = max1363_mode_table[s0to3].modemask; + } else if ((st->mask_low | st->mask_high) & 0x30) { + st->configbyte |= max1363_mode_table[d0m1to2m3].conf; + modemask = max1363_mode_table[d0m1to2m3].modemask; + } else { + st->configbyte |= max1363_mode_table[d1m0to3m2].conf; + modemask = max1363_mode_table[d1m0to3m2].modemask; + } + numelements = bitmap_weight(modemask, MAX1363_MAX_CHANNELS); + len = 3 * numelements + 3; + tx_buf = kmalloc(len, GFP_KERNEL); + if (!tx_buf) { + ret = -ENOMEM; + goto error_ret; + } + tx_buf[0] = st->configbyte; + tx_buf[1] = st->setupbyte; + tx_buf[2] = (st->monitor_speed << 1); + + /* + * So we need to do yet another bit of nefarious scan mode + * setup to match what we need. + */ + for (j = 0; j < 8; j++) + if (test_bit(j, modemask)) { + /* Establish the mode is in the scan */ + if (st->mask_low & (1 << j)) { + tx_buf[i] = (st->thresh_low[j] >> 4) & 0xFF; + tx_buf[i + 1] = (st->thresh_low[j] << 4) & 0xF0; + } else if (j < 4) { + tx_buf[i] = 0; + tx_buf[i + 1] = 0; + } else { + tx_buf[i] = 0x80; + tx_buf[i + 1] = 0; + } + if (st->mask_high & (1 << j)) { + tx_buf[i + 1] |= + (st->thresh_high[j] >> 8) & 0x0F; + tx_buf[i + 2] = st->thresh_high[j] & 0xFF; + } else if (j < 4) { + tx_buf[i + 1] |= 0x0F; + tx_buf[i + 2] = 0xFF; + } else { + tx_buf[i + 1] |= 0x07; + tx_buf[i + 2] = 0xFF; + } + i += 3; + } + + + ret = st->send(st->client, tx_buf, len); + if (ret < 0) + goto error_ret; + if (ret != len) { + ret = -EIO; + goto error_ret; + } + + /* + * Now that we hopefully have sensible thresholds in place it is + * time to turn the interrupts on. + * It is unclear from the data sheet if this should be necessary + * (i.e. whether monitor mode setup is atomic) but it appears to + * be in practice. + */ + tx_buf[0] = st->setupbyte; + tx_buf[1] = MAX1363_MON_INT_ENABLE | (st->monitor_speed << 1) | 0xF0; + ret = st->send(st->client, tx_buf, 2); + if (ret < 0) + goto error_ret; + if (ret != 2) { + ret = -EIO; + goto error_ret; + } + ret = 0; + st->monitor_on = true; +error_ret: + + kfree(tx_buf); + + return ret; +} + +/* + * To keep this manageable we always use one of 3 scan modes. + * Scan 0...3, 0-1,2-3 and 1-0,3-2 + */ + +static inline int __max1363_check_event_mask(int thismask, int checkmask) +{ + int ret = 0; + /* Is it unipolar */ + if (thismask < 4) { + if (checkmask & ~0x0F) { + ret = -EBUSY; + goto error_ret; + } + } else if (thismask < 6) { + if (checkmask & ~0x30) { + ret = -EBUSY; + goto error_ret; + } + } else if (checkmask & ~0xC0) + ret = -EBUSY; +error_ret: + return ret; +} + +static int max1363_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) +{ + int ret = 0; + struct max1363_state *st = iio_priv(indio_dev); + u16 unifiedmask; + int number = chan->channel; + + mutex_lock(&indio_dev->mlock); + unifiedmask = st->mask_low | st->mask_high; + if (dir == IIO_EV_DIR_FALLING) { + + if (state == 0) + st->mask_low &= ~(1 << number); + else { + ret = __max1363_check_event_mask((1 << number), + unifiedmask); + if (ret) + goto error_ret; + st->mask_low |= (1 << number); + } + } else { + if (state == 0) + st->mask_high &= ~(1 << number); + else { + ret = __max1363_check_event_mask((1 << number), + unifiedmask); + if (ret) + goto error_ret; + st->mask_high |= (1 << number); + } + } + + max1363_monitor_mode_update(st, !!(st->mask_high | st->mask_low)); +error_ret: + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +/* + * As with scan_elements, only certain sets of these can + * be combined. + */ +static struct attribute *max1363_event_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static struct attribute_group max1363_event_attribute_group = { + .attrs = max1363_event_attributes, + .name = "events", +}; + +static int max1363_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct max1363_state *st = iio_priv(indio_dev); + + /* + * Need to figure out the current mode based upon the requested + * scan mask in iio_dev + */ + st->current_mode = max1363_match_mode(scan_mask, st->chip_info); + if (!st->current_mode) + return -EINVAL; + max1363_set_scan_mode(st); + return 0; +} + +static const struct iio_info max1238_info = { + .read_raw = &max1363_read_raw, + .driver_module = THIS_MODULE, + .update_scan_mode = &max1363_update_scan_mode, +}; + +static const struct iio_info max1363_info = { + .read_event_value = &max1363_read_thresh, + .write_event_value = &max1363_write_thresh, + .read_event_config = &max1363_read_event_config, + .write_event_config = &max1363_write_event_config, + .read_raw = &max1363_read_raw, + .update_scan_mode = &max1363_update_scan_mode, + .driver_module = THIS_MODULE, + .event_attrs = &max1363_event_attribute_group, +}; + +/* max1363 and max1368 tested - rest from data sheet */ +static const struct max1363_chip_info max1363_chip_info_tbl[] = { + [max1361] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1361_channels, + .num_channels = ARRAY_SIZE(max1361_channels), + .info = &max1363_info, + }, + [max1362] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1361_channels, + .num_channels = ARRAY_SIZE(max1361_channels), + .info = &max1363_info, + }, + [max1363] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + .info = &max1363_info, + }, + [max1364] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + .info = &max1363_info, + }, + [max1036] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max1037] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max1038] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1038_channels, + .num_channels = ARRAY_SIZE(max1038_channels), + }, + [max1039] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1038_channels, + .num_channels = ARRAY_SIZE(max1038_channels), + }, + [max1136] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max1137] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max1138] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1138_channels, + .num_channels = ARRAY_SIZE(max1138_channels), + }, + [max1139] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1138_channels, + .num_channels = ARRAY_SIZE(max1138_channels), + }, + [max1236] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1236_channels, + .num_channels = ARRAY_SIZE(max1236_channels), + }, + [max1237] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1236_channels, + .num_channels = ARRAY_SIZE(max1236_channels), + }, + [max1238] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max1239] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11600] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max11601] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max11602] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11602_channels, + .num_channels = ARRAY_SIZE(max11602_channels), + }, + [max11603] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11602_channels, + .num_channels = ARRAY_SIZE(max11602_channels), + }, + [max11604] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1038_channels, + .num_channels = ARRAY_SIZE(max1038_channels), + }, + [max11605] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1038_channels, + .num_channels = ARRAY_SIZE(max1038_channels), + }, + [max11606] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max11607] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max11608] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11608_channels, + .num_channels = ARRAY_SIZE(max11608_channels), + }, + [max11609] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11608_channels, + .num_channels = ARRAY_SIZE(max11608_channels), + }, + [max11610] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1138_channels, + .num_channels = ARRAY_SIZE(max1138_channels), + }, + [max11611] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1138_channels, + .num_channels = ARRAY_SIZE(max1138_channels), + }, + [max11612] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + }, + [max11613] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + }, + [max11614] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11614_channels, + .num_channels = ARRAY_SIZE(max11614_channels), + }, + [max11615] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11614_channels, + .num_channels = ARRAY_SIZE(max11614_channels), + }, + [max11616] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11617] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11644] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11644_channels, + .num_channels = ARRAY_SIZE(max11644_channels), + }, + [max11645] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11644_channels, + .num_channels = ARRAY_SIZE(max11644_channels), + }, + [max11646] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11646_channels, + .num_channels = ARRAY_SIZE(max11646_channels), + }, + [max11647] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11646_channels, + .num_channels = ARRAY_SIZE(max11646_channels), + }, +}; + +static int max1363_initial_setup(struct max1363_state *st) +{ + st->setupbyte = MAX1363_SETUP_INT_CLOCK + | MAX1363_SETUP_UNIPOLAR + | MAX1363_SETUP_NORESET; + + if (st->vref) + st->setupbyte |= MAX1363_SETUP_AIN3_IS_REF_EXT_TO_REF; + else + st->setupbyte |= MAX1363_SETUP_POWER_UP_INT_REF + | MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_INT; + + /* Set scan mode writes the config anyway so wait until then */ + st->setupbyte = MAX1363_SETUP_BYTE(st->setupbyte); + st->current_mode = &max1363_mode_table[st->chip_info->default_mode]; + st->configbyte = MAX1363_CONFIG_BYTE(st->configbyte); + + return max1363_set_scan_mode(st); +} + +static int max1363_alloc_scan_masks(struct iio_dev *indio_dev) +{ + struct max1363_state *st = iio_priv(indio_dev); + unsigned long *masks; + int i; + + masks = devm_kzalloc(&indio_dev->dev, + BITS_TO_LONGS(MAX1363_MAX_CHANNELS) * sizeof(long) * + (st->chip_info->num_modes + 1), GFP_KERNEL); + if (!masks) + return -ENOMEM; + + for (i = 0; i < st->chip_info->num_modes; i++) + bitmap_copy(masks + BITS_TO_LONGS(MAX1363_MAX_CHANNELS)*i, + max1363_mode_table[st->chip_info->mode_list[i]] + .modemask, MAX1363_MAX_CHANNELS); + + indio_dev->available_scan_masks = masks; + + return 0; +} + +static irqreturn_t max1363_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct max1363_state *st = iio_priv(indio_dev); + __u8 *rxbuf; + int b_sent; + size_t d_size; + unsigned long numvals = bitmap_weight(st->current_mode->modemask, + MAX1363_MAX_CHANNELS); + + /* Ensure the timestamp is 8 byte aligned */ + if (st->chip_info->bits != 8) + d_size = numvals*2; + else + d_size = numvals; + if (indio_dev->scan_timestamp) { + d_size += sizeof(s64); + if (d_size % sizeof(s64)) + d_size += sizeof(s64) - (d_size % sizeof(s64)); + } + /* Monitor mode prevents reading. Whilst not currently implemented + * might as well have this test in here in the meantime as it does + * no harm. + */ + if (numvals == 0) + goto done; + + rxbuf = kmalloc(d_size, GFP_KERNEL); + if (rxbuf == NULL) + goto done; + if (st->chip_info->bits != 8) + b_sent = st->recv(st->client, rxbuf, numvals * 2); + else + b_sent = st->recv(st->client, rxbuf, numvals); + if (b_sent < 0) + goto done_free; + + iio_push_to_buffers_with_timestamp(indio_dev, rxbuf, iio_get_time_ns()); + +done_free: + kfree(rxbuf); +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int max1363_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct max1363_state *st; + struct iio_dev *indio_dev; + struct regulator *vref; + + indio_dev = devm_iio_device_alloc(&client->dev, + sizeof(struct max1363_state)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->dev.of_node = client->dev.of_node; + ret = iio_map_array_register(indio_dev, client->dev.platform_data); + if (ret < 0) + return ret; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get(&client->dev, "vcc"); + if (IS_ERR(st->reg)) { + ret = PTR_ERR(st->reg); + goto error_unregister_map; + } + + ret = regulator_enable(st->reg); + if (ret) + goto error_unregister_map; + + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + st->chip_info = &max1363_chip_info_tbl[id->driver_data]; + st->client = client; + + st->vref_uv = st->chip_info->int_vref_mv * 1000; + vref = devm_regulator_get_optional(&client->dev, "vref"); + if (!IS_ERR(vref)) { + int vref_uv; + + ret = regulator_enable(vref); + if (ret) + goto error_disable_reg; + st->vref = vref; + vref_uv = regulator_get_voltage(vref); + if (vref_uv <= 0) { + ret = -EINVAL; + goto error_disable_reg; + } + st->vref_uv = vref_uv; + } + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + st->send = i2c_master_send; + st->recv = i2c_master_recv; + } else if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE) + && st->chip_info->bits == 8) { + st->send = max1363_smbus_send; + st->recv = max1363_smbus_recv; + } else { + ret = -EOPNOTSUPP; + goto error_disable_reg; + } + + ret = max1363_alloc_scan_masks(indio_dev); + if (ret) + goto error_disable_reg; + + /* Establish that the iio_dev is a child of the i2c device */ + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = st->chip_info->info; + indio_dev->modes = INDIO_DIRECT_MODE; + ret = max1363_initial_setup(st); + if (ret < 0) + goto error_disable_reg; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &max1363_trigger_handler, NULL); + if (ret) + goto error_disable_reg; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, st->client->irq, + NULL, + &max1363_event_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "max1363_event", + indio_dev); + + if (ret) + goto error_uninit_buffer; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_uninit_buffer; + + return 0; + +error_uninit_buffer: + iio_triggered_buffer_cleanup(indio_dev); +error_disable_reg: + if (st->vref) + regulator_disable(st->vref); + regulator_disable(st->reg); +error_unregister_map: + iio_map_array_unregister(indio_dev); + return ret; +} + +static int max1363_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct max1363_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (st->vref) + regulator_disable(st->vref); + regulator_disable(st->reg); + iio_map_array_unregister(indio_dev); + + return 0; +} + +static const struct i2c_device_id max1363_id[] = { + { "max1361", max1361 }, + { "max1362", max1362 }, + { "max1363", max1363 }, + { "max1364", max1364 }, + { "max1036", max1036 }, + { "max1037", max1037 }, + { "max1038", max1038 }, + { "max1039", max1039 }, + { "max1136", max1136 }, + { "max1137", max1137 }, + { "max1138", max1138 }, + { "max1139", max1139 }, + { "max1236", max1236 }, + { "max1237", max1237 }, + { "max1238", max1238 }, + { "max1239", max1239 }, + { "max11600", max11600 }, + { "max11601", max11601 }, + { "max11602", max11602 }, + { "max11603", max11603 }, + { "max11604", max11604 }, + { "max11605", max11605 }, + { "max11606", max11606 }, + { "max11607", max11607 }, + { "max11608", max11608 }, + { "max11609", max11609 }, + { "max11610", max11610 }, + { "max11611", max11611 }, + { "max11612", max11612 }, + { "max11613", max11613 }, + { "max11614", max11614 }, + { "max11615", max11615 }, + { "max11616", max11616 }, + { "max11617", max11617 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max1363_id); + +static struct i2c_driver max1363_driver = { + .driver = { + .name = "max1363", + }, + .probe = max1363_probe, + .remove = max1363_remove, + .id_table = max1363_id, +}; +module_i2c_driver(max1363_driver); + +MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); +MODULE_DESCRIPTION("Maxim 1363 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mcp320x.c b/drivers/iio/adc/mcp320x.c new file mode 100644 index 000000000..8d9c9b921 --- /dev/null +++ b/drivers/iio/adc/mcp320x.c @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2013 Oskar Andero <oskar.andero@gmail.com> + * Copyright (C) 2014 Rose Technology + * Allan Bendorff Jensen <abj@rosetechnology.dk> + * Soren Andersen <san@rosetechnology.dk> + * + * Driver for following ADC chips from Microchip Technology's: + * 10 Bit converter + * MCP3001 + * MCP3002 + * MCP3004 + * MCP3008 + * ------------ + * 12 bit converter + * MCP3201 + * MCP3202 + * MCP3204 + * MCP3208 + * ------------ + * + * Datasheet can be found here: + * http://ww1.microchip.com/downloads/en/DeviceDoc/21293C.pdf mcp3001 + * http://ww1.microchip.com/downloads/en/DeviceDoc/21294E.pdf mcp3002 + * http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf mcp3004/08 + * http://ww1.microchip.com/downloads/en/DeviceDoc/21290D.pdf mcp3201 + * http://ww1.microchip.com/downloads/en/DeviceDoc/21034D.pdf mcp3202 + * http://ww1.microchip.com/downloads/en/DeviceDoc/21298c.pdf mcp3204/08 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/regulator/consumer.h> + +enum { + mcp3001, + mcp3002, + mcp3004, + mcp3008, + mcp3201, + mcp3202, + mcp3204, + mcp3208, +}; + +struct mcp320x_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int resolution; +}; + +struct mcp320x { + struct spi_device *spi; + struct spi_message msg; + struct spi_transfer transfer[2]; + + struct regulator *reg; + struct mutex lock; + const struct mcp320x_chip_info *chip_info; + + u8 tx_buf ____cacheline_aligned; + u8 rx_buf[2]; +}; + +static int mcp320x_channel_to_tx_data(int device_index, + const unsigned int channel, bool differential) +{ + int start_bit = 1; + + switch (device_index) { + case mcp3001: + case mcp3201: + return 0; + case mcp3002: + case mcp3202: + return ((start_bit << 4) | (!differential << 3) | + (channel << 2)); + case mcp3004: + case mcp3204: + case mcp3008: + case mcp3208: + return ((start_bit << 6) | (!differential << 5) | + (channel << 2)); + default: + return -EINVAL; + } +} + +static int mcp320x_adc_conversion(struct mcp320x *adc, u8 channel, + bool differential, int device_index) +{ + int ret; + + adc->rx_buf[0] = 0; + adc->rx_buf[1] = 0; + adc->tx_buf = mcp320x_channel_to_tx_data(device_index, + channel, differential); + + if (device_index != mcp3001 && device_index != mcp3201) { + ret = spi_sync(adc->spi, &adc->msg); + if (ret < 0) + return ret; + } else { + ret = spi_read(adc->spi, &adc->rx_buf, sizeof(adc->rx_buf)); + if (ret < 0) + return ret; + } + + switch (device_index) { + case mcp3001: + return (adc->rx_buf[0] << 5 | adc->rx_buf[1] >> 3); + case mcp3002: + case mcp3004: + case mcp3008: + return (adc->rx_buf[0] << 2 | adc->rx_buf[1] >> 6); + case mcp3201: + return (adc->rx_buf[0] << 7 | adc->rx_buf[1] >> 1); + case mcp3202: + case mcp3204: + case mcp3208: + return (adc->rx_buf[0] << 4 | adc->rx_buf[1] >> 4); + default: + return -EINVAL; + } +} + +static int mcp320x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct mcp320x *adc = iio_priv(indio_dev); + int ret = -EINVAL; + int device_index = 0; + + mutex_lock(&adc->lock); + + device_index = spi_get_device_id(adc->spi)->driver_data; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = mcp320x_adc_conversion(adc, channel->address, + channel->differential, device_index); + + if (ret < 0) + goto out; + + *val = ret; + ret = IIO_VAL_INT; + break; + + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(adc->reg); + if (ret < 0) + goto out; + + /* convert regulator output voltage to mV */ + *val = ret / 1000; + *val2 = adc->chip_info->resolution; + ret = IIO_VAL_FRACTIONAL_LOG2; + break; + } + +out: + mutex_unlock(&adc->lock); + + return ret; +} + +#define MCP320X_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (num), \ + .address = (num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + } + +#define MCP320X_VOLTAGE_CHANNEL_DIFF(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (num * 2), \ + .channel2 = (num * 2 + 1), \ + .address = (num * 2), \ + .differential = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + } + +static const struct iio_chan_spec mcp3201_channels[] = { + MCP320X_VOLTAGE_CHANNEL_DIFF(0), +}; + +static const struct iio_chan_spec mcp3202_channels[] = { + MCP320X_VOLTAGE_CHANNEL(0), + MCP320X_VOLTAGE_CHANNEL(1), + MCP320X_VOLTAGE_CHANNEL_DIFF(0), +}; + +static const struct iio_chan_spec mcp3204_channels[] = { + MCP320X_VOLTAGE_CHANNEL(0), + MCP320X_VOLTAGE_CHANNEL(1), + MCP320X_VOLTAGE_CHANNEL(2), + MCP320X_VOLTAGE_CHANNEL(3), + MCP320X_VOLTAGE_CHANNEL_DIFF(0), + MCP320X_VOLTAGE_CHANNEL_DIFF(1), +}; + +static const struct iio_chan_spec mcp3208_channels[] = { + MCP320X_VOLTAGE_CHANNEL(0), + MCP320X_VOLTAGE_CHANNEL(1), + MCP320X_VOLTAGE_CHANNEL(2), + MCP320X_VOLTAGE_CHANNEL(3), + MCP320X_VOLTAGE_CHANNEL(4), + MCP320X_VOLTAGE_CHANNEL(5), + MCP320X_VOLTAGE_CHANNEL(6), + MCP320X_VOLTAGE_CHANNEL(7), + MCP320X_VOLTAGE_CHANNEL_DIFF(0), + MCP320X_VOLTAGE_CHANNEL_DIFF(1), + MCP320X_VOLTAGE_CHANNEL_DIFF(2), + MCP320X_VOLTAGE_CHANNEL_DIFF(3), +}; + +static const struct iio_info mcp320x_info = { + .read_raw = mcp320x_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct mcp320x_chip_info mcp320x_chip_infos[] = { + [mcp3001] = { + .channels = mcp3201_channels, + .num_channels = ARRAY_SIZE(mcp3201_channels), + .resolution = 10 + }, + [mcp3002] = { + .channels = mcp3202_channels, + .num_channels = ARRAY_SIZE(mcp3202_channels), + .resolution = 10 + }, + [mcp3004] = { + .channels = mcp3204_channels, + .num_channels = ARRAY_SIZE(mcp3204_channels), + .resolution = 10 + }, + [mcp3008] = { + .channels = mcp3208_channels, + .num_channels = ARRAY_SIZE(mcp3208_channels), + .resolution = 10 + }, + [mcp3201] = { + .channels = mcp3201_channels, + .num_channels = ARRAY_SIZE(mcp3201_channels), + .resolution = 12 + }, + [mcp3202] = { + .channels = mcp3202_channels, + .num_channels = ARRAY_SIZE(mcp3202_channels), + .resolution = 12 + }, + [mcp3204] = { + .channels = mcp3204_channels, + .num_channels = ARRAY_SIZE(mcp3204_channels), + .resolution = 12 + }, + [mcp3208] = { + .channels = mcp3208_channels, + .num_channels = ARRAY_SIZE(mcp3208_channels), + .resolution = 12 + }, +}; + +static int mcp320x_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct mcp320x *adc; + const struct mcp320x_chip_info *chip_info; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mcp320x_info; + + chip_info = &mcp320x_chip_infos[spi_get_device_id(spi)->driver_data]; + indio_dev->channels = chip_info->channels; + indio_dev->num_channels = chip_info->num_channels; + + adc->transfer[0].tx_buf = &adc->tx_buf; + adc->transfer[0].len = sizeof(adc->tx_buf); + adc->transfer[1].rx_buf = adc->rx_buf; + adc->transfer[1].len = sizeof(adc->rx_buf); + + spi_message_init_with_transfers(&adc->msg, adc->transfer, + ARRAY_SIZE(adc->transfer)); + + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) + return PTR_ERR(adc->reg); + + ret = regulator_enable(adc->reg); + if (ret < 0) + return ret; + + mutex_init(&adc->lock); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto reg_disable; + + return 0; + +reg_disable: + regulator_disable(adc->reg); + + return ret; +} + +static int mcp320x_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct mcp320x *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(adc->reg); + + return 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id mcp320x_dt_ids[] = { + { + .compatible = "mcp3001", + .data = &mcp320x_chip_infos[mcp3001], + }, { + .compatible = "mcp3002", + .data = &mcp320x_chip_infos[mcp3002], + }, { + .compatible = "mcp3004", + .data = &mcp320x_chip_infos[mcp3004], + }, { + .compatible = "mcp3008", + .data = &mcp320x_chip_infos[mcp3008], + }, { + .compatible = "mcp3201", + .data = &mcp320x_chip_infos[mcp3201], + }, { + .compatible = "mcp3202", + .data = &mcp320x_chip_infos[mcp3202], + }, { + .compatible = "mcp3204", + .data = &mcp320x_chip_infos[mcp3204], + }, { + .compatible = "mcp3208", + .data = &mcp320x_chip_infos[mcp3208], + }, { + } +}; +MODULE_DEVICE_TABLE(of, mcp320x_dt_ids); +#endif + +static const struct spi_device_id mcp320x_id[] = { + { "mcp3001", mcp3001 }, + { "mcp3002", mcp3002 }, + { "mcp3004", mcp3004 }, + { "mcp3008", mcp3008 }, + { "mcp3201", mcp3201 }, + { "mcp3202", mcp3202 }, + { "mcp3204", mcp3204 }, + { "mcp3208", mcp3208 }, + { } +}; +MODULE_DEVICE_TABLE(spi, mcp320x_id); + +static struct spi_driver mcp320x_driver = { + .driver = { + .name = "mcp320x", + .owner = THIS_MODULE, + }, + .probe = mcp320x_probe, + .remove = mcp320x_remove, + .id_table = mcp320x_id, +}; +module_spi_driver(mcp320x_driver); + +MODULE_AUTHOR("Oskar Andero <oskar.andero@gmail.com>"); +MODULE_DESCRIPTION("Microchip Technology MCP3x01/02/04/08"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mcp3422.c b/drivers/iio/adc/mcp3422.c new file mode 100644 index 000000000..b96c63647 --- /dev/null +++ b/drivers/iio/adc/mcp3422.c @@ -0,0 +1,417 @@ +/* + * mcp3422.c - driver for the Microchip mcp3422/3/4/6/7/8 chip family + * + * Copyright (C) 2013, Angelo Compagnucci + * Author: Angelo Compagnucci <angelo.compagnucci@gmail.com> + * + * Datasheet: http://ww1.microchip.com/downloads/en/devicedoc/22088b.pdf + * http://ww1.microchip.com/downloads/en/DeviceDoc/22226a.pdf + * + * This driver exports the value of analog input voltage to sysfs, the + * voltage unit is nV. + * + * 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. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/of.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* Masks */ +#define MCP3422_CHANNEL_MASK 0x60 +#define MCP3422_PGA_MASK 0x03 +#define MCP3422_SRATE_MASK 0x0C +#define MCP3422_SRATE_240 0x0 +#define MCP3422_SRATE_60 0x1 +#define MCP3422_SRATE_15 0x2 +#define MCP3422_SRATE_3 0x3 +#define MCP3422_PGA_1 0 +#define MCP3422_PGA_2 1 +#define MCP3422_PGA_4 2 +#define MCP3422_PGA_8 3 +#define MCP3422_CONT_SAMPLING 0x10 + +#define MCP3422_CHANNEL(config) (((config) & MCP3422_CHANNEL_MASK) >> 5) +#define MCP3422_PGA(config) ((config) & MCP3422_PGA_MASK) +#define MCP3422_SAMPLE_RATE(config) (((config) & MCP3422_SRATE_MASK) >> 2) + +#define MCP3422_CHANNEL_VALUE(value) (((value) << 5) & MCP3422_CHANNEL_MASK) +#define MCP3422_PGA_VALUE(value) ((value) & MCP3422_PGA_MASK) +#define MCP3422_SAMPLE_RATE_VALUE(value) ((value << 2) & MCP3422_SRATE_MASK) + +#define MCP3422_CHAN(_index) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + } + +static const int mcp3422_scales[4][4] = { + { 1000000, 500000, 250000, 125000 }, + { 250000 , 125000, 62500 , 31250 }, + { 62500 , 31250 , 15625 , 7812 }, + { 15625 , 7812 , 3906 , 1953 } }; + +/* Constant msleep times for data acquisitions */ +static const int mcp3422_read_times[4] = { + [MCP3422_SRATE_240] = 1000 / 240, + [MCP3422_SRATE_60] = 1000 / 60, + [MCP3422_SRATE_15] = 1000 / 15, + [MCP3422_SRATE_3] = 1000 / 3 }; + +/* sample rates to integer conversion table */ +static const int mcp3422_sample_rates[4] = { + [MCP3422_SRATE_240] = 240, + [MCP3422_SRATE_60] = 60, + [MCP3422_SRATE_15] = 15, + [MCP3422_SRATE_3] = 3 }; + +/* sample rates to sign extension table */ +static const int mcp3422_sign_extend[4] = { + [MCP3422_SRATE_240] = 11, + [MCP3422_SRATE_60] = 13, + [MCP3422_SRATE_15] = 15, + [MCP3422_SRATE_3] = 17 }; + +/* Client data (each client gets its own) */ +struct mcp3422 { + struct i2c_client *i2c; + u8 id; + u8 config; + u8 pga[4]; + struct mutex lock; +}; + +static int mcp3422_update_config(struct mcp3422 *adc, u8 newconfig) +{ + int ret; + + mutex_lock(&adc->lock); + + ret = i2c_master_send(adc->i2c, &newconfig, 1); + if (ret > 0) { + adc->config = newconfig; + ret = 0; + } + + mutex_unlock(&adc->lock); + + return ret; +} + +static int mcp3422_read(struct mcp3422 *adc, int *value, u8 *config) +{ + int ret = 0; + u8 sample_rate = MCP3422_SAMPLE_RATE(adc->config); + u8 buf[4] = {0, 0, 0, 0}; + u32 temp; + + if (sample_rate == MCP3422_SRATE_3) { + ret = i2c_master_recv(adc->i2c, buf, 4); + temp = buf[0] << 16 | buf[1] << 8 | buf[2]; + *config = buf[3]; + } else { + ret = i2c_master_recv(adc->i2c, buf, 3); + temp = buf[0] << 8 | buf[1]; + *config = buf[2]; + } + + *value = sign_extend32(temp, mcp3422_sign_extend[sample_rate]); + + return ret; +} + +static int mcp3422_read_channel(struct mcp3422 *adc, + struct iio_chan_spec const *channel, int *value) +{ + int ret; + u8 config; + u8 req_channel = channel->channel; + + if (req_channel != MCP3422_CHANNEL(adc->config)) { + config = adc->config; + config &= ~MCP3422_CHANNEL_MASK; + config |= MCP3422_CHANNEL_VALUE(req_channel); + config &= ~MCP3422_PGA_MASK; + config |= MCP3422_PGA_VALUE(adc->pga[req_channel]); + ret = mcp3422_update_config(adc, config); + if (ret < 0) + return ret; + msleep(mcp3422_read_times[MCP3422_SAMPLE_RATE(adc->config)]); + } + + return mcp3422_read(adc, value, &config); +} + +static int mcp3422_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int *val1, + int *val2, long mask) +{ + struct mcp3422 *adc = iio_priv(iio); + int err; + + u8 sample_rate = MCP3422_SAMPLE_RATE(adc->config); + u8 pga = MCP3422_PGA(adc->config); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = mcp3422_read_channel(adc, channel, val1); + if (err < 0) + return -EINVAL; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + + *val1 = 0; + *val2 = mcp3422_scales[sample_rate][pga]; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val1 = mcp3422_sample_rates[MCP3422_SAMPLE_RATE(adc->config)]; + return IIO_VAL_INT; + + default: + break; + } + + return -EINVAL; +} + +static int mcp3422_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int val1, + int val2, long mask) +{ + struct mcp3422 *adc = iio_priv(iio); + u8 temp; + u8 config = adc->config; + u8 req_channel = channel->channel; + u8 sample_rate = MCP3422_SAMPLE_RATE(config); + u8 i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val1 != 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mcp3422_scales[0]); i++) { + if (val2 == mcp3422_scales[sample_rate][i]) { + adc->pga[req_channel] = i; + + config &= ~MCP3422_CHANNEL_MASK; + config |= MCP3422_CHANNEL_VALUE(req_channel); + config &= ~MCP3422_PGA_MASK; + config |= MCP3422_PGA_VALUE(adc->pga[req_channel]); + + return mcp3422_update_config(adc, config); + } + } + return -EINVAL; + + case IIO_CHAN_INFO_SAMP_FREQ: + switch (val1) { + case 240: + temp = MCP3422_SRATE_240; + break; + case 60: + temp = MCP3422_SRATE_60; + break; + case 15: + temp = MCP3422_SRATE_15; + break; + case 3: + if (adc->id > 4) + return -EINVAL; + temp = MCP3422_SRATE_3; + break; + default: + return -EINVAL; + } + + config &= ~MCP3422_CHANNEL_MASK; + config |= MCP3422_CHANNEL_VALUE(req_channel); + config &= ~MCP3422_SRATE_MASK; + config |= MCP3422_SAMPLE_RATE_VALUE(temp); + + return mcp3422_update_config(adc, config); + + default: + break; + } + + return -EINVAL; +} + +static int mcp3422_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static ssize_t mcp3422_show_samp_freqs(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mcp3422 *adc = iio_priv(dev_to_iio_dev(dev)); + + if (adc->id > 4) + return sprintf(buf, "240 60 15\n"); + + return sprintf(buf, "240 60 15 3\n"); +} + +static ssize_t mcp3422_show_scales(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mcp3422 *adc = iio_priv(dev_to_iio_dev(dev)); + u8 sample_rate = MCP3422_SAMPLE_RATE(adc->config); + + return sprintf(buf, "0.%09u 0.%09u 0.%09u 0.%09u\n", + mcp3422_scales[sample_rate][0], + mcp3422_scales[sample_rate][1], + mcp3422_scales[sample_rate][2], + mcp3422_scales[sample_rate][3]); +} + +static IIO_DEVICE_ATTR(sampling_frequency_available, S_IRUGO, + mcp3422_show_samp_freqs, NULL, 0); +static IIO_DEVICE_ATTR(in_voltage_scale_available, S_IRUGO, + mcp3422_show_scales, NULL, 0); + +static struct attribute *mcp3422_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group mcp3422_attribute_group = { + .attrs = mcp3422_attributes, +}; + +static const struct iio_chan_spec mcp3422_channels[] = { + MCP3422_CHAN(0), + MCP3422_CHAN(1), +}; + +static const struct iio_chan_spec mcp3424_channels[] = { + MCP3422_CHAN(0), + MCP3422_CHAN(1), + MCP3422_CHAN(2), + MCP3422_CHAN(3), +}; + +static const struct iio_info mcp3422_info = { + .read_raw = mcp3422_read_raw, + .write_raw = mcp3422_write_raw, + .write_raw_get_fmt = mcp3422_write_raw_get_fmt, + .attrs = &mcp3422_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int mcp3422_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct mcp3422 *adc; + int err; + u8 config; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->i2c = client; + adc->id = (u8)(id->driver_data); + + mutex_init(&adc->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mcp3422_info; + + switch (adc->id) { + case 2: + case 3: + case 6: + case 7: + indio_dev->channels = mcp3422_channels; + indio_dev->num_channels = ARRAY_SIZE(mcp3422_channels); + break; + case 4: + case 8: + indio_dev->channels = mcp3424_channels; + indio_dev->num_channels = ARRAY_SIZE(mcp3424_channels); + break; + } + + /* meaningful default configuration */ + config = (MCP3422_CONT_SAMPLING + | MCP3422_CHANNEL_VALUE(1) + | MCP3422_PGA_VALUE(MCP3422_PGA_1) + | MCP3422_SAMPLE_RATE_VALUE(MCP3422_SRATE_240)); + mcp3422_update_config(adc, config); + + err = devm_iio_device_register(&client->dev, indio_dev); + if (err < 0) + return err; + + i2c_set_clientdata(client, indio_dev); + + return 0; +} + +static const struct i2c_device_id mcp3422_id[] = { + { "mcp3422", 2 }, + { "mcp3423", 3 }, + { "mcp3424", 4 }, + { "mcp3426", 6 }, + { "mcp3427", 7 }, + { "mcp3428", 8 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp3422_id); + +#ifdef CONFIG_OF +static const struct of_device_id mcp3422_of_match[] = { + { .compatible = "mcp3422" }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp3422_of_match); +#endif + +static struct i2c_driver mcp3422_driver = { + .driver = { + .name = "mcp3422", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(mcp3422_of_match), + }, + .probe = mcp3422_probe, + .id_table = mcp3422_id, +}; +module_i2c_driver(mcp3422_driver); + +MODULE_AUTHOR("Angelo Compagnucci <angelo.compagnucci@gmail.com>"); +MODULE_DESCRIPTION("Microchip mcp3422/3/4/6/7/8 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/men_z188_adc.c b/drivers/iio/adc/men_z188_adc.c new file mode 100644 index 000000000..d095efe1b --- /dev/null +++ b/drivers/iio/adc/men_z188_adc.c @@ -0,0 +1,173 @@ +/* + * MEN 16z188 Analog to Digial Converter + * + * Copyright (C) 2014 MEN Mikroelektronik GmbH (www.men.de) + * Author: Johannes Thumshirn <johannes.thumshirn@men.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mcb.h> +#include <linux/io.h> +#include <linux/iio/iio.h> + +#define Z188_ADC_MAX_CHAN 8 +#define Z188_ADC_GAIN 0x0700000 +#define Z188_MODE_VOLTAGE BIT(27) +#define Z188_CFG_AUTO 0x1 +#define Z188_CTRL_REG 0x40 + +#define ADC_DATA(x) (((x) >> 2) & 0x7ffffc) +#define ADC_OVR(x) ((x) & 0x1) + +struct z188_adc { + struct resource *mem; + void __iomem *base; +}; + +#define Z188_ADC_CHANNEL(idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec z188_adc_iio_channels[] = { + Z188_ADC_CHANNEL(0), + Z188_ADC_CHANNEL(1), + Z188_ADC_CHANNEL(2), + Z188_ADC_CHANNEL(3), + Z188_ADC_CHANNEL(4), + Z188_ADC_CHANNEL(5), + Z188_ADC_CHANNEL(6), + Z188_ADC_CHANNEL(7), +}; + +static int z188_iio_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long info) +{ + struct z188_adc *adc = iio_priv(iio_dev); + int ret; + u16 tmp; + + switch (info) { + case IIO_CHAN_INFO_RAW: + tmp = readw(adc->base + chan->channel * 4); + + if (ADC_OVR(tmp)) { + dev_info(&iio_dev->dev, + "Oversampling error on ADC channel %d\n", + chan->channel); + return -EIO; + } + *val = ADC_DATA(tmp); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static struct iio_info z188_adc_info = { + .read_raw = &z188_iio_read_raw, + .driver_module = THIS_MODULE, +}; + +static void men_z188_config_channels(void __iomem *addr) +{ + int i; + u32 cfg; + u32 ctl; + + ctl = readl(addr + Z188_CTRL_REG); + ctl |= Z188_CFG_AUTO; + writel(ctl, addr + Z188_CTRL_REG); + + for (i = 0; i < Z188_ADC_MAX_CHAN; i++) { + cfg = readl(addr + i); + cfg &= ~Z188_ADC_GAIN; + cfg |= Z188_MODE_VOLTAGE; + writel(cfg, addr + i); + } +} + +static int men_z188_probe(struct mcb_device *dev, + const struct mcb_device_id *id) +{ + struct z188_adc *adc; + struct iio_dev *indio_dev; + struct resource *mem; + + indio_dev = devm_iio_device_alloc(&dev->dev, sizeof(struct z188_adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + indio_dev->name = "z188-adc"; + indio_dev->dev.parent = &dev->dev; + indio_dev->info = &z188_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = z188_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(z188_adc_iio_channels); + + mem = mcb_request_mem(dev, "z188-adc"); + if (IS_ERR(mem)) + return PTR_ERR(mem); + + adc->base = ioremap(mem->start, resource_size(mem)); + if (adc->base == NULL) + goto err; + + men_z188_config_channels(adc->base); + + adc->mem = mem; + mcb_set_drvdata(dev, indio_dev); + + return iio_device_register(indio_dev); + +err: + mcb_release_mem(mem); + return -ENXIO; +} + +static void men_z188_remove(struct mcb_device *dev) +{ + struct iio_dev *indio_dev = mcb_get_drvdata(dev); + struct z188_adc *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iounmap(adc->base); + mcb_release_mem(adc->mem); +} + +static const struct mcb_device_id men_z188_ids[] = { + { .device = 0xbc }, + { } +}; +MODULE_DEVICE_TABLE(mcb, men_z188_ids); + +static struct mcb_driver men_z188_driver = { + .driver = { + .name = "z188-adc", + .owner = THIS_MODULE, + }, + .probe = men_z188_probe, + .remove = men_z188_remove, + .id_table = men_z188_ids, +}; +module_mcb_driver(men_z188_driver); + +MODULE_AUTHOR("Johannes Thumshirn <johannes.thumshirn@men.de>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("IIO ADC driver for MEN 16z188 ADC Core"); +MODULE_ALIAS("mcb:16z188"); diff --git a/drivers/iio/adc/nau7802.c b/drivers/iio/adc/nau7802.c new file mode 100644 index 000000000..e525aa647 --- /dev/null +++ b/drivers/iio/adc/nau7802.c @@ -0,0 +1,582 @@ +/* + * Driver for the Nuvoton NAU7802 ADC + * + * Copyright 2013 Free Electrons + * + * Licensed under the GPLv2 or later. + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/wait.h> +#include <linux/log2.h> +#include <linux/of.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define NAU7802_REG_PUCTRL 0x00 +#define NAU7802_PUCTRL_RR(x) (x << 0) +#define NAU7802_PUCTRL_RR_BIT NAU7802_PUCTRL_RR(1) +#define NAU7802_PUCTRL_PUD(x) (x << 1) +#define NAU7802_PUCTRL_PUD_BIT NAU7802_PUCTRL_PUD(1) +#define NAU7802_PUCTRL_PUA(x) (x << 2) +#define NAU7802_PUCTRL_PUA_BIT NAU7802_PUCTRL_PUA(1) +#define NAU7802_PUCTRL_PUR(x) (x << 3) +#define NAU7802_PUCTRL_PUR_BIT NAU7802_PUCTRL_PUR(1) +#define NAU7802_PUCTRL_CS(x) (x << 4) +#define NAU7802_PUCTRL_CS_BIT NAU7802_PUCTRL_CS(1) +#define NAU7802_PUCTRL_CR(x) (x << 5) +#define NAU7802_PUCTRL_CR_BIT NAU7802_PUCTRL_CR(1) +#define NAU7802_PUCTRL_AVDDS(x) (x << 7) +#define NAU7802_PUCTRL_AVDDS_BIT NAU7802_PUCTRL_AVDDS(1) +#define NAU7802_REG_CTRL1 0x01 +#define NAU7802_CTRL1_VLDO(x) (x << 3) +#define NAU7802_CTRL1_GAINS(x) (x) +#define NAU7802_CTRL1_GAINS_BITS 0x07 +#define NAU7802_REG_CTRL2 0x02 +#define NAU7802_CTRL2_CHS(x) (x << 7) +#define NAU7802_CTRL2_CRS(x) (x << 4) +#define NAU7802_SAMP_FREQ_320 0x07 +#define NAU7802_CTRL2_CHS_BIT NAU7802_CTRL2_CHS(1) +#define NAU7802_REG_ADC_B2 0x12 +#define NAU7802_REG_ADC_B1 0x13 +#define NAU7802_REG_ADC_B0 0x14 +#define NAU7802_REG_ADC_CTRL 0x15 + +#define NAU7802_MIN_CONVERSIONS 6 + +struct nau7802_state { + struct i2c_client *client; + s32 last_value; + struct mutex lock; + struct mutex data_lock; + u32 vref_mv; + u32 conversion_count; + u32 min_conversions; + u8 sample_rate; + u32 scale_avail[8]; + struct completion value_ok; +}; + +#define NAU7802_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (chan), \ + .scan_index = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) \ +} + +static const struct iio_chan_spec nau7802_chan_array[] = { + NAU7802_CHANNEL(0), + NAU7802_CHANNEL(1), +}; + +static const u16 nau7802_sample_freq_avail[] = {10, 20, 40, 80, + 10, 10, 10, 320}; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("10 40 80 320"); + +static struct attribute *nau7802_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group nau7802_attribute_group = { + .attrs = nau7802_attributes, +}; + +static int nau7802_set_gain(struct nau7802_state *st, int gain) +{ + int ret; + + mutex_lock(&st->lock); + st->conversion_count = 0; + + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_CTRL1); + if (ret < 0) + goto nau7802_sysfs_set_gain_out; + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_CTRL1, + (ret & (~NAU7802_CTRL1_GAINS_BITS)) | + gain); + +nau7802_sysfs_set_gain_out: + mutex_unlock(&st->lock); + + return ret; +} + +static int nau7802_read_conversion(struct nau7802_state *st) +{ + int data; + + mutex_lock(&st->data_lock); + data = i2c_smbus_read_byte_data(st->client, NAU7802_REG_ADC_B2); + if (data < 0) + goto nau7802_read_conversion_out; + st->last_value = data << 16; + + data = i2c_smbus_read_byte_data(st->client, NAU7802_REG_ADC_B1); + if (data < 0) + goto nau7802_read_conversion_out; + st->last_value |= data << 8; + + data = i2c_smbus_read_byte_data(st->client, NAU7802_REG_ADC_B0); + if (data < 0) + goto nau7802_read_conversion_out; + st->last_value |= data; + + st->last_value = sign_extend32(st->last_value, 23); + +nau7802_read_conversion_out: + mutex_unlock(&st->data_lock); + + return data; +} + +/* + * Conversions are synchronised on the rising edge of NAU7802_PUCTRL_CS_BIT + */ +static int nau7802_sync(struct nau7802_state *st) +{ + int ret; + + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_PUCTRL); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_PUCTRL, + ret | NAU7802_PUCTRL_CS_BIT); + + return ret; +} + +static irqreturn_t nau7802_eoc_trigger(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct nau7802_state *st = iio_priv(indio_dev); + int status; + + status = i2c_smbus_read_byte_data(st->client, NAU7802_REG_PUCTRL); + if (status < 0) + return IRQ_HANDLED; + + if (!(status & NAU7802_PUCTRL_CR_BIT)) + return IRQ_NONE; + + if (nau7802_read_conversion(st) < 0) + return IRQ_HANDLED; + + /* + * Because there is actually only one ADC for both channels, we have to + * wait for enough conversions to happen before getting a significant + * value when changing channels and the values are far apart. + */ + if (st->conversion_count < NAU7802_MIN_CONVERSIONS) + st->conversion_count++; + if (st->conversion_count >= NAU7802_MIN_CONVERSIONS) + complete_all(&st->value_ok); + + return IRQ_HANDLED; +} + +static int nau7802_read_irq(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct nau7802_state *st = iio_priv(indio_dev); + int ret; + + reinit_completion(&st->value_ok); + enable_irq(st->client->irq); + + nau7802_sync(st); + + /* read registers to ensure we flush everything */ + ret = nau7802_read_conversion(st); + if (ret < 0) + goto read_chan_info_failure; + + /* Wait for a conversion to finish */ + ret = wait_for_completion_interruptible_timeout(&st->value_ok, + msecs_to_jiffies(1000)); + if (ret == 0) + ret = -ETIMEDOUT; + + if (ret < 0) + goto read_chan_info_failure; + + disable_irq(st->client->irq); + + *val = st->last_value; + + return IIO_VAL_INT; + +read_chan_info_failure: + disable_irq(st->client->irq); + + return ret; +} + +static int nau7802_read_poll(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct nau7802_state *st = iio_priv(indio_dev); + int ret; + + nau7802_sync(st); + + /* read registers to ensure we flush everything */ + ret = nau7802_read_conversion(st); + if (ret < 0) + return ret; + + /* + * Because there is actually only one ADC for both channels, we have to + * wait for enough conversions to happen before getting a significant + * value when changing channels and the values are far appart. + */ + do { + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_PUCTRL); + if (ret < 0) + return ret; + + while (!(ret & NAU7802_PUCTRL_CR_BIT)) { + if (st->sample_rate != NAU7802_SAMP_FREQ_320) + msleep(20); + else + mdelay(4); + ret = i2c_smbus_read_byte_data(st->client, + NAU7802_REG_PUCTRL); + if (ret < 0) + return ret; + } + + ret = nau7802_read_conversion(st); + if (ret < 0) + return ret; + if (st->conversion_count < NAU7802_MIN_CONVERSIONS) + st->conversion_count++; + } while (st->conversion_count < NAU7802_MIN_CONVERSIONS); + + *val = st->last_value; + + return IIO_VAL_INT; +} + +static int nau7802_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct nau7802_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + /* + * Select the channel to use + * - Channel 1 is value 0 in the CHS register + * - Channel 2 is value 1 in the CHS register + */ + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_CTRL2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + + if (((ret & NAU7802_CTRL2_CHS_BIT) && !chan->channel) || + (!(ret & NAU7802_CTRL2_CHS_BIT) && + chan->channel)) { + st->conversion_count = 0; + ret = i2c_smbus_write_byte_data(st->client, + NAU7802_REG_CTRL2, + NAU7802_CTRL2_CHS(chan->channel) | + NAU7802_CTRL2_CRS(st->sample_rate)); + + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + } + + if (st->client->irq) + ret = nau7802_read_irq(indio_dev, chan, val); + else + ret = nau7802_read_poll(indio_dev, chan, val); + + mutex_unlock(&st->lock); + return ret; + + case IIO_CHAN_INFO_SCALE: + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_CTRL1); + if (ret < 0) + return ret; + + /* + * We have 24 bits of signed data, that means 23 bits of data + * plus the sign bit + */ + *val = st->vref_mv; + *val2 = 23 + (ret & NAU7802_CTRL1_GAINS_BITS); + + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = nau7802_sample_freq_avail[st->sample_rate]; + *val2 = 0; + return IIO_VAL_INT; + + default: + break; + } + + return -EINVAL; +} + +static int nau7802_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct nau7802_state *st = iio_priv(indio_dev); + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + if (val2 == st->scale_avail[i]) + return nau7802_set_gain(st, i); + + break; + + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < ARRAY_SIZE(nau7802_sample_freq_avail); i++) + if (val == nau7802_sample_freq_avail[i]) { + mutex_lock(&st->lock); + st->sample_rate = i; + st->conversion_count = 0; + ret = i2c_smbus_write_byte_data(st->client, + NAU7802_REG_CTRL2, + NAU7802_CTRL2_CRS(st->sample_rate)); + mutex_unlock(&st->lock); + return ret; + } + + break; + + default: + break; + } + + return -EINVAL; +} + +static int nau7802_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static const struct iio_info nau7802_info = { + .driver_module = THIS_MODULE, + .read_raw = &nau7802_read_raw, + .write_raw = &nau7802_write_raw, + .write_raw_get_fmt = nau7802_write_raw_get_fmt, + .attrs = &nau7802_attribute_group, +}; + +static int nau7802_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct nau7802_state *st; + struct device_node *np = client->dev.of_node; + int i, ret; + u8 data; + u32 tmp = 0; + + if (!client->dev.of_node) { + dev_err(&client->dev, "No device tree node available.\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + + indio_dev->dev.parent = &client->dev; + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &nau7802_info; + + st->client = client; + + /* Reset the device */ + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_PUCTRL, + NAU7802_PUCTRL_RR_BIT); + if (ret < 0) + return ret; + + /* Enter normal operation mode */ + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_PUCTRL, + NAU7802_PUCTRL_PUD_BIT); + if (ret < 0) + return ret; + + /* + * After about 200 usecs, the device should be ready and then + * the Power Up bit will be set to 1. If not, wait for it. + */ + udelay(210); + ret = i2c_smbus_read_byte_data(st->client, NAU7802_REG_PUCTRL); + if (ret < 0) + return ret; + if (!(ret & NAU7802_PUCTRL_PUR_BIT)) + return ret; + + of_property_read_u32(np, "nuvoton,vldo", &tmp); + st->vref_mv = tmp; + + data = NAU7802_PUCTRL_PUD_BIT | NAU7802_PUCTRL_PUA_BIT | + NAU7802_PUCTRL_CS_BIT; + if (tmp >= 2400) + data |= NAU7802_PUCTRL_AVDDS_BIT; + + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_PUCTRL, data); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_ADC_CTRL, 0x30); + if (ret < 0) + return ret; + + if (tmp >= 2400) { + data = NAU7802_CTRL1_VLDO((4500 - tmp) / 300); + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_CTRL1, + data); + if (ret < 0) + return ret; + } + + /* Populate available ADC input ranges */ + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + st->scale_avail[i] = (((u64)st->vref_mv) * 1000000000ULL) + >> (23 + i); + + init_completion(&st->value_ok); + + /* + * The ADC fires continuously and we can't do anything about + * it. So we need to have the IRQ disabled by default, and we + * will enable them back when we will need them.. + */ + if (client->irq) { + ret = request_threaded_irq(client->irq, + NULL, + nau7802_eoc_trigger, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + client->dev.driver->name, + indio_dev); + if (ret) { + /* + * What may happen here is that our IRQ controller is + * not able to get level interrupt but this is required + * by this ADC as when going over 40 sample per second, + * the interrupt line may stay high between conversions. + * So, we continue no matter what but we switch to + * polling mode. + */ + dev_info(&client->dev, + "Failed to allocate IRQ, using polling mode\n"); + client->irq = 0; + } else + disable_irq(client->irq); + } + + if (!client->irq) { + /* + * We are polling, use the fastest sample rate by + * default + */ + st->sample_rate = NAU7802_SAMP_FREQ_320; + ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_CTRL2, + NAU7802_CTRL2_CRS(st->sample_rate)); + if (ret) + goto error_free_irq; + } + + /* Setup the ADC channels available on the board */ + indio_dev->num_channels = ARRAY_SIZE(nau7802_chan_array); + indio_dev->channels = nau7802_chan_array; + + mutex_init(&st->lock); + mutex_init(&st->data_lock); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "Couldn't register the device.\n"); + goto error_device_register; + } + + return 0; + +error_device_register: + mutex_destroy(&st->lock); + mutex_destroy(&st->data_lock); +error_free_irq: + if (client->irq) + free_irq(client->irq, indio_dev); + + return ret; +} + +static int nau7802_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct nau7802_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + mutex_destroy(&st->lock); + mutex_destroy(&st->data_lock); + if (client->irq) + free_irq(client->irq, indio_dev); + + return 0; +} + +static const struct i2c_device_id nau7802_i2c_id[] = { + { "nau7802", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nau7802_i2c_id); + +static const struct of_device_id nau7802_dt_ids[] = { + { .compatible = "nuvoton,nau7802" }, + {}, +}; +MODULE_DEVICE_TABLE(of, nau7802_dt_ids); + +static struct i2c_driver nau7802_driver = { + .probe = nau7802_probe, + .remove = nau7802_remove, + .id_table = nau7802_i2c_id, + .driver = { + .name = "nau7802", + .of_match_table = nau7802_dt_ids, + }, +}; + +module_i2c_driver(nau7802_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Nuvoton NAU7802 ADC Driver"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@free-electrons.com>"); diff --git a/drivers/iio/adc/qcom-spmi-iadc.c b/drivers/iio/adc/qcom-spmi-iadc.c new file mode 100644 index 000000000..fabd24edc --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-iadc.c @@ -0,0 +1,596 @@ +/* + * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/bitops.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/iio/iio.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +/* IADC register and bit definition */ +#define IADC_REVISION2 0x1 +#define IADC_REVISION2_SUPPORTED_IADC 1 + +#define IADC_PERPH_TYPE 0x4 +#define IADC_PERPH_TYPE_ADC 8 + +#define IADC_PERPH_SUBTYPE 0x5 +#define IADC_PERPH_SUBTYPE_IADC 3 + +#define IADC_STATUS1 0x8 +#define IADC_STATUS1_OP_MODE 4 +#define IADC_STATUS1_REQ_STS BIT(1) +#define IADC_STATUS1_EOC BIT(0) +#define IADC_STATUS1_REQ_STS_EOC_MASK 0x3 + +#define IADC_MODE_CTL 0x40 +#define IADC_OP_MODE_SHIFT 3 +#define IADC_OP_MODE_NORMAL 0 +#define IADC_TRIM_EN BIT(0) + +#define IADC_EN_CTL1 0x46 +#define IADC_EN_CTL1_SET BIT(7) + +#define IADC_CH_SEL_CTL 0x48 + +#define IADC_DIG_PARAM 0x50 +#define IADC_DIG_DEC_RATIO_SEL_SHIFT 2 + +#define IADC_HW_SETTLE_DELAY 0x51 + +#define IADC_CONV_REQ 0x52 +#define IADC_CONV_REQ_SET BIT(7) + +#define IADC_FAST_AVG_CTL 0x5a +#define IADC_FAST_AVG_EN 0x5b +#define IADC_FAST_AVG_EN_SET BIT(7) + +#define IADC_PERH_RESET_CTL3 0xda +#define IADC_FOLLOW_WARM_RB BIT(2) + +#define IADC_DATA 0x60 /* 16 bits */ + +#define IADC_SEC_ACCESS 0xd0 +#define IADC_SEC_ACCESS_DATA 0xa5 + +#define IADC_NOMINAL_RSENSE 0xf4 +#define IADC_NOMINAL_RSENSE_SIGN_MASK BIT(7) + +#define IADC_REF_GAIN_MICRO_VOLTS 17857 + +#define IADC_INT_RSENSE_DEVIATION 15625 /* nano Ohms per bit */ + +#define IADC_INT_RSENSE_IDEAL_VALUE 10000 /* micro Ohms */ +#define IADC_INT_RSENSE_DEFAULT_VALUE 7800 /* micro Ohms */ +#define IADC_INT_RSENSE_DEFAULT_GF 9000 /* micro Ohms */ +#define IADC_INT_RSENSE_DEFAULT_SMIC 9700 /* micro Ohms */ + +#define IADC_CONV_TIME_MIN_US 2000 +#define IADC_CONV_TIME_MAX_US 2100 + +#define IADC_DEF_PRESCALING 0 /* 1:1 */ +#define IADC_DEF_DECIMATION 0 /* 512 */ +#define IADC_DEF_HW_SETTLE_TIME 0 /* 0 us */ +#define IADC_DEF_AVG_SAMPLES 0 /* 1 sample */ + +/* IADC channel list */ +#define IADC_INT_RSENSE 0 +#define IADC_EXT_RSENSE 1 +#define IADC_GAIN_17P857MV 3 +#define IADC_EXT_OFFSET_CSP_CSN 5 +#define IADC_INT_OFFSET_CSP2_CSN2 6 + +/** + * struct iadc_chip - IADC Current ADC device structure. + * @regmap: regmap for register read/write. + * @dev: This device pointer. + * @base: base offset for the ADC peripheral. + * @rsense: Values of the internal and external sense resister in micro Ohms. + * @poll_eoc: Poll for end of conversion instead of waiting for IRQ. + * @offset: Raw offset values for the internal and external channels. + * @gain: Raw gain of the channels. + * @lock: ADC lock for access to the peripheral. + * @complete: ADC notification after end of conversion interrupt is received. + */ +struct iadc_chip { + struct regmap *regmap; + struct device *dev; + u16 base; + bool poll_eoc; + u32 rsense[2]; + u16 offset[2]; + u16 gain; + struct mutex lock; + struct completion complete; +}; + +static int iadc_read(struct iadc_chip *iadc, u16 offset, u8 *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(iadc->regmap, iadc->base + offset, &val); + if (ret < 0) + return ret; + + *data = val; + return 0; +} + +static int iadc_write(struct iadc_chip *iadc, u16 offset, u8 data) +{ + return regmap_write(iadc->regmap, iadc->base + offset, data); +} + +static int iadc_reset(struct iadc_chip *iadc) +{ + u8 data; + int ret; + + ret = iadc_write(iadc, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); + if (ret < 0) + return ret; + + ret = iadc_read(iadc, IADC_PERH_RESET_CTL3, &data); + if (ret < 0) + return ret; + + ret = iadc_write(iadc, IADC_SEC_ACCESS, IADC_SEC_ACCESS_DATA); + if (ret < 0) + return ret; + + data |= IADC_FOLLOW_WARM_RB; + + return iadc_write(iadc, IADC_PERH_RESET_CTL3, data); +} + +static int iadc_set_state(struct iadc_chip *iadc, bool state) +{ + return iadc_write(iadc, IADC_EN_CTL1, state ? IADC_EN_CTL1_SET : 0); +} + +static void iadc_status_show(struct iadc_chip *iadc) +{ + u8 mode, sta1, chan, dig, en, req; + int ret; + + ret = iadc_read(iadc, IADC_MODE_CTL, &mode); + if (ret < 0) + return; + + ret = iadc_read(iadc, IADC_DIG_PARAM, &dig); + if (ret < 0) + return; + + ret = iadc_read(iadc, IADC_CH_SEL_CTL, &chan); + if (ret < 0) + return; + + ret = iadc_read(iadc, IADC_CONV_REQ, &req); + if (ret < 0) + return; + + ret = iadc_read(iadc, IADC_STATUS1, &sta1); + if (ret < 0) + return; + + ret = iadc_read(iadc, IADC_EN_CTL1, &en); + if (ret < 0) + return; + + dev_err(iadc->dev, + "mode:%02x en:%02x chan:%02x dig:%02x req:%02x sta1:%02x\n", + mode, en, chan, dig, req, sta1); +} + +static int iadc_configure(struct iadc_chip *iadc, int channel) +{ + u8 decim, mode; + int ret; + + /* Mode selection */ + mode = (IADC_OP_MODE_NORMAL << IADC_OP_MODE_SHIFT) | IADC_TRIM_EN; + ret = iadc_write(iadc, IADC_MODE_CTL, mode); + if (ret < 0) + return ret; + + /* Channel selection */ + ret = iadc_write(iadc, IADC_CH_SEL_CTL, channel); + if (ret < 0) + return ret; + + /* Digital parameter setup */ + decim = IADC_DEF_DECIMATION << IADC_DIG_DEC_RATIO_SEL_SHIFT; + ret = iadc_write(iadc, IADC_DIG_PARAM, decim); + if (ret < 0) + return ret; + + /* HW settle time delay */ + ret = iadc_write(iadc, IADC_HW_SETTLE_DELAY, IADC_DEF_HW_SETTLE_TIME); + if (ret < 0) + return ret; + + ret = iadc_write(iadc, IADC_FAST_AVG_CTL, IADC_DEF_AVG_SAMPLES); + if (ret < 0) + return ret; + + if (IADC_DEF_AVG_SAMPLES) + ret = iadc_write(iadc, IADC_FAST_AVG_EN, IADC_FAST_AVG_EN_SET); + else + ret = iadc_write(iadc, IADC_FAST_AVG_EN, 0); + + if (ret < 0) + return ret; + + if (!iadc->poll_eoc) + reinit_completion(&iadc->complete); + + ret = iadc_set_state(iadc, true); + if (ret < 0) + return ret; + + /* Request conversion */ + return iadc_write(iadc, IADC_CONV_REQ, IADC_CONV_REQ_SET); +} + +static int iadc_poll_wait_eoc(struct iadc_chip *iadc, unsigned int interval_us) +{ + unsigned int count, retry; + int ret; + u8 sta1; + + retry = interval_us / IADC_CONV_TIME_MIN_US; + + for (count = 0; count < retry; count++) { + ret = iadc_read(iadc, IADC_STATUS1, &sta1); + if (ret < 0) + return ret; + + sta1 &= IADC_STATUS1_REQ_STS_EOC_MASK; + if (sta1 == IADC_STATUS1_EOC) + return 0; + + usleep_range(IADC_CONV_TIME_MIN_US, IADC_CONV_TIME_MAX_US); + } + + iadc_status_show(iadc); + + return -ETIMEDOUT; +} + +static int iadc_read_result(struct iadc_chip *iadc, u16 *data) +{ + return regmap_bulk_read(iadc->regmap, iadc->base + IADC_DATA, data, 2); +} + +static int iadc_do_conversion(struct iadc_chip *iadc, int chan, u16 *data) +{ + unsigned int wait; + int ret; + + ret = iadc_configure(iadc, chan); + if (ret < 0) + goto exit; + + wait = BIT(IADC_DEF_AVG_SAMPLES) * IADC_CONV_TIME_MIN_US * 2; + + if (iadc->poll_eoc) { + ret = iadc_poll_wait_eoc(iadc, wait); + } else { + ret = wait_for_completion_timeout(&iadc->complete, + usecs_to_jiffies(wait)); + if (!ret) + ret = -ETIMEDOUT; + else + /* double check conversion status */ + ret = iadc_poll_wait_eoc(iadc, IADC_CONV_TIME_MIN_US); + } + + if (!ret) + ret = iadc_read_result(iadc, data); +exit: + iadc_set_state(iadc, false); + if (ret < 0) + dev_err(iadc->dev, "conversion failed\n"); + + return ret; +} + +static int iadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct iadc_chip *iadc = iio_priv(indio_dev); + s32 isense_ua, vsense_uv; + u16 adc_raw, vsense_raw; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&iadc->lock); + ret = iadc_do_conversion(iadc, chan->channel, &adc_raw); + mutex_unlock(&iadc->lock); + if (ret < 0) + return ret; + + vsense_raw = adc_raw - iadc->offset[chan->channel]; + + vsense_uv = vsense_raw * IADC_REF_GAIN_MICRO_VOLTS; + vsense_uv /= (s32)iadc->gain - iadc->offset[chan->channel]; + + isense_ua = vsense_uv / iadc->rsense[chan->channel]; + + dev_dbg(iadc->dev, "off %d gain %d adc %d %duV I %duA\n", + iadc->offset[chan->channel], iadc->gain, + adc_raw, vsense_uv, isense_ua); + + *val = isense_ua; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static const struct iio_info iadc_info = { + .read_raw = iadc_read_raw, + .driver_module = THIS_MODULE, +}; + +static irqreturn_t iadc_isr(int irq, void *dev_id) +{ + struct iadc_chip *iadc = dev_id; + + complete(&iadc->complete); + + return IRQ_HANDLED; +} + +static int iadc_update_offset(struct iadc_chip *iadc) +{ + int ret; + + ret = iadc_do_conversion(iadc, IADC_GAIN_17P857MV, &iadc->gain); + if (ret < 0) + return ret; + + ret = iadc_do_conversion(iadc, IADC_INT_OFFSET_CSP2_CSN2, + &iadc->offset[IADC_INT_RSENSE]); + if (ret < 0) + return ret; + + if (iadc->gain == iadc->offset[IADC_INT_RSENSE]) { + dev_err(iadc->dev, "error: internal offset == gain %d\n", + iadc->gain); + return -EINVAL; + } + + ret = iadc_do_conversion(iadc, IADC_EXT_OFFSET_CSP_CSN, + &iadc->offset[IADC_EXT_RSENSE]); + if (ret < 0) + return ret; + + if (iadc->gain == iadc->offset[IADC_EXT_RSENSE]) { + dev_err(iadc->dev, "error: external offset == gain %d\n", + iadc->gain); + return -EINVAL; + } + + return 0; +} + +static int iadc_version_check(struct iadc_chip *iadc) +{ + u8 val; + int ret; + + ret = iadc_read(iadc, IADC_PERPH_TYPE, &val); + if (ret < 0) + return ret; + + if (val < IADC_PERPH_TYPE_ADC) { + dev_err(iadc->dev, "%d is not ADC\n", val); + return -EINVAL; + } + + ret = iadc_read(iadc, IADC_PERPH_SUBTYPE, &val); + if (ret < 0) + return ret; + + if (val < IADC_PERPH_SUBTYPE_IADC) { + dev_err(iadc->dev, "%d is not IADC\n", val); + return -EINVAL; + } + + ret = iadc_read(iadc, IADC_REVISION2, &val); + if (ret < 0) + return ret; + + if (val < IADC_REVISION2_SUPPORTED_IADC) { + dev_err(iadc->dev, "revision %d not supported\n", val); + return -EINVAL; + } + + return 0; +} + +static int iadc_rsense_read(struct iadc_chip *iadc, struct device_node *node) +{ + int ret, sign, int_sense; + u8 deviation; + + ret = of_property_read_u32(node, "qcom,external-resistor-micro-ohms", + &iadc->rsense[IADC_EXT_RSENSE]); + if (ret < 0) + iadc->rsense[IADC_EXT_RSENSE] = IADC_INT_RSENSE_IDEAL_VALUE; + + if (!iadc->rsense[IADC_EXT_RSENSE]) { + dev_err(iadc->dev, "external resistor can't be zero Ohms"); + return -EINVAL; + } + + ret = iadc_read(iadc, IADC_NOMINAL_RSENSE, &deviation); + if (ret < 0) + return ret; + + /* + * Deviation value stored is an offset from 10 mili Ohms, bit 7 is + * the sign, the remaining bits have an LSB of 15625 nano Ohms. + */ + sign = (deviation & IADC_NOMINAL_RSENSE_SIGN_MASK) ? -1 : 1; + + deviation &= ~IADC_NOMINAL_RSENSE_SIGN_MASK; + + /* Scale it to nono Ohms */ + int_sense = IADC_INT_RSENSE_IDEAL_VALUE * 1000; + int_sense += sign * deviation * IADC_INT_RSENSE_DEVIATION; + int_sense /= 1000; /* micro Ohms */ + + iadc->rsense[IADC_INT_RSENSE] = int_sense; + return 0; +} + +static const struct iio_chan_spec iadc_channels[] = { + { + .type = IIO_CURRENT, + .datasheet_name = "INTERNAL_RSENSE", + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + }, + { + .type = IIO_CURRENT, + .datasheet_name = "EXTERNAL_RSENSE", + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, + }, +}; + +static int iadc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct iadc_chip *iadc; + int ret, irq_eoc; + u32 res; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*iadc)); + if (!indio_dev) + return -ENOMEM; + + iadc = iio_priv(indio_dev); + iadc->dev = dev; + + iadc->regmap = dev_get_regmap(dev->parent, NULL); + if (!iadc->regmap) + return -ENODEV; + + init_completion(&iadc->complete); + mutex_init(&iadc->lock); + + ret = of_property_read_u32(node, "reg", &res); + if (ret < 0) + return -ENODEV; + + iadc->base = res; + + ret = iadc_version_check(iadc); + if (ret < 0) + return -ENODEV; + + ret = iadc_rsense_read(iadc, node); + if (ret < 0) + return -ENODEV; + + dev_dbg(iadc->dev, "sense resistors %d and %d micro Ohm\n", + iadc->rsense[IADC_INT_RSENSE], + iadc->rsense[IADC_EXT_RSENSE]); + + irq_eoc = platform_get_irq(pdev, 0); + if (irq_eoc == -EPROBE_DEFER) + return irq_eoc; + + if (irq_eoc < 0) + iadc->poll_eoc = true; + + ret = iadc_reset(iadc); + if (ret < 0) { + dev_err(dev, "reset failed\n"); + return ret; + } + + if (!iadc->poll_eoc) { + ret = devm_request_irq(dev, irq_eoc, iadc_isr, 0, + "spmi-iadc", iadc); + if (!ret) + enable_irq_wake(irq_eoc); + else + return ret; + } else { + device_init_wakeup(iadc->dev, 1); + } + + ret = iadc_update_offset(iadc); + if (ret < 0) { + dev_err(dev, "failed offset calibration\n"); + return ret; + } + + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = node; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &iadc_info; + indio_dev->channels = iadc_channels; + indio_dev->num_channels = ARRAY_SIZE(iadc_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id iadc_match_table[] = { + { .compatible = "qcom,spmi-iadc" }, + { } +}; + +MODULE_DEVICE_TABLE(of, iadc_match_table); + +static struct platform_driver iadc_driver = { + .driver = { + .name = "qcom-spmi-iadc", + .of_match_table = iadc_match_table, + }, + .probe = iadc_probe, +}; + +module_platform_driver(iadc_driver); + +MODULE_ALIAS("platform:qcom-spmi-iadc"); +MODULE_DESCRIPTION("Qualcomm SPMI PMIC current ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>"); diff --git a/drivers/iio/adc/qcom-spmi-vadc.c b/drivers/iio/adc/qcom-spmi-vadc.c new file mode 100644 index 000000000..0c4618b4d --- /dev/null +++ b/drivers/iio/adc/qcom-spmi-vadc.c @@ -0,0 +1,1017 @@ +/* + * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/bitops.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/iio/iio.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/log2.h> + +#include <dt-bindings/iio/qcom,spmi-vadc.h> + +/* VADC register and bit definitions */ +#define VADC_REVISION2 0x1 +#define VADC_REVISION2_SUPPORTED_VADC 1 + +#define VADC_PERPH_TYPE 0x4 +#define VADC_PERPH_TYPE_ADC 8 + +#define VADC_PERPH_SUBTYPE 0x5 +#define VADC_PERPH_SUBTYPE_VADC 1 + +#define VADC_STATUS1 0x8 +#define VADC_STATUS1_OP_MODE 4 +#define VADC_STATUS1_REQ_STS BIT(1) +#define VADC_STATUS1_EOC BIT(0) +#define VADC_STATUS1_REQ_STS_EOC_MASK 0x3 + +#define VADC_MODE_CTL 0x40 +#define VADC_OP_MODE_SHIFT 3 +#define VADC_OP_MODE_NORMAL 0 +#define VADC_AMUX_TRIM_EN BIT(1) +#define VADC_ADC_TRIM_EN BIT(0) + +#define VADC_EN_CTL1 0x46 +#define VADC_EN_CTL1_SET BIT(7) + +#define VADC_ADC_CH_SEL_CTL 0x48 + +#define VADC_ADC_DIG_PARAM 0x50 +#define VADC_ADC_DIG_DEC_RATIO_SEL_SHIFT 2 + +#define VADC_HW_SETTLE_DELAY 0x51 + +#define VADC_CONV_REQ 0x52 +#define VADC_CONV_REQ_SET BIT(7) + +#define VADC_FAST_AVG_CTL 0x5a +#define VADC_FAST_AVG_EN 0x5b +#define VADC_FAST_AVG_EN_SET BIT(7) + +#define VADC_ACCESS 0xd0 +#define VADC_ACCESS_DATA 0xa5 + +#define VADC_PERH_RESET_CTL3 0xda +#define VADC_FOLLOW_WARM_RB BIT(2) + +#define VADC_DATA 0x60 /* 16 bits */ + +#define VADC_CONV_TIME_MIN_US 2000 +#define VADC_CONV_TIME_MAX_US 2100 + +/* Min ADC code represents 0V */ +#define VADC_MIN_ADC_CODE 0x6000 +/* Max ADC code represents full-scale range of 1.8V */ +#define VADC_MAX_ADC_CODE 0xa800 + +#define VADC_ABSOLUTE_RANGE_UV 625000 +#define VADC_RATIOMETRIC_RANGE_UV 1800000 + +#define VADC_DEF_PRESCALING 0 /* 1:1 */ +#define VADC_DEF_DECIMATION 0 /* 512 */ +#define VADC_DEF_HW_SETTLE_TIME 0 /* 0 us */ +#define VADC_DEF_AVG_SAMPLES 0 /* 1 sample */ +#define VADC_DEF_CALIB_TYPE VADC_CALIB_ABSOLUTE + +#define VADC_DECIMATION_MIN 512 +#define VADC_DECIMATION_MAX 4096 + +#define VADC_HW_SETTLE_DELAY_MAX 10000 +#define VADC_AVG_SAMPLES_MAX 512 + +#define KELVINMIL_CELSIUSMIL 273150 + +#define VADC_CHAN_MIN VADC_USBIN +#define VADC_CHAN_MAX VADC_LR_MUX3_BUF_PU1_PU2_XO_THERM + +/* + * VADC_CALIB_ABSOLUTE: uses the 625mV and 1.25V as reference channels. + * VADC_CALIB_RATIOMETRIC: uses the reference voltage (1.8V) and GND for + * calibration. + */ +enum vadc_calibration { + VADC_CALIB_ABSOLUTE = 0, + VADC_CALIB_RATIOMETRIC +}; + +/** + * struct vadc_linear_graph - Represent ADC characteristics. + * @dy: numerator slope to calculate the gain. + * @dx: denominator slope to calculate the gain. + * @gnd: A/D word of the ground reference used for the channel. + * + * Each ADC device has different offset and gain parameters which are + * computed to calibrate the device. + */ +struct vadc_linear_graph { + s32 dy; + s32 dx; + s32 gnd; +}; + +/** + * struct vadc_prescale_ratio - Represent scaling ratio for ADC input. + * @num: the inverse numerator of the gain applied to the input channel. + * @den: the inverse denominator of the gain applied to the input channel. + */ +struct vadc_prescale_ratio { + u32 num; + u32 den; +}; + +/** + * struct vadc_channel_prop - VADC channel property. + * @channel: channel number, refer to the channel list. + * @calibration: calibration type. + * @decimation: sampling rate supported for the channel. + * @prescale: channel scaling performed on the input signal. + * @hw_settle_time: the time between AMUX being configured and the + * start of conversion. + * @avg_samples: ability to provide single result from the ADC + * that is an average of multiple measurements. + */ +struct vadc_channel_prop { + unsigned int channel; + enum vadc_calibration calibration; + unsigned int decimation; + unsigned int prescale; + unsigned int hw_settle_time; + unsigned int avg_samples; +}; + +/** + * struct vadc_priv - VADC private structure. + * @regmap: pointer to struct regmap. + * @dev: pointer to struct device. + * @base: base address for the ADC peripheral. + * @nchannels: number of VADC channels. + * @chan_props: array of VADC channel properties. + * @iio_chans: array of IIO channels specification. + * @are_ref_measured: are reference points measured. + * @poll_eoc: use polling instead of interrupt. + * @complete: VADC result notification after interrupt is received. + * @graph: store parameters for calibration. + * @lock: ADC lock for access to the peripheral. + */ +struct vadc_priv { + struct regmap *regmap; + struct device *dev; + u16 base; + unsigned int nchannels; + struct vadc_channel_prop *chan_props; + struct iio_chan_spec *iio_chans; + bool are_ref_measured; + bool poll_eoc; + struct completion complete; + struct vadc_linear_graph graph[2]; + struct mutex lock; +}; + +static const struct vadc_prescale_ratio vadc_prescale_ratios[] = { + {.num = 1, .den = 1}, + {.num = 1, .den = 3}, + {.num = 1, .den = 4}, + {.num = 1, .den = 6}, + {.num = 1, .den = 20}, + {.num = 1, .den = 8}, + {.num = 10, .den = 81}, + {.num = 1, .den = 10} +}; + +static int vadc_read(struct vadc_priv *vadc, u16 offset, u8 *data) +{ + return regmap_bulk_read(vadc->regmap, vadc->base + offset, data, 1); +} + +static int vadc_write(struct vadc_priv *vadc, u16 offset, u8 data) +{ + return regmap_write(vadc->regmap, vadc->base + offset, data); +} + +static int vadc_reset(struct vadc_priv *vadc) +{ + u8 data; + int ret; + + ret = vadc_write(vadc, VADC_ACCESS, VADC_ACCESS_DATA); + if (ret) + return ret; + + ret = vadc_read(vadc, VADC_PERH_RESET_CTL3, &data); + if (ret) + return ret; + + ret = vadc_write(vadc, VADC_ACCESS, VADC_ACCESS_DATA); + if (ret) + return ret; + + data |= VADC_FOLLOW_WARM_RB; + + return vadc_write(vadc, VADC_PERH_RESET_CTL3, data); +} + +static int vadc_set_state(struct vadc_priv *vadc, bool state) +{ + return vadc_write(vadc, VADC_EN_CTL1, state ? VADC_EN_CTL1_SET : 0); +} + +static void vadc_show_status(struct vadc_priv *vadc) +{ + u8 mode, sta1, chan, dig, en, req; + int ret; + + ret = vadc_read(vadc, VADC_MODE_CTL, &mode); + if (ret) + return; + + ret = vadc_read(vadc, VADC_ADC_DIG_PARAM, &dig); + if (ret) + return; + + ret = vadc_read(vadc, VADC_ADC_CH_SEL_CTL, &chan); + if (ret) + return; + + ret = vadc_read(vadc, VADC_CONV_REQ, &req); + if (ret) + return; + + ret = vadc_read(vadc, VADC_STATUS1, &sta1); + if (ret) + return; + + ret = vadc_read(vadc, VADC_EN_CTL1, &en); + if (ret) + return; + + dev_err(vadc->dev, + "mode:%02x en:%02x chan:%02x dig:%02x req:%02x sta1:%02x\n", + mode, en, chan, dig, req, sta1); +} + +static int vadc_configure(struct vadc_priv *vadc, + struct vadc_channel_prop *prop) +{ + u8 decimation, mode_ctrl; + int ret; + + /* Mode selection */ + mode_ctrl = (VADC_OP_MODE_NORMAL << VADC_OP_MODE_SHIFT) | + VADC_ADC_TRIM_EN | VADC_AMUX_TRIM_EN; + ret = vadc_write(vadc, VADC_MODE_CTL, mode_ctrl); + if (ret) + return ret; + + /* Channel selection */ + ret = vadc_write(vadc, VADC_ADC_CH_SEL_CTL, prop->channel); + if (ret) + return ret; + + /* Digital parameter setup */ + decimation = prop->decimation << VADC_ADC_DIG_DEC_RATIO_SEL_SHIFT; + ret = vadc_write(vadc, VADC_ADC_DIG_PARAM, decimation); + if (ret) + return ret; + + /* HW settle time delay */ + ret = vadc_write(vadc, VADC_HW_SETTLE_DELAY, prop->hw_settle_time); + if (ret) + return ret; + + ret = vadc_write(vadc, VADC_FAST_AVG_CTL, prop->avg_samples); + if (ret) + return ret; + + if (prop->avg_samples) + ret = vadc_write(vadc, VADC_FAST_AVG_EN, VADC_FAST_AVG_EN_SET); + else + ret = vadc_write(vadc, VADC_FAST_AVG_EN, 0); + + return ret; +} + +static int vadc_poll_wait_eoc(struct vadc_priv *vadc, unsigned int interval_us) +{ + unsigned int count, retry; + u8 sta1; + int ret; + + retry = interval_us / VADC_CONV_TIME_MIN_US; + + for (count = 0; count < retry; count++) { + ret = vadc_read(vadc, VADC_STATUS1, &sta1); + if (ret) + return ret; + + sta1 &= VADC_STATUS1_REQ_STS_EOC_MASK; + if (sta1 == VADC_STATUS1_EOC) + return 0; + + usleep_range(VADC_CONV_TIME_MIN_US, VADC_CONV_TIME_MAX_US); + } + + vadc_show_status(vadc); + + return -ETIMEDOUT; +} + +static int vadc_read_result(struct vadc_priv *vadc, u16 *data) +{ + int ret; + + ret = regmap_bulk_read(vadc->regmap, vadc->base + VADC_DATA, data, 2); + if (ret) + return ret; + + *data = clamp_t(u16, *data, VADC_MIN_ADC_CODE, VADC_MAX_ADC_CODE); + + return 0; +} + +static struct vadc_channel_prop *vadc_get_channel(struct vadc_priv *vadc, + unsigned int num) +{ + unsigned int i; + + for (i = 0; i < vadc->nchannels; i++) + if (vadc->chan_props[i].channel == num) + return &vadc->chan_props[i]; + + dev_dbg(vadc->dev, "no such channel %02x\n", num); + + return NULL; +} + +static int vadc_do_conversion(struct vadc_priv *vadc, + struct vadc_channel_prop *prop, u16 *data) +{ + unsigned int timeout; + int ret; + + mutex_lock(&vadc->lock); + + ret = vadc_configure(vadc, prop); + if (ret) + goto unlock; + + if (!vadc->poll_eoc) + reinit_completion(&vadc->complete); + + ret = vadc_set_state(vadc, true); + if (ret) + goto unlock; + + ret = vadc_write(vadc, VADC_CONV_REQ, VADC_CONV_REQ_SET); + if (ret) + goto err_disable; + + timeout = BIT(prop->avg_samples) * VADC_CONV_TIME_MIN_US * 2; + + if (vadc->poll_eoc) { + ret = vadc_poll_wait_eoc(vadc, timeout); + } else { + ret = wait_for_completion_timeout(&vadc->complete, timeout); + if (!ret) { + ret = -ETIMEDOUT; + goto err_disable; + } + + /* Double check conversion status */ + ret = vadc_poll_wait_eoc(vadc, VADC_CONV_TIME_MIN_US); + if (ret) + goto err_disable; + } + + ret = vadc_read_result(vadc, data); + +err_disable: + vadc_set_state(vadc, false); + if (ret) + dev_err(vadc->dev, "conversion failed\n"); +unlock: + mutex_unlock(&vadc->lock); + return ret; +} + +static int vadc_measure_ref_points(struct vadc_priv *vadc) +{ + struct vadc_channel_prop *prop; + u16 read_1, read_2; + int ret; + + vadc->graph[VADC_CALIB_RATIOMETRIC].dx = VADC_RATIOMETRIC_RANGE_UV; + vadc->graph[VADC_CALIB_ABSOLUTE].dx = VADC_ABSOLUTE_RANGE_UV; + + prop = vadc_get_channel(vadc, VADC_REF_1250MV); + ret = vadc_do_conversion(vadc, prop, &read_1); + if (ret) + goto err; + + /* Try with buffered 625mV channel first */ + prop = vadc_get_channel(vadc, VADC_SPARE1); + if (!prop) + prop = vadc_get_channel(vadc, VADC_REF_625MV); + + ret = vadc_do_conversion(vadc, prop, &read_2); + if (ret) + goto err; + + if (read_1 == read_2) { + ret = -EINVAL; + goto err; + } + + vadc->graph[VADC_CALIB_ABSOLUTE].dy = read_1 - read_2; + vadc->graph[VADC_CALIB_ABSOLUTE].gnd = read_2; + + /* Ratiometric calibration */ + prop = vadc_get_channel(vadc, VADC_VDD_VADC); + ret = vadc_do_conversion(vadc, prop, &read_1); + if (ret) + goto err; + + prop = vadc_get_channel(vadc, VADC_GND_REF); + ret = vadc_do_conversion(vadc, prop, &read_2); + if (ret) + goto err; + + if (read_1 == read_2) { + ret = -EINVAL; + goto err; + } + + vadc->graph[VADC_CALIB_RATIOMETRIC].dy = read_1 - read_2; + vadc->graph[VADC_CALIB_RATIOMETRIC].gnd = read_2; +err: + if (ret) + dev_err(vadc->dev, "measure reference points failed\n"); + + return ret; +} + +static s32 vadc_calibrate(struct vadc_priv *vadc, + const struct vadc_channel_prop *prop, u16 adc_code) +{ + const struct vadc_prescale_ratio *prescale; + s64 voltage; + + voltage = adc_code - vadc->graph[prop->calibration].gnd; + voltage *= vadc->graph[prop->calibration].dx; + voltage = div64_s64(voltage, vadc->graph[prop->calibration].dy); + + if (prop->calibration == VADC_CALIB_ABSOLUTE) + voltage += vadc->graph[prop->calibration].dx; + + if (voltage < 0) + voltage = 0; + + prescale = &vadc_prescale_ratios[prop->prescale]; + + voltage = voltage * prescale->den; + + return div64_s64(voltage, prescale->num); +} + +static int vadc_decimation_from_dt(u32 value) +{ + if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN || + value > VADC_DECIMATION_MAX) + return -EINVAL; + + return __ffs64(value / VADC_DECIMATION_MIN); +} + +static int vadc_prescaling_from_dt(u32 num, u32 den) +{ + unsigned int pre; + + for (pre = 0; pre < ARRAY_SIZE(vadc_prescale_ratios); pre++) + if (vadc_prescale_ratios[pre].num == num && + vadc_prescale_ratios[pre].den == den) + break; + + if (pre == ARRAY_SIZE(vadc_prescale_ratios)) + return -EINVAL; + + return pre; +} + +static int vadc_hw_settle_time_from_dt(u32 value) +{ + if ((value <= 1000 && value % 100) || (value > 1000 && value % 2000)) + return -EINVAL; + + if (value <= 1000) + value /= 100; + else + value = value / 2000 + 10; + + return value; +} + +static int vadc_avg_samples_from_dt(u32 value) +{ + if (!is_power_of_2(value) || value > VADC_AVG_SAMPLES_MAX) + return -EINVAL; + + return __ffs64(value); +} + +static int vadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct vadc_priv *vadc = iio_priv(indio_dev); + struct vadc_channel_prop *prop; + u16 adc_code; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + prop = &vadc->chan_props[chan->address]; + ret = vadc_do_conversion(vadc, prop, &adc_code); + if (ret) + break; + + *val = vadc_calibrate(vadc, prop, adc_code); + + /* 2mV/K, return milli Celsius */ + *val /= 2; + *val -= KELVINMIL_CELSIUSMIL; + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + prop = &vadc->chan_props[chan->address]; + ret = vadc_do_conversion(vadc, prop, &adc_code); + if (ret) + break; + + *val = vadc_calibrate(vadc, prop, adc_code); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int vadc_of_xlate(struct iio_dev *indio_dev, + const struct of_phandle_args *iiospec) +{ + struct vadc_priv *vadc = iio_priv(indio_dev); + unsigned int i; + + for (i = 0; i < vadc->nchannels; i++) + if (vadc->iio_chans[i].channel == iiospec->args[0]) + return i; + + return -EINVAL; +} + +static const struct iio_info vadc_info = { + .read_raw = vadc_read_raw, + .of_xlate = vadc_of_xlate, + .driver_module = THIS_MODULE, +}; + +struct vadc_channels { + const char *datasheet_name; + unsigned int prescale_index; + enum iio_chan_type type; + long info_mask; +}; + +#define VADC_CHAN(_dname, _type, _mask, _pre) \ + [VADC_##_dname] = { \ + .datasheet_name = __stringify(_dname), \ + .prescale_index = _pre, \ + .type = _type, \ + .info_mask = _mask \ + }, \ + +#define VADC_CHAN_TEMP(_dname, _pre) \ + VADC_CHAN(_dname, IIO_TEMP, BIT(IIO_CHAN_INFO_PROCESSED), _pre) \ + +#define VADC_CHAN_VOLT(_dname, _pre) \ + VADC_CHAN(_dname, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), \ + _pre) \ + +/* + * The array represents all possible ADC channels found in the supported PMICs. + * Every index in the array is equal to the channel number per datasheet. The + * gaps in the array should be treated as reserved channels. + */ +static const struct vadc_channels vadc_chans[] = { + VADC_CHAN_VOLT(USBIN, 4) + VADC_CHAN_VOLT(DCIN, 4) + VADC_CHAN_VOLT(VCHG_SNS, 3) + VADC_CHAN_VOLT(SPARE1_03, 1) + VADC_CHAN_VOLT(USB_ID_MV, 1) + VADC_CHAN_VOLT(VCOIN, 1) + VADC_CHAN_VOLT(VBAT_SNS, 1) + VADC_CHAN_VOLT(VSYS, 1) + VADC_CHAN_TEMP(DIE_TEMP, 0) + VADC_CHAN_VOLT(REF_625MV, 0) + VADC_CHAN_VOLT(REF_1250MV, 0) + VADC_CHAN_VOLT(CHG_TEMP, 0) + VADC_CHAN_VOLT(SPARE1, 0) + VADC_CHAN_VOLT(SPARE2, 0) + VADC_CHAN_VOLT(GND_REF, 0) + VADC_CHAN_VOLT(VDD_VADC, 0) + + VADC_CHAN_VOLT(P_MUX1_1_1, 0) + VADC_CHAN_VOLT(P_MUX2_1_1, 0) + VADC_CHAN_VOLT(P_MUX3_1_1, 0) + VADC_CHAN_VOLT(P_MUX4_1_1, 0) + VADC_CHAN_VOLT(P_MUX5_1_1, 0) + VADC_CHAN_VOLT(P_MUX6_1_1, 0) + VADC_CHAN_VOLT(P_MUX7_1_1, 0) + VADC_CHAN_VOLT(P_MUX8_1_1, 0) + VADC_CHAN_VOLT(P_MUX9_1_1, 0) + VADC_CHAN_VOLT(P_MUX10_1_1, 0) + VADC_CHAN_VOLT(P_MUX11_1_1, 0) + VADC_CHAN_VOLT(P_MUX12_1_1, 0) + VADC_CHAN_VOLT(P_MUX13_1_1, 0) + VADC_CHAN_VOLT(P_MUX14_1_1, 0) + VADC_CHAN_VOLT(P_MUX15_1_1, 0) + VADC_CHAN_VOLT(P_MUX16_1_1, 0) + + VADC_CHAN_VOLT(P_MUX1_1_3, 1) + VADC_CHAN_VOLT(P_MUX2_1_3, 1) + VADC_CHAN_VOLT(P_MUX3_1_3, 1) + VADC_CHAN_VOLT(P_MUX4_1_3, 1) + VADC_CHAN_VOLT(P_MUX5_1_3, 1) + VADC_CHAN_VOLT(P_MUX6_1_3, 1) + VADC_CHAN_VOLT(P_MUX7_1_3, 1) + VADC_CHAN_VOLT(P_MUX8_1_3, 1) + VADC_CHAN_VOLT(P_MUX9_1_3, 1) + VADC_CHAN_VOLT(P_MUX10_1_3, 1) + VADC_CHAN_VOLT(P_MUX11_1_3, 1) + VADC_CHAN_VOLT(P_MUX12_1_3, 1) + VADC_CHAN_VOLT(P_MUX13_1_3, 1) + VADC_CHAN_VOLT(P_MUX14_1_3, 1) + VADC_CHAN_VOLT(P_MUX15_1_3, 1) + VADC_CHAN_VOLT(P_MUX16_1_3, 1) + + VADC_CHAN_VOLT(LR_MUX1_BAT_THERM, 0) + VADC_CHAN_VOLT(LR_MUX2_BAT_ID, 0) + VADC_CHAN_VOLT(LR_MUX3_XO_THERM, 0) + VADC_CHAN_VOLT(LR_MUX4_AMUX_THM1, 0) + VADC_CHAN_VOLT(LR_MUX5_AMUX_THM2, 0) + VADC_CHAN_VOLT(LR_MUX6_AMUX_THM3, 0) + VADC_CHAN_VOLT(LR_MUX7_HW_ID, 0) + VADC_CHAN_VOLT(LR_MUX8_AMUX_THM4, 0) + VADC_CHAN_VOLT(LR_MUX9_AMUX_THM5, 0) + VADC_CHAN_VOLT(LR_MUX10_USB_ID, 0) + VADC_CHAN_VOLT(AMUX_PU1, 0) + VADC_CHAN_VOLT(AMUX_PU2, 0) + VADC_CHAN_VOLT(LR_MUX3_BUF_XO_THERM, 0) + + VADC_CHAN_VOLT(LR_MUX1_PU1_BAT_THERM, 0) + VADC_CHAN_VOLT(LR_MUX2_PU1_BAT_ID, 0) + VADC_CHAN_VOLT(LR_MUX3_PU1_XO_THERM, 0) + VADC_CHAN_VOLT(LR_MUX4_PU1_AMUX_THM1, 0) + VADC_CHAN_VOLT(LR_MUX5_PU1_AMUX_THM2, 0) + VADC_CHAN_VOLT(LR_MUX6_PU1_AMUX_THM3, 0) + VADC_CHAN_VOLT(LR_MUX7_PU1_AMUX_HW_ID, 0) + VADC_CHAN_VOLT(LR_MUX8_PU1_AMUX_THM4, 0) + VADC_CHAN_VOLT(LR_MUX9_PU1_AMUX_THM5, 0) + VADC_CHAN_VOLT(LR_MUX10_PU1_AMUX_USB_ID, 0) + VADC_CHAN_VOLT(LR_MUX3_BUF_PU1_XO_THERM, 0) + + VADC_CHAN_VOLT(LR_MUX1_PU2_BAT_THERM, 0) + VADC_CHAN_VOLT(LR_MUX2_PU2_BAT_ID, 0) + VADC_CHAN_VOLT(LR_MUX3_PU2_XO_THERM, 0) + VADC_CHAN_VOLT(LR_MUX4_PU2_AMUX_THM1, 0) + VADC_CHAN_VOLT(LR_MUX5_PU2_AMUX_THM2, 0) + VADC_CHAN_VOLT(LR_MUX6_PU2_AMUX_THM3, 0) + VADC_CHAN_VOLT(LR_MUX7_PU2_AMUX_HW_ID, 0) + VADC_CHAN_VOLT(LR_MUX8_PU2_AMUX_THM4, 0) + VADC_CHAN_VOLT(LR_MUX9_PU2_AMUX_THM5, 0) + VADC_CHAN_VOLT(LR_MUX10_PU2_AMUX_USB_ID, 0) + VADC_CHAN_VOLT(LR_MUX3_BUF_PU2_XO_THERM, 0) + + VADC_CHAN_VOLT(LR_MUX1_PU1_PU2_BAT_THERM, 0) + VADC_CHAN_VOLT(LR_MUX2_PU1_PU2_BAT_ID, 0) + VADC_CHAN_VOLT(LR_MUX3_PU1_PU2_XO_THERM, 0) + VADC_CHAN_VOLT(LR_MUX4_PU1_PU2_AMUX_THM1, 0) + VADC_CHAN_VOLT(LR_MUX5_PU1_PU2_AMUX_THM2, 0) + VADC_CHAN_VOLT(LR_MUX6_PU1_PU2_AMUX_THM3, 0) + VADC_CHAN_VOLT(LR_MUX7_PU1_PU2_AMUX_HW_ID, 0) + VADC_CHAN_VOLT(LR_MUX8_PU1_PU2_AMUX_THM4, 0) + VADC_CHAN_VOLT(LR_MUX9_PU1_PU2_AMUX_THM5, 0) + VADC_CHAN_VOLT(LR_MUX10_PU1_PU2_AMUX_USB_ID, 0) + VADC_CHAN_VOLT(LR_MUX3_BUF_PU1_PU2_XO_THERM, 0) +}; + +static int vadc_get_dt_channel_data(struct device *dev, + struct vadc_channel_prop *prop, + struct device_node *node) +{ + const char *name = node->name; + u32 chan, value, varr[2]; + int ret; + + ret = of_property_read_u32(node, "reg", &chan); + if (ret) { + dev_err(dev, "invalid channel number %s\n", name); + return ret; + } + + if (chan > VADC_CHAN_MAX || chan < VADC_CHAN_MIN) { + dev_err(dev, "%s invalid channel number %d\n", name, chan); + return -EINVAL; + } + + /* the channel has DT description */ + prop->channel = chan; + + ret = of_property_read_u32(node, "qcom,decimation", &value); + if (!ret) { + ret = vadc_decimation_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid decimation %d\n", + chan, value); + return ret; + } + prop->decimation = ret; + } else { + prop->decimation = VADC_DEF_DECIMATION; + } + + ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2); + if (!ret) { + ret = vadc_prescaling_from_dt(varr[0], varr[1]); + if (ret < 0) { + dev_err(dev, "%02x invalid pre-scaling <%d %d>\n", + chan, varr[0], varr[1]); + return ret; + } + prop->prescale = ret; + } else { + prop->prescale = vadc_chans[prop->channel].prescale_index; + } + + ret = of_property_read_u32(node, "qcom,hw-settle-time", &value); + if (!ret) { + ret = vadc_hw_settle_time_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid hw-settle-time %d us\n", + chan, value); + return ret; + } + prop->hw_settle_time = ret; + } else { + prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME; + } + + ret = of_property_read_u32(node, "qcom,avg-samples", &value); + if (!ret) { + ret = vadc_avg_samples_from_dt(value); + if (ret < 0) { + dev_err(dev, "%02x invalid avg-samples %d\n", + chan, value); + return ret; + } + prop->avg_samples = ret; + } else { + prop->avg_samples = VADC_DEF_AVG_SAMPLES; + } + + if (of_property_read_bool(node, "qcom,ratiometric")) + prop->calibration = VADC_CALIB_RATIOMETRIC; + else + prop->calibration = VADC_CALIB_ABSOLUTE; + + dev_dbg(dev, "%02x name %s\n", chan, name); + + return 0; +} + +static int vadc_get_dt_data(struct vadc_priv *vadc, struct device_node *node) +{ + const struct vadc_channels *vadc_chan; + struct iio_chan_spec *iio_chan; + struct vadc_channel_prop prop; + struct device_node *child; + unsigned int index = 0; + int ret; + + vadc->nchannels = of_get_available_child_count(node); + if (!vadc->nchannels) + return -EINVAL; + + vadc->iio_chans = devm_kcalloc(vadc->dev, vadc->nchannels, + sizeof(*vadc->iio_chans), GFP_KERNEL); + if (!vadc->iio_chans) + return -ENOMEM; + + vadc->chan_props = devm_kcalloc(vadc->dev, vadc->nchannels, + sizeof(*vadc->chan_props), GFP_KERNEL); + if (!vadc->chan_props) + return -ENOMEM; + + iio_chan = vadc->iio_chans; + + for_each_available_child_of_node(node, child) { + ret = vadc_get_dt_channel_data(vadc->dev, &prop, child); + if (ret) + return ret; + + vadc->chan_props[index] = prop; + + vadc_chan = &vadc_chans[prop.channel]; + + iio_chan->channel = prop.channel; + iio_chan->datasheet_name = vadc_chan->datasheet_name; + iio_chan->info_mask_separate = vadc_chan->info_mask; + iio_chan->type = vadc_chan->type; + iio_chan->indexed = 1; + iio_chan->address = index++; + + iio_chan++; + } + + /* These channels are mandatory, they are used as reference points */ + if (!vadc_get_channel(vadc, VADC_REF_1250MV)) { + dev_err(vadc->dev, "Please define 1.25V channel\n"); + return -ENODEV; + } + + if (!vadc_get_channel(vadc, VADC_REF_625MV)) { + dev_err(vadc->dev, "Please define 0.625V channel\n"); + return -ENODEV; + } + + if (!vadc_get_channel(vadc, VADC_VDD_VADC)) { + dev_err(vadc->dev, "Please define VDD channel\n"); + return -ENODEV; + } + + if (!vadc_get_channel(vadc, VADC_GND_REF)) { + dev_err(vadc->dev, "Please define GND channel\n"); + return -ENODEV; + } + + return 0; +} + +static irqreturn_t vadc_isr(int irq, void *dev_id) +{ + struct vadc_priv *vadc = dev_id; + + complete(&vadc->complete); + + return IRQ_HANDLED; +} + +static int vadc_check_revision(struct vadc_priv *vadc) +{ + u8 val; + int ret; + + ret = vadc_read(vadc, VADC_PERPH_TYPE, &val); + if (ret) + return ret; + + if (val < VADC_PERPH_TYPE_ADC) { + dev_err(vadc->dev, "%d is not ADC\n", val); + return -ENODEV; + } + + ret = vadc_read(vadc, VADC_PERPH_SUBTYPE, &val); + if (ret) + return ret; + + if (val < VADC_PERPH_SUBTYPE_VADC) { + dev_err(vadc->dev, "%d is not VADC\n", val); + return -ENODEV; + } + + ret = vadc_read(vadc, VADC_REVISION2, &val); + if (ret) + return ret; + + if (val < VADC_REVISION2_SUPPORTED_VADC) { + dev_err(vadc->dev, "revision %d not supported\n", val); + return -ENODEV; + } + + return 0; +} + +static int vadc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct vadc_priv *vadc; + struct regmap *regmap; + int ret, irq_eoc; + u32 reg; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + ret = of_property_read_u32(node, "reg", ®); + if (ret < 0) + return ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*vadc)); + if (!indio_dev) + return -ENOMEM; + + vadc = iio_priv(indio_dev); + vadc->regmap = regmap; + vadc->dev = dev; + vadc->base = reg; + vadc->are_ref_measured = false; + init_completion(&vadc->complete); + mutex_init(&vadc->lock); + + ret = vadc_check_revision(vadc); + if (ret) + return ret; + + ret = vadc_get_dt_data(vadc, node); + if (ret) + return ret; + + irq_eoc = platform_get_irq(pdev, 0); + if (irq_eoc < 0) { + if (irq_eoc == -EPROBE_DEFER || irq_eoc == -EINVAL) + return irq_eoc; + vadc->poll_eoc = true; + } else { + ret = devm_request_irq(dev, irq_eoc, vadc_isr, 0, + "spmi-vadc", vadc); + if (ret) + return ret; + } + + ret = vadc_reset(vadc); + if (ret) { + dev_err(dev, "reset failed\n"); + return ret; + } + + ret = vadc_measure_ref_points(vadc); + if (ret) + return ret; + + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = node; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &vadc_info; + indio_dev->channels = vadc->iio_chans; + indio_dev->num_channels = vadc->nchannels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id vadc_match_table[] = { + { .compatible = "qcom,spmi-vadc" }, + { } +}; +MODULE_DEVICE_TABLE(of, vadc_match_table); + +static struct platform_driver vadc_driver = { + .driver = { + .name = "qcom-spmi-vadc", + .of_match_table = vadc_match_table, + }, + .probe = vadc_probe, +}; +module_platform_driver(vadc_driver); + +MODULE_ALIAS("platform:qcom-spmi-vadc"); +MODULE_DESCRIPTION("Qualcomm SPMI PMIC voltage ADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Stanimir Varbanov <svarbanov@mm-sol.com>"); +MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>"); diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c new file mode 100644 index 000000000..9c311c1e1 --- /dev/null +++ b/drivers/iio/adc/rockchip_saradc.c @@ -0,0 +1,355 @@ +/* + * Rockchip Successive Approximation Register (SAR) A/D Converter + * Copyright (C) 2014 ROCKCHIP, Inc. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> + +#define SARADC_DATA 0x00 + +#define SARADC_STAS 0x04 +#define SARADC_STAS_BUSY BIT(0) + +#define SARADC_CTRL 0x08 +#define SARADC_CTRL_IRQ_STATUS BIT(6) +#define SARADC_CTRL_IRQ_ENABLE BIT(5) +#define SARADC_CTRL_POWER_CTRL BIT(3) +#define SARADC_CTRL_CHN_MASK 0x7 + +#define SARADC_DLY_PU_SOC 0x0c +#define SARADC_DLY_PU_SOC_MASK 0x3f + +#define SARADC_TIMEOUT msecs_to_jiffies(100) + +struct rockchip_saradc_data { + int num_bits; + const struct iio_chan_spec *channels; + int num_channels; + unsigned long clk_rate; +}; + +struct rockchip_saradc { + void __iomem *regs; + struct clk *pclk; + struct clk *clk; + struct completion completion; + struct regulator *vref; + const struct rockchip_saradc_data *data; + u16 last_val; +}; + +static int rockchip_saradc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rockchip_saradc *info = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + + reinit_completion(&info->completion); + + /* 8 clock periods as delay between power up and start cmd */ + writel_relaxed(8, info->regs + SARADC_DLY_PU_SOC); + + /* Select the channel to be used and trigger conversion */ + writel(SARADC_CTRL_POWER_CTRL + | (chan->channel & SARADC_CTRL_CHN_MASK) + | SARADC_CTRL_IRQ_ENABLE, + info->regs + SARADC_CTRL); + + if (!wait_for_completion_timeout(&info->completion, + SARADC_TIMEOUT)) { + writel_relaxed(0, info->regs + SARADC_CTRL); + mutex_unlock(&indio_dev->mlock); + return -ETIMEDOUT; + } + + *val = info->last_val; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(info->vref); + if (ret < 0) { + dev_err(&indio_dev->dev, "failed to get voltage\n"); + return ret; + } + + *val = ret / 1000; + *val2 = info->data->num_bits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static irqreturn_t rockchip_saradc_isr(int irq, void *dev_id) +{ + struct rockchip_saradc *info = (struct rockchip_saradc *)dev_id; + + /* Read value */ + info->last_val = readl_relaxed(info->regs + SARADC_DATA); + info->last_val &= GENMASK(info->data->num_bits - 1, 0); + + /* Clear irq & power down adc */ + writel_relaxed(0, info->regs + SARADC_CTRL); + + complete(&info->completion); + + return IRQ_HANDLED; +} + +static const struct iio_info rockchip_saradc_iio_info = { + .read_raw = rockchip_saradc_read_raw, + .driver_module = THIS_MODULE, +}; + +#define ADC_CHANNEL(_index, _id) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _id, \ +} + +static const struct iio_chan_spec rockchip_saradc_iio_channels[] = { + ADC_CHANNEL(0, "adc0"), + ADC_CHANNEL(1, "adc1"), + ADC_CHANNEL(2, "adc2"), +}; + +static const struct rockchip_saradc_data saradc_data = { + .num_bits = 10, + .channels = rockchip_saradc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_saradc_iio_channels), + .clk_rate = 1000000, +}; + +static const struct iio_chan_spec rockchip_rk3066_tsadc_iio_channels[] = { + ADC_CHANNEL(0, "adc0"), + ADC_CHANNEL(1, "adc1"), +}; + +static const struct rockchip_saradc_data rk3066_tsadc_data = { + .num_bits = 12, + .channels = rockchip_rk3066_tsadc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_rk3066_tsadc_iio_channels), + .clk_rate = 50000, +}; + +static const struct of_device_id rockchip_saradc_match[] = { + { + .compatible = "rockchip,saradc", + .data = &saradc_data, + }, { + .compatible = "rockchip,rk3066-tsadc", + .data = &rk3066_tsadc_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, rockchip_saradc_match); + +static int rockchip_saradc_probe(struct platform_device *pdev) +{ + struct rockchip_saradc *info = NULL; + struct device_node *np = pdev->dev.of_node; + struct iio_dev *indio_dev = NULL; + struct resource *mem; + const struct of_device_id *match; + int ret; + int irq; + + if (!np) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) { + dev_err(&pdev->dev, "failed allocating iio device\n"); + return -ENOMEM; + } + info = iio_priv(indio_dev); + + match = of_match_device(rockchip_saradc_match, &pdev->dev); + info->data = match->data; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + init_completion(&info->completion); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, rockchip_saradc_isr, + 0, dev_name(&pdev->dev), info); + if (ret < 0) { + dev_err(&pdev->dev, "failed requesting irq %d\n", irq); + return ret; + } + + info->pclk = devm_clk_get(&pdev->dev, "apb_pclk"); + if (IS_ERR(info->pclk)) { + dev_err(&pdev->dev, "failed to get pclk\n"); + return PTR_ERR(info->pclk); + } + + info->clk = devm_clk_get(&pdev->dev, "saradc"); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "failed to get adc clock\n"); + return PTR_ERR(info->clk); + } + + info->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(info->vref)) { + dev_err(&pdev->dev, "failed to get regulator, %ld\n", + PTR_ERR(info->vref)); + return PTR_ERR(info->vref); + } + + /* + * Use a default value for the converter clock. + * This may become user-configurable in the future. + */ + ret = clk_set_rate(info->clk, info->data->clk_rate); + if (ret < 0) { + dev_err(&pdev->dev, "failed to set adc clk rate, %d\n", ret); + return ret; + } + + ret = regulator_enable(info->vref); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable vref regulator\n"); + return ret; + } + + ret = clk_prepare_enable(info->pclk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable pclk\n"); + goto err_reg_voltage; + } + + ret = clk_prepare_enable(info->clk); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable converter clock\n"); + goto err_pclk; + } + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &rockchip_saradc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = info->data->channels; + indio_dev->num_channels = info->data->num_channels; + + ret = iio_device_register(indio_dev); + if (ret) + goto err_clk; + + return 0; + +err_clk: + clk_disable_unprepare(info->clk); +err_pclk: + clk_disable_unprepare(info->pclk); +err_reg_voltage: + regulator_disable(info->vref); + return ret; +} + +static int rockchip_saradc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct rockchip_saradc *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + clk_disable_unprepare(info->clk); + clk_disable_unprepare(info->pclk); + regulator_disable(info->vref); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int rockchip_saradc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rockchip_saradc *info = iio_priv(indio_dev); + + clk_disable_unprepare(info->clk); + clk_disable_unprepare(info->pclk); + regulator_disable(info->vref); + + return 0; +} + +static int rockchip_saradc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rockchip_saradc *info = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(info->vref); + if (ret) + return ret; + + ret = clk_prepare_enable(info->pclk); + if (ret) + return ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + return ret; + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(rockchip_saradc_pm_ops, + rockchip_saradc_suspend, rockchip_saradc_resume); + +static struct platform_driver rockchip_saradc_driver = { + .probe = rockchip_saradc_probe, + .remove = rockchip_saradc_remove, + .driver = { + .name = "rockchip-saradc", + .of_match_table = rockchip_saradc_match, + .pm = &rockchip_saradc_pm_ops, + }, +}; + +module_platform_driver(rockchip_saradc_driver); + +MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); +MODULE_DESCRIPTION("Rockchip SARADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-adc081c.c b/drivers/iio/adc/ti-adc081c.c new file mode 100644 index 000000000..b3a82b4d1 --- /dev/null +++ b/drivers/iio/adc/ti-adc081c.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <linux/iio/iio.h> +#include <linux/regulator/consumer.h> + +struct adc081c { + struct i2c_client *i2c; + struct regulator *ref; +}; + +#define REG_CONV_RES 0x00 + +static int adc081c_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int *value, + int *shift, long mask) +{ + struct adc081c *adc = iio_priv(iio); + int err; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = i2c_smbus_read_word_swapped(adc->i2c, REG_CONV_RES); + if (err < 0) + return err; + + *value = (err >> 4) & 0xff; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + err = regulator_get_voltage(adc->ref); + if (err < 0) + return err; + + *value = err / 1000; + *shift = 8; + + return IIO_VAL_FRACTIONAL_LOG2; + + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec adc081c_channel = { + .type = IIO_VOLTAGE, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +}; + +static const struct iio_info adc081c_info = { + .read_raw = adc081c_read_raw, + .driver_module = THIS_MODULE, +}; + +static int adc081c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *iio; + struct adc081c *adc; + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + iio = devm_iio_device_alloc(&client->dev, sizeof(*adc)); + if (!iio) + return -ENOMEM; + + adc = iio_priv(iio); + adc->i2c = client; + + adc->ref = devm_regulator_get(&client->dev, "vref"); + if (IS_ERR(adc->ref)) + return PTR_ERR(adc->ref); + + err = regulator_enable(adc->ref); + if (err < 0) + return err; + + iio->dev.parent = &client->dev; + iio->name = dev_name(&client->dev); + iio->modes = INDIO_DIRECT_MODE; + iio->info = &adc081c_info; + + iio->channels = &adc081c_channel; + iio->num_channels = 1; + + err = iio_device_register(iio); + if (err < 0) + goto regulator_disable; + + i2c_set_clientdata(client, iio); + + return 0; + +regulator_disable: + regulator_disable(adc->ref); + + return err; +} + +static int adc081c_remove(struct i2c_client *client) +{ + struct iio_dev *iio = i2c_get_clientdata(client); + struct adc081c *adc = iio_priv(iio); + + iio_device_unregister(iio); + regulator_disable(adc->ref); + + return 0; +} + +static const struct i2c_device_id adc081c_id[] = { + { "adc081c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adc081c_id); + +#ifdef CONFIG_OF +static const struct of_device_id adc081c_of_match[] = { + { .compatible = "ti,adc081c" }, + { } +}; +MODULE_DEVICE_TABLE(of, adc081c_of_match); +#endif + +static struct i2c_driver adc081c_driver = { + .driver = { + .name = "adc081c", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(adc081c_of_match), + }, + .probe = adc081c_probe, + .remove = adc081c_remove, + .id_table = adc081c_id, +}; +module_i2c_driver(adc081c_driver); + +MODULE_AUTHOR("Thierry Reding <thierry.reding@avionic-design.de>"); +MODULE_DESCRIPTION("Texas Instruments ADC081C021/027 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c new file mode 100644 index 000000000..655cb564e --- /dev/null +++ b/drivers/iio/adc/ti-adc128s052.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2014 Angelo Compagnucci <angelo.compagnucci@gmail.com> + * + * Driver for Texas Instruments' ADC128S052 ADC chip. + * Datasheet can be found here: + * http://www.ti.com/lit/ds/symlink/adc128s052.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/err.h> +#include <linux/spi/spi.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/regulator/consumer.h> + +struct adc128 { + struct spi_device *spi; + + struct regulator *reg; + struct mutex lock; + + u8 buffer[2] ____cacheline_aligned; +}; + +static int adc128_adc_conversion(struct adc128 *adc, u8 channel) +{ + int ret; + + mutex_lock(&adc->lock); + + adc->buffer[0] = channel << 3; + adc->buffer[1] = 0; + + ret = spi_write(adc->spi, &adc->buffer, 2); + if (ret < 0) { + mutex_unlock(&adc->lock); + return ret; + } + + ret = spi_read(adc->spi, &adc->buffer, 2); + + mutex_unlock(&adc->lock); + + if (ret < 0) + return ret; + + return ((adc->buffer[0] << 8 | adc->buffer[1]) & 0xFFF); +} + +static int adc128_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct adc128 *adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + + ret = adc128_adc_conversion(adc, channel->channel); + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + + ret = regulator_get_voltage(adc->reg); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } + +} + +#define ADC128_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + } + +static const struct iio_chan_spec adc128_channels[] = { + ADC128_VOLTAGE_CHANNEL(0), + ADC128_VOLTAGE_CHANNEL(1), + ADC128_VOLTAGE_CHANNEL(2), + ADC128_VOLTAGE_CHANNEL(3), + ADC128_VOLTAGE_CHANNEL(4), + ADC128_VOLTAGE_CHANNEL(5), + ADC128_VOLTAGE_CHANNEL(6), + ADC128_VOLTAGE_CHANNEL(7), +}; + +static const struct iio_info adc128_info = { + .read_raw = adc128_read_raw, + .driver_module = THIS_MODULE, +}; + +static int adc128_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adc128 *adc; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + + spi_set_drvdata(spi, indio_dev); + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &adc128_info; + + indio_dev->channels = adc128_channels; + indio_dev->num_channels = ARRAY_SIZE(adc128_channels); + + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) + return PTR_ERR(adc->reg); + + ret = regulator_enable(adc->reg); + if (ret < 0) + return ret; + + mutex_init(&adc->lock); + + ret = iio_device_register(indio_dev); + + return ret; +} + +static int adc128_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adc128 *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(adc->reg); + + return 0; +} + +static const struct spi_device_id adc128_id[] = { + { "adc128s052", 0}, + { } +}; +MODULE_DEVICE_TABLE(spi, adc128_id); + +static struct spi_driver adc128_driver = { + .driver = { + .name = "adc128s052", + .owner = THIS_MODULE, + }, + .probe = adc128_probe, + .remove = adc128_remove, + .id_table = adc128_id, +}; +module_spi_driver(adc128_driver); + +MODULE_AUTHOR("Angelo Compagnucci <angelo.compagnucci@gmail.com>"); +MODULE_DESCRIPTION("Texas Instruments ADC128S052"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c new file mode 100644 index 000000000..a0e7161f0 --- /dev/null +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -0,0 +1,547 @@ +/* + * TI ADC MFD driver + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/iio/iio.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/iio/machine.h> +#include <linux/iio/driver.h> + +#include <linux/mfd/ti_am335x_tscadc.h> +#include <linux/iio/buffer.h> +#include <linux/iio/kfifo_buf.h> + +struct tiadc_device { + struct ti_tscadc_dev *mfd_tscadc; + int channels; + u8 channel_line[8]; + u8 channel_step[8]; + int buffer_en_ch_steps; + u16 data[8]; +}; + +static unsigned int tiadc_readl(struct tiadc_device *adc, unsigned int reg) +{ + return readl(adc->mfd_tscadc->tscadc_base + reg); +} + +static void tiadc_writel(struct tiadc_device *adc, unsigned int reg, + unsigned int val) +{ + writel(val, adc->mfd_tscadc->tscadc_base + reg); +} + +static u32 get_adc_step_mask(struct tiadc_device *adc_dev) +{ + u32 step_en; + + step_en = ((1 << adc_dev->channels) - 1); + step_en <<= TOTAL_STEPS - adc_dev->channels + 1; + return step_en; +} + +static u32 get_adc_chan_step_mask(struct tiadc_device *adc_dev, + struct iio_chan_spec const *chan) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(adc_dev->channel_step); i++) { + if (chan->channel == adc_dev->channel_line[i]) { + u32 step; + + step = adc_dev->channel_step[i]; + /* +1 for the charger */ + return 1 << (step + 1); + } + } + WARN_ON(1); + return 0; +} + +static u32 get_adc_step_bit(struct tiadc_device *adc_dev, int chan) +{ + return 1 << adc_dev->channel_step[chan]; +} + +static void tiadc_step_config(struct iio_dev *indio_dev) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + unsigned int stepconfig; + int i, steps = 0; + + /* + * There are 16 configurable steps and 8 analog input + * lines available which are shared between Touchscreen and ADC. + * + * Steps forwards i.e. from 0 towards 16 are used by ADC + * depending on number of input lines needed. + * Channel would represent which analog input + * needs to be given to ADC to digitalize data. + */ + + if (iio_buffer_enabled(indio_dev)) + stepconfig = STEPCONFIG_AVG_16 | STEPCONFIG_FIFO1 + | STEPCONFIG_MODE_SWCNT; + else + stepconfig = STEPCONFIG_AVG_16 | STEPCONFIG_FIFO1; + + for (i = 0; i < adc_dev->channels; i++) { + int chan; + + chan = adc_dev->channel_line[i]; + tiadc_writel(adc_dev, REG_STEPCONFIG(steps), + stepconfig | STEPCONFIG_INP(chan)); + tiadc_writel(adc_dev, REG_STEPDELAY(steps), + STEPCONFIG_OPENDLY); + adc_dev->channel_step[i] = steps; + steps++; + } +} + +static irqreturn_t tiadc_irq_h(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct tiadc_device *adc_dev = iio_priv(indio_dev); + unsigned int status, config; + status = tiadc_readl(adc_dev, REG_IRQSTATUS); + + /* + * ADC and touchscreen share the IRQ line. + * FIFO0 interrupts are used by TSC. Handle FIFO1 IRQs here only + */ + if (status & IRQENB_FIFO1OVRRUN) { + /* FIFO Overrun. Clear flag. Disable/Enable ADC to recover */ + config = tiadc_readl(adc_dev, REG_CTRL); + config &= ~(CNTRLREG_TSCSSENB); + tiadc_writel(adc_dev, REG_CTRL, config); + tiadc_writel(adc_dev, REG_IRQSTATUS, IRQENB_FIFO1OVRRUN + | IRQENB_FIFO1UNDRFLW | IRQENB_FIFO1THRES); + tiadc_writel(adc_dev, REG_CTRL, (config | CNTRLREG_TSCSSENB)); + return IRQ_HANDLED; + } else if (status & IRQENB_FIFO1THRES) { + /* Disable irq and wake worker thread */ + tiadc_writel(adc_dev, REG_IRQCLR, IRQENB_FIFO1THRES); + return IRQ_WAKE_THREAD; + } + + return IRQ_NONE; +} + +static irqreturn_t tiadc_worker_h(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct tiadc_device *adc_dev = iio_priv(indio_dev); + int i, k, fifo1count, read; + u16 *data = adc_dev->data; + + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + for (k = 0; k < fifo1count; k = k + i) { + for (i = 0; i < (indio_dev->scan_bytes)/2; i++) { + read = tiadc_readl(adc_dev, REG_FIFO1); + data[i] = read & FIFOREAD_DATA_MASK; + } + iio_push_to_buffers(indio_dev, (u8 *) data); + } + + tiadc_writel(adc_dev, REG_IRQSTATUS, IRQENB_FIFO1THRES); + tiadc_writel(adc_dev, REG_IRQENABLE, IRQENB_FIFO1THRES); + + return IRQ_HANDLED; +} + +static int tiadc_buffer_preenable(struct iio_dev *indio_dev) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + int i, fifo1count, read; + + tiadc_writel(adc_dev, REG_IRQCLR, (IRQENB_FIFO1THRES | + IRQENB_FIFO1OVRRUN | + IRQENB_FIFO1UNDRFLW)); + + /* Flush FIFO. Needed in corner cases in simultaneous tsc/adc use */ + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + for (i = 0; i < fifo1count; i++) + read = tiadc_readl(adc_dev, REG_FIFO1); + + return 0; +} + +static int tiadc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + unsigned int enb = 0; + u8 bit; + + tiadc_step_config(indio_dev); + for_each_set_bit(bit, indio_dev->active_scan_mask, adc_dev->channels) + enb |= (get_adc_step_bit(adc_dev, bit) << 1); + adc_dev->buffer_en_ch_steps = enb; + + am335x_tsc_se_set_cache(adc_dev->mfd_tscadc, enb); + + tiadc_writel(adc_dev, REG_IRQSTATUS, IRQENB_FIFO1THRES + | IRQENB_FIFO1OVRRUN | IRQENB_FIFO1UNDRFLW); + tiadc_writel(adc_dev, REG_IRQENABLE, IRQENB_FIFO1THRES + | IRQENB_FIFO1OVRRUN); + + return 0; +} + +static int tiadc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + int fifo1count, i, read; + + tiadc_writel(adc_dev, REG_IRQCLR, (IRQENB_FIFO1THRES | + IRQENB_FIFO1OVRRUN | IRQENB_FIFO1UNDRFLW)); + am335x_tsc_se_clr(adc_dev->mfd_tscadc, adc_dev->buffer_en_ch_steps); + adc_dev->buffer_en_ch_steps = 0; + + /* Flush FIFO of leftover data in the time it takes to disable adc */ + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + for (i = 0; i < fifo1count; i++) + read = tiadc_readl(adc_dev, REG_FIFO1); + + return 0; +} + +static int tiadc_buffer_postdisable(struct iio_dev *indio_dev) +{ + tiadc_step_config(indio_dev); + + return 0; +} + +static const struct iio_buffer_setup_ops tiadc_buffer_setup_ops = { + .preenable = &tiadc_buffer_preenable, + .postenable = &tiadc_buffer_postenable, + .predisable = &tiadc_buffer_predisable, + .postdisable = &tiadc_buffer_postdisable, +}; + +static int tiadc_iio_buffered_hardware_setup(struct iio_dev *indio_dev, + irqreturn_t (*pollfunc_bh)(int irq, void *p), + irqreturn_t (*pollfunc_th)(int irq, void *p), + int irq, + unsigned long flags, + const struct iio_buffer_setup_ops *setup_ops) +{ + struct iio_buffer *buffer; + int ret; + + buffer = iio_kfifo_allocate(); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + ret = request_threaded_irq(irq, pollfunc_th, pollfunc_bh, + flags, indio_dev->name, indio_dev); + if (ret) + goto error_kfifo_free; + + indio_dev->setup_ops = setup_ops; + indio_dev->modes |= INDIO_BUFFER_HARDWARE; + + return 0; + +error_kfifo_free: + iio_kfifo_free(indio_dev->buffer); + return ret; +} + +static void tiadc_iio_buffered_hardware_remove(struct iio_dev *indio_dev) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + + free_irq(adc_dev->mfd_tscadc->irq, indio_dev); + iio_kfifo_free(indio_dev->buffer); +} + + +static const char * const chan_name_ain[] = { + "AIN0", + "AIN1", + "AIN2", + "AIN3", + "AIN4", + "AIN5", + "AIN6", + "AIN7", +}; + +static int tiadc_channel_init(struct iio_dev *indio_dev, int channels) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + struct iio_chan_spec *chan_array; + struct iio_chan_spec *chan; + int i; + + indio_dev->num_channels = channels; + chan_array = kcalloc(channels, + sizeof(struct iio_chan_spec), GFP_KERNEL); + if (chan_array == NULL) + return -ENOMEM; + + chan = chan_array; + for (i = 0; i < channels; i++, chan++) { + + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->channel = adc_dev->channel_line[i]; + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + chan->datasheet_name = chan_name_ain[chan->channel]; + chan->scan_index = i; + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = 12; + chan->scan_type.storagebits = 16; + } + + indio_dev->channels = chan_array; + + return 0; +} + +static void tiadc_channels_remove(struct iio_dev *indio_dev) +{ + kfree(indio_dev->channels); +} + +static int tiadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tiadc_device *adc_dev = iio_priv(indio_dev); + int i, map_val; + unsigned int fifo1count, read, stepid; + bool found = false; + u32 step_en; + unsigned long timeout; + + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + step_en = get_adc_chan_step_mask(adc_dev, chan); + if (!step_en) + return -EINVAL; + + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + while (fifo1count--) + tiadc_readl(adc_dev, REG_FIFO1); + + am335x_tsc_se_set_once(adc_dev->mfd_tscadc, step_en); + + timeout = jiffies + usecs_to_jiffies + (IDLE_TIMEOUT * adc_dev->channels); + /* Wait for Fifo threshold interrupt */ + while (1) { + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + if (fifo1count) + break; + + if (time_after(jiffies, timeout)) { + am335x_tsc_se_adc_done(adc_dev->mfd_tscadc); + return -EAGAIN; + } + } + map_val = adc_dev->channel_step[chan->scan_index]; + + /* + * We check the complete FIFO. We programmed just one entry but in case + * something went wrong we left empty handed (-EAGAIN previously) and + * then the value apeared somehow in the FIFO we would have two entries. + * Therefore we read every item and keep only the latest version of the + * requested channel. + */ + for (i = 0; i < fifo1count; i++) { + read = tiadc_readl(adc_dev, REG_FIFO1); + stepid = read & FIFOREAD_CHNLID_MASK; + stepid = stepid >> 0x10; + + if (stepid == map_val) { + read = read & FIFOREAD_DATA_MASK; + found = true; + *val = (u16) read; + } + } + am335x_tsc_se_adc_done(adc_dev->mfd_tscadc); + + if (found == false) + return -EBUSY; + return IIO_VAL_INT; +} + +static const struct iio_info tiadc_info = { + .read_raw = &tiadc_read_raw, + .driver_module = THIS_MODULE, +}; + +static int tiadc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct tiadc_device *adc_dev; + struct device_node *node = pdev->dev.of_node; + struct property *prop; + const __be32 *cur; + int err; + u32 val; + int channels = 0; + + if (!node) { + dev_err(&pdev->dev, "Could not find valid DT data.\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct tiadc_device)); + if (indio_dev == NULL) { + dev_err(&pdev->dev, "failed to allocate iio device\n"); + return -ENOMEM; + } + adc_dev = iio_priv(indio_dev); + + adc_dev->mfd_tscadc = ti_tscadc_dev_get(pdev); + + of_property_for_each_u32(node, "ti,adc-channels", prop, cur, val) { + adc_dev->channel_line[channels] = val; + channels++; + } + adc_dev->channels = channels; + + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = dev_name(&pdev->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &tiadc_info; + + tiadc_step_config(indio_dev); + tiadc_writel(adc_dev, REG_FIFO1THR, FIFO1_THRESHOLD); + + err = tiadc_channel_init(indio_dev, adc_dev->channels); + if (err < 0) + return err; + + err = tiadc_iio_buffered_hardware_setup(indio_dev, + &tiadc_worker_h, + &tiadc_irq_h, + adc_dev->mfd_tscadc->irq, + IRQF_SHARED, + &tiadc_buffer_setup_ops); + + if (err) + goto err_free_channels; + + err = iio_device_register(indio_dev); + if (err) + goto err_buffer_unregister; + + platform_set_drvdata(pdev, indio_dev); + + return 0; + +err_buffer_unregister: + tiadc_iio_buffered_hardware_remove(indio_dev); +err_free_channels: + tiadc_channels_remove(indio_dev); + return err; +} + +static int tiadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct tiadc_device *adc_dev = iio_priv(indio_dev); + u32 step_en; + + iio_device_unregister(indio_dev); + tiadc_iio_buffered_hardware_remove(indio_dev); + tiadc_channels_remove(indio_dev); + + step_en = get_adc_step_mask(adc_dev); + am335x_tsc_se_clr(adc_dev->mfd_tscadc, step_en); + + return 0; +} + +#ifdef CONFIG_PM +static int tiadc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tiadc_device *adc_dev = iio_priv(indio_dev); + struct ti_tscadc_dev *tscadc_dev; + unsigned int idle; + + tscadc_dev = ti_tscadc_dev_get(to_platform_device(dev)); + if (!device_may_wakeup(tscadc_dev->dev)) { + idle = tiadc_readl(adc_dev, REG_CTRL); + idle &= ~(CNTRLREG_TSCSSENB); + tiadc_writel(adc_dev, REG_CTRL, (idle | + CNTRLREG_POWERDOWN)); + } + + return 0; +} + +static int tiadc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tiadc_device *adc_dev = iio_priv(indio_dev); + unsigned int restore; + + /* Make sure ADC is powered up */ + restore = tiadc_readl(adc_dev, REG_CTRL); + restore &= ~(CNTRLREG_POWERDOWN); + tiadc_writel(adc_dev, REG_CTRL, restore); + + tiadc_step_config(indio_dev); + am335x_tsc_se_set_cache(adc_dev->mfd_tscadc, + adc_dev->buffer_en_ch_steps); + return 0; +} + +static const struct dev_pm_ops tiadc_pm_ops = { + .suspend = tiadc_suspend, + .resume = tiadc_resume, +}; +#define TIADC_PM_OPS (&tiadc_pm_ops) +#else +#define TIADC_PM_OPS NULL +#endif + +static const struct of_device_id ti_adc_dt_ids[] = { + { .compatible = "ti,am3359-adc", }, + { } +}; +MODULE_DEVICE_TABLE(of, ti_adc_dt_ids); + +static struct platform_driver tiadc_driver = { + .driver = { + .name = "TI-am335x-adc", + .pm = TIADC_PM_OPS, + .of_match_table = ti_adc_dt_ids, + }, + .probe = tiadc_probe, + .remove = tiadc_remove, +}; +module_platform_driver(tiadc_driver); + +MODULE_DESCRIPTION("TI ADC controller driver"); +MODULE_AUTHOR("Rachna Patil <rachna@ti.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/twl4030-madc.c b/drivers/iio/adc/twl4030-madc.c new file mode 100644 index 000000000..4caecbea4 --- /dev/null +++ b/drivers/iio/adc/twl4030-madc.c @@ -0,0 +1,896 @@ +/* + * + * TWL4030 MADC module driver-This driver monitors the real time + * conversion of analog signals like battery temperature, + * battery type, battery level etc. + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * J Keerthy <j-keerthy@ti.com> + * + * Based on twl4030-madc.c + * Copyright (C) 2008 Nokia Corporation + * Mikko Ylinen <mikko.k.ylinen@nokia.com> + * + * Amit Kucheria <amit.kucheria@canonical.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/i2c/twl.h> +#include <linux/i2c/twl4030-madc.h> +#include <linux/module.h> +#include <linux/stddef.h> +#include <linux/mutex.h> +#include <linux/bitops.h> +#include <linux/jiffies.h> +#include <linux/types.h> +#include <linux/gfp.h> +#include <linux/err.h> + +#include <linux/iio/iio.h> + +/** + * struct twl4030_madc_data - a container for madc info + * @dev: Pointer to device structure for madc + * @lock: Mutex protecting this data structure + * @requests: Array of request struct corresponding to SW1, SW2 and RT + * @use_second_irq: IRQ selection (main or co-processor) + * @imr: Interrupt mask register of MADC + * @isr: Interrupt status register of MADC + */ +struct twl4030_madc_data { + struct device *dev; + struct mutex lock; /* mutex protecting this data structure */ + struct twl4030_madc_request requests[TWL4030_MADC_NUM_METHODS]; + bool use_second_irq; + u8 imr; + u8 isr; +}; + +static int twl4030_madc_read(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct twl4030_madc_data *madc = iio_priv(iio_dev); + struct twl4030_madc_request req; + int ret; + + req.method = madc->use_second_irq ? TWL4030_MADC_SW2 : TWL4030_MADC_SW1; + + req.channels = BIT(chan->channel); + req.active = false; + req.func_cb = NULL; + req.type = TWL4030_MADC_WAIT; + req.raw = !(mask == IIO_CHAN_INFO_PROCESSED); + req.do_avg = (mask == IIO_CHAN_INFO_AVERAGE_RAW); + + ret = twl4030_madc_conversion(&req); + if (ret < 0) + return ret; + + *val = req.rbuf[chan->channel]; + + return IIO_VAL_INT; +} + +static const struct iio_info twl4030_madc_iio_info = { + .read_raw = &twl4030_madc_read, + .driver_module = THIS_MODULE, +}; + +#define TWL4030_ADC_CHANNEL(_channel, _type, _name) { \ + .type = _type, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_AVERAGE_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED), \ + .datasheet_name = _name, \ + .indexed = 1, \ +} + +static const struct iio_chan_spec twl4030_madc_iio_channels[] = { + TWL4030_ADC_CHANNEL(0, IIO_VOLTAGE, "ADCIN0"), + TWL4030_ADC_CHANNEL(1, IIO_TEMP, "ADCIN1"), + TWL4030_ADC_CHANNEL(2, IIO_VOLTAGE, "ADCIN2"), + TWL4030_ADC_CHANNEL(3, IIO_VOLTAGE, "ADCIN3"), + TWL4030_ADC_CHANNEL(4, IIO_VOLTAGE, "ADCIN4"), + TWL4030_ADC_CHANNEL(5, IIO_VOLTAGE, "ADCIN5"), + TWL4030_ADC_CHANNEL(6, IIO_VOLTAGE, "ADCIN6"), + TWL4030_ADC_CHANNEL(7, IIO_VOLTAGE, "ADCIN7"), + TWL4030_ADC_CHANNEL(8, IIO_VOLTAGE, "ADCIN8"), + TWL4030_ADC_CHANNEL(9, IIO_VOLTAGE, "ADCIN9"), + TWL4030_ADC_CHANNEL(10, IIO_CURRENT, "ADCIN10"), + TWL4030_ADC_CHANNEL(11, IIO_VOLTAGE, "ADCIN11"), + TWL4030_ADC_CHANNEL(12, IIO_VOLTAGE, "ADCIN12"), + TWL4030_ADC_CHANNEL(13, IIO_VOLTAGE, "ADCIN13"), + TWL4030_ADC_CHANNEL(14, IIO_VOLTAGE, "ADCIN14"), + TWL4030_ADC_CHANNEL(15, IIO_VOLTAGE, "ADCIN15"), +}; + +static struct twl4030_madc_data *twl4030_madc; + +struct twl4030_prescale_divider_ratios { + s16 numerator; + s16 denominator; +}; + +static const struct twl4030_prescale_divider_ratios +twl4030_divider_ratios[16] = { + {1, 1}, /* CHANNEL 0 No Prescaler */ + {1, 1}, /* CHANNEL 1 No Prescaler */ + {6, 10}, /* CHANNEL 2 */ + {6, 10}, /* CHANNEL 3 */ + {6, 10}, /* CHANNEL 4 */ + {6, 10}, /* CHANNEL 5 */ + {6, 10}, /* CHANNEL 6 */ + {6, 10}, /* CHANNEL 7 */ + {3, 14}, /* CHANNEL 8 */ + {1, 3}, /* CHANNEL 9 */ + {1, 1}, /* CHANNEL 10 No Prescaler */ + {15, 100}, /* CHANNEL 11 */ + {1, 4}, /* CHANNEL 12 */ + {1, 1}, /* CHANNEL 13 Reserved channels */ + {1, 1}, /* CHANNEL 14 Reseved channels */ + {5, 11}, /* CHANNEL 15 */ +}; + + +/* Conversion table from -3 to 55 degrees Celcius */ +static int twl4030_therm_tbl[] = { + 30800, 29500, 28300, 27100, + 26000, 24900, 23900, 22900, 22000, 21100, 20300, 19400, 18700, + 17900, 17200, 16500, 15900, 15300, 14700, 14100, 13600, 13100, + 12600, 12100, 11600, 11200, 10800, 10400, 10000, 9630, 9280, + 8950, 8620, 8310, 8020, 7730, 7460, 7200, 6950, 6710, + 6470, 6250, 6040, 5830, 5640, 5450, 5260, 5090, 4920, + 4760, 4600, 4450, 4310, 4170, 4040, 3910, 3790, 3670, + 3550 +}; + +/* + * Structure containing the registers + * of different conversion methods supported by MADC. + * Hardware or RT real time conversion request initiated by external host + * processor for RT Signal conversions. + * External host processors can also request for non RT conversions + * SW1 and SW2 software conversions also called asynchronous or GPC request. + */ +static +const struct twl4030_madc_conversion_method twl4030_conversion_methods[] = { + [TWL4030_MADC_RT] = { + .sel = TWL4030_MADC_RTSELECT_LSB, + .avg = TWL4030_MADC_RTAVERAGE_LSB, + .rbase = TWL4030_MADC_RTCH0_LSB, + }, + [TWL4030_MADC_SW1] = { + .sel = TWL4030_MADC_SW1SELECT_LSB, + .avg = TWL4030_MADC_SW1AVERAGE_LSB, + .rbase = TWL4030_MADC_GPCH0_LSB, + .ctrl = TWL4030_MADC_CTRL_SW1, + }, + [TWL4030_MADC_SW2] = { + .sel = TWL4030_MADC_SW2SELECT_LSB, + .avg = TWL4030_MADC_SW2AVERAGE_LSB, + .rbase = TWL4030_MADC_GPCH0_LSB, + .ctrl = TWL4030_MADC_CTRL_SW2, + }, +}; + +/** + * twl4030_madc_channel_raw_read() - Function to read a particular channel value + * @madc: pointer to struct twl4030_madc_data + * @reg: lsb of ADC Channel + * + * Return: 0 on success, an error code otherwise. + */ +static int twl4030_madc_channel_raw_read(struct twl4030_madc_data *madc, u8 reg) +{ + u16 val; + int ret; + /* + * For each ADC channel, we have MSB and LSB register pair. MSB address + * is always LSB address+1. reg parameter is the address of LSB register + */ + ret = twl_i2c_read_u16(TWL4030_MODULE_MADC, &val, reg); + if (ret) { + dev_err(madc->dev, "unable to read register 0x%X\n", reg); + return ret; + } + + return (int)(val >> 6); +} + +/* + * Return battery temperature in degrees Celsius + * Or < 0 on failure. + */ +static int twl4030battery_temperature(int raw_volt) +{ + u8 val; + int temp, curr, volt, res, ret; + + volt = (raw_volt * TEMP_STEP_SIZE) / TEMP_PSR_R; + /* Getting and calculating the supply current in micro amperes */ + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, &val, + REG_BCICTL2); + if (ret < 0) + return ret; + + curr = ((val & TWL4030_BCI_ITHEN) + 1) * 10; + /* Getting and calculating the thermistor resistance in ohms */ + res = volt * 1000 / curr; + /* calculating temperature */ + for (temp = 58; temp >= 0; temp--) { + int actual = twl4030_therm_tbl[temp]; + if ((actual - res) >= 0) + break; + } + + return temp + 1; +} + +static int twl4030battery_current(int raw_volt) +{ + int ret; + u8 val; + + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, &val, + TWL4030_BCI_BCICTL1); + if (ret) + return ret; + if (val & TWL4030_BCI_CGAIN) /* slope of 0.44 mV/mA */ + return (raw_volt * CURR_STEP_SIZE) / CURR_PSR_R1; + else /* slope of 0.88 mV/mA */ + return (raw_volt * CURR_STEP_SIZE) / CURR_PSR_R2; +} + +/* + * Function to read channel values + * @madc - pointer to twl4030_madc_data struct + * @reg_base - Base address of the first channel + * @Channels - 16 bit bitmap. If the bit is set, channel's value is read + * @buf - The channel values are stored here. if read fails error + * @raw - Return raw values without conversion + * value is stored + * Returns the number of successfully read channels. + */ +static int twl4030_madc_read_channels(struct twl4030_madc_data *madc, + u8 reg_base, unsigned + long channels, int *buf, + bool raw) +{ + int count = 0; + int i; + u8 reg; + + for_each_set_bit(i, &channels, TWL4030_MADC_MAX_CHANNELS) { + reg = reg_base + (2 * i); + buf[i] = twl4030_madc_channel_raw_read(madc, reg); + if (buf[i] < 0) { + dev_err(madc->dev, "Unable to read register 0x%X\n", + reg); + return buf[i]; + } + if (raw) { + count++; + continue; + } + switch (i) { + case 10: + buf[i] = twl4030battery_current(buf[i]); + if (buf[i] < 0) { + dev_err(madc->dev, "err reading current\n"); + return buf[i]; + } else { + count++; + buf[i] = buf[i] - 750; + } + break; + case 1: + buf[i] = twl4030battery_temperature(buf[i]); + if (buf[i] < 0) { + dev_err(madc->dev, "err reading temperature\n"); + return buf[i]; + } else { + buf[i] -= 3; + count++; + } + break; + default: + count++; + /* Analog Input (V) = conv_result * step_size / R + * conv_result = decimal value of 10-bit conversion + * result + * step size = 1.5 / (2 ^ 10 -1) + * R = Prescaler ratio for input channels. + * Result given in mV hence multiplied by 1000. + */ + buf[i] = (buf[i] * 3 * 1000 * + twl4030_divider_ratios[i].denominator) + / (2 * 1023 * + twl4030_divider_ratios[i].numerator); + } + } + + return count; +} + +/* + * Enables irq. + * @madc - pointer to twl4030_madc_data struct + * @id - irq number to be enabled + * can take one of TWL4030_MADC_RT, TWL4030_MADC_SW1, TWL4030_MADC_SW2 + * corresponding to RT, SW1, SW2 conversion requests. + * If the i2c read fails it returns an error else returns 0. + */ +static int twl4030_madc_enable_irq(struct twl4030_madc_data *madc, u8 id) +{ + u8 val; + int ret; + + ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &val, madc->imr); + if (ret) { + dev_err(madc->dev, "unable to read imr register 0x%X\n", + madc->imr); + return ret; + } + + val &= ~(1 << id); + ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, val, madc->imr); + if (ret) { + dev_err(madc->dev, + "unable to write imr register 0x%X\n", madc->imr); + return ret; + } + + return 0; +} + +/* + * Disables irq. + * @madc - pointer to twl4030_madc_data struct + * @id - irq number to be disabled + * can take one of TWL4030_MADC_RT, TWL4030_MADC_SW1, TWL4030_MADC_SW2 + * corresponding to RT, SW1, SW2 conversion requests. + * Returns error if i2c read/write fails. + */ +static int twl4030_madc_disable_irq(struct twl4030_madc_data *madc, u8 id) +{ + u8 val; + int ret; + + ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &val, madc->imr); + if (ret) { + dev_err(madc->dev, "unable to read imr register 0x%X\n", + madc->imr); + return ret; + } + val |= (1 << id); + ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, val, madc->imr); + if (ret) { + dev_err(madc->dev, + "unable to write imr register 0x%X\n", madc->imr); + return ret; + } + + return 0; +} + +static irqreturn_t twl4030_madc_threaded_irq_handler(int irq, void *_madc) +{ + struct twl4030_madc_data *madc = _madc; + const struct twl4030_madc_conversion_method *method; + u8 isr_val, imr_val; + int i, len, ret; + struct twl4030_madc_request *r; + + mutex_lock(&madc->lock); + ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &isr_val, madc->isr); + if (ret) { + dev_err(madc->dev, "unable to read isr register 0x%X\n", + madc->isr); + goto err_i2c; + } + ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, &imr_val, madc->imr); + if (ret) { + dev_err(madc->dev, "unable to read imr register 0x%X\n", + madc->imr); + goto err_i2c; + } + isr_val &= ~imr_val; + for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) { + if (!(isr_val & (1 << i))) + continue; + ret = twl4030_madc_disable_irq(madc, i); + if (ret < 0) + dev_dbg(madc->dev, "Disable interrupt failed %d\n", i); + madc->requests[i].result_pending = 1; + } + for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) { + r = &madc->requests[i]; + /* No pending results for this method, move to next one */ + if (!r->result_pending) + continue; + method = &twl4030_conversion_methods[r->method]; + /* Read results */ + len = twl4030_madc_read_channels(madc, method->rbase, + r->channels, r->rbuf, r->raw); + /* Return results to caller */ + if (r->func_cb != NULL) { + r->func_cb(len, r->channels, r->rbuf); + r->func_cb = NULL; + } + /* Free request */ + r->result_pending = 0; + r->active = 0; + } + mutex_unlock(&madc->lock); + + return IRQ_HANDLED; + +err_i2c: + /* + * In case of error check whichever request is active + * and service the same. + */ + for (i = 0; i < TWL4030_MADC_NUM_METHODS; i++) { + r = &madc->requests[i]; + if (r->active == 0) + continue; + method = &twl4030_conversion_methods[r->method]; + /* Read results */ + len = twl4030_madc_read_channels(madc, method->rbase, + r->channels, r->rbuf, r->raw); + /* Return results to caller */ + if (r->func_cb != NULL) { + r->func_cb(len, r->channels, r->rbuf); + r->func_cb = NULL; + } + /* Free request */ + r->result_pending = 0; + r->active = 0; + } + mutex_unlock(&madc->lock); + + return IRQ_HANDLED; +} + +static int twl4030_madc_set_irq(struct twl4030_madc_data *madc, + struct twl4030_madc_request *req) +{ + struct twl4030_madc_request *p; + int ret; + + p = &madc->requests[req->method]; + memcpy(p, req, sizeof(*req)); + ret = twl4030_madc_enable_irq(madc, req->method); + if (ret < 0) { + dev_err(madc->dev, "enable irq failed!!\n"); + return ret; + } + + return 0; +} + +/* + * Function which enables the madc conversion + * by writing to the control register. + * @madc - pointer to twl4030_madc_data struct + * @conv_method - can be TWL4030_MADC_RT, TWL4030_MADC_SW2, TWL4030_MADC_SW1 + * corresponding to RT SW1 or SW2 conversion methods. + * Returns 0 if succeeds else a negative error value + */ +static int twl4030_madc_start_conversion(struct twl4030_madc_data *madc, + int conv_method) +{ + const struct twl4030_madc_conversion_method *method; + int ret = 0; + + if (conv_method != TWL4030_MADC_SW1 && conv_method != TWL4030_MADC_SW2) + return -ENOTSUPP; + + method = &twl4030_conversion_methods[conv_method]; + ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, TWL4030_MADC_SW_START, + method->ctrl); + if (ret) { + dev_err(madc->dev, "unable to write ctrl register 0x%X\n", + method->ctrl); + return ret; + } + + return 0; +} + +/* + * Function that waits for conversion to be ready + * @madc - pointer to twl4030_madc_data struct + * @timeout_ms - timeout value in milliseconds + * @status_reg - ctrl register + * returns 0 if succeeds else a negative error value + */ +static int twl4030_madc_wait_conversion_ready(struct twl4030_madc_data *madc, + unsigned int timeout_ms, + u8 status_reg) +{ + unsigned long timeout; + int ret; + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + do { + u8 reg; + + ret = twl_i2c_read_u8(TWL4030_MODULE_MADC, ®, status_reg); + if (ret) { + dev_err(madc->dev, + "unable to read status register 0x%X\n", + status_reg); + return ret; + } + if (!(reg & TWL4030_MADC_BUSY) && (reg & TWL4030_MADC_EOC_SW)) + return 0; + usleep_range(500, 2000); + } while (!time_after(jiffies, timeout)); + dev_err(madc->dev, "conversion timeout!\n"); + + return -EAGAIN; +} + +/* + * An exported function which can be called from other kernel drivers. + * @req twl4030_madc_request structure + * req->rbuf will be filled with read values of channels based on the + * channel index. If a particular channel reading fails there will + * be a negative error value in the corresponding array element. + * returns 0 if succeeds else error value + */ +int twl4030_madc_conversion(struct twl4030_madc_request *req) +{ + const struct twl4030_madc_conversion_method *method; + int ret; + + if (!req || !twl4030_madc) + return -EINVAL; + + mutex_lock(&twl4030_madc->lock); + if (req->method < TWL4030_MADC_RT || req->method > TWL4030_MADC_SW2) { + ret = -EINVAL; + goto out; + } + /* Do we have a conversion request ongoing */ + if (twl4030_madc->requests[req->method].active) { + ret = -EBUSY; + goto out; + } + method = &twl4030_conversion_methods[req->method]; + /* Select channels to be converted */ + ret = twl_i2c_write_u16(TWL4030_MODULE_MADC, req->channels, method->sel); + if (ret) { + dev_err(twl4030_madc->dev, + "unable to write sel register 0x%X\n", method->sel); + goto out; + } + /* Select averaging for all channels if do_avg is set */ + if (req->do_avg) { + ret = twl_i2c_write_u16(TWL4030_MODULE_MADC, req->channels, + method->avg); + if (ret) { + dev_err(twl4030_madc->dev, + "unable to write avg register 0x%X\n", + method->avg); + goto out; + } + } + if (req->type == TWL4030_MADC_IRQ_ONESHOT && req->func_cb != NULL) { + ret = twl4030_madc_set_irq(twl4030_madc, req); + if (ret < 0) + goto out; + ret = twl4030_madc_start_conversion(twl4030_madc, req->method); + if (ret < 0) + goto out; + twl4030_madc->requests[req->method].active = 1; + ret = 0; + goto out; + } + /* With RT method we should not be here anymore */ + if (req->method == TWL4030_MADC_RT) { + ret = -EINVAL; + goto out; + } + ret = twl4030_madc_start_conversion(twl4030_madc, req->method); + if (ret < 0) + goto out; + twl4030_madc->requests[req->method].active = 1; + /* Wait until conversion is ready (ctrl register returns EOC) */ + ret = twl4030_madc_wait_conversion_ready(twl4030_madc, 5, method->ctrl); + if (ret) { + twl4030_madc->requests[req->method].active = 0; + goto out; + } + ret = twl4030_madc_read_channels(twl4030_madc, method->rbase, + req->channels, req->rbuf, req->raw); + twl4030_madc->requests[req->method].active = 0; + +out: + mutex_unlock(&twl4030_madc->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(twl4030_madc_conversion); + +int twl4030_get_madc_conversion(int channel_no) +{ + struct twl4030_madc_request req; + int temp = 0; + int ret; + + req.channels = (1 << channel_no); + req.method = TWL4030_MADC_SW2; + req.active = 0; + req.raw = 0; + req.func_cb = NULL; + ret = twl4030_madc_conversion(&req); + if (ret < 0) + return ret; + if (req.rbuf[channel_no] > 0) + temp = req.rbuf[channel_no]; + + return temp; +} +EXPORT_SYMBOL_GPL(twl4030_get_madc_conversion); + +/** + * twl4030_madc_set_current_generator() - setup bias current + * + * @madc: pointer to twl4030_madc_data struct + * @chan: can be one of the two values: + * TWL4030_BCI_ITHEN + * Enables bias current for main battery type reading + * TWL4030_BCI_TYPEN + * Enables bias current for main battery temperature sensing + * @on: enable or disable chan. + * + * Function to enable or disable bias current for + * main battery type reading or temperature sensing + */ +static int twl4030_madc_set_current_generator(struct twl4030_madc_data *madc, + int chan, int on) +{ + int ret; + int regmask; + u8 regval; + + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, + ®val, TWL4030_BCI_BCICTL1); + if (ret) { + dev_err(madc->dev, "unable to read BCICTL1 reg 0x%X", + TWL4030_BCI_BCICTL1); + return ret; + } + + regmask = chan ? TWL4030_BCI_ITHEN : TWL4030_BCI_TYPEN; + if (on) + regval |= regmask; + else + regval &= ~regmask; + + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + regval, TWL4030_BCI_BCICTL1); + if (ret) { + dev_err(madc->dev, "unable to write BCICTL1 reg 0x%X\n", + TWL4030_BCI_BCICTL1); + return ret; + } + + return 0; +} + +/* + * Function that sets MADC software power on bit to enable MADC + * @madc - pointer to twl4030_madc_data struct + * @on - Enable or disable MADC software power on bit. + * returns error if i2c read/write fails else 0 + */ +static int twl4030_madc_set_power(struct twl4030_madc_data *madc, int on) +{ + u8 regval; + int ret; + + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, + ®val, TWL4030_MADC_CTRL1); + if (ret) { + dev_err(madc->dev, "unable to read madc ctrl1 reg 0x%X\n", + TWL4030_MADC_CTRL1); + return ret; + } + if (on) + regval |= TWL4030_MADC_MADCON; + else + regval &= ~TWL4030_MADC_MADCON; + ret = twl_i2c_write_u8(TWL4030_MODULE_MADC, regval, TWL4030_MADC_CTRL1); + if (ret) { + dev_err(madc->dev, "unable to write madc ctrl1 reg 0x%X\n", + TWL4030_MADC_CTRL1); + return ret; + } + + return 0; +} + +/* + * Initialize MADC and request for threaded irq + */ +static int twl4030_madc_probe(struct platform_device *pdev) +{ + struct twl4030_madc_data *madc; + struct twl4030_madc_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; + int irq, ret; + u8 regval; + struct iio_dev *iio_dev = NULL; + + if (!pdata && !np) { + dev_err(&pdev->dev, "neither platform data nor Device Tree node available\n"); + return -EINVAL; + } + + iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*madc)); + if (!iio_dev) { + dev_err(&pdev->dev, "failed allocating iio device\n"); + return -ENOMEM; + } + + madc = iio_priv(iio_dev); + madc->dev = &pdev->dev; + + iio_dev->name = dev_name(&pdev->dev); + iio_dev->dev.parent = &pdev->dev; + iio_dev->dev.of_node = pdev->dev.of_node; + iio_dev->info = &twl4030_madc_iio_info; + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->channels = twl4030_madc_iio_channels; + iio_dev->num_channels = ARRAY_SIZE(twl4030_madc_iio_channels); + + /* + * Phoenix provides 2 interrupt lines. The first one is connected to + * the OMAP. The other one can be connected to the other processor such + * as modem. Hence two separate ISR and IMR registers. + */ + if (pdata) + madc->use_second_irq = (pdata->irq_line != 1); + else + madc->use_second_irq = of_property_read_bool(np, + "ti,system-uses-second-madc-irq"); + + madc->imr = madc->use_second_irq ? TWL4030_MADC_IMR2 : + TWL4030_MADC_IMR1; + madc->isr = madc->use_second_irq ? TWL4030_MADC_ISR2 : + TWL4030_MADC_ISR1; + + ret = twl4030_madc_set_power(madc, 1); + if (ret < 0) + return ret; + ret = twl4030_madc_set_current_generator(madc, 0, 1); + if (ret < 0) + goto err_current_generator; + + ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, + ®val, TWL4030_BCI_BCICTL1); + if (ret) { + dev_err(&pdev->dev, "unable to read reg BCI CTL1 0x%X\n", + TWL4030_BCI_BCICTL1); + goto err_i2c; + } + regval |= TWL4030_BCI_MESBAT; + ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, + regval, TWL4030_BCI_BCICTL1); + if (ret) { + dev_err(&pdev->dev, "unable to write reg BCI Ctl1 0x%X\n", + TWL4030_BCI_BCICTL1); + goto err_i2c; + } + + /* Check that MADC clock is on */ + ret = twl_i2c_read_u8(TWL4030_MODULE_INTBR, ®val, TWL4030_REG_GPBR1); + if (ret) { + dev_err(&pdev->dev, "unable to read reg GPBR1 0x%X\n", + TWL4030_REG_GPBR1); + goto err_i2c; + } + + /* If MADC clk is not on, turn it on */ + if (!(regval & TWL4030_GPBR1_MADC_HFCLK_EN)) { + dev_info(&pdev->dev, "clk disabled, enabling\n"); + regval |= TWL4030_GPBR1_MADC_HFCLK_EN; + ret = twl_i2c_write_u8(TWL4030_MODULE_INTBR, regval, + TWL4030_REG_GPBR1); + if (ret) { + dev_err(&pdev->dev, "unable to write reg GPBR1 0x%X\n", + TWL4030_REG_GPBR1); + goto err_i2c; + } + } + + platform_set_drvdata(pdev, iio_dev); + mutex_init(&madc->lock); + + irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + twl4030_madc_threaded_irq_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "twl4030_madc", madc); + if (ret) { + dev_err(&pdev->dev, "could not request irq\n"); + goto err_i2c; + } + twl4030_madc = madc; + + ret = iio_device_register(iio_dev); + if (ret) { + dev_err(&pdev->dev, "could not register iio device\n"); + goto err_i2c; + } + + return 0; + +err_i2c: + twl4030_madc_set_current_generator(madc, 0, 0); +err_current_generator: + twl4030_madc_set_power(madc, 0); + return ret; +} + +static int twl4030_madc_remove(struct platform_device *pdev) +{ + struct iio_dev *iio_dev = platform_get_drvdata(pdev); + struct twl4030_madc_data *madc = iio_priv(iio_dev); + + iio_device_unregister(iio_dev); + + twl4030_madc_set_current_generator(madc, 0, 0); + twl4030_madc_set_power(madc, 0); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id twl_madc_of_match[] = { + { .compatible = "ti,twl4030-madc", }, + { }, +}; +MODULE_DEVICE_TABLE(of, twl_madc_of_match); +#endif + +static struct platform_driver twl4030_madc_driver = { + .probe = twl4030_madc_probe, + .remove = twl4030_madc_remove, + .driver = { + .name = "twl4030_madc", + .of_match_table = of_match_ptr(twl_madc_of_match), + }, +}; + +module_platform_driver(twl4030_madc_driver); + +MODULE_DESCRIPTION("TWL4030 ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("J Keerthy"); +MODULE_ALIAS("platform:twl4030_madc"); diff --git a/drivers/iio/adc/twl6030-gpadc.c b/drivers/iio/adc/twl6030-gpadc.c new file mode 100644 index 000000000..df12c57e6 --- /dev/null +++ b/drivers/iio/adc/twl6030-gpadc.c @@ -0,0 +1,1009 @@ +/* + * TWL6030 GPADC module driver + * + * Copyright (C) 2009-2013 Texas Instruments Inc. + * Nishant Kamat <nskamat@ti.com> + * Balaji T K <balajitk@ti.com> + * Graeme Gregory <gg@slimlogic.co.uk> + * Girish S Ghongdemath <girishsg@ti.com> + * Ambresh K <ambresh@ti.com> + * Oleksandr Kozaruk <oleksandr.kozaruk@ti.com + * + * Based on twl4030-madc.c + * Copyright (C) 2008 Nokia Corporation + * Mikko Ylinen <mikko.k.ylinen@nokia.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/i2c/twl.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define DRIVER_NAME "twl6030_gpadc" + +/* + * twl6030 per TRM has 17 channels, and twl6032 has 19 channels + * 2 test network channels are not used, + * 2 die temperature channels are not used either, as it is not + * defined how to convert ADC value to temperature + */ +#define TWL6030_GPADC_USED_CHANNELS 13 +#define TWL6030_GPADC_MAX_CHANNELS 15 +#define TWL6032_GPADC_USED_CHANNELS 15 +#define TWL6032_GPADC_MAX_CHANNELS 19 +#define TWL6030_GPADC_NUM_TRIM_REGS 16 + +#define TWL6030_GPADC_CTRL_P1 0x05 + +#define TWL6032_GPADC_GPSELECT_ISB 0x07 +#define TWL6032_GPADC_CTRL_P1 0x08 + +#define TWL6032_GPADC_GPCH0_LSB 0x0d +#define TWL6032_GPADC_GPCH0_MSB 0x0e + +#define TWL6030_GPADC_CTRL_P1_SP1 BIT(3) + +#define TWL6030_GPADC_GPCH0_LSB (0x29) + +#define TWL6030_GPADC_RT_SW1_EOC_MASK BIT(5) + +#define TWL6030_GPADC_TRIM1 0xCD + +#define TWL6030_REG_TOGGLE1 0x90 +#define TWL6030_GPADCS BIT(1) +#define TWL6030_GPADCR BIT(0) + +/** + * struct twl6030_chnl_calib - channel calibration + * @gain: slope coefficient for ideal curve + * @gain_error: gain error + * @offset_error: offset of the real curve + */ +struct twl6030_chnl_calib { + s32 gain; + s32 gain_error; + s32 offset_error; +}; + +/** + * struct twl6030_ideal_code - GPADC calibration parameters + * GPADC is calibrated in two points: close to the beginning and + * to the and of the measurable input range + * + * @channel: channel number + * @code1: ideal code for the input at the beginning + * @code2: ideal code for at the end of the range + * @volt1: voltage input at the beginning(low voltage) + * @volt2: voltage input at the end(high voltage) + */ +struct twl6030_ideal_code { + int channel; + u16 code1; + u16 code2; + u16 volt1; + u16 volt2; +}; + +struct twl6030_gpadc_data; + +/** + * struct twl6030_gpadc_platform_data - platform specific data + * @nchannels: number of GPADC channels + * @iio_channels: iio channels + * @twl6030_ideal: pointer to calibration parameters + * @start_conversion: pointer to ADC start conversion function + * @channel_to_reg pointer to ADC function to convert channel to + * register address for reading conversion result + * @calibrate: pointer to calibration function + */ +struct twl6030_gpadc_platform_data { + const int nchannels; + const struct iio_chan_spec *iio_channels; + const struct twl6030_ideal_code *ideal; + int (*start_conversion)(int channel); + u8 (*channel_to_reg)(int channel); + int (*calibrate)(struct twl6030_gpadc_data *gpadc); +}; + +/** + * struct twl6030_gpadc_data - GPADC data + * @dev: device pointer + * @lock: mutual exclusion lock for the structure + * @irq_complete: completion to signal end of conversion + * @twl6030_cal_tbl: pointer to calibration data for each + * channel with gain error and offset + * @pdata: pointer to device specific data + */ +struct twl6030_gpadc_data { + struct device *dev; + struct mutex lock; + struct completion irq_complete; + struct twl6030_chnl_calib *twl6030_cal_tbl; + const struct twl6030_gpadc_platform_data *pdata; +}; + +/* + * channels 11, 12, 13, 15 and 16 have no calibration data + * calibration offset is same for channels 1, 3, 4, 5 + * + * The data is taken from GPADC_TRIM registers description. + * GPADC_TRIM registers keep difference between the code measured + * at volt1 and volt2 input voltages and corresponding code1 and code2 + */ +static const struct twl6030_ideal_code + twl6030_ideal[TWL6030_GPADC_USED_CHANNELS] = { + [0] = { /* ch 0, external, battery type, resistor value */ + .channel = 0, + .code1 = 116, + .code2 = 745, + .volt1 = 141, + .volt2 = 910, + }, + [1] = { /* ch 1, external, battery temperature, NTC resistor value */ + .channel = 1, + .code1 = 82, + .code2 = 900, + .volt1 = 100, + .volt2 = 1100, + }, + [2] = { /* ch 2, external, audio accessory/general purpose */ + .channel = 2, + .code1 = 55, + .code2 = 818, + .volt1 = 101, + .volt2 = 1499, + }, + [3] = { /* ch 3, external, general purpose */ + .channel = 3, + .code1 = 82, + .code2 = 900, + .volt1 = 100, + .volt2 = 1100, + }, + [4] = { /* ch 4, external, temperature measurement/general purpose */ + .channel = 4, + .code1 = 82, + .code2 = 900, + .volt1 = 100, + .volt2 = 1100, + }, + [5] = { /* ch 5, external, general purpose */ + .channel = 5, + .code1 = 82, + .code2 = 900, + .volt1 = 100, + .volt2 = 1100, + }, + [6] = { /* ch 6, external, general purpose */ + .channel = 6, + .code1 = 82, + .code2 = 900, + .volt1 = 100, + .volt2 = 1100, + }, + [7] = { /* ch 7, internal, main battery */ + .channel = 7, + .code1 = 614, + .code2 = 941, + .volt1 = 3001, + .volt2 = 4599, + }, + [8] = { /* ch 8, internal, backup battery */ + .channel = 8, + .code1 = 82, + .code2 = 688, + .volt1 = 501, + .volt2 = 4203, + }, + [9] = { /* ch 9, internal, external charger input */ + .channel = 9, + .code1 = 182, + .code2 = 818, + .volt1 = 2001, + .volt2 = 8996, + }, + [10] = { /* ch 10, internal, VBUS */ + .channel = 10, + .code1 = 149, + .code2 = 818, + .volt1 = 1001, + .volt2 = 5497, + }, + [11] = { /* ch 11, internal, VBUS charging current */ + .channel = 11, + }, + /* ch 12, internal, Die temperature */ + /* ch 13, internal, Die temperature */ + [12] = { /* ch 14, internal, USB ID line */ + .channel = 14, + .code1 = 48, + .code2 = 714, + .volt1 = 323, + .volt2 = 4800, + }, +}; + +static const struct twl6030_ideal_code + twl6032_ideal[TWL6032_GPADC_USED_CHANNELS] = { + [0] = { /* ch 0, external, battery type, resistor value */ + .channel = 0, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [1] = { /* ch 1, external, battery temperature, NTC resistor value */ + .channel = 1, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [2] = { /* ch 2, external, audio accessory/general purpose */ + .channel = 2, + .code1 = 1441, + .code2 = 3276, + .volt1 = 660, + .volt2 = 1500, + }, + [3] = { /* ch 3, external, temperature with external diode/general + purpose */ + .channel = 3, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [4] = { /* ch 4, external, temperature measurement/general purpose */ + .channel = 4, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [5] = { /* ch 5, external, general purpose */ + .channel = 5, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [6] = { /* ch 6, external, general purpose */ + .channel = 6, + .code1 = 1441, + .code2 = 3276, + .volt1 = 440, + .volt2 = 1000, + }, + [7] = { /* ch7, internal, system supply */ + .channel = 7, + .code1 = 1441, + .code2 = 3276, + .volt1 = 2200, + .volt2 = 5000, + }, + [8] = { /* ch8, internal, backup battery */ + .channel = 8, + .code1 = 1441, + .code2 = 3276, + .volt1 = 2200, + .volt2 = 5000, + }, + [9] = { /* ch 9, internal, external charger input */ + .channel = 9, + .code1 = 1441, + .code2 = 3276, + .volt1 = 3960, + .volt2 = 9000, + }, + [10] = { /* ch10, internal, VBUS */ + .channel = 10, + .code1 = 150, + .code2 = 751, + .volt1 = 1000, + .volt2 = 5000, + }, + [11] = { /* ch 11, internal, VBUS DC-DC output current */ + .channel = 11, + .code1 = 1441, + .code2 = 3276, + .volt1 = 660, + .volt2 = 1500, + }, + /* ch 12, internal, Die temperature */ + /* ch 13, internal, Die temperature */ + [12] = { /* ch 14, internal, USB ID line */ + .channel = 14, + .code1 = 1441, + .code2 = 3276, + .volt1 = 2420, + .volt2 = 5500, + }, + /* ch 15, internal, test network */ + /* ch 16, internal, test network */ + [13] = { /* ch 17, internal, battery charging current */ + .channel = 17, + }, + [14] = { /* ch 18, internal, battery voltage */ + .channel = 18, + .code1 = 1441, + .code2 = 3276, + .volt1 = 2200, + .volt2 = 5000, + }, +}; + +static inline int twl6030_gpadc_write(u8 reg, u8 val) +{ + return twl_i2c_write_u8(TWL6030_MODULE_GPADC, val, reg); +} + +static inline int twl6030_gpadc_read(u8 reg, u8 *val) +{ + + return twl_i2c_read(TWL6030_MODULE_GPADC, val, reg, 2); +} + +static int twl6030_gpadc_enable_irq(u8 mask) +{ + int ret; + + ret = twl6030_interrupt_unmask(mask, REG_INT_MSK_LINE_B); + if (ret < 0) + return ret; + + ret = twl6030_interrupt_unmask(mask, REG_INT_MSK_STS_B); + + return ret; +} + +static void twl6030_gpadc_disable_irq(u8 mask) +{ + twl6030_interrupt_mask(mask, REG_INT_MSK_LINE_B); + twl6030_interrupt_mask(mask, REG_INT_MSK_STS_B); +} + +static irqreturn_t twl6030_gpadc_irq_handler(int irq, void *indio_dev) +{ + struct twl6030_gpadc_data *gpadc = iio_priv(indio_dev); + + complete(&gpadc->irq_complete); + + return IRQ_HANDLED; +} + +static int twl6030_start_conversion(int channel) +{ + return twl6030_gpadc_write(TWL6030_GPADC_CTRL_P1, + TWL6030_GPADC_CTRL_P1_SP1); +} + +static int twl6032_start_conversion(int channel) +{ + int ret; + + ret = twl6030_gpadc_write(TWL6032_GPADC_GPSELECT_ISB, channel); + if (ret) + return ret; + + return twl6030_gpadc_write(TWL6032_GPADC_CTRL_P1, + TWL6030_GPADC_CTRL_P1_SP1); +} + +static u8 twl6030_channel_to_reg(int channel) +{ + return TWL6030_GPADC_GPCH0_LSB + 2 * channel; +} + +static u8 twl6032_channel_to_reg(int channel) +{ + /* + * for any prior chosen channel, when the conversion is ready + * the result is avalable in GPCH0_LSB, GPCH0_MSB. + */ + + return TWL6032_GPADC_GPCH0_LSB; +} + +static int twl6030_gpadc_lookup(const struct twl6030_ideal_code *ideal, + int channel, int size) +{ + int i; + + for (i = 0; i < size; i++) + if (ideal[i].channel == channel) + break; + + return i; +} + +static int twl6030_channel_calibrated(const struct twl6030_gpadc_platform_data + *pdata, int channel) +{ + const struct twl6030_ideal_code *ideal = pdata->ideal; + int i; + + i = twl6030_gpadc_lookup(ideal, channel, pdata->nchannels); + /* not calibrated channels have 0 in all structure members */ + return pdata->ideal[i].code2; +} + +static int twl6030_gpadc_make_correction(struct twl6030_gpadc_data *gpadc, + int channel, int raw_code) +{ + const struct twl6030_ideal_code *ideal = gpadc->pdata->ideal; + int corrected_code; + int i; + + i = twl6030_gpadc_lookup(ideal, channel, gpadc->pdata->nchannels); + corrected_code = ((raw_code * 1000) - + gpadc->twl6030_cal_tbl[i].offset_error) / + gpadc->twl6030_cal_tbl[i].gain_error; + + return corrected_code; +} + +static int twl6030_gpadc_get_raw(struct twl6030_gpadc_data *gpadc, + int channel, int *res) +{ + u8 reg = gpadc->pdata->channel_to_reg(channel); + __le16 val; + int raw_code; + int ret; + + ret = twl6030_gpadc_read(reg, (u8 *)&val); + if (ret) { + dev_dbg(gpadc->dev, "unable to read register 0x%X\n", reg); + return ret; + } + + raw_code = le16_to_cpu(val); + dev_dbg(gpadc->dev, "GPADC raw code: %d", raw_code); + + if (twl6030_channel_calibrated(gpadc->pdata, channel)) + *res = twl6030_gpadc_make_correction(gpadc, channel, raw_code); + else + *res = raw_code; + + return ret; +} + +static int twl6030_gpadc_get_processed(struct twl6030_gpadc_data *gpadc, + int channel, int *val) +{ + const struct twl6030_ideal_code *ideal = gpadc->pdata->ideal; + int corrected_code; + int channel_value; + int i; + int ret; + + ret = twl6030_gpadc_get_raw(gpadc, channel, &corrected_code); + if (ret) + return ret; + + i = twl6030_gpadc_lookup(ideal, channel, gpadc->pdata->nchannels); + channel_value = corrected_code * + gpadc->twl6030_cal_tbl[i].gain; + + /* Shift back into mV range */ + channel_value /= 1000; + + dev_dbg(gpadc->dev, "GPADC corrected code: %d", corrected_code); + dev_dbg(gpadc->dev, "GPADC value: %d", channel_value); + + *val = channel_value; + + return ret; +} + +static int twl6030_gpadc_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct twl6030_gpadc_data *gpadc = iio_priv(indio_dev); + int ret; + long timeout; + + mutex_lock(&gpadc->lock); + + ret = gpadc->pdata->start_conversion(chan->channel); + if (ret) { + dev_err(gpadc->dev, "failed to start conversion\n"); + goto err; + } + /* wait for conversion to complete */ + timeout = wait_for_completion_interruptible_timeout( + &gpadc->irq_complete, msecs_to_jiffies(5000)); + if (timeout == 0) { + ret = -ETIMEDOUT; + goto err; + } else if (timeout < 0) { + ret = -EINTR; + goto err; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = twl6030_gpadc_get_raw(gpadc, chan->channel, val); + ret = ret ? -EIO : IIO_VAL_INT; + break; + + case IIO_CHAN_INFO_PROCESSED: + ret = twl6030_gpadc_get_processed(gpadc, chan->channel, val); + ret = ret ? -EIO : IIO_VAL_INT; + break; + + default: + break; + } +err: + mutex_unlock(&gpadc->lock); + + return ret; +} + +/* + * The GPADC channels are calibrated using a two point calibration method. + * The channels measured with two known values: volt1 and volt2, and + * ideal corresponding output codes are known: code1, code2. + * The difference(d1, d2) between ideal and measured codes stored in trim + * registers. + * The goal is to find offset and gain of the real curve for each calibrated + * channel. + * gain: k = 1 + ((d2 - d1) / (x2 - x1)) + * offset: b = d1 + (k - 1) * x1 + */ +static void twl6030_calibrate_channel(struct twl6030_gpadc_data *gpadc, + int channel, int d1, int d2) +{ + int b, k, gain, x1, x2, i; + const struct twl6030_ideal_code *ideal = gpadc->pdata->ideal; + + i = twl6030_gpadc_lookup(ideal, channel, gpadc->pdata->nchannels); + + /* Gain */ + gain = ((ideal[i].volt2 - ideal[i].volt1) * 1000) / + (ideal[i].code2 - ideal[i].code1); + + x1 = ideal[i].code1; + x2 = ideal[i].code2; + + /* k - real curve gain */ + k = 1000 + (((d2 - d1) * 1000) / (x2 - x1)); + + /* b - offset of the real curve gain */ + b = (d1 * 1000) - (k - 1000) * x1; + + gpadc->twl6030_cal_tbl[i].gain = gain; + gpadc->twl6030_cal_tbl[i].gain_error = k; + gpadc->twl6030_cal_tbl[i].offset_error = b; + + dev_dbg(gpadc->dev, "GPADC d1 for Chn: %d = %d\n", channel, d1); + dev_dbg(gpadc->dev, "GPADC d2 for Chn: %d = %d\n", channel, d2); + dev_dbg(gpadc->dev, "GPADC x1 for Chn: %d = %d\n", channel, x1); + dev_dbg(gpadc->dev, "GPADC x2 for Chn: %d = %d\n", channel, x2); + dev_dbg(gpadc->dev, "GPADC Gain for Chn: %d = %d\n", channel, gain); + dev_dbg(gpadc->dev, "GPADC k for Chn: %d = %d\n", channel, k); + dev_dbg(gpadc->dev, "GPADC b for Chn: %d = %d\n", channel, b); +} + +static inline int twl6030_gpadc_get_trim_offset(s8 d) +{ + /* + * XXX NOTE! + * bit 0 - sign, bit 7 - reserved, 6..1 - trim value + * though, the documentation states that trim value + * is absolute value, the correct conversion results are + * obtained if the value is interpreted as 2's complement. + */ + __u32 temp = ((d & 0x7f) >> 1) | ((d & 1) << 6); + + return sign_extend32(temp, 6); +} + +static int twl6030_calibration(struct twl6030_gpadc_data *gpadc) +{ + int ret; + int chn; + u8 trim_regs[TWL6030_GPADC_NUM_TRIM_REGS]; + s8 d1, d2; + + /* + * for calibration two measurements have been performed at + * factory, for some channels, during the production test and + * have been stored in registers. This two stored values are + * used to correct the measurements. The values represent + * offsets for the given input from the output on ideal curve. + */ + ret = twl_i2c_read(TWL6030_MODULE_ID2, trim_regs, + TWL6030_GPADC_TRIM1, TWL6030_GPADC_NUM_TRIM_REGS); + if (ret < 0) { + dev_err(gpadc->dev, "calibration failed\n"); + return ret; + } + + for (chn = 0; chn < TWL6030_GPADC_MAX_CHANNELS; chn++) { + + switch (chn) { + case 0: + d1 = trim_regs[0]; + d2 = trim_regs[1]; + break; + case 1: + case 3: + case 4: + case 5: + case 6: + d1 = trim_regs[4]; + d2 = trim_regs[5]; + break; + case 2: + d1 = trim_regs[12]; + d2 = trim_regs[13]; + break; + case 7: + d1 = trim_regs[6]; + d2 = trim_regs[7]; + break; + case 8: + d1 = trim_regs[2]; + d2 = trim_regs[3]; + break; + case 9: + d1 = trim_regs[8]; + d2 = trim_regs[9]; + break; + case 10: + d1 = trim_regs[10]; + d2 = trim_regs[11]; + break; + case 14: + d1 = trim_regs[14]; + d2 = trim_regs[15]; + break; + default: + continue; + } + + d1 = twl6030_gpadc_get_trim_offset(d1); + d2 = twl6030_gpadc_get_trim_offset(d2); + + twl6030_calibrate_channel(gpadc, chn, d1, d2); + } + + return 0; +} + +static int twl6032_get_trim_value(u8 *trim_regs, unsigned int reg0, + unsigned int reg1, unsigned int mask0, unsigned int mask1, + unsigned int shift0) +{ + int val; + + val = (trim_regs[reg0] & mask0) << shift0; + val |= (trim_regs[reg1] & mask1) >> 1; + if (trim_regs[reg1] & 0x01) + val = -val; + + return val; +} + +static int twl6032_calibration(struct twl6030_gpadc_data *gpadc) +{ + int chn, d1 = 0, d2 = 0, temp; + u8 trim_regs[TWL6030_GPADC_NUM_TRIM_REGS]; + int ret; + + ret = twl_i2c_read(TWL6030_MODULE_ID2, trim_regs, + TWL6030_GPADC_TRIM1, TWL6030_GPADC_NUM_TRIM_REGS); + if (ret < 0) { + dev_err(gpadc->dev, "calibration failed\n"); + return ret; + } + + /* + * Loop to calculate the value needed for returning voltages from + * GPADC not values. + * + * gain is calculated to 3 decimal places fixed point. + */ + for (chn = 0; chn < TWL6032_GPADC_MAX_CHANNELS; chn++) { + + switch (chn) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 11: + case 14: + d1 = twl6032_get_trim_value(trim_regs, 2, 0, 0x1f, + 0x06, 2); + d2 = twl6032_get_trim_value(trim_regs, 3, 1, 0x3f, + 0x06, 2); + break; + case 8: + temp = twl6032_get_trim_value(trim_regs, 2, 0, 0x1f, + 0x06, 2); + d1 = temp + twl6032_get_trim_value(trim_regs, 7, 6, + 0x18, 0x1E, 1); + + temp = twl6032_get_trim_value(trim_regs, 3, 1, 0x3F, + 0x06, 2); + d2 = temp + twl6032_get_trim_value(trim_regs, 9, 7, + 0x1F, 0x06, 2); + break; + case 9: + temp = twl6032_get_trim_value(trim_regs, 2, 0, 0x1f, + 0x06, 2); + d1 = temp + twl6032_get_trim_value(trim_regs, 13, 11, + 0x18, 0x1E, 1); + + temp = twl6032_get_trim_value(trim_regs, 3, 1, 0x3f, + 0x06, 2); + d2 = temp + twl6032_get_trim_value(trim_regs, 15, 13, + 0x1F, 0x06, 1); + break; + case 10: + d1 = twl6032_get_trim_value(trim_regs, 10, 8, 0x0f, + 0x0E, 3); + d2 = twl6032_get_trim_value(trim_regs, 14, 12, 0x0f, + 0x0E, 3); + break; + case 7: + case 18: + temp = twl6032_get_trim_value(trim_regs, 2, 0, 0x1f, + 0x06, 2); + + d1 = (trim_regs[4] & 0x7E) >> 1; + if (trim_regs[4] & 0x01) + d1 = -d1; + d1 += temp; + + temp = twl6032_get_trim_value(trim_regs, 3, 1, 0x3f, + 0x06, 2); + + d2 = (trim_regs[5] & 0xFE) >> 1; + if (trim_regs[5] & 0x01) + d2 = -d2; + + d2 += temp; + break; + default: + /* No data for other channels */ + continue; + } + + twl6030_calibrate_channel(gpadc, chn, d1, d2); + } + + return 0; +} + +#define TWL6030_GPADC_CHAN(chn, _type, chan_info) { \ + .type = _type, \ + .channel = chn, \ + .info_mask_separate = BIT(chan_info), \ + .indexed = 1, \ +} + +static const struct iio_chan_spec twl6030_gpadc_iio_channels[] = { + TWL6030_GPADC_CHAN(0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(1, IIO_TEMP, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(3, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(4, IIO_TEMP, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(11, IIO_VOLTAGE, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), +}; + +static const struct iio_chan_spec twl6032_gpadc_iio_channels[] = { + TWL6030_GPADC_CHAN(0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(1, IIO_TEMP, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(3, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(4, IIO_TEMP, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(11, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + TWL6030_GPADC_CHAN(17, IIO_VOLTAGE, IIO_CHAN_INFO_RAW), + TWL6030_GPADC_CHAN(18, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), +}; + +static const struct iio_info twl6030_gpadc_iio_info = { + .read_raw = &twl6030_gpadc_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct twl6030_gpadc_platform_data twl6030_pdata = { + .iio_channels = twl6030_gpadc_iio_channels, + .nchannels = TWL6030_GPADC_USED_CHANNELS, + .ideal = twl6030_ideal, + .start_conversion = twl6030_start_conversion, + .channel_to_reg = twl6030_channel_to_reg, + .calibrate = twl6030_calibration, +}; + +static const struct twl6030_gpadc_platform_data twl6032_pdata = { + .iio_channels = twl6032_gpadc_iio_channels, + .nchannels = TWL6032_GPADC_USED_CHANNELS, + .ideal = twl6032_ideal, + .start_conversion = twl6032_start_conversion, + .channel_to_reg = twl6032_channel_to_reg, + .calibrate = twl6032_calibration, +}; + +static const struct of_device_id of_twl6030_match_tbl[] = { + { + .compatible = "ti,twl6030-gpadc", + .data = &twl6030_pdata, + }, + { + .compatible = "ti,twl6032-gpadc", + .data = &twl6032_pdata, + }, + { /* end */ } +}; + +static int twl6030_gpadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct twl6030_gpadc_data *gpadc; + const struct twl6030_gpadc_platform_data *pdata; + const struct of_device_id *match; + struct iio_dev *indio_dev; + int irq; + int ret; + + match = of_match_device(of_twl6030_match_tbl, dev); + if (!match) + return -EINVAL; + + pdata = match->data; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gpadc)); + if (!indio_dev) + return -ENOMEM; + + gpadc = iio_priv(indio_dev); + + gpadc->twl6030_cal_tbl = devm_kzalloc(dev, + sizeof(*gpadc->twl6030_cal_tbl) * + pdata->nchannels, GFP_KERNEL); + if (!gpadc->twl6030_cal_tbl) + return -ENOMEM; + + gpadc->dev = dev; + gpadc->pdata = pdata; + + platform_set_drvdata(pdev, indio_dev); + mutex_init(&gpadc->lock); + init_completion(&gpadc->irq_complete); + + ret = pdata->calibrate(gpadc); + if (ret < 0) { + dev_err(&pdev->dev, "failed to read calibration registers\n"); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get irq\n"); + return irq; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, + twl6030_gpadc_irq_handler, + IRQF_ONESHOT, "twl6030_gpadc", indio_dev); + + ret = twl6030_gpadc_enable_irq(TWL6030_GPADC_RT_SW1_EOC_MASK); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable GPADC interrupt\n"); + return ret; + } + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCS, + TWL6030_REG_TOGGLE1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to enable GPADC module\n"); + return ret; + } + + indio_dev->name = DRIVER_NAME; + indio_dev->dev.parent = dev; + indio_dev->info = &twl6030_gpadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = pdata->iio_channels; + indio_dev->num_channels = pdata->nchannels; + + return iio_device_register(indio_dev); +} + +static int twl6030_gpadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + twl6030_gpadc_disable_irq(TWL6030_GPADC_RT_SW1_EOC_MASK); + iio_device_unregister(indio_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int twl6030_gpadc_suspend(struct device *pdev) +{ + int ret; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCR, + TWL6030_REG_TOGGLE1); + if (ret) + dev_err(pdev, "error resetting GPADC (%d)!\n", ret); + + return 0; +}; + +static int twl6030_gpadc_resume(struct device *pdev) +{ + int ret; + + ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCS, + TWL6030_REG_TOGGLE1); + if (ret) + dev_err(pdev, "error setting GPADC (%d)!\n", ret); + + return 0; +}; +#endif + +static SIMPLE_DEV_PM_OPS(twl6030_gpadc_pm_ops, twl6030_gpadc_suspend, + twl6030_gpadc_resume); + +static struct platform_driver twl6030_gpadc_driver = { + .probe = twl6030_gpadc_probe, + .remove = twl6030_gpadc_remove, + .driver = { + .name = DRIVER_NAME, + .pm = &twl6030_gpadc_pm_ops, + .of_match_table = of_twl6030_match_tbl, + }, +}; + +module_platform_driver(twl6030_gpadc_driver); + +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_AUTHOR("Balaji T K <balajitk@ti.com>"); +MODULE_AUTHOR("Graeme Gregory <gg@slimlogic.co.uk>"); +MODULE_AUTHOR("Oleksandr Kozaruk <oleksandr.kozaruk@ti.com"); +MODULE_DESCRIPTION("twl6030 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c new file mode 100644 index 000000000..56292ae45 --- /dev/null +++ b/drivers/iio/adc/vf610_adc.c @@ -0,0 +1,763 @@ +/* + * Freescale Vybrid vf610 ADC driver + * + * Copyright 2013 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/regulator/consumer.h> +#include <linux/of_platform.h> +#include <linux/err.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/driver.h> + +/* This will be the driver name the kernel reports */ +#define DRIVER_NAME "vf610-adc" + +/* Vybrid/IMX ADC registers */ +#define VF610_REG_ADC_HC0 0x00 +#define VF610_REG_ADC_HC1 0x04 +#define VF610_REG_ADC_HS 0x08 +#define VF610_REG_ADC_R0 0x0c +#define VF610_REG_ADC_R1 0x10 +#define VF610_REG_ADC_CFG 0x14 +#define VF610_REG_ADC_GC 0x18 +#define VF610_REG_ADC_GS 0x1c +#define VF610_REG_ADC_CV 0x20 +#define VF610_REG_ADC_OFS 0x24 +#define VF610_REG_ADC_CAL 0x28 +#define VF610_REG_ADC_PCTL 0x30 + +/* Configuration register field define */ +#define VF610_ADC_MODE_BIT8 0x00 +#define VF610_ADC_MODE_BIT10 0x04 +#define VF610_ADC_MODE_BIT12 0x08 +#define VF610_ADC_MODE_MASK 0x0c +#define VF610_ADC_BUSCLK2_SEL 0x01 +#define VF610_ADC_ALTCLK_SEL 0x02 +#define VF610_ADC_ADACK_SEL 0x03 +#define VF610_ADC_ADCCLK_MASK 0x03 +#define VF610_ADC_CLK_DIV2 0x20 +#define VF610_ADC_CLK_DIV4 0x40 +#define VF610_ADC_CLK_DIV8 0x60 +#define VF610_ADC_CLK_MASK 0x60 +#define VF610_ADC_ADLSMP_LONG 0x10 +#define VF610_ADC_ADSTS_MASK 0x300 +#define VF610_ADC_ADLPC_EN 0x80 +#define VF610_ADC_ADHSC_EN 0x400 +#define VF610_ADC_REFSEL_VALT 0x100 +#define VF610_ADC_REFSEL_VBG 0x1000 +#define VF610_ADC_ADTRG_HARD 0x2000 +#define VF610_ADC_AVGS_8 0x4000 +#define VF610_ADC_AVGS_16 0x8000 +#define VF610_ADC_AVGS_32 0xC000 +#define VF610_ADC_AVGS_MASK 0xC000 +#define VF610_ADC_OVWREN 0x10000 + +/* General control register field define */ +#define VF610_ADC_ADACKEN 0x1 +#define VF610_ADC_DMAEN 0x2 +#define VF610_ADC_ACREN 0x4 +#define VF610_ADC_ACFGT 0x8 +#define VF610_ADC_ACFE 0x10 +#define VF610_ADC_AVGEN 0x20 +#define VF610_ADC_ADCON 0x40 +#define VF610_ADC_CAL 0x80 + +/* Other field define */ +#define VF610_ADC_ADCHC(x) ((x) & 0x1F) +#define VF610_ADC_AIEN (0x1 << 7) +#define VF610_ADC_CONV_DISABLE 0x1F +#define VF610_ADC_HS_COCO0 0x1 +#define VF610_ADC_CALF 0x2 +#define VF610_ADC_TIMEOUT msecs_to_jiffies(100) + +enum clk_sel { + VF610_ADCIOC_BUSCLK_SET, + VF610_ADCIOC_ALTCLK_SET, + VF610_ADCIOC_ADACK_SET, +}; + +enum vol_ref { + VF610_ADCIOC_VR_VREF_SET, + VF610_ADCIOC_VR_VALT_SET, + VF610_ADCIOC_VR_VBG_SET, +}; + +enum average_sel { + VF610_ADC_SAMPLE_1, + VF610_ADC_SAMPLE_4, + VF610_ADC_SAMPLE_8, + VF610_ADC_SAMPLE_16, + VF610_ADC_SAMPLE_32, +}; + +struct vf610_adc_feature { + enum clk_sel clk_sel; + enum vol_ref vol_ref; + + int clk_div; + int sample_rate; + int res_mode; + + bool lpm; + bool calibration; + bool ovwren; +}; + +struct vf610_adc { + struct device *dev; + void __iomem *regs; + struct clk *clk; + + u32 vref_uv; + u32 value; + struct regulator *vref; + struct vf610_adc_feature adc_feature; + + u32 sample_freq_avail[5]; + + struct completion completion; +}; + +static const u32 vf610_hw_avgs[] = { 1, 4, 8, 16, 32 }; + +#define VF610_ADC_CHAN(_idx, _chan_type) { \ + .type = (_chan_type), \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +#define VF610_ADC_TEMPERATURE_CHAN(_idx, _chan_type) { \ + .type = (_chan_type), \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ +} + +static const struct iio_chan_spec vf610_adc_iio_channels[] = { + VF610_ADC_CHAN(0, IIO_VOLTAGE), + VF610_ADC_CHAN(1, IIO_VOLTAGE), + VF610_ADC_CHAN(2, IIO_VOLTAGE), + VF610_ADC_CHAN(3, IIO_VOLTAGE), + VF610_ADC_CHAN(4, IIO_VOLTAGE), + VF610_ADC_CHAN(5, IIO_VOLTAGE), + VF610_ADC_CHAN(6, IIO_VOLTAGE), + VF610_ADC_CHAN(7, IIO_VOLTAGE), + VF610_ADC_CHAN(8, IIO_VOLTAGE), + VF610_ADC_CHAN(9, IIO_VOLTAGE), + VF610_ADC_CHAN(10, IIO_VOLTAGE), + VF610_ADC_CHAN(11, IIO_VOLTAGE), + VF610_ADC_CHAN(12, IIO_VOLTAGE), + VF610_ADC_CHAN(13, IIO_VOLTAGE), + VF610_ADC_CHAN(14, IIO_VOLTAGE), + VF610_ADC_CHAN(15, IIO_VOLTAGE), + VF610_ADC_TEMPERATURE_CHAN(26, IIO_TEMP), + /* sentinel */ +}; + +static inline void vf610_adc_calculate_rates(struct vf610_adc *info) +{ + unsigned long adck_rate, ipg_rate = clk_get_rate(info->clk); + int i; + + /* + * Calculate ADC sample frequencies + * Sample time unit is ADCK cycles. ADCK clk source is ipg clock, + * which is the same as bus clock. + * + * ADC conversion time = SFCAdder + AverageNum x (BCT + LSTAdder) + * SFCAdder: fixed to 6 ADCK cycles + * AverageNum: 1, 4, 8, 16, 32 samples for hardware average. + * BCT (Base Conversion Time): fixed to 25 ADCK cycles for 12 bit mode + * LSTAdder(Long Sample Time): fixed to 3 ADCK cycles + */ + adck_rate = ipg_rate / info->adc_feature.clk_div; + for (i = 0; i < ARRAY_SIZE(vf610_hw_avgs); i++) + info->sample_freq_avail[i] = + adck_rate / (6 + vf610_hw_avgs[i] * (25 + 3)); +} + +static inline void vf610_adc_cfg_init(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &info->adc_feature; + + /* set default Configuration for ADC controller */ + adc_feature->clk_sel = VF610_ADCIOC_BUSCLK_SET; + adc_feature->vol_ref = VF610_ADCIOC_VR_VREF_SET; + + adc_feature->calibration = true; + adc_feature->ovwren = true; + + adc_feature->res_mode = 12; + adc_feature->sample_rate = 1; + adc_feature->lpm = true; + + /* Use a save ADCK which is below 20MHz on all devices */ + adc_feature->clk_div = 8; + + vf610_adc_calculate_rates(info); +} + +static void vf610_adc_cfg_post_set(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &info->adc_feature; + int cfg_data = 0; + int gc_data = 0; + + switch (adc_feature->clk_sel) { + case VF610_ADCIOC_ALTCLK_SET: + cfg_data |= VF610_ADC_ALTCLK_SEL; + break; + case VF610_ADCIOC_ADACK_SET: + cfg_data |= VF610_ADC_ADACK_SEL; + break; + default: + break; + } + + /* low power set for calibration */ + cfg_data |= VF610_ADC_ADLPC_EN; + + /* enable high speed for calibration */ + cfg_data |= VF610_ADC_ADHSC_EN; + + /* voltage reference */ + switch (adc_feature->vol_ref) { + case VF610_ADCIOC_VR_VREF_SET: + break; + case VF610_ADCIOC_VR_VALT_SET: + cfg_data |= VF610_ADC_REFSEL_VALT; + break; + case VF610_ADCIOC_VR_VBG_SET: + cfg_data |= VF610_ADC_REFSEL_VBG; + break; + default: + dev_err(info->dev, "error voltage reference\n"); + } + + /* data overwrite enable */ + if (adc_feature->ovwren) + cfg_data |= VF610_ADC_OVWREN; + + writel(cfg_data, info->regs + VF610_REG_ADC_CFG); + writel(gc_data, info->regs + VF610_REG_ADC_GC); +} + +static void vf610_adc_calibration(struct vf610_adc *info) +{ + int adc_gc, hc_cfg; + + if (!info->adc_feature.calibration) + return; + + /* enable calibration interrupt */ + hc_cfg = VF610_ADC_AIEN | VF610_ADC_CONV_DISABLE; + writel(hc_cfg, info->regs + VF610_REG_ADC_HC0); + + adc_gc = readl(info->regs + VF610_REG_ADC_GC); + writel(adc_gc | VF610_ADC_CAL, info->regs + VF610_REG_ADC_GC); + + if (!wait_for_completion_timeout(&info->completion, VF610_ADC_TIMEOUT)) + dev_err(info->dev, "Timeout for adc calibration\n"); + + adc_gc = readl(info->regs + VF610_REG_ADC_GS); + if (adc_gc & VF610_ADC_CALF) + dev_err(info->dev, "ADC calibration failed\n"); + + info->adc_feature.calibration = false; +} + +static void vf610_adc_cfg_set(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &(info->adc_feature); + int cfg_data; + + cfg_data = readl(info->regs + VF610_REG_ADC_CFG); + + cfg_data &= ~VF610_ADC_ADLPC_EN; + if (adc_feature->lpm) + cfg_data |= VF610_ADC_ADLPC_EN; + + cfg_data &= ~VF610_ADC_ADHSC_EN; + + writel(cfg_data, info->regs + VF610_REG_ADC_CFG); +} + +static void vf610_adc_sample_set(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &(info->adc_feature); + int cfg_data, gc_data; + + cfg_data = readl(info->regs + VF610_REG_ADC_CFG); + gc_data = readl(info->regs + VF610_REG_ADC_GC); + + /* resolution mode */ + cfg_data &= ~VF610_ADC_MODE_MASK; + switch (adc_feature->res_mode) { + case 8: + cfg_data |= VF610_ADC_MODE_BIT8; + break; + case 10: + cfg_data |= VF610_ADC_MODE_BIT10; + break; + case 12: + cfg_data |= VF610_ADC_MODE_BIT12; + break; + default: + dev_err(info->dev, "error resolution mode\n"); + break; + } + + /* clock select and clock divider */ + cfg_data &= ~(VF610_ADC_CLK_MASK | VF610_ADC_ADCCLK_MASK); + switch (adc_feature->clk_div) { + case 1: + break; + case 2: + cfg_data |= VF610_ADC_CLK_DIV2; + break; + case 4: + cfg_data |= VF610_ADC_CLK_DIV4; + break; + case 8: + cfg_data |= VF610_ADC_CLK_DIV8; + break; + case 16: + switch (adc_feature->clk_sel) { + case VF610_ADCIOC_BUSCLK_SET: + cfg_data |= VF610_ADC_BUSCLK2_SEL | VF610_ADC_CLK_DIV8; + break; + default: + dev_err(info->dev, "error clk divider\n"); + break; + } + break; + } + + /* Use the short sample mode */ + cfg_data &= ~(VF610_ADC_ADLSMP_LONG | VF610_ADC_ADSTS_MASK); + + /* update hardware average selection */ + cfg_data &= ~VF610_ADC_AVGS_MASK; + gc_data &= ~VF610_ADC_AVGEN; + switch (adc_feature->sample_rate) { + case VF610_ADC_SAMPLE_1: + break; + case VF610_ADC_SAMPLE_4: + gc_data |= VF610_ADC_AVGEN; + break; + case VF610_ADC_SAMPLE_8: + gc_data |= VF610_ADC_AVGEN; + cfg_data |= VF610_ADC_AVGS_8; + break; + case VF610_ADC_SAMPLE_16: + gc_data |= VF610_ADC_AVGEN; + cfg_data |= VF610_ADC_AVGS_16; + break; + case VF610_ADC_SAMPLE_32: + gc_data |= VF610_ADC_AVGEN; + cfg_data |= VF610_ADC_AVGS_32; + break; + default: + dev_err(info->dev, + "error hardware sample average select\n"); + } + + writel(cfg_data, info->regs + VF610_REG_ADC_CFG); + writel(gc_data, info->regs + VF610_REG_ADC_GC); +} + +static void vf610_adc_hw_init(struct vf610_adc *info) +{ + /* CFG: Feature set */ + vf610_adc_cfg_post_set(info); + vf610_adc_sample_set(info); + + /* adc calibration */ + vf610_adc_calibration(info); + + /* CFG: power and speed set */ + vf610_adc_cfg_set(info); +} + +static int vf610_adc_read_data(struct vf610_adc *info) +{ + int result; + + result = readl(info->regs + VF610_REG_ADC_R0); + + switch (info->adc_feature.res_mode) { + case 8: + result &= 0xFF; + break; + case 10: + result &= 0x3FF; + break; + case 12: + result &= 0xFFF; + break; + default: + break; + } + + return result; +} + +static irqreturn_t vf610_adc_isr(int irq, void *dev_id) +{ + struct vf610_adc *info = (struct vf610_adc *)dev_id; + int coco; + + coco = readl(info->regs + VF610_REG_ADC_HS); + if (coco & VF610_ADC_HS_COCO0) { + info->value = vf610_adc_read_data(info); + complete(&info->completion); + } + + return IRQ_HANDLED; +} + +static ssize_t vf610_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct vf610_adc *info = iio_priv(dev_to_iio_dev(dev)); + size_t len = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(info->sample_freq_avail); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%u ", info->sample_freq_avail[i]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(vf610_show_samp_freq_avail); + +static struct attribute *vf610_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group vf610_attribute_group = { + .attrs = vf610_attributes, +}; + +static int vf610_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct vf610_adc *info = iio_priv(indio_dev); + unsigned int hc_cfg; + long ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&indio_dev->mlock); + reinit_completion(&info->completion); + + hc_cfg = VF610_ADC_ADCHC(chan->channel); + hc_cfg |= VF610_ADC_AIEN; + writel(hc_cfg, info->regs + VF610_REG_ADC_HC0); + ret = wait_for_completion_interruptible_timeout + (&info->completion, VF610_ADC_TIMEOUT); + if (ret == 0) { + mutex_unlock(&indio_dev->mlock); + return -ETIMEDOUT; + } + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + switch (chan->type) { + case IIO_VOLTAGE: + *val = info->value; + break; + case IIO_TEMP: + /* + * Calculate in degree Celsius times 1000 + * Using sensor slope of 1.84 mV/°C and + * V at 25°C of 696 mV + */ + *val = 25000 - ((int)info->value - 864) * 1000000 / 1840; + break; + default: + mutex_unlock(&indio_dev->mlock); + return -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = info->vref_uv / 1000; + *val2 = info->adc_feature.res_mode; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = info->sample_freq_avail[info->adc_feature.sample_rate]; + *val2 = 0; + return IIO_VAL_INT; + + default: + break; + } + + return -EINVAL; +} + +static int vf610_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct vf610_adc *info = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; + i < ARRAY_SIZE(info->sample_freq_avail); + i++) + if (val == info->sample_freq_avail[i]) { + info->adc_feature.sample_rate = i; + vf610_adc_sample_set(info); + return 0; + } + break; + + default: + break; + } + + return -EINVAL; +} + +static int vf610_adc_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct vf610_adc *info = iio_priv(indio_dev); + + if ((readval == NULL) || + (!(reg % 4) || (reg > VF610_REG_ADC_PCTL))) + return -EINVAL; + + *readval = readl(info->regs + reg); + + return 0; +} + +static const struct iio_info vf610_adc_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = &vf610_read_raw, + .write_raw = &vf610_write_raw, + .debugfs_reg_access = &vf610_adc_reg_access, + .attrs = &vf610_attribute_group, +}; + +static const struct of_device_id vf610_adc_match[] = { + { .compatible = "fsl,vf610-adc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vf610_adc_match); + +static int vf610_adc_probe(struct platform_device *pdev) +{ + struct vf610_adc *info; + struct iio_dev *indio_dev; + struct resource *mem; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc)); + if (!indio_dev) { + dev_err(&pdev->dev, "Failed allocating iio device\n"); + return -ENOMEM; + } + + info = iio_priv(indio_dev); + info->dev = &pdev->dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return irq; + } + + ret = devm_request_irq(info->dev, irq, + vf610_adc_isr, 0, + dev_name(&pdev->dev), info); + if (ret < 0) { + dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", irq); + return ret; + } + + info->clk = devm_clk_get(&pdev->dev, "adc"); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "failed getting clock, err = %ld\n", + PTR_ERR(info->clk)); + return PTR_ERR(info->clk); + } + + info->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(info->vref)) + return PTR_ERR(info->vref); + + ret = regulator_enable(info->vref); + if (ret) + return ret; + + info->vref_uv = regulator_get_voltage(info->vref); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&info->completion); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &vf610_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = vf610_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(vf610_adc_iio_channels); + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(&pdev->dev, + "Could not prepare or enable the clock.\n"); + goto error_adc_clk_enable; + } + + vf610_adc_cfg_init(info); + vf610_adc_hw_init(info); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto error_iio_device_register; + } + + return 0; + + +error_iio_device_register: + clk_disable_unprepare(info->clk); +error_adc_clk_enable: + regulator_disable(info->vref); + + return ret; +} + +static int vf610_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct vf610_adc *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(info->vref); + clk_disable_unprepare(info->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int vf610_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct vf610_adc *info = iio_priv(indio_dev); + int hc_cfg; + + /* ADC controller enters to stop mode */ + hc_cfg = readl(info->regs + VF610_REG_ADC_HC0); + hc_cfg |= VF610_ADC_CONV_DISABLE; + writel(hc_cfg, info->regs + VF610_REG_ADC_HC0); + + clk_disable_unprepare(info->clk); + regulator_disable(info->vref); + + return 0; +} + +static int vf610_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct vf610_adc *info = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(info->vref); + if (ret) + return ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + goto disable_reg; + + vf610_adc_hw_init(info); + + return 0; + +disable_reg: + regulator_disable(info->vref); + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(vf610_adc_pm_ops, vf610_adc_suspend, vf610_adc_resume); + +static struct platform_driver vf610_adc_driver = { + .probe = vf610_adc_probe, + .remove = vf610_adc_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = vf610_adc_match, + .pm = &vf610_adc_pm_ops, + }, +}; + +module_platform_driver(vf610_adc_driver); + +MODULE_AUTHOR("Fugang Duan <B38611@freescale.com>"); +MODULE_DESCRIPTION("Freescale VF610 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/viperboard_adc.c b/drivers/iio/adc/viperboard_adc.c new file mode 100644 index 000000000..3be2e3572 --- /dev/null +++ b/drivers/iio/adc/viperboard_adc.c @@ -0,0 +1,157 @@ +/* + * Nano River Technologies viperboard IIO ADC driver + * + * (C) 2012 by Lemonage GmbH + * Author: Lars Poeschel <poeschel@lemonage.de> + * All rights reserved. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> + +#include <linux/usb.h> +#include <linux/iio/iio.h> + +#include <linux/mfd/viperboard.h> + +#define VPRBRD_ADC_CMD_GET 0x00 + +struct vprbrd_adc_msg { + u8 cmd; + u8 chan; + u8 val; +} __packed; + +struct vprbrd_adc { + struct vprbrd *vb; +}; + +#define VPRBRD_ADC_CHANNEL(_index) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +static struct iio_chan_spec const vprbrd_adc_iio_channels[] = { + VPRBRD_ADC_CHANNEL(0), + VPRBRD_ADC_CHANNEL(1), + VPRBRD_ADC_CHANNEL(2), + VPRBRD_ADC_CHANNEL(3), +}; + +static int vprbrd_iio_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long info) +{ + int ret, error = 0; + struct vprbrd_adc *adc = iio_priv(iio_dev); + struct vprbrd *vb = adc->vb; + struct vprbrd_adc_msg *admsg = (struct vprbrd_adc_msg *)vb->buf; + + switch (info) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&vb->lock); + + admsg->cmd = VPRBRD_ADC_CMD_GET; + admsg->chan = chan->channel; + admsg->val = 0x00; + + ret = usb_control_msg(vb->usb_dev, + usb_sndctrlpipe(vb->usb_dev, 0), VPRBRD_USB_REQUEST_ADC, + VPRBRD_USB_TYPE_OUT, 0x0000, 0x0000, admsg, + sizeof(struct vprbrd_adc_msg), VPRBRD_USB_TIMEOUT_MS); + if (ret != sizeof(struct vprbrd_adc_msg)) { + dev_err(&iio_dev->dev, "usb send error on adc read\n"); + error = -EREMOTEIO; + } + + ret = usb_control_msg(vb->usb_dev, + usb_rcvctrlpipe(vb->usb_dev, 0), VPRBRD_USB_REQUEST_ADC, + VPRBRD_USB_TYPE_IN, 0x0000, 0x0000, admsg, + sizeof(struct vprbrd_adc_msg), VPRBRD_USB_TIMEOUT_MS); + + *val = admsg->val; + + mutex_unlock(&vb->lock); + + if (ret != sizeof(struct vprbrd_adc_msg)) { + dev_err(&iio_dev->dev, "usb recv error on adc read\n"); + error = -EREMOTEIO; + } + + if (error) + goto error; + + return IIO_VAL_INT; + default: + error = -EINVAL; + break; + } +error: + return error; +} + +static const struct iio_info vprbrd_adc_iio_info = { + .read_raw = &vprbrd_iio_read_raw, + .driver_module = THIS_MODULE, +}; + +static int vprbrd_adc_probe(struct platform_device *pdev) +{ + struct vprbrd *vb = dev_get_drvdata(pdev->dev.parent); + struct vprbrd_adc *adc; + struct iio_dev *indio_dev; + int ret; + + /* registering iio */ + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!indio_dev) { + dev_err(&pdev->dev, "failed allocating iio device\n"); + return -ENOMEM; + } + + adc = iio_priv(indio_dev); + adc->vb = vb; + indio_dev->name = "viperboard adc"; + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &vprbrd_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = vprbrd_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(vprbrd_adc_iio_channels); + + ret = devm_iio_device_register(&pdev->dev, indio_dev); + if (ret) { + dev_err(&pdev->dev, "could not register iio (adc)"); + return ret; + } + + return 0; +} + +static struct platform_driver vprbrd_adc_driver = { + .driver = { + .name = "viperboard-adc", + }, + .probe = vprbrd_adc_probe, +}; + +module_platform_driver(vprbrd_adc_driver); + +MODULE_AUTHOR("Lars Poeschel <poeschel@lemonage.de>"); +MODULE_DESCRIPTION("IIO ADC driver for Nano River Techs Viperboard"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:viperboard-adc"); diff --git a/drivers/iio/adc/xilinx-xadc-core.c b/drivers/iio/adc/xilinx-xadc-core.c new file mode 100644 index 000000000..ce93bd8e3 --- /dev/null +++ b/drivers/iio/adc/xilinx-xadc-core.c @@ -0,0 +1,1337 @@ +/* + * Xilinx XADC driver + * + * Copyright 2013-2014 Analog Devices Inc. + * Author: Lars-Peter Clauen <lars@metafoo.de> + * + * Licensed under the GPL-2. + * + * Documentation for the parts can be found at: + * - XADC hardmacro: Xilinx UG480 + * - ZYNQ XADC interface: Xilinx UG585 + * - AXI XADC interface: Xilinx PG019 + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sysfs.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include "xilinx-xadc.h" + +static const unsigned int XADC_ZYNQ_UNMASK_TIMEOUT = 500; + +/* ZYNQ register definitions */ +#define XADC_ZYNQ_REG_CFG 0x00 +#define XADC_ZYNQ_REG_INTSTS 0x04 +#define XADC_ZYNQ_REG_INTMSK 0x08 +#define XADC_ZYNQ_REG_STATUS 0x0c +#define XADC_ZYNQ_REG_CFIFO 0x10 +#define XADC_ZYNQ_REG_DFIFO 0x14 +#define XADC_ZYNQ_REG_CTL 0x18 + +#define XADC_ZYNQ_CFG_ENABLE BIT(31) +#define XADC_ZYNQ_CFG_CFIFOTH_MASK (0xf << 20) +#define XADC_ZYNQ_CFG_CFIFOTH_OFFSET 20 +#define XADC_ZYNQ_CFG_DFIFOTH_MASK (0xf << 16) +#define XADC_ZYNQ_CFG_DFIFOTH_OFFSET 16 +#define XADC_ZYNQ_CFG_WEDGE BIT(13) +#define XADC_ZYNQ_CFG_REDGE BIT(12) +#define XADC_ZYNQ_CFG_TCKRATE_MASK (0x3 << 8) +#define XADC_ZYNQ_CFG_TCKRATE_DIV2 (0x0 << 8) +#define XADC_ZYNQ_CFG_TCKRATE_DIV4 (0x1 << 8) +#define XADC_ZYNQ_CFG_TCKRATE_DIV8 (0x2 << 8) +#define XADC_ZYNQ_CFG_TCKRATE_DIV16 (0x3 << 8) +#define XADC_ZYNQ_CFG_IGAP_MASK 0x1f +#define XADC_ZYNQ_CFG_IGAP(x) (x) + +#define XADC_ZYNQ_INT_CFIFO_LTH BIT(9) +#define XADC_ZYNQ_INT_DFIFO_GTH BIT(8) +#define XADC_ZYNQ_INT_ALARM_MASK 0xff +#define XADC_ZYNQ_INT_ALARM_OFFSET 0 + +#define XADC_ZYNQ_STATUS_CFIFO_LVL_MASK (0xf << 16) +#define XADC_ZYNQ_STATUS_CFIFO_LVL_OFFSET 16 +#define XADC_ZYNQ_STATUS_DFIFO_LVL_MASK (0xf << 12) +#define XADC_ZYNQ_STATUS_DFIFO_LVL_OFFSET 12 +#define XADC_ZYNQ_STATUS_CFIFOF BIT(11) +#define XADC_ZYNQ_STATUS_CFIFOE BIT(10) +#define XADC_ZYNQ_STATUS_DFIFOF BIT(9) +#define XADC_ZYNQ_STATUS_DFIFOE BIT(8) +#define XADC_ZYNQ_STATUS_OT BIT(7) +#define XADC_ZYNQ_STATUS_ALM(x) BIT(x) + +#define XADC_ZYNQ_CTL_RESET BIT(4) + +#define XADC_ZYNQ_CMD_NOP 0x00 +#define XADC_ZYNQ_CMD_READ 0x01 +#define XADC_ZYNQ_CMD_WRITE 0x02 + +#define XADC_ZYNQ_CMD(cmd, addr, data) (((cmd) << 26) | ((addr) << 16) | (data)) + +/* AXI register definitions */ +#define XADC_AXI_REG_RESET 0x00 +#define XADC_AXI_REG_STATUS 0x04 +#define XADC_AXI_REG_ALARM_STATUS 0x08 +#define XADC_AXI_REG_CONVST 0x0c +#define XADC_AXI_REG_XADC_RESET 0x10 +#define XADC_AXI_REG_GIER 0x5c +#define XADC_AXI_REG_IPISR 0x60 +#define XADC_AXI_REG_IPIER 0x68 +#define XADC_AXI_ADC_REG_OFFSET 0x200 + +#define XADC_AXI_RESET_MAGIC 0xa +#define XADC_AXI_GIER_ENABLE BIT(31) + +#define XADC_AXI_INT_EOS BIT(4) +#define XADC_AXI_INT_ALARM_MASK 0x3c0f + +#define XADC_FLAGS_BUFFERED BIT(0) + +static void xadc_write_reg(struct xadc *xadc, unsigned int reg, + uint32_t val) +{ + writel(val, xadc->base + reg); +} + +static void xadc_read_reg(struct xadc *xadc, unsigned int reg, + uint32_t *val) +{ + *val = readl(xadc->base + reg); +} + +/* + * The ZYNQ interface uses two asynchronous FIFOs for communication with the + * XADC. Reads and writes to the XADC register are performed by submitting a + * request to the command FIFO (CFIFO), once the request has been completed the + * result can be read from the data FIFO (DFIFO). The method currently used in + * this driver is to submit the request for a read/write operation, then go to + * sleep and wait for an interrupt that signals that a response is available in + * the data FIFO. + */ + +static void xadc_zynq_write_fifo(struct xadc *xadc, uint32_t *cmd, + unsigned int n) +{ + unsigned int i; + + for (i = 0; i < n; i++) + xadc_write_reg(xadc, XADC_ZYNQ_REG_CFIFO, cmd[i]); +} + +static void xadc_zynq_drain_fifo(struct xadc *xadc) +{ + uint32_t status, tmp; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_STATUS, &status); + + while (!(status & XADC_ZYNQ_STATUS_DFIFOE)) { + xadc_read_reg(xadc, XADC_ZYNQ_REG_DFIFO, &tmp); + xadc_read_reg(xadc, XADC_ZYNQ_REG_STATUS, &status); + } +} + +static void xadc_zynq_update_intmsk(struct xadc *xadc, unsigned int mask, + unsigned int val) +{ + xadc->zynq_intmask &= ~mask; + xadc->zynq_intmask |= val; + + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTMSK, + xadc->zynq_intmask | xadc->zynq_masked_alarm); +} + +static int xadc_zynq_write_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t val) +{ + uint32_t cmd[1]; + uint32_t tmp; + int ret; + + spin_lock_irq(&xadc->lock); + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_DFIFO_GTH, + XADC_ZYNQ_INT_DFIFO_GTH); + + reinit_completion(&xadc->completion); + + cmd[0] = XADC_ZYNQ_CMD(XADC_ZYNQ_CMD_WRITE, reg, val); + xadc_zynq_write_fifo(xadc, cmd, ARRAY_SIZE(cmd)); + xadc_read_reg(xadc, XADC_ZYNQ_REG_CFG, &tmp); + tmp &= ~XADC_ZYNQ_CFG_DFIFOTH_MASK; + tmp |= 0 << XADC_ZYNQ_CFG_DFIFOTH_OFFSET; + xadc_write_reg(xadc, XADC_ZYNQ_REG_CFG, tmp); + + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_DFIFO_GTH, 0); + spin_unlock_irq(&xadc->lock); + + ret = wait_for_completion_interruptible_timeout(&xadc->completion, HZ); + if (ret == 0) + ret = -EIO; + else + ret = 0; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_DFIFO, &tmp); + + return ret; +} + +static int xadc_zynq_read_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t *val) +{ + uint32_t cmd[2]; + uint32_t resp, tmp; + int ret; + + cmd[0] = XADC_ZYNQ_CMD(XADC_ZYNQ_CMD_READ, reg, 0); + cmd[1] = XADC_ZYNQ_CMD(XADC_ZYNQ_CMD_NOP, 0, 0); + + spin_lock_irq(&xadc->lock); + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_DFIFO_GTH, + XADC_ZYNQ_INT_DFIFO_GTH); + xadc_zynq_drain_fifo(xadc); + reinit_completion(&xadc->completion); + + xadc_zynq_write_fifo(xadc, cmd, ARRAY_SIZE(cmd)); + xadc_read_reg(xadc, XADC_ZYNQ_REG_CFG, &tmp); + tmp &= ~XADC_ZYNQ_CFG_DFIFOTH_MASK; + tmp |= 1 << XADC_ZYNQ_CFG_DFIFOTH_OFFSET; + xadc_write_reg(xadc, XADC_ZYNQ_REG_CFG, tmp); + + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_DFIFO_GTH, 0); + spin_unlock_irq(&xadc->lock); + ret = wait_for_completion_interruptible_timeout(&xadc->completion, HZ); + if (ret == 0) + ret = -EIO; + if (ret < 0) + return ret; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_DFIFO, &resp); + xadc_read_reg(xadc, XADC_ZYNQ_REG_DFIFO, &resp); + + *val = resp & 0xffff; + + return 0; +} + +static unsigned int xadc_zynq_transform_alarm(unsigned int alarm) +{ + return ((alarm & 0x80) >> 4) | + ((alarm & 0x78) << 1) | + (alarm & 0x07); +} + +/* + * The ZYNQ threshold interrupts are level sensitive. Since we can't make the + * threshold condition go way from within the interrupt handler, this means as + * soon as a threshold condition is present we would enter the interrupt handler + * again and again. To work around this we mask all active thresholds interrupts + * in the interrupt handler and start a timer. In this timer we poll the + * interrupt status and only if the interrupt is inactive we unmask it again. + */ +static void xadc_zynq_unmask_worker(struct work_struct *work) +{ + struct xadc *xadc = container_of(work, struct xadc, zynq_unmask_work.work); + unsigned int misc_sts, unmask; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_STATUS, &misc_sts); + + misc_sts &= XADC_ZYNQ_INT_ALARM_MASK; + + spin_lock_irq(&xadc->lock); + + /* Clear those bits which are not active anymore */ + unmask = (xadc->zynq_masked_alarm ^ misc_sts) & xadc->zynq_masked_alarm; + xadc->zynq_masked_alarm &= misc_sts; + + /* Also clear those which are masked out anyway */ + xadc->zynq_masked_alarm &= ~xadc->zynq_intmask; + + /* Clear the interrupts before we unmask them */ + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTSTS, unmask); + + xadc_zynq_update_intmsk(xadc, 0, 0); + + spin_unlock_irq(&xadc->lock); + + /* if still pending some alarm re-trigger the timer */ + if (xadc->zynq_masked_alarm) { + schedule_delayed_work(&xadc->zynq_unmask_work, + msecs_to_jiffies(XADC_ZYNQ_UNMASK_TIMEOUT)); + } +} + +static irqreturn_t xadc_zynq_threaded_interrupt_handler(int irq, void *devid) +{ + struct iio_dev *indio_dev = devid; + struct xadc *xadc = iio_priv(indio_dev); + unsigned int alarm; + + spin_lock_irq(&xadc->lock); + alarm = xadc->zynq_alarm; + xadc->zynq_alarm = 0; + spin_unlock_irq(&xadc->lock); + + xadc_handle_events(indio_dev, xadc_zynq_transform_alarm(alarm)); + + /* unmask the required interrupts in timer. */ + schedule_delayed_work(&xadc->zynq_unmask_work, + msecs_to_jiffies(XADC_ZYNQ_UNMASK_TIMEOUT)); + + return IRQ_HANDLED; +} + +static irqreturn_t xadc_zynq_interrupt_handler(int irq, void *devid) +{ + struct iio_dev *indio_dev = devid; + struct xadc *xadc = iio_priv(indio_dev); + irqreturn_t ret = IRQ_HANDLED; + uint32_t status; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_INTSTS, &status); + + status &= ~(xadc->zynq_intmask | xadc->zynq_masked_alarm); + + if (!status) + return IRQ_NONE; + + spin_lock(&xadc->lock); + + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTSTS, status); + + if (status & XADC_ZYNQ_INT_DFIFO_GTH) { + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_DFIFO_GTH, + XADC_ZYNQ_INT_DFIFO_GTH); + complete(&xadc->completion); + } + + status &= XADC_ZYNQ_INT_ALARM_MASK; + if (status) { + xadc->zynq_alarm |= status; + xadc->zynq_masked_alarm |= status; + /* + * mask the current event interrupt, + * unmask it when the interrupt is no more active. + */ + xadc_zynq_update_intmsk(xadc, 0, 0); + ret = IRQ_WAKE_THREAD; + } + spin_unlock(&xadc->lock); + + return ret; +} + +#define XADC_ZYNQ_TCK_RATE_MAX 50000000 +#define XADC_ZYNQ_IGAP_DEFAULT 20 + +static int xadc_zynq_setup(struct platform_device *pdev, + struct iio_dev *indio_dev, int irq) +{ + struct xadc *xadc = iio_priv(indio_dev); + unsigned long pcap_rate; + unsigned int tck_div; + unsigned int div; + unsigned int igap; + unsigned int tck_rate; + + /* TODO: Figure out how to make igap and tck_rate configurable */ + igap = XADC_ZYNQ_IGAP_DEFAULT; + tck_rate = XADC_ZYNQ_TCK_RATE_MAX; + + xadc->zynq_intmask = ~0; + + pcap_rate = clk_get_rate(xadc->clk); + + if (tck_rate > XADC_ZYNQ_TCK_RATE_MAX) + tck_rate = XADC_ZYNQ_TCK_RATE_MAX; + if (tck_rate > pcap_rate / 2) { + div = 2; + } else { + div = pcap_rate / tck_rate; + if (pcap_rate / div > XADC_ZYNQ_TCK_RATE_MAX) + div++; + } + + if (div <= 3) + tck_div = XADC_ZYNQ_CFG_TCKRATE_DIV2; + else if (div <= 7) + tck_div = XADC_ZYNQ_CFG_TCKRATE_DIV4; + else if (div <= 15) + tck_div = XADC_ZYNQ_CFG_TCKRATE_DIV8; + else + tck_div = XADC_ZYNQ_CFG_TCKRATE_DIV16; + + xadc_write_reg(xadc, XADC_ZYNQ_REG_CTL, XADC_ZYNQ_CTL_RESET); + xadc_write_reg(xadc, XADC_ZYNQ_REG_CTL, 0); + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTSTS, ~0); + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTMSK, xadc->zynq_intmask); + xadc_write_reg(xadc, XADC_ZYNQ_REG_CFG, XADC_ZYNQ_CFG_ENABLE | + XADC_ZYNQ_CFG_REDGE | XADC_ZYNQ_CFG_WEDGE | + tck_div | XADC_ZYNQ_CFG_IGAP(igap)); + + return 0; +} + +static unsigned long xadc_zynq_get_dclk_rate(struct xadc *xadc) +{ + unsigned int div; + uint32_t val; + + xadc_read_reg(xadc, XADC_ZYNQ_REG_CFG, &val); + + switch (val & XADC_ZYNQ_CFG_TCKRATE_MASK) { + case XADC_ZYNQ_CFG_TCKRATE_DIV4: + div = 4; + break; + case XADC_ZYNQ_CFG_TCKRATE_DIV8: + div = 8; + break; + case XADC_ZYNQ_CFG_TCKRATE_DIV16: + div = 16; + break; + default: + div = 2; + break; + } + + return clk_get_rate(xadc->clk) / div; +} + +static void xadc_zynq_update_alarm(struct xadc *xadc, unsigned int alarm) +{ + unsigned long flags; + uint32_t status; + + /* Move OT to bit 7 */ + alarm = ((alarm & 0x08) << 4) | ((alarm & 0xf0) >> 1) | (alarm & 0x07); + + spin_lock_irqsave(&xadc->lock, flags); + + /* Clear previous interrupts if any. */ + xadc_read_reg(xadc, XADC_ZYNQ_REG_INTSTS, &status); + xadc_write_reg(xadc, XADC_ZYNQ_REG_INTSTS, status & alarm); + + xadc_zynq_update_intmsk(xadc, XADC_ZYNQ_INT_ALARM_MASK, + ~alarm & XADC_ZYNQ_INT_ALARM_MASK); + + spin_unlock_irqrestore(&xadc->lock, flags); +} + +static const struct xadc_ops xadc_zynq_ops = { + .read = xadc_zynq_read_adc_reg, + .write = xadc_zynq_write_adc_reg, + .setup = xadc_zynq_setup, + .get_dclk_rate = xadc_zynq_get_dclk_rate, + .interrupt_handler = xadc_zynq_interrupt_handler, + .threaded_interrupt_handler = xadc_zynq_threaded_interrupt_handler, + .update_alarm = xadc_zynq_update_alarm, +}; + +static int xadc_axi_read_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t *val) +{ + uint32_t val32; + + xadc_read_reg(xadc, XADC_AXI_ADC_REG_OFFSET + reg * 4, &val32); + *val = val32 & 0xffff; + + return 0; +} + +static int xadc_axi_write_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t val) +{ + xadc_write_reg(xadc, XADC_AXI_ADC_REG_OFFSET + reg * 4, val); + + return 0; +} + +static int xadc_axi_setup(struct platform_device *pdev, + struct iio_dev *indio_dev, int irq) +{ + struct xadc *xadc = iio_priv(indio_dev); + + xadc_write_reg(xadc, XADC_AXI_REG_RESET, XADC_AXI_RESET_MAGIC); + xadc_write_reg(xadc, XADC_AXI_REG_GIER, XADC_AXI_GIER_ENABLE); + + return 0; +} + +static irqreturn_t xadc_axi_interrupt_handler(int irq, void *devid) +{ + struct iio_dev *indio_dev = devid; + struct xadc *xadc = iio_priv(indio_dev); + uint32_t status, mask; + unsigned int events; + + xadc_read_reg(xadc, XADC_AXI_REG_IPISR, &status); + xadc_read_reg(xadc, XADC_AXI_REG_IPIER, &mask); + status &= mask; + + if (!status) + return IRQ_NONE; + + if ((status & XADC_AXI_INT_EOS) && xadc->trigger) + iio_trigger_poll(xadc->trigger); + + if (status & XADC_AXI_INT_ALARM_MASK) { + /* + * The order of the bits in the AXI-XADC status register does + * not match the order of the bits in the XADC alarm enable + * register. xadc_handle_events() expects the events to be in + * the same order as the XADC alarm enable register. + */ + events = (status & 0x000e) >> 1; + events |= (status & 0x0001) << 3; + events |= (status & 0x3c00) >> 6; + xadc_handle_events(indio_dev, events); + } + + xadc_write_reg(xadc, XADC_AXI_REG_IPISR, status); + + return IRQ_HANDLED; +} + +static void xadc_axi_update_alarm(struct xadc *xadc, unsigned int alarm) +{ + uint32_t val; + unsigned long flags; + + /* + * The order of the bits in the AXI-XADC status register does not match + * the order of the bits in the XADC alarm enable register. We get + * passed the alarm mask in the same order as in the XADC alarm enable + * register. + */ + alarm = ((alarm & 0x07) << 1) | ((alarm & 0x08) >> 3) | + ((alarm & 0xf0) << 6); + + spin_lock_irqsave(&xadc->lock, flags); + xadc_read_reg(xadc, XADC_AXI_REG_IPIER, &val); + val &= ~XADC_AXI_INT_ALARM_MASK; + val |= alarm; + xadc_write_reg(xadc, XADC_AXI_REG_IPIER, val); + spin_unlock_irqrestore(&xadc->lock, flags); +} + +static unsigned long xadc_axi_get_dclk(struct xadc *xadc) +{ + return clk_get_rate(xadc->clk); +} + +static const struct xadc_ops xadc_axi_ops = { + .read = xadc_axi_read_adc_reg, + .write = xadc_axi_write_adc_reg, + .setup = xadc_axi_setup, + .get_dclk_rate = xadc_axi_get_dclk, + .update_alarm = xadc_axi_update_alarm, + .interrupt_handler = xadc_axi_interrupt_handler, + .flags = XADC_FLAGS_BUFFERED, +}; + +static int _xadc_update_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t mask, uint16_t val) +{ + uint16_t tmp; + int ret; + + ret = _xadc_read_adc_reg(xadc, reg, &tmp); + if (ret) + return ret; + + return _xadc_write_adc_reg(xadc, reg, (tmp & ~mask) | val); +} + +static int xadc_update_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t mask, uint16_t val) +{ + int ret; + + mutex_lock(&xadc->mutex); + ret = _xadc_update_adc_reg(xadc, reg, mask, val); + mutex_unlock(&xadc->mutex); + + return ret; +} + +static unsigned long xadc_get_dclk_rate(struct xadc *xadc) +{ + return xadc->ops->get_dclk_rate(xadc); +} + +static int xadc_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *mask) +{ + struct xadc *xadc = iio_priv(indio_dev); + unsigned int n; + + n = bitmap_weight(mask, indio_dev->masklength); + + kfree(xadc->data); + xadc->data = kcalloc(n, sizeof(*xadc->data), GFP_KERNEL); + if (!xadc->data) + return -ENOMEM; + + return 0; +} + +static unsigned int xadc_scan_index_to_channel(unsigned int scan_index) +{ + switch (scan_index) { + case 5: + return XADC_REG_VCCPINT; + case 6: + return XADC_REG_VCCPAUX; + case 7: + return XADC_REG_VCCO_DDR; + case 8: + return XADC_REG_TEMP; + case 9: + return XADC_REG_VCCINT; + case 10: + return XADC_REG_VCCAUX; + case 11: + return XADC_REG_VPVN; + case 12: + return XADC_REG_VREFP; + case 13: + return XADC_REG_VREFN; + case 14: + return XADC_REG_VCCBRAM; + default: + return XADC_REG_VAUX(scan_index - 16); + } +} + +static irqreturn_t xadc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct xadc *xadc = iio_priv(indio_dev); + unsigned int chan; + int i, j; + + if (!xadc->data) + goto out; + + j = 0; + for_each_set_bit(i, indio_dev->active_scan_mask, + indio_dev->masklength) { + chan = xadc_scan_index_to_channel(i); + xadc_read_adc_reg(xadc, chan, &xadc->data[j]); + j++; + } + + iio_push_to_buffers(indio_dev, xadc->data); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int xadc_trigger_set_state(struct iio_trigger *trigger, bool state) +{ + struct xadc *xadc = iio_trigger_get_drvdata(trigger); + unsigned long flags; + unsigned int convst; + unsigned int val; + int ret = 0; + + mutex_lock(&xadc->mutex); + + if (state) { + /* Only one of the two triggers can be active at the a time. */ + if (xadc->trigger != NULL) { + ret = -EBUSY; + goto err_out; + } else { + xadc->trigger = trigger; + if (trigger == xadc->convst_trigger) + convst = XADC_CONF0_EC; + else + convst = 0; + } + ret = _xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF0_EC, + convst); + if (ret) + goto err_out; + } else { + xadc->trigger = NULL; + } + + spin_lock_irqsave(&xadc->lock, flags); + xadc_read_reg(xadc, XADC_AXI_REG_IPIER, &val); + xadc_write_reg(xadc, XADC_AXI_REG_IPISR, val & XADC_AXI_INT_EOS); + if (state) + val |= XADC_AXI_INT_EOS; + else + val &= ~XADC_AXI_INT_EOS; + xadc_write_reg(xadc, XADC_AXI_REG_IPIER, val); + spin_unlock_irqrestore(&xadc->lock, flags); + +err_out: + mutex_unlock(&xadc->mutex); + + return ret; +} + +static const struct iio_trigger_ops xadc_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &xadc_trigger_set_state, +}; + +static struct iio_trigger *xadc_alloc_trigger(struct iio_dev *indio_dev, + const char *name) +{ + struct iio_trigger *trig; + int ret; + + trig = iio_trigger_alloc("%s%d-%s", indio_dev->name, + indio_dev->id, name); + if (trig == NULL) + return ERR_PTR(-ENOMEM); + + trig->dev.parent = indio_dev->dev.parent; + trig->ops = &xadc_trigger_ops; + iio_trigger_set_drvdata(trig, iio_priv(indio_dev)); + + ret = iio_trigger_register(trig); + if (ret) + goto error_free_trig; + + return trig; + +error_free_trig: + iio_trigger_free(trig); + return ERR_PTR(ret); +} + +static int xadc_power_adc_b(struct xadc *xadc, unsigned int seq_mode) +{ + uint16_t val; + + switch (seq_mode) { + case XADC_CONF1_SEQ_SIMULTANEOUS: + case XADC_CONF1_SEQ_INDEPENDENT: + val = XADC_CONF2_PD_ADC_B; + break; + default: + val = 0; + break; + } + + return xadc_update_adc_reg(xadc, XADC_REG_CONF2, XADC_CONF2_PD_MASK, + val); +} + +static int xadc_get_seq_mode(struct xadc *xadc, unsigned long scan_mode) +{ + unsigned int aux_scan_mode = scan_mode >> 16; + + if (xadc->external_mux_mode == XADC_EXTERNAL_MUX_DUAL) + return XADC_CONF1_SEQ_SIMULTANEOUS; + + if ((aux_scan_mode & 0xff00) == 0 || + (aux_scan_mode & 0x00ff) == 0) + return XADC_CONF1_SEQ_CONTINUOUS; + + return XADC_CONF1_SEQ_SIMULTANEOUS; +} + +static int xadc_postdisable(struct iio_dev *indio_dev) +{ + struct xadc *xadc = iio_priv(indio_dev); + unsigned long scan_mask; + int ret; + int i; + + scan_mask = 1; /* Run calibration as part of the sequence */ + for (i = 0; i < indio_dev->num_channels; i++) + scan_mask |= BIT(indio_dev->channels[i].scan_index); + + /* Enable all channels and calibration */ + ret = xadc_write_adc_reg(xadc, XADC_REG_SEQ(0), scan_mask & 0xffff); + if (ret) + return ret; + + ret = xadc_write_adc_reg(xadc, XADC_REG_SEQ(1), scan_mask >> 16); + if (ret) + return ret; + + ret = xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF1_SEQ_MASK, + XADC_CONF1_SEQ_CONTINUOUS); + if (ret) + return ret; + + return xadc_power_adc_b(xadc, XADC_CONF1_SEQ_CONTINUOUS); +} + +static int xadc_preenable(struct iio_dev *indio_dev) +{ + struct xadc *xadc = iio_priv(indio_dev); + unsigned long scan_mask; + int seq_mode; + int ret; + + ret = xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF1_SEQ_MASK, + XADC_CONF1_SEQ_DEFAULT); + if (ret) + goto err; + + scan_mask = *indio_dev->active_scan_mask; + seq_mode = xadc_get_seq_mode(xadc, scan_mask); + + ret = xadc_write_adc_reg(xadc, XADC_REG_SEQ(0), scan_mask & 0xffff); + if (ret) + goto err; + + ret = xadc_write_adc_reg(xadc, XADC_REG_SEQ(1), scan_mask >> 16); + if (ret) + goto err; + + ret = xadc_power_adc_b(xadc, seq_mode); + if (ret) + goto err; + + ret = xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF1_SEQ_MASK, + seq_mode); + if (ret) + goto err; + + return 0; +err: + xadc_postdisable(indio_dev); + return ret; +} + +static struct iio_buffer_setup_ops xadc_buffer_ops = { + .preenable = &xadc_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &xadc_postdisable, +}; + +static int xadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct xadc *xadc = iio_priv(indio_dev); + unsigned int div; + uint16_t val16; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + ret = xadc_read_adc_reg(xadc, chan->address, &val16); + if (ret < 0) + return ret; + + val16 >>= 4; + if (chan->scan_type.sign == 'u') + *val = val16; + else + *val = sign_extend32(val16, 11); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + /* V = (val * 3.0) / 4096 */ + switch (chan->address) { + case XADC_REG_VCCINT: + case XADC_REG_VCCAUX: + case XADC_REG_VREFP: + case XADC_REG_VCCBRAM: + case XADC_REG_VCCPINT: + case XADC_REG_VCCPAUX: + case XADC_REG_VCCO_DDR: + *val = 3000; + break; + default: + *val = 1000; + break; + } + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + /* Temp in C = (val * 503.975) / 4096 - 273.15 */ + *val = 503975; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + /* Only the temperature channel has an offset */ + *val = -((273150 << 12) / 503975); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = xadc_read_adc_reg(xadc, XADC_REG_CONF2, &val16); + if (ret) + return ret; + + div = (val16 & XADC_CONF2_DIV_MASK) >> XADC_CONF2_DIV_OFFSET; + if (div < 2) + div = 2; + + *val = xadc_get_dclk_rate(xadc) / div / 26; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int xadc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + struct xadc *xadc = iio_priv(indio_dev); + unsigned long clk_rate = xadc_get_dclk_rate(xadc); + unsigned int div; + + if (info != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + if (val <= 0) + return -EINVAL; + + /* Max. 150 kSPS */ + if (val > 150000) + val = 150000; + + val *= 26; + + /* Min 1MHz */ + if (val < 1000000) + val = 1000000; + + /* + * We want to round down, but only if we do not exceed the 150 kSPS + * limit. + */ + div = clk_rate / val; + if (clk_rate / div / 26 > 150000) + div++; + if (div < 2) + div = 2; + else if (div > 0xff) + div = 0xff; + + return xadc_update_adc_reg(xadc, XADC_REG_CONF2, XADC_CONF2_DIV_MASK, + div << XADC_CONF2_DIV_OFFSET); +} + +static const struct iio_event_spec xadc_temp_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +/* Separate values for upper and lower thresholds, but only a shared enabled */ +static const struct iio_event_spec xadc_voltage_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define XADC_CHAN_TEMP(_chan, _scan_index, _addr) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = (_chan), \ + .address = (_addr), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .event_spec = xadc_temp_events, \ + .num_event_specs = ARRAY_SIZE(xadc_temp_events), \ + .scan_index = (_scan_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_CPU, \ + }, \ +} + +#define XADC_CHAN_VOLTAGE(_chan, _scan_index, _addr, _ext, _alarm) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_chan), \ + .address = (_addr), \ + .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 = (_alarm) ? xadc_voltage_events : NULL, \ + .num_event_specs = (_alarm) ? ARRAY_SIZE(xadc_voltage_events) : 0, \ + .scan_index = (_scan_index), \ + .scan_type = { \ + .sign = ((_addr) == XADC_REG_VREFN) ? 's' : 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_CPU, \ + }, \ + .extend_name = _ext, \ +} + +static const struct iio_chan_spec xadc_channels[] = { + XADC_CHAN_TEMP(0, 8, XADC_REG_TEMP), + XADC_CHAN_VOLTAGE(0, 9, XADC_REG_VCCINT, "vccint", true), + XADC_CHAN_VOLTAGE(1, 10, XADC_REG_VCCAUX, "vccaux", true), + XADC_CHAN_VOLTAGE(2, 14, XADC_REG_VCCBRAM, "vccbram", true), + XADC_CHAN_VOLTAGE(3, 5, XADC_REG_VCCPINT, "vccpint", true), + XADC_CHAN_VOLTAGE(4, 6, XADC_REG_VCCPAUX, "vccpaux", true), + XADC_CHAN_VOLTAGE(5, 7, XADC_REG_VCCO_DDR, "vccoddr", true), + XADC_CHAN_VOLTAGE(6, 12, XADC_REG_VREFP, "vrefp", false), + XADC_CHAN_VOLTAGE(7, 13, XADC_REG_VREFN, "vrefn", false), + XADC_CHAN_VOLTAGE(8, 11, XADC_REG_VPVN, NULL, false), + XADC_CHAN_VOLTAGE(9, 16, XADC_REG_VAUX(0), NULL, false), + XADC_CHAN_VOLTAGE(10, 17, XADC_REG_VAUX(1), NULL, false), + XADC_CHAN_VOLTAGE(11, 18, XADC_REG_VAUX(2), NULL, false), + XADC_CHAN_VOLTAGE(12, 19, XADC_REG_VAUX(3), NULL, false), + XADC_CHAN_VOLTAGE(13, 20, XADC_REG_VAUX(4), NULL, false), + XADC_CHAN_VOLTAGE(14, 21, XADC_REG_VAUX(5), NULL, false), + XADC_CHAN_VOLTAGE(15, 22, XADC_REG_VAUX(6), NULL, false), + XADC_CHAN_VOLTAGE(16, 23, XADC_REG_VAUX(7), NULL, false), + XADC_CHAN_VOLTAGE(17, 24, XADC_REG_VAUX(8), NULL, false), + XADC_CHAN_VOLTAGE(18, 25, XADC_REG_VAUX(9), NULL, false), + XADC_CHAN_VOLTAGE(19, 26, XADC_REG_VAUX(10), NULL, false), + XADC_CHAN_VOLTAGE(20, 27, XADC_REG_VAUX(11), NULL, false), + XADC_CHAN_VOLTAGE(21, 28, XADC_REG_VAUX(12), NULL, false), + XADC_CHAN_VOLTAGE(22, 29, XADC_REG_VAUX(13), NULL, false), + XADC_CHAN_VOLTAGE(23, 30, XADC_REG_VAUX(14), NULL, false), + XADC_CHAN_VOLTAGE(24, 31, XADC_REG_VAUX(15), NULL, false), +}; + +static const struct iio_info xadc_info = { + .read_raw = &xadc_read_raw, + .write_raw = &xadc_write_raw, + .read_event_config = &xadc_read_event_config, + .write_event_config = &xadc_write_event_config, + .read_event_value = &xadc_read_event_value, + .write_event_value = &xadc_write_event_value, + .update_scan_mode = &xadc_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static const struct of_device_id xadc_of_match_table[] = { + { .compatible = "xlnx,zynq-xadc-1.00.a", (void *)&xadc_zynq_ops }, + { .compatible = "xlnx,axi-xadc-1.00.a", (void *)&xadc_axi_ops }, + { }, +}; +MODULE_DEVICE_TABLE(of, xadc_of_match_table); + +static int xadc_parse_dt(struct iio_dev *indio_dev, struct device_node *np, + unsigned int *conf) +{ + struct xadc *xadc = iio_priv(indio_dev); + struct iio_chan_spec *channels, *chan; + struct device_node *chan_node, *child; + unsigned int num_channels; + const char *external_mux; + u32 ext_mux_chan; + int reg; + int ret; + + *conf = 0; + + ret = of_property_read_string(np, "xlnx,external-mux", &external_mux); + if (ret < 0 || strcasecmp(external_mux, "none") == 0) + xadc->external_mux_mode = XADC_EXTERNAL_MUX_NONE; + else if (strcasecmp(external_mux, "single") == 0) + xadc->external_mux_mode = XADC_EXTERNAL_MUX_SINGLE; + else if (strcasecmp(external_mux, "dual") == 0) + xadc->external_mux_mode = XADC_EXTERNAL_MUX_DUAL; + else + return -EINVAL; + + if (xadc->external_mux_mode != XADC_EXTERNAL_MUX_NONE) { + ret = of_property_read_u32(np, "xlnx,external-mux-channel", + &ext_mux_chan); + if (ret < 0) + return ret; + + if (xadc->external_mux_mode == XADC_EXTERNAL_MUX_SINGLE) { + if (ext_mux_chan == 0) + ext_mux_chan = XADC_REG_VPVN; + else if (ext_mux_chan <= 16) + ext_mux_chan = XADC_REG_VAUX(ext_mux_chan - 1); + else + return -EINVAL; + } else { + if (ext_mux_chan > 0 && ext_mux_chan <= 8) + ext_mux_chan = XADC_REG_VAUX(ext_mux_chan - 1); + else + return -EINVAL; + } + + *conf |= XADC_CONF0_MUX | XADC_CONF0_CHAN(ext_mux_chan); + } + + channels = kmemdup(xadc_channels, sizeof(xadc_channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + num_channels = 9; + chan = &channels[9]; + + chan_node = of_get_child_by_name(np, "xlnx,channels"); + if (chan_node) { + for_each_child_of_node(chan_node, child) { + if (num_channels >= ARRAY_SIZE(xadc_channels)) { + of_node_put(child); + break; + } + + ret = of_property_read_u32(child, "reg", ®); + if (ret || reg > 16) + continue; + + if (of_property_read_bool(child, "xlnx,bipolar")) + chan->scan_type.sign = 's'; + + if (reg == 0) { + chan->scan_index = 11; + chan->address = XADC_REG_VPVN; + } else { + chan->scan_index = 15 + reg; + chan->address = XADC_REG_VAUX(reg - 1); + } + num_channels++; + chan++; + } + } + of_node_put(chan_node); + + indio_dev->num_channels = num_channels; + indio_dev->channels = krealloc(channels, sizeof(*channels) * + num_channels, GFP_KERNEL); + /* If we can't resize the channels array, just use the original */ + if (!indio_dev->channels) + indio_dev->channels = channels; + + return 0; +} + +static int xadc_probe(struct platform_device *pdev) +{ + const struct of_device_id *id; + struct iio_dev *indio_dev; + unsigned int bipolar_mask; + struct resource *mem; + unsigned int conf0; + struct xadc *xadc; + int ret; + int irq; + int i; + + if (!pdev->dev.of_node) + return -ENODEV; + + id = of_match_node(xadc_of_match_table, pdev->dev.of_node); + if (!id) + return -EINVAL; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return -ENXIO; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*xadc)); + if (!indio_dev) + return -ENOMEM; + + xadc = iio_priv(indio_dev); + xadc->ops = id->data; + init_completion(&xadc->completion); + mutex_init(&xadc->mutex); + spin_lock_init(&xadc->lock); + INIT_DELAYED_WORK(&xadc->zynq_unmask_work, xadc_zynq_unmask_worker); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + xadc->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(xadc->base)) + return PTR_ERR(xadc->base); + + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->name = "xadc"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &xadc_info; + + ret = xadc_parse_dt(indio_dev, pdev->dev.of_node, &conf0); + if (ret) + goto err_device_free; + + if (xadc->ops->flags & XADC_FLAGS_BUFFERED) { + ret = iio_triggered_buffer_setup(indio_dev, + &iio_pollfunc_store_time, &xadc_trigger_handler, + &xadc_buffer_ops); + if (ret) + goto err_device_free; + + xadc->convst_trigger = xadc_alloc_trigger(indio_dev, "convst"); + if (IS_ERR(xadc->convst_trigger)) { + ret = PTR_ERR(xadc->convst_trigger); + goto err_triggered_buffer_cleanup; + } + xadc->samplerate_trigger = xadc_alloc_trigger(indio_dev, + "samplerate"); + if (IS_ERR(xadc->samplerate_trigger)) { + ret = PTR_ERR(xadc->samplerate_trigger); + goto err_free_convst_trigger; + } + } + + xadc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(xadc->clk)) { + ret = PTR_ERR(xadc->clk); + goto err_free_samplerate_trigger; + } + clk_prepare_enable(xadc->clk); + + ret = xadc->ops->setup(pdev, indio_dev, irq); + if (ret) + goto err_free_samplerate_trigger; + + ret = request_threaded_irq(irq, xadc->ops->interrupt_handler, + xadc->ops->threaded_interrupt_handler, + 0, dev_name(&pdev->dev), indio_dev); + if (ret) + goto err_clk_disable_unprepare; + + for (i = 0; i < 16; i++) + xadc_read_adc_reg(xadc, XADC_REG_THRESHOLD(i), + &xadc->threshold[i]); + + ret = xadc_write_adc_reg(xadc, XADC_REG_CONF0, conf0); + if (ret) + goto err_free_irq; + + bipolar_mask = 0; + for (i = 0; i < indio_dev->num_channels; i++) { + if (indio_dev->channels[i].scan_type.sign == 's') + bipolar_mask |= BIT(indio_dev->channels[i].scan_index); + } + + ret = xadc_write_adc_reg(xadc, XADC_REG_INPUT_MODE(0), bipolar_mask); + if (ret) + goto err_free_irq; + ret = xadc_write_adc_reg(xadc, XADC_REG_INPUT_MODE(1), + bipolar_mask >> 16); + if (ret) + goto err_free_irq; + + /* Disable all alarms */ + xadc_update_adc_reg(xadc, XADC_REG_CONF1, XADC_CONF1_ALARM_MASK, + XADC_CONF1_ALARM_MASK); + + /* Set thresholds to min/max */ + for (i = 0; i < 16; i++) { + /* + * Set max voltage threshold and both temperature thresholds to + * 0xffff, min voltage threshold to 0. + */ + if (i % 8 < 4 || i == 7) + xadc->threshold[i] = 0xffff; + else + xadc->threshold[i] = 0; + xadc_write_adc_reg(xadc, XADC_REG_THRESHOLD(i), + xadc->threshold[i]); + } + + /* Go to non-buffered mode */ + xadc_postdisable(indio_dev); + + ret = iio_device_register(indio_dev); + if (ret) + goto err_free_irq; + + platform_set_drvdata(pdev, indio_dev); + + return 0; + +err_free_irq: + free_irq(irq, indio_dev); +err_free_samplerate_trigger: + if (xadc->ops->flags & XADC_FLAGS_BUFFERED) + iio_trigger_free(xadc->samplerate_trigger); +err_free_convst_trigger: + if (xadc->ops->flags & XADC_FLAGS_BUFFERED) + iio_trigger_free(xadc->convst_trigger); +err_triggered_buffer_cleanup: + if (xadc->ops->flags & XADC_FLAGS_BUFFERED) + iio_triggered_buffer_cleanup(indio_dev); +err_clk_disable_unprepare: + clk_disable_unprepare(xadc->clk); +err_device_free: + kfree(indio_dev->channels); + + return ret; +} + +static int xadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct xadc *xadc = iio_priv(indio_dev); + int irq = platform_get_irq(pdev, 0); + + iio_device_unregister(indio_dev); + if (xadc->ops->flags & XADC_FLAGS_BUFFERED) { + iio_trigger_free(xadc->samplerate_trigger); + iio_trigger_free(xadc->convst_trigger); + iio_triggered_buffer_cleanup(indio_dev); + } + free_irq(irq, indio_dev); + clk_disable_unprepare(xadc->clk); + cancel_delayed_work(&xadc->zynq_unmask_work); + kfree(xadc->data); + kfree(indio_dev->channels); + + return 0; +} + +static struct platform_driver xadc_driver = { + .probe = xadc_probe, + .remove = xadc_remove, + .driver = { + .name = "xadc", + .of_match_table = xadc_of_match_table, + }, +}; +module_platform_driver(xadc_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Xilinx XADC IIO driver"); diff --git a/drivers/iio/adc/xilinx-xadc-events.c b/drivers/iio/adc/xilinx-xadc-events.c new file mode 100644 index 000000000..edcf3aabd --- /dev/null +++ b/drivers/iio/adc/xilinx-xadc-events.c @@ -0,0 +1,248 @@ +/* + * Xilinx XADC driver + * + * Copyright 2013 Analog Devices Inc. + * Author: Lars-Peter Clauen <lars@metafoo.de> + * + * Licensed under the GPL-2. + */ + +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/kernel.h> + +#include "xilinx-xadc.h" + +static const struct iio_chan_spec *xadc_event_to_channel( + struct iio_dev *indio_dev, unsigned int event) +{ + switch (event) { + case XADC_THRESHOLD_OT_MAX: + case XADC_THRESHOLD_TEMP_MAX: + return &indio_dev->channels[0]; + case XADC_THRESHOLD_VCCINT_MAX: + case XADC_THRESHOLD_VCCAUX_MAX: + return &indio_dev->channels[event]; + default: + return &indio_dev->channels[event-1]; + } +} + +static void xadc_handle_event(struct iio_dev *indio_dev, unsigned int event) +{ + const struct iio_chan_spec *chan; + + /* Temperature threshold error, we don't handle this yet */ + if (event == 0) + return; + + chan = xadc_event_to_channel(indio_dev, event); + + if (chan->type == IIO_TEMP) { + /* + * The temperature channel only supports over-temperature + * events. + */ + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + iio_get_time_ns()); + } else { + /* + * For other channels we don't know whether it is a upper or + * lower threshold event. Userspace will have to check the + * channel value if it wants to know. + */ + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER), + iio_get_time_ns()); + } +} + +void xadc_handle_events(struct iio_dev *indio_dev, unsigned long events) +{ + unsigned int i; + + for_each_set_bit(i, &events, 8) + xadc_handle_event(indio_dev, i); +} + +static unsigned xadc_get_threshold_offset(const struct iio_chan_spec *chan, + enum iio_event_direction dir) +{ + unsigned int offset; + + if (chan->type == IIO_TEMP) { + offset = XADC_THRESHOLD_OT_MAX; + } else { + if (chan->channel < 2) + offset = chan->channel + 1; + else + offset = chan->channel + 6; + } + + if (dir == IIO_EV_DIR_FALLING) + offset += 4; + + return offset; +} + +static unsigned int xadc_get_alarm_mask(const struct iio_chan_spec *chan) +{ + if (chan->type == IIO_TEMP) { + return XADC_ALARM_OT_MASK; + } else { + switch (chan->channel) { + case 0: + return XADC_ALARM_VCCINT_MASK; + case 1: + return XADC_ALARM_VCCAUX_MASK; + case 2: + return XADC_ALARM_VCCBRAM_MASK; + case 3: + return XADC_ALARM_VCCPINT_MASK; + case 4: + return XADC_ALARM_VCCPAUX_MASK; + case 5: + return XADC_ALARM_VCCODDR_MASK; + default: + /* We will never get here */ + return 0; + } + } +} + +int xadc_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 xadc *xadc = iio_priv(indio_dev); + + return (bool)(xadc->alarm_mask & xadc_get_alarm_mask(chan)); +} + +int xadc_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) +{ + unsigned int alarm = xadc_get_alarm_mask(chan); + struct xadc *xadc = iio_priv(indio_dev); + uint16_t cfg, old_cfg; + int ret; + + mutex_lock(&xadc->mutex); + + if (state) + xadc->alarm_mask |= alarm; + else + xadc->alarm_mask &= ~alarm; + + xadc->ops->update_alarm(xadc, xadc->alarm_mask); + + ret = _xadc_read_adc_reg(xadc, XADC_REG_CONF1, &cfg); + if (ret) + goto err_out; + + old_cfg = cfg; + cfg |= XADC_CONF1_ALARM_MASK; + cfg &= ~((xadc->alarm_mask & 0xf0) << 4); /* bram, pint, paux, ddr */ + cfg &= ~((xadc->alarm_mask & 0x08) >> 3); /* ot */ + cfg &= ~((xadc->alarm_mask & 0x07) << 1); /* temp, vccint, vccaux */ + if (old_cfg != cfg) + ret = _xadc_write_adc_reg(xadc, XADC_REG_CONF1, cfg); + +err_out: + mutex_unlock(&xadc->mutex); + + return ret; +} + +/* Register value is msb aligned, the lower 4 bits are ignored */ +#define XADC_THRESHOLD_VALUE_SHIFT 4 + +int xadc_read_event_value(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) +{ + unsigned int offset = xadc_get_threshold_offset(chan, dir); + struct xadc *xadc = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + *val = xadc->threshold[offset]; + break; + case IIO_EV_INFO_HYSTERESIS: + *val = xadc->temp_hysteresis; + break; + default: + return -EINVAL; + } + + *val >>= XADC_THRESHOLD_VALUE_SHIFT; + + return IIO_VAL_INT; +} + +int xadc_write_event_value(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) +{ + unsigned int offset = xadc_get_threshold_offset(chan, dir); + struct xadc *xadc = iio_priv(indio_dev); + int ret = 0; + + val <<= XADC_THRESHOLD_VALUE_SHIFT; + + if (val < 0 || val > 0xffff) + return -EINVAL; + + mutex_lock(&xadc->mutex); + + switch (info) { + case IIO_EV_INFO_VALUE: + xadc->threshold[offset] = val; + break; + case IIO_EV_INFO_HYSTERESIS: + xadc->temp_hysteresis = val; + break; + default: + mutex_unlock(&xadc->mutex); + return -EINVAL; + } + + if (chan->type == IIO_TEMP) { + /* + * According to the datasheet we need to set the lower 4 bits to + * 0x3, otherwise 125 degree celsius will be used as the + * threshold. + */ + val |= 0x3; + + /* + * Since we store the hysteresis as relative (to the threshold) + * value, but the hardware expects an absolute value we need to + * recalcualte this value whenever the hysteresis or the + * threshold changes. + */ + if (xadc->threshold[offset] < xadc->temp_hysteresis) + xadc->threshold[offset + 4] = 0; + else + xadc->threshold[offset + 4] = xadc->threshold[offset] - + xadc->temp_hysteresis; + ret = _xadc_write_adc_reg(xadc, XADC_REG_THRESHOLD(offset + 4), + xadc->threshold[offset + 4]); + if (ret) + goto out_unlock; + } + + if (info == IIO_EV_INFO_VALUE) + ret = _xadc_write_adc_reg(xadc, XADC_REG_THRESHOLD(offset), val); + +out_unlock: + mutex_unlock(&xadc->mutex); + + return ret; +} diff --git a/drivers/iio/adc/xilinx-xadc.h b/drivers/iio/adc/xilinx-xadc.h new file mode 100644 index 000000000..54adc5087 --- /dev/null +++ b/drivers/iio/adc/xilinx-xadc.h @@ -0,0 +1,209 @@ +/* + * Xilinx XADC driver + * + * Copyright 2013 Analog Devices Inc. + * Author: Lars-Peter Clauen <lars@metafoo.de> + * + * Licensed under the GPL-2. + */ + +#ifndef __IIO_XILINX_XADC__ +#define __IIO_XILINX_XADC__ + +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> + +struct iio_dev; +struct clk; +struct xadc_ops; +struct platform_device; + +void xadc_handle_events(struct iio_dev *indio_dev, unsigned long events); + +int xadc_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir); +int xadc_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); +int xadc_read_event_value(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); +int xadc_write_event_value(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); + +enum xadc_external_mux_mode { + XADC_EXTERNAL_MUX_NONE, + XADC_EXTERNAL_MUX_SINGLE, + XADC_EXTERNAL_MUX_DUAL, +}; + +struct xadc { + void __iomem *base; + struct clk *clk; + + const struct xadc_ops *ops; + + uint16_t threshold[16]; + uint16_t temp_hysteresis; + unsigned int alarm_mask; + + uint16_t *data; + + struct iio_trigger *trigger; + struct iio_trigger *convst_trigger; + struct iio_trigger *samplerate_trigger; + + enum xadc_external_mux_mode external_mux_mode; + + unsigned int zynq_alarm; + unsigned int zynq_masked_alarm; + unsigned int zynq_intmask; + struct delayed_work zynq_unmask_work; + + struct mutex mutex; + spinlock_t lock; + + struct completion completion; +}; + +struct xadc_ops { + int (*read)(struct xadc *, unsigned int, uint16_t *); + int (*write)(struct xadc *, unsigned int, uint16_t); + int (*setup)(struct platform_device *pdev, struct iio_dev *indio_dev, + int irq); + void (*update_alarm)(struct xadc *, unsigned int); + unsigned long (*get_dclk_rate)(struct xadc *); + irqreturn_t (*interrupt_handler)(int, void *); + irqreturn_t (*threaded_interrupt_handler)(int, void *); + + unsigned int flags; +}; + +static inline int _xadc_read_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t *val) +{ + lockdep_assert_held(&xadc->mutex); + return xadc->ops->read(xadc, reg, val); +} + +static inline int _xadc_write_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t val) +{ + lockdep_assert_held(&xadc->mutex); + return xadc->ops->write(xadc, reg, val); +} + +static inline int xadc_read_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t *val) +{ + int ret; + + mutex_lock(&xadc->mutex); + ret = _xadc_read_adc_reg(xadc, reg, val); + mutex_unlock(&xadc->mutex); + return ret; +} + +static inline int xadc_write_adc_reg(struct xadc *xadc, unsigned int reg, + uint16_t val) +{ + int ret; + + mutex_lock(&xadc->mutex); + ret = _xadc_write_adc_reg(xadc, reg, val); + mutex_unlock(&xadc->mutex); + return ret; +} + +/* XADC hardmacro register definitions */ +#define XADC_REG_TEMP 0x00 +#define XADC_REG_VCCINT 0x01 +#define XADC_REG_VCCAUX 0x02 +#define XADC_REG_VPVN 0x03 +#define XADC_REG_VREFP 0x04 +#define XADC_REG_VREFN 0x05 +#define XADC_REG_VCCBRAM 0x06 + +#define XADC_REG_VCCPINT 0x0d +#define XADC_REG_VCCPAUX 0x0e +#define XADC_REG_VCCO_DDR 0x0f +#define XADC_REG_VAUX(x) (0x10 + (x)) + +#define XADC_REG_MAX_TEMP 0x20 +#define XADC_REG_MAX_VCCINT 0x21 +#define XADC_REG_MAX_VCCAUX 0x22 +#define XADC_REG_MAX_VCCBRAM 0x23 +#define XADC_REG_MIN_TEMP 0x24 +#define XADC_REG_MIN_VCCINT 0x25 +#define XADC_REG_MIN_VCCAUX 0x26 +#define XADC_REG_MIN_VCCBRAM 0x27 +#define XADC_REG_MAX_VCCPINT 0x28 +#define XADC_REG_MAX_VCCPAUX 0x29 +#define XADC_REG_MAX_VCCO_DDR 0x2a +#define XADC_REG_MIN_VCCPINT 0x2c +#define XADC_REG_MIN_VCCPAUX 0x2d +#define XADC_REG_MIN_VCCO_DDR 0x2e + +#define XADC_REG_CONF0 0x40 +#define XADC_REG_CONF1 0x41 +#define XADC_REG_CONF2 0x42 +#define XADC_REG_SEQ(x) (0x48 + (x)) +#define XADC_REG_INPUT_MODE(x) (0x4c + (x)) +#define XADC_REG_THRESHOLD(x) (0x50 + (x)) + +#define XADC_REG_FLAG 0x3f + +#define XADC_CONF0_EC BIT(9) +#define XADC_CONF0_ACQ BIT(8) +#define XADC_CONF0_MUX BIT(11) +#define XADC_CONF0_CHAN(x) (x) + +#define XADC_CONF1_SEQ_MASK (0xf << 12) +#define XADC_CONF1_SEQ_DEFAULT (0 << 12) +#define XADC_CONF1_SEQ_SINGLE_PASS (1 << 12) +#define XADC_CONF1_SEQ_CONTINUOUS (2 << 12) +#define XADC_CONF1_SEQ_SINGLE_CHANNEL (3 << 12) +#define XADC_CONF1_SEQ_SIMULTANEOUS (4 << 12) +#define XADC_CONF1_SEQ_INDEPENDENT (8 << 12) +#define XADC_CONF1_ALARM_MASK 0x0f0f + +#define XADC_CONF2_DIV_MASK 0xff00 +#define XADC_CONF2_DIV_OFFSET 8 + +#define XADC_CONF2_PD_MASK (0x3 << 4) +#define XADC_CONF2_PD_NONE (0x0 << 4) +#define XADC_CONF2_PD_ADC_B (0x2 << 4) +#define XADC_CONF2_PD_BOTH (0x3 << 4) + +#define XADC_ALARM_TEMP_MASK BIT(0) +#define XADC_ALARM_VCCINT_MASK BIT(1) +#define XADC_ALARM_VCCAUX_MASK BIT(2) +#define XADC_ALARM_OT_MASK BIT(3) +#define XADC_ALARM_VCCBRAM_MASK BIT(4) +#define XADC_ALARM_VCCPINT_MASK BIT(5) +#define XADC_ALARM_VCCPAUX_MASK BIT(6) +#define XADC_ALARM_VCCODDR_MASK BIT(7) + +#define XADC_THRESHOLD_TEMP_MAX 0x0 +#define XADC_THRESHOLD_VCCINT_MAX 0x1 +#define XADC_THRESHOLD_VCCAUX_MAX 0x2 +#define XADC_THRESHOLD_OT_MAX 0x3 +#define XADC_THRESHOLD_TEMP_MIN 0x4 +#define XADC_THRESHOLD_VCCINT_MIN 0x5 +#define XADC_THRESHOLD_VCCAUX_MIN 0x6 +#define XADC_THRESHOLD_OT_MIN 0x7 +#define XADC_THRESHOLD_VCCBRAM_MAX 0x8 +#define XADC_THRESHOLD_VCCPINT_MAX 0x9 +#define XADC_THRESHOLD_VCCPAUX_MAX 0xa +#define XADC_THRESHOLD_VCCODDR_MAX 0xb +#define XADC_THRESHOLD_VCCBRAM_MIN 0xc +#define XADC_THRESHOLD_VCCPINT_MIN 0xd +#define XADC_THRESHOLD_VCCPAUX_MIN 0xe +#define XADC_THRESHOLD_VCCODDR_MIN 0xf + +#endif diff --git a/drivers/iio/amplifiers/Kconfig b/drivers/iio/amplifiers/Kconfig new file mode 100644 index 000000000..e9c5f2cd9 --- /dev/null +++ b/drivers/iio/amplifiers/Kconfig @@ -0,0 +1,19 @@ +# +# Gain Amplifiers, etc. +# +# When adding new entries keep the list in alphabetical order + +menu "Amplifiers" + +config AD8366 + tristate "Analog Devices AD8366 VGA" + depends on SPI + select BITREVERSE + help + Say yes here to build support for Analog Devices AD8366 + SPI Dual-Digital Variable Gain Amplifier (VGA). + + To compile this driver as a module, choose M here: the + module will be called ad8366. + +endmenu diff --git a/drivers/iio/amplifiers/Makefile b/drivers/iio/amplifiers/Makefile new file mode 100644 index 000000000..8da4b7878 --- /dev/null +++ b/drivers/iio/amplifiers/Makefile @@ -0,0 +1,6 @@ +# +# Makefile iio/amplifiers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD8366) += ad8366.o diff --git a/drivers/iio/amplifiers/ad8366.c b/drivers/iio/amplifiers/ad8366.c new file mode 100644 index 000000000..c0d364eba --- /dev/null +++ b/drivers/iio/amplifiers/ad8366.c @@ -0,0 +1,213 @@ +/* + * AD8366 SPI Dual-Digital Variable Gain Amplifier (VGA) + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/bitrev.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +struct ad8366_state { + struct spi_device *spi; + struct regulator *reg; + unsigned char ch[2]; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + unsigned char data[2] ____cacheline_aligned; +}; + +static int ad8366_write(struct iio_dev *indio_dev, + unsigned char ch_a, unsigned char ch_b) +{ + struct ad8366_state *st = iio_priv(indio_dev); + int ret; + + ch_a = bitrev8(ch_a & 0x3F); + ch_b = bitrev8(ch_b & 0x3F); + + st->data[0] = ch_b >> 4; + st->data[1] = (ch_b << 4) | (ch_a >> 2); + + ret = spi_write(st->spi, st->data, ARRAY_SIZE(st->data)); + if (ret < 0) + dev_err(&indio_dev->dev, "write failed (%d)", ret); + + return ret; +} + +static int ad8366_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad8366_state *st = iio_priv(indio_dev); + int ret; + unsigned code; + + mutex_lock(&indio_dev->mlock); + switch (m) { + case IIO_CHAN_INFO_HARDWAREGAIN: + code = st->ch[chan->channel]; + + /* Values in dB */ + code = code * 253 + 4500; + *val = code / 1000; + *val2 = (code % 1000) * 1000; + + ret = IIO_VAL_INT_PLUS_MICRO_DB; + break; + default: + ret = -EINVAL; + } + mutex_unlock(&indio_dev->mlock); + + return ret; +}; + +static int ad8366_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad8366_state *st = iio_priv(indio_dev); + unsigned code; + int ret; + + if (val < 0 || val2 < 0) + return -EINVAL; + + /* Values in dB */ + code = (((u8)val * 1000) + ((u32)val2 / 1000)); + + if (code > 20500 || code < 4500) + return -EINVAL; + + code = (code - 4500) / 253; + + mutex_lock(&indio_dev->mlock); + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + st->ch[chan->channel] = code; + ret = ad8366_write(indio_dev, st->ch[0], st->ch[1]); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static const struct iio_info ad8366_info = { + .read_raw = &ad8366_read_raw, + .write_raw = &ad8366_write_raw, + .driver_module = THIS_MODULE, +}; + +#define AD8366_CHAN(_channel) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = _channel, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_HARDWAREGAIN),\ +} + +static const struct iio_chan_spec ad8366_channels[] = { + AD8366_CHAN(0), + AD8366_CHAN(1), +}; + +static int ad8366_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad8366_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + } + + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad8366_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad8366_channels; + indio_dev->num_channels = ARRAY_SIZE(ad8366_channels); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + ad8366_write(indio_dev, 0, 0); + + return 0; + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ad8366_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad8366_state *st = iio_priv(indio_dev); + struct regulator *reg = st->reg; + + iio_device_unregister(indio_dev); + + if (!IS_ERR(reg)) + regulator_disable(reg); + + return 0; +} + +static const struct spi_device_id ad8366_id[] = { + {"ad8366", 0}, + {} +}; + +static struct spi_driver ad8366_driver = { + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + }, + .probe = ad8366_probe, + .remove = ad8366_remove, + .id_table = ad8366_id, +}; + +module_spi_driver(ad8366_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD8366 VGA"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/buffer_cb.c b/drivers/iio/buffer_cb.c new file mode 100644 index 000000000..eb46e728a --- /dev/null +++ b/drivers/iio/buffer_cb.c @@ -0,0 +1,124 @@ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/iio/buffer.h> +#include <linux/iio/consumer.h> + +struct iio_cb_buffer { + struct iio_buffer buffer; + int (*cb)(const void *data, void *private); + void *private; + struct iio_channel *channels; +}; + +static struct iio_cb_buffer *buffer_to_cb_buffer(struct iio_buffer *buffer) +{ + return container_of(buffer, struct iio_cb_buffer, buffer); +} + +static int iio_buffer_cb_store_to(struct iio_buffer *buffer, const void *data) +{ + struct iio_cb_buffer *cb_buff = buffer_to_cb_buffer(buffer); + return cb_buff->cb(data, cb_buff->private); +} + +static void iio_buffer_cb_release(struct iio_buffer *buffer) +{ + struct iio_cb_buffer *cb_buff = buffer_to_cb_buffer(buffer); + kfree(cb_buff->buffer.scan_mask); + kfree(cb_buff); +} + +static const struct iio_buffer_access_funcs iio_cb_access = { + .store_to = &iio_buffer_cb_store_to, + .release = &iio_buffer_cb_release, +}; + +struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, + int (*cb)(const void *data, + void *private), + void *private) +{ + int ret; + struct iio_cb_buffer *cb_buff; + struct iio_dev *indio_dev; + struct iio_channel *chan; + + cb_buff = kzalloc(sizeof(*cb_buff), GFP_KERNEL); + if (cb_buff == NULL) + return ERR_PTR(-ENOMEM); + + iio_buffer_init(&cb_buff->buffer); + + cb_buff->private = private; + cb_buff->cb = cb; + cb_buff->buffer.access = &iio_cb_access; + INIT_LIST_HEAD(&cb_buff->buffer.demux_list); + + cb_buff->channels = iio_channel_get_all(dev); + if (IS_ERR(cb_buff->channels)) { + ret = PTR_ERR(cb_buff->channels); + goto error_free_cb_buff; + } + + indio_dev = cb_buff->channels[0].indio_dev; + cb_buff->buffer.scan_mask + = kcalloc(BITS_TO_LONGS(indio_dev->masklength), sizeof(long), + GFP_KERNEL); + if (cb_buff->buffer.scan_mask == NULL) { + ret = -ENOMEM; + goto error_release_channels; + } + chan = &cb_buff->channels[0]; + while (chan->indio_dev) { + if (chan->indio_dev != indio_dev) { + ret = -EINVAL; + goto error_free_scan_mask; + } + set_bit(chan->channel->scan_index, + cb_buff->buffer.scan_mask); + chan++; + } + + return cb_buff; + +error_free_scan_mask: + kfree(cb_buff->buffer.scan_mask); +error_release_channels: + iio_channel_release_all(cb_buff->channels); +error_free_cb_buff: + kfree(cb_buff); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(iio_channel_get_all_cb); + +int iio_channel_start_all_cb(struct iio_cb_buffer *cb_buff) +{ + return iio_update_buffers(cb_buff->channels[0].indio_dev, + &cb_buff->buffer, + NULL); +} +EXPORT_SYMBOL_GPL(iio_channel_start_all_cb); + +void iio_channel_stop_all_cb(struct iio_cb_buffer *cb_buff) +{ + iio_update_buffers(cb_buff->channels[0].indio_dev, + NULL, + &cb_buff->buffer); +} +EXPORT_SYMBOL_GPL(iio_channel_stop_all_cb); + +void iio_channel_release_all_cb(struct iio_cb_buffer *cb_buff) +{ + iio_channel_release_all(cb_buff->channels); + iio_buffer_put(&cb_buff->buffer); +} +EXPORT_SYMBOL_GPL(iio_channel_release_all_cb); + +struct iio_channel +*iio_channel_cb_get_channels(const struct iio_cb_buffer *cb_buffer) +{ + return cb_buffer->channels; +} +EXPORT_SYMBOL_GPL(iio_channel_cb_get_channels); diff --git a/drivers/iio/common/Kconfig b/drivers/iio/common/Kconfig new file mode 100644 index 000000000..790f106d7 --- /dev/null +++ b/drivers/iio/common/Kconfig @@ -0,0 +1,7 @@ +# +# IIO common modules +# + +source "drivers/iio/common/hid-sensors/Kconfig" +source "drivers/iio/common/ssp_sensors/Kconfig" +source "drivers/iio/common/st_sensors/Kconfig" diff --git a/drivers/iio/common/Makefile b/drivers/iio/common/Makefile new file mode 100644 index 000000000..b1e4d9c95 --- /dev/null +++ b/drivers/iio/common/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the IIO common modules. +# Common modules contains modules, which can be shared among multiple +# IIO modules. For example if the trigger processing is common for +# multiple IIO modules then this can be moved to a common module +# instead of duplicating in each module. +# + +# When adding new entries keep the list in alphabetical order +obj-y += hid-sensors/ +obj-y += ssp_sensors/ +obj-y += st_sensors/ diff --git a/drivers/iio/common/hid-sensors/Kconfig b/drivers/iio/common/hid-sensors/Kconfig new file mode 100644 index 000000000..39188b72c --- /dev/null +++ b/drivers/iio/common/hid-sensors/Kconfig @@ -0,0 +1,28 @@ +# +# Hid Sensor common modules +# +menu "Hid Sensor IIO Common" + +config HID_SENSOR_IIO_COMMON + tristate "Common modules for all HID Sensor IIO drivers" + depends on HID_SENSOR_HUB + select HID_SENSOR_IIO_TRIGGER if IIO_BUFFER + help + Say yes here to build support for HID sensor to use + HID sensor common processing for attributes and IIO triggers. + There are many attributes which can be shared among multiple + HID sensor drivers, this module contains processing for those + attributes. + +config HID_SENSOR_IIO_TRIGGER + tristate "Common module (trigger) for all HID Sensor IIO drivers" + depends on HID_SENSOR_HUB && HID_SENSOR_IIO_COMMON + select IIO_TRIGGER + help + Say yes here to build trigger support for HID sensors. + Triggers will be send if all requested attributes were read. + + If this driver is compiled as a module, it will be named + hid-sensor-trigger. + +endmenu diff --git a/drivers/iio/common/hid-sensors/Makefile b/drivers/iio/common/hid-sensors/Makefile new file mode 100644 index 000000000..22e7c5a82 --- /dev/null +++ b/drivers/iio/common/hid-sensors/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Hid sensor common modules. +# + +obj-$(CONFIG_HID_SENSOR_IIO_COMMON) += hid-sensor-iio-common.o +obj-$(CONFIG_HID_SENSOR_IIO_TRIGGER) += hid-sensor-trigger.o +hid-sensor-iio-common-y := hid-sensor-attributes.o diff --git a/drivers/iio/common/hid-sensors/hid-sensor-attributes.c b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c new file mode 100644 index 000000000..e81f43476 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -0,0 +1,397 @@ +/* + * HID Sensors Driver + * Copyright (c) 2012, 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; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +static struct { + u32 usage_id; + int unit; /* 0 for default others from HID sensor spec */ + int scale_val0; /* scale, whole number */ + int scale_val1; /* scale, fraction in micros */ +} unit_conversion[] = { + {HID_USAGE_SENSOR_ACCEL_3D, 0, 9, 806650}, + {HID_USAGE_SENSOR_ACCEL_3D, + HID_USAGE_SENSOR_UNITS_METERS_PER_SEC_SQRD, 1, 0}, + {HID_USAGE_SENSOR_ACCEL_3D, + HID_USAGE_SENSOR_UNITS_G, 9, 806650}, + + {HID_USAGE_SENSOR_GYRO_3D, 0, 0, 17453}, + {HID_USAGE_SENSOR_GYRO_3D, + HID_USAGE_SENSOR_UNITS_RADIANS_PER_SECOND, 1, 0}, + {HID_USAGE_SENSOR_GYRO_3D, + HID_USAGE_SENSOR_UNITS_DEGREES_PER_SECOND, 0, 17453}, + + {HID_USAGE_SENSOR_COMPASS_3D, 0, 0, 1000}, + {HID_USAGE_SENSOR_COMPASS_3D, HID_USAGE_SENSOR_UNITS_GAUSS, 1, 0}, + + {HID_USAGE_SENSOR_INCLINOMETER_3D, 0, 0, 17453}, + {HID_USAGE_SENSOR_INCLINOMETER_3D, + HID_USAGE_SENSOR_UNITS_DEGREES, 0, 17453}, + {HID_USAGE_SENSOR_INCLINOMETER_3D, + HID_USAGE_SENSOR_UNITS_RADIANS, 1, 0}, + + {HID_USAGE_SENSOR_ALS, 0, 1, 0}, + {HID_USAGE_SENSOR_ALS, HID_USAGE_SENSOR_UNITS_LUX, 1, 0}, + + {HID_USAGE_SENSOR_PRESSURE, 0, 100000, 0}, + {HID_USAGE_SENSOR_PRESSURE, HID_USAGE_SENSOR_UNITS_PASCAL, 1, 0}, +}; + +static int pow_10(unsigned power) +{ + int i; + int ret = 1; + for (i = 0; i < power; ++i) + ret = ret * 10; + + return ret; +} + +static void simple_div(int dividend, int divisor, int *whole, + int *micro_frac) +{ + int rem; + int exp = 0; + + *micro_frac = 0; + if (divisor == 0) { + *whole = 0; + return; + } + *whole = dividend/divisor; + rem = dividend % divisor; + if (rem) { + while (rem <= divisor) { + rem *= 10; + exp++; + } + *micro_frac = (rem / divisor) * pow_10(6-exp); + } +} + +static void split_micro_fraction(unsigned int no, int exp, int *val1, int *val2) +{ + *val1 = no/pow_10(exp); + *val2 = no%pow_10(exp) * pow_10(6-exp); +} + +/* +VTF format uses exponent and variable size format. +For example if the size is 2 bytes +0x0067 with VTF16E14 format -> +1.03 +To convert just change to 0x67 to decimal and use two decimal as E14 stands +for 10^-2. +Negative numbers are 2's complement +*/ +static void convert_from_vtf_format(u32 value, int size, int exp, + int *val1, int *val2) +{ + int sign = 1; + + if (value & BIT(size*8 - 1)) { + value = ((1LL << (size * 8)) - value); + sign = -1; + } + exp = hid_sensor_convert_exponent(exp); + if (exp >= 0) { + *val1 = sign * value * pow_10(exp); + *val2 = 0; + } else { + split_micro_fraction(value, -exp, val1, val2); + if (*val1) + *val1 = sign * (*val1); + else + *val2 = sign * (*val2); + } +} + +static u32 convert_to_vtf_format(int size, int exp, int val1, int val2) +{ + u32 value; + int sign = 1; + + if (val1 < 0 || val2 < 0) + sign = -1; + exp = hid_sensor_convert_exponent(exp); + if (exp < 0) { + value = abs(val1) * pow_10(-exp); + value += abs(val2) / pow_10(6+exp); + } else + value = abs(val1) / pow_10(exp); + if (sign < 0) + value = ((1LL << (size * 8)) - value); + + return value; +} + +s32 hid_sensor_read_poll_value(struct hid_sensor_common *st) +{ + s32 value = 0; + int ret; + + ret = sensor_hub_get_feature(st->hsdev, + st->poll.report_id, + st->poll.index, sizeof(value), &value); + + if (ret < 0 || value < 0) { + return -EINVAL; + } else { + if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND) + value = value * 1000; + } + + return value; +} +EXPORT_SYMBOL(hid_sensor_read_poll_value); + +int hid_sensor_read_samp_freq_value(struct hid_sensor_common *st, + int *val1, int *val2) +{ + s32 value; + int ret; + + ret = sensor_hub_get_feature(st->hsdev, + st->poll.report_id, + st->poll.index, sizeof(value), &value); + if (ret < 0 || value < 0) { + *val1 = *val2 = 0; + return -EINVAL; + } else { + if (st->poll.units == HID_USAGE_SENSOR_UNITS_MILLISECOND) + simple_div(1000, value, val1, val2); + else if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND) + simple_div(1, value, val1, val2); + else { + *val1 = *val2 = 0; + return -EINVAL; + } + } + + return IIO_VAL_INT_PLUS_MICRO; +} +EXPORT_SYMBOL(hid_sensor_read_samp_freq_value); + +int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st, + int val1, int val2) +{ + s32 value; + int ret; + + if (val1 < 0 || val2 < 0) + ret = -EINVAL; + + value = val1 * pow_10(6) + val2; + if (value) { + if (st->poll.units == HID_USAGE_SENSOR_UNITS_MILLISECOND) + value = pow_10(9)/value; + else if (st->poll.units == HID_USAGE_SENSOR_UNITS_SECOND) + value = pow_10(6)/value; + else + value = 0; + } + ret = sensor_hub_set_feature(st->hsdev, st->poll.report_id, + st->poll.index, sizeof(value), &value); + if (ret < 0 || value < 0) + ret = -EINVAL; + + return ret; +} +EXPORT_SYMBOL(hid_sensor_write_samp_freq_value); + +int hid_sensor_read_raw_hyst_value(struct hid_sensor_common *st, + int *val1, int *val2) +{ + s32 value; + int ret; + + ret = sensor_hub_get_feature(st->hsdev, + st->sensitivity.report_id, + st->sensitivity.index, sizeof(value), + &value); + if (ret < 0 || value < 0) { + *val1 = *val2 = 0; + return -EINVAL; + } else { + convert_from_vtf_format(value, st->sensitivity.size, + st->sensitivity.unit_expo, + val1, val2); + } + + return IIO_VAL_INT_PLUS_MICRO; +} +EXPORT_SYMBOL(hid_sensor_read_raw_hyst_value); + +int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st, + int val1, int val2) +{ + s32 value; + int ret; + + value = convert_to_vtf_format(st->sensitivity.size, + st->sensitivity.unit_expo, + val1, val2); + ret = sensor_hub_set_feature(st->hsdev, st->sensitivity.report_id, + st->sensitivity.index, sizeof(value), + &value); + if (ret < 0 || value < 0) + ret = -EINVAL; + + return ret; +} +EXPORT_SYMBOL(hid_sensor_write_raw_hyst_value); + +/* + * This fuction applies the unit exponent to the scale. + * For example: + * 9.806650 ->exp:2-> val0[980]val1[665000] + * 9.000806 ->exp:2-> val0[900]val1[80600] + * 0.174535 ->exp:2-> val0[17]val1[453500] + * 1.001745 ->exp:0-> val0[1]val1[1745] + * 1.001745 ->exp:2-> val0[100]val1[174500] + * 1.001745 ->exp:4-> val0[10017]val1[450000] + * 9.806650 ->exp:-2-> val0[0]val1[98066] + */ +static void adjust_exponent_micro(int *val0, int *val1, int scale0, + int scale1, int exp) +{ + int i; + int x; + int res; + int rem; + + if (exp > 0) { + *val0 = scale0 * pow_10(exp); + res = 0; + if (exp > 6) { + *val1 = 0; + return; + } + for (i = 0; i < exp; ++i) { + x = scale1 / pow_10(5 - i); + res += (pow_10(exp - 1 - i) * x); + scale1 = scale1 % pow_10(5 - i); + } + *val0 += res; + *val1 = scale1 * pow_10(exp); + } else if (exp < 0) { + exp = abs(exp); + if (exp > 6) { + *val0 = *val1 = 0; + return; + } + *val0 = scale0 / pow_10(exp); + rem = scale0 % pow_10(exp); + res = 0; + for (i = 0; i < (6 - exp); ++i) { + x = scale1 / pow_10(5 - i); + res += (pow_10(5 - exp - i) * x); + scale1 = scale1 % pow_10(5 - i); + } + *val1 = rem * pow_10(6 - exp) + res; + } else { + *val0 = scale0; + *val1 = scale1; + } +} + +int hid_sensor_format_scale(u32 usage_id, + struct hid_sensor_hub_attribute_info *attr_info, + int *val0, int *val1) +{ + int i; + int exp; + + *val0 = 1; + *val1 = 0; + + for (i = 0; i < ARRAY_SIZE(unit_conversion); ++i) { + if (unit_conversion[i].usage_id == usage_id && + unit_conversion[i].unit == attr_info->units) { + exp = hid_sensor_convert_exponent( + attr_info->unit_expo); + adjust_exponent_micro(val0, val1, + unit_conversion[i].scale_val0, + unit_conversion[i].scale_val1, exp); + break; + } + } + + return IIO_VAL_INT_PLUS_MICRO; +} +EXPORT_SYMBOL(hid_sensor_format_scale); + +static +int hid_sensor_get_reporting_interval(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + struct hid_sensor_common *st) +{ + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_PROP_REPORT_INTERVAL, + &st->poll); + /* Default unit of measure is milliseconds */ + if (st->poll.units == 0) + st->poll.units = HID_USAGE_SENSOR_UNITS_MILLISECOND; + return 0; + +} + +int hid_sensor_parse_common_attributes(struct hid_sensor_hub_device *hsdev, + u32 usage_id, + struct hid_sensor_common *st) +{ + + + hid_sensor_get_reporting_interval(hsdev, usage_id, st); + + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_PROP_REPORT_STATE, + &st->report_state); + + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_PROY_POWER_STATE, + &st->power_state); + + sensor_hub_input_get_attribute_info(hsdev, + HID_FEATURE_REPORT, usage_id, + HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS, + &st->sensitivity); + + hid_dbg(hsdev->hdev, "common attributes: %x:%x, %x:%x, %x:%x %x:%x\n", + st->poll.index, st->poll.report_id, + st->report_state.index, st->report_state.report_id, + st->power_state.index, st->power_state.report_id, + st->sensitivity.index, st->sensitivity.report_id); + + return 0; +} +EXPORT_SYMBOL(hid_sensor_parse_common_attributes); + +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>"); +MODULE_DESCRIPTION("HID Sensor common attribute processing"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c new file mode 100644 index 000000000..595511022 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -0,0 +1,220 @@ +/* + * HID Sensors Driver + * Copyright (c) 2012, 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; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/sysfs.h> +#include "hid-sensor-trigger.h" + +static int _hid_sensor_power_state(struct hid_sensor_common *st, bool state) +{ + int state_val; + int report_val; + s32 poll_value = 0; + + if (state) { + if (!atomic_read(&st->user_requested_state)) + return 0; + if (sensor_hub_device_open(st->hsdev)) + return -EIO; + + atomic_inc(&st->data_ready); + + state_val = hid_sensor_get_usage_index(st->hsdev, + st->power_state.report_id, + st->power_state.index, + HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM); + report_val = hid_sensor_get_usage_index(st->hsdev, + st->report_state.report_id, + st->report_state.index, + HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM); + + poll_value = hid_sensor_read_poll_value(st); + } else { + int val; + + val = atomic_dec_if_positive(&st->data_ready); + if (val < 0) + return 0; + + sensor_hub_device_close(st->hsdev); + state_val = hid_sensor_get_usage_index(st->hsdev, + st->power_state.report_id, + st->power_state.index, + HID_USAGE_SENSOR_PROP_POWER_STATE_D4_POWER_OFF_ENUM); + report_val = hid_sensor_get_usage_index(st->hsdev, + st->report_state.report_id, + st->report_state.index, + HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM); + } + + if (state_val >= 0) { + state_val += st->power_state.logical_minimum; + sensor_hub_set_feature(st->hsdev, st->power_state.report_id, + st->power_state.index, sizeof(state_val), + &state_val); + } + + if (report_val >= 0) { + report_val += st->report_state.logical_minimum; + sensor_hub_set_feature(st->hsdev, st->report_state.report_id, + st->report_state.index, + sizeof(report_val), + &report_val); + } + + sensor_hub_get_feature(st->hsdev, st->power_state.report_id, + st->power_state.index, + sizeof(state_val), &state_val); + if (state && poll_value) + msleep_interruptible(poll_value * 2); + + return 0; +} +EXPORT_SYMBOL(hid_sensor_power_state); + +int hid_sensor_power_state(struct hid_sensor_common *st, bool state) +{ + +#ifdef CONFIG_PM + int ret; + + atomic_set(&st->user_requested_state, state); + if (state) + ret = pm_runtime_get_sync(&st->pdev->dev); + else { + pm_runtime_mark_last_busy(&st->pdev->dev); + ret = pm_runtime_put_autosuspend(&st->pdev->dev); + } + if (ret < 0) { + if (state) + pm_runtime_put_noidle(&st->pdev->dev); + return ret; + } + + return 0; +#else + atomic_set(&st->user_requested_state, state); + return _hid_sensor_power_state(st, state); +#endif +} + +static int hid_sensor_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + return hid_sensor_power_state(iio_trigger_get_drvdata(trig), state); +} + +void hid_sensor_remove_trigger(struct hid_sensor_common *attrb) +{ + iio_trigger_unregister(attrb->trigger); + iio_trigger_free(attrb->trigger); +} +EXPORT_SYMBOL(hid_sensor_remove_trigger); + +static const struct iio_trigger_ops hid_sensor_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &hid_sensor_data_rdy_trigger_set_state, +}; + +int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name, + struct hid_sensor_common *attrb) +{ + int ret; + struct iio_trigger *trig; + + trig = iio_trigger_alloc("%s-dev%d", name, indio_dev->id); + if (trig == NULL) { + dev_err(&indio_dev->dev, "Trigger Allocate Failed\n"); + ret = -ENOMEM; + goto error_ret; + } + + trig->dev.parent = indio_dev->dev.parent; + iio_trigger_set_drvdata(trig, attrb); + trig->ops = &hid_sensor_trigger_ops; + ret = iio_trigger_register(trig); + + if (ret) { + dev_err(&indio_dev->dev, "Trigger Register Failed\n"); + goto error_free_trig; + } + attrb->trigger = trig; + indio_dev->trig = iio_trigger_get(trig); + + ret = pm_runtime_set_active(&indio_dev->dev); + if (ret) + goto error_unreg_trigger; + + iio_device_set_drvdata(indio_dev, attrb); + pm_suspend_ignore_children(&attrb->pdev->dev, true); + pm_runtime_enable(&attrb->pdev->dev); + /* Default to 3 seconds, but can be changed from sysfs */ + pm_runtime_set_autosuspend_delay(&attrb->pdev->dev, + 3000); + pm_runtime_use_autosuspend(&attrb->pdev->dev); + + return ret; +error_unreg_trigger: + iio_trigger_unregister(trig); +error_free_trig: + iio_trigger_free(trig); +error_ret: + return ret; +} +EXPORT_SYMBOL(hid_sensor_setup_trigger); + +#ifdef CONFIG_PM +static int hid_sensor_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + + return _hid_sensor_power_state(attrb, false); +} + +static int hid_sensor_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev); + + return _hid_sensor_power_state(attrb, true); +} + +#endif + +const struct dev_pm_ops hid_sensor_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(hid_sensor_suspend, hid_sensor_resume) + SET_RUNTIME_PM_OPS(hid_sensor_suspend, + hid_sensor_resume, NULL) +}; +EXPORT_SYMBOL(hid_sensor_pm_ops); + +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>"); +MODULE_DESCRIPTION("HID Sensor trigger processing"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.h b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h new file mode 100644 index 000000000..9f4713f42 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h @@ -0,0 +1,32 @@ +/* + * HID Sensors Driver + * Copyright (c) 2012, 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; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#ifndef _HID_SENSOR_TRIGGER_H +#define _HID_SENSOR_TRIGGER_H + +#include <linux/pm.h> +#include <linux/pm_runtime.h> + +extern const struct dev_pm_ops hid_sensor_pm_ops; + +int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name, + struct hid_sensor_common *attrb); +void hid_sensor_remove_trigger(struct hid_sensor_common *attrb); +int hid_sensor_power_state(struct hid_sensor_common *st, bool state); + +#endif diff --git a/drivers/iio/common/ssp_sensors/Kconfig b/drivers/iio/common/ssp_sensors/Kconfig new file mode 100644 index 000000000..0ea4faf01 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/Kconfig @@ -0,0 +1,26 @@ +# +# SSP sensor drivers and commons configuration +# +menu "SSP Sensor Common" + +config IIO_SSP_SENSORS_COMMONS + tristate "Commons for all SSP Sensor IIO drivers" + depends on IIO_SSP_SENSORHUB + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build commons for SSP sensors. + To compile this as a module, choose M here: the module + will be called ssp_iio. + +config IIO_SSP_SENSORHUB + tristate "Samsung Sensorhub driver" + depends on SPI + select MFD_CORE + help + SSP driver for sensorhub. + If you say yes here you get ssp support for sensorhub. + To compile this driver as a module, choose M here: the + module will be called sensorhub. + +endmenu diff --git a/drivers/iio/common/ssp_sensors/Makefile b/drivers/iio/common/ssp_sensors/Makefile new file mode 100644 index 000000000..1e0389eb0 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for SSP sensor drivers and commons. +# + +sensorhub-objs := ssp_dev.o ssp_spi.o +obj-$(CONFIG_IIO_SSP_SENSORHUB) += sensorhub.o + +obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_iio.o diff --git a/drivers/iio/common/ssp_sensors/ssp.h b/drivers/iio/common/ssp_sensors/ssp.h new file mode 100644 index 000000000..b910e91d7 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp.h @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * 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. + * + */ + +#ifndef __SSP_SENSORHUB_H__ +#define __SSP_SENSORHUB_H__ + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/iio/common/ssp_sensors.h> +#include <linux/iio/iio.h> +#include <linux/spi/spi.h> + +#define SSP_DEVICE_ID 0x55 + +#ifdef SSP_DBG +#define ssp_dbg(format, ...) pr_info("[SSP] "format, ##__VA_ARGS__) +#else +#define ssp_dbg(format, ...) +#endif + +#define SSP_SW_RESET_TIME 3000 +/* Sensor polling in ms */ +#define SSP_DEFAULT_POLLING_DELAY 200 +#define SSP_DEFAULT_RETRIES 3 +#define SSP_DATA_PACKET_SIZE 960 +#define SSP_HEADER_BUFFER_SIZE 4 + +enum { + SSP_KERNEL_BINARY = 0, + SSP_KERNEL_CRASHED_BINARY, +}; + +enum { + SSP_INITIALIZATION_STATE = 0, + SSP_NO_SENSOR_STATE, + SSP_ADD_SENSOR_STATE, + SSP_RUNNING_SENSOR_STATE, +}; + +/* Firmware download STATE */ +enum { + SSP_FW_DL_STATE_FAIL = -1, + SSP_FW_DL_STATE_NONE = 0, + SSP_FW_DL_STATE_NEED_TO_SCHEDULE, + SSP_FW_DL_STATE_SCHEDULED, + SSP_FW_DL_STATE_DOWNLOADING, + SSP_FW_DL_STATE_SYNC, + SSP_FW_DL_STATE_DONE, +}; + +#define SSP_INVALID_REVISION 99999 +#define SSP_INVALID_REVISION2 0xffffff + +/* AP -> SSP Instruction */ +#define SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD 0xa1 +#define SSP_MSG2SSP_INST_BYPASS_SENSOR_RM 0xa2 +#define SSP_MSG2SSP_INST_REMOVE_ALL 0xa3 +#define SSP_MSG2SSP_INST_CHANGE_DELAY 0xa4 +#define SSP_MSG2SSP_INST_LIBRARY_ADD 0xb1 +#define SSP_MSG2SSP_INST_LIBRARY_REMOVE 0xb2 +#define SSP_MSG2SSP_INST_LIB_NOTI 0xb4 +#define SSP_MSG2SSP_INST_LIB_DATA 0xc1 + +#define SSP_MSG2SSP_AP_MCU_SET_GYRO_CAL 0xcd +#define SSP_MSG2SSP_AP_MCU_SET_ACCEL_CAL 0xce +#define SSP_MSG2SSP_AP_STATUS_SHUTDOWN 0xd0 +#define SSP_MSG2SSP_AP_STATUS_WAKEUP 0xd1 +#define SSP_MSG2SSP_AP_STATUS_SLEEP 0xd2 +#define SSP_MSG2SSP_AP_STATUS_RESUME 0xd3 +#define SSP_MSG2SSP_AP_STATUS_SUSPEND 0xd4 +#define SSP_MSG2SSP_AP_STATUS_RESET 0xd5 +#define SSP_MSG2SSP_AP_STATUS_POW_CONNECTED 0xd6 +#define SSP_MSG2SSP_AP_STATUS_POW_DISCONNECTED 0xd7 +#define SSP_MSG2SSP_AP_TEMPHUMIDITY_CAL_DONE 0xda +#define SSP_MSG2SSP_AP_MCU_SET_DUMPMODE 0xdb +#define SSP_MSG2SSP_AP_MCU_DUMP_CHECK 0xdc +#define SSP_MSG2SSP_AP_MCU_BATCH_FLUSH 0xdd +#define SSP_MSG2SSP_AP_MCU_BATCH_COUNT 0xdf + +#define SSP_MSG2SSP_AP_WHOAMI 0x0f +#define SSP_MSG2SSP_AP_FIRMWARE_REV 0xf0 +#define SSP_MSG2SSP_AP_SENSOR_FORMATION 0xf1 +#define SSP_MSG2SSP_AP_SENSOR_PROXTHRESHOLD 0xf2 +#define SSP_MSG2SSP_AP_SENSOR_BARCODE_EMUL 0xf3 +#define SSP_MSG2SSP_AP_SENSOR_SCANNING 0xf4 +#define SSP_MSG2SSP_AP_SET_MAGNETIC_HWOFFSET 0xf5 +#define SSP_MSG2SSP_AP_GET_MAGNETIC_HWOFFSET 0xf6 +#define SSP_MSG2SSP_AP_SENSOR_GESTURE_CURRENT 0xf7 +#define SSP_MSG2SSP_AP_GET_THERM 0xf8 +#define SSP_MSG2SSP_AP_GET_BIG_DATA 0xf9 +#define SSP_MSG2SSP_AP_SET_BIG_DATA 0xfa +#define SSP_MSG2SSP_AP_START_BIG_DATA 0xfb +#define SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX 0xfd +#define SSP_MSG2SSP_AP_SENSOR_TILT 0xea +#define SSP_MSG2SSP_AP_MCU_SET_TIME 0xfe +#define SSP_MSG2SSP_AP_MCU_GET_TIME 0xff + +#define SSP_MSG2SSP_AP_FUSEROM 0x01 + +/* voice data */ +#define SSP_TYPE_WAKE_UP_VOICE_SERVICE 0x01 +#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_AM 0x01 +#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_GRAMMER 0x02 + +/* Factory Test */ +#define SSP_ACCELEROMETER_FACTORY 0x80 +#define SSP_GYROSCOPE_FACTORY 0x81 +#define SSP_GEOMAGNETIC_FACTORY 0x82 +#define SSP_PRESSURE_FACTORY 0x85 +#define SSP_GESTURE_FACTORY 0x86 +#define SSP_TEMPHUMIDITY_CRC_FACTORY 0x88 +#define SSP_GYROSCOPE_TEMP_FACTORY 0x8a +#define SSP_GYROSCOPE_DPS_FACTORY 0x8b +#define SSP_MCU_FACTORY 0x8c +#define SSP_MCU_SLEEP_FACTORY 0x8d + +/* SSP -> AP ACK about write CMD */ +#define SSP_MSG_ACK 0x80 /* ACK from SSP to AP */ +#define SSP_MSG_NAK 0x70 /* NAK from SSP to AP */ + +struct ssp_sensorhub_info { + char *fw_name; + char *fw_crashed_name; + unsigned int fw_rev; + const u8 * const mag_table; + const unsigned int mag_length; +}; + +/* ssp_msg options bit */ +#define SSP_RW 0 +#define SSP_INDEX 3 + +#define SSP_AP2HUB_READ 0 +#define SSP_AP2HUB_WRITE 1 +#define SSP_HUB2AP_WRITE 2 +#define SSP_AP2HUB_READY 3 +#define SSP_AP2HUB_RETURN 4 + +/** + * struct ssp_data - ssp platformdata structure + * @spi: spi device + * @sensorhub_info: info about sensorhub board specific features + * @wdt_timer: watchdog timer + * @work_wdt: watchdog work + * @work_firmware: firmware upgrade work queue + * @work_refresh: refresh work queue for reset request from MCU + * @shut_down: shut down flag + * @mcu_dump_mode: mcu dump mode for debug + * @time_syncing: time syncing indication flag + * @timestamp: previous time in ns calculated for time syncing + * @check_status: status table for each sensor + * @com_fail_cnt: communication fail count + * @reset_cnt: reset count + * @timeout_cnt: timeout count + * @available_sensors: available sensors seen by sensorhub (bit array) + * @cur_firm_rev: cached current firmware revision + * @last_resume_state: last AP resume/suspend state used to handle the PM + * state of ssp + * @last_ap_state: (obsolete) sleep notification for MCU + * @sensor_enable: sensor enable mask + * @delay_buf: data acquisition intervals table + * @batch_latency_buf: yet unknown but existing in communication protocol + * @batch_opt_buf: yet unknown but existing in communication protocol + * @accel_position: yet unknown but existing in communication protocol + * @mag_position: yet unknown but existing in communication protocol + * @fw_dl_state: firmware download state + * @comm_lock: lock protecting the handshake + * @pending_lock: lock protecting pending list and completion + * @mcu_reset_gpio: mcu reset line + * @ap_mcu_gpio: ap to mcu gpio line + * @mcu_ap_gpio: mcu to ap gpio line + * @pending_list: pending list for messages queued to be sent/read + * @sensor_devs: registered IIO devices table + * @enable_refcount: enable reference count for wdt (watchdog timer) + * @header_buffer: cache aligned buffer for packet header + */ +struct ssp_data { + struct spi_device *spi; + struct ssp_sensorhub_info *sensorhub_info; + struct timer_list wdt_timer; + struct work_struct work_wdt; + struct delayed_work work_refresh; + + bool shut_down; + bool mcu_dump_mode; + bool time_syncing; + int64_t timestamp; + + int check_status[SSP_SENSOR_MAX]; + + unsigned int com_fail_cnt; + unsigned int reset_cnt; + unsigned int timeout_cnt; + + unsigned int available_sensors; + unsigned int cur_firm_rev; + + char last_resume_state; + char last_ap_state; + + unsigned int sensor_enable; + u32 delay_buf[SSP_SENSOR_MAX]; + s32 batch_latency_buf[SSP_SENSOR_MAX]; + s8 batch_opt_buf[SSP_SENSOR_MAX]; + + int accel_position; + int mag_position; + int fw_dl_state; + + struct mutex comm_lock; + struct mutex pending_lock; + + int mcu_reset_gpio; + int ap_mcu_gpio; + int mcu_ap_gpio; + + struct list_head pending_list; + + struct iio_dev *sensor_devs[SSP_SENSOR_MAX]; + atomic_t enable_refcount; + + __le16 header_buffer[SSP_HEADER_BUFFER_SIZE / sizeof(__le16)] + ____cacheline_aligned; +}; + +void ssp_clean_pending_list(struct ssp_data *data); + +int ssp_command(struct ssp_data *data, char command, int arg); + +int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type, + u8 *send_buf, u8 length); + +int ssp_irq_msg(struct ssp_data *data); + +int ssp_get_chipid(struct ssp_data *data); + +int ssp_set_magnetic_matrix(struct ssp_data *data); + +unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data); + +unsigned int ssp_get_firmware_rev(struct ssp_data *data); + +int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay); + +#endif /* __SSP_SENSORHUB_H__ */ diff --git a/drivers/iio/common/ssp_sensors/ssp_dev.c b/drivers/iio/common/ssp_sensors/ssp_dev.c new file mode 100644 index 000000000..0748301d3 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_dev.c @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * 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. + * + */ + +#include <linux/iio/iio.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/of_platform.h> +#include "ssp.h" + +#define SSP_WDT_TIME 10000 +#define SSP_LIMIT_RESET_CNT 20 +#define SSP_LIMIT_TIMEOUT_CNT 3 + +/* It is possible that it is max clk rate for version 1.0 of bootcode */ +#define SSP_BOOT_SPI_HZ 400000 + +/* + * These fields can look enigmatic but this structure is used mainly to flat + * some values and depends on command type. + */ +struct ssp_instruction { + __le32 a; + __le32 b; + u8 c; +} __attribute__((__packed__)); + +static const u8 ssp_magnitude_table[] = {110, 85, 171, 71, 203, 195, 0, 67, + 208, 56, 175, 244, 206, 213, 0, 92, 250, 0, 55, 48, 189, 252, 171, + 243, 13, 45, 250}; + +static const struct ssp_sensorhub_info ssp_rinato_info = { + .fw_name = "/*(DEBLOBBED)*/", + .fw_crashed_name = "/*(DEBLOBBED)*/", + .fw_rev = 14052300, + .mag_table = ssp_magnitude_table, + .mag_length = ARRAY_SIZE(ssp_magnitude_table), +}; + +static const struct ssp_sensorhub_info ssp_thermostat_info = { + .fw_name = "/*(DEBLOBBED)*/", + .fw_crashed_name = "/*(DEBLOBBED)*/", + .fw_rev = 14080600, + .mag_table = ssp_magnitude_table, + .mag_length = ARRAY_SIZE(ssp_magnitude_table), +}; + +static const struct mfd_cell sensorhub_sensor_devs[] = { + { + .name = "ssp-accelerometer", + }, + { + .name = "ssp-gyroscope", + }, +}; + +static void ssp_toggle_mcu_reset_gpio(struct ssp_data *data) +{ + gpio_set_value(data->mcu_reset_gpio, 0); + usleep_range(1000, 1200); + gpio_set_value(data->mcu_reset_gpio, 1); + msleep(50); +} + +static void ssp_sync_available_sensors(struct ssp_data *data) +{ + int i, ret; + + for (i = 0; i < SSP_SENSOR_MAX; ++i) { + if (data->available_sensors & BIT(i)) { + ret = ssp_enable_sensor(data, i, data->delay_buf[i]); + if (ret < 0) { + dev_err(&data->spi->dev, + "Sync sensor nr: %d fail\n", i); + continue; + } + } + } + + ret = ssp_command(data, SSP_MSG2SSP_AP_MCU_SET_DUMPMODE, + data->mcu_dump_mode); + if (ret < 0) + dev_err(&data->spi->dev, + "SSP_MSG2SSP_AP_MCU_SET_DUMPMODE failed\n"); +} + +static void ssp_enable_mcu(struct ssp_data *data, bool enable) +{ + dev_info(&data->spi->dev, "current shutdown = %d, old = %d\n", enable, + data->shut_down); + + if (enable && data->shut_down) { + data->shut_down = false; + enable_irq(data->spi->irq); + enable_irq_wake(data->spi->irq); + } else if (!enable && !data->shut_down) { + data->shut_down = true; + disable_irq(data->spi->irq); + disable_irq_wake(data->spi->irq); + } else { + dev_warn(&data->spi->dev, "current shutdown = %d, old = %d\n", + enable, data->shut_down); + } +} + +/* + * This function is the first one which communicates with the mcu so it is + * possible that the first attempt will fail + */ +static int ssp_check_fwbl(struct ssp_data *data) +{ + int retries = 0; + + while (retries++ < 5) { + data->cur_firm_rev = ssp_get_firmware_rev(data); + if (data->cur_firm_rev == SSP_INVALID_REVISION || + data->cur_firm_rev == SSP_INVALID_REVISION2) { + dev_warn(&data->spi->dev, + "Invalid revision, trying %d time\n", retries); + } else { + break; + } + } + + if (data->cur_firm_rev == SSP_INVALID_REVISION || + data->cur_firm_rev == SSP_INVALID_REVISION2) { + dev_err(&data->spi->dev, "SSP_INVALID_REVISION\n"); + return SSP_FW_DL_STATE_NEED_TO_SCHEDULE; + } + + dev_info(&data->spi->dev, + "MCU Firm Rev : Old = %8u, New = %8u\n", + data->cur_firm_rev, + data->sensorhub_info->fw_rev); + + if (data->cur_firm_rev != data->sensorhub_info->fw_rev) + return SSP_FW_DL_STATE_NEED_TO_SCHEDULE; + + return SSP_FW_DL_STATE_NONE; +} + +static void ssp_reset_mcu(struct ssp_data *data) +{ + ssp_enable_mcu(data, false); + ssp_clean_pending_list(data); + ssp_toggle_mcu_reset_gpio(data); + ssp_enable_mcu(data, true); +} + +static void ssp_wdt_work_func(struct work_struct *work) +{ + struct ssp_data *data = container_of(work, struct ssp_data, work_wdt); + + dev_err(&data->spi->dev, "%s - Sensor state: 0x%x, RC: %u, CC: %u\n", + __func__, data->available_sensors, data->reset_cnt, + data->com_fail_cnt); + + ssp_reset_mcu(data); + data->com_fail_cnt = 0; + data->timeout_cnt = 0; +} + +static void ssp_wdt_timer_func(unsigned long ptr) +{ + struct ssp_data *data = (struct ssp_data *)ptr; + + switch (data->fw_dl_state) { + case SSP_FW_DL_STATE_FAIL: + case SSP_FW_DL_STATE_DOWNLOADING: + case SSP_FW_DL_STATE_SYNC: + goto _mod; + } + + if (data->timeout_cnt > SSP_LIMIT_TIMEOUT_CNT || + data->com_fail_cnt > SSP_LIMIT_RESET_CNT) + queue_work(system_power_efficient_wq, &data->work_wdt); +_mod: + mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME)); +} + +static void ssp_enable_wdt_timer(struct ssp_data *data) +{ + mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME)); +} + +static void ssp_disable_wdt_timer(struct ssp_data *data) +{ + del_timer_sync(&data->wdt_timer); + cancel_work_sync(&data->work_wdt); +} + +/** + * ssp_get_sensor_delay() - gets sensor data acquisition period + * @data: sensorhub structure + * @type: SSP sensor type + * + * Returns acquisition period in ms + */ +u32 ssp_get_sensor_delay(struct ssp_data *data, enum ssp_sensor_type type) +{ + return data->delay_buf[type]; +} +EXPORT_SYMBOL(ssp_get_sensor_delay); + +/** + * ssp_enable_sensor() - enables data acquisition for sensor + * @data: sensorhub structure + * @type: SSP sensor type + * @delay: delay in ms + * + * Returns 0 or negative value in case of error + */ +int ssp_enable_sensor(struct ssp_data *data, enum ssp_sensor_type type, + u32 delay) +{ + int ret; + struct ssp_instruction to_send; + + to_send.a = cpu_to_le32(delay); + to_send.b = cpu_to_le32(data->batch_latency_buf[type]); + to_send.c = data->batch_opt_buf[type]; + + switch (data->check_status[type]) { + case SSP_INITIALIZATION_STATE: + /* do calibration step, now just enable */ + case SSP_ADD_SENSOR_STATE: + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD, + type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, "Enabling sensor failed\n"); + data->check_status[type] = SSP_NO_SENSOR_STATE; + goto derror; + } + + data->sensor_enable |= BIT(type); + data->check_status[type] = SSP_RUNNING_SENSOR_STATE; + break; + case SSP_RUNNING_SENSOR_STATE: + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_CHANGE_DELAY, type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, + "Changing sensor delay failed\n"); + goto derror; + } + break; + default: + data->check_status[type] = SSP_ADD_SENSOR_STATE; + break; + } + + data->delay_buf[type] = delay; + + if (atomic_inc_return(&data->enable_refcount) == 1) + ssp_enable_wdt_timer(data); + + return 0; + +derror: + return ret; +} +EXPORT_SYMBOL(ssp_enable_sensor); + +/** + * ssp_change_delay() - changes data acquisition for sensor + * @data: sensorhub structure + * @type: SSP sensor type + * @delay: delay in ms + * + * Returns 0 or negative value in case of error + */ +int ssp_change_delay(struct ssp_data *data, enum ssp_sensor_type type, + u32 delay) +{ + int ret; + struct ssp_instruction to_send; + + to_send.a = cpu_to_le32(delay); + to_send.b = cpu_to_le32(data->batch_latency_buf[type]); + to_send.c = data->batch_opt_buf[type]; + + ret = ssp_send_instruction(data, SSP_MSG2SSP_INST_CHANGE_DELAY, type, + (u8 *)&to_send, sizeof(to_send)); + if (ret < 0) { + dev_err(&data->spi->dev, "Changing sensor delay failed\n"); + return ret; + } + + data->delay_buf[type] = delay; + + return 0; +} +EXPORT_SYMBOL(ssp_change_delay); + +/** + * ssp_disable_sensor() - disables sensor + * + * @data: sensorhub structure + * @type: SSP sensor type + * + * Returns 0 or negative value in case of error + */ +int ssp_disable_sensor(struct ssp_data *data, enum ssp_sensor_type type) +{ + int ret; + __le32 command; + + if (data->sensor_enable & BIT(type)) { + command = cpu_to_le32(data->delay_buf[type]); + + ret = ssp_send_instruction(data, + SSP_MSG2SSP_INST_BYPASS_SENSOR_RM, + type, (u8 *)&command, + sizeof(command)); + if (ret < 0) { + dev_err(&data->spi->dev, "Remove sensor fail\n"); + return ret; + } + + data->sensor_enable &= ~BIT(type); + } + + data->check_status[type] = SSP_ADD_SENSOR_STATE; + + if (atomic_dec_and_test(&data->enable_refcount)) + ssp_disable_wdt_timer(data); + + return 0; +} +EXPORT_SYMBOL(ssp_disable_sensor); + +static irqreturn_t ssp_irq_thread_fn(int irq, void *dev_id) +{ + struct ssp_data *data = dev_id; + + /* + * This wrapper is done to preserve error path for ssp_irq_msg, also + * it is defined in different file. + */ + ssp_irq_msg(data); + + return IRQ_HANDLED; +} + +static int ssp_initialize_mcu(struct ssp_data *data) +{ + int ret; + + ssp_clean_pending_list(data); + + ret = ssp_get_chipid(data); + if (ret != SSP_DEVICE_ID) { + dev_err(&data->spi->dev, "%s - MCU %s ret = %d\n", __func__, + ret < 0 ? "is not working" : "identification failed", + ret); + return ret < 0 ? ret : -ENODEV; + } + + dev_info(&data->spi->dev, "MCU device ID = %d\n", ret); + + /* + * needs clarification, for now do not want to export all transfer + * methods to sensors' drivers + */ + ret = ssp_set_magnetic_matrix(data); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s - ssp_set_magnetic_matrix failed\n", __func__); + return ret; + } + + data->available_sensors = ssp_get_sensor_scanning_info(data); + if (data->available_sensors == 0) { + dev_err(&data->spi->dev, + "%s - ssp_get_sensor_scanning_info failed\n", __func__); + return -EIO; + } + + data->cur_firm_rev = ssp_get_firmware_rev(data); + dev_info(&data->spi->dev, "MCU Firm Rev : New = %8u\n", + data->cur_firm_rev); + + return ssp_command(data, SSP_MSG2SSP_AP_MCU_DUMP_CHECK, 0); +} + +/* + * sensorhub can request its reinitialization as some brutal and rare error + * handling. It can be requested from the MCU. + */ +static void ssp_refresh_task(struct work_struct *work) +{ + struct ssp_data *data = container_of((struct delayed_work *)work, + struct ssp_data, work_refresh); + + dev_info(&data->spi->dev, "refreshing\n"); + + data->reset_cnt++; + + if (ssp_initialize_mcu(data) >= 0) { + ssp_sync_available_sensors(data); + if (data->last_ap_state != 0) + ssp_command(data, data->last_ap_state, 0); + + if (data->last_resume_state != 0) + ssp_command(data, data->last_resume_state, 0); + + data->timeout_cnt = 0; + data->com_fail_cnt = 0; + } +} + +int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay) +{ + cancel_delayed_work_sync(&data->work_refresh); + + return queue_delayed_work(system_power_efficient_wq, + &data->work_refresh, + msecs_to_jiffies(delay)); +} + +#ifdef CONFIG_OF +static const struct of_device_id ssp_of_match[] = { + { + .compatible = "samsung,sensorhub-rinato", + .data = &ssp_rinato_info, + }, { + .compatible = "samsung,sensorhub-thermostat", + .data = &ssp_thermostat_info, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ssp_of_match); + +static struct ssp_data *ssp_parse_dt(struct device *dev) +{ + int ret; + struct ssp_data *data; + struct device_node *node = dev->of_node; + const struct of_device_id *match; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->mcu_ap_gpio = of_get_named_gpio(node, "mcu-ap-gpios", 0); + if (data->mcu_ap_gpio < 0) + goto err_free_pd; + + data->ap_mcu_gpio = of_get_named_gpio(node, "ap-mcu-gpios", 0); + if (data->ap_mcu_gpio < 0) + goto err_free_pd; + + data->mcu_reset_gpio = of_get_named_gpio(node, "mcu-reset-gpios", 0); + if (data->mcu_reset_gpio < 0) + goto err_free_pd; + + ret = devm_gpio_request_one(dev, data->ap_mcu_gpio, GPIOF_OUT_INIT_HIGH, + "ap-mcu-gpios"); + if (ret) + goto err_free_pd; + + ret = devm_gpio_request_one(dev, data->mcu_reset_gpio, + GPIOF_OUT_INIT_HIGH, "mcu-reset-gpios"); + if (ret) + goto err_ap_mcu; + + match = of_match_node(ssp_of_match, node); + if (!match) + goto err_mcu_reset_gpio; + + data->sensorhub_info = (struct ssp_sensorhub_info *)match->data; + + dev_set_drvdata(dev, data); + + return data; + +err_mcu_reset_gpio: + devm_gpio_free(dev, data->mcu_reset_gpio); +err_ap_mcu: + devm_gpio_free(dev, data->ap_mcu_gpio); +err_free_pd: + devm_kfree(dev, data); + return NULL; +} +#else +static struct ssp_data *ssp_parse_dt(struct device *pdev) +{ + return NULL; +} +#endif + +/** + * ssp_register_consumer() - registers iio consumer in ssp framework + * + * @indio_dev: consumer iio device + * @type: ssp sensor type + */ +void ssp_register_consumer(struct iio_dev *indio_dev, enum ssp_sensor_type type) +{ + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + data->sensor_devs[type] = indio_dev; +} +EXPORT_SYMBOL(ssp_register_consumer); + +static int ssp_probe(struct spi_device *spi) +{ + int ret, i; + struct ssp_data *data; + + data = ssp_parse_dt(&spi->dev); + if (!data) { + dev_err(&spi->dev, "Failed to find platform data\n"); + return -ENODEV; + } + + ret = mfd_add_devices(&spi->dev, -1, sensorhub_sensor_devs, + ARRAY_SIZE(sensorhub_sensor_devs), NULL, 0, NULL); + if (ret < 0) { + dev_err(&spi->dev, "mfd add devices fail\n"); + return ret; + } + + spi->mode = SPI_MODE_1; + ret = spi_setup(spi); + if (ret < 0) { + dev_err(&spi->dev, "Failed to setup spi\n"); + return ret; + } + + data->fw_dl_state = SSP_FW_DL_STATE_NONE; + data->spi = spi; + spi_set_drvdata(spi, data); + + mutex_init(&data->comm_lock); + + for (i = 0; i < SSP_SENSOR_MAX; ++i) { + data->delay_buf[i] = SSP_DEFAULT_POLLING_DELAY; + data->batch_latency_buf[i] = 0; + data->batch_opt_buf[i] = 0; + data->check_status[i] = SSP_INITIALIZATION_STATE; + } + + data->delay_buf[SSP_BIO_HRM_LIB] = 100; + + data->time_syncing = true; + + mutex_init(&data->pending_lock); + INIT_LIST_HEAD(&data->pending_list); + + atomic_set(&data->enable_refcount, 0); + + INIT_WORK(&data->work_wdt, ssp_wdt_work_func); + INIT_DELAYED_WORK(&data->work_refresh, ssp_refresh_task); + + setup_timer(&data->wdt_timer, ssp_wdt_timer_func, (unsigned long)data); + + ret = request_threaded_irq(data->spi->irq, NULL, + ssp_irq_thread_fn, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "SSP_Int", data); + if (ret < 0) { + dev_err(&spi->dev, "Irq request fail\n"); + goto err_setup_irq; + } + + /* Let's start with enabled one so irq balance could be ok */ + data->shut_down = false; + + /* just to avoid unbalanced irq set wake up */ + enable_irq_wake(data->spi->irq); + + data->fw_dl_state = ssp_check_fwbl(data); + if (data->fw_dl_state == SSP_FW_DL_STATE_NONE) { + ret = ssp_initialize_mcu(data); + if (ret < 0) { + dev_err(&spi->dev, "Initialize_mcu failed\n"); + goto err_read_reg; + } + } else { + dev_err(&spi->dev, "Firmware version not supported\n"); + ret = -EPERM; + goto err_read_reg; + } + + return 0; + +err_read_reg: + free_irq(data->spi->irq, data); +err_setup_irq: + mutex_destroy(&data->pending_lock); + mutex_destroy(&data->comm_lock); + + dev_err(&spi->dev, "Probe failed!\n"); + + return ret; +} + +static int ssp_remove(struct spi_device *spi) +{ + struct ssp_data *data = spi_get_drvdata(spi); + + if (ssp_command(data, SSP_MSG2SSP_AP_STATUS_SHUTDOWN, 0) < 0) + dev_err(&data->spi->dev, + "SSP_MSG2SSP_AP_STATUS_SHUTDOWN failed\n"); + + ssp_enable_mcu(data, false); + ssp_disable_wdt_timer(data); + + ssp_clean_pending_list(data); + + free_irq(data->spi->irq, data); + + del_timer_sync(&data->wdt_timer); + cancel_work_sync(&data->work_wdt); + + mutex_destroy(&data->comm_lock); + mutex_destroy(&data->pending_lock); + + mfd_remove_devices(&spi->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int ssp_suspend(struct device *dev) +{ + int ret; + struct ssp_data *data = spi_get_drvdata(to_spi_device(dev)); + + data->last_resume_state = SSP_MSG2SSP_AP_STATUS_SUSPEND; + + if (atomic_read(&data->enable_refcount) > 0) + ssp_disable_wdt_timer(data); + + ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_SUSPEND, 0); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s SSP_MSG2SSP_AP_STATUS_SUSPEND failed\n", __func__); + + ssp_enable_wdt_timer(data); + return ret; + } + + data->time_syncing = false; + disable_irq(data->spi->irq); + + return 0; +} + +static int ssp_resume(struct device *dev) +{ + int ret; + struct ssp_data *data = spi_get_drvdata(to_spi_device(dev)); + + enable_irq(data->spi->irq); + + if (atomic_read(&data->enable_refcount) > 0) + ssp_enable_wdt_timer(data); + + ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_RESUME, 0); + if (ret < 0) { + dev_err(&data->spi->dev, + "%s SSP_MSG2SSP_AP_STATUS_RESUME failed\n", __func__); + ssp_disable_wdt_timer(data); + return ret; + } + + /* timesyncing is set by MCU */ + data->last_resume_state = SSP_MSG2SSP_AP_STATUS_RESUME; + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops ssp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ssp_suspend, ssp_resume) +}; + +static struct spi_driver ssp_driver = { + .probe = ssp_probe, + .remove = ssp_remove, + .driver = { + .pm = &ssp_pm_ops, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ssp_of_match), + .name = "sensorhub" + }, +}; + +module_spi_driver(ssp_driver); + +MODULE_DESCRIPTION("ssp sensorhub driver"); +MODULE_AUTHOR("Samsung Electronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/ssp_sensors/ssp_iio.c b/drivers/iio/common/ssp_sensors/ssp_iio.c new file mode 100644 index 000000000..a3ae165f8 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_iio.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * 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. + * + */ + +#include <linux/iio/common/ssp_sensors.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/module.h> +#include <linux/slab.h> +#include "ssp_iio_sensor.h" + +/** + * ssp_common_buffer_postenable() - generic postenable callback for ssp buffer + * + * @indio_dev: iio device + * + * Returns 0 or negative value in case of error + */ +int ssp_common_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ssp_sensor_data *spd = iio_priv(indio_dev); + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + /* the allocation is made in post because scan size is known in this + * moment + * */ + spd->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL | GFP_DMA); + if (!spd->buffer) + return -ENOMEM; + + return ssp_enable_sensor(data, spd->type, + ssp_get_sensor_delay(data, spd->type)); +} +EXPORT_SYMBOL(ssp_common_buffer_postenable); + +/** + * ssp_common_buffer_postdisable() - generic postdisable callback for ssp buffer + * + * @indio_dev: iio device + * + * Returns 0 or negative value in case of error + */ +int ssp_common_buffer_postdisable(struct iio_dev *indio_dev) +{ + int ret; + struct ssp_sensor_data *spd = iio_priv(indio_dev); + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + ret = ssp_disable_sensor(data, spd->type); + if (ret < 0) + return ret; + + kfree(spd->buffer); + + return ret; +} +EXPORT_SYMBOL(ssp_common_buffer_postdisable); + +/** + * ssp_common_process_data() - Common process data callback for ssp sensors + * + * @indio_dev: iio device + * @buf: source buffer + * @len: sensor data length + * @timestamp: system timestamp + * + * Returns 0 or negative value in case of error + */ +int ssp_common_process_data(struct iio_dev *indio_dev, void *buf, + unsigned int len, int64_t timestamp) +{ + __le32 time; + int64_t calculated_time; + struct ssp_sensor_data *spd = iio_priv(indio_dev); + + if (indio_dev->scan_bytes == 0) + return 0; + + /* + * it always sends full set of samples, remember about available masks + */ + memcpy(spd->buffer, buf, len); + + if (indio_dev->scan_timestamp) { + memcpy(&time, &((char *)buf)[len], SSP_TIME_SIZE); + calculated_time = + timestamp + (int64_t)le32_to_cpu(time) * 1000000; + } + + return iio_push_to_buffers_with_timestamp(indio_dev, spd->buffer, + calculated_time); +} +EXPORT_SYMBOL(ssp_common_process_data); + +MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>"); +MODULE_DESCRIPTION("Samsung sensorhub commons"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h b/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h new file mode 100644 index 000000000..541c6590d --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_iio_sensor.h @@ -0,0 +1,71 @@ +#ifndef __SSP_IIO_SENSOR_H__ +#define __SSP_IIO_SENSOR_H__ + +#define SSP_CHANNEL_AG(_type, _mod, _index) \ +{ \ + .type = _type,\ + .modified = 1,\ + .channel2 = _mod,\ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .scan_index = _index,\ + .scan_type = {\ + .sign = 's',\ + .realbits = 16,\ + .storagebits = 16,\ + .shift = 0,\ + .endianness = IIO_LE,\ + },\ +} + +/* It is defined here as it is a mixed timestamp */ +#define SSP_CHAN_TIMESTAMP(_si) { \ + .type = IIO_TIMESTAMP, \ + .channel = -1, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 64, \ + .storagebits = 64, \ + }, \ +} + +#define SSP_MS_PER_S 1000 +#define SSP_INVERTED_SCALING_FACTOR 1000000U + +#define SSP_FACTOR_WITH_MS \ + (SSP_INVERTED_SCALING_FACTOR * SSP_MS_PER_S) + +int ssp_common_buffer_postenable(struct iio_dev *indio_dev); + +int ssp_common_buffer_postdisable(struct iio_dev *indio_dev); + +int ssp_common_process_data(struct iio_dev *indio_dev, void *buf, + unsigned int len, int64_t timestamp); + +/* Converts time in ms to frequency */ +static inline void ssp_convert_to_freq(u32 time, int *integer_part, + int *fractional) +{ + if (time == 0) { + *fractional = 0; + *integer_part = 0; + return; + } + + *integer_part = SSP_FACTOR_WITH_MS / time; + *fractional = *integer_part % SSP_INVERTED_SCALING_FACTOR; + *integer_part = *integer_part / SSP_INVERTED_SCALING_FACTOR; +} + +/* Converts frequency to time in ms */ +static inline int ssp_convert_to_time(int integer_part, int fractional) +{ + u64 value; + + value = (u64)integer_part * SSP_INVERTED_SCALING_FACTOR + fractional; + if (value == 0) + return 0; + + return div64_u64((u64)SSP_FACTOR_WITH_MS, value); +} +#endif /* __SSP_IIO_SENSOR_H__ */ diff --git a/drivers/iio/common/ssp_sensors/ssp_spi.c b/drivers/iio/common/ssp_sensors/ssp_spi.c new file mode 100644 index 000000000..704284a47 --- /dev/null +++ b/drivers/iio/common/ssp_sensors/ssp_spi.c @@ -0,0 +1,608 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * 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. + * + */ + +#include "ssp.h" + +#define SSP_DEV (&data->spi->dev) +#define SSP_GET_MESSAGE_TYPE(data) (data & (3 << SSP_RW)) + +/* + * SSP -> AP Instruction + * They tell what packet type can be expected. In the future there will + * be less of them. BYPASS means common sensor packets with accel, gyro, + * hrm etc. data. LIBRARY and META are mock-up's for now. + */ +#define SSP_MSG2AP_INST_BYPASS_DATA 0x37 +#define SSP_MSG2AP_INST_LIBRARY_DATA 0x01 +#define SSP_MSG2AP_INST_DEBUG_DATA 0x03 +#define SSP_MSG2AP_INST_BIG_DATA 0x04 +#define SSP_MSG2AP_INST_META_DATA 0x05 +#define SSP_MSG2AP_INST_TIME_SYNC 0x06 +#define SSP_MSG2AP_INST_RESET 0x07 + +#define SSP_UNIMPLEMENTED -1 + +struct ssp_msg_header { + u8 cmd; + __le16 length; + __le16 options; + __le32 data; +} __attribute__((__packed__)); + +struct ssp_msg { + u16 length; + u16 options; + struct list_head list; + struct completion *done; + struct ssp_msg_header *h; + char *buffer; +}; + +static const int ssp_offset_map[SSP_SENSOR_MAX] = { + [SSP_ACCELEROMETER_SENSOR] = SSP_ACCELEROMETER_SIZE + + SSP_TIME_SIZE, + [SSP_GYROSCOPE_SENSOR] = SSP_GYROSCOPE_SIZE + + SSP_TIME_SIZE, + [SSP_GEOMAGNETIC_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GEOMAGNETIC_RAW] = SSP_UNIMPLEMENTED, + [SSP_GEOMAGNETIC_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PRESSURE_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GESTURE_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PROXIMITY_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_TEMPERATURE_HUMIDITY_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_LIGHT_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_PROXIMITY_RAW] = SSP_UNIMPLEMENTED, + [SSP_ORIENTATION_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_STEP_DETECTOR] = SSP_UNIMPLEMENTED, + [SSP_SIG_MOTION_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GYRO_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED, + [SSP_GAME_ROTATION_VECTOR] = SSP_UNIMPLEMENTED, + [SSP_ROTATION_VECTOR] = SSP_UNIMPLEMENTED, + [SSP_STEP_COUNTER] = SSP_UNIMPLEMENTED, + [SSP_BIO_HRM_RAW] = SSP_BIO_HRM_RAW_SIZE + + SSP_TIME_SIZE, + [SSP_BIO_HRM_RAW_FAC] = SSP_BIO_HRM_RAW_FAC_SIZE + + SSP_TIME_SIZE, + [SSP_BIO_HRM_LIB] = SSP_BIO_HRM_LIB_SIZE + + SSP_TIME_SIZE, +}; + +#define SSP_HEADER_SIZE (sizeof(struct ssp_msg_header)) +#define SSP_HEADER_SIZE_ALIGNED (ALIGN(SSP_HEADER_SIZE, 4)) + +static struct ssp_msg *ssp_create_msg(u8 cmd, u16 len, u16 opt, u32 data) +{ + struct ssp_msg_header h; + struct ssp_msg *msg; + + msg = kzalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) + return NULL; + + h.cmd = cmd; + h.length = cpu_to_le16(len); + h.options = cpu_to_le16(opt); + h.data = cpu_to_le32(data); + + msg->buffer = kzalloc(SSP_HEADER_SIZE_ALIGNED + len, + GFP_KERNEL | GFP_DMA); + if (!msg->buffer) { + kfree(msg); + return NULL; + } + + msg->length = len; + msg->options = opt; + + memcpy(msg->buffer, &h, SSP_HEADER_SIZE); + + return msg; +} + +/* + * It is a bit heavy to do it this way but often the function is used to compose + * the message from smaller chunks which are placed on the stack. Often the + * chunks are small so memcpy should be optimalized. + */ +static inline void ssp_fill_buffer(struct ssp_msg *m, unsigned int offset, + const void *src, unsigned int len) +{ + memcpy(&m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], src, len); +} + +static inline void ssp_get_buffer(struct ssp_msg *m, unsigned int offset, + void *dest, unsigned int len) +{ + memcpy(dest, &m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], len); +} + +#define SSP_GET_BUFFER_AT_INDEX(m, index) \ + (m->buffer[SSP_HEADER_SIZE_ALIGNED + index]) +#define SSP_SET_BUFFER_AT_INDEX(m, index, val) \ + (m->buffer[SSP_HEADER_SIZE_ALIGNED + index] = val) + +static void ssp_clean_msg(struct ssp_msg *m) +{ + kfree(m->buffer); + kfree(m); +} + +static int ssp_print_mcu_debug(char *data_frame, int *data_index, + int received_len) +{ + int length = data_frame[(*data_index)++]; + + if (length > received_len - *data_index || length <= 0) { + ssp_dbg("[SSP]: MSG From MCU-invalid debug length(%d/%d)\n", + length, received_len); + return length ? length : -EPROTO; + } + + ssp_dbg("[SSP]: MSG From MCU - %s\n", &data_frame[*data_index]); + + *data_index += length; + + return 0; +} + +/* + * It was designed that way - additional lines to some kind of handshake, + * please do not ask why - only the firmware guy can know it. + */ +static int ssp_check_lines(struct ssp_data *data, bool state) +{ + int delay_cnt = 0; + + gpio_set_value_cansleep(data->ap_mcu_gpio, state); + + while (gpio_get_value_cansleep(data->mcu_ap_gpio) != state) { + usleep_range(3000, 3500); + + if (data->shut_down || delay_cnt++ > 500) { + dev_err(SSP_DEV, "%s:timeout, hw ack wait fail %d\n", + __func__, state); + + if (!state) + gpio_set_value_cansleep(data->ap_mcu_gpio, 1); + + return -ETIMEDOUT; + } + } + + return 0; +} + +static int ssp_do_transfer(struct ssp_data *data, struct ssp_msg *msg, + struct completion *done, int timeout) +{ + int status; + /* + * check if this is a short one way message or the whole transfer has + * second part after an interrupt + */ + const bool use_no_irq = msg->length == 0; + + if (data->shut_down) + return -EPERM; + + msg->done = done; + + mutex_lock(&data->comm_lock); + + status = ssp_check_lines(data, false); + if (status < 0) + goto _error_locked; + + status = spi_write(data->spi, msg->buffer, SSP_HEADER_SIZE); + if (status < 0) { + gpio_set_value_cansleep(data->ap_mcu_gpio, 1); + dev_err(SSP_DEV, "%s spi_write fail\n", __func__); + goto _error_locked; + } + + if (!use_no_irq) { + mutex_lock(&data->pending_lock); + list_add_tail(&msg->list, &data->pending_list); + mutex_unlock(&data->pending_lock); + } + + status = ssp_check_lines(data, true); + if (status < 0) { + if (!use_no_irq) { + mutex_lock(&data->pending_lock); + list_del(&msg->list); + mutex_unlock(&data->pending_lock); + } + goto _error_locked; + } + + mutex_unlock(&data->comm_lock); + + if (!use_no_irq && done) + if (wait_for_completion_timeout(done, + msecs_to_jiffies(timeout)) == + 0) { + mutex_lock(&data->pending_lock); + list_del(&msg->list); + mutex_unlock(&data->pending_lock); + + data->timeout_cnt++; + return -ETIMEDOUT; + } + + return 0; + +_error_locked: + mutex_unlock(&data->comm_lock); + data->timeout_cnt++; + return status; +} + +static inline int ssp_spi_sync_command(struct ssp_data *data, + struct ssp_msg *msg) +{ + return ssp_do_transfer(data, msg, NULL, 0); +} + +static int ssp_spi_sync(struct ssp_data *data, struct ssp_msg *msg, + int timeout) +{ + DECLARE_COMPLETION_ONSTACK(done); + + if (WARN_ON(!msg->length)) + return -EPERM; + + return ssp_do_transfer(data, msg, &done, timeout); +} + +static int ssp_handle_big_data(struct ssp_data *data, char *dataframe, int *idx) +{ + /* mock-up, it will be changed with adding another sensor types */ + *idx += 8; + return 0; +} + +static int ssp_parse_dataframe(struct ssp_data *data, char *dataframe, int len) +{ + int idx, sd; + struct timespec ts; + struct ssp_sensor_data *spd; + struct iio_dev **indio_devs = data->sensor_devs; + + getnstimeofday(&ts); + + for (idx = 0; idx < len;) { + switch (dataframe[idx++]) { + case SSP_MSG2AP_INST_BYPASS_DATA: + sd = dataframe[idx++]; + if (sd < 0 || sd >= SSP_SENSOR_MAX) { + dev_err(SSP_DEV, + "Mcu data frame1 error %d\n", sd); + return -EPROTO; + } + + if (indio_devs[sd]) { + spd = iio_priv(indio_devs[sd]); + if (spd->process_data) + spd->process_data(indio_devs[sd], + &dataframe[idx], + data->timestamp); + } else { + dev_err(SSP_DEV, "no client for frame\n"); + } + + idx += ssp_offset_map[sd]; + break; + case SSP_MSG2AP_INST_DEBUG_DATA: + sd = ssp_print_mcu_debug(dataframe, &idx, len); + if (sd) { + dev_err(SSP_DEV, + "Mcu data frame3 error %d\n", sd); + return sd; + } + break; + case SSP_MSG2AP_INST_LIBRARY_DATA: + idx += len; + break; + case SSP_MSG2AP_INST_BIG_DATA: + ssp_handle_big_data(data, dataframe, &idx); + break; + case SSP_MSG2AP_INST_TIME_SYNC: + data->time_syncing = true; + break; + case SSP_MSG2AP_INST_RESET: + ssp_queue_ssp_refresh_task(data, 0); + break; + } + } + + if (data->time_syncing) + data->timestamp = ts.tv_sec * 1000000000ULL + ts.tv_nsec; + + return 0; +} + +/* threaded irq */ +int ssp_irq_msg(struct ssp_data *data) +{ + bool found = false; + char *buffer; + u8 msg_type; + int ret; + u16 length, msg_options; + struct ssp_msg *msg, *n; + + ret = spi_read(data->spi, data->header_buffer, SSP_HEADER_BUFFER_SIZE); + if (ret < 0) { + dev_err(SSP_DEV, "header read fail\n"); + return ret; + } + + length = le16_to_cpu(data->header_buffer[1]); + msg_options = le16_to_cpu(data->header_buffer[0]); + + if (length == 0) { + dev_err(SSP_DEV, "length received from mcu is 0\n"); + return -EINVAL; + } + + msg_type = SSP_GET_MESSAGE_TYPE(msg_options); + + switch (msg_type) { + case SSP_AP2HUB_READ: + case SSP_AP2HUB_WRITE: + /* + * this is a small list, a few elements - the packets can be + * received with no order + */ + mutex_lock(&data->pending_lock); + list_for_each_entry_safe(msg, n, &data->pending_list, list) { + if (msg->options == msg_options) { + list_del(&msg->list); + found = true; + break; + } + } + + if (!found) { + /* + * here can be implemented dead messages handling + * but the slave should not send such ones - it is to + * check but let's handle this + */ + buffer = kmalloc(length, GFP_KERNEL | GFP_DMA); + if (!buffer) { + ret = -ENOMEM; + goto _unlock; + } + + /* got dead packet so it is always an error */ + ret = spi_read(data->spi, buffer, length); + if (ret >= 0) + ret = -EPROTO; + + kfree(buffer); + + dev_err(SSP_DEV, "No match error %x\n", + msg_options); + + goto _unlock; + } + + if (msg_type == SSP_AP2HUB_READ) + ret = spi_read(data->spi, + &msg->buffer[SSP_HEADER_SIZE_ALIGNED], + msg->length); + + if (msg_type == SSP_AP2HUB_WRITE) { + ret = spi_write(data->spi, + &msg->buffer[SSP_HEADER_SIZE_ALIGNED], + msg->length); + if (msg_options & SSP_AP2HUB_RETURN) { + msg->options = + SSP_AP2HUB_READ | SSP_AP2HUB_RETURN; + msg->length = 1; + + list_add_tail(&msg->list, &data->pending_list); + goto _unlock; + } + } + + if (msg->done) + if (!completion_done(msg->done)) + complete(msg->done); +_unlock: + mutex_unlock(&data->pending_lock); + break; + case SSP_HUB2AP_WRITE: + buffer = kzalloc(length, GFP_KERNEL | GFP_DMA); + if (!buffer) + return -ENOMEM; + + ret = spi_read(data->spi, buffer, length); + if (ret < 0) { + dev_err(SSP_DEV, "spi read fail\n"); + kfree(buffer); + break; + } + + ret = ssp_parse_dataframe(data, buffer, length); + + kfree(buffer); + break; + + default: + dev_err(SSP_DEV, "unknown msg type\n"); + return -EPROTO; + } + + return ret; +} + +void ssp_clean_pending_list(struct ssp_data *data) +{ + struct ssp_msg *msg, *n; + + mutex_lock(&data->pending_lock); + list_for_each_entry_safe(msg, n, &data->pending_list, list) { + list_del(&msg->list); + + if (msg->done) + if (!completion_done(msg->done)) + complete(msg->done); + } + mutex_unlock(&data->pending_lock); +} + +int ssp_command(struct ssp_data *data, char command, int arg) +{ + int ret; + struct ssp_msg *msg; + + msg = ssp_create_msg(command, 0, SSP_AP2HUB_WRITE, arg); + if (!msg) + return -ENOMEM; + + ssp_dbg("%s - command 0x%x %d\n", __func__, command, arg); + + ret = ssp_spi_sync_command(data, msg); + ssp_clean_msg(msg); + + return ret; +} + +int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type, + u8 *send_buf, u8 length) +{ + int ret; + struct ssp_msg *msg; + + if (data->fw_dl_state == SSP_FW_DL_STATE_DOWNLOADING) { + dev_err(SSP_DEV, "%s - Skip Inst! DL state = %d\n", + __func__, data->fw_dl_state); + return -EBUSY; + } else if (!(data->available_sensors & BIT(sensor_type)) && + (inst <= SSP_MSG2SSP_INST_CHANGE_DELAY)) { + dev_err(SSP_DEV, "%s - Bypass Inst Skip! - %u\n", + __func__, sensor_type); + return -EIO; /* just fail */ + } + + msg = ssp_create_msg(inst, length + 2, SSP_AP2HUB_WRITE, 0); + if (!msg) + return -ENOMEM; + + ssp_fill_buffer(msg, 0, &sensor_type, 1); + ssp_fill_buffer(msg, 1, send_buf, length); + + ssp_dbg("%s - Inst = 0x%x, Sensor Type = 0x%x, data = %u\n", + __func__, inst, sensor_type, send_buf[1]); + + ret = ssp_spi_sync(data, msg, 1000); + ssp_clean_msg(msg); + + return ret; +} + +int ssp_get_chipid(struct ssp_data *data) +{ + int ret; + char buffer; + struct ssp_msg *msg; + + msg = ssp_create_msg(SSP_MSG2SSP_AP_WHOAMI, 1, SSP_AP2HUB_READ, 0); + if (!msg) + return -ENOMEM; + + ret = ssp_spi_sync(data, msg, 1000); + + buffer = SSP_GET_BUFFER_AT_INDEX(msg, 0); + + ssp_clean_msg(msg); + + return ret < 0 ? ret : buffer; +} + +int ssp_set_magnetic_matrix(struct ssp_data *data) +{ + int ret; + struct ssp_msg *msg; + + msg = ssp_create_msg(SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX, + data->sensorhub_info->mag_length, SSP_AP2HUB_WRITE, + 0); + if (!msg) + return -ENOMEM; + + ssp_fill_buffer(msg, 0, data->sensorhub_info->mag_table, + data->sensorhub_info->mag_length); + + ret = ssp_spi_sync(data, msg, 1000); + ssp_clean_msg(msg); + + return ret; +} + +unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data) +{ + int ret; + __le32 result; + u32 cpu_result = 0; + + struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_SENSOR_SCANNING, 4, + SSP_AP2HUB_READ, 0); + if (!msg) + return 0; + + ret = ssp_spi_sync(data, msg, 1000); + if (ret < 0) { + dev_err(SSP_DEV, "%s - spi read fail %d\n", __func__, ret); + goto _exit; + } + + ssp_get_buffer(msg, 0, &result, 4); + cpu_result = le32_to_cpu(result); + + dev_info(SSP_DEV, "%s state: 0x%08x\n", __func__, cpu_result); + +_exit: + ssp_clean_msg(msg); + return cpu_result; +} + +unsigned int ssp_get_firmware_rev(struct ssp_data *data) +{ + int ret; + __le32 result; + + struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_FIRMWARE_REV, 4, + SSP_AP2HUB_READ, 0); + if (!msg) + return SSP_INVALID_REVISION; + + ret = ssp_spi_sync(data, msg, 1000); + if (ret < 0) { + dev_err(SSP_DEV, "%s - transfer fail %d\n", __func__, ret); + ret = SSP_INVALID_REVISION; + goto _exit; + } + + ssp_get_buffer(msg, 0, &result, 4); + ret = le32_to_cpu(result); + +_exit: + ssp_clean_msg(msg); + return ret; +} diff --git a/drivers/iio/common/st_sensors/Kconfig b/drivers/iio/common/st_sensors/Kconfig new file mode 100644 index 000000000..865f1ca33 --- /dev/null +++ b/drivers/iio/common/st_sensors/Kconfig @@ -0,0 +1,14 @@ +# +# STMicroelectronics sensors common library +# + +config IIO_ST_SENSORS_I2C + tristate + +config IIO_ST_SENSORS_SPI + tristate + +config IIO_ST_SENSORS_CORE + tristate + select IIO_ST_SENSORS_I2C if I2C + select IIO_ST_SENSORS_SPI if SPI_MASTER diff --git a/drivers/iio/common/st_sensors/Makefile b/drivers/iio/common/st_sensors/Makefile new file mode 100644 index 000000000..9f3e24f30 --- /dev/null +++ b/drivers/iio/common/st_sensors/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the STMicroelectronics sensor common modules. +# + +obj-$(CONFIG_IIO_ST_SENSORS_I2C) += st_sensors_i2c.o +obj-$(CONFIG_IIO_ST_SENSORS_SPI) += st_sensors_spi.o +obj-$(CONFIG_IIO_ST_SENSORS_CORE) += st_sensors.o +st_sensors-y := st_sensors_core.o +st_sensors-$(CONFIG_IIO_BUFFER) += st_sensors_buffer.o +st_sensors-$(CONFIG_IIO_TRIGGER) += st_sensors_trigger.o diff --git a/drivers/iio/common/st_sensors/st_sensors_buffer.c b/drivers/iio/common/st_sensors/st_sensors_buffer.c new file mode 100644 index 000000000..e18bc6782 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_buffer.c @@ -0,0 +1,128 @@ +/* + * STMicroelectronics sensors buffer library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/interrupt.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/irqreturn.h> + +#include <linux/iio/common/st_sensors.h> + + +int st_sensors_get_buffer_element(struct iio_dev *indio_dev, u8 *buf) +{ + u8 *addr; + int i, n = 0, len; + struct st_sensor_data *sdata = iio_priv(indio_dev); + unsigned int num_data_channels = sdata->num_data_channels; + unsigned int byte_for_channel = + indio_dev->channels[0].scan_type.storagebits >> 3; + + addr = kmalloc(num_data_channels, GFP_KERNEL); + if (!addr) { + len = -ENOMEM; + goto st_sensors_get_buffer_element_error; + } + + for (i = 0; i < num_data_channels; i++) { + if (test_bit(i, indio_dev->active_scan_mask)) { + addr[n] = indio_dev->channels[i].address; + n++; + } + } + switch (n) { + case 1: + len = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev, + addr[0], byte_for_channel, buf, sdata->multiread_bit); + break; + case 2: + if ((addr[1] - addr[0]) == byte_for_channel) { + len = sdata->tf->read_multiple_byte(&sdata->tb, + sdata->dev, addr[0], byte_for_channel * n, + buf, sdata->multiread_bit); + } else { + u8 *rx_array; + rx_array = kmalloc(byte_for_channel * num_data_channels, + GFP_KERNEL); + if (!rx_array) { + len = -ENOMEM; + goto st_sensors_free_memory; + } + + len = sdata->tf->read_multiple_byte(&sdata->tb, + sdata->dev, addr[0], + byte_for_channel * num_data_channels, + rx_array, sdata->multiread_bit); + if (len < 0) { + kfree(rx_array); + goto st_sensors_free_memory; + } + + for (i = 0; i < n * byte_for_channel; i++) { + if (i < n) + buf[i] = rx_array[i]; + else + buf[i] = rx_array[n + i]; + } + kfree(rx_array); + len = byte_for_channel * n; + } + break; + case 3: + len = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev, + addr[0], byte_for_channel * num_data_channels, + buf, sdata->multiread_bit); + break; + default: + len = -EINVAL; + goto st_sensors_free_memory; + } + if (len != byte_for_channel * n) { + len = -EIO; + goto st_sensors_free_memory; + } + +st_sensors_free_memory: + kfree(addr); +st_sensors_get_buffer_element_error: + return len; +} +EXPORT_SYMBOL(st_sensors_get_buffer_element); + +irqreturn_t st_sensors_trigger_handler(int irq, void *p) +{ + int len; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + len = st_sensors_get_buffer_element(indio_dev, sdata->buffer_data); + if (len < 0) + goto st_sensors_get_buffer_element_error; + + iio_push_to_buffers_with_timestamp(indio_dev, sdata->buffer_data, + pf->timestamp); + +st_sensors_get_buffer_element_error: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL(st_sensors_trigger_handler); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors buffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c new file mode 100644 index 000000000..8dd0477e2 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -0,0 +1,556 @@ +/* + * STMicroelectronics sensors core library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/regulator/consumer.h> +#include <linux/of.h> +#include <asm/unaligned.h> +#include <linux/iio/common/st_sensors.h> + + +#define ST_SENSORS_WAI_ADDRESS 0x0f + +static inline u32 st_sensors_get_unaligned_le24(const u8 *p) +{ + return (s32)((p[0] | p[1] << 8 | p[2] << 16) << 8) >> 8; +} + +static int st_sensors_write_data_with_mask(struct iio_dev *indio_dev, + u8 reg_addr, u8 mask, u8 data) +{ + int err; + u8 new_data; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + err = sdata->tf->read_byte(&sdata->tb, sdata->dev, reg_addr, &new_data); + if (err < 0) + goto st_sensors_write_data_with_mask_error; + + new_data = ((new_data & (~mask)) | ((data << __ffs(mask)) & mask)); + err = sdata->tf->write_byte(&sdata->tb, sdata->dev, reg_addr, new_data); + +st_sensors_write_data_with_mask_error: + return err; +} + +static int st_sensors_match_odr(struct st_sensor_settings *sensor_settings, + unsigned int odr, struct st_sensor_odr_avl *odr_out) +{ + int i, ret = -EINVAL; + + for (i = 0; i < ST_SENSORS_ODR_LIST_MAX; i++) { + if (sensor_settings->odr.odr_avl[i].hz == 0) + goto st_sensors_match_odr_error; + + if (sensor_settings->odr.odr_avl[i].hz == odr) { + odr_out->hz = sensor_settings->odr.odr_avl[i].hz; + odr_out->value = sensor_settings->odr.odr_avl[i].value; + ret = 0; + break; + } + } + +st_sensors_match_odr_error: + return ret; +} + +int st_sensors_set_odr(struct iio_dev *indio_dev, unsigned int odr) +{ + int err; + struct st_sensor_odr_avl odr_out = {0, 0}; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + err = st_sensors_match_odr(sdata->sensor_settings, odr, &odr_out); + if (err < 0) + goto st_sensors_match_odr_error; + + if ((sdata->sensor_settings->odr.addr == + sdata->sensor_settings->pw.addr) && + (sdata->sensor_settings->odr.mask == + sdata->sensor_settings->pw.mask)) { + if (sdata->enabled == true) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->odr.addr, + sdata->sensor_settings->odr.mask, + odr_out.value); + } else { + err = 0; + } + } else { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->odr.addr, + sdata->sensor_settings->odr.mask, + odr_out.value); + } + if (err >= 0) + sdata->odr = odr_out.hz; + +st_sensors_match_odr_error: + return err; +} +EXPORT_SYMBOL(st_sensors_set_odr); + +static int st_sensors_match_fs(struct st_sensor_settings *sensor_settings, + unsigned int fs, int *index_fs_avl) +{ + int i, ret = -EINVAL; + + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if (sensor_settings->fs.fs_avl[i].num == 0) + goto st_sensors_match_odr_error; + + if (sensor_settings->fs.fs_avl[i].num == fs) { + *index_fs_avl = i; + ret = 0; + break; + } + } + +st_sensors_match_odr_error: + return ret; +} + +static int st_sensors_set_fullscale(struct iio_dev *indio_dev, unsigned int fs) +{ + int err, i = 0; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + err = st_sensors_match_fs(sdata->sensor_settings, fs, &i); + if (err < 0) + goto st_accel_set_fullscale_error; + + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->fs.addr, + sdata->sensor_settings->fs.mask, + sdata->sensor_settings->fs.fs_avl[i].value); + if (err < 0) + goto st_accel_set_fullscale_error; + + sdata->current_fullscale = (struct st_sensor_fullscale_avl *) + &sdata->sensor_settings->fs.fs_avl[i]; + return err; + +st_accel_set_fullscale_error: + dev_err(&indio_dev->dev, "failed to set new fullscale.\n"); + return err; +} + +int st_sensors_set_enable(struct iio_dev *indio_dev, bool enable) +{ + u8 tmp_value; + int err = -EINVAL; + bool found = false; + struct st_sensor_odr_avl odr_out = {0, 0}; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (enable) { + tmp_value = sdata->sensor_settings->pw.value_on; + if ((sdata->sensor_settings->odr.addr == + sdata->sensor_settings->pw.addr) && + (sdata->sensor_settings->odr.mask == + sdata->sensor_settings->pw.mask)) { + err = st_sensors_match_odr(sdata->sensor_settings, + sdata->odr, &odr_out); + if (err < 0) + goto set_enable_error; + tmp_value = odr_out.value; + found = true; + } + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->pw.addr, + sdata->sensor_settings->pw.mask, tmp_value); + if (err < 0) + goto set_enable_error; + + sdata->enabled = true; + + if (found) + sdata->odr = odr_out.hz; + } else { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->pw.addr, + sdata->sensor_settings->pw.mask, + sdata->sensor_settings->pw.value_off); + if (err < 0) + goto set_enable_error; + + sdata->enabled = false; + } + +set_enable_error: + return err; +} +EXPORT_SYMBOL(st_sensors_set_enable); + +int st_sensors_set_axis_enable(struct iio_dev *indio_dev, u8 axis_enable) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + return st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->enable_axis.addr, + sdata->sensor_settings->enable_axis.mask, + axis_enable); +} +EXPORT_SYMBOL(st_sensors_set_axis_enable); + +void st_sensors_power_enable(struct iio_dev *indio_dev) +{ + struct st_sensor_data *pdata = iio_priv(indio_dev); + int err; + + /* Regulators not mandatory, but if requested we should enable them. */ + pdata->vdd = devm_regulator_get_optional(indio_dev->dev.parent, "vdd"); + if (!IS_ERR(pdata->vdd)) { + err = regulator_enable(pdata->vdd); + if (err != 0) + dev_warn(&indio_dev->dev, + "Failed to enable specified Vdd supply\n"); + } + + pdata->vdd_io = devm_regulator_get_optional(indio_dev->dev.parent, "vddio"); + if (!IS_ERR(pdata->vdd_io)) { + err = regulator_enable(pdata->vdd_io); + if (err != 0) + dev_warn(&indio_dev->dev, + "Failed to enable specified Vdd_IO supply\n"); + } +} +EXPORT_SYMBOL(st_sensors_power_enable); + +void st_sensors_power_disable(struct iio_dev *indio_dev) +{ + struct st_sensor_data *pdata = iio_priv(indio_dev); + + if (!IS_ERR(pdata->vdd)) + regulator_disable(pdata->vdd); + + if (!IS_ERR(pdata->vdd_io)) + regulator_disable(pdata->vdd_io); +} +EXPORT_SYMBOL(st_sensors_power_disable); + +static int st_sensors_set_drdy_int_pin(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + switch (pdata->drdy_int_pin) { + case 1: + if (sdata->sensor_settings->drdy_irq.mask_int1 == 0) { + dev_err(&indio_dev->dev, + "DRDY on INT1 not available.\n"); + return -EINVAL; + } + sdata->drdy_int_pin = 1; + break; + case 2: + if (sdata->sensor_settings->drdy_irq.mask_int2 == 0) { + dev_err(&indio_dev->dev, + "DRDY on INT2 not available.\n"); + return -EINVAL; + } + sdata->drdy_int_pin = 2; + break; + default: + dev_err(&indio_dev->dev, "DRDY on pdata not valid.\n"); + return -EINVAL; + } + + return 0; +} + +#ifdef CONFIG_OF +static struct st_sensors_platform_data *st_sensors_of_probe(struct device *dev, + struct st_sensors_platform_data *defdata) +{ + struct st_sensors_platform_data *pdata; + struct device_node *np = dev->of_node; + u32 val; + + if (!np) + return NULL; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!of_property_read_u32(np, "st,drdy-int-pin", &val) && (val <= 2)) + pdata->drdy_int_pin = (u8) val; + else + pdata->drdy_int_pin = defdata ? defdata->drdy_int_pin : 1; + + return pdata; +} +#else +static struct st_sensors_platform_data *st_sensors_of_probe(struct device *dev, + struct st_sensors_platform_data *defdata) +{ + return NULL; +} +#endif + +int st_sensors_init_sensor(struct iio_dev *indio_dev, + struct st_sensors_platform_data *pdata) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + struct st_sensors_platform_data *of_pdata; + int err = 0; + + /* If OF/DT pdata exists, it will take precedence of anything else */ + of_pdata = st_sensors_of_probe(indio_dev->dev.parent, pdata); + if (of_pdata) + pdata = of_pdata; + + if (pdata) { + err = st_sensors_set_drdy_int_pin(indio_dev, pdata); + if (err < 0) + return err; + } + + err = st_sensors_set_enable(indio_dev, false); + if (err < 0) + return err; + + if (sdata->current_fullscale) { + err = st_sensors_set_fullscale(indio_dev, + sdata->current_fullscale->num); + if (err < 0) + return err; + } else + dev_info(&indio_dev->dev, "Full-scale not possible\n"); + + err = st_sensors_set_odr(indio_dev, sdata->odr); + if (err < 0) + return err; + + /* set BDU */ + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->bdu.addr, + sdata->sensor_settings->bdu.mask, true); + if (err < 0) + return err; + + err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); + + return err; +} +EXPORT_SYMBOL(st_sensors_init_sensor); + +int st_sensors_set_dataready_irq(struct iio_dev *indio_dev, bool enable) +{ + int err; + u8 drdy_mask; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + if (!sdata->sensor_settings->drdy_irq.addr) + return 0; + + /* Enable/Disable the interrupt generator 1. */ + if (sdata->sensor_settings->drdy_irq.ig1.en_addr > 0) { + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->drdy_irq.ig1.en_addr, + sdata->sensor_settings->drdy_irq.ig1.en_mask, + (int)enable); + if (err < 0) + goto st_accel_set_dataready_irq_error; + } + + if (sdata->drdy_int_pin == 1) + drdy_mask = sdata->sensor_settings->drdy_irq.mask_int1; + else + drdy_mask = sdata->sensor_settings->drdy_irq.mask_int2; + + /* Enable/Disable the interrupt generator for data ready. */ + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->drdy_irq.addr, + drdy_mask, (int)enable); + +st_accel_set_dataready_irq_error: + return err; +} +EXPORT_SYMBOL(st_sensors_set_dataready_irq); + +int st_sensors_set_fullscale_by_gain(struct iio_dev *indio_dev, int scale) +{ + int err = -EINVAL, i; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if ((sdata->sensor_settings->fs.fs_avl[i].gain == scale) && + (sdata->sensor_settings->fs.fs_avl[i].gain != 0)) { + err = 0; + break; + } + } + if (err < 0) + goto st_sensors_match_scale_error; + + err = st_sensors_set_fullscale(indio_dev, + sdata->sensor_settings->fs.fs_avl[i].num); + +st_sensors_match_scale_error: + return err; +} +EXPORT_SYMBOL(st_sensors_set_fullscale_by_gain); + +static int st_sensors_read_axis_data(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *data) +{ + int err; + u8 *outdata; + struct st_sensor_data *sdata = iio_priv(indio_dev); + unsigned int byte_for_channel = ch->scan_type.storagebits >> 3; + + outdata = kmalloc(byte_for_channel, GFP_KERNEL); + if (!outdata) + return -ENOMEM; + + err = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev, + ch->address, byte_for_channel, + outdata, sdata->multiread_bit); + if (err < 0) + goto st_sensors_free_memory; + + if (byte_for_channel == 2) + *data = (s16)get_unaligned_le16(outdata); + else if (byte_for_channel == 3) + *data = (s32)st_sensors_get_unaligned_le24(outdata); + +st_sensors_free_memory: + kfree(outdata); + + return err; +} + +int st_sensors_read_info_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + err = -EBUSY; + goto out; + } else { + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto out; + + msleep((sdata->sensor_settings->bootime * 1000) / sdata->odr); + err = st_sensors_read_axis_data(indio_dev, ch, val); + if (err < 0) + goto out; + + *val = *val >> ch->scan_type.shift; + + err = st_sensors_set_enable(indio_dev, false); + } +out: + mutex_unlock(&indio_dev->mlock); + + return err; +} +EXPORT_SYMBOL(st_sensors_read_info_raw); + +int st_sensors_check_device_support(struct iio_dev *indio_dev, + int num_sensors_list, + const struct st_sensor_settings *sensor_settings) +{ + u8 wai; + int i, n, err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + err = sdata->tf->read_byte(&sdata->tb, sdata->dev, + ST_SENSORS_DEFAULT_WAI_ADDRESS, &wai); + if (err < 0) { + dev_err(&indio_dev->dev, "failed to read Who-Am-I register.\n"); + goto read_wai_error; + } + + for (i = 0; i < num_sensors_list; i++) { + if (sensor_settings[i].wai == wai) + break; + } + if (i == num_sensors_list) + goto device_not_supported; + + for (n = 0; n < ARRAY_SIZE(sensor_settings[i].sensors_supported); n++) { + if (strcmp(indio_dev->name, + &sensor_settings[i].sensors_supported[n][0]) == 0) + break; + } + if (n == ARRAY_SIZE(sensor_settings[i].sensors_supported)) { + dev_err(&indio_dev->dev, "device name and WhoAmI mismatch.\n"); + goto sensor_name_mismatch; + } + + sdata->sensor_settings = + (struct st_sensor_settings *)&sensor_settings[i]; + + return i; + +device_not_supported: + dev_err(&indio_dev->dev, "device not supported: WhoAmI (0x%x).\n", wai); +sensor_name_mismatch: + err = -ENODEV; +read_wai_error: + return err; +} +EXPORT_SYMBOL(st_sensors_check_device_support); + +ssize_t st_sensors_sysfs_sampling_frequency_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + for (i = 0; i < ST_SENSORS_ODR_LIST_MAX; i++) { + if (sdata->sensor_settings->odr.odr_avl[i].hz == 0) + break; + + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", + sdata->sensor_settings->odr.odr_avl[i].hz); + } + mutex_unlock(&indio_dev->mlock); + buf[len - 1] = '\n'; + + return len; +} +EXPORT_SYMBOL(st_sensors_sysfs_sampling_frequency_avail); + +ssize_t st_sensors_sysfs_scale_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { + if (sdata->sensor_settings->fs.fs_avl[i].num == 0) + break; + + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ", + sdata->sensor_settings->fs.fs_avl[i].gain); + } + mutex_unlock(&indio_dev->mlock); + buf[len - 1] = '\n'; + + return len; +} +EXPORT_SYMBOL(st_sensors_sysfs_scale_avail); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_i2c.c b/drivers/iio/common/st_sensors/st_sensors_i2c.c new file mode 100644 index 000000000..98cfee296 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_i2c.c @@ -0,0 +1,112 @@ +/* + * STMicroelectronics sensors i2c library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/iio/iio.h> +#include <linux/of_device.h> + +#include <linux/iio/common/st_sensors_i2c.h> + + +#define ST_SENSORS_I2C_MULTIREAD 0x80 + +static unsigned int st_sensors_i2c_get_irq(struct iio_dev *indio_dev) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + return to_i2c_client(sdata->dev)->irq; +} + +static int st_sensors_i2c_read_byte(struct st_sensor_transfer_buffer *tb, + struct device *dev, u8 reg_addr, u8 *res_byte) +{ + int err; + + err = i2c_smbus_read_byte_data(to_i2c_client(dev), reg_addr); + if (err < 0) + goto st_accel_i2c_read_byte_error; + + *res_byte = err & 0xff; + +st_accel_i2c_read_byte_error: + return err < 0 ? err : 0; +} + +static int st_sensors_i2c_read_multiple_byte( + struct st_sensor_transfer_buffer *tb, struct device *dev, + u8 reg_addr, int len, u8 *data, bool multiread_bit) +{ + if (multiread_bit) + reg_addr |= ST_SENSORS_I2C_MULTIREAD; + + return i2c_smbus_read_i2c_block_data(to_i2c_client(dev), + reg_addr, len, data); +} + +static int st_sensors_i2c_write_byte(struct st_sensor_transfer_buffer *tb, + struct device *dev, u8 reg_addr, u8 data) +{ + return i2c_smbus_write_byte_data(to_i2c_client(dev), reg_addr, data); +} + +static const struct st_sensor_transfer_function st_sensors_tf_i2c = { + .read_byte = st_sensors_i2c_read_byte, + .write_byte = st_sensors_i2c_write_byte, + .read_multiple_byte = st_sensors_i2c_read_multiple_byte, +}; + +void st_sensors_i2c_configure(struct iio_dev *indio_dev, + struct i2c_client *client, struct st_sensor_data *sdata) +{ + i2c_set_clientdata(client, indio_dev); + + indio_dev->dev.parent = &client->dev; + indio_dev->name = client->name; + + sdata->dev = &client->dev; + sdata->tf = &st_sensors_tf_i2c; + sdata->get_irq_data_ready = st_sensors_i2c_get_irq; +} +EXPORT_SYMBOL(st_sensors_i2c_configure); + +#ifdef CONFIG_OF +/** + * st_sensors_of_i2c_probe() - device tree probe for ST I2C sensors + * @client: the I2C client device for the sensor + * @match: the OF match table for the device, containing compatible strings + * but also a .data field with the corresponding internal kernel name + * used by this sensor. + * + * In effect this function matches a compatible string to an internal kernel + * name for a certain sensor device, so that the rest of the autodetection can + * rely on that name from this point on. I2C client devices will be renamed + * to match the internal kernel convention. + */ +void st_sensors_of_i2c_probe(struct i2c_client *client, + const struct of_device_id *match) +{ + const struct of_device_id *of_id; + + of_id = of_match_device(match, &client->dev); + if (!of_id) + return; + + /* The name from the OF match takes precedence if present */ + strncpy(client->name, of_id->data, sizeof(client->name)); + client->name[sizeof(client->name) - 1] = '\0'; +} +EXPORT_SYMBOL(st_sensors_of_i2c_probe); +#endif + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_spi.c b/drivers/iio/common/st_sensors/st_sensors_spi.c new file mode 100644 index 000000000..5b377373f --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_spi.c @@ -0,0 +1,122 @@ +/* + * STMicroelectronics sensors spi library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors_spi.h> + + +#define ST_SENSORS_SPI_MULTIREAD 0xc0 +#define ST_SENSORS_SPI_READ 0x80 + +static unsigned int st_sensors_spi_get_irq(struct iio_dev *indio_dev) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + return to_spi_device(sdata->dev)->irq; +} + +static int st_sensors_spi_read(struct st_sensor_transfer_buffer *tb, + struct device *dev, u8 reg_addr, int len, u8 *data, bool multiread_bit) +{ + int err; + + struct spi_transfer xfers[] = { + { + .tx_buf = tb->tx_buf, + .bits_per_word = 8, + .len = 1, + }, + { + .rx_buf = tb->rx_buf, + .bits_per_word = 8, + .len = len, + } + }; + + mutex_lock(&tb->buf_lock); + if ((multiread_bit) && (len > 1)) + tb->tx_buf[0] = reg_addr | ST_SENSORS_SPI_MULTIREAD; + else + tb->tx_buf[0] = reg_addr | ST_SENSORS_SPI_READ; + + err = spi_sync_transfer(to_spi_device(dev), xfers, ARRAY_SIZE(xfers)); + if (err) + goto acc_spi_read_error; + + memcpy(data, tb->rx_buf, len); + mutex_unlock(&tb->buf_lock); + return len; + +acc_spi_read_error: + mutex_unlock(&tb->buf_lock); + return err; +} + +static int st_sensors_spi_read_byte(struct st_sensor_transfer_buffer *tb, + struct device *dev, u8 reg_addr, u8 *res_byte) +{ + return st_sensors_spi_read(tb, dev, reg_addr, 1, res_byte, false); +} + +static int st_sensors_spi_read_multiple_byte( + struct st_sensor_transfer_buffer *tb, struct device *dev, + u8 reg_addr, int len, u8 *data, bool multiread_bit) +{ + return st_sensors_spi_read(tb, dev, reg_addr, len, data, multiread_bit); +} + +static int st_sensors_spi_write_byte(struct st_sensor_transfer_buffer *tb, + struct device *dev, u8 reg_addr, u8 data) +{ + int err; + + struct spi_transfer xfers = { + .tx_buf = tb->tx_buf, + .bits_per_word = 8, + .len = 2, + }; + + mutex_lock(&tb->buf_lock); + tb->tx_buf[0] = reg_addr; + tb->tx_buf[1] = data; + + err = spi_sync_transfer(to_spi_device(dev), &xfers, 1); + mutex_unlock(&tb->buf_lock); + + return err; +} + +static const struct st_sensor_transfer_function st_sensors_tf_spi = { + .read_byte = st_sensors_spi_read_byte, + .write_byte = st_sensors_spi_write_byte, + .read_multiple_byte = st_sensors_spi_read_multiple_byte, +}; + +void st_sensors_spi_configure(struct iio_dev *indio_dev, + struct spi_device *spi, struct st_sensor_data *sdata) +{ + spi_set_drvdata(spi, indio_dev); + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi->modalias; + + sdata->dev = &spi->dev; + sdata->tf = &st_sensors_tf_spi; + sdata->get_irq_data_ready = st_sensors_spi_get_irq; +} +EXPORT_SYMBOL(st_sensors_spi_configure); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/common/st_sensors/st_sensors_trigger.c b/drivers/iio/common/st_sensors/st_sensors_trigger.c new file mode 100644 index 000000000..8d8ca6f1e --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c @@ -0,0 +1,77 @@ +/* + * STMicroelectronics sensors trigger library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/interrupt.h> + +#include <linux/iio/common/st_sensors.h> + + +int st_sensors_allocate_trigger(struct iio_dev *indio_dev, + const struct iio_trigger_ops *trigger_ops) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + sdata->trig = iio_trigger_alloc("%s-trigger", indio_dev->name); + if (sdata->trig == NULL) { + err = -ENOMEM; + dev_err(&indio_dev->dev, "failed to allocate iio trigger.\n"); + goto iio_trigger_alloc_error; + } + + err = request_threaded_irq(sdata->get_irq_data_ready(indio_dev), + iio_trigger_generic_data_rdy_poll, + NULL, + IRQF_TRIGGER_RISING, + sdata->trig->name, + sdata->trig); + if (err) + goto request_irq_error; + + iio_trigger_set_drvdata(sdata->trig, indio_dev); + sdata->trig->ops = trigger_ops; + sdata->trig->dev.parent = sdata->dev; + + err = iio_trigger_register(sdata->trig); + if (err < 0) { + dev_err(&indio_dev->dev, "failed to register iio trigger.\n"); + goto iio_trigger_register_error; + } + indio_dev->trig = iio_trigger_get(sdata->trig); + + return 0; + +iio_trigger_register_error: + free_irq(sdata->get_irq_data_ready(indio_dev), sdata->trig); +request_irq_error: + iio_trigger_free(sdata->trig); +iio_trigger_alloc_error: + return err; +} +EXPORT_SYMBOL(st_sensors_allocate_trigger); + +void st_sensors_deallocate_trigger(struct iio_dev *indio_dev) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + iio_trigger_unregister(sdata->trig); + free_irq(sdata->get_irq_data_ready(indio_dev), sdata->trig); + iio_trigger_free(sdata->trig); +} +EXPORT_SYMBOL(st_sensors_deallocate_trigger); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig new file mode 100644 index 000000000..13471a76e --- /dev/null +++ b/drivers/iio/dac/Kconfig @@ -0,0 +1,189 @@ +# +# DAC drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Digital to analog converters" + +config AD5064 + tristate "Analog Devices AD5064 and similar multi-channel DAC driver" + depends on (SPI_MASTER && I2C!=m) || I2C + help + Say yes here to build support for Analog Devices AD5024, AD5025, AD5044, + AD5045, AD5064, AD5064-1, AD5065, AD5628, AD5629R, AD5648, AD5666, AD5668, + AD5669R Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5064. + +config AD5360 + tristate "Analog Devices AD5360/61/62/63/70/71/73 DAC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5360, AD5361, + AD5362, AD5363, AD5370, AD5371, AD5373 multi-channel + Digital to Analog Converters (DAC). + + To compile this driver as module choose M here: the module will be called + ad5360. + +config AD5380 + tristate "Analog Devices AD5380/81/82/83/84/90/91/92 DAC driver" + depends on (SPI_MASTER && I2C!=m) || I2C + select REGMAP_I2C if I2C + select REGMAP_SPI if SPI_MASTER + help + Say yes here to build support for Analog Devices AD5380, AD5381, + AD5382, AD5383, AD5384, AD5390, AD5391, AD5392 multi-channel + Digital to Analog Converters (DAC). + + To compile this driver as module choose M here: the module will be called + ad5380. + +config AD5421 + tristate "Analog Devices AD5421 DAC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5421 loop-powered + digital-to-analog convertors (DAC). + + To compile this driver as module choose M here: the module will be called + ad5421. + +config AD5446 + tristate "Analog Devices AD5446 and similar single channel DACs driver" + depends on (SPI_MASTER && I2C!=m) || I2C + help + Say yes here to build support for Analog Devices AD5300, AD5301, AD5310, + AD5311, AD5320, AD5321, AD5444, AD5446, AD5450, AD5451, AD5452, AD5453, + AD5512A, AD5541A, AD5542A, AD5543, AD5553, AD5601, AD5602, AD5611, AD5612, + AD5620, AD5621, AD5622, AD5640, AD5641, AD5660, AD5662 DACs. + + To compile this driver as a module, choose M here: the + module will be called ad5446. + +config AD5449 + tristate "Analog Devices AD5449 and similar DACs driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5415, AD5426, AD5429, + AD5432, AD5439, AD5443, AD5449 Digital to Analog Converters. + + To compile this driver as a module, choose M here: the + module will be called ad5449. + +config AD5504 + tristate "Analog Devices AD5504/AD5501 DAC SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5504, AD5501, + High Voltage Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5504. + +config AD5624R_SPI + tristate "Analog Devices AD5624/44/64R DAC spi driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5624R, AD5644R and + AD5664R converters (DAC). This driver uses the common SPI interface. + +config AD5686 + tristate "Analog Devices AD5686R/AD5685R/AD5684R DAC SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5686R, AD5685R, + AD5684R, AD5791 Voltage Output Digital to + Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5686. + +config AD5755 + tristate "Analog Devices AD5755/AD5755-1/AD5757/AD5735/AD5737 DAC driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5755, AD5755-1, + AD5757, AD5735, AD5737 quad channel Digital to + Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5755. + +config AD5764 + tristate "Analog Devices AD5764/64R/44/44R DAC driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5764, AD5764R, AD5744, + AD5744R Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5764. + +config AD5791 + tristate "Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5760, AD5780, + AD5781, AD5790, AD5791 High Resolution Voltage Output Digital to + Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5791. + +config AD7303 + tristate "Analog Devices AD7303 DAC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD7303 Digital to Analog + Converters (DAC). + + To compile this driver as module choose M here: the module will be called + ad7303. + +config MAX517 + tristate "Maxim MAX517/518/519/520/521 DAC driver" + depends on I2C + help + If you say yes here you get support for the following Maxim chips + (I2C 8-Bit DACs with rail-to-rail outputs): + MAX517 - Single channel, single reference + MAX518 - Dual channel, ref=Vdd + MAX519 - Dual channel, dual reference + MAX520 - Quad channel, quad reference + MAX521 - Octal channel, independent ref for ch0-3, shared ref for ch4-7 + + This driver can also be built as a module. If so, the module + will be called max517. + +config MAX5821 + tristate "Maxim MAX5821 DAC driver" + depends on I2C + depends on OF + help + Say yes here to build support for Maxim MAX5821 + 10 bits DAC. + +config MCP4725 + tristate "MCP4725 DAC driver" + depends on I2C + ---help--- + Say Y here if you want to build a driver for the Microchip + MCP 4725 12-bit digital-to-analog converter (DAC) with I2C + interface. + + To compile this driver as a module, choose M here: the module + will be called mcp4725. + +config MCP4922 + tristate "MCP4902, MCP4912, MCP4922 DAC driver" + depends on SPI + help + Say yes here to build the driver for the Microchip MCP4902 + MCP4912, and MCP4922 DAC devices. + + To compile this driver as a module, choose M here: the module + will be called mcp4922. + +endmenu diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile new file mode 100644 index 000000000..52be7e1ac --- /dev/null +++ b/drivers/iio/dac/Makefile @@ -0,0 +1,22 @@ +# +# Makefile for industrial I/O DAC drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD5360) += ad5360.o +obj-$(CONFIG_AD5380) += ad5380.o +obj-$(CONFIG_AD5421) += ad5421.o +obj-$(CONFIG_AD5624R_SPI) += ad5624r_spi.o +obj-$(CONFIG_AD5064) += ad5064.o +obj-$(CONFIG_AD5504) += ad5504.o +obj-$(CONFIG_AD5446) += ad5446.o +obj-$(CONFIG_AD5449) += ad5449.o +obj-$(CONFIG_AD5755) += ad5755.o +obj-$(CONFIG_AD5764) += ad5764.o +obj-$(CONFIG_AD5791) += ad5791.o +obj-$(CONFIG_AD5686) += ad5686.o +obj-$(CONFIG_AD7303) += ad7303.o +obj-$(CONFIG_MAX517) += max517.o +obj-$(CONFIG_MAX5821) += max5821.o +obj-$(CONFIG_MCP4725) += mcp4725.o +obj-$(CONFIG_MCP4922) += mcp4922.o diff --git a/drivers/iio/dac/ad5064.c b/drivers/iio/dac/ad5064.c new file mode 100644 index 000000000..f03b92fd3 --- /dev/null +++ b/drivers/iio/dac/ad5064.c @@ -0,0 +1,684 @@ +/* + * AD5024, AD5025, AD5044, AD5045, AD5064, AD5064-1, AD5065, AD5628, AD5629R, + * AD5648, AD5666, AD5668, AD5669R Digital to analog converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/regulator/consumer.h> +#include <asm/unaligned.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define AD5064_MAX_DAC_CHANNELS 8 +#define AD5064_MAX_VREFS 4 + +#define AD5064_ADDR(x) ((x) << 20) +#define AD5064_CMD(x) ((x) << 24) + +#define AD5064_ADDR_ALL_DAC 0xF + +#define AD5064_CMD_WRITE_INPUT_N 0x0 +#define AD5064_CMD_UPDATE_DAC_N 0x1 +#define AD5064_CMD_WRITE_INPUT_N_UPDATE_ALL 0x2 +#define AD5064_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define AD5064_CMD_POWERDOWN_DAC 0x4 +#define AD5064_CMD_CLEAR 0x5 +#define AD5064_CMD_LDAC_MASK 0x6 +#define AD5064_CMD_RESET 0x7 +#define AD5064_CMD_CONFIG 0x8 + +#define AD5064_CONFIG_DAISY_CHAIN_ENABLE BIT(1) +#define AD5064_CONFIG_INT_VREF_ENABLE BIT(0) + +#define AD5064_LDAC_PWRDN_NONE 0x0 +#define AD5064_LDAC_PWRDN_1K 0x1 +#define AD5064_LDAC_PWRDN_100K 0x2 +#define AD5064_LDAC_PWRDN_3STATE 0x3 + +/** + * struct ad5064_chip_info - chip specific information + * @shared_vref: whether the vref supply is shared between channels + * @internal_vref: internal reference voltage. 0 if the chip has no internal + * vref. + * @channel: channel specification + * @num_channels: number of channels + */ + +struct ad5064_chip_info { + bool shared_vref; + unsigned long internal_vref; + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct ad5064_state; + +typedef int (*ad5064_write_func)(struct ad5064_state *st, unsigned int cmd, + unsigned int addr, unsigned int val); + +/** + * struct ad5064_state - driver instance specific data + * @dev: the device for this driver instance + * @chip_info: chip model specific constants, available modes etc + * @vref_reg: vref supply regulators + * @pwr_down: whether channel is powered down + * @pwr_down_mode: channel's current power down mode + * @dac_cache: current DAC raw value (chip does not support readback) + * @use_internal_vref: set to true if the internal reference voltage should be + * used. + * @write: register write callback + * @data: i2c/spi transfer buffers + */ + +struct ad5064_state { + struct device *dev; + const struct ad5064_chip_info *chip_info; + struct regulator_bulk_data vref_reg[AD5064_MAX_VREFS]; + bool pwr_down[AD5064_MAX_DAC_CHANNELS]; + u8 pwr_down_mode[AD5064_MAX_DAC_CHANNELS]; + unsigned int dac_cache[AD5064_MAX_DAC_CHANNELS]; + bool use_internal_vref; + + ad5064_write_func write; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + u8 i2c[3]; + __be32 spi; + } data ____cacheline_aligned; +}; + +enum ad5064_type { + ID_AD5024, + ID_AD5025, + ID_AD5044, + ID_AD5045, + ID_AD5064, + ID_AD5064_1, + ID_AD5065, + ID_AD5628_1, + ID_AD5628_2, + ID_AD5648_1, + ID_AD5648_2, + ID_AD5666_1, + ID_AD5666_2, + ID_AD5668_1, + ID_AD5668_2, +}; + +static int ad5064_write(struct ad5064_state *st, unsigned int cmd, + unsigned int addr, unsigned int val, unsigned int shift) +{ + val <<= shift; + + return st->write(st, cmd, addr, val); +} + +static int ad5064_sync_powerdown_mode(struct ad5064_state *st, + const struct iio_chan_spec *chan) +{ + unsigned int val; + int ret; + + val = (0x1 << chan->address); + + if (st->pwr_down[chan->channel]) + val |= st->pwr_down_mode[chan->channel] << 8; + + ret = ad5064_write(st, AD5064_CMD_POWERDOWN_DAC, 0, val, 0); + + return ret; +} + +static const char * const ad5064_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "three_state", +}; + +static int ad5064_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5064_state *st = iio_priv(indio_dev); + + return st->pwr_down_mode[chan->channel] - 1; +} + +static int ad5064_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5064_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&indio_dev->mlock); + st->pwr_down_mode[chan->channel] = mode + 1; + + ret = ad5064_sync_powerdown_mode(st, chan); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static const struct iio_enum ad5064_powerdown_mode_enum = { + .items = ad5064_powerdown_modes, + .num_items = ARRAY_SIZE(ad5064_powerdown_modes), + .get = ad5064_get_powerdown_mode, + .set = ad5064_set_powerdown_mode, +}; + +static ssize_t ad5064_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5064_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->pwr_down[chan->channel]); +} + +static ssize_t ad5064_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + struct ad5064_state *st = iio_priv(indio_dev); + bool pwr_down; + int ret; + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + st->pwr_down[chan->channel] = pwr_down; + + ret = ad5064_sync_powerdown_mode(st, chan); + mutex_unlock(&indio_dev->mlock); + return ret ? ret : len; +} + +static int ad5064_get_vref(struct ad5064_state *st, + struct iio_chan_spec const *chan) +{ + unsigned int i; + + if (st->use_internal_vref) + return st->chip_info->internal_vref; + + i = st->chip_info->shared_vref ? 0 : chan->channel; + return regulator_get_voltage(st->vref_reg[i].consumer); +} + +static int ad5064_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5064_state *st = iio_priv(indio_dev); + int scale_uv; + + switch (m) { + case IIO_CHAN_INFO_RAW: + *val = st->dac_cache[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = ad5064_get_vref(st, chan); + if (scale_uv < 0) + return scale_uv; + + *val = scale_uv / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + return -EINVAL; +} + +static int ad5064_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct ad5064_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = ad5064_write(st, AD5064_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, val, chan->scan_type.shift); + if (ret == 0) + st->dac_cache[chan->channel] = val; + mutex_unlock(&indio_dev->mlock); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad5064_info = { + .read_raw = ad5064_read_raw, + .write_raw = ad5064_write_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec_ext_info ad5064_ext_info[] = { + { + .name = "powerdown", + .read = ad5064_read_dac_powerdown, + .write = ad5064_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad5064_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", &ad5064_powerdown_mode_enum), + { }, +}; + +#define AD5064_CHANNEL(chan, addr, bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = addr, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 20 - bits, \ + }, \ + .ext_info = ad5064_ext_info, \ +} + +#define DECLARE_AD5064_CHANNELS(name, bits) \ +const struct iio_chan_spec name[] = { \ + AD5064_CHANNEL(0, 0, bits), \ + AD5064_CHANNEL(1, 1, bits), \ + AD5064_CHANNEL(2, 2, bits), \ + AD5064_CHANNEL(3, 3, bits), \ + AD5064_CHANNEL(4, 4, bits), \ + AD5064_CHANNEL(5, 5, bits), \ + AD5064_CHANNEL(6, 6, bits), \ + AD5064_CHANNEL(7, 7, bits), \ +} + +#define DECLARE_AD5065_CHANNELS(name, bits) \ +const struct iio_chan_spec name[] = { \ + AD5064_CHANNEL(0, 0, bits), \ + AD5064_CHANNEL(1, 3, bits), \ +} + +static DECLARE_AD5064_CHANNELS(ad5024_channels, 12); +static DECLARE_AD5064_CHANNELS(ad5044_channels, 14); +static DECLARE_AD5064_CHANNELS(ad5064_channels, 16); + +static DECLARE_AD5065_CHANNELS(ad5025_channels, 12); +static DECLARE_AD5065_CHANNELS(ad5045_channels, 14); +static DECLARE_AD5065_CHANNELS(ad5065_channels, 16); + +static const struct ad5064_chip_info ad5064_chip_info_tbl[] = { + [ID_AD5024] = { + .shared_vref = false, + .channels = ad5024_channels, + .num_channels = 4, + }, + [ID_AD5025] = { + .shared_vref = false, + .channels = ad5025_channels, + .num_channels = 2, + }, + [ID_AD5044] = { + .shared_vref = false, + .channels = ad5044_channels, + .num_channels = 4, + }, + [ID_AD5045] = { + .shared_vref = false, + .channels = ad5045_channels, + .num_channels = 2, + }, + [ID_AD5064] = { + .shared_vref = false, + .channels = ad5064_channels, + .num_channels = 4, + }, + [ID_AD5064_1] = { + .shared_vref = true, + .channels = ad5064_channels, + .num_channels = 4, + }, + [ID_AD5065] = { + .shared_vref = false, + .channels = ad5065_channels, + .num_channels = 2, + }, + [ID_AD5628_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5024_channels, + .num_channels = 8, + }, + [ID_AD5628_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5024_channels, + .num_channels = 8, + }, + [ID_AD5648_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5044_channels, + .num_channels = 8, + }, + [ID_AD5648_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5044_channels, + .num_channels = 8, + }, + [ID_AD5666_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5064_channels, + .num_channels = 4, + }, + [ID_AD5666_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5064_channels, + .num_channels = 4, + }, + [ID_AD5668_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5064_channels, + .num_channels = 8, + }, + [ID_AD5668_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5064_channels, + .num_channels = 8, + }, +}; + +static inline unsigned int ad5064_num_vref(struct ad5064_state *st) +{ + return st->chip_info->shared_vref ? 1 : st->chip_info->num_channels; +} + +static const char * const ad5064_vref_names[] = { + "vrefA", + "vrefB", + "vrefC", + "vrefD", +}; + +static const char * const ad5064_vref_name(struct ad5064_state *st, + unsigned int vref) +{ + return st->chip_info->shared_vref ? "vref" : ad5064_vref_names[vref]; +} + +static int ad5064_probe(struct device *dev, enum ad5064_type type, + const char *name, ad5064_write_func write) +{ + struct iio_dev *indio_dev; + struct ad5064_state *st; + unsigned int midscale; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + + st->chip_info = &ad5064_chip_info_tbl[type]; + st->dev = dev; + st->write = write; + + for (i = 0; i < ad5064_num_vref(st); ++i) + st->vref_reg[i].supply = ad5064_vref_name(st, i); + + ret = devm_regulator_bulk_get(dev, ad5064_num_vref(st), + st->vref_reg); + if (ret) { + if (!st->chip_info->internal_vref) + return ret; + st->use_internal_vref = true; + ret = ad5064_write(st, AD5064_CMD_CONFIG, 0, + AD5064_CONFIG_INT_VREF_ENABLE, 0); + if (ret) { + dev_err(dev, "Failed to enable internal vref: %d\n", + ret); + return ret; + } + } else { + ret = regulator_bulk_enable(ad5064_num_vref(st), st->vref_reg); + if (ret) + return ret; + } + + indio_dev->dev.parent = dev; + indio_dev->name = name; + indio_dev->info = &ad5064_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + midscale = (1 << indio_dev->channels[0].scan_type.realbits) / 2; + + for (i = 0; i < st->chip_info->num_channels; ++i) { + st->pwr_down_mode[i] = AD5064_LDAC_PWRDN_1K; + st->dac_cache[i] = midscale; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!st->use_internal_vref) + regulator_bulk_disable(ad5064_num_vref(st), st->vref_reg); + + return ret; +} + +static int ad5064_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5064_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (!st->use_internal_vref) + regulator_bulk_disable(ad5064_num_vref(st), st->vref_reg); + + return 0; +} + +#if IS_ENABLED(CONFIG_SPI_MASTER) + +static int ad5064_spi_write(struct ad5064_state *st, unsigned int cmd, + unsigned int addr, unsigned int val) +{ + struct spi_device *spi = to_spi_device(st->dev); + + st->data.spi = cpu_to_be32(AD5064_CMD(cmd) | AD5064_ADDR(addr) | val); + return spi_write(spi, &st->data.spi, sizeof(st->data.spi)); +} + +static int ad5064_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + + return ad5064_probe(&spi->dev, id->driver_data, id->name, + ad5064_spi_write); +} + +static int ad5064_spi_remove(struct spi_device *spi) +{ + return ad5064_remove(&spi->dev); +} + +static const struct spi_device_id ad5064_spi_ids[] = { + {"ad5024", ID_AD5024}, + {"ad5025", ID_AD5025}, + {"ad5044", ID_AD5044}, + {"ad5045", ID_AD5045}, + {"ad5064", ID_AD5064}, + {"ad5064-1", ID_AD5064_1}, + {"ad5065", ID_AD5065}, + {"ad5628-1", ID_AD5628_1}, + {"ad5628-2", ID_AD5628_2}, + {"ad5648-1", ID_AD5648_1}, + {"ad5648-2", ID_AD5648_2}, + {"ad5666-1", ID_AD5666_1}, + {"ad5666-2", ID_AD5666_2}, + {"ad5668-1", ID_AD5668_1}, + {"ad5668-2", ID_AD5668_2}, + {"ad5668-3", ID_AD5668_2}, /* similar enough to ad5668-2 */ + {} +}; +MODULE_DEVICE_TABLE(spi, ad5064_spi_ids); + +static struct spi_driver ad5064_spi_driver = { + .driver = { + .name = "ad5064", + .owner = THIS_MODULE, + }, + .probe = ad5064_spi_probe, + .remove = ad5064_spi_remove, + .id_table = ad5064_spi_ids, +}; + +static int __init ad5064_spi_register_driver(void) +{ + return spi_register_driver(&ad5064_spi_driver); +} + +static void ad5064_spi_unregister_driver(void) +{ + spi_unregister_driver(&ad5064_spi_driver); +} + +#else + +static inline int ad5064_spi_register_driver(void) { return 0; } +static inline void ad5064_spi_unregister_driver(void) { } + +#endif + +#if IS_ENABLED(CONFIG_I2C) + +static int ad5064_i2c_write(struct ad5064_state *st, unsigned int cmd, + unsigned int addr, unsigned int val) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + + st->data.i2c[0] = (cmd << 4) | addr; + put_unaligned_be16(val, &st->data.i2c[1]); + return i2c_master_send(i2c, st->data.i2c, 3); +} + +static int ad5064_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + return ad5064_probe(&i2c->dev, id->driver_data, id->name, + ad5064_i2c_write); +} + +static int ad5064_i2c_remove(struct i2c_client *i2c) +{ + return ad5064_remove(&i2c->dev); +} + +static const struct i2c_device_id ad5064_i2c_ids[] = { + {"ad5629-1", ID_AD5628_1}, + {"ad5629-2", ID_AD5628_2}, + {"ad5629-3", ID_AD5628_2}, /* similar enough to ad5629-2 */ + {"ad5669-1", ID_AD5668_1}, + {"ad5669-2", ID_AD5668_2}, + {"ad5669-3", ID_AD5668_2}, /* similar enough to ad5669-2 */ + {} +}; +MODULE_DEVICE_TABLE(i2c, ad5064_i2c_ids); + +static struct i2c_driver ad5064_i2c_driver = { + .driver = { + .name = "ad5064", + .owner = THIS_MODULE, + }, + .probe = ad5064_i2c_probe, + .remove = ad5064_i2c_remove, + .id_table = ad5064_i2c_ids, +}; + +static int __init ad5064_i2c_register_driver(void) +{ + return i2c_add_driver(&ad5064_i2c_driver); +} + +static void __exit ad5064_i2c_unregister_driver(void) +{ + i2c_del_driver(&ad5064_i2c_driver); +} + +#else + +static inline int ad5064_i2c_register_driver(void) { return 0; } +static inline void ad5064_i2c_unregister_driver(void) { } + +#endif + +static int __init ad5064_init(void) +{ + int ret; + + ret = ad5064_spi_register_driver(); + if (ret) + return ret; + + ret = ad5064_i2c_register_driver(); + if (ret) { + ad5064_spi_unregister_driver(); + return ret; + } + + return 0; +} +module_init(ad5064_init); + +static void __exit ad5064_exit(void) +{ + ad5064_i2c_unregister_driver(); + ad5064_spi_unregister_driver(); +} +module_exit(ad5064_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices AD5024 and similar multi-channel DACs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5360.c b/drivers/iio/dac/ad5360.c new file mode 100644 index 000000000..64634d7f5 --- /dev/null +++ b/drivers/iio/dac/ad5360.c @@ -0,0 +1,562 @@ +/* + * Analog devices AD5360, AD5361, AD5362, AD5363, AD5370, AD5371, AD5373 + * multi-channel Digital to Analog Converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define AD5360_CMD(x) ((x) << 22) +#define AD5360_ADDR(x) ((x) << 16) + +#define AD5360_READBACK_TYPE(x) ((x) << 13) +#define AD5360_READBACK_ADDR(x) ((x) << 7) + +#define AD5360_CHAN_ADDR(chan) ((chan) + 0x8) + +#define AD5360_CMD_WRITE_DATA 0x3 +#define AD5360_CMD_WRITE_OFFSET 0x2 +#define AD5360_CMD_WRITE_GAIN 0x1 +#define AD5360_CMD_SPECIAL_FUNCTION 0x0 + +/* Special function register addresses */ +#define AD5360_REG_SF_NOP 0x0 +#define AD5360_REG_SF_CTRL 0x1 +#define AD5360_REG_SF_OFS(x) (0x2 + (x)) +#define AD5360_REG_SF_READBACK 0x5 + +#define AD5360_SF_CTRL_PWR_DOWN BIT(0) + +#define AD5360_READBACK_X1A 0x0 +#define AD5360_READBACK_X1B 0x1 +#define AD5360_READBACK_OFFSET 0x2 +#define AD5360_READBACK_GAIN 0x3 +#define AD5360_READBACK_SF 0x4 + + +/** + * struct ad5360_chip_info - chip specific information + * @channel_template: channel specification template + * @num_channels: number of channels + * @channels_per_group: number of channels per group + * @num_vrefs: number of vref supplies for the chip +*/ + +struct ad5360_chip_info { + struct iio_chan_spec channel_template; + unsigned int num_channels; + unsigned int channels_per_group; + unsigned int num_vrefs; +}; + +/** + * struct ad5360_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip model specific constants, available modes etc + * @vref_reg: vref supply regulators + * @ctrl: control register cache + * @data: spi transfer buffers + */ + +struct ad5360_state { + struct spi_device *spi; + const struct ad5360_chip_info *chip_info; + struct regulator_bulk_data vref_reg[3]; + unsigned int ctrl; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +enum ad5360_type { + ID_AD5360, + ID_AD5361, + ID_AD5362, + ID_AD5363, + ID_AD5370, + ID_AD5371, + ID_AD5372, + ID_AD5373, +}; + +#define AD5360_CHANNEL(bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 16 - (bits), \ + }, \ +} + +static const struct ad5360_chip_info ad5360_chip_info_tbl[] = { + [ID_AD5360] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 16, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5361] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 16, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5362] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 8, + .channels_per_group = 4, + .num_vrefs = 2, + }, + [ID_AD5363] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 8, + .channels_per_group = 4, + .num_vrefs = 2, + }, + [ID_AD5370] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 40, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5371] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 40, + .channels_per_group = 8, + .num_vrefs = 3, + }, + [ID_AD5372] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 32, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5373] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 32, + .channels_per_group = 8, + .num_vrefs = 2, + }, +}; + +static unsigned int ad5360_get_channel_vref_index(struct ad5360_state *st, + unsigned int channel) +{ + unsigned int i; + + /* The first groups have their own vref, while the remaining groups + * share the last vref */ + i = channel / st->chip_info->channels_per_group; + if (i >= st->chip_info->num_vrefs) + i = st->chip_info->num_vrefs - 1; + + return i; +} + +static int ad5360_get_channel_vref(struct ad5360_state *st, + unsigned int channel) +{ + unsigned int i = ad5360_get_channel_vref_index(st, channel); + + return regulator_get_voltage(st->vref_reg[i].consumer); +} + + +static int ad5360_write_unlocked(struct iio_dev *indio_dev, + unsigned int cmd, unsigned int addr, unsigned int val, + unsigned int shift) +{ + struct ad5360_state *st = iio_priv(indio_dev); + + val <<= shift; + val |= AD5360_CMD(cmd) | AD5360_ADDR(addr); + st->data[0].d32 = cpu_to_be32(val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5360_write(struct iio_dev *indio_dev, unsigned int cmd, + unsigned int addr, unsigned int val, unsigned int shift) +{ + int ret; + + mutex_lock(&indio_dev->mlock); + ret = ad5360_write_unlocked(indio_dev, cmd, addr, val, shift); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5360_read(struct iio_dev *indio_dev, unsigned int type, + unsigned int addr) +{ + struct ad5360_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + mutex_lock(&indio_dev->mlock); + + st->data[0].d32 = cpu_to_be32(AD5360_CMD(AD5360_CMD_SPECIAL_FUNCTION) | + AD5360_ADDR(AD5360_REG_SF_READBACK) | + AD5360_READBACK_TYPE(type) | + AD5360_READBACK_ADDR(addr)); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret >= 0) + ret = be32_to_cpu(st->data[1].d32) & 0xffff; + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static ssize_t ad5360_read_dac_powerdown(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad5360_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", (bool)(st->ctrl & AD5360_SF_CTRL_PWR_DOWN)); +} + +static int ad5360_update_ctrl(struct iio_dev *indio_dev, unsigned int set, + unsigned int clr) +{ + struct ad5360_state *st = iio_priv(indio_dev); + unsigned int ret; + + mutex_lock(&indio_dev->mlock); + + st->ctrl |= set; + st->ctrl &= ~clr; + + ret = ad5360_write_unlocked(indio_dev, AD5360_CMD_SPECIAL_FUNCTION, + AD5360_REG_SF_CTRL, st->ctrl, 0); + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static ssize_t ad5360_write_dac_powerdown(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool pwr_down; + int ret; + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + if (pwr_down) + ret = ad5360_update_ctrl(indio_dev, AD5360_SF_CTRL_PWR_DOWN, 0); + else + ret = ad5360_update_ctrl(indio_dev, 0, AD5360_SF_CTRL_PWR_DOWN); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage_powerdown, + S_IRUGO | S_IWUSR, + ad5360_read_dac_powerdown, + ad5360_write_dac_powerdown, 0); + +static struct attribute *ad5360_attributes[] = { + &iio_dev_attr_out_voltage_powerdown.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5360_attribute_group = { + .attrs = ad5360_attributes, +}; + +static int ad5360_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5360_state *st = iio_priv(indio_dev); + int max_val = (1 << chan->scan_type.realbits); + unsigned int ofs_index; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5360_write(indio_dev, AD5360_CMD_WRITE_DATA, + chan->address, val, chan->scan_type.shift); + + case IIO_CHAN_INFO_CALIBBIAS: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5360_write(indio_dev, AD5360_CMD_WRITE_OFFSET, + chan->address, val, chan->scan_type.shift); + + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5360_write(indio_dev, AD5360_CMD_WRITE_GAIN, + chan->address, val, chan->scan_type.shift); + + case IIO_CHAN_INFO_OFFSET: + if (val <= -max_val || val > 0) + return -EINVAL; + + val = -val; + + /* offset is supposed to have the same scale as raw, but it + * is always 14bits wide, so on a chip where the raw value has + * more bits, we need to shift offset. */ + val >>= (chan->scan_type.realbits - 14); + + /* There is one DAC offset register per vref. Changing one + * channels offset will also change the offset for all other + * channels which share the same vref supply. */ + ofs_index = ad5360_get_channel_vref_index(st, chan->channel); + return ad5360_write(indio_dev, AD5360_CMD_SPECIAL_FUNCTION, + AD5360_REG_SF_OFS(ofs_index), val, 0); + default: + break; + } + + return -EINVAL; +} + +static int ad5360_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5360_state *st = iio_priv(indio_dev); + unsigned int ofs_index; + int scale_uv; + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5360_read(indio_dev, AD5360_READBACK_X1A, + chan->address); + if (ret < 0) + return ret; + *val = ret >> chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = ad5360_get_channel_vref(st, chan->channel); + if (scale_uv < 0) + return scale_uv; + + /* vout = 4 * vref * dac_code */ + *val = scale_uv * 4 / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_CALIBBIAS: + ret = ad5360_read(indio_dev, AD5360_READBACK_OFFSET, + chan->address); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + ret = ad5360_read(indio_dev, AD5360_READBACK_GAIN, + chan->address); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + ofs_index = ad5360_get_channel_vref_index(st, chan->channel); + ret = ad5360_read(indio_dev, AD5360_READBACK_SF, + AD5360_REG_SF_OFS(ofs_index)); + if (ret < 0) + return ret; + + ret <<= (chan->scan_type.realbits - 14); + *val = -ret; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info ad5360_info = { + .read_raw = ad5360_read_raw, + .write_raw = ad5360_write_raw, + .attrs = &ad5360_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const char * const ad5360_vref_name[] = { + "vref0", "vref1", "vref2" +}; + +static int ad5360_alloc_channels(struct iio_dev *indio_dev) +{ + struct ad5360_state *st = iio_priv(indio_dev); + struct iio_chan_spec *channels; + unsigned int i; + + channels = kcalloc(st->chip_info->num_channels, + sizeof(struct iio_chan_spec), GFP_KERNEL); + + if (!channels) + return -ENOMEM; + + for (i = 0; i < st->chip_info->num_channels; ++i) { + channels[i] = st->chip_info->channel_template; + channels[i].channel = i; + channels[i].address = AD5360_CHAN_ADDR(i); + } + + indio_dev->channels = channels; + + return 0; +} + +static int ad5360_probe(struct spi_device *spi) +{ + enum ad5360_type type = spi_get_device_id(spi)->driver_data; + struct iio_dev *indio_dev; + struct ad5360_state *st; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->chip_info = &ad5360_chip_info_tbl[type]; + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5360_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = st->chip_info->num_channels; + + ret = ad5360_alloc_channels(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to allocate channel spec: %d\n", ret); + return ret; + } + + for (i = 0; i < st->chip_info->num_vrefs; ++i) + st->vref_reg[i].supply = ad5360_vref_name[i]; + + ret = devm_regulator_bulk_get(&st->spi->dev, st->chip_info->num_vrefs, + st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to request vref regulators: %d\n", ret); + goto error_free_channels; + } + + ret = regulator_bulk_enable(st->chip_info->num_vrefs, st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to enable vref regulators: %d\n", ret); + goto error_free_channels; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device: %d\n", ret); + goto error_disable_reg; + } + + return 0; + +error_disable_reg: + regulator_bulk_disable(st->chip_info->num_vrefs, st->vref_reg); +error_free_channels: + kfree(indio_dev->channels); + + return ret; +} + +static int ad5360_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5360_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + kfree(indio_dev->channels); + + regulator_bulk_disable(st->chip_info->num_vrefs, st->vref_reg); + + return 0; +} + +static const struct spi_device_id ad5360_ids[] = { + { "ad5360", ID_AD5360 }, + { "ad5361", ID_AD5361 }, + { "ad5362", ID_AD5362 }, + { "ad5363", ID_AD5363 }, + { "ad5370", ID_AD5370 }, + { "ad5371", ID_AD5371 }, + { "ad5372", ID_AD5372 }, + { "ad5373", ID_AD5373 }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5360_ids); + +static struct spi_driver ad5360_driver = { + .driver = { + .name = "ad5360", + .owner = THIS_MODULE, + }, + .probe = ad5360_probe, + .remove = ad5360_remove, + .id_table = ad5360_ids, +}; +module_spi_driver(ad5360_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices AD5360/61/62/63/70/71/72/73 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5380.c b/drivers/iio/dac/ad5380.c new file mode 100644 index 000000000..9de4c4d38 --- /dev/null +++ b/drivers/iio/dac/ad5380.c @@ -0,0 +1,654 @@ +/* + * Analog devices AD5380, AD5381, AD5382, AD5383, AD5390, AD5391, AD5392 + * multi-channel Digital to Analog Converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define AD5380_REG_DATA(x) (((x) << 2) | 3) +#define AD5380_REG_OFFSET(x) (((x) << 2) | 2) +#define AD5380_REG_GAIN(x) (((x) << 2) | 1) +#define AD5380_REG_SF_PWR_DOWN (8 << 2) +#define AD5380_REG_SF_PWR_UP (9 << 2) +#define AD5380_REG_SF_CTRL (12 << 2) + +#define AD5380_CTRL_PWR_DOWN_MODE_OFFSET 13 +#define AD5380_CTRL_INT_VREF_2V5 BIT(12) +#define AD5380_CTRL_INT_VREF_EN BIT(10) + +/** + * struct ad5380_chip_info - chip specific information + * @channel_template: channel specification template + * @num_channels: number of channels + * @int_vref: internal vref in uV +*/ + +struct ad5380_chip_info { + struct iio_chan_spec channel_template; + unsigned int num_channels; + unsigned int int_vref; +}; + +/** + * struct ad5380_state - driver instance specific data + * @regmap: regmap instance used by the device + * @chip_info: chip model specific constants, available modes etc + * @vref_reg: vref supply regulator + * @vref: actual reference voltage used in uA + * @pwr_down: whether the chip is currently in power down mode + */ + +struct ad5380_state { + struct regmap *regmap; + const struct ad5380_chip_info *chip_info; + struct regulator *vref_reg; + int vref; + bool pwr_down; +}; + +enum ad5380_type { + ID_AD5380_3, + ID_AD5380_5, + ID_AD5381_3, + ID_AD5381_5, + ID_AD5382_3, + ID_AD5382_5, + ID_AD5383_3, + ID_AD5383_5, + ID_AD5390_3, + ID_AD5390_5, + ID_AD5391_3, + ID_AD5391_5, + ID_AD5392_3, + ID_AD5392_5, +}; + +static ssize_t ad5380_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5380_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5380_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + struct ad5380_state *st = iio_priv(indio_dev); + bool pwr_down; + int ret; + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + if (pwr_down) + ret = regmap_write(st->regmap, AD5380_REG_SF_PWR_DOWN, 0); + else + ret = regmap_write(st->regmap, AD5380_REG_SF_PWR_UP, 0); + + st->pwr_down = pwr_down; + + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static const char * const ad5380_powerdown_modes[] = { + "100kohm_to_gnd", + "three_state", +}; + +static int ad5380_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5380_state *st = iio_priv(indio_dev); + unsigned int mode; + int ret; + + ret = regmap_read(st->regmap, AD5380_REG_SF_CTRL, &mode); + if (ret) + return ret; + + mode = (mode >> AD5380_CTRL_PWR_DOWN_MODE_OFFSET) & 1; + + return mode; +} + +static int ad5380_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5380_state *st = iio_priv(indio_dev); + int ret; + + ret = regmap_update_bits(st->regmap, AD5380_REG_SF_CTRL, + 1 << AD5380_CTRL_PWR_DOWN_MODE_OFFSET, + mode << AD5380_CTRL_PWR_DOWN_MODE_OFFSET); + + return ret; +} + +static const struct iio_enum ad5380_powerdown_mode_enum = { + .items = ad5380_powerdown_modes, + .num_items = ARRAY_SIZE(ad5380_powerdown_modes), + .get = ad5380_get_powerdown_mode, + .set = ad5380_set_powerdown_mode, +}; + +static unsigned int ad5380_info_to_reg(struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case 0: + return AD5380_REG_DATA(chan->address); + case IIO_CHAN_INFO_CALIBBIAS: + return AD5380_REG_OFFSET(chan->address); + case IIO_CHAN_INFO_CALIBSCALE: + return AD5380_REG_GAIN(chan->address); + default: + break; + } + + return 0; +} + +static int ad5380_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + const unsigned int max_val = (1 << chan->scan_type.realbits); + struct ad5380_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= max_val || val < 0) + return -EINVAL; + + return regmap_write(st->regmap, + ad5380_info_to_reg(chan, info), + val << chan->scan_type.shift); + case IIO_CHAN_INFO_CALIBBIAS: + val += (1 << chan->scan_type.realbits) / 2; + if (val >= max_val || val < 0) + return -EINVAL; + + return regmap_write(st->regmap, + AD5380_REG_OFFSET(chan->address), + val << chan->scan_type.shift); + default: + break; + } + return -EINVAL; +} + +static int ad5380_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad5380_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_CALIBSCALE: + ret = regmap_read(st->regmap, ad5380_info_to_reg(chan, info), + val); + if (ret) + return ret; + *val >>= chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + ret = regmap_read(st->regmap, AD5380_REG_OFFSET(chan->address), + val); + if (ret) + return ret; + *val >>= chan->scan_type.shift; + val -= (1 << chan->scan_type.realbits) / 2; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 2 * st->vref; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info ad5380_info = { + .read_raw = ad5380_read_raw, + .write_raw = ad5380_write_raw, + .driver_module = THIS_MODULE, +}; + +static struct iio_chan_spec_ext_info ad5380_ext_info[] = { + { + .name = "powerdown", + .read = ad5380_read_dac_powerdown, + .write = ad5380_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad5380_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", &ad5380_powerdown_mode_enum), + { }, +}; + +#define AD5380_CHANNEL(_bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 14 - (_bits), \ + }, \ + .ext_info = ad5380_ext_info, \ +} + +static const struct ad5380_chip_info ad5380_chip_info_tbl[] = { + [ID_AD5380_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 40, + .int_vref = 1250, + }, + [ID_AD5380_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 40, + .int_vref = 2500, + }, + [ID_AD5381_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 1250, + }, + [ID_AD5381_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 2500, + }, + [ID_AD5382_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 32, + .int_vref = 1250, + }, + [ID_AD5382_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 32, + .int_vref = 2500, + }, + [ID_AD5383_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 32, + .int_vref = 1250, + }, + [ID_AD5383_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 32, + .int_vref = 2500, + }, + [ID_AD5390_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 16, + .int_vref = 1250, + }, + [ID_AD5390_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 16, + .int_vref = 2500, + }, + [ID_AD5391_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 1250, + }, + [ID_AD5391_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 2500, + }, + [ID_AD5392_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 8, + .int_vref = 1250, + }, + [ID_AD5392_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 8, + .int_vref = 2500, + }, +}; + +static int ad5380_alloc_channels(struct iio_dev *indio_dev) +{ + struct ad5380_state *st = iio_priv(indio_dev); + struct iio_chan_spec *channels; + unsigned int i; + + channels = kcalloc(st->chip_info->num_channels, + sizeof(struct iio_chan_spec), GFP_KERNEL); + + if (!channels) + return -ENOMEM; + + for (i = 0; i < st->chip_info->num_channels; ++i) { + channels[i] = st->chip_info->channel_template; + channels[i].channel = i; + channels[i].address = i; + } + + indio_dev->channels = channels; + + return 0; +} + +static int ad5380_probe(struct device *dev, struct regmap *regmap, + enum ad5380_type type, const char *name) +{ + struct iio_dev *indio_dev; + struct ad5380_state *st; + unsigned int ctrl = 0; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) { + dev_err(dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + + st->chip_info = &ad5380_chip_info_tbl[type]; + st->regmap = regmap; + + indio_dev->dev.parent = dev; + indio_dev->name = name; + indio_dev->info = &ad5380_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = st->chip_info->num_channels; + + ret = ad5380_alloc_channels(indio_dev); + if (ret) { + dev_err(dev, "Failed to allocate channel spec: %d\n", ret); + return ret; + } + + if (st->chip_info->int_vref == 2500) + ctrl |= AD5380_CTRL_INT_VREF_2V5; + + st->vref_reg = devm_regulator_get(dev, "vref"); + if (!IS_ERR(st->vref_reg)) { + ret = regulator_enable(st->vref_reg); + if (ret) { + dev_err(dev, "Failed to enable vref regulators: %d\n", + ret); + goto error_free_reg; + } + + ret = regulator_get_voltage(st->vref_reg); + if (ret < 0) + goto error_disable_reg; + + st->vref = ret / 1000; + } else { + st->vref = st->chip_info->int_vref; + ctrl |= AD5380_CTRL_INT_VREF_EN; + } + + ret = regmap_write(st->regmap, AD5380_REG_SF_CTRL, ctrl); + if (ret) { + dev_err(dev, "Failed to write to device: %d\n", ret); + goto error_disable_reg; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "Failed to register iio device: %d\n", ret); + goto error_disable_reg; + } + + return 0; + +error_disable_reg: + if (!IS_ERR(st->vref_reg)) + regulator_disable(st->vref_reg); +error_free_reg: + kfree(indio_dev->channels); + + return ret; +} + +static int ad5380_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5380_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + kfree(indio_dev->channels); + + if (!IS_ERR(st->vref_reg)) { + regulator_disable(st->vref_reg); + } + + return 0; +} + +static bool ad5380_reg_false(struct device *dev, unsigned int reg) +{ + return false; +} + +static const struct regmap_config ad5380_regmap_config = { + .reg_bits = 10, + .val_bits = 14, + + .max_register = AD5380_REG_DATA(40), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = ad5380_reg_false, + .readable_reg = ad5380_reg_false, +}; + +#if IS_ENABLED(CONFIG_SPI_MASTER) + +static int ad5380_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &ad5380_regmap_config); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return ad5380_probe(&spi->dev, regmap, id->driver_data, id->name); +} + +static int ad5380_spi_remove(struct spi_device *spi) +{ + return ad5380_remove(&spi->dev); +} + +static const struct spi_device_id ad5380_spi_ids[] = { + { "ad5380-3", ID_AD5380_3 }, + { "ad5380-5", ID_AD5380_5 }, + { "ad5381-3", ID_AD5381_3 }, + { "ad5381-5", ID_AD5381_5 }, + { "ad5382-3", ID_AD5382_3 }, + { "ad5382-5", ID_AD5382_5 }, + { "ad5383-3", ID_AD5383_3 }, + { "ad5383-5", ID_AD5383_5 }, + { "ad5384-3", ID_AD5380_3 }, + { "ad5384-5", ID_AD5380_5 }, + { "ad5390-3", ID_AD5390_3 }, + { "ad5390-5", ID_AD5390_5 }, + { "ad5391-3", ID_AD5391_3 }, + { "ad5391-5", ID_AD5391_5 }, + { "ad5392-3", ID_AD5392_3 }, + { "ad5392-5", ID_AD5392_5 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5380_spi_ids); + +static struct spi_driver ad5380_spi_driver = { + .driver = { + .name = "ad5380", + .owner = THIS_MODULE, + }, + .probe = ad5380_spi_probe, + .remove = ad5380_spi_remove, + .id_table = ad5380_spi_ids, +}; + +static inline int ad5380_spi_register_driver(void) +{ + return spi_register_driver(&ad5380_spi_driver); +} + +static inline void ad5380_spi_unregister_driver(void) +{ + spi_unregister_driver(&ad5380_spi_driver); +} + +#else + +static inline int ad5380_spi_register_driver(void) +{ + return 0; +} + +static inline void ad5380_spi_unregister_driver(void) +{ +} + +#endif + +#if IS_ENABLED(CONFIG_I2C) + +static int ad5380_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(i2c, &ad5380_regmap_config); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return ad5380_probe(&i2c->dev, regmap, id->driver_data, id->name); +} + +static int ad5380_i2c_remove(struct i2c_client *i2c) +{ + return ad5380_remove(&i2c->dev); +} + +static const struct i2c_device_id ad5380_i2c_ids[] = { + { "ad5380-3", ID_AD5380_3 }, + { "ad5380-5", ID_AD5380_5 }, + { "ad5381-3", ID_AD5381_3 }, + { "ad5381-5", ID_AD5381_5 }, + { "ad5382-3", ID_AD5382_3 }, + { "ad5382-5", ID_AD5382_5 }, + { "ad5383-3", ID_AD5383_3 }, + { "ad5383-5", ID_AD5383_5 }, + { "ad5384-3", ID_AD5380_3 }, + { "ad5384-5", ID_AD5380_5 }, + { "ad5390-3", ID_AD5390_3 }, + { "ad5390-5", ID_AD5390_5 }, + { "ad5391-3", ID_AD5391_3 }, + { "ad5391-5", ID_AD5391_5 }, + { "ad5392-3", ID_AD5392_3 }, + { "ad5392-5", ID_AD5392_5 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5380_i2c_ids); + +static struct i2c_driver ad5380_i2c_driver = { + .driver = { + .name = "ad5380", + .owner = THIS_MODULE, + }, + .probe = ad5380_i2c_probe, + .remove = ad5380_i2c_remove, + .id_table = ad5380_i2c_ids, +}; + +static inline int ad5380_i2c_register_driver(void) +{ + return i2c_add_driver(&ad5380_i2c_driver); +} + +static inline void ad5380_i2c_unregister_driver(void) +{ + i2c_del_driver(&ad5380_i2c_driver); +} + +#else + +static inline int ad5380_i2c_register_driver(void) +{ + return 0; +} + +static inline void ad5380_i2c_unregister_driver(void) +{ +} + +#endif + +static int __init ad5380_spi_init(void) +{ + int ret; + + ret = ad5380_spi_register_driver(); + if (ret) + return ret; + + ret = ad5380_i2c_register_driver(); + if (ret) { + ad5380_spi_unregister_driver(); + return ret; + } + + return 0; +} +module_init(ad5380_spi_init); + +static void __exit ad5380_spi_exit(void) +{ + ad5380_i2c_unregister_driver(); + ad5380_spi_unregister_driver(); + +} +module_exit(ad5380_spi_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices AD5380/81/82/83/84/90/91/92 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5421.c b/drivers/iio/dac/ad5421.c new file mode 100644 index 000000000..787ef1d85 --- /dev/null +++ b/drivers/iio/dac/ad5421.c @@ -0,0 +1,536 @@ +/* + * AD5421 Digital to analog converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/dac/ad5421.h> + + +#define AD5421_REG_DAC_DATA 0x1 +#define AD5421_REG_CTRL 0x2 +#define AD5421_REG_OFFSET 0x3 +#define AD5421_REG_GAIN 0x4 +/* load dac and fault shared the same register number. Writing to it will cause + * a dac load command, reading from it will return the fault status register */ +#define AD5421_REG_LOAD_DAC 0x5 +#define AD5421_REG_FAULT 0x5 +#define AD5421_REG_FORCE_ALARM_CURRENT 0x6 +#define AD5421_REG_RESET 0x7 +#define AD5421_REG_START_CONVERSION 0x8 +#define AD5421_REG_NOOP 0x9 + +#define AD5421_CTRL_WATCHDOG_DISABLE BIT(12) +#define AD5421_CTRL_AUTO_FAULT_READBACK BIT(11) +#define AD5421_CTRL_MIN_CURRENT BIT(9) +#define AD5421_CTRL_ADC_SOURCE_TEMP BIT(8) +#define AD5421_CTRL_ADC_ENABLE BIT(7) +#define AD5421_CTRL_PWR_DOWN_INT_VREF BIT(6) + +#define AD5421_FAULT_SPI BIT(15) +#define AD5421_FAULT_PEC BIT(14) +#define AD5421_FAULT_OVER_CURRENT BIT(13) +#define AD5421_FAULT_UNDER_CURRENT BIT(12) +#define AD5421_FAULT_TEMP_OVER_140 BIT(11) +#define AD5421_FAULT_TEMP_OVER_100 BIT(10) +#define AD5421_FAULT_UNDER_VOLTAGE_6V BIT(9) +#define AD5421_FAULT_UNDER_VOLTAGE_12V BIT(8) + +/* These bits will cause the fault pin to go high */ +#define AD5421_FAULT_TRIGGER_IRQ \ + (AD5421_FAULT_SPI | AD5421_FAULT_PEC | AD5421_FAULT_OVER_CURRENT | \ + AD5421_FAULT_UNDER_CURRENT | AD5421_FAULT_TEMP_OVER_140) + +/** + * struct ad5421_state - driver instance specific data + * @spi: spi_device + * @ctrl: control register cache + * @current_range: current range which the device is configured for + * @data: spi transfer buffers + * @fault_mask: software masking of events + */ +struct ad5421_state { + struct spi_device *spi; + unsigned int ctrl; + enum ad5421_current_range current_range; + unsigned int fault_mask; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +static const struct iio_event_spec ad5421_current_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 ad5421_temp_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec ad5421_channels[] = { + { + .type = IIO_CURRENT, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, + .event_spec = ad5421_current_event, + .num_event_specs = ARRAY_SIZE(ad5421_current_event), + }, + { + .type = IIO_TEMP, + .channel = -1, + .event_spec = ad5421_temp_event, + .num_event_specs = ARRAY_SIZE(ad5421_temp_event), + }, +}; + +static int ad5421_write_unlocked(struct iio_dev *indio_dev, + unsigned int reg, unsigned int val) +{ + struct ad5421_state *st = iio_priv(indio_dev); + + st->data[0].d32 = cpu_to_be32((reg << 16) | val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5421_write(struct iio_dev *indio_dev, unsigned int reg, + unsigned int val) +{ + int ret; + + mutex_lock(&indio_dev->mlock); + ret = ad5421_write_unlocked(indio_dev, reg, val); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5421_read(struct iio_dev *indio_dev, unsigned int reg) +{ + struct ad5421_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + mutex_lock(&indio_dev->mlock); + + st->data[0].d32 = cpu_to_be32((1 << 23) | (reg << 16)); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret >= 0) + ret = be32_to_cpu(st->data[1].d32) & 0xffff; + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5421_update_ctrl(struct iio_dev *indio_dev, unsigned int set, + unsigned int clr) +{ + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int ret; + + mutex_lock(&indio_dev->mlock); + + st->ctrl &= ~clr; + st->ctrl |= set; + + ret = ad5421_write_unlocked(indio_dev, AD5421_REG_CTRL, st->ctrl); + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static irqreturn_t ad5421_fault_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int fault; + unsigned int old_fault = 0; + unsigned int events; + + fault = ad5421_read(indio_dev, AD5421_REG_FAULT); + if (!fault) + return IRQ_NONE; + + /* If we had a fault, this might mean that the DAC has lost its state + * and has been reset. Make sure that the control register actually + * contains what we expect it to contain. Otherwise the watchdog might + * be enabled and we get watchdog timeout faults, which will render the + * DAC unusable. */ + ad5421_update_ctrl(indio_dev, 0, 0); + + + /* The fault pin stays high as long as a fault condition is present and + * it is not possible to mask fault conditions. For certain fault + * conditions for example like over-temperature it takes some time + * until the fault condition disappears. If we would exit the interrupt + * handler immediately after handling the event it would be entered + * again instantly. Thus we fall back to polling in case we detect that + * a interrupt condition is still present. + */ + do { + /* 0xffff is a invalid value for the register and will only be + * read if there has been a communication error */ + if (fault == 0xffff) + fault = 0; + + /* we are only interested in new events */ + events = (old_fault ^ fault) & fault; + events &= st->fault_mask; + + if (events & AD5421_FAULT_OVER_CURRENT) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + } + + if (events & AD5421_FAULT_UNDER_CURRENT) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns()); + } + + if (events & AD5421_FAULT_TEMP_OVER_140) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + } + + old_fault = fault; + fault = ad5421_read(indio_dev, AD5421_REG_FAULT); + + /* still active? go to sleep for some time */ + if (fault & AD5421_FAULT_TRIGGER_IRQ) + msleep(1000); + + } while (fault & AD5421_FAULT_TRIGGER_IRQ); + + + return IRQ_HANDLED; +} + +static void ad5421_get_current_min_max(struct ad5421_state *st, + unsigned int *min, unsigned int *max) +{ + /* The current range is configured using external pins, which are + * usually hard-wired and not run-time switchable. */ + switch (st->current_range) { + case AD5421_CURRENT_RANGE_4mA_20mA: + *min = 4000; + *max = 20000; + break; + case AD5421_CURRENT_RANGE_3mA8_21mA: + *min = 3800; + *max = 21000; + break; + case AD5421_CURRENT_RANGE_3mA2_24mA: + *min = 3200; + *max = 24000; + break; + default: + *min = 0; + *max = 1; + break; + } +} + +static inline unsigned int ad5421_get_offset(struct ad5421_state *st) +{ + unsigned int min, max; + + ad5421_get_current_min_max(st, &min, &max); + return (min * (1 << 16)) / (max - min); +} + +static int ad5421_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long m) +{ + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int min, max; + int ret; + + if (chan->type != IIO_CURRENT) + return -EINVAL; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5421_read(indio_dev, AD5421_REG_DAC_DATA); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ad5421_get_current_min_max(st, &min, &max); + *val = max - min; + *val2 = (1 << 16) * 1000; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OFFSET: + *val = ad5421_get_offset(st); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + ret = ad5421_read(indio_dev, AD5421_REG_OFFSET); + if (ret < 0) + return ret; + *val = ret - 32768; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + ret = ad5421_read(indio_dev, AD5421_REG_GAIN); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ad5421_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + const unsigned int max_val = 1 << 16; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5421_write(indio_dev, AD5421_REG_DAC_DATA, val); + case IIO_CHAN_INFO_CALIBBIAS: + val += 32768; + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5421_write(indio_dev, AD5421_REG_OFFSET, val); + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5421_write(indio_dev, AD5421_REG_GAIN, val); + default: + break; + } + + return -EINVAL; +} + +static int ad5421_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 ad5421_state *st = iio_priv(indio_dev); + unsigned int mask; + + switch (chan->type) { + case IIO_CURRENT: + if (dir == IIO_EV_DIR_RISING) + mask = AD5421_FAULT_OVER_CURRENT; + else + mask = AD5421_FAULT_UNDER_CURRENT; + break; + case IIO_TEMP: + mask = AD5421_FAULT_TEMP_OVER_140; + break; + default: + return -EINVAL; + } + + mutex_lock(&indio_dev->mlock); + if (state) + st->fault_mask |= mask; + else + st->fault_mask &= ~mask; + mutex_unlock(&indio_dev->mlock); + + return 0; +} + +static int ad5421_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 ad5421_state *st = iio_priv(indio_dev); + unsigned int mask; + + switch (chan->type) { + case IIO_CURRENT: + if (dir == IIO_EV_DIR_RISING) + mask = AD5421_FAULT_OVER_CURRENT; + else + mask = AD5421_FAULT_UNDER_CURRENT; + break; + case IIO_TEMP: + mask = AD5421_FAULT_TEMP_OVER_140; + break; + default: + return -EINVAL; + } + + return (bool)(st->fault_mask & mask); +} + +static int ad5421_read_event_value(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) +{ + int ret; + + switch (chan->type) { + case IIO_CURRENT: + ret = ad5421_read(indio_dev, AD5421_REG_DAC_DATA); + if (ret < 0) + return ret; + *val = ret; + break; + case IIO_TEMP: + *val = 140000; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static const struct iio_info ad5421_info = { + .read_raw = ad5421_read_raw, + .write_raw = ad5421_write_raw, + .read_event_config = ad5421_read_event_config, + .write_event_config = ad5421_write_event_config, + .read_event_value = ad5421_read_event_value, + .driver_module = THIS_MODULE, +}; + +static int ad5421_probe(struct spi_device *spi) +{ + struct ad5421_platform_data *pdata = dev_get_platdata(&spi->dev); + struct iio_dev *indio_dev; + struct ad5421_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = "ad5421"; + indio_dev->info = &ad5421_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad5421_channels; + indio_dev->num_channels = ARRAY_SIZE(ad5421_channels); + + st->ctrl = AD5421_CTRL_WATCHDOG_DISABLE | + AD5421_CTRL_AUTO_FAULT_READBACK; + + if (pdata) { + st->current_range = pdata->current_range; + if (pdata->external_vref) + st->ctrl |= AD5421_CTRL_PWR_DOWN_INT_VREF; + } else { + st->current_range = AD5421_CURRENT_RANGE_4mA_20mA; + } + + /* write initial ctrl register value */ + ad5421_update_ctrl(indio_dev, 0, 0); + + if (spi->irq) { + ret = devm_request_threaded_irq(&spi->dev, spi->irq, + NULL, + ad5421_fault_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ad5421 fault", + indio_dev); + if (ret) + return ret; + } + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static struct spi_driver ad5421_driver = { + .driver = { + .name = "ad5421", + .owner = THIS_MODULE, + }, + .probe = ad5421_probe, +}; +module_spi_driver(ad5421_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices AD5421 DAC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:ad5421"); diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c new file mode 100644 index 000000000..46bb62a5c --- /dev/null +++ b/drivers/iio/dac/ad5446.c @@ -0,0 +1,623 @@ +/* + * AD5446 SPI DAC driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/list.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define MODE_PWRDWN_1k 0x1 +#define MODE_PWRDWN_100k 0x2 +#define MODE_PWRDWN_TRISTATE 0x3 + +/** + * struct ad5446_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip model specific constants, available modes etc + * @reg: supply regulator + * @vref_mv: actual reference voltage used + */ + +struct ad5446_state { + struct device *dev; + const struct ad5446_chip_info *chip_info; + struct regulator *reg; + unsigned short vref_mv; + unsigned cached_val; + unsigned pwr_down_mode; + unsigned pwr_down; +}; + +/** + * struct ad5446_chip_info - chip specific information + * @channel: channel spec for the DAC + * @int_vref_mv: AD5620/40/60: the internal reference voltage + * @write: chip specific helper function to write to the register + */ + +struct ad5446_chip_info { + struct iio_chan_spec channel; + u16 int_vref_mv; + int (*write)(struct ad5446_state *st, unsigned val); +}; + +static const char * const ad5446_powerdown_modes[] = { + "1kohm_to_gnd", "100kohm_to_gnd", "three_state" +}; + +static int ad5446_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5446_state *st = iio_priv(indio_dev); + + st->pwr_down_mode = mode + 1; + + return 0; +} + +static int ad5446_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5446_state *st = iio_priv(indio_dev); + + return st->pwr_down_mode - 1; +} + +static const struct iio_enum ad5446_powerdown_mode_enum = { + .items = ad5446_powerdown_modes, + .num_items = ARRAY_SIZE(ad5446_powerdown_modes), + .get = ad5446_get_powerdown_mode, + .set = ad5446_set_powerdown_mode, +}; + +static ssize_t ad5446_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad5446_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5446_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct ad5446_state *st = iio_priv(indio_dev); + unsigned int shift; + unsigned int val; + bool powerdown; + int ret; + + ret = strtobool(buf, &powerdown); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + st->pwr_down = powerdown; + + if (st->pwr_down) { + shift = chan->scan_type.realbits + chan->scan_type.shift; + val = st->pwr_down_mode << shift; + } else { + val = st->cached_val; + } + + ret = st->chip_info->write(st, val); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[] = { + { + .name = "powerdown", + .read = ad5446_read_dac_powerdown, + .write = ad5446_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad5446_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", &ad5446_powerdown_mode_enum), + { }, +}; + +#define _AD5446_CHANNEL(bits, storage, _shift, ext) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = (storage), \ + .shift = (_shift), \ + }, \ + .ext_info = (ext), \ +} + +#define AD5446_CHANNEL(bits, storage, shift) \ + _AD5446_CHANNEL(bits, storage, shift, NULL) + +#define AD5446_CHANNEL_POWERDOWN(bits, storage, shift) \ + _AD5446_CHANNEL(bits, storage, shift, ad5446_ext_info_powerdown) + +static int ad5446_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5446_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + *val = st->cached_val; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int ad5446_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5446_state *st = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + val <<= chan->scan_type.shift; + mutex_lock(&indio_dev->mlock); + st->cached_val = val; + if (!st->pwr_down) + ret = st->chip_info->write(st, val); + mutex_unlock(&indio_dev->mlock); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad5446_info = { + .read_raw = ad5446_read_raw, + .write_raw = ad5446_write_raw, + .driver_module = THIS_MODULE, +}; + +static int ad5446_probe(struct device *dev, const char *name, + const struct ad5446_chip_info *chip_info) +{ + struct ad5446_state *st; + struct iio_dev *indio_dev; + struct regulator *reg; + int ret, voltage_uv = 0; + + reg = devm_regulator_get(dev, "vcc"); + if (!IS_ERR(reg)) { + ret = regulator_enable(reg); + if (ret) + return ret; + + ret = regulator_get_voltage(reg); + if (ret < 0) + goto error_disable_reg; + + voltage_uv = ret; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_disable_reg; + } + st = iio_priv(indio_dev); + st->chip_info = chip_info; + + dev_set_drvdata(dev, indio_dev); + st->reg = reg; + st->dev = dev; + + /* Establish that the iio_dev is a child of the device */ + indio_dev->dev.parent = dev; + indio_dev->name = name; + indio_dev->info = &ad5446_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &st->chip_info->channel; + indio_dev->num_channels = 1; + + st->pwr_down_mode = MODE_PWRDWN_1k; + + if (st->chip_info->int_vref_mv) + st->vref_mv = st->chip_info->int_vref_mv; + else if (voltage_uv) + st->vref_mv = voltage_uv / 1000; + else + dev_warn(dev, "reference voltage unspecified\n"); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!IS_ERR(reg)) + regulator_disable(reg); + return ret; +} + +static int ad5446_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5446_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +#if IS_ENABLED(CONFIG_SPI_MASTER) + +static int ad5446_write(struct ad5446_state *st, unsigned val) +{ + struct spi_device *spi = to_spi_device(st->dev); + __be16 data = cpu_to_be16(val); + + return spi_write(spi, &data, sizeof(data)); +} + +static int ad5660_write(struct ad5446_state *st, unsigned val) +{ + struct spi_device *spi = to_spi_device(st->dev); + uint8_t data[3]; + + data[0] = (val >> 16) & 0xFF; + data[1] = (val >> 8) & 0xFF; + data[2] = val & 0xFF; + + return spi_write(spi, data, sizeof(data)); +} + +/** + * ad5446_supported_spi_device_ids: + * The AD5620/40/60 parts are available in different fixed internal reference + * voltage options. The actual part numbers may look differently + * (and a bit cryptic), however this style is used to make clear which + * parts are supported here. + */ +enum ad5446_supported_spi_device_ids { + ID_AD5300, + ID_AD5310, + ID_AD5320, + ID_AD5444, + ID_AD5446, + ID_AD5450, + ID_AD5451, + ID_AD5541A, + ID_AD5512A, + ID_AD5553, + ID_AD5601, + ID_AD5611, + ID_AD5621, + ID_AD5641, + ID_AD5620_2500, + ID_AD5620_1250, + ID_AD5640_2500, + ID_AD5640_1250, + ID_AD5660_2500, + ID_AD5660_1250, + ID_AD5662, +}; + +static const struct ad5446_chip_info ad5446_spi_chip_info[] = { + [ID_AD5300] = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), + .write = ad5446_write, + }, + [ID_AD5310] = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), + .write = ad5446_write, + }, + [ID_AD5320] = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), + .write = ad5446_write, + }, + [ID_AD5444] = { + .channel = AD5446_CHANNEL(12, 16, 2), + .write = ad5446_write, + }, + [ID_AD5446] = { + .channel = AD5446_CHANNEL(14, 16, 0), + .write = ad5446_write, + }, + [ID_AD5450] = { + .channel = AD5446_CHANNEL(8, 16, 6), + .write = ad5446_write, + }, + [ID_AD5451] = { + .channel = AD5446_CHANNEL(10, 16, 4), + .write = ad5446_write, + }, + [ID_AD5541A] = { + .channel = AD5446_CHANNEL(16, 16, 0), + .write = ad5446_write, + }, + [ID_AD5512A] = { + .channel = AD5446_CHANNEL(12, 16, 4), + .write = ad5446_write, + }, + [ID_AD5553] = { + .channel = AD5446_CHANNEL(14, 16, 0), + .write = ad5446_write, + }, + [ID_AD5601] = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 6), + .write = ad5446_write, + }, + [ID_AD5611] = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 4), + .write = ad5446_write, + }, + [ID_AD5621] = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .write = ad5446_write, + }, + [ID_AD5641] = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .write = ad5446_write, + }, + [ID_AD5620_2500] = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .int_vref_mv = 2500, + .write = ad5446_write, + }, + [ID_AD5620_1250] = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .int_vref_mv = 1250, + .write = ad5446_write, + }, + [ID_AD5640_2500] = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .int_vref_mv = 2500, + .write = ad5446_write, + }, + [ID_AD5640_1250] = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .int_vref_mv = 1250, + .write = ad5446_write, + }, + [ID_AD5660_2500] = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .int_vref_mv = 2500, + .write = ad5660_write, + }, + [ID_AD5660_1250] = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .int_vref_mv = 1250, + .write = ad5660_write, + }, + [ID_AD5662] = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .write = ad5660_write, + }, +}; + +static const struct spi_device_id ad5446_spi_ids[] = { + {"ad5300", ID_AD5300}, + {"ad5310", ID_AD5310}, + {"ad5320", ID_AD5320}, + {"ad5444", ID_AD5444}, + {"ad5446", ID_AD5446}, + {"ad5450", ID_AD5450}, + {"ad5451", ID_AD5451}, + {"ad5452", ID_AD5444}, /* ad5452 is compatible to the ad5444 */ + {"ad5453", ID_AD5446}, /* ad5453 is compatible to the ad5446 */ + {"ad5512a", ID_AD5512A}, + {"ad5541a", ID_AD5541A}, + {"ad5542a", ID_AD5541A}, /* ad5541a and ad5542a are compatible */ + {"ad5543", ID_AD5541A}, /* ad5541a and ad5543 are compatible */ + {"ad5553", ID_AD5553}, + {"ad5601", ID_AD5601}, + {"ad5611", ID_AD5611}, + {"ad5621", ID_AD5621}, + {"ad5641", ID_AD5641}, + {"ad5620-2500", ID_AD5620_2500}, /* AD5620/40/60: */ + {"ad5620-1250", ID_AD5620_1250}, /* part numbers may look differently */ + {"ad5640-2500", ID_AD5640_2500}, + {"ad5640-1250", ID_AD5640_1250}, + {"ad5660-2500", ID_AD5660_2500}, + {"ad5660-1250", ID_AD5660_1250}, + {"ad5662", ID_AD5662}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5446_spi_ids); + +static int ad5446_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + + return ad5446_probe(&spi->dev, id->name, + &ad5446_spi_chip_info[id->driver_data]); +} + +static int ad5446_spi_remove(struct spi_device *spi) +{ + return ad5446_remove(&spi->dev); +} + +static struct spi_driver ad5446_spi_driver = { + .driver = { + .name = "ad5446", + .owner = THIS_MODULE, + }, + .probe = ad5446_spi_probe, + .remove = ad5446_spi_remove, + .id_table = ad5446_spi_ids, +}; + +static int __init ad5446_spi_register_driver(void) +{ + return spi_register_driver(&ad5446_spi_driver); +} + +static void ad5446_spi_unregister_driver(void) +{ + spi_unregister_driver(&ad5446_spi_driver); +} + +#else + +static inline int ad5446_spi_register_driver(void) { return 0; } +static inline void ad5446_spi_unregister_driver(void) { } + +#endif + +#if IS_ENABLED(CONFIG_I2C) + +static int ad5622_write(struct ad5446_state *st, unsigned val) +{ + struct i2c_client *client = to_i2c_client(st->dev); + __be16 data = cpu_to_be16(val); + + return i2c_master_send(client, (char *)&data, sizeof(data)); +} + +/** + * ad5446_supported_i2c_device_ids: + * The AD5620/40/60 parts are available in different fixed internal reference + * voltage options. The actual part numbers may look differently + * (and a bit cryptic), however this style is used to make clear which + * parts are supported here. + */ +enum ad5446_supported_i2c_device_ids { + ID_AD5602, + ID_AD5612, + ID_AD5622, +}; + +static const struct ad5446_chip_info ad5446_i2c_chip_info[] = { + [ID_AD5602] = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), + .write = ad5622_write, + }, + [ID_AD5612] = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), + .write = ad5622_write, + }, + [ID_AD5622] = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), + .write = ad5622_write, + }, +}; + +static int ad5446_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + return ad5446_probe(&i2c->dev, id->name, + &ad5446_i2c_chip_info[id->driver_data]); +} + +static int ad5446_i2c_remove(struct i2c_client *i2c) +{ + return ad5446_remove(&i2c->dev); +} + +static const struct i2c_device_id ad5446_i2c_ids[] = { + {"ad5301", ID_AD5602}, + {"ad5311", ID_AD5612}, + {"ad5321", ID_AD5622}, + {"ad5602", ID_AD5602}, + {"ad5612", ID_AD5612}, + {"ad5622", ID_AD5622}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ad5446_i2c_ids); + +static struct i2c_driver ad5446_i2c_driver = { + .driver = { + .name = "ad5446", + .owner = THIS_MODULE, + }, + .probe = ad5446_i2c_probe, + .remove = ad5446_i2c_remove, + .id_table = ad5446_i2c_ids, +}; + +static int __init ad5446_i2c_register_driver(void) +{ + return i2c_add_driver(&ad5446_i2c_driver); +} + +static void __exit ad5446_i2c_unregister_driver(void) +{ + i2c_del_driver(&ad5446_i2c_driver); +} + +#else + +static inline int ad5446_i2c_register_driver(void) { return 0; } +static inline void ad5446_i2c_unregister_driver(void) { } + +#endif + +static int __init ad5446_init(void) +{ + int ret; + + ret = ad5446_spi_register_driver(); + if (ret) + return ret; + + ret = ad5446_i2c_register_driver(); + if (ret) { + ad5446_spi_unregister_driver(); + return ret; + } + + return 0; +} +module_init(ad5446_init); + +static void __exit ad5446_exit(void) +{ + ad5446_i2c_unregister_driver(); + ad5446_spi_unregister_driver(); +} +module_exit(ad5446_exit); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD5444/AD5446 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5449.c b/drivers/iio/dac/ad5449.c new file mode 100644 index 000000000..64d7256cb --- /dev/null +++ b/drivers/iio/dac/ad5449.c @@ -0,0 +1,369 @@ +/* + * AD5415, AD5426, AD5429, AD5432, AD5439, AD5443, AD5449 Digital to Analog + * Converter driver. + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/regulator/consumer.h> +#include <asm/unaligned.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include <linux/platform_data/ad5449.h> + +#define AD5449_MAX_CHANNELS 2 +#define AD5449_MAX_VREFS 2 + +#define AD5449_CMD_NOOP 0x0 +#define AD5449_CMD_LOAD_AND_UPDATE(x) (0x1 + (x) * 3) +#define AD5449_CMD_READ(x) (0x2 + (x) * 3) +#define AD5449_CMD_LOAD(x) (0x3 + (x) * 3) +#define AD5449_CMD_CTRL 13 + +#define AD5449_CTRL_SDO_OFFSET 10 +#define AD5449_CTRL_DAISY_CHAIN BIT(9) +#define AD5449_CTRL_HCLR_TO_MIDSCALE BIT(8) +#define AD5449_CTRL_SAMPLE_RISING BIT(7) + +/** + * struct ad5449_chip_info - chip specific information + * @channels: Channel specification + * @num_channels: Number of channels + * @has_ctrl: Chip has a control register + */ +struct ad5449_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + bool has_ctrl; +}; + +/** + * struct ad5449 - driver instance specific data + * @spi: the SPI device for this driver instance + * @chip_info: chip model specific constants, available modes etc + * @vref_reg: vref supply regulators + * @has_sdo: whether the SDO line is connected + * @dac_cache: Cache for the DAC values + * @data: spi transfer buffers + */ +struct ad5449 { + struct spi_device *spi; + const struct ad5449_chip_info *chip_info; + struct regulator_bulk_data vref_reg[AD5449_MAX_VREFS]; + + bool has_sdo; + uint16_t dac_cache[AD5449_MAX_CHANNELS]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __be16 data[2] ____cacheline_aligned; +}; + +enum ad5449_type { + ID_AD5426, + ID_AD5429, + ID_AD5432, + ID_AD5439, + ID_AD5443, + ID_AD5449, +}; + +static int ad5449_write(struct iio_dev *indio_dev, unsigned int addr, + unsigned int val) +{ + struct ad5449 *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&indio_dev->mlock); + st->data[0] = cpu_to_be16((addr << 12) | val); + ret = spi_write(st->spi, st->data, 2); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5449_read(struct iio_dev *indio_dev, unsigned int addr, + unsigned int *val) +{ + struct ad5449 *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0], + .len = 2, + .cs_change = 1, + }, { + .tx_buf = &st->data[1], + .rx_buf = &st->data[1], + .len = 2, + }, + }; + + mutex_lock(&indio_dev->mlock); + st->data[0] = cpu_to_be16(addr << 12); + st->data[1] = cpu_to_be16(AD5449_CMD_NOOP); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + goto out_unlock; + + *val = be16_to_cpu(st->data[1]); + +out_unlock: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static int ad5449_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad5449 *st = iio_priv(indio_dev); + struct regulator_bulk_data *reg; + int scale_uv; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (st->has_sdo) { + ret = ad5449_read(indio_dev, + AD5449_CMD_READ(chan->address), val); + if (ret) + return ret; + *val &= 0xfff; + } else { + *val = st->dac_cache[chan->address]; + } + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + reg = &st->vref_reg[chan->channel]; + scale_uv = regulator_get_voltage(reg->consumer); + if (scale_uv < 0) + return scale_uv; + + *val = scale_uv / 1000; + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + + return -EINVAL; +} + +static int ad5449_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + struct ad5449 *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (val < 0 || val >= (1 << chan->scan_type.realbits)) + return -EINVAL; + + ret = ad5449_write(indio_dev, + AD5449_CMD_LOAD_AND_UPDATE(chan->address), + val << chan->scan_type.shift); + if (ret == 0) + st->dac_cache[chan->address] = val; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad5449_info = { + .read_raw = ad5449_read_raw, + .write_raw = ad5449_write_raw, + .driver_module = THIS_MODULE, +}; + +#define AD5449_CHANNEL(chan, bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = (chan), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 12 - (bits), \ + }, \ +} + +#define DECLARE_AD5449_CHANNELS(name, bits) \ +const struct iio_chan_spec name[] = { \ + AD5449_CHANNEL(0, bits), \ + AD5449_CHANNEL(1, bits), \ +} + +static DECLARE_AD5449_CHANNELS(ad5429_channels, 8); +static DECLARE_AD5449_CHANNELS(ad5439_channels, 10); +static DECLARE_AD5449_CHANNELS(ad5449_channels, 12); + +static const struct ad5449_chip_info ad5449_chip_info[] = { + [ID_AD5426] = { + .channels = ad5429_channels, + .num_channels = 1, + .has_ctrl = false, + }, + [ID_AD5429] = { + .channels = ad5429_channels, + .num_channels = 2, + .has_ctrl = true, + }, + [ID_AD5432] = { + .channels = ad5439_channels, + .num_channels = 1, + .has_ctrl = false, + }, + [ID_AD5439] = { + .channels = ad5439_channels, + .num_channels = 2, + .has_ctrl = true, + }, + [ID_AD5443] = { + .channels = ad5449_channels, + .num_channels = 1, + .has_ctrl = false, + }, + [ID_AD5449] = { + .channels = ad5449_channels, + .num_channels = 2, + .has_ctrl = true, + }, +}; + +static const char *ad5449_vref_name(struct ad5449 *st, int n) +{ + if (st->chip_info->num_channels == 1) + return "VREF"; + + if (n == 0) + return "VREFA"; + else + return "VREFB"; +} + +static int ad5449_spi_probe(struct spi_device *spi) +{ + struct ad5449_platform_data *pdata = spi->dev.platform_data; + const struct spi_device_id *id = spi_get_device_id(spi); + struct iio_dev *indio_dev; + struct ad5449 *st; + unsigned int i; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->chip_info = &ad5449_chip_info[id->driver_data]; + st->spi = spi; + + for (i = 0; i < st->chip_info->num_channels; ++i) + st->vref_reg[i].supply = ad5449_vref_name(st, i); + + ret = devm_regulator_bulk_get(&spi->dev, st->chip_info->num_channels, + st->vref_reg); + if (ret) + return ret; + + ret = regulator_bulk_enable(st->chip_info->num_channels, st->vref_reg); + if (ret) + return ret; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = id->name; + indio_dev->info = &ad5449_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + if (st->chip_info->has_ctrl) { + unsigned int ctrl = 0x00; + if (pdata) { + if (pdata->hardware_clear_to_midscale) + ctrl |= AD5449_CTRL_HCLR_TO_MIDSCALE; + ctrl |= pdata->sdo_mode << AD5449_CTRL_SDO_OFFSET; + st->has_sdo = pdata->sdo_mode != AD5449_SDO_DISABLED; + } else { + st->has_sdo = true; + } + ad5449_write(indio_dev, AD5449_CMD_CTRL, ctrl); + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + regulator_bulk_disable(st->chip_info->num_channels, st->vref_reg); + + return ret; +} + +static int ad5449_spi_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5449 *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + regulator_bulk_disable(st->chip_info->num_channels, st->vref_reg); + + return 0; +} + +static const struct spi_device_id ad5449_spi_ids[] = { + { "ad5415", ID_AD5449 }, + { "ad5426", ID_AD5426 }, + { "ad5429", ID_AD5429 }, + { "ad5432", ID_AD5432 }, + { "ad5439", ID_AD5439 }, + { "ad5443", ID_AD5443 }, + { "ad5449", ID_AD5449 }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5449_spi_ids); + +static struct spi_driver ad5449_spi_driver = { + .driver = { + .name = "ad5449", + .owner = THIS_MODULE, + }, + .probe = ad5449_spi_probe, + .remove = ad5449_spi_remove, + .id_table = ad5449_spi_ids, +}; +module_spi_driver(ad5449_spi_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices AD5449 and similar DACs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5504.c b/drivers/iio/dac/ad5504.c new file mode 100644 index 000000000..581ec141d --- /dev/null +++ b/drivers/iio/dac/ad5504.c @@ -0,0 +1,377 @@ +/* + * AD5504, AD5501 High Voltage Digital to Analog Converter + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/regulator/consumer.h> +#include <linux/module.h> +#include <linux/bitops.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/dac/ad5504.h> + +#define AD5504_RES_MASK GENMASK(11, 0) +#define AD5504_CMD_READ BIT(15) +#define AD5504_CMD_WRITE 0 +#define AD5504_ADDR(addr) ((addr) << 12) + +/* Registers */ +#define AD5504_ADDR_NOOP 0 +#define AD5504_ADDR_DAC(x) ((x) + 1) +#define AD5504_ADDR_ALL_DAC 5 +#define AD5504_ADDR_CTRL 7 + +/* Control Register */ +#define AD5504_DAC_PWR(ch) ((ch) << 2) +#define AD5504_DAC_PWRDWN_MODE(mode) ((mode) << 6) +#define AD5504_DAC_PWRDN_20K 0 +#define AD5504_DAC_PWRDN_3STATE 1 + +/** + * struct ad5446_state - driver instance specific data + * @spi: spi_device + * @reg: supply regulator + * @vref_mv: actual reference voltage used + * @pwr_down_mask power down mask + * @pwr_down_mode current power down mode + * @data: transfer buffer + */ +struct ad5504_state { + struct spi_device *spi; + struct regulator *reg; + unsigned short vref_mv; + unsigned pwr_down_mask; + unsigned pwr_down_mode; + + __be16 data[2] ____cacheline_aligned; +}; + +/** + * ad5504_supported_device_ids: + */ + +enum ad5504_supported_device_ids { + ID_AD5504, + ID_AD5501, +}; + +static int ad5504_spi_write(struct ad5504_state *st, u8 addr, u16 val) +{ + st->data[0] = cpu_to_be16(AD5504_CMD_WRITE | AD5504_ADDR(addr) | + (val & AD5504_RES_MASK)); + + return spi_write(st->spi, &st->data[0], 2); +} + +static int ad5504_spi_read(struct ad5504_state *st, u8 addr) +{ + int ret; + struct spi_transfer t = { + .tx_buf = &st->data[0], + .rx_buf = &st->data[1], + .len = 2, + }; + + st->data[0] = cpu_to_be16(AD5504_CMD_READ | AD5504_ADDR(addr)); + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret < 0) + return ret; + + return be16_to_cpu(st->data[1]) & AD5504_RES_MASK; +} + +static int ad5504_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5504_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5504_spi_read(st, chan->address); + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int ad5504_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5504_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + return ad5504_spi_write(st, chan->address, val); + default: + return -EINVAL; + } +} + +static const char * const ad5504_powerdown_modes[] = { + "20kohm_to_gnd", + "three_state", +}; + +static int ad5504_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5504_state *st = iio_priv(indio_dev); + + return st->pwr_down_mode; +} + +static int ad5504_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5504_state *st = iio_priv(indio_dev); + + st->pwr_down_mode = mode; + + return 0; +} + +static const struct iio_enum ad5504_powerdown_mode_enum = { + .items = ad5504_powerdown_modes, + .num_items = ARRAY_SIZE(ad5504_powerdown_modes), + .get = ad5504_get_powerdown_mode, + .set = ad5504_set_powerdown_mode, +}; + +static ssize_t ad5504_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5504_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", + !(st->pwr_down_mask & (1 << chan->channel))); +} + +static ssize_t ad5504_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + bool pwr_down; + int ret; + struct ad5504_state *st = iio_priv(indio_dev); + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + if (pwr_down) + st->pwr_down_mask |= (1 << chan->channel); + else + st->pwr_down_mask &= ~(1 << chan->channel); + + ret = ad5504_spi_write(st, AD5504_ADDR_CTRL, + AD5504_DAC_PWRDWN_MODE(st->pwr_down_mode) | + AD5504_DAC_PWR(st->pwr_down_mask)); + + /* writes to the CTRL register must be followed by a NOOP */ + ad5504_spi_write(st, AD5504_ADDR_NOOP, 0); + + return ret ? ret : len; +} + +static IIO_CONST_ATTR(temp0_thresh_rising_value, "110000"); +static IIO_CONST_ATTR(temp0_thresh_rising_en, "1"); + +static struct attribute *ad5504_ev_attributes[] = { + &iio_const_attr_temp0_thresh_rising_value.dev_attr.attr, + &iio_const_attr_temp0_thresh_rising_en.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad5504_ev_attribute_group = { + .attrs = ad5504_ev_attributes, + .name = "events", +}; + +static irqreturn_t ad5504_event_handler(int irq, void *private) +{ + iio_push_event(private, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + + return IRQ_HANDLED; +} + +static const struct iio_info ad5504_info = { + .write_raw = ad5504_write_raw, + .read_raw = ad5504_read_raw, + .event_attrs = &ad5504_ev_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec_ext_info ad5504_ext_info[] = { + { + .name = "powerdown", + .read = ad5504_read_dac_powerdown, + .write = ad5504_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad5504_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", &ad5504_powerdown_mode_enum), + { }, +}; + +#define AD5504_CHANNEL(_chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = AD5504_ADDR_DAC(_chan), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ + .ext_info = ad5504_ext_info, \ +} + +static const struct iio_chan_spec ad5504_channels[] = { + AD5504_CHANNEL(0), + AD5504_CHANNEL(1), + AD5504_CHANNEL(2), + AD5504_CHANNEL(3), +}; + +static int ad5504_probe(struct spi_device *spi) +{ + struct ad5504_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev; + struct ad5504_state *st; + struct regulator *reg; + int ret, voltage_uv = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(reg)) { + ret = regulator_enable(reg); + if (ret) + return ret; + + ret = regulator_get_voltage(reg); + if (ret < 0) + goto error_disable_reg; + + voltage_uv = ret; + } + + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + if (voltage_uv) + st->vref_mv = voltage_uv / 1000; + else if (pdata) + st->vref_mv = pdata->vref_mv; + else + dev_warn(&spi->dev, "reference voltage unspecified\n"); + + st->reg = reg; + st->spi = spi; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(st->spi)->name; + indio_dev->info = &ad5504_info; + if (spi_get_device_id(st->spi)->driver_data == ID_AD5501) + indio_dev->num_channels = 1; + else + indio_dev->num_channels = 4; + indio_dev->channels = ad5504_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (spi->irq) { + ret = devm_request_threaded_irq(&spi->dev, spi->irq, + NULL, + &ad5504_event_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + spi_get_device_id(st->spi)->name, + indio_dev); + if (ret) + goto error_disable_reg; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!IS_ERR(reg)) + regulator_disable(reg); + + return ret; +} + +static int ad5504_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5504_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad5504_id[] = { + {"ad5504", ID_AD5504}, + {"ad5501", ID_AD5501}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5504_id); + +static struct spi_driver ad5504_driver = { + .driver = { + .name = "ad5504", + .owner = THIS_MODULE, + }, + .probe = ad5504_probe, + .remove = ad5504_remove, + .id_table = ad5504_id, +}; +module_spi_driver(ad5504_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD5501/AD5501 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5624r.h b/drivers/iio/dac/ad5624r.h new file mode 100644 index 000000000..5dca3028c --- /dev/null +++ b/drivers/iio/dac/ad5624r.h @@ -0,0 +1,79 @@ +/* + * AD5624R SPI DAC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef SPI_AD5624R_H_ +#define SPI_AD5624R_H_ + +#define AD5624R_DAC_CHANNELS 4 + +#define AD5624R_ADDR_DAC0 0x0 +#define AD5624R_ADDR_DAC1 0x1 +#define AD5624R_ADDR_DAC2 0x2 +#define AD5624R_ADDR_DAC3 0x3 +#define AD5624R_ADDR_ALL_DAC 0x7 + +#define AD5624R_CMD_WRITE_INPUT_N 0x0 +#define AD5624R_CMD_UPDATE_DAC_N 0x1 +#define AD5624R_CMD_WRITE_INPUT_N_UPDATE_ALL 0x2 +#define AD5624R_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define AD5624R_CMD_POWERDOWN_DAC 0x4 +#define AD5624R_CMD_RESET 0x5 +#define AD5624R_CMD_LDAC_SETUP 0x6 +#define AD5624R_CMD_INTERNAL_REFER_SETUP 0x7 + +#define AD5624R_LDAC_PWRDN_NONE 0x0 +#define AD5624R_LDAC_PWRDN_1K 0x1 +#define AD5624R_LDAC_PWRDN_100K 0x2 +#define AD5624R_LDAC_PWRDN_3STATE 0x3 + +/** + * struct ad5624r_chip_info - chip specific information + * @channels: channel spec for the DAC + * @int_vref_mv: AD5620/40/60: the internal reference voltage + */ + +struct ad5624r_chip_info { + const struct iio_chan_spec *channels; + u16 int_vref_mv; +}; + +/** + * struct ad5446_state - driver instance specific data + * @indio_dev: the industrial I/O device + * @us: spi_device + * @chip_info: chip model specific constants, available modes etc + * @reg: supply regulator + * @vref_mv: actual reference voltage used + * @pwr_down_mask power down mask + * @pwr_down_mode current power down mode + */ + +struct ad5624r_state { + struct spi_device *us; + const struct ad5624r_chip_info *chip_info; + struct regulator *reg; + unsigned short vref_mv; + unsigned pwr_down_mask; + unsigned pwr_down_mode; +}; + +/** + * ad5624r_supported_device_ids: + * The AD5624/44/64 parts are available in different + * fixed internal reference voltage options. + */ + +enum ad5624r_supported_device_ids { + ID_AD5624R3, + ID_AD5644R3, + ID_AD5664R3, + ID_AD5624R5, + ID_AD5644R5, + ID_AD5664R5, +}; + +#endif /* SPI_AD5624R_H_ */ diff --git a/drivers/iio/dac/ad5624r_spi.c b/drivers/iio/dac/ad5624r_spi.c new file mode 100644 index 000000000..e98428df0 --- /dev/null +++ b/drivers/iio/dac/ad5624r_spi.c @@ -0,0 +1,319 @@ +/* + * AD5624R, AD5644R, AD5664R Digital to analog convertors spi driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/regulator/consumer.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include "ad5624r.h" + +static int ad5624r_spi_write(struct spi_device *spi, + u8 cmd, u8 addr, u16 val, u8 shift) +{ + u32 data; + u8 msg[3]; + + /* + * The input shift register is 24 bits wide. The first two bits are + * don't care bits. The next three are the command bits, C2 to C0, + * followed by the 3-bit DAC address, A2 to A0, and then the + * 16-, 14-, 12-bit data-word. The data-word comprises the 16-, + * 14-, 12-bit input code followed by 0, 2, or 4 don't care bits, + * for the AD5664R, AD5644R, and AD5624R, respectively. + */ + data = (0 << 22) | (cmd << 19) | (addr << 16) | (val << shift); + msg[0] = data >> 16; + msg[1] = data >> 8; + msg[2] = data; + + return spi_write(spi, msg, 3); +} + +static int ad5624r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int ad5624r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + return ad5624r_spi_write(st->us, + AD5624R_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, val, + chan->scan_type.shift); + default: + return -EINVAL; + } +} + +static const char * const ad5624r_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "three_state" +}; + +static int ad5624r_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + + return st->pwr_down_mode; +} + +static int ad5624r_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + + st->pwr_down_mode = mode; + + return 0; +} + +static const struct iio_enum ad5624r_powerdown_mode_enum = { + .items = ad5624r_powerdown_modes, + .num_items = ARRAY_SIZE(ad5624r_powerdown_modes), + .get = ad5624r_get_powerdown_mode, + .set = ad5624r_set_powerdown_mode, +}; + +static ssize_t ad5624r_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", + !!(st->pwr_down_mask & (1 << chan->channel))); +} + +static ssize_t ad5624r_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + bool pwr_down; + int ret; + struct ad5624r_state *st = iio_priv(indio_dev); + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + if (pwr_down) + st->pwr_down_mask |= (1 << chan->channel); + else + st->pwr_down_mask &= ~(1 << chan->channel); + + ret = ad5624r_spi_write(st->us, AD5624R_CMD_POWERDOWN_DAC, 0, + (st->pwr_down_mode << 4) | + st->pwr_down_mask, 16); + + return ret ? ret : len; +} + +static const struct iio_info ad5624r_info = { + .write_raw = ad5624r_write_raw, + .read_raw = ad5624r_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec_ext_info ad5624r_ext_info[] = { + { + .name = "powerdown", + .read = ad5624r_read_dac_powerdown, + .write = ad5624r_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad5624r_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", &ad5624r_powerdown_mode_enum), + { }, +}; + +#define AD5624R_CHANNEL(_chan, _bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = (_chan), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + }, \ + .ext_info = ad5624r_ext_info, \ +} + +#define DECLARE_AD5624R_CHANNELS(_name, _bits) \ + const struct iio_chan_spec _name##_channels[] = { \ + AD5624R_CHANNEL(0, _bits), \ + AD5624R_CHANNEL(1, _bits), \ + AD5624R_CHANNEL(2, _bits), \ + AD5624R_CHANNEL(3, _bits), \ +} + +static DECLARE_AD5624R_CHANNELS(ad5624r, 12); +static DECLARE_AD5624R_CHANNELS(ad5644r, 14); +static DECLARE_AD5624R_CHANNELS(ad5664r, 16); + +static const struct ad5624r_chip_info ad5624r_chip_info_tbl[] = { + [ID_AD5624R3] = { + .channels = ad5624r_channels, + .int_vref_mv = 1250, + }, + [ID_AD5624R5] = { + .channels = ad5624r_channels, + .int_vref_mv = 2500, + }, + [ID_AD5644R3] = { + .channels = ad5644r_channels, + .int_vref_mv = 1250, + }, + [ID_AD5644R5] = { + .channels = ad5644r_channels, + .int_vref_mv = 2500, + }, + [ID_AD5664R3] = { + .channels = ad5664r_channels, + .int_vref_mv = 1250, + }, + [ID_AD5664R5] = { + .channels = ad5664r_channels, + .int_vref_mv = 2500, + }, +}; + +static int ad5624r_probe(struct spi_device *spi) +{ + struct ad5624r_state *st; + struct iio_dev *indio_dev; + int ret, voltage_uv = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + st->reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = regulator_get_voltage(st->reg); + if (ret < 0) + goto error_disable_reg; + + voltage_uv = ret; + } + + spi_set_drvdata(spi, indio_dev); + st->chip_info = + &ad5624r_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + if (voltage_uv) + st->vref_mv = voltage_uv / 1000; + else + st->vref_mv = st->chip_info->int_vref_mv; + + st->us = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5624r_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = AD5624R_DAC_CHANNELS; + + ret = ad5624r_spi_write(spi, AD5624R_CMD_INTERNAL_REFER_SETUP, 0, + !!voltage_uv, 16); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ad5624r_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5624r_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad5624r_id[] = { + {"ad5624r3", ID_AD5624R3}, + {"ad5644r3", ID_AD5644R3}, + {"ad5664r3", ID_AD5664R3}, + {"ad5624r5", ID_AD5624R5}, + {"ad5644r5", ID_AD5644R5}, + {"ad5664r5", ID_AD5664R5}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5624r_id); + +static struct spi_driver ad5624r_driver = { + .driver = { + .name = "ad5624r", + .owner = THIS_MODULE, + }, + .probe = ad5624r_probe, + .remove = ad5624r_remove, + .id_table = ad5624r_id, +}; +module_spi_driver(ad5624r_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices AD5624/44/64R DAC spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c new file mode 100644 index 000000000..15c73e202 --- /dev/null +++ b/drivers/iio/dac/ad5686.c @@ -0,0 +1,408 @@ +/* + * AD5686R, AD5685R, AD5684R Digital to analog converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define AD5686_DAC_CHANNELS 4 + +#define AD5686_ADDR(x) ((x) << 16) +#define AD5686_CMD(x) ((x) << 20) + +#define AD5686_ADDR_DAC(chan) (0x1 << (chan)) +#define AD5686_ADDR_ALL_DAC 0xF + +#define AD5686_CMD_NOOP 0x0 +#define AD5686_CMD_WRITE_INPUT_N 0x1 +#define AD5686_CMD_UPDATE_DAC_N 0x2 +#define AD5686_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define AD5686_CMD_POWERDOWN_DAC 0x4 +#define AD5686_CMD_LDAC_MASK 0x5 +#define AD5686_CMD_RESET 0x6 +#define AD5686_CMD_INTERNAL_REFER_SETUP 0x7 +#define AD5686_CMD_DAISY_CHAIN_ENABLE 0x8 +#define AD5686_CMD_READBACK_ENABLE 0x9 + +#define AD5686_LDAC_PWRDN_NONE 0x0 +#define AD5686_LDAC_PWRDN_1K 0x1 +#define AD5686_LDAC_PWRDN_100K 0x2 +#define AD5686_LDAC_PWRDN_3STATE 0x3 + +/** + * struct ad5686_chip_info - chip specific information + * @int_vref_mv: AD5620/40/60: the internal reference voltage + * @channel: channel specification +*/ + +struct ad5686_chip_info { + u16 int_vref_mv; + struct iio_chan_spec channel[AD5686_DAC_CHANNELS]; +}; + +/** + * struct ad5446_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip model specific constants, available modes etc + * @reg: supply regulator + * @vref_mv: actual reference voltage used + * @pwr_down_mask: power down mask + * @pwr_down_mode: current power down mode + * @data: spi transfer buffers + */ + +struct ad5686_state { + struct spi_device *spi; + const struct ad5686_chip_info *chip_info; + struct regulator *reg; + unsigned short vref_mv; + unsigned pwr_down_mask; + unsigned pwr_down_mode; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + + union { + __be32 d32; + u8 d8[4]; + } data[3] ____cacheline_aligned; +}; + +/** + * ad5686_supported_device_ids: + */ + +enum ad5686_supported_device_ids { + ID_AD5684, + ID_AD5685, + ID_AD5686, +}; +static int ad5686_spi_write(struct ad5686_state *st, + u8 cmd, u8 addr, u16 val, u8 shift) +{ + val <<= shift; + + st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) | + AD5686_ADDR(addr) | + val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5686_spi_read(struct ad5686_state *st, u8 addr) +{ + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .tx_buf = &st->data[1].d8[1], + .rx_buf = &st->data[2].d8[1], + .len = 3, + }, + }; + int ret; + + st->data[0].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_READBACK_ENABLE) | + AD5686_ADDR(addr)); + st->data[1].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_NOOP)); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + return be32_to_cpu(st->data[2].d32); +} + +static const char * const ad5686_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "three_state" +}; + +static int ad5686_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5686_state *st = iio_priv(indio_dev); + + return ((st->pwr_down_mode >> (chan->channel * 2)) & 0x3) - 1; +} + +static int ad5686_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5686_state *st = iio_priv(indio_dev); + + st->pwr_down_mode &= ~(0x3 << (chan->channel * 2)); + st->pwr_down_mode |= ((mode + 1) << (chan->channel * 2)); + + return 0; +} + +static const struct iio_enum ad5686_powerdown_mode_enum = { + .items = ad5686_powerdown_modes, + .num_items = ARRAY_SIZE(ad5686_powerdown_modes), + .get = ad5686_get_powerdown_mode, + .set = ad5686_set_powerdown_mode, +}; + +static ssize_t ad5686_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5686_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", !!(st->pwr_down_mask & + (0x3 << (chan->channel * 2)))); +} + +static ssize_t ad5686_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + bool readin; + int ret; + struct ad5686_state *st = iio_priv(indio_dev); + + ret = strtobool(buf, &readin); + if (ret) + return ret; + + if (readin) + st->pwr_down_mask |= (0x3 << (chan->channel * 2)); + else + st->pwr_down_mask &= ~(0x3 << (chan->channel * 2)); + + ret = ad5686_spi_write(st, AD5686_CMD_POWERDOWN_DAC, 0, + st->pwr_down_mask & st->pwr_down_mode, 0); + + return ret ? ret : len; +} + +static int ad5686_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5686_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + ret = ad5686_spi_read(st, chan->address); + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int ad5686_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5686_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val > (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = ad5686_spi_write(st, + AD5686_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, + val, + chan->scan_type.shift); + mutex_unlock(&indio_dev->mlock); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad5686_info = { + .read_raw = ad5686_read_raw, + .write_raw = ad5686_write_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec_ext_info ad5686_ext_info[] = { + { + .name = "powerdown", + .read = ad5686_read_dac_powerdown, + .write = ad5686_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad5686_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", &ad5686_powerdown_mode_enum), + { }, +}; + +#define AD5868_CHANNEL(chan, bits, _shift) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\ + .address = AD5686_ADDR_DAC(chan), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = (_shift), \ + }, \ + .ext_info = ad5686_ext_info, \ +} + +static const struct ad5686_chip_info ad5686_chip_info_tbl[] = { + [ID_AD5684] = { + .channel[0] = AD5868_CHANNEL(0, 12, 4), + .channel[1] = AD5868_CHANNEL(1, 12, 4), + .channel[2] = AD5868_CHANNEL(2, 12, 4), + .channel[3] = AD5868_CHANNEL(3, 12, 4), + .int_vref_mv = 2500, + }, + [ID_AD5685] = { + .channel[0] = AD5868_CHANNEL(0, 14, 2), + .channel[1] = AD5868_CHANNEL(1, 14, 2), + .channel[2] = AD5868_CHANNEL(2, 14, 2), + .channel[3] = AD5868_CHANNEL(3, 14, 2), + .int_vref_mv = 2500, + }, + [ID_AD5686] = { + .channel[0] = AD5868_CHANNEL(0, 16, 0), + .channel[1] = AD5868_CHANNEL(1, 16, 0), + .channel[2] = AD5868_CHANNEL(2, 16, 0), + .channel[3] = AD5868_CHANNEL(3, 16, 0), + .int_vref_mv = 2500, + }, +}; + + +static int ad5686_probe(struct spi_device *spi) +{ + struct ad5686_state *st; + struct iio_dev *indio_dev; + int ret, voltage_uv = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->reg = devm_regulator_get_optional(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = regulator_get_voltage(st->reg); + if (ret < 0) + goto error_disable_reg; + + voltage_uv = ret; + } + + st->chip_info = + &ad5686_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + if (voltage_uv) + st->vref_mv = voltage_uv / 1000; + else + st->vref_mv = st->chip_info->int_vref_mv; + + st->spi = spi; + + /* Set all the power down mode for all channels to 1K pulldown */ + st->pwr_down_mode = 0x55; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5686_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channel; + indio_dev->num_channels = AD5686_DAC_CHANNELS; + + ret = ad5686_spi_write(st, AD5686_CMD_INTERNAL_REFER_SETUP, 0, + !!voltage_uv, 0); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + return ret; +} + +static int ad5686_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5686_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad5686_id[] = { + {"ad5684", ID_AD5684}, + {"ad5685", ID_AD5685}, + {"ad5686", ID_AD5686}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5686_id); + +static struct spi_driver ad5686_driver = { + .driver = { + .name = "ad5686", + .owner = THIS_MODULE, + }, + .probe = ad5686_probe, + .remove = ad5686_remove, + .id_table = ad5686_id, +}; +module_spi_driver(ad5686_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD5686/85/84 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5755.c b/drivers/iio/dac/ad5755.c new file mode 100644 index 000000000..a7c851f62 --- /dev/null +++ b/drivers/iio/dac/ad5755.c @@ -0,0 +1,622 @@ +/* + * AD5755, AD5755-1, AD5757, AD5735, AD5737 Digital to analog converters driver + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/platform_data/ad5755.h> + +#define AD5755_NUM_CHANNELS 4 + +#define AD5755_ADDR(x) ((x) << 16) + +#define AD5755_WRITE_REG_DATA(chan) (chan) +#define AD5755_WRITE_REG_GAIN(chan) (0x08 | (chan)) +#define AD5755_WRITE_REG_OFFSET(chan) (0x10 | (chan)) +#define AD5755_WRITE_REG_CTRL(chan) (0x1c | (chan)) + +#define AD5755_READ_REG_DATA(chan) (chan) +#define AD5755_READ_REG_CTRL(chan) (0x4 | (chan)) +#define AD5755_READ_REG_GAIN(chan) (0x8 | (chan)) +#define AD5755_READ_REG_OFFSET(chan) (0xc | (chan)) +#define AD5755_READ_REG_CLEAR(chan) (0x10 | (chan)) +#define AD5755_READ_REG_SLEW(chan) (0x14 | (chan)) +#define AD5755_READ_REG_STATUS 0x18 +#define AD5755_READ_REG_MAIN 0x19 +#define AD5755_READ_REG_DC_DC 0x1a + +#define AD5755_CTRL_REG_SLEW 0x0 +#define AD5755_CTRL_REG_MAIN 0x1 +#define AD5755_CTRL_REG_DAC 0x2 +#define AD5755_CTRL_REG_DC_DC 0x3 +#define AD5755_CTRL_REG_SW 0x4 + +#define AD5755_READ_FLAG 0x800000 + +#define AD5755_NOOP 0x1CE000 + +#define AD5755_DAC_INT_EN BIT(8) +#define AD5755_DAC_CLR_EN BIT(7) +#define AD5755_DAC_OUT_EN BIT(6) +#define AD5755_DAC_INT_CURRENT_SENSE_RESISTOR BIT(5) +#define AD5755_DAC_DC_DC_EN BIT(4) +#define AD5755_DAC_VOLTAGE_OVERRANGE_EN BIT(3) + +#define AD5755_DC_DC_MAXV 0 +#define AD5755_DC_DC_FREQ_SHIFT 2 +#define AD5755_DC_DC_PHASE_SHIFT 4 +#define AD5755_EXT_DC_DC_COMP_RES BIT(6) + +#define AD5755_SLEW_STEP_SIZE_SHIFT 0 +#define AD5755_SLEW_RATE_SHIFT 3 +#define AD5755_SLEW_ENABLE BIT(12) + +/** + * struct ad5755_chip_info - chip specific information + * @channel_template: channel specification + * @calib_shift: shift for the calibration data registers + * @has_voltage_out: whether the chip has voltage outputs + */ +struct ad5755_chip_info { + const struct iio_chan_spec channel_template; + unsigned int calib_shift; + bool has_voltage_out; +}; + +/** + * struct ad5755_state - driver instance specific data + * @spi: spi device the driver is attached to + * @chip_info: chip model specific constants, available modes etc + * @pwr_down: bitmask which contains hether a channel is powered down or not + * @ctrl: software shadow of the channel ctrl registers + * @channels: iio channel spec for the device + * @data: spi transfer buffers + */ +struct ad5755_state { + struct spi_device *spi; + const struct ad5755_chip_info *chip_info; + unsigned int pwr_down; + unsigned int ctrl[AD5755_NUM_CHANNELS]; + struct iio_chan_spec channels[AD5755_NUM_CHANNELS]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + + union { + __be32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +enum ad5755_type { + ID_AD5755, + ID_AD5757, + ID_AD5735, + ID_AD5737, +}; + +static int ad5755_write_unlocked(struct iio_dev *indio_dev, + unsigned int reg, unsigned int val) +{ + struct ad5755_state *st = iio_priv(indio_dev); + + st->data[0].d32 = cpu_to_be32((reg << 16) | val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5755_write_ctrl_unlocked(struct iio_dev *indio_dev, + unsigned int channel, unsigned int reg, unsigned int val) +{ + return ad5755_write_unlocked(indio_dev, + AD5755_WRITE_REG_CTRL(channel), (reg << 13) | val); +} + +static int ad5755_write(struct iio_dev *indio_dev, unsigned int reg, + unsigned int val) +{ + int ret; + + mutex_lock(&indio_dev->mlock); + ret = ad5755_write_unlocked(indio_dev, reg, val); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5755_write_ctrl(struct iio_dev *indio_dev, unsigned int channel, + unsigned int reg, unsigned int val) +{ + int ret; + + mutex_lock(&indio_dev->mlock); + ret = ad5755_write_ctrl_unlocked(indio_dev, channel, reg, val); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5755_read(struct iio_dev *indio_dev, unsigned int addr) +{ + struct ad5755_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .tx_buf = &st->data[1].d8[1], + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + mutex_lock(&indio_dev->mlock); + + st->data[0].d32 = cpu_to_be32(AD5755_READ_FLAG | (addr << 16)); + st->data[1].d32 = cpu_to_be32(AD5755_NOOP); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret >= 0) + ret = be32_to_cpu(st->data[1].d32) & 0xffff; + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5755_update_dac_ctrl(struct iio_dev *indio_dev, + unsigned int channel, unsigned int set, unsigned int clr) +{ + struct ad5755_state *st = iio_priv(indio_dev); + int ret; + + st->ctrl[channel] |= set; + st->ctrl[channel] &= ~clr; + + ret = ad5755_write_ctrl_unlocked(indio_dev, channel, + AD5755_CTRL_REG_DAC, st->ctrl[channel]); + + return ret; +} + +static int ad5755_set_channel_pwr_down(struct iio_dev *indio_dev, + unsigned int channel, bool pwr_down) +{ + struct ad5755_state *st = iio_priv(indio_dev); + unsigned int mask = BIT(channel); + + mutex_lock(&indio_dev->mlock); + + if ((bool)(st->pwr_down & mask) == pwr_down) + goto out_unlock; + + if (!pwr_down) { + st->pwr_down &= ~mask; + ad5755_update_dac_ctrl(indio_dev, channel, + AD5755_DAC_INT_EN | AD5755_DAC_DC_DC_EN, 0); + udelay(200); + ad5755_update_dac_ctrl(indio_dev, channel, + AD5755_DAC_OUT_EN, 0); + } else { + st->pwr_down |= mask; + ad5755_update_dac_ctrl(indio_dev, channel, + 0, AD5755_DAC_INT_EN | AD5755_DAC_OUT_EN | + AD5755_DAC_DC_DC_EN); + } + +out_unlock: + mutex_unlock(&indio_dev->mlock); + + return 0; +} + +static const int ad5755_min_max_table[][2] = { + [AD5755_MODE_VOLTAGE_0V_5V] = { 0, 5000 }, + [AD5755_MODE_VOLTAGE_0V_10V] = { 0, 10000 }, + [AD5755_MODE_VOLTAGE_PLUSMINUS_5V] = { -5000, 5000 }, + [AD5755_MODE_VOLTAGE_PLUSMINUS_10V] = { -10000, 10000 }, + [AD5755_MODE_CURRENT_4mA_20mA] = { 4, 20 }, + [AD5755_MODE_CURRENT_0mA_20mA] = { 0, 20 }, + [AD5755_MODE_CURRENT_0mA_24mA] = { 0, 24 }, +}; + +static void ad5755_get_min_max(struct ad5755_state *st, + struct iio_chan_spec const *chan, int *min, int *max) +{ + enum ad5755_mode mode = st->ctrl[chan->channel] & 7; + *min = ad5755_min_max_table[mode][0]; + *max = ad5755_min_max_table[mode][1]; +} + +static inline int ad5755_get_offset(struct ad5755_state *st, + struct iio_chan_spec const *chan) +{ + int min, max; + + ad5755_get_min_max(st, chan, &min, &max); + return (min * (1 << chan->scan_type.realbits)) / (max - min); +} + +static int ad5755_chan_reg_info(struct ad5755_state *st, + struct iio_chan_spec const *chan, long info, bool write, + unsigned int *reg, unsigned int *shift, unsigned int *offset) +{ + switch (info) { + case IIO_CHAN_INFO_RAW: + if (write) + *reg = AD5755_WRITE_REG_DATA(chan->address); + else + *reg = AD5755_READ_REG_DATA(chan->address); + *shift = chan->scan_type.shift; + *offset = 0; + break; + case IIO_CHAN_INFO_CALIBBIAS: + if (write) + *reg = AD5755_WRITE_REG_OFFSET(chan->address); + else + *reg = AD5755_READ_REG_OFFSET(chan->address); + *shift = st->chip_info->calib_shift; + *offset = 32768; + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (write) + *reg = AD5755_WRITE_REG_GAIN(chan->address); + else + *reg = AD5755_READ_REG_GAIN(chan->address); + *shift = st->chip_info->calib_shift; + *offset = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ad5755_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ + struct ad5755_state *st = iio_priv(indio_dev); + unsigned int reg, shift, offset; + int min, max; + int ret; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + ad5755_get_min_max(st, chan, &min, &max); + *val = max - min; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + *val = ad5755_get_offset(st, chan); + return IIO_VAL_INT; + default: + ret = ad5755_chan_reg_info(st, chan, info, false, + ®, &shift, &offset); + if (ret) + return ret; + + ret = ad5755_read(indio_dev, reg); + if (ret < 0) + return ret; + + *val = (ret - offset) >> shift; + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ad5755_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, long info) +{ + struct ad5755_state *st = iio_priv(indio_dev); + unsigned int shift, reg, offset; + int ret; + + ret = ad5755_chan_reg_info(st, chan, info, true, + ®, &shift, &offset); + if (ret) + return ret; + + val <<= shift; + val += offset; + + if (val < 0 || val > 0xffff) + return -EINVAL; + + return ad5755_write(indio_dev, reg, val); +} + +static ssize_t ad5755_read_powerdown(struct iio_dev *indio_dev, uintptr_t priv, + const struct iio_chan_spec *chan, char *buf) +{ + struct ad5755_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", + (bool)(st->pwr_down & (1 << chan->channel))); +} + +static ssize_t ad5755_write_powerdown(struct iio_dev *indio_dev, uintptr_t priv, + struct iio_chan_spec const *chan, const char *buf, size_t len) +{ + bool pwr_down; + int ret; + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + ret = ad5755_set_channel_pwr_down(indio_dev, chan->channel, pwr_down); + return ret ? ret : len; +} + +static const struct iio_info ad5755_info = { + .read_raw = ad5755_read_raw, + .write_raw = ad5755_write_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec_ext_info ad5755_ext_info[] = { + { + .name = "powerdown", + .read = ad5755_read_powerdown, + .write = ad5755_write_powerdown, + .shared = IIO_SEPARATE, + }, + { }, +}; + +#define AD5755_CHANNEL(_bits) { \ + .indexed = 1, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + }, \ + .ext_info = ad5755_ext_info, \ +} + +static const struct ad5755_chip_info ad5755_chip_info_tbl[] = { + [ID_AD5735] = { + .channel_template = AD5755_CHANNEL(14), + .has_voltage_out = true, + .calib_shift = 4, + }, + [ID_AD5737] = { + .channel_template = AD5755_CHANNEL(14), + .has_voltage_out = false, + .calib_shift = 4, + }, + [ID_AD5755] = { + .channel_template = AD5755_CHANNEL(16), + .has_voltage_out = true, + .calib_shift = 0, + }, + [ID_AD5757] = { + .channel_template = AD5755_CHANNEL(16), + .has_voltage_out = false, + .calib_shift = 0, + }, +}; + +static bool ad5755_is_valid_mode(struct ad5755_state *st, enum ad5755_mode mode) +{ + switch (mode) { + case AD5755_MODE_VOLTAGE_0V_5V: + case AD5755_MODE_VOLTAGE_0V_10V: + case AD5755_MODE_VOLTAGE_PLUSMINUS_5V: + case AD5755_MODE_VOLTAGE_PLUSMINUS_10V: + return st->chip_info->has_voltage_out; + case AD5755_MODE_CURRENT_4mA_20mA: + case AD5755_MODE_CURRENT_0mA_20mA: + case AD5755_MODE_CURRENT_0mA_24mA: + return true; + default: + return false; + } +} + +static int ad5755_setup_pdata(struct iio_dev *indio_dev, + const struct ad5755_platform_data *pdata) +{ + struct ad5755_state *st = iio_priv(indio_dev); + unsigned int val; + unsigned int i; + int ret; + + if (pdata->dc_dc_phase > AD5755_DC_DC_PHASE_90_DEGREE || + pdata->dc_dc_freq > AD5755_DC_DC_FREQ_650kHZ || + pdata->dc_dc_maxv > AD5755_DC_DC_MAXV_29V5) + return -EINVAL; + + val = pdata->dc_dc_maxv << AD5755_DC_DC_MAXV; + val |= pdata->dc_dc_freq << AD5755_DC_DC_FREQ_SHIFT; + val |= pdata->dc_dc_phase << AD5755_DC_DC_PHASE_SHIFT; + if (pdata->ext_dc_dc_compenstation_resistor) + val |= AD5755_EXT_DC_DC_COMP_RES; + + ret = ad5755_write_ctrl(indio_dev, 0, AD5755_CTRL_REG_DC_DC, val); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(pdata->dac); ++i) { + val = pdata->dac[i].slew.step_size << + AD5755_SLEW_STEP_SIZE_SHIFT; + val |= pdata->dac[i].slew.rate << + AD5755_SLEW_RATE_SHIFT; + if (pdata->dac[i].slew.enable) + val |= AD5755_SLEW_ENABLE; + + ret = ad5755_write_ctrl(indio_dev, i, + AD5755_CTRL_REG_SLEW, val); + if (ret < 0) + return ret; + } + + for (i = 0; i < ARRAY_SIZE(pdata->dac); ++i) { + if (!ad5755_is_valid_mode(st, pdata->dac[i].mode)) + return -EINVAL; + + val = 0; + if (!pdata->dac[i].ext_current_sense_resistor) + val |= AD5755_DAC_INT_CURRENT_SENSE_RESISTOR; + if (pdata->dac[i].enable_voltage_overrange) + val |= AD5755_DAC_VOLTAGE_OVERRANGE_EN; + val |= pdata->dac[i].mode; + + ret = ad5755_update_dac_ctrl(indio_dev, i, val, 0); + if (ret < 0) + return ret; + } + + return 0; +} + +static bool ad5755_is_voltage_mode(enum ad5755_mode mode) +{ + switch (mode) { + case AD5755_MODE_VOLTAGE_0V_5V: + case AD5755_MODE_VOLTAGE_0V_10V: + case AD5755_MODE_VOLTAGE_PLUSMINUS_5V: + case AD5755_MODE_VOLTAGE_PLUSMINUS_10V: + return true; + default: + return false; + } +} + +static int ad5755_init_channels(struct iio_dev *indio_dev, + const struct ad5755_platform_data *pdata) +{ + struct ad5755_state *st = iio_priv(indio_dev); + struct iio_chan_spec *channels = st->channels; + unsigned int i; + + for (i = 0; i < AD5755_NUM_CHANNELS; ++i) { + channels[i] = st->chip_info->channel_template; + channels[i].channel = i; + channels[i].address = i; + if (pdata && ad5755_is_voltage_mode(pdata->dac[i].mode)) + channels[i].type = IIO_VOLTAGE; + else + channels[i].type = IIO_CURRENT; + } + + indio_dev->channels = channels; + + return 0; +} + +#define AD5755_DEFAULT_DAC_PDATA { \ + .mode = AD5755_MODE_CURRENT_4mA_20mA, \ + .ext_current_sense_resistor = true, \ + .enable_voltage_overrange = false, \ + .slew = { \ + .enable = false, \ + .rate = AD5755_SLEW_RATE_64k, \ + .step_size = AD5755_SLEW_STEP_SIZE_1, \ + }, \ + } + +static const struct ad5755_platform_data ad5755_default_pdata = { + .ext_dc_dc_compenstation_resistor = false, + .dc_dc_phase = AD5755_DC_DC_PHASE_ALL_SAME_EDGE, + .dc_dc_freq = AD5755_DC_DC_FREQ_410kHZ, + .dc_dc_maxv = AD5755_DC_DC_MAXV_23V, + .dac = { + [0] = AD5755_DEFAULT_DAC_PDATA, + [1] = AD5755_DEFAULT_DAC_PDATA, + [2] = AD5755_DEFAULT_DAC_PDATA, + [3] = AD5755_DEFAULT_DAC_PDATA, + }, +}; + +static int ad5755_probe(struct spi_device *spi) +{ + enum ad5755_type type = spi_get_device_id(spi)->driver_data; + const struct ad5755_platform_data *pdata = dev_get_platdata(&spi->dev); + struct iio_dev *indio_dev; + struct ad5755_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->chip_info = &ad5755_chip_info_tbl[type]; + st->spi = spi; + st->pwr_down = 0xf; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5755_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = AD5755_NUM_CHANNELS; + + if (!pdata) + pdata = &ad5755_default_pdata; + + ret = ad5755_init_channels(indio_dev, pdata); + if (ret) + return ret; + + ret = ad5755_setup_pdata(indio_dev, pdata); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const struct spi_device_id ad5755_id[] = { + { "ad5755", ID_AD5755 }, + { "ad5755-1", ID_AD5755 }, + { "ad5757", ID_AD5757 }, + { "ad5735", ID_AD5735 }, + { "ad5737", ID_AD5737 }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5755_id); + +static struct spi_driver ad5755_driver = { + .driver = { + .name = "ad5755", + .owner = THIS_MODULE, + }, + .probe = ad5755_probe, + .id_table = ad5755_id, +}; +module_spi_driver(ad5755_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices AD5755/55-1/57/35/37 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5764.c b/drivers/iio/dac/ad5764.c new file mode 100644 index 000000000..d0d381653 --- /dev/null +++ b/drivers/iio/dac/ad5764.c @@ -0,0 +1,370 @@ +/* + * Analog devices AD5764, AD5764R, AD5744, AD5744R quad-channel + * Digital to Analog Converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define AD5764_REG_SF_NOP 0x0 +#define AD5764_REG_SF_CONFIG 0x1 +#define AD5764_REG_SF_CLEAR 0x4 +#define AD5764_REG_SF_LOAD 0x5 +#define AD5764_REG_DATA(x) ((2 << 3) | (x)) +#define AD5764_REG_COARSE_GAIN(x) ((3 << 3) | (x)) +#define AD5764_REG_FINE_GAIN(x) ((4 << 3) | (x)) +#define AD5764_REG_OFFSET(x) ((5 << 3) | (x)) + +#define AD5764_NUM_CHANNELS 4 + +/** + * struct ad5764_chip_info - chip specific information + * @int_vref: Value of the internal reference voltage in uV - 0 if external + * reference voltage is used + * @channel channel specification +*/ + +struct ad5764_chip_info { + unsigned long int_vref; + const struct iio_chan_spec *channels; +}; + +/** + * struct ad5764_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip info + * @vref_reg: vref supply regulators + * @data: spi transfer buffers + */ + +struct ad5764_state { + struct spi_device *spi; + const struct ad5764_chip_info *chip_info; + struct regulator_bulk_data vref_reg[2]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +enum ad5764_type { + ID_AD5744, + ID_AD5744R, + ID_AD5764, + ID_AD5764R, +}; + +#define AD5764_CHANNEL(_chan, _bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .address = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + }, \ +} + +#define DECLARE_AD5764_CHANNELS(_name, _bits) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD5764_CHANNEL(0, (_bits)), \ + AD5764_CHANNEL(1, (_bits)), \ + AD5764_CHANNEL(2, (_bits)), \ + AD5764_CHANNEL(3, (_bits)), \ +}; + +static DECLARE_AD5764_CHANNELS(ad5764, 16); +static DECLARE_AD5764_CHANNELS(ad5744, 14); + +static const struct ad5764_chip_info ad5764_chip_infos[] = { + [ID_AD5744] = { + .int_vref = 0, + .channels = ad5744_channels, + }, + [ID_AD5744R] = { + .int_vref = 5000000, + .channels = ad5744_channels, + }, + [ID_AD5764] = { + .int_vref = 0, + .channels = ad5764_channels, + }, + [ID_AD5764R] = { + .int_vref = 5000000, + .channels = ad5764_channels, + }, +}; + +static int ad5764_write(struct iio_dev *indio_dev, unsigned int reg, + unsigned int val) +{ + struct ad5764_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&indio_dev->mlock); + st->data[0].d32 = cpu_to_be32((reg << 16) | val); + + ret = spi_write(st->spi, &st->data[0].d8[1], 3); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5764_read(struct iio_dev *indio_dev, unsigned int reg, + unsigned int *val) +{ + struct ad5764_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + mutex_lock(&indio_dev->mlock); + + st->data[0].d32 = cpu_to_be32((1 << 23) | (reg << 16)); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret >= 0) + *val = be32_to_cpu(st->data[1].d32) & 0xffff; + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5764_chan_info_to_reg(struct iio_chan_spec const *chan, long info) +{ + switch (info) { + case 0: + return AD5764_REG_DATA(chan->address); + case IIO_CHAN_INFO_CALIBBIAS: + return AD5764_REG_OFFSET(chan->address); + case IIO_CHAN_INFO_CALIBSCALE: + return AD5764_REG_FINE_GAIN(chan->address); + default: + break; + } + + return 0; +} + +static int ad5764_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + const int max_val = (1 << chan->scan_type.realbits); + unsigned int reg; + + switch (info) { + case IIO_CHAN_INFO_RAW: + if (val >= max_val || val < 0) + return -EINVAL; + val <<= chan->scan_type.shift; + break; + case IIO_CHAN_INFO_CALIBBIAS: + if (val >= 128 || val < -128) + return -EINVAL; + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= 32 || val < -32) + return -EINVAL; + break; + default: + return -EINVAL; + } + + reg = ad5764_chan_info_to_reg(chan, info); + return ad5764_write(indio_dev, reg, (u16)val); +} + +static int ad5764_get_channel_vref(struct ad5764_state *st, + unsigned int channel) +{ + if (st->chip_info->int_vref) + return st->chip_info->int_vref; + else + return regulator_get_voltage(st->vref_reg[channel / 2].consumer); +} + +static int ad5764_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad5764_state *st = iio_priv(indio_dev); + unsigned int reg; + int vref; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + reg = AD5764_REG_DATA(chan->address); + ret = ad5764_read(indio_dev, reg, val); + if (ret < 0) + return ret; + *val >>= chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + reg = AD5764_REG_OFFSET(chan->address); + ret = ad5764_read(indio_dev, reg, val); + if (ret < 0) + return ret; + *val = sign_extend32(*val, 7); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + reg = AD5764_REG_FINE_GAIN(chan->address); + ret = ad5764_read(indio_dev, reg, val); + if (ret < 0) + return ret; + *val = sign_extend32(*val, 5); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* vout = 4 * vref + ((dac_code / 65536) - 0.5) */ + vref = ad5764_get_channel_vref(st, chan->channel); + if (vref < 0) + return vref; + + *val = vref * 4 / 1000; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + *val = -(1 << chan->scan_type.realbits) / 2; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info ad5764_info = { + .read_raw = ad5764_read_raw, + .write_raw = ad5764_write_raw, + .driver_module = THIS_MODULE, +}; + +static int ad5764_probe(struct spi_device *spi) +{ + enum ad5764_type type = spi_get_device_id(spi)->driver_data; + struct iio_dev *indio_dev; + struct ad5764_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + st->chip_info = &ad5764_chip_infos[type]; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5764_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = AD5764_NUM_CHANNELS; + indio_dev->channels = st->chip_info->channels; + + if (st->chip_info->int_vref == 0) { + st->vref_reg[0].supply = "vrefAB"; + st->vref_reg[1].supply = "vrefCD"; + + ret = devm_regulator_bulk_get(&st->spi->dev, + ARRAY_SIZE(st->vref_reg), st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to request vref regulators: %d\n", + ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(st->vref_reg), + st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to enable vref regulators: %d\n", + ret); + return ret; + } + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device: %d\n", ret); + goto error_disable_reg; + } + + return 0; + +error_disable_reg: + if (st->chip_info->int_vref == 0) + regulator_bulk_disable(ARRAY_SIZE(st->vref_reg), st->vref_reg); + return ret; +} + +static int ad5764_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5764_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (st->chip_info->int_vref == 0) + regulator_bulk_disable(ARRAY_SIZE(st->vref_reg), st->vref_reg); + + return 0; +} + +static const struct spi_device_id ad5764_ids[] = { + { "ad5744", ID_AD5744 }, + { "ad5744r", ID_AD5744R }, + { "ad5764", ID_AD5764 }, + { "ad5764r", ID_AD5764R }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5764_ids); + +static struct spi_driver ad5764_driver = { + .driver = { + .name = "ad5764", + .owner = THIS_MODULE, + }, + .probe = ad5764_probe, + .remove = ad5764_remove, + .id_table = ad5764_ids, +}; +module_spi_driver(ad5764_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices AD5744/AD5744R/AD5764/AD5764R DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c new file mode 100644 index 000000000..5ba785f18 --- /dev/null +++ b/drivers/iio/dac/ad5791.c @@ -0,0 +1,474 @@ +/* + * AD5760, AD5780, AD5781, AD5790, AD5791 Voltage Output Digital to Analog + * Converter + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/regulator/consumer.h> +#include <linux/module.h> +#include <linux/bitops.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/dac/ad5791.h> + +#define AD5791_DAC_MASK GENMASK(19, 0) + +#define AD5791_CMD_READ BIT(23) +#define AD5791_CMD_WRITE 0 +#define AD5791_ADDR(addr) ((addr) << 20) + +/* Registers */ +#define AD5791_ADDR_NOOP 0 +#define AD5791_ADDR_DAC0 1 +#define AD5791_ADDR_CTRL 2 +#define AD5791_ADDR_CLRCODE 3 +#define AD5791_ADDR_SW_CTRL 4 + +/* Control Register */ +#define AD5791_CTRL_RBUF BIT(1) +#define AD5791_CTRL_OPGND BIT(2) +#define AD5791_CTRL_DACTRI BIT(3) +#define AD5791_CTRL_BIN2SC BIT(4) +#define AD5791_CTRL_SDODIS BIT(5) +#define AD5761_CTRL_LINCOMP(x) ((x) << 6) + +#define AD5791_LINCOMP_0_10 0 +#define AD5791_LINCOMP_10_12 1 +#define AD5791_LINCOMP_12_16 2 +#define AD5791_LINCOMP_16_19 3 +#define AD5791_LINCOMP_19_20 12 + +#define AD5780_LINCOMP_0_10 0 +#define AD5780_LINCOMP_10_20 12 + +/* Software Control Register */ +#define AD5791_SWCTRL_LDAC BIT(0) +#define AD5791_SWCTRL_CLR BIT(1) +#define AD5791_SWCTRL_RESET BIT(2) + +#define AD5791_DAC_PWRDN_6K 0 +#define AD5791_DAC_PWRDN_3STATE 1 + +/** + * struct ad5791_chip_info - chip specific information + * @get_lin_comp: function pointer to the device specific function + */ + +struct ad5791_chip_info { + int (*get_lin_comp) (unsigned int span); +}; + +/** + * struct ad5791_state - driver instance specific data + * @spi: spi_device + * @reg_vdd: positive supply regulator + * @reg_vss: negative supply regulator + * @chip_info: chip model specific constants + * @vref_mv: actual reference voltage used + * @vref_neg_mv: voltage of the negative supply + * @pwr_down_mode current power down mode + */ + +struct ad5791_state { + struct spi_device *spi; + struct regulator *reg_vdd; + struct regulator *reg_vss; + const struct ad5791_chip_info *chip_info; + unsigned short vref_mv; + unsigned int vref_neg_mv; + unsigned ctrl; + unsigned pwr_down_mode; + bool pwr_down; + + union { + __be32 d32; + u8 d8[4]; + } data[3] ____cacheline_aligned; +}; + +/** + * ad5791_supported_device_ids: + */ + +enum ad5791_supported_device_ids { + ID_AD5760, + ID_AD5780, + ID_AD5781, + ID_AD5791, +}; + +static int ad5791_spi_write(struct ad5791_state *st, u8 addr, u32 val) +{ + st->data[0].d32 = cpu_to_be32(AD5791_CMD_WRITE | + AD5791_ADDR(addr) | + (val & AD5791_DAC_MASK)); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5791_spi_read(struct ad5791_state *st, u8 addr, u32 *val) +{ + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->data[0].d8[1], + .bits_per_word = 8, + .len = 3, + .cs_change = 1, + }, { + .tx_buf = &st->data[1].d8[1], + .rx_buf = &st->data[2].d8[1], + .bits_per_word = 8, + .len = 3, + }, + }; + + st->data[0].d32 = cpu_to_be32(AD5791_CMD_READ | + AD5791_ADDR(addr)); + st->data[1].d32 = cpu_to_be32(AD5791_ADDR(AD5791_ADDR_NOOP)); + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + + *val = be32_to_cpu(st->data[2].d32); + + return ret; +} + +static const char * const ad5791_powerdown_modes[] = { + "6kohm_to_gnd", + "three_state", +}; + +static int ad5791_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + return st->pwr_down_mode; +} + +static int ad5791_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int mode) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + st->pwr_down_mode = mode; + + return 0; +} + +static const struct iio_enum ad5791_powerdown_mode_enum = { + .items = ad5791_powerdown_modes, + .num_items = ARRAY_SIZE(ad5791_powerdown_modes), + .get = ad5791_get_powerdown_mode, + .set = ad5791_set_powerdown_mode, +}; + +static ssize_t ad5791_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5791_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + bool pwr_down; + int ret; + struct ad5791_state *st = iio_priv(indio_dev); + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + if (!pwr_down) { + st->ctrl &= ~(AD5791_CTRL_OPGND | AD5791_CTRL_DACTRI); + } else { + if (st->pwr_down_mode == AD5791_DAC_PWRDN_6K) + st->ctrl |= AD5791_CTRL_OPGND; + else if (st->pwr_down_mode == AD5791_DAC_PWRDN_3STATE) + st->ctrl |= AD5791_CTRL_DACTRI; + } + st->pwr_down = pwr_down; + + ret = ad5791_spi_write(st, AD5791_ADDR_CTRL, st->ctrl); + + return ret ? ret : len; +} + +static int ad5791_get_lin_comp(unsigned int span) +{ + if (span <= 10000) + return AD5791_LINCOMP_0_10; + else if (span <= 12000) + return AD5791_LINCOMP_10_12; + else if (span <= 16000) + return AD5791_LINCOMP_12_16; + else if (span <= 19000) + return AD5791_LINCOMP_16_19; + else + return AD5791_LINCOMP_19_20; +} + +static int ad5780_get_lin_comp(unsigned int span) +{ + if (span <= 10000) + return AD5780_LINCOMP_0_10; + else + return AD5780_LINCOMP_10_20; +} +static const struct ad5791_chip_info ad5791_chip_info_tbl[] = { + [ID_AD5760] = { + .get_lin_comp = ad5780_get_lin_comp, + }, + [ID_AD5780] = { + .get_lin_comp = ad5780_get_lin_comp, + }, + [ID_AD5781] = { + .get_lin_comp = ad5791_get_lin_comp, + }, + [ID_AD5791] = { + .get_lin_comp = ad5791_get_lin_comp, + }, +}; + +static int ad5791_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5791_state *st = iio_priv(indio_dev); + u64 val64; + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5791_spi_read(st, chan->address, val); + if (ret) + return ret; + *val &= AD5791_DAC_MASK; + *val >>= chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mv; + *val2 = (1 << chan->scan_type.realbits) - 1; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OFFSET: + val64 = (((u64)st->vref_neg_mv) << chan->scan_type.realbits); + do_div(val64, st->vref_mv); + *val = -val64; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +}; + +static const struct iio_chan_spec_ext_info ad5791_ext_info[] = { + { + .name = "powerdown", + .shared = IIO_SHARED_BY_TYPE, + .read = ad5791_read_dac_powerdown, + .write = ad5791_write_dac_powerdown, + }, + IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, + &ad5791_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", &ad5791_powerdown_mode_enum), + { }, +}; + +#define AD5791_CHAN(bits, _shift) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .address = AD5791_ADDR_DAC0, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 24, \ + .shift = (_shift), \ + }, \ + .ext_info = ad5791_ext_info, \ +} + +static const struct iio_chan_spec ad5791_channels[] = { + [ID_AD5760] = AD5791_CHAN(16, 4), + [ID_AD5780] = AD5791_CHAN(18, 2), + [ID_AD5781] = AD5791_CHAN(18, 2), + [ID_AD5791] = AD5791_CHAN(20, 0) +}; + +static int ad5791_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + val &= GENMASK(chan->scan_type.realbits - 1, 0); + val <<= chan->scan_type.shift; + + return ad5791_spi_write(st, chan->address, val); + + default: + return -EINVAL; + } +} + +static const struct iio_info ad5791_info = { + .read_raw = &ad5791_read_raw, + .write_raw = &ad5791_write_raw, + .driver_module = THIS_MODULE, +}; + +static int ad5791_probe(struct spi_device *spi) +{ + struct ad5791_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev; + struct ad5791_state *st; + int ret, pos_voltage_uv = 0, neg_voltage_uv = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + st->reg_vdd = devm_regulator_get(&spi->dev, "vdd"); + if (!IS_ERR(st->reg_vdd)) { + ret = regulator_enable(st->reg_vdd); + if (ret) + return ret; + + ret = regulator_get_voltage(st->reg_vdd); + if (ret < 0) + goto error_disable_reg_pos; + + pos_voltage_uv = ret; + } + + st->reg_vss = devm_regulator_get(&spi->dev, "vss"); + if (!IS_ERR(st->reg_vss)) { + ret = regulator_enable(st->reg_vss); + if (ret) + goto error_disable_reg_pos; + + ret = regulator_get_voltage(st->reg_vss); + if (ret < 0) + goto error_disable_reg_neg; + + neg_voltage_uv = ret; + } + + st->pwr_down = true; + st->spi = spi; + + if (!IS_ERR(st->reg_vss) && !IS_ERR(st->reg_vdd)) { + st->vref_mv = (pos_voltage_uv + neg_voltage_uv) / 1000; + st->vref_neg_mv = neg_voltage_uv / 1000; + } else if (pdata) { + st->vref_mv = pdata->vref_pos_mv + pdata->vref_neg_mv; + st->vref_neg_mv = pdata->vref_neg_mv; + } else { + dev_warn(&spi->dev, "reference voltage unspecified\n"); + } + + ret = ad5791_spi_write(st, AD5791_ADDR_SW_CTRL, AD5791_SWCTRL_RESET); + if (ret) + goto error_disable_reg_neg; + + st->chip_info = &ad5791_chip_info_tbl[spi_get_device_id(spi) + ->driver_data]; + + + st->ctrl = AD5761_CTRL_LINCOMP(st->chip_info->get_lin_comp(st->vref_mv)) + | ((pdata && pdata->use_rbuf_gain2) ? 0 : AD5791_CTRL_RBUF) | + AD5791_CTRL_BIN2SC; + + ret = ad5791_spi_write(st, AD5791_ADDR_CTRL, st->ctrl | + AD5791_CTRL_OPGND | AD5791_CTRL_DACTRI); + if (ret) + goto error_disable_reg_neg; + + spi_set_drvdata(spi, indio_dev); + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ad5791_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels + = &ad5791_channels[spi_get_device_id(spi)->driver_data]; + indio_dev->num_channels = 1; + indio_dev->name = spi_get_device_id(st->spi)->name; + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg_neg; + + return 0; + +error_disable_reg_neg: + if (!IS_ERR(st->reg_vss)) + regulator_disable(st->reg_vss); +error_disable_reg_pos: + if (!IS_ERR(st->reg_vdd)) + regulator_disable(st->reg_vdd); + return ret; +} + +static int ad5791_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5791_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg_vdd)) + regulator_disable(st->reg_vdd); + + if (!IS_ERR(st->reg_vss)) + regulator_disable(st->reg_vss); + + return 0; +} + +static const struct spi_device_id ad5791_id[] = { + {"ad5760", ID_AD5760}, + {"ad5780", ID_AD5780}, + {"ad5781", ID_AD5781}, + {"ad5790", ID_AD5791}, + {"ad5791", ID_AD5791}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5791_id); + +static struct spi_driver ad5791_driver = { + .driver = { + .name = "ad5791", + .owner = THIS_MODULE, + }, + .probe = ad5791_probe, + .remove = ad5791_remove, + .id_table = ad5791_id, +}; +module_spi_driver(ad5791_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad7303.c b/drivers/iio/dac/ad7303.c new file mode 100644 index 000000000..fa2810032 --- /dev/null +++ b/drivers/iio/dac/ad7303.c @@ -0,0 +1,303 @@ +/* + * AD7303 Digital to analog converters driver + * + * Copyright 2013 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/regulator/consumer.h> +#include <linux/of.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include <linux/platform_data/ad7303.h> + +#define AD7303_CFG_EXTERNAL_VREF BIT(15) +#define AD7303_CFG_POWER_DOWN(ch) BIT(11 + (ch)) +#define AD7303_CFG_ADDR_OFFSET 10 + +#define AD7303_CMD_UPDATE_DAC (0x3 << 8) + +/** + * struct ad7303_state - driver instance specific data + * @spi: the device for this driver instance + * @config: cached config register value + * @dac_cache: current DAC raw value (chip does not support readback) + * @data: spi transfer buffer + */ + +struct ad7303_state { + struct spi_device *spi; + uint16_t config; + uint8_t dac_cache[2]; + + struct regulator *vdd_reg; + struct regulator *vref_reg; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __be16 data ____cacheline_aligned; +}; + +static int ad7303_write(struct ad7303_state *st, unsigned int chan, + uint8_t val) +{ + st->data = cpu_to_be16(AD7303_CMD_UPDATE_DAC | + (chan << AD7303_CFG_ADDR_OFFSET) | + st->config | val); + + return spi_write(st->spi, &st->data, sizeof(st->data)); +} + +static ssize_t ad7303_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct ad7303_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", (bool)(st->config & + AD7303_CFG_POWER_DOWN(chan->channel))); +} + +static ssize_t ad7303_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + struct ad7303_state *st = iio_priv(indio_dev); + bool pwr_down; + int ret; + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + if (pwr_down) + st->config |= AD7303_CFG_POWER_DOWN(chan->channel); + else + st->config &= ~AD7303_CFG_POWER_DOWN(chan->channel); + + /* There is no noop cmd which allows us to only update the powerdown + * mode, so just write one of the DAC channels again */ + ad7303_write(st, chan->channel, st->dac_cache[chan->channel]); + + mutex_unlock(&indio_dev->mlock); + return len; +} + +static int ad7303_get_vref(struct ad7303_state *st, + struct iio_chan_spec const *chan) +{ + int ret; + + if (st->config & AD7303_CFG_EXTERNAL_VREF) + return regulator_get_voltage(st->vref_reg); + + ret = regulator_get_voltage(st->vdd_reg); + if (ret < 0) + return ret; + return ret / 2; +} + +static int ad7303_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad7303_state *st = iio_priv(indio_dev); + int vref_uv; + + switch (info) { + case IIO_CHAN_INFO_RAW: + *val = st->dac_cache[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + vref_uv = ad7303_get_vref(st, chan); + if (vref_uv < 0) + return vref_uv; + + *val = 2 * vref_uv / 1000; + *val2 = chan->scan_type.realbits; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + return -EINVAL; +} + +static int ad7303_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct ad7303_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = ad7303_write(st, chan->address, val); + if (ret == 0) + st->dac_cache[chan->channel] = val; + mutex_unlock(&indio_dev->mlock); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad7303_info = { + .read_raw = ad7303_read_raw, + .write_raw = ad7303_write_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec_ext_info ad7303_ext_info[] = { + { + .name = "powerdown", + .read = ad7303_read_dac_powerdown, + .write = ad7303_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + { }, +}; + +#define AD7303_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .address = (chan), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = '8', \ + .storagebits = '8', \ + .shift = '0', \ + }, \ + .ext_info = ad7303_ext_info, \ +} + +static const struct iio_chan_spec ad7303_channels[] = { + AD7303_CHANNEL(0), + AD7303_CHANNEL(1), +}; + +static int ad7303_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct iio_dev *indio_dev; + struct ad7303_state *st; + bool ext_ref; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + st->vdd_reg = devm_regulator_get(&spi->dev, "Vdd"); + if (IS_ERR(st->vdd_reg)) + return PTR_ERR(st->vdd_reg); + + ret = regulator_enable(st->vdd_reg); + if (ret) + return ret; + + if (spi->dev.of_node) { + ext_ref = of_property_read_bool(spi->dev.of_node, + "REF-supply"); + } else { + struct ad7303_platform_data *pdata = spi->dev.platform_data; + if (pdata && pdata->use_external_ref) + ext_ref = true; + else + ext_ref = false; + } + + if (ext_ref) { + st->vref_reg = devm_regulator_get(&spi->dev, "REF"); + if (IS_ERR(st->vref_reg)) { + ret = PTR_ERR(st->vref_reg); + goto err_disable_vdd_reg; + } + + ret = regulator_enable(st->vref_reg); + if (ret) + goto err_disable_vdd_reg; + + st->config |= AD7303_CFG_EXTERNAL_VREF; + } + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = id->name; + indio_dev->info = &ad7303_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7303_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7303_channels); + + ret = iio_device_register(indio_dev); + if (ret) + goto err_disable_vref_reg; + + return 0; + +err_disable_vref_reg: + if (st->vref_reg) + regulator_disable(st->vref_reg); +err_disable_vdd_reg: + regulator_disable(st->vdd_reg); + return ret; +} + +static int ad7303_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7303_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (st->vref_reg) + regulator_disable(st->vref_reg); + regulator_disable(st->vdd_reg); + + return 0; +} + +static const struct spi_device_id ad7303_spi_ids[] = { + { "ad7303", 0 }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7303_spi_ids); + +static struct spi_driver ad7303_driver = { + .driver = { + .name = "ad7303", + .owner = THIS_MODULE, + }, + .probe = ad7303_probe, + .remove = ad7303_remove, + .id_table = ad7303_spi_ids, +}; +module_spi_driver(ad7303_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices AD7303 DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/max517.c b/drivers/iio/dac/max517.c new file mode 100644 index 000000000..5507b3970 --- /dev/null +++ b/drivers/iio/dac/max517.c @@ -0,0 +1,243 @@ +/* + * max517.c - Support for Maxim MAX517, MAX518 and MAX519 + * + * Copyright (C) 2010, 2011 Roland Stigge <stigge@antcom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/err.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/dac/max517.h> + +#define MAX517_DRV_NAME "max517" + +/* Commands */ +#define COMMAND_CHANNEL0 0x00 +#define COMMAND_CHANNEL1 0x01 /* for MAX518 and MAX519 */ +#define COMMAND_PD 0x08 /* Power Down */ + +enum max517_device_ids { + ID_MAX517, + ID_MAX518, + ID_MAX519, + ID_MAX520, + ID_MAX521, +}; + +struct max517_data { + struct i2c_client *client; + unsigned short vref_mv[8]; +}; + +/* + * channel: bit 0: channel 1 + * bit 1: channel 2 + * (this way, it's possible to set both channels at once) + */ +static int max517_set_value(struct iio_dev *indio_dev, + long val, int channel) +{ + struct max517_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + u8 outbuf[2]; + int res; + + if (val < 0 || val > 255) + return -EINVAL; + + outbuf[0] = channel; + outbuf[1] = val; + + res = i2c_master_send(client, outbuf, 2); + if (res < 0) + return res; + else if (res != 2) + return -EIO; + else + return 0; +} + +static int max517_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct max517_data *data = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_SCALE: + /* Corresponds to Vref / 2^(bits) */ + *val = data->vref_mv[chan->channel]; + *val2 = 8; + return IIO_VAL_FRACTIONAL_LOG2; + default: + break; + } + return -EINVAL; +} + +static int max517_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = max517_set_value(indio_dev, val, chan->channel); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int max517_suspend(struct device *dev) +{ + u8 outbuf = COMMAND_PD; + + return i2c_master_send(to_i2c_client(dev), &outbuf, 1); +} + +static int max517_resume(struct device *dev) +{ + u8 outbuf = 0; + + return i2c_master_send(to_i2c_client(dev), &outbuf, 1); +} + +static SIMPLE_DEV_PM_OPS(max517_pm_ops, max517_suspend, max517_resume); +#define MAX517_PM_OPS (&max517_pm_ops) +#else +#define MAX517_PM_OPS NULL +#endif + +static const struct iio_info max517_info = { + .read_raw = max517_read_raw, + .write_raw = max517_write_raw, + .driver_module = THIS_MODULE, +}; + +#define MAX517_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec max517_channels[] = { + MAX517_CHANNEL(0), + MAX517_CHANNEL(1), + MAX517_CHANNEL(2), + MAX517_CHANNEL(3), + MAX517_CHANNEL(4), + MAX517_CHANNEL(5), + MAX517_CHANNEL(6), + MAX517_CHANNEL(7), +}; + +static int max517_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max517_data *data; + struct iio_dev *indio_dev; + struct max517_platform_data *platform_data = client->dev.platform_data; + int chan; + + 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; + + /* establish that the iio_dev is a child of the i2c device */ + indio_dev->dev.parent = &client->dev; + + switch (id->driver_data) { + case ID_MAX521: + indio_dev->num_channels = 8; + break; + case ID_MAX520: + indio_dev->num_channels = 4; + break; + case ID_MAX519: + case ID_MAX518: + indio_dev->num_channels = 2; + break; + default: /* single channel for MAX517 */ + indio_dev->num_channels = 1; + break; + } + indio_dev->channels = max517_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &max517_info; + + /* + * Reference voltage on MAX518 and default is 5V, else take vref_mv + * from platform_data + */ + for (chan = 0; chan < indio_dev->num_channels; chan++) { + if (id->driver_data == ID_MAX518 || !platform_data) + data->vref_mv[chan] = 5000; /* mV */ + else + data->vref_mv[chan] = platform_data->vref_mv[chan]; + } + + return iio_device_register(indio_dev); +} + +static int max517_remove(struct i2c_client *client) +{ + iio_device_unregister(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id max517_id[] = { + { "max517", ID_MAX517 }, + { "max518", ID_MAX518 }, + { "max519", ID_MAX519 }, + { "max520", ID_MAX520 }, + { "max521", ID_MAX521 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max517_id); + +static struct i2c_driver max517_driver = { + .driver = { + .name = MAX517_DRV_NAME, + .pm = MAX517_PM_OPS, + }, + .probe = max517_probe, + .remove = max517_remove, + .id_table = max517_id, +}; +module_i2c_driver(max517_driver); + +MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>"); +MODULE_DESCRIPTION("MAX517/518/519/520/521 8-bit DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/max5821.c b/drivers/iio/dac/max5821.c new file mode 100644 index 000000000..6e914495b --- /dev/null +++ b/drivers/iio/dac/max5821.c @@ -0,0 +1,405 @@ + /* + * iio/dac/max5821.c + * Copyright (C) 2014 Philippe Reynes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/regulator/consumer.h> + +#define MAX5821_MAX_DAC_CHANNELS 2 + +/* command bytes */ +#define MAX5821_LOAD_DAC_A_IN_REG_B 0x00 +#define MAX5821_LOAD_DAC_B_IN_REG_A 0x10 +#define MAX5821_EXTENDED_COMMAND_MODE 0xf0 +#define MAX5821_READ_DAC_A_COMMAND 0xf1 +#define MAX5821_READ_DAC_B_COMMAND 0xf2 + +#define MAX5821_EXTENDED_POWER_UP 0x00 +#define MAX5821_EXTENDED_POWER_DOWN_MODE0 0x01 +#define MAX5821_EXTENDED_POWER_DOWN_MODE1 0x02 +#define MAX5821_EXTENDED_POWER_DOWN_MODE2 0x03 +#define MAX5821_EXTENDED_DAC_A 0x04 +#define MAX5821_EXTENDED_DAC_B 0x08 + +enum max5821_device_ids { + ID_MAX5821, +}; + +struct max5821_data { + struct i2c_client *client; + struct regulator *vref_reg; + unsigned short vref_mv; + bool powerdown[MAX5821_MAX_DAC_CHANNELS]; + u8 powerdown_mode[MAX5821_MAX_DAC_CHANNELS]; + struct mutex lock; +}; + +static const char * const max5821_powerdown_modes[] = { + "three_state", + "1kohm_to_gnd", + "100kohm_to_gnd", +}; + +enum { + MAX5821_THREE_STATE, + MAX5821_1KOHM_TO_GND, + MAX5821_100KOHM_TO_GND +}; + +static int max5821_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct max5821_data *st = iio_priv(indio_dev); + + return st->powerdown_mode[chan->channel]; +} + +static int max5821_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct max5821_data *st = iio_priv(indio_dev); + + st->powerdown_mode[chan->channel] = mode; + + return 0; +} + +static const struct iio_enum max5821_powerdown_mode_enum = { + .items = max5821_powerdown_modes, + .num_items = ARRAY_SIZE(max5821_powerdown_modes), + .get = max5821_get_powerdown_mode, + .set = max5821_set_powerdown_mode, +}; + +static ssize_t max5821_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct max5821_data *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->powerdown[chan->channel]); +} + +static int max5821_sync_powerdown_mode(struct max5821_data *data, + const struct iio_chan_spec *chan) +{ + u8 outbuf[2]; + + outbuf[0] = MAX5821_EXTENDED_COMMAND_MODE; + + if (chan->channel == 0) + outbuf[1] = MAX5821_EXTENDED_DAC_A; + else + outbuf[1] = MAX5821_EXTENDED_DAC_B; + + if (data->powerdown[chan->channel]) + outbuf[1] |= data->powerdown_mode[chan->channel] + 1; + else + outbuf[1] |= MAX5821_EXTENDED_POWER_UP; + + return i2c_master_send(data->client, outbuf, 2); +} + +static ssize_t max5821_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct max5821_data *data = iio_priv(indio_dev); + bool powerdown; + int ret; + + ret = strtobool(buf, &powerdown); + if (ret) + return ret; + + data->powerdown[chan->channel] = powerdown; + + ret = max5821_sync_powerdown_mode(data, chan); + if (ret < 0) + return ret; + + return len; +} + +static const struct iio_chan_spec_ext_info max5821_ext_info[] = { + { + .name = "powerdown", + .read = max5821_read_dac_powerdown, + .write = max5821_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &max5821_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", &max5821_powerdown_mode_enum), + { }, +}; + +#define MAX5821_CHANNEL(chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = max5821_ext_info, \ +} + +static const struct iio_chan_spec max5821_channels[] = { + MAX5821_CHANNEL(0), + MAX5821_CHANNEL(1) +}; + +static const u8 max5821_read_dac_command[] = { + MAX5821_READ_DAC_A_COMMAND, + MAX5821_READ_DAC_B_COMMAND +}; + +static const u8 max5821_load_dac_command[] = { + MAX5821_LOAD_DAC_A_IN_REG_B, + MAX5821_LOAD_DAC_B_IN_REG_A +}; + +static int max5821_get_value(struct iio_dev *indio_dev, + int *val, int channel) +{ + struct max5821_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + u8 outbuf[1]; + u8 inbuf[2]; + int ret; + + if ((channel != 0) && (channel != 1)) + return -EINVAL; + + outbuf[0] = max5821_read_dac_command[channel]; + + mutex_lock(&data->lock); + + ret = i2c_master_send(client, outbuf, 1); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } else if (ret != 1) { + mutex_unlock(&data->lock); + return -EIO; + } + + ret = i2c_master_recv(client, inbuf, 2); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } else if (ret != 2) { + mutex_unlock(&data->lock); + return -EIO; + } + + mutex_unlock(&data->lock); + + *val = ((inbuf[0] & 0x0f) << 6) | (inbuf[1] >> 2); + + return IIO_VAL_INT; +} + +static int max5821_set_value(struct iio_dev *indio_dev, + int val, int channel) +{ + struct max5821_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + u8 outbuf[2]; + int ret; + + if ((val < 0) || (val > 1023)) + return -EINVAL; + + if ((channel != 0) && (channel != 1)) + return -EINVAL; + + outbuf[0] = max5821_load_dac_command[channel]; + outbuf[0] |= val >> 6; + outbuf[1] = (val & 0x3f) << 2; + + ret = i2c_master_send(client, outbuf, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + else + return 0; +} + +static int max5821_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max5821_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return max5821_get_value(indio_dev, val, chan->channel); + case IIO_CHAN_INFO_SCALE: + *val = data->vref_mv; + *val2 = 10; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int max5821_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + if (val2 != 0) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return max5821_set_value(indio_dev, val, chan->channel); + default: + return -EINVAL; + } +} + +#ifdef CONFIG_PM_SLEEP +static int max5821_suspend(struct device *dev) +{ + u8 outbuf[2] = { MAX5821_EXTENDED_COMMAND_MODE, + MAX5821_EXTENDED_DAC_A | + MAX5821_EXTENDED_DAC_B | + MAX5821_EXTENDED_POWER_DOWN_MODE2 }; + + return i2c_master_send(to_i2c_client(dev), outbuf, 2); +} + +static int max5821_resume(struct device *dev) +{ + u8 outbuf[2] = { MAX5821_EXTENDED_COMMAND_MODE, + MAX5821_EXTENDED_DAC_A | + MAX5821_EXTENDED_DAC_B | + MAX5821_EXTENDED_POWER_UP }; + + return i2c_master_send(to_i2c_client(dev), outbuf, 2); +} + +static SIMPLE_DEV_PM_OPS(max5821_pm_ops, max5821_suspend, max5821_resume); +#define MAX5821_PM_OPS (&max5821_pm_ops) +#else +#define MAX5821_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static const struct iio_info max5821_info = { + .read_raw = max5821_read_raw, + .write_raw = max5821_write_raw, + .driver_module = THIS_MODULE, +}; + +static int max5821_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max5821_data *data; + struct iio_dev *indio_dev; + u32 tmp; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + /* max5821 start in powerdown mode 100Kohm to ground */ + for (tmp = 0; tmp < MAX5821_MAX_DAC_CHANNELS; tmp++) { + data->powerdown[tmp] = true; + data->powerdown_mode[tmp] = MAX5821_100KOHM_TO_GND; + } + + data->vref_reg = devm_regulator_get(&client->dev, "vref"); + if (IS_ERR(data->vref_reg)) { + ret = PTR_ERR(data->vref_reg); + dev_err(&client->dev, + "Failed to get vref regulator: %d\n", ret); + goto error_free_reg; + } + + ret = regulator_enable(data->vref_reg); + if (ret) { + dev_err(&client->dev, + "Failed to enable vref regulator: %d\n", ret); + goto error_free_reg; + } + + ret = regulator_get_voltage(data->vref_reg); + if (ret < 0) { + dev_err(&client->dev, + "Failed to get voltage on regulator: %d\n", ret); + goto error_disable_reg; + } + + data->vref_mv = ret / 1000; + + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->num_channels = ARRAY_SIZE(max5821_channels); + indio_dev->channels = max5821_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &max5821_info; + + return iio_device_register(indio_dev); + +error_disable_reg: + regulator_disable(data->vref_reg); + +error_free_reg: + + return ret; +} + +static int max5821_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct max5821_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(data->vref_reg); + + return 0; +} + +static const struct i2c_device_id max5821_id[] = { + { "max5821", ID_MAX5821 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max5821_id); + +static const struct of_device_id max5821_of_match[] = { + { .compatible = "maxim,max5821" }, + { } +}; + +static struct i2c_driver max5821_driver = { + .driver = { + .name = "max5821", + .pm = MAX5821_PM_OPS, + .owner = THIS_MODULE, + }, + .probe = max5821_probe, + .remove = max5821_remove, + .id_table = max5821_id, +}; +module_i2c_driver(max5821_driver); + +MODULE_AUTHOR("Philippe Reynes <tremyfr@yahoo.fr>"); +MODULE_DESCRIPTION("MAX5821 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/mcp4725.c b/drivers/iio/dac/mcp4725.c new file mode 100644 index 000000000..43d145884 --- /dev/null +++ b/drivers/iio/dac/mcp4725.c @@ -0,0 +1,349 @@ +/* + * mcp4725.c - Support for Microchip MCP4725 + * + * Copyright (C) 2012 Peter Meerwald <pmeerw@pmeerw.net> + * + * Based on max517 by Roland Stigge <stigge@antcom.de> + * + * 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. + * + * driver for the Microchip I2C 12-bit digital-to-analog converter (DAC) + * (7-bit I2C slave address 0x60, the three LSBs can be configured in + * hardware) + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include <linux/iio/dac/mcp4725.h> + +#define MCP4725_DRV_NAME "mcp4725" + +struct mcp4725_data { + struct i2c_client *client; + u16 vref_mv; + u16 dac_value; + bool powerdown; + unsigned powerdown_mode; +}; + +static int mcp4725_suspend(struct device *dev) +{ + struct mcp4725_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + u8 outbuf[2]; + + outbuf[0] = (data->powerdown_mode + 1) << 4; + outbuf[1] = 0; + data->powerdown = true; + + return i2c_master_send(data->client, outbuf, 2); +} + +static int mcp4725_resume(struct device *dev) +{ + struct mcp4725_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + u8 outbuf[2]; + + /* restore previous DAC value */ + outbuf[0] = (data->dac_value >> 8) & 0xf; + outbuf[1] = data->dac_value & 0xff; + data->powerdown = false; + + return i2c_master_send(data->client, outbuf, 2); +} + +#ifdef CONFIG_PM_SLEEP +static SIMPLE_DEV_PM_OPS(mcp4725_pm_ops, mcp4725_suspend, mcp4725_resume); +#define MCP4725_PM_OPS (&mcp4725_pm_ops) +#else +#define MCP4725_PM_OPS NULL +#endif + +static ssize_t mcp4725_store_eeprom(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mcp4725_data *data = iio_priv(indio_dev); + int tries = 20; + u8 inoutbuf[3]; + bool state; + int ret; + + ret = strtobool(buf, &state); + if (ret < 0) + return ret; + + if (!state) + return 0; + + inoutbuf[0] = 0x60; /* write EEPROM */ + inoutbuf[1] = data->dac_value >> 4; + inoutbuf[2] = (data->dac_value & 0xf) << 4; + + ret = i2c_master_send(data->client, inoutbuf, 3); + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + + /* wait for write complete, takes up to 50ms */ + while (tries--) { + msleep(20); + ret = i2c_master_recv(data->client, inoutbuf, 3); + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + + if (inoutbuf[0] & 0x80) + break; + } + + if (tries < 0) { + dev_err(&data->client->dev, + "mcp4725_store_eeprom() failed, incomplete\n"); + return -EIO; + } + + return len; +} + +static IIO_DEVICE_ATTR(store_eeprom, S_IWUSR, NULL, mcp4725_store_eeprom, 0); + +static struct attribute *mcp4725_attributes[] = { + &iio_dev_attr_store_eeprom.dev_attr.attr, + NULL, +}; + +static const struct attribute_group mcp4725_attribute_group = { + .attrs = mcp4725_attributes, +}; + +static const char * const mcp4725_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "500kohm_to_gnd" +}; + +static int mcp4725_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + + return data->powerdown_mode; +} + +static int mcp4725_set_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned mode) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + + data->powerdown_mode = mode; + + return 0; +} + +static ssize_t mcp4725_read_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", data->powerdown); +} + +static ssize_t mcp4725_write_powerdown(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + bool state; + int ret; + + ret = strtobool(buf, &state); + if (ret) + return ret; + + if (state) + ret = mcp4725_suspend(&data->client->dev); + else + ret = mcp4725_resume(&data->client->dev); + if (ret < 0) + return ret; + + return len; +} + +static const struct iio_enum mcp4725_powerdown_mode_enum = { + .items = mcp4725_powerdown_modes, + .num_items = ARRAY_SIZE(mcp4725_powerdown_modes), + .get = mcp4725_get_powerdown_mode, + .set = mcp4725_set_powerdown_mode, +}; + +static const struct iio_chan_spec_ext_info mcp4725_ext_info[] = { + { + .name = "powerdown", + .read = mcp4725_read_powerdown, + .write = mcp4725_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp4725_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", &mcp4725_powerdown_mode_enum), + { }, +}; + +static const struct iio_chan_spec mcp4725_channel = { + .type = IIO_VOLTAGE, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .ext_info = mcp4725_ext_info, +}; + +static int mcp4725_set_value(struct iio_dev *indio_dev, int val) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + u8 outbuf[2]; + int ret; + + if (val >= (1 << 12) || val < 0) + return -EINVAL; + + outbuf[0] = (val >> 8) & 0xf; + outbuf[1] = val & 0xff; + + ret = i2c_master_send(data->client, outbuf, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + else + return 0; +} + +static int mcp4725_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = data->dac_value; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = data->vref_mv; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static int mcp4725_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mcp4725_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = mcp4725_set_value(indio_dev, val); + data->dac_value = val; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct iio_info mcp4725_info = { + .read_raw = mcp4725_read_raw, + .write_raw = mcp4725_write_raw, + .attrs = &mcp4725_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int mcp4725_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mcp4725_data *data; + struct iio_dev *indio_dev; + struct mcp4725_platform_data *platform_data = client->dev.platform_data; + u8 inbuf[3]; + u8 pd; + int err; + + if (!platform_data || !platform_data->vref_mv) { + dev_err(&client->dev, "invalid platform data"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &mcp4725_info; + indio_dev->channels = &mcp4725_channel; + indio_dev->num_channels = 1; + indio_dev->modes = INDIO_DIRECT_MODE; + + data->vref_mv = platform_data->vref_mv; + + /* read current DAC value */ + err = i2c_master_recv(client, inbuf, 3); + if (err < 0) { + dev_err(&client->dev, "failed to read DAC value"); + return err; + } + pd = (inbuf[0] >> 1) & 0x3; + data->powerdown = pd > 0 ? true : false; + data->powerdown_mode = pd ? pd-1 : 2; /* 500kohm_to_gnd */ + data->dac_value = (inbuf[1] << 4) | (inbuf[2] >> 4); + + return iio_device_register(indio_dev); +} + +static int mcp4725_remove(struct i2c_client *client) +{ + iio_device_unregister(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id mcp4725_id[] = { + { "mcp4725", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp4725_id); + +static struct i2c_driver mcp4725_driver = { + .driver = { + .name = MCP4725_DRV_NAME, + .pm = MCP4725_PM_OPS, + }, + .probe = mcp4725_probe, + .remove = mcp4725_remove, + .id_table = mcp4725_id, +}; +module_i2c_driver(mcp4725_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("MCP4725 12-bit DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp4922.c b/drivers/iio/dac/mcp4922.c new file mode 100644 index 000000000..92cf4ca69 --- /dev/null +++ b/drivers/iio/dac/mcp4922.c @@ -0,0 +1,216 @@ +/* + * mcp4922.c + * + * Driver for Microchip Digital to Analog Converters. + * Supports MCP4902, MCP4912, and MCP4922. + * + * Copyright (c) 2014 EMAC Inc. + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/regulator/consumer.h> +#include <linux/bitops.h> + +#define MCP4922_NUM_CHANNELS 2 + +enum mcp4922_supported_device_ids { + ID_MCP4902, + ID_MCP4912, + ID_MCP4922, +}; + +struct mcp4922_state { + struct spi_device *spi; + unsigned int value[MCP4922_NUM_CHANNELS]; + unsigned int vref_mv; + struct regulator *vref_reg; + u8 mosi[2] ____cacheline_aligned; +}; + +#define MCP4922_CHAN(chan, bits) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 12 - (bits), \ + }, \ +} + +static int mcp4922_spi_write(struct mcp4922_state *state, u8 addr, u32 val) +{ + state->mosi[1] = val & 0xff; + state->mosi[0] = (addr == 0) ? 0x00 : 0x80; + state->mosi[0] |= 0x30 | ((val >> 8) & 0x0f); + + return spi_write(state->spi, state->mosi, 2); +} + +static int mcp4922_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct mcp4922_state *state = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = state->value[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = state->vref_mv; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int mcp4922_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct mcp4922_state *state = iio_priv(indio_dev); + + if (val2 != 0) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val > GENMASK(chan->scan_type.realbits-1, 0)) + return -EINVAL; + val <<= chan->scan_type.shift; + state->value[chan->channel] = val; + return mcp4922_spi_write(state, chan->channel, val); + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec mcp4922_channels[3][MCP4922_NUM_CHANNELS] = { + [ID_MCP4902] = { MCP4922_CHAN(0, 8), MCP4922_CHAN(1, 8) }, + [ID_MCP4912] = { MCP4922_CHAN(0, 10), MCP4922_CHAN(1, 10) }, + [ID_MCP4922] = { MCP4922_CHAN(0, 12), MCP4922_CHAN(1, 12) }, +}; + +static const struct iio_info mcp4922_info = { + .read_raw = &mcp4922_read_raw, + .write_raw = &mcp4922_write_raw, + .driver_module = THIS_MODULE, +}; + +static int mcp4922_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct mcp4922_state *state; + const struct spi_device_id *id; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); + if (indio_dev == NULL) + return -ENOMEM; + + state = iio_priv(indio_dev); + state->spi = spi; + state->vref_reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(state->vref_reg)) { + dev_err(&spi->dev, "Vref regulator not specified\n"); + return PTR_ERR(state->vref_reg); + } + + ret = regulator_enable(state->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to enable vref regulator: %d\n", + ret); + return ret; + } + + ret = regulator_get_voltage(state->vref_reg); + if (ret < 0) { + dev_err(&spi->dev, "Failed to read vref regulator: %d\n", + ret); + goto error_disable_reg; + } + state->vref_mv = ret / 1000; + + spi_set_drvdata(spi, indio_dev); + id = spi_get_device_id(spi); + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &mcp4922_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mcp4922_channels[id->driver_data]; + indio_dev->num_channels = MCP4922_NUM_CHANNELS; + indio_dev->name = id->name; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device: %d\n", + ret); + goto error_disable_reg; + } + + return 0; + +error_disable_reg: + regulator_disable(state->vref_reg); + + return ret; +} + +static int mcp4922_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct mcp4922_state *state; + + iio_device_unregister(indio_dev); + state = iio_priv(indio_dev); + regulator_disable(state->vref_reg); + + return 0; +} + +static const struct spi_device_id mcp4922_id[] = { + {"mcp4902", ID_MCP4902}, + {"mcp4912", ID_MCP4912}, + {"mcp4922", ID_MCP4922}, + {} +}; +MODULE_DEVICE_TABLE(spi, mcp4922_id); + +static struct spi_driver mcp4922_driver = { + .driver = { + .name = "mcp4922", + .owner = THIS_MODULE, + }, + .probe = mcp4922_probe, + .remove = mcp4922_remove, + .id_table = mcp4922_id, +}; +module_spi_driver(mcp4922_driver); + +MODULE_AUTHOR("Michael Welling <mwelling@ieee.org>"); +MODULE_DESCRIPTION("Microchip MCP4902, MCP4912, MCP4922 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig new file mode 100644 index 000000000..dc5e0b728 --- /dev/null +++ b/drivers/iio/frequency/Kconfig @@ -0,0 +1,42 @@ +# +# Frequency +# Direct Digital Synthesis drivers (DDS) +# Clock Distribution device drivers +# Phase-Locked Loop (PLL) frequency synthesizers +# +# When adding new entries keep the list in alphabetical order + +menu "Frequency Synthesizers DDS/PLL" + +menu "Clock Generator/Distribution" + +config AD9523 + tristate "Analog Devices AD9523 Low Jitter Clock Generator" + depends on SPI + help + Say yes here to build support for Analog Devices AD9523 Low Jitter + Clock Generator. The driver provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad9523. + +endmenu + +# +# Phase-Locked Loop (PLL) frequency synthesizers +# + +menu "Phase-Locked Loop (PLL) frequency synthesizers" + +config ADF4350 + tristate "Analog Devices ADF4350/ADF4351 Wideband Synthesizers" + depends on SPI + help + Say yes here to build support for Analog Devices ADF4350/ADF4351 + Wideband Synthesizers. The driver provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called adf4350. + +endmenu +endmenu diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile new file mode 100644 index 000000000..2bca03f3e --- /dev/null +++ b/drivers/iio/frequency/Makefile @@ -0,0 +1,7 @@ +# +# Makefile iio/frequency +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AD9523) += ad9523.o +obj-$(CONFIG_ADF4350) += adf4350.o diff --git a/drivers/iio/frequency/ad9523.c b/drivers/iio/frequency/ad9523.c new file mode 100644 index 000000000..50ed8d1ca --- /dev/null +++ b/drivers/iio/frequency/ad9523.c @@ -0,0 +1,1040 @@ +/* + * AD9523 SPI Low Jitter Clock Generator + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/frequency/ad9523.h> + +#define AD9523_READ (1 << 15) +#define AD9523_WRITE (0 << 15) +#define AD9523_CNT(x) (((x) - 1) << 13) +#define AD9523_ADDR(x) ((x) & 0xFFF) + +#define AD9523_R1B (1 << 16) +#define AD9523_R2B (2 << 16) +#define AD9523_R3B (3 << 16) +#define AD9523_TRANSF_LEN(x) ((x) >> 16) + +#define AD9523_SERIAL_PORT_CONFIG (AD9523_R1B | 0x0) +#define AD9523_VERSION_REGISTER (AD9523_R1B | 0x2) +#define AD9523_PART_REGISTER (AD9523_R1B | 0x3) +#define AD9523_READBACK_CTRL (AD9523_R1B | 0x4) + +#define AD9523_EEPROM_CUSTOMER_VERSION_ID (AD9523_R2B | 0x6) + +#define AD9523_PLL1_REF_A_DIVIDER (AD9523_R2B | 0x11) +#define AD9523_PLL1_REF_B_DIVIDER (AD9523_R2B | 0x13) +#define AD9523_PLL1_REF_TEST_DIVIDER (AD9523_R1B | 0x14) +#define AD9523_PLL1_FEEDBACK_DIVIDER (AD9523_R2B | 0x17) +#define AD9523_PLL1_CHARGE_PUMP_CTRL (AD9523_R2B | 0x19) +#define AD9523_PLL1_INPUT_RECEIVERS_CTRL (AD9523_R1B | 0x1A) +#define AD9523_PLL1_REF_CTRL (AD9523_R1B | 0x1B) +#define AD9523_PLL1_MISC_CTRL (AD9523_R1B | 0x1C) +#define AD9523_PLL1_LOOP_FILTER_CTRL (AD9523_R1B | 0x1D) + +#define AD9523_PLL2_CHARGE_PUMP (AD9523_R1B | 0xF0) +#define AD9523_PLL2_FEEDBACK_DIVIDER_AB (AD9523_R1B | 0xF1) +#define AD9523_PLL2_CTRL (AD9523_R1B | 0xF2) +#define AD9523_PLL2_VCO_CTRL (AD9523_R1B | 0xF3) +#define AD9523_PLL2_VCO_DIVIDER (AD9523_R1B | 0xF4) +#define AD9523_PLL2_LOOP_FILTER_CTRL (AD9523_R2B | 0xF6) +#define AD9523_PLL2_R2_DIVIDER (AD9523_R1B | 0xF7) + +#define AD9523_CHANNEL_CLOCK_DIST(ch) (AD9523_R3B | (0x192 + 3 * ch)) + +#define AD9523_PLL1_OUTPUT_CTRL (AD9523_R1B | 0x1BA) +#define AD9523_PLL1_OUTPUT_CHANNEL_CTRL (AD9523_R1B | 0x1BB) + +#define AD9523_READBACK_0 (AD9523_R1B | 0x22C) +#define AD9523_READBACK_1 (AD9523_R1B | 0x22D) + +#define AD9523_STATUS_SIGNALS (AD9523_R3B | 0x232) +#define AD9523_POWER_DOWN_CTRL (AD9523_R1B | 0x233) +#define AD9523_IO_UPDATE (AD9523_R1B | 0x234) + +#define AD9523_EEPROM_DATA_XFER_STATUS (AD9523_R1B | 0xB00) +#define AD9523_EEPROM_ERROR_READBACK (AD9523_R1B | 0xB01) +#define AD9523_EEPROM_CTRL1 (AD9523_R1B | 0xB02) +#define AD9523_EEPROM_CTRL2 (AD9523_R1B | 0xB03) + +/* AD9523_SERIAL_PORT_CONFIG */ + +#define AD9523_SER_CONF_SDO_ACTIVE (1 << 7) +#define AD9523_SER_CONF_SOFT_RESET (1 << 5) + +/* AD9523_READBACK_CTRL */ +#define AD9523_READBACK_CTRL_READ_BUFFERED (1 << 0) + +/* AD9523_PLL1_CHARGE_PUMP_CTRL */ +#define AD9523_PLL1_CHARGE_PUMP_CURRENT_nA(x) (((x) / 500) & 0x7F) +#define AD9523_PLL1_CHARGE_PUMP_TRISTATE (1 << 7) +#define AD9523_PLL1_CHARGE_PUMP_MODE_NORMAL (3 << 8) +#define AD9523_PLL1_CHARGE_PUMP_MODE_PUMP_DOWN (2 << 8) +#define AD9523_PLL1_CHARGE_PUMP_MODE_PUMP_UP (1 << 8) +#define AD9523_PLL1_CHARGE_PUMP_MODE_TRISTATE (0 << 8) +#define AD9523_PLL1_BACKLASH_PW_MIN (0 << 10) +#define AD9523_PLL1_BACKLASH_PW_LOW (1 << 10) +#define AD9523_PLL1_BACKLASH_PW_HIGH (2 << 10) +#define AD9523_PLL1_BACKLASH_PW_MAX (3 << 10) + +/* AD9523_PLL1_INPUT_RECEIVERS_CTRL */ +#define AD9523_PLL1_REF_TEST_RCV_EN (1 << 7) +#define AD9523_PLL1_REFB_DIFF_RCV_EN (1 << 6) +#define AD9523_PLL1_REFA_DIFF_RCV_EN (1 << 5) +#define AD9523_PLL1_REFB_RCV_EN (1 << 4) +#define AD9523_PLL1_REFA_RCV_EN (1 << 3) +#define AD9523_PLL1_REFA_REFB_PWR_CTRL_EN (1 << 2) +#define AD9523_PLL1_OSC_IN_CMOS_NEG_INP_EN (1 << 1) +#define AD9523_PLL1_OSC_IN_DIFF_EN (1 << 0) + +/* AD9523_PLL1_REF_CTRL */ +#define AD9523_PLL1_BYPASS_REF_TEST_DIV_EN (1 << 7) +#define AD9523_PLL1_BYPASS_FEEDBACK_DIV_EN (1 << 6) +#define AD9523_PLL1_ZERO_DELAY_MODE_INT (1 << 5) +#define AD9523_PLL1_ZERO_DELAY_MODE_EXT (0 << 5) +#define AD9523_PLL1_OSC_IN_PLL_FEEDBACK_EN (1 << 4) +#define AD9523_PLL1_ZD_IN_CMOS_NEG_INP_EN (1 << 3) +#define AD9523_PLL1_ZD_IN_DIFF_EN (1 << 2) +#define AD9523_PLL1_REFB_CMOS_NEG_INP_EN (1 << 1) +#define AD9523_PLL1_REFA_CMOS_NEG_INP_EN (1 << 0) + +/* AD9523_PLL1_MISC_CTRL */ +#define AD9523_PLL1_REFB_INDEP_DIV_CTRL_EN (1 << 7) +#define AD9523_PLL1_OSC_CTRL_FAIL_VCC_BY2_EN (1 << 6) +#define AD9523_PLL1_REF_MODE(x) ((x) << 2) +#define AD9523_PLL1_BYPASS_REFB_DIV (1 << 1) +#define AD9523_PLL1_BYPASS_REFA_DIV (1 << 0) + +/* AD9523_PLL1_LOOP_FILTER_CTRL */ +#define AD9523_PLL1_LOOP_FILTER_RZERO(x) ((x) & 0xF) + +/* AD9523_PLL2_CHARGE_PUMP */ +#define AD9523_PLL2_CHARGE_PUMP_CURRENT_nA(x) ((x) / 3500) + +/* AD9523_PLL2_FEEDBACK_DIVIDER_AB */ +#define AD9523_PLL2_FB_NDIV_A_CNT(x) (((x) & 0x3) << 6) +#define AD9523_PLL2_FB_NDIV_B_CNT(x) (((x) & 0x3F) << 0) +#define AD9523_PLL2_FB_NDIV(a, b) (4 * (b) + (a)) + +/* AD9523_PLL2_CTRL */ +#define AD9523_PLL2_CHARGE_PUMP_MODE_NORMAL (3 << 0) +#define AD9523_PLL2_CHARGE_PUMP_MODE_PUMP_DOWN (2 << 0) +#define AD9523_PLL2_CHARGE_PUMP_MODE_PUMP_UP (1 << 0) +#define AD9523_PLL2_CHARGE_PUMP_MODE_TRISTATE (0 << 0) +#define AD9523_PLL2_BACKLASH_PW_MIN (0 << 2) +#define AD9523_PLL2_BACKLASH_PW_LOW (1 << 2) +#define AD9523_PLL2_BACKLASH_PW_HIGH (2 << 2) +#define AD9523_PLL2_BACKLASH_PW_MAX (3 << 1) +#define AD9523_PLL2_BACKLASH_CTRL_EN (1 << 4) +#define AD9523_PLL2_FREQ_DOUBLER_EN (1 << 5) +#define AD9523_PLL2_LOCK_DETECT_PWR_DOWN_EN (1 << 7) + +/* AD9523_PLL2_VCO_CTRL */ +#define AD9523_PLL2_VCO_CALIBRATE (1 << 1) +#define AD9523_PLL2_FORCE_VCO_MIDSCALE (1 << 2) +#define AD9523_PLL2_FORCE_REFERENCE_VALID (1 << 3) +#define AD9523_PLL2_FORCE_RELEASE_SYNC (1 << 4) + +/* AD9523_PLL2_VCO_DIVIDER */ +#define AD9523_PLL2_VCO_DIV_M1(x) ((((x) - 3) & 0x3) << 0) +#define AD9523_PLL2_VCO_DIV_M2(x) ((((x) - 3) & 0x3) << 4) +#define AD9523_PLL2_VCO_DIV_M1_PWR_DOWN_EN (1 << 2) +#define AD9523_PLL2_VCO_DIV_M2_PWR_DOWN_EN (1 << 6) + +/* AD9523_PLL2_LOOP_FILTER_CTRL */ +#define AD9523_PLL2_LOOP_FILTER_CPOLE1(x) (((x) & 0x7) << 0) +#define AD9523_PLL2_LOOP_FILTER_RZERO(x) (((x) & 0x7) << 3) +#define AD9523_PLL2_LOOP_FILTER_RPOLE2(x) (((x) & 0x7) << 6) +#define AD9523_PLL2_LOOP_FILTER_RZERO_BYPASS_EN (1 << 8) + +/* AD9523_PLL2_R2_DIVIDER */ +#define AD9523_PLL2_R2_DIVIDER_VAL(x) (((x) & 0x1F) << 0) + +/* AD9523_CHANNEL_CLOCK_DIST */ +#define AD9523_CLK_DIST_DIV_PHASE(x) (((x) & 0x3F) << 18) +#define AD9523_CLK_DIST_DIV_PHASE_REV(x) ((ret >> 18) & 0x3F) +#define AD9523_CLK_DIST_DIV(x) ((((x) - 1) & 0x3FF) << 8) +#define AD9523_CLK_DIST_DIV_REV(x) (((ret >> 8) & 0x3FF) + 1) +#define AD9523_CLK_DIST_INV_DIV_OUTPUT_EN (1 << 7) +#define AD9523_CLK_DIST_IGNORE_SYNC_EN (1 << 6) +#define AD9523_CLK_DIST_PWR_DOWN_EN (1 << 5) +#define AD9523_CLK_DIST_LOW_PWR_MODE_EN (1 << 4) +#define AD9523_CLK_DIST_DRIVER_MODE(x) (((x) & 0xF) << 0) + +/* AD9523_PLL1_OUTPUT_CTRL */ +#define AD9523_PLL1_OUTP_CTRL_VCO_DIV_SEL_CH6_M2 (1 << 7) +#define AD9523_PLL1_OUTP_CTRL_VCO_DIV_SEL_CH5_M2 (1 << 6) +#define AD9523_PLL1_OUTP_CTRL_VCO_DIV_SEL_CH4_M2 (1 << 5) +#define AD9523_PLL1_OUTP_CTRL_CMOS_DRV_WEAK (1 << 4) +#define AD9523_PLL1_OUTP_CTRL_OUTPUT_DIV_1 (0 << 0) +#define AD9523_PLL1_OUTP_CTRL_OUTPUT_DIV_2 (1 << 0) +#define AD9523_PLL1_OUTP_CTRL_OUTPUT_DIV_4 (2 << 0) +#define AD9523_PLL1_OUTP_CTRL_OUTPUT_DIV_8 (4 << 0) +#define AD9523_PLL1_OUTP_CTRL_OUTPUT_DIV_16 (8 << 0) + +/* AD9523_PLL1_OUTPUT_CHANNEL_CTRL */ +#define AD9523_PLL1_OUTP_CH_CTRL_OUTPUT_PWR_DOWN_EN (1 << 7) +#define AD9523_PLL1_OUTP_CH_CTRL_VCO_DIV_SEL_CH9_M2 (1 << 6) +#define AD9523_PLL1_OUTP_CH_CTRL_VCO_DIV_SEL_CH8_M2 (1 << 5) +#define AD9523_PLL1_OUTP_CH_CTRL_VCO_DIV_SEL_CH7_M2 (1 << 4) +#define AD9523_PLL1_OUTP_CH_CTRL_VCXO_SRC_SEL_CH3 (1 << 3) +#define AD9523_PLL1_OUTP_CH_CTRL_VCXO_SRC_SEL_CH2 (1 << 2) +#define AD9523_PLL1_OUTP_CH_CTRL_VCXO_SRC_SEL_CH1 (1 << 1) +#define AD9523_PLL1_OUTP_CH_CTRL_VCXO_SRC_SEL_CH0 (1 << 0) + +/* AD9523_READBACK_0 */ +#define AD9523_READBACK_0_STAT_PLL2_REF_CLK (1 << 7) +#define AD9523_READBACK_0_STAT_PLL2_FB_CLK (1 << 6) +#define AD9523_READBACK_0_STAT_VCXO (1 << 5) +#define AD9523_READBACK_0_STAT_REF_TEST (1 << 4) +#define AD9523_READBACK_0_STAT_REFB (1 << 3) +#define AD9523_READBACK_0_STAT_REFA (1 << 2) +#define AD9523_READBACK_0_STAT_PLL2_LD (1 << 1) +#define AD9523_READBACK_0_STAT_PLL1_LD (1 << 0) + +/* AD9523_READBACK_1 */ +#define AD9523_READBACK_1_HOLDOVER_ACTIVE (1 << 3) +#define AD9523_READBACK_1_AUTOMODE_SEL_REFB (1 << 2) +#define AD9523_READBACK_1_VCO_CALIB_IN_PROGRESS (1 << 0) + +/* AD9523_STATUS_SIGNALS */ +#define AD9523_STATUS_SIGNALS_SYNC_MAN_CTRL (1 << 16) +#define AD9523_STATUS_MONITOR_01_PLL12_LOCKED (0x302) +/* AD9523_POWER_DOWN_CTRL */ +#define AD9523_POWER_DOWN_CTRL_PLL1_PWR_DOWN (1 << 2) +#define AD9523_POWER_DOWN_CTRL_PLL2_PWR_DOWN (1 << 1) +#define AD9523_POWER_DOWN_CTRL_DIST_PWR_DOWN (1 << 0) + +/* AD9523_IO_UPDATE */ +#define AD9523_IO_UPDATE_EN (1 << 0) + +/* AD9523_EEPROM_DATA_XFER_STATUS */ +#define AD9523_EEPROM_DATA_XFER_IN_PROGRESS (1 << 0) + +/* AD9523_EEPROM_ERROR_READBACK */ +#define AD9523_EEPROM_ERROR_READBACK_FAIL (1 << 0) + +/* AD9523_EEPROM_CTRL1 */ +#define AD9523_EEPROM_CTRL1_SOFT_EEPROM (1 << 1) +#define AD9523_EEPROM_CTRL1_EEPROM_WRITE_PROT_DIS (1 << 0) + +/* AD9523_EEPROM_CTRL2 */ +#define AD9523_EEPROM_CTRL2_REG2EEPROM (1 << 0) + +#define AD9523_NUM_CHAN 14 +#define AD9523_NUM_CHAN_ALT_CLK_SRC 10 + +/* Helpers to avoid excess line breaks */ +#define AD_IFE(_pde, _a, _b) ((pdata->_pde) ? _a : _b) +#define AD_IF(_pde, _a) AD_IFE(_pde, _a, 0) + +enum { + AD9523_STAT_PLL1_LD, + AD9523_STAT_PLL2_LD, + AD9523_STAT_REFA, + AD9523_STAT_REFB, + AD9523_STAT_REF_TEST, + AD9523_STAT_VCXO, + AD9523_STAT_PLL2_FB_CLK, + AD9523_STAT_PLL2_REF_CLK, + AD9523_SYNC, + AD9523_EEPROM, +}; + +enum { + AD9523_VCO1, + AD9523_VCO2, + AD9523_VCXO, + AD9523_NUM_CLK_SRC, +}; + +struct ad9523_state { + struct spi_device *spi; + struct regulator *reg; + struct ad9523_platform_data *pdata; + struct iio_chan_spec ad9523_channels[AD9523_NUM_CHAN]; + + unsigned long vcxo_freq; + unsigned long vco_freq; + unsigned long vco_out_freq[AD9523_NUM_CLK_SRC]; + unsigned char vco_out_map[AD9523_NUM_CHAN_ALT_CLK_SRC]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +static int ad9523_read(struct iio_dev *indio_dev, unsigned addr) +{ + struct ad9523_state *st = iio_priv(indio_dev); + int ret; + + /* We encode the register size 1..3 bytes into the register address. + * On transfer we get the size from the register datum, and make sure + * the result is properly aligned. + */ + + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[2], + .len = 2, + }, { + .rx_buf = &st->data[1].d8[4 - AD9523_TRANSF_LEN(addr)], + .len = AD9523_TRANSF_LEN(addr), + }, + }; + + st->data[0].d32 = cpu_to_be32(AD9523_READ | + AD9523_CNT(AD9523_TRANSF_LEN(addr)) | + AD9523_ADDR(addr)); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + dev_err(&indio_dev->dev, "read failed (%d)", ret); + else + ret = be32_to_cpu(st->data[1].d32) & (0xFFFFFF >> + (8 * (3 - AD9523_TRANSF_LEN(addr)))); + + return ret; +}; + +static int ad9523_write(struct iio_dev *indio_dev, unsigned addr, unsigned val) +{ + struct ad9523_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[2], + .len = 2, + }, { + .tx_buf = &st->data[1].d8[4 - AD9523_TRANSF_LEN(addr)], + .len = AD9523_TRANSF_LEN(addr), + }, + }; + + st->data[0].d32 = cpu_to_be32(AD9523_WRITE | + AD9523_CNT(AD9523_TRANSF_LEN(addr)) | + AD9523_ADDR(addr)); + st->data[1].d32 = cpu_to_be32(val); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + + if (ret < 0) + dev_err(&indio_dev->dev, "write failed (%d)", ret); + + return ret; +} + +static int ad9523_io_update(struct iio_dev *indio_dev) +{ + return ad9523_write(indio_dev, AD9523_IO_UPDATE, AD9523_IO_UPDATE_EN); +} + +static int ad9523_vco_out_map(struct iio_dev *indio_dev, + unsigned ch, unsigned out) +{ + struct ad9523_state *st = iio_priv(indio_dev); + int ret; + unsigned mask; + + switch (ch) { + case 0 ... 3: + ret = ad9523_read(indio_dev, AD9523_PLL1_OUTPUT_CHANNEL_CTRL); + if (ret < 0) + break; + mask = AD9523_PLL1_OUTP_CH_CTRL_VCXO_SRC_SEL_CH0 << ch; + if (out) { + ret |= mask; + out = 2; + } else { + ret &= ~mask; + } + ret = ad9523_write(indio_dev, + AD9523_PLL1_OUTPUT_CHANNEL_CTRL, ret); + break; + case 4 ... 6: + ret = ad9523_read(indio_dev, AD9523_PLL1_OUTPUT_CTRL); + if (ret < 0) + break; + mask = AD9523_PLL1_OUTP_CTRL_VCO_DIV_SEL_CH4_M2 << (ch - 4); + if (out) + ret |= mask; + else + ret &= ~mask; + ret = ad9523_write(indio_dev, AD9523_PLL1_OUTPUT_CTRL, ret); + break; + case 7 ... 9: + ret = ad9523_read(indio_dev, AD9523_PLL1_OUTPUT_CHANNEL_CTRL); + if (ret < 0) + break; + mask = AD9523_PLL1_OUTP_CH_CTRL_VCO_DIV_SEL_CH7_M2 << (ch - 7); + if (out) + ret |= mask; + else + ret &= ~mask; + ret = ad9523_write(indio_dev, + AD9523_PLL1_OUTPUT_CHANNEL_CTRL, ret); + break; + default: + return 0; + } + + st->vco_out_map[ch] = out; + + return ret; +} + +static int ad9523_set_clock_provider(struct iio_dev *indio_dev, + unsigned ch, unsigned long freq) +{ + struct ad9523_state *st = iio_priv(indio_dev); + long tmp1, tmp2; + bool use_alt_clk_src; + + switch (ch) { + case 0 ... 3: + use_alt_clk_src = (freq == st->vco_out_freq[AD9523_VCXO]); + break; + case 4 ... 9: + tmp1 = st->vco_out_freq[AD9523_VCO1] / freq; + tmp2 = st->vco_out_freq[AD9523_VCO2] / freq; + tmp1 *= freq; + tmp2 *= freq; + use_alt_clk_src = (abs(tmp1 - freq) > abs(tmp2 - freq)); + break; + default: + /* Ch 10..14: No action required, return success */ + return 0; + } + + return ad9523_vco_out_map(indio_dev, ch, use_alt_clk_src); +} + +static int ad9523_store_eeprom(struct iio_dev *indio_dev) +{ + int ret, tmp; + + ret = ad9523_write(indio_dev, AD9523_EEPROM_CTRL1, + AD9523_EEPROM_CTRL1_EEPROM_WRITE_PROT_DIS); + if (ret < 0) + return ret; + ret = ad9523_write(indio_dev, AD9523_EEPROM_CTRL2, + AD9523_EEPROM_CTRL2_REG2EEPROM); + if (ret < 0) + return ret; + + tmp = 4; + do { + msleep(20); + ret = ad9523_read(indio_dev, + AD9523_EEPROM_DATA_XFER_STATUS); + if (ret < 0) + return ret; + } while ((ret & AD9523_EEPROM_DATA_XFER_IN_PROGRESS) && tmp--); + + ret = ad9523_write(indio_dev, AD9523_EEPROM_CTRL1, 0); + if (ret < 0) + return ret; + + ret = ad9523_read(indio_dev, AD9523_EEPROM_ERROR_READBACK); + if (ret < 0) + return ret; + + if (ret & AD9523_EEPROM_ERROR_READBACK_FAIL) { + dev_err(&indio_dev->dev, "Verify EEPROM failed"); + ret = -EIO; + } + + return ret; +} + +static int ad9523_sync(struct iio_dev *indio_dev) +{ + int ret, tmp; + + ret = ad9523_read(indio_dev, AD9523_STATUS_SIGNALS); + if (ret < 0) + return ret; + + tmp = ret; + tmp |= AD9523_STATUS_SIGNALS_SYNC_MAN_CTRL; + + ret = ad9523_write(indio_dev, AD9523_STATUS_SIGNALS, tmp); + if (ret < 0) + return ret; + + ad9523_io_update(indio_dev); + tmp &= ~AD9523_STATUS_SIGNALS_SYNC_MAN_CTRL; + + ret = ad9523_write(indio_dev, AD9523_STATUS_SIGNALS, tmp); + if (ret < 0) + return ret; + + return ad9523_io_update(indio_dev); +} + +static ssize_t ad9523_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + bool state; + int ret; + + ret = strtobool(buf, &state); + if (ret < 0) + return ret; + + if (!state) + return 0; + + mutex_lock(&indio_dev->mlock); + switch ((u32)this_attr->address) { + case AD9523_SYNC: + ret = ad9523_sync(indio_dev); + break; + case AD9523_EEPROM: + ret = ad9523_store_eeprom(indio_dev); + break; + default: + ret = -ENODEV; + } + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static ssize_t ad9523_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + + mutex_lock(&indio_dev->mlock); + ret = ad9523_read(indio_dev, AD9523_READBACK_0); + if (ret >= 0) { + ret = sprintf(buf, "%d\n", !!(ret & (1 << + (u32)this_attr->address))); + } + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static IIO_DEVICE_ATTR(pll1_locked, S_IRUGO, + ad9523_show, + NULL, + AD9523_STAT_PLL1_LD); + +static IIO_DEVICE_ATTR(pll2_locked, S_IRUGO, + ad9523_show, + NULL, + AD9523_STAT_PLL2_LD); + +static IIO_DEVICE_ATTR(pll1_reference_clk_a_present, S_IRUGO, + ad9523_show, + NULL, + AD9523_STAT_REFA); + +static IIO_DEVICE_ATTR(pll1_reference_clk_b_present, S_IRUGO, + ad9523_show, + NULL, + AD9523_STAT_REFB); + +static IIO_DEVICE_ATTR(pll1_reference_clk_test_present, S_IRUGO, + ad9523_show, + NULL, + AD9523_STAT_REF_TEST); + +static IIO_DEVICE_ATTR(vcxo_clk_present, S_IRUGO, + ad9523_show, + NULL, + AD9523_STAT_VCXO); + +static IIO_DEVICE_ATTR(pll2_feedback_clk_present, S_IRUGO, + ad9523_show, + NULL, + AD9523_STAT_PLL2_FB_CLK); + +static IIO_DEVICE_ATTR(pll2_reference_clk_present, S_IRUGO, + ad9523_show, + NULL, + AD9523_STAT_PLL2_REF_CLK); + +static IIO_DEVICE_ATTR(sync_dividers, S_IWUSR, + NULL, + ad9523_store, + AD9523_SYNC); + +static IIO_DEVICE_ATTR(store_eeprom, S_IWUSR, + NULL, + ad9523_store, + AD9523_EEPROM); + +static struct attribute *ad9523_attributes[] = { + &iio_dev_attr_sync_dividers.dev_attr.attr, + &iio_dev_attr_store_eeprom.dev_attr.attr, + &iio_dev_attr_pll2_feedback_clk_present.dev_attr.attr, + &iio_dev_attr_pll2_reference_clk_present.dev_attr.attr, + &iio_dev_attr_pll1_reference_clk_a_present.dev_attr.attr, + &iio_dev_attr_pll1_reference_clk_b_present.dev_attr.attr, + &iio_dev_attr_pll1_reference_clk_test_present.dev_attr.attr, + &iio_dev_attr_vcxo_clk_present.dev_attr.attr, + &iio_dev_attr_pll1_locked.dev_attr.attr, + &iio_dev_attr_pll2_locked.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad9523_attribute_group = { + .attrs = ad9523_attributes, +}; + +static int ad9523_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad9523_state *st = iio_priv(indio_dev); + unsigned code; + int ret; + + mutex_lock(&indio_dev->mlock); + ret = ad9523_read(indio_dev, AD9523_CHANNEL_CLOCK_DIST(chan->channel)); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + *val = !(ret & AD9523_CLK_DIST_PWR_DOWN_EN); + return IIO_VAL_INT; + case IIO_CHAN_INFO_FREQUENCY: + *val = st->vco_out_freq[st->vco_out_map[chan->channel]] / + AD9523_CLK_DIST_DIV_REV(ret); + return IIO_VAL_INT; + case IIO_CHAN_INFO_PHASE: + code = (AD9523_CLK_DIST_DIV_PHASE_REV(ret) * 3141592) / + AD9523_CLK_DIST_DIV_REV(ret); + *val = code / 1000000; + *val2 = (code % 1000000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +}; + +static int ad9523_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad9523_state *st = iio_priv(indio_dev); + unsigned reg; + int ret, tmp, code; + + mutex_lock(&indio_dev->mlock); + ret = ad9523_read(indio_dev, AD9523_CHANNEL_CLOCK_DIST(chan->channel)); + if (ret < 0) + goto out; + + reg = ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val) + reg &= ~AD9523_CLK_DIST_PWR_DOWN_EN; + else + reg |= AD9523_CLK_DIST_PWR_DOWN_EN; + break; + case IIO_CHAN_INFO_FREQUENCY: + if (val <= 0) { + ret = -EINVAL; + goto out; + } + ret = ad9523_set_clock_provider(indio_dev, chan->channel, val); + if (ret < 0) + goto out; + tmp = st->vco_out_freq[st->vco_out_map[chan->channel]] / val; + tmp = clamp(tmp, 1, 1024); + reg &= ~(0x3FF << 8); + reg |= AD9523_CLK_DIST_DIV(tmp); + break; + case IIO_CHAN_INFO_PHASE: + code = val * 1000000 + val2 % 1000000; + tmp = (code * AD9523_CLK_DIST_DIV_REV(ret)) / 3141592; + tmp = clamp(tmp, 0, 63); + reg &= ~AD9523_CLK_DIST_DIV_PHASE(~0); + reg |= AD9523_CLK_DIST_DIV_PHASE(tmp); + break; + default: + ret = -EINVAL; + goto out; + } + + ret = ad9523_write(indio_dev, AD9523_CHANNEL_CLOCK_DIST(chan->channel), + reg); + if (ret < 0) + goto out; + + ad9523_io_update(indio_dev); +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static int ad9523_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + int ret; + + mutex_lock(&indio_dev->mlock); + if (readval == NULL) { + ret = ad9523_write(indio_dev, reg | AD9523_R1B, writeval); + ad9523_io_update(indio_dev); + } else { + ret = ad9523_read(indio_dev, reg | AD9523_R1B); + if (ret < 0) + goto out_unlock; + *readval = ret; + ret = 0; + } + +out_unlock: + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static const struct iio_info ad9523_info = { + .read_raw = &ad9523_read_raw, + .write_raw = &ad9523_write_raw, + .debugfs_reg_access = &ad9523_reg_access, + .attrs = &ad9523_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ad9523_setup(struct iio_dev *indio_dev) +{ + struct ad9523_state *st = iio_priv(indio_dev); + struct ad9523_platform_data *pdata = st->pdata; + struct ad9523_channel_spec *chan; + unsigned long active_mask = 0; + int ret, i; + + ret = ad9523_write(indio_dev, AD9523_SERIAL_PORT_CONFIG, + AD9523_SER_CONF_SOFT_RESET | + (st->spi->mode & SPI_3WIRE ? 0 : + AD9523_SER_CONF_SDO_ACTIVE)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_READBACK_CTRL, + AD9523_READBACK_CTRL_READ_BUFFERED); + if (ret < 0) + return ret; + + ret = ad9523_io_update(indio_dev); + if (ret < 0) + return ret; + + /* + * PLL1 Setup + */ + ret = ad9523_write(indio_dev, AD9523_PLL1_REF_A_DIVIDER, + pdata->refa_r_div); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_REF_B_DIVIDER, + pdata->refb_r_div); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_FEEDBACK_DIVIDER, + pdata->pll1_feedback_div); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_CHARGE_PUMP_CTRL, + AD9523_PLL1_CHARGE_PUMP_CURRENT_nA(pdata-> + pll1_charge_pump_current_nA) | + AD9523_PLL1_CHARGE_PUMP_MODE_NORMAL | + AD9523_PLL1_BACKLASH_PW_MIN); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_INPUT_RECEIVERS_CTRL, + AD_IF(refa_diff_rcv_en, AD9523_PLL1_REFA_RCV_EN) | + AD_IF(refb_diff_rcv_en, AD9523_PLL1_REFB_RCV_EN) | + AD_IF(osc_in_diff_en, AD9523_PLL1_OSC_IN_DIFF_EN) | + AD_IF(osc_in_cmos_neg_inp_en, + AD9523_PLL1_OSC_IN_CMOS_NEG_INP_EN) | + AD_IF(refa_diff_rcv_en, AD9523_PLL1_REFA_DIFF_RCV_EN) | + AD_IF(refb_diff_rcv_en, AD9523_PLL1_REFB_DIFF_RCV_EN)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_REF_CTRL, + AD_IF(zd_in_diff_en, AD9523_PLL1_ZD_IN_DIFF_EN) | + AD_IF(zd_in_cmos_neg_inp_en, + AD9523_PLL1_ZD_IN_CMOS_NEG_INP_EN) | + AD_IF(zero_delay_mode_internal_en, + AD9523_PLL1_ZERO_DELAY_MODE_INT) | + AD_IF(osc_in_feedback_en, AD9523_PLL1_OSC_IN_PLL_FEEDBACK_EN) | + AD_IF(refa_cmos_neg_inp_en, AD9523_PLL1_REFA_CMOS_NEG_INP_EN) | + AD_IF(refb_cmos_neg_inp_en, AD9523_PLL1_REFB_CMOS_NEG_INP_EN)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_MISC_CTRL, + AD9523_PLL1_REFB_INDEP_DIV_CTRL_EN | + AD9523_PLL1_REF_MODE(pdata->ref_mode)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL1_LOOP_FILTER_CTRL, + AD9523_PLL1_LOOP_FILTER_RZERO(pdata->pll1_loop_filter_rzero)); + if (ret < 0) + return ret; + /* + * PLL2 Setup + */ + + ret = ad9523_write(indio_dev, AD9523_PLL2_CHARGE_PUMP, + AD9523_PLL2_CHARGE_PUMP_CURRENT_nA(pdata-> + pll2_charge_pump_current_nA)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL2_FEEDBACK_DIVIDER_AB, + AD9523_PLL2_FB_NDIV_A_CNT(pdata->pll2_ndiv_a_cnt) | + AD9523_PLL2_FB_NDIV_B_CNT(pdata->pll2_ndiv_b_cnt)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL2_CTRL, + AD9523_PLL2_CHARGE_PUMP_MODE_NORMAL | + AD9523_PLL2_BACKLASH_CTRL_EN | + AD_IF(pll2_freq_doubler_en, AD9523_PLL2_FREQ_DOUBLER_EN)); + if (ret < 0) + return ret; + + st->vco_freq = (pdata->vcxo_freq * (pdata->pll2_freq_doubler_en ? 2 : 1) + / pdata->pll2_r2_div) * AD9523_PLL2_FB_NDIV(pdata-> + pll2_ndiv_a_cnt, pdata->pll2_ndiv_b_cnt); + + ret = ad9523_write(indio_dev, AD9523_PLL2_VCO_CTRL, + AD9523_PLL2_VCO_CALIBRATE); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL2_VCO_DIVIDER, + AD9523_PLL2_VCO_DIV_M1(pdata->pll2_vco_diff_m1) | + AD9523_PLL2_VCO_DIV_M2(pdata->pll2_vco_diff_m2) | + AD_IFE(pll2_vco_diff_m1, 0, + AD9523_PLL2_VCO_DIV_M1_PWR_DOWN_EN) | + AD_IFE(pll2_vco_diff_m2, 0, + AD9523_PLL2_VCO_DIV_M2_PWR_DOWN_EN)); + if (ret < 0) + return ret; + + if (pdata->pll2_vco_diff_m1) + st->vco_out_freq[AD9523_VCO1] = + st->vco_freq / pdata->pll2_vco_diff_m1; + + if (pdata->pll2_vco_diff_m2) + st->vco_out_freq[AD9523_VCO2] = + st->vco_freq / pdata->pll2_vco_diff_m2; + + st->vco_out_freq[AD9523_VCXO] = pdata->vcxo_freq; + + ret = ad9523_write(indio_dev, AD9523_PLL2_R2_DIVIDER, + AD9523_PLL2_R2_DIVIDER_VAL(pdata->pll2_r2_div)); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_PLL2_LOOP_FILTER_CTRL, + AD9523_PLL2_LOOP_FILTER_CPOLE1(pdata->cpole1) | + AD9523_PLL2_LOOP_FILTER_RZERO(pdata->rzero) | + AD9523_PLL2_LOOP_FILTER_RPOLE2(pdata->rpole2) | + AD_IF(rzero_bypass_en, + AD9523_PLL2_LOOP_FILTER_RZERO_BYPASS_EN)); + if (ret < 0) + return ret; + + for (i = 0; i < pdata->num_channels; i++) { + chan = &pdata->channels[i]; + if (chan->channel_num < AD9523_NUM_CHAN) { + __set_bit(chan->channel_num, &active_mask); + ret = ad9523_write(indio_dev, + AD9523_CHANNEL_CLOCK_DIST(chan->channel_num), + AD9523_CLK_DIST_DRIVER_MODE(chan->driver_mode) | + AD9523_CLK_DIST_DIV(chan->channel_divider) | + AD9523_CLK_DIST_DIV_PHASE(chan->divider_phase) | + (chan->sync_ignore_en ? + AD9523_CLK_DIST_IGNORE_SYNC_EN : 0) | + (chan->divider_output_invert_en ? + AD9523_CLK_DIST_INV_DIV_OUTPUT_EN : 0) | + (chan->low_power_mode_en ? + AD9523_CLK_DIST_LOW_PWR_MODE_EN : 0) | + (chan->output_dis ? + AD9523_CLK_DIST_PWR_DOWN_EN : 0)); + if (ret < 0) + return ret; + + ret = ad9523_vco_out_map(indio_dev, chan->channel_num, + chan->use_alt_clock_src); + if (ret < 0) + return ret; + + st->ad9523_channels[i].type = IIO_ALTVOLTAGE; + st->ad9523_channels[i].output = 1; + st->ad9523_channels[i].indexed = 1; + st->ad9523_channels[i].channel = chan->channel_num; + st->ad9523_channels[i].extend_name = + chan->extended_name; + st->ad9523_channels[i].info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PHASE) | + BIT(IIO_CHAN_INFO_FREQUENCY); + } + } + + for_each_clear_bit(i, &active_mask, AD9523_NUM_CHAN) + ad9523_write(indio_dev, + AD9523_CHANNEL_CLOCK_DIST(i), + AD9523_CLK_DIST_DRIVER_MODE(TRISTATE) | + AD9523_CLK_DIST_PWR_DOWN_EN); + + ret = ad9523_write(indio_dev, AD9523_POWER_DOWN_CTRL, 0); + if (ret < 0) + return ret; + + ret = ad9523_write(indio_dev, AD9523_STATUS_SIGNALS, + AD9523_STATUS_MONITOR_01_PLL12_LOCKED); + if (ret < 0) + return ret; + + ret = ad9523_io_update(indio_dev); + if (ret < 0) + return ret; + + return 0; +} + +static int ad9523_probe(struct spi_device *spi) +{ + struct ad9523_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev; + struct ad9523_state *st; + int ret; + + if (!pdata) { + dev_err(&spi->dev, "no platform data?\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + } + + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + st->pdata = pdata; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = (pdata->name[0] != 0) ? pdata->name : + spi_get_device_id(spi)->name; + indio_dev->info = &ad9523_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->ad9523_channels; + indio_dev->num_channels = pdata->num_channels; + + ret = ad9523_setup(indio_dev); + if (ret < 0) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + dev_info(&spi->dev, "probed %s\n", indio_dev->name); + + return 0; + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ad9523_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad9523_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad9523_id[] = { + {"ad9523-1", 9523}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad9523_id); + +static struct spi_driver ad9523_driver = { + .driver = { + .name = "ad9523", + .owner = THIS_MODULE, + }, + .probe = ad9523_probe, + .remove = ad9523_remove, + .id_table = ad9523_id, +}; +module_spi_driver(ad9523_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Analog Devices AD9523 CLOCKDIST/PLL"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/frequency/adf4350.c b/drivers/iio/frequency/adf4350.c new file mode 100644 index 000000000..10a0dfc3b --- /dev/null +++ b/drivers/iio/frequency/adf4350.c @@ -0,0 +1,639 @@ +/* + * ADF4350/ADF4351 SPI Wideband Synthesizer driver + * + * Copyright 2012-2013 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/gcd.h> +#include <linux/gpio.h> +#include <asm/div64.h> +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_gpio.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/frequency/adf4350.h> + +enum { + ADF4350_FREQ, + ADF4350_FREQ_REFIN, + ADF4350_FREQ_RESOLUTION, + ADF4350_PWRDOWN, +}; + +struct adf4350_state { + struct spi_device *spi; + struct regulator *reg; + struct adf4350_platform_data *pdata; + struct clk *clk; + unsigned long clkin; + unsigned long chspc; /* Channel Spacing */ + unsigned long fpfd; /* Phase Frequency Detector */ + unsigned long min_out_freq; + unsigned r0_fract; + unsigned r0_int; + unsigned r1_mod; + unsigned r4_rf_div_sel; + unsigned long regs[6]; + unsigned long regs_hw[6]; + unsigned long long freq_req; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __be32 val ____cacheline_aligned; +}; + +static struct adf4350_platform_data default_pdata = { + .channel_spacing = 10000, + .r2_user_settings = ADF4350_REG2_PD_POLARITY_POS | + ADF4350_REG2_CHARGE_PUMP_CURR_uA(2500), + .r3_user_settings = ADF4350_REG3_12BIT_CLKDIV_MODE(0), + .r4_user_settings = ADF4350_REG4_OUTPUT_PWR(3) | + ADF4350_REG4_MUTE_TILL_LOCK_EN, + .gpio_lock_detect = -1, +}; + +static int adf4350_sync_config(struct adf4350_state *st) +{ + int ret, i, doublebuf = 0; + + for (i = ADF4350_REG5; i >= ADF4350_REG0; i--) { + if ((st->regs_hw[i] != st->regs[i]) || + ((i == ADF4350_REG0) && doublebuf)) { + + switch (i) { + case ADF4350_REG1: + case ADF4350_REG4: + doublebuf = 1; + break; + } + + st->val = cpu_to_be32(st->regs[i] | i); + ret = spi_write(st->spi, &st->val, 4); + if (ret < 0) + return ret; + st->regs_hw[i] = st->regs[i]; + dev_dbg(&st->spi->dev, "[%d] 0x%X\n", + i, (u32)st->regs[i] | i); + } + } + return 0; +} + +static int adf4350_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct adf4350_state *st = iio_priv(indio_dev); + int ret; + + if (reg > ADF4350_REG5) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + if (readval == NULL) { + st->regs[reg] = writeval & ~(BIT(0) | BIT(1) | BIT(2)); + ret = adf4350_sync_config(st); + } else { + *readval = st->regs_hw[reg]; + ret = 0; + } + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int adf4350_tune_r_cnt(struct adf4350_state *st, unsigned short r_cnt) +{ + struct adf4350_platform_data *pdata = st->pdata; + + do { + r_cnt++; + st->fpfd = (st->clkin * (pdata->ref_doubler_en ? 2 : 1)) / + (r_cnt * (pdata->ref_div2_en ? 2 : 1)); + } while (st->fpfd > ADF4350_MAX_FREQ_PFD); + + return r_cnt; +} + +static int adf4350_set_freq(struct adf4350_state *st, unsigned long long freq) +{ + struct adf4350_platform_data *pdata = st->pdata; + u64 tmp; + u32 div_gcd, prescaler, chspc; + u16 mdiv, r_cnt = 0; + u8 band_sel_div; + + if (freq > ADF4350_MAX_OUT_FREQ || freq < st->min_out_freq) + return -EINVAL; + + if (freq > ADF4350_MAX_FREQ_45_PRESC) { + prescaler = ADF4350_REG1_PRESCALER; + mdiv = 75; + } else { + prescaler = 0; + mdiv = 23; + } + + st->r4_rf_div_sel = 0; + + while (freq < ADF4350_MIN_VCO_FREQ) { + freq <<= 1; + st->r4_rf_div_sel++; + } + + /* + * Allow a predefined reference division factor + * if not set, compute our own + */ + if (pdata->ref_div_factor) + r_cnt = pdata->ref_div_factor - 1; + + chspc = st->chspc; + + do { + do { + do { + r_cnt = adf4350_tune_r_cnt(st, r_cnt); + st->r1_mod = st->fpfd / chspc; + if (r_cnt > ADF4350_MAX_R_CNT) { + /* try higher spacing values */ + chspc++; + r_cnt = 0; + } + } while ((st->r1_mod > ADF4350_MAX_MODULUS) && r_cnt); + } while (r_cnt == 0); + + tmp = freq * (u64)st->r1_mod + (st->fpfd >> 1); + do_div(tmp, st->fpfd); /* Div round closest (n + d/2)/d */ + st->r0_fract = do_div(tmp, st->r1_mod); + st->r0_int = tmp; + } while (mdiv > st->r0_int); + + band_sel_div = DIV_ROUND_UP(st->fpfd, ADF4350_MAX_BANDSEL_CLK); + + if (st->r0_fract && st->r1_mod) { + div_gcd = gcd(st->r1_mod, st->r0_fract); + st->r1_mod /= div_gcd; + st->r0_fract /= div_gcd; + } else { + st->r0_fract = 0; + st->r1_mod = 1; + } + + dev_dbg(&st->spi->dev, "VCO: %llu Hz, PFD %lu Hz\n" + "REF_DIV %d, R0_INT %d, R0_FRACT %d\n" + "R1_MOD %d, RF_DIV %d\nPRESCALER %s, BAND_SEL_DIV %d\n", + freq, st->fpfd, r_cnt, st->r0_int, st->r0_fract, st->r1_mod, + 1 << st->r4_rf_div_sel, prescaler ? "8/9" : "4/5", + band_sel_div); + + st->regs[ADF4350_REG0] = ADF4350_REG0_INT(st->r0_int) | + ADF4350_REG0_FRACT(st->r0_fract); + + st->regs[ADF4350_REG1] = ADF4350_REG1_PHASE(1) | + ADF4350_REG1_MOD(st->r1_mod) | + prescaler; + + st->regs[ADF4350_REG2] = + ADF4350_REG2_10BIT_R_CNT(r_cnt) | + ADF4350_REG2_DOUBLE_BUFF_EN | + (pdata->ref_doubler_en ? ADF4350_REG2_RMULT2_EN : 0) | + (pdata->ref_div2_en ? ADF4350_REG2_RDIV2_EN : 0) | + (pdata->r2_user_settings & (ADF4350_REG2_PD_POLARITY_POS | + ADF4350_REG2_LDP_6ns | ADF4350_REG2_LDF_INT_N | + ADF4350_REG2_CHARGE_PUMP_CURR_uA(5000) | + ADF4350_REG2_MUXOUT(0x7) | ADF4350_REG2_NOISE_MODE(0x3))); + + st->regs[ADF4350_REG3] = pdata->r3_user_settings & + (ADF4350_REG3_12BIT_CLKDIV(0xFFF) | + ADF4350_REG3_12BIT_CLKDIV_MODE(0x3) | + ADF4350_REG3_12BIT_CSR_EN | + ADF4351_REG3_CHARGE_CANCELLATION_EN | + ADF4351_REG3_ANTI_BACKLASH_3ns_EN | + ADF4351_REG3_BAND_SEL_CLOCK_MODE_HIGH); + + st->regs[ADF4350_REG4] = + ADF4350_REG4_FEEDBACK_FUND | + ADF4350_REG4_RF_DIV_SEL(st->r4_rf_div_sel) | + ADF4350_REG4_8BIT_BAND_SEL_CLKDIV(band_sel_div) | + ADF4350_REG4_RF_OUT_EN | + (pdata->r4_user_settings & + (ADF4350_REG4_OUTPUT_PWR(0x3) | + ADF4350_REG4_AUX_OUTPUT_PWR(0x3) | + ADF4350_REG4_AUX_OUTPUT_EN | + ADF4350_REG4_AUX_OUTPUT_FUND | + ADF4350_REG4_MUTE_TILL_LOCK_EN)); + + st->regs[ADF4350_REG5] = ADF4350_REG5_LD_PIN_MODE_DIGITAL; + st->freq_req = freq; + + return adf4350_sync_config(st); +} + +static ssize_t adf4350_write(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct adf4350_state *st = iio_priv(indio_dev); + unsigned long long readin; + unsigned long tmp; + int ret; + + ret = kstrtoull(buf, 10, &readin); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + switch ((u32)private) { + case ADF4350_FREQ: + ret = adf4350_set_freq(st, readin); + break; + case ADF4350_FREQ_REFIN: + if (readin > ADF4350_MAX_FREQ_REFIN) { + ret = -EINVAL; + break; + } + + if (st->clk) { + tmp = clk_round_rate(st->clk, readin); + if (tmp != readin) { + ret = -EINVAL; + break; + } + ret = clk_set_rate(st->clk, tmp); + if (ret < 0) + break; + } + st->clkin = readin; + ret = adf4350_set_freq(st, st->freq_req); + break; + case ADF4350_FREQ_RESOLUTION: + if (readin == 0) + ret = -EINVAL; + else + st->chspc = readin; + break; + case ADF4350_PWRDOWN: + if (readin) + st->regs[ADF4350_REG2] |= ADF4350_REG2_POWER_DOWN_EN; + else + st->regs[ADF4350_REG2] &= ~ADF4350_REG2_POWER_DOWN_EN; + + adf4350_sync_config(st); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static ssize_t adf4350_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct adf4350_state *st = iio_priv(indio_dev); + unsigned long long val; + int ret = 0; + + mutex_lock(&indio_dev->mlock); + switch ((u32)private) { + case ADF4350_FREQ: + val = (u64)((st->r0_int * st->r1_mod) + st->r0_fract) * + (u64)st->fpfd; + do_div(val, st->r1_mod * (1 << st->r4_rf_div_sel)); + /* PLL unlocked? return error */ + if (gpio_is_valid(st->pdata->gpio_lock_detect)) + if (!gpio_get_value(st->pdata->gpio_lock_detect)) { + dev_dbg(&st->spi->dev, "PLL un-locked\n"); + ret = -EBUSY; + } + break; + case ADF4350_FREQ_REFIN: + if (st->clk) + st->clkin = clk_get_rate(st->clk); + + val = st->clkin; + break; + case ADF4350_FREQ_RESOLUTION: + val = st->chspc; + break; + case ADF4350_PWRDOWN: + val = !!(st->regs[ADF4350_REG2] & ADF4350_REG2_POWER_DOWN_EN); + break; + default: + ret = -EINVAL; + val = 0; + } + mutex_unlock(&indio_dev->mlock); + + return ret < 0 ? ret : sprintf(buf, "%llu\n", val); +} + +#define _ADF4350_EXT_INFO(_name, _ident) { \ + .name = _name, \ + .read = adf4350_read, \ + .write = adf4350_write, \ + .private = _ident, \ + .shared = IIO_SEPARATE, \ +} + +static const struct iio_chan_spec_ext_info adf4350_ext_info[] = { + /* Ideally we use IIO_CHAN_INFO_FREQUENCY, but there are + * values > 2^32 in order to support the entire frequency range + * in Hz. Using scale is a bit ugly. + */ + _ADF4350_EXT_INFO("frequency", ADF4350_FREQ), + _ADF4350_EXT_INFO("frequency_resolution", ADF4350_FREQ_RESOLUTION), + _ADF4350_EXT_INFO("refin_frequency", ADF4350_FREQ_REFIN), + _ADF4350_EXT_INFO("powerdown", ADF4350_PWRDOWN), + { }, +}; + +static const struct iio_chan_spec adf4350_chan = { + .type = IIO_ALTVOLTAGE, + .indexed = 1, + .output = 1, + .ext_info = adf4350_ext_info, +}; + +static const struct iio_info adf4350_info = { + .debugfs_reg_access = &adf4350_reg_access, + .driver_module = THIS_MODULE, +}; + +#ifdef CONFIG_OF +static struct adf4350_platform_data *adf4350_parse_dt(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct adf4350_platform_data *pdata; + unsigned int tmp; + int ret; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + strncpy(&pdata->name[0], np->name, SPI_NAME_SIZE - 1); + + tmp = 10000; + of_property_read_u32(np, "adi,channel-spacing", &tmp); + pdata->channel_spacing = tmp; + + tmp = 0; + of_property_read_u32(np, "adi,power-up-frequency", &tmp); + pdata->power_up_frequency = tmp; + + tmp = 0; + of_property_read_u32(np, "adi,reference-div-factor", &tmp); + pdata->ref_div_factor = tmp; + + ret = of_get_gpio(np, 0); + if (ret < 0) + pdata->gpio_lock_detect = -1; + else + pdata->gpio_lock_detect = ret; + + pdata->ref_doubler_en = of_property_read_bool(np, + "adi,reference-doubler-enable"); + pdata->ref_div2_en = of_property_read_bool(np, + "adi,reference-div2-enable"); + + /* r2_user_settings */ + pdata->r2_user_settings = of_property_read_bool(np, + "adi,phase-detector-polarity-positive-enable") ? + ADF4350_REG2_PD_POLARITY_POS : 0; + pdata->r2_user_settings |= of_property_read_bool(np, + "adi,lock-detect-precision-6ns-enable") ? + ADF4350_REG2_LDP_6ns : 0; + pdata->r2_user_settings |= of_property_read_bool(np, + "adi,lock-detect-function-integer-n-enable") ? + ADF4350_REG2_LDF_INT_N : 0; + + tmp = 2500; + of_property_read_u32(np, "adi,charge-pump-current", &tmp); + pdata->r2_user_settings |= ADF4350_REG2_CHARGE_PUMP_CURR_uA(tmp); + + tmp = 0; + of_property_read_u32(np, "adi,muxout-select", &tmp); + pdata->r2_user_settings |= ADF4350_REG2_MUXOUT(tmp); + + pdata->r2_user_settings |= of_property_read_bool(np, + "adi,low-spur-mode-enable") ? + ADF4350_REG2_NOISE_MODE(0x3) : 0; + + /* r3_user_settings */ + + pdata->r3_user_settings = of_property_read_bool(np, + "adi,cycle-slip-reduction-enable") ? + ADF4350_REG3_12BIT_CSR_EN : 0; + pdata->r3_user_settings |= of_property_read_bool(np, + "adi,charge-cancellation-enable") ? + ADF4351_REG3_CHARGE_CANCELLATION_EN : 0; + + pdata->r3_user_settings |= of_property_read_bool(np, + "adi,anti-backlash-3ns-enable") ? + ADF4351_REG3_ANTI_BACKLASH_3ns_EN : 0; + pdata->r3_user_settings |= of_property_read_bool(np, + "adi,band-select-clock-mode-high-enable") ? + ADF4351_REG3_BAND_SEL_CLOCK_MODE_HIGH : 0; + + tmp = 0; + of_property_read_u32(np, "adi,12bit-clk-divider", &tmp); + pdata->r3_user_settings |= ADF4350_REG3_12BIT_CLKDIV(tmp); + + tmp = 0; + of_property_read_u32(np, "adi,clk-divider-mode", &tmp); + pdata->r3_user_settings |= ADF4350_REG3_12BIT_CLKDIV_MODE(tmp); + + /* r4_user_settings */ + + pdata->r4_user_settings = of_property_read_bool(np, + "adi,aux-output-enable") ? + ADF4350_REG4_AUX_OUTPUT_EN : 0; + pdata->r4_user_settings |= of_property_read_bool(np, + "adi,aux-output-fundamental-enable") ? + ADF4350_REG4_AUX_OUTPUT_FUND : 0; + pdata->r4_user_settings |= of_property_read_bool(np, + "adi,mute-till-lock-enable") ? + ADF4350_REG4_MUTE_TILL_LOCK_EN : 0; + + tmp = 0; + of_property_read_u32(np, "adi,output-power", &tmp); + pdata->r4_user_settings |= ADF4350_REG4_OUTPUT_PWR(tmp); + + tmp = 0; + of_property_read_u32(np, "adi,aux-output-power", &tmp); + pdata->r4_user_settings |= ADF4350_REG4_AUX_OUTPUT_PWR(tmp); + + return pdata; +} +#else +static +struct adf4350_platform_data *adf4350_parse_dt(struct device *dev) +{ + return NULL; +} +#endif + +static int adf4350_probe(struct spi_device *spi) +{ + struct adf4350_platform_data *pdata; + struct iio_dev *indio_dev; + struct adf4350_state *st; + struct clk *clk = NULL; + int ret; + + if (spi->dev.of_node) { + pdata = adf4350_parse_dt(&spi->dev); + if (pdata == NULL) + return -EINVAL; + } else { + pdata = spi->dev.platform_data; + } + + if (!pdata) { + dev_warn(&spi->dev, "no platform data? using default\n"); + pdata = &default_pdata; + } + + if (!pdata->clkin) { + clk = devm_clk_get(&spi->dev, "clkin"); + if (IS_ERR(clk)) + return -EPROBE_DEFER; + + ret = clk_prepare_enable(clk); + if (ret < 0) + return ret; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_disable_clk; + } + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_disable_clk; + } + + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + st->pdata = pdata; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = (pdata->name[0] != 0) ? pdata->name : + spi_get_device_id(spi)->name; + + indio_dev->info = &adf4350_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &adf4350_chan; + indio_dev->num_channels = 1; + + st->chspc = pdata->channel_spacing; + if (clk) { + st->clk = clk; + st->clkin = clk_get_rate(clk); + } else { + st->clkin = pdata->clkin; + } + + st->min_out_freq = spi_get_device_id(spi)->driver_data == 4351 ? + ADF4351_MIN_OUT_FREQ : ADF4350_MIN_OUT_FREQ; + + memset(st->regs_hw, 0xFF, sizeof(st->regs_hw)); + + if (gpio_is_valid(pdata->gpio_lock_detect)) { + ret = devm_gpio_request(&spi->dev, pdata->gpio_lock_detect, + indio_dev->name); + if (ret) { + dev_err(&spi->dev, "fail to request lock detect GPIO-%d", + pdata->gpio_lock_detect); + goto error_disable_reg; + } + gpio_direction_input(pdata->gpio_lock_detect); + } + + if (pdata->power_up_frequency) { + ret = adf4350_set_freq(st, pdata->power_up_frequency); + if (ret) + goto error_disable_reg; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_disable_clk: + if (clk) + clk_disable_unprepare(clk); + + return ret; +} + +static int adf4350_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adf4350_state *st = iio_priv(indio_dev); + struct regulator *reg = st->reg; + + st->regs[ADF4350_REG2] |= ADF4350_REG2_POWER_DOWN_EN; + adf4350_sync_config(st); + + iio_device_unregister(indio_dev); + + if (st->clk) + clk_disable_unprepare(st->clk); + + if (!IS_ERR(reg)) + regulator_disable(reg); + + return 0; +} + +static const struct spi_device_id adf4350_id[] = { + {"adf4350", 4350}, + {"adf4351", 4351}, + {} +}; + +static struct spi_driver adf4350_driver = { + .driver = { + .name = "adf4350", + .owner = THIS_MODULE, + }, + .probe = adf4350_probe, + .remove = adf4350_remove, + .id_table = adf4350_id, +}; +module_spi_driver(adf4350_driver); + +MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>"); +MODULE_DESCRIPTION("Analog Devices ADF4350/ADF4351 PLL"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/Kconfig b/drivers/iio/gyro/Kconfig new file mode 100644 index 000000000..b3d0e94f7 --- /dev/null +++ b/drivers/iio/gyro/Kconfig @@ -0,0 +1,112 @@ +# +# IIO Digital Gyroscope Sensor drivers configuration +# +# When adding new entries keep the list in alphabetical order + +menu "Digital gyroscope sensors" + +config ADIS16080 + tristate "Analog Devices ADIS16080/100 Yaw Rate Gyroscope with SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADIS16080, ADIS16100 Yaw + Rate Gyroscope with SPI. + +config ADIS16130 + tristate "Analog Devices ADIS16130 High Precision Angular Rate Sensor driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADIS16130 High Precision + Angular Rate Sensor driver. + +config ADIS16136 + tristate "Analog devices ADIS16136 and similar gyroscopes driver" + depends on SPI_MASTER + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say yes here to build support for the Analog Devices ADIS16133, ADIS16135, + ADIS16136 gyroscope devices. + +config ADIS16260 + tristate "Analog Devices ADIS16260 Digital Gyroscope Sensor SPI driver" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say yes here to build support for Analog Devices ADIS16260 ADIS16265 + ADIS16250 ADIS16255 and ADIS16251 programmable digital gyroscope sensors. + + This driver can also be built as a module. If so, the module + will be called adis16260. + +config ADXRS450 + tristate "Analog Devices ADXRS450/3 Digital Output Gyroscope SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADXRS450 and ADXRS453 + programmable digital output gyroscope. + + This driver can also be built as a module. If so, the module + will be called adxrs450. + +config BMG160 + tristate "BOSCH BMG160 Gyro Sensor" + depends on I2C + select IIO_TRIGGERED_BUFFER if IIO_BUFFER + help + Say yes here to build support for Bosch BMG160 Tri-axis Gyro Sensor + driver. This driver also supports BMI055 gyroscope. + + This driver can also be built as a module. If so, the module + will be called bmg160. + +config HID_SENSOR_GYRO_3D + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID Gyroscope 3D" + help + Say yes here to build support for the HID SENSOR + Gyroscope 3D. + +config IIO_ST_GYRO_3AXIS + tristate "STMicroelectronics gyroscopes 3-Axis Driver" + depends on (I2C || SPI_MASTER) && SYSFS + select IIO_ST_SENSORS_CORE + select IIO_ST_GYRO_I2C_3AXIS if (I2C) + select IIO_ST_GYRO_SPI_3AXIS if (SPI_MASTER) + select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) + help + Say yes here to build support for STMicroelectronics gyroscopes: + L3G4200D, LSM330DL, L3GD20, LSM330DLC, L3G4IS, LSM330. + + This driver can also be built as a module. If so, these modules + will be created: + - st_gyro (core functions for the driver [it is mandatory]); + - st_gyro_i2c (necessary for the I2C devices [optional*]); + - st_gyro_spi (necessary for the SPI devices [optional*]); + + (*) one of these is necessary to do something. + +config IIO_ST_GYRO_I2C_3AXIS + tristate + depends on IIO_ST_GYRO_3AXIS + depends on IIO_ST_SENSORS_I2C + +config IIO_ST_GYRO_SPI_3AXIS + tristate + depends on IIO_ST_GYRO_3AXIS + depends on IIO_ST_SENSORS_SPI + +config ITG3200 + tristate "InvenSense ITG3200 Digital 3-Axis Gyroscope I2C driver" + depends on I2C + select IIO_TRIGGERED_BUFFER if IIO_BUFFER + help + Say yes here to add support for the InvenSense ITG3200 digital + 3-axis gyroscope sensor. + +endmenu diff --git a/drivers/iio/gyro/Makefile b/drivers/iio/gyro/Makefile new file mode 100644 index 000000000..f46341b39 --- /dev/null +++ b/drivers/iio/gyro/Makefile @@ -0,0 +1,26 @@ +# +# Makefile for industrial I/O gyroscope sensor drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ADIS16080) += adis16080.o +obj-$(CONFIG_ADIS16130) += adis16130.o +obj-$(CONFIG_ADIS16136) += adis16136.o +obj-$(CONFIG_ADIS16260) += adis16260.o +obj-$(CONFIG_ADXRS450) += adxrs450.o +obj-$(CONFIG_BMG160) += bmg160.o + +obj-$(CONFIG_HID_SENSOR_GYRO_3D) += hid-sensor-gyro-3d.o + +itg3200-y := itg3200_core.o +itg3200-$(CONFIG_IIO_BUFFER) += itg3200_buffer.o +obj-$(CONFIG_ITG3200) += itg3200.o + +obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_gyro_sensor.o + +obj-$(CONFIG_IIO_ST_GYRO_3AXIS) += st_gyro.o +st_gyro-y := st_gyro_core.o +st_gyro-$(CONFIG_IIO_BUFFER) += st_gyro_buffer.o + +obj-$(CONFIG_IIO_ST_GYRO_I2C_3AXIS) += st_gyro_i2c.o +obj-$(CONFIG_IIO_ST_GYRO_SPI_3AXIS) += st_gyro_spi.o diff --git a/drivers/iio/gyro/adis16080.c b/drivers/iio/gyro/adis16080.c new file mode 100644 index 000000000..add509837 --- /dev/null +++ b/drivers/iio/gyro/adis16080.c @@ -0,0 +1,241 @@ +/* + * ADIS16080/100 Yaw Rate Gyroscope with SPI driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define ADIS16080_DIN_GYRO (0 << 10) /* Gyroscope output */ +#define ADIS16080_DIN_TEMP (1 << 10) /* Temperature output */ +#define ADIS16080_DIN_AIN1 (2 << 10) +#define ADIS16080_DIN_AIN2 (3 << 10) + +/* + * 1: Write contents on DIN to control register. + * 0: No changes to control register. + */ + +#define ADIS16080_DIN_WRITE (1 << 15) + +struct adis16080_chip_info { + int scale_val; + int scale_val2; +}; + +/** + * struct adis16080_state - device instance specific data + * @us: actual spi_device to write data + * @info: chip specific parameters + * @buf: transmit or receive buffer + **/ +struct adis16080_state { + struct spi_device *us; + const struct adis16080_chip_info *info; + + __be16 buf ____cacheline_aligned; +}; + +static int adis16080_read_sample(struct iio_dev *indio_dev, + u16 addr, int *val) +{ + struct adis16080_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->buf, + .len = 2, + .cs_change = 1, + }, { + .rx_buf = &st->buf, + .len = 2, + }, + }; + + st->buf = cpu_to_be16(addr | ADIS16080_DIN_WRITE); + + ret = spi_sync_transfer(st->us, t, ARRAY_SIZE(t)); + if (ret == 0) + *val = sign_extend32(be16_to_cpu(st->buf), 11); + + return ret; +} + +static int adis16080_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct adis16080_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + ret = adis16080_read_sample(indio_dev, chan->address, val); + mutex_unlock(&indio_dev->mlock); + return ret ? ret : IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = st->info->scale_val; + *val2 = st->info->scale_val2; + return IIO_VAL_FRACTIONAL; + case IIO_VOLTAGE: + /* VREF = 5V, 12 bits */ + *val = 5000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_TEMP: + /* 85 C = 585, 25 C = 0 */ + *val = 85000 - 25000; + *val2 = 585; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_VOLTAGE: + /* 2.5 V = 0 */ + *val = 2048; + return IIO_VAL_INT; + case IIO_TEMP: + /* 85 C = 585, 25 C = 0 */ + *val = DIV_ROUND_CLOSEST(25 * 585, 85 - 25); + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec adis16080_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = ADIS16080_DIN_GYRO, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = ADIS16080_DIN_AIN1, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = ADIS16080_DIN_AIN2, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = ADIS16080_DIN_TEMP, + } +}; + +static const struct iio_info adis16080_info = { + .read_raw = &adis16080_read_raw, + .driver_module = THIS_MODULE, +}; + +enum { + ID_ADIS16080, + ID_ADIS16100, +}; + +static const struct adis16080_chip_info adis16080_chip_info[] = { + [ID_ADIS16080] = { + /* 80 degree = 819, 819 rad = 46925 degree */ + .scale_val = 80, + .scale_val2 = 46925, + }, + [ID_ADIS16100] = { + /* 300 degree = 1230, 1230 rad = 70474 degree */ + .scale_val = 300, + .scale_val2 = 70474, + }, +}; + +static int adis16080_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct adis16080_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + /* Allocate the comms buffers */ + st->us = spi; + st->info = &adis16080_chip_info[id->driver_data]; + + indio_dev->name = spi->dev.driver->name; + indio_dev->channels = adis16080_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16080_channels); + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16080_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + return iio_device_register(indio_dev); +} + +static int adis16080_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + return 0; +} + +static const struct spi_device_id adis16080_ids[] = { + { "adis16080", ID_ADIS16080 }, + { "adis16100", ID_ADIS16100 }, + {}, +}; +MODULE_DEVICE_TABLE(spi, adis16080_ids); + +static struct spi_driver adis16080_driver = { + .driver = { + .name = "adis16080", + .owner = THIS_MODULE, + }, + .probe = adis16080_probe, + .remove = adis16080_remove, + .id_table = adis16080_ids, +}; +module_spi_driver(adis16080_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16080/100 Yaw Rate Gyroscope Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/adis16130.c b/drivers/iio/gyro/adis16130.c new file mode 100644 index 000000000..8d08c7ed1 --- /dev/null +++ b/drivers/iio/gyro/adis16130.c @@ -0,0 +1,179 @@ +/* + * ADIS16130 Digital Output, High Precision Angular Rate Sensor driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/mutex.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> + +#define ADIS16130_CON 0x0 +#define ADIS16130_CON_RD (1 << 6) +#define ADIS16130_IOP 0x1 + +/* 1 = data-ready signal low when unread data on all channels; */ +#define ADIS16130_IOP_ALL_RDY (1 << 3) +#define ADIS16130_IOP_SYNC (1 << 0) /* 1 = synchronization enabled */ +#define ADIS16130_RATEDATA 0x8 /* Gyroscope output, rate of rotation */ +#define ADIS16130_TEMPDATA 0xA /* Temperature output */ +#define ADIS16130_RATECS 0x28 /* Gyroscope channel setup */ +#define ADIS16130_RATECS_EN (1 << 3) /* 1 = channel enable; */ +#define ADIS16130_TEMPCS 0x2A /* Temperature channel setup */ +#define ADIS16130_TEMPCS_EN (1 << 3) +#define ADIS16130_RATECONV 0x30 +#define ADIS16130_TEMPCONV 0x32 +#define ADIS16130_MODE 0x38 +#define ADIS16130_MODE_24BIT (1 << 1) /* 1 = 24-bit resolution; */ + +/** + * struct adis16130_state - device instance specific data + * @us: actual spi_device to write data + * @buf_lock: mutex to protect tx and rx + * @buf: unified tx/rx buffer + **/ +struct adis16130_state { + struct spi_device *us; + struct mutex buf_lock; + u8 buf[4] ____cacheline_aligned; +}; + +static int adis16130_spi_read(struct iio_dev *indio_dev, u8 reg_addr, u32 *val) +{ + int ret; + struct adis16130_state *st = iio_priv(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->buf, + .rx_buf = st->buf, + .len = 4, + }; + + mutex_lock(&st->buf_lock); + + st->buf[0] = ADIS16130_CON_RD | reg_addr; + st->buf[1] = st->buf[2] = st->buf[3] = 0; + + ret = spi_sync_transfer(st->us, &xfer, 1); + if (ret == 0) + *val = (st->buf[1] << 16) | (st->buf[2] << 8) | st->buf[3]; + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int adis16130_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + int ret; + u32 temp; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + ret = adis16130_spi_read(indio_dev, chan->address, &temp); + mutex_unlock(&indio_dev->mlock); + if (ret) + return ret; + *val = temp; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + /* 0 degree = 838860, 250 degree = 14260608 */ + *val = 250; + *val2 = 336440817; /* RAD_TO_DEGREE(14260608 - 8388608) */ + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + /* 0C = 8036283, 105C = 9516048 */ + *val = 105000; + *val2 = 9516048 - 8036283; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = -8388608; + return IIO_VAL_INT; + case IIO_TEMP: + *val = -8036283; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static const struct iio_chan_spec adis16130_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = ADIS16130_RATEDATA, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = ADIS16130_TEMPDATA, + } +}; + +static const struct iio_info adis16130_info = { + .read_raw = &adis16130_read_raw, + .driver_module = THIS_MODULE, +}; + +static int adis16130_probe(struct spi_device *spi) +{ + struct adis16130_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + indio_dev->name = spi->dev.driver->name; + indio_dev->channels = adis16130_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16130_channels); + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16130_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static struct spi_driver adis16130_driver = { + .driver = { + .name = "adis16130", + .owner = THIS_MODULE, + }, + .probe = adis16130_probe, +}; +module_spi_driver(adis16130_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16130 High Precision Angular Rate"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16130"); diff --git a/drivers/iio/gyro/adis16136.c b/drivers/iio/gyro/adis16136.c new file mode 100644 index 000000000..591bd555e --- /dev/null +++ b/drivers/iio/gyro/adis16136.c @@ -0,0 +1,577 @@ +/* + * ADIS16133/ADIS16135/ADIS16136 gyroscope driver + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/imu/adis.h> + +#include <linux/debugfs.h> + +#define ADIS16136_REG_FLASH_CNT 0x00 +#define ADIS16136_REG_TEMP_OUT 0x02 +#define ADIS16136_REG_GYRO_OUT2 0x04 +#define ADIS16136_REG_GYRO_OUT 0x06 +#define ADIS16136_REG_GYRO_OFF2 0x08 +#define ADIS16136_REG_GYRO_OFF 0x0A +#define ADIS16136_REG_ALM_MAG1 0x10 +#define ADIS16136_REG_ALM_MAG2 0x12 +#define ADIS16136_REG_ALM_SAMPL1 0x14 +#define ADIS16136_REG_ALM_SAMPL2 0x16 +#define ADIS16136_REG_ALM_CTRL 0x18 +#define ADIS16136_REG_GPIO_CTRL 0x1A +#define ADIS16136_REG_MSC_CTRL 0x1C +#define ADIS16136_REG_SMPL_PRD 0x1E +#define ADIS16136_REG_AVG_CNT 0x20 +#define ADIS16136_REG_DEC_RATE 0x22 +#define ADIS16136_REG_SLP_CTRL 0x24 +#define ADIS16136_REG_DIAG_STAT 0x26 +#define ADIS16136_REG_GLOB_CMD 0x28 +#define ADIS16136_REG_LOT1 0x32 +#define ADIS16136_REG_LOT2 0x34 +#define ADIS16136_REG_LOT3 0x36 +#define ADIS16136_REG_PROD_ID 0x38 +#define ADIS16136_REG_SERIAL_NUM 0x3A + +#define ADIS16136_DIAG_STAT_FLASH_UPDATE_FAIL 2 +#define ADIS16136_DIAG_STAT_SPI_FAIL 3 +#define ADIS16136_DIAG_STAT_SELF_TEST_FAIL 5 +#define ADIS16136_DIAG_STAT_FLASH_CHKSUM_FAIL 6 + +#define ADIS16136_MSC_CTRL_MEMORY_TEST BIT(11) +#define ADIS16136_MSC_CTRL_SELF_TEST BIT(10) + +struct adis16136_chip_info { + unsigned int precision; + unsigned int fullscale; +}; + +struct adis16136 { + const struct adis16136_chip_info *chip_info; + + struct adis adis; +}; + +#ifdef CONFIG_DEBUG_FS + +static ssize_t adis16136_show_serial(struct file *file, + char __user *userbuf, size_t count, loff_t *ppos) +{ + struct adis16136 *adis16136 = file->private_data; + uint16_t lot1, lot2, lot3, serial; + char buf[20]; + size_t len; + int ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_SERIAL_NUM, + &serial); + if (ret < 0) + return ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_LOT1, &lot1); + if (ret < 0) + return ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_LOT2, &lot2); + if (ret < 0) + return ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_LOT3, &lot3); + if (ret < 0) + return ret; + + len = snprintf(buf, sizeof(buf), "%.4x%.4x%.4x-%.4x\n", lot1, lot2, + lot3, serial); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16136_serial_fops = { + .open = simple_open, + .read = adis16136_show_serial, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static int adis16136_show_product_id(void *arg, u64 *val) +{ + struct adis16136 *adis16136 = arg; + u16 prod_id; + int ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_PROD_ID, + &prod_id); + if (ret < 0) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(adis16136_product_id_fops, + adis16136_show_product_id, NULL, "%llu\n"); + +static int adis16136_show_flash_count(void *arg, u64 *val) +{ + struct adis16136 *adis16136 = arg; + uint16_t flash_count; + int ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_FLASH_CNT, + &flash_count); + if (ret < 0) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(adis16136_flash_count_fops, + adis16136_show_flash_count, NULL, "%lld\n"); + +static int adis16136_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + + debugfs_create_file("serial_number", 0400, indio_dev->debugfs_dentry, + adis16136, &adis16136_serial_fops); + debugfs_create_file("product_id", 0400, indio_dev->debugfs_dentry, + adis16136, &adis16136_product_id_fops); + debugfs_create_file("flash_count", 0400, indio_dev->debugfs_dentry, + adis16136, &adis16136_flash_count_fops); + + return 0; +} + +#else + +static int adis16136_debugfs_init(struct iio_dev *indio_dev) +{ + return 0; +} + +#endif + +static int adis16136_set_freq(struct adis16136 *adis16136, unsigned int freq) +{ + unsigned int t; + + t = 32768 / freq; + if (t < 0xf) + t = 0xf; + else if (t > 0xffff) + t = 0xffff; + else + t--; + + return adis_write_reg_16(&adis16136->adis, ADIS16136_REG_SMPL_PRD, t); +} + +static int adis16136_get_freq(struct adis16136 *adis16136, unsigned int *freq) +{ + uint16_t t; + int ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_SMPL_PRD, &t); + if (ret < 0) + return ret; + + *freq = 32768 / (t + 1); + + return 0; +} + +static ssize_t adis16136_write_frequency(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis16136 *adis16136 = iio_priv(indio_dev); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + if (val == 0) + return -EINVAL; + + ret = adis16136_set_freq(adis16136, val); + + return ret ? ret : len; +} + +static ssize_t adis16136_read_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis16136 *adis16136 = iio_priv(indio_dev); + unsigned int freq; + int ret; + + ret = adis16136_get_freq(adis16136, &freq); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", freq); +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + adis16136_read_frequency, + adis16136_write_frequency); + +static const unsigned adis16136_3db_divisors[] = { + [0] = 2, /* Special case */ + [1] = 6, + [2] = 12, + [3] = 25, + [4] = 50, + [5] = 100, + [6] = 200, + [7] = 200, /* Not a valid setting */ +}; + +static int adis16136_set_filter(struct iio_dev *indio_dev, int val) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + unsigned int freq; + int i, ret; + + ret = adis16136_get_freq(adis16136, &freq); + if (ret < 0) + return ret; + + for (i = ARRAY_SIZE(adis16136_3db_divisors) - 1; i >= 1; i--) { + if (freq / adis16136_3db_divisors[i] >= val) + break; + } + + return adis_write_reg_16(&adis16136->adis, ADIS16136_REG_AVG_CNT, i); +} + +static int adis16136_get_filter(struct iio_dev *indio_dev, int *val) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + unsigned int freq; + uint16_t val16; + int ret; + + mutex_lock(&indio_dev->mlock); + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_AVG_CNT, &val16); + if (ret < 0) + goto err_unlock; + + ret = adis16136_get_freq(adis16136, &freq); + if (ret < 0) + goto err_unlock; + + *val = freq / adis16136_3db_divisors[val16 & 0x07]; + +err_unlock: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : IIO_VAL_INT; +} + +static int adis16136_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + uint32_t val32; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = adis16136->chip_info->precision; + *val2 = (adis16136->chip_info->fullscale << 16); + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + *val = 10; + *val2 = 697000; /* 0.010697 degree Celsius */ + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + ret = adis_read_reg_32(&adis16136->adis, + ADIS16136_REG_GYRO_OFF2, &val32); + if (ret < 0) + return ret; + + *val = sign_extend32(val32, 31); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return adis16136_get_filter(indio_dev, val); + default: + return -EINVAL; + } +} + +static int adis16136_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, long info) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_CALIBBIAS: + return adis_write_reg_32(&adis16136->adis, + ADIS16136_REG_GYRO_OFF2, val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return adis16136_set_filter(indio_dev, val); + default: + break; + } + + return -EINVAL; +} + +enum { + ADIS16136_SCAN_GYRO, + ADIS16136_SCAN_TEMP, +}; + +static const struct iio_chan_spec adis16136_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + + .address = ADIS16136_REG_GYRO_OUT2, + .scan_index = ADIS16136_SCAN_GYRO, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = ADIS16136_REG_TEMP_OUT, + .scan_index = ADIS16136_SCAN_TEMP, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static struct attribute *adis16136_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16136_attribute_group = { + .attrs = adis16136_attributes, +}; + +static const struct iio_info adis16136_info = { + .driver_module = THIS_MODULE, + .attrs = &adis16136_attribute_group, + .read_raw = &adis16136_read_raw, + .write_raw = &adis16136_write_raw, + .update_scan_mode = adis_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, +}; + +static int adis16136_stop_device(struct iio_dev *indio_dev) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + int ret; + + ret = adis_write_reg_16(&adis16136->adis, ADIS16136_REG_SLP_CTRL, 0xff); + if (ret) + dev_err(&indio_dev->dev, + "Could not power down device: %d\n", ret); + + return ret; +} + +static int adis16136_initial_setup(struct iio_dev *indio_dev) +{ + struct adis16136 *adis16136 = iio_priv(indio_dev); + unsigned int device_id; + uint16_t prod_id; + int ret; + + ret = adis_initial_startup(&adis16136->adis); + if (ret) + return ret; + + ret = adis_read_reg_16(&adis16136->adis, ADIS16136_REG_PROD_ID, + &prod_id); + if (ret) + return ret; + + sscanf(indio_dev->name, "adis%u\n", &device_id); + + if (prod_id != device_id) + dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", + device_id, prod_id); + + return 0; +} + +static const char * const adis16136_status_error_msgs[] = { + [ADIS16136_DIAG_STAT_FLASH_UPDATE_FAIL] = "Flash update failed", + [ADIS16136_DIAG_STAT_SPI_FAIL] = "SPI failure", + [ADIS16136_DIAG_STAT_SELF_TEST_FAIL] = "Self test error", + [ADIS16136_DIAG_STAT_FLASH_CHKSUM_FAIL] = "Flash checksum error", +}; + +static const struct adis_data adis16136_data = { + .diag_stat_reg = ADIS16136_REG_DIAG_STAT, + .glob_cmd_reg = ADIS16136_REG_GLOB_CMD, + .msc_ctrl_reg = ADIS16136_REG_MSC_CTRL, + + .self_test_mask = ADIS16136_MSC_CTRL_SELF_TEST, + .startup_delay = 80, + + .read_delay = 10, + .write_delay = 10, + + .status_error_msgs = adis16136_status_error_msgs, + .status_error_mask = BIT(ADIS16136_DIAG_STAT_FLASH_UPDATE_FAIL) | + BIT(ADIS16136_DIAG_STAT_SPI_FAIL) | + BIT(ADIS16136_DIAG_STAT_SELF_TEST_FAIL) | + BIT(ADIS16136_DIAG_STAT_FLASH_CHKSUM_FAIL), +}; + +enum adis16136_id { + ID_ADIS16133, + ID_ADIS16135, + ID_ADIS16136, +}; + +static const struct adis16136_chip_info adis16136_chip_info[] = { + [ID_ADIS16133] = { + .precision = IIO_DEGREE_TO_RAD(1200), + .fullscale = 24000, + }, + [ID_ADIS16135] = { + .precision = IIO_DEGREE_TO_RAD(300), + .fullscale = 24000, + }, + [ID_ADIS16136] = { + .precision = IIO_DEGREE_TO_RAD(450), + .fullscale = 24623, + }, +}; + +static int adis16136_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct adis16136 *adis16136; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adis16136)); + if (indio_dev == NULL) + return -ENOMEM; + + spi_set_drvdata(spi, indio_dev); + + adis16136 = iio_priv(indio_dev); + + adis16136->chip_info = &adis16136_chip_info[id->driver_data]; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = adis16136_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16136_channels); + indio_dev->info = &adis16136_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(&adis16136->adis, indio_dev, spi, &adis16136_data); + if (ret) + return ret; + + ret = adis_setup_buffer_and_trigger(&adis16136->adis, indio_dev, NULL); + if (ret) + return ret; + + ret = adis16136_initial_setup(indio_dev); + if (ret) + goto error_cleanup_buffer; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_stop_device; + + adis16136_debugfs_init(indio_dev); + + return 0; + +error_stop_device: + adis16136_stop_device(indio_dev); +error_cleanup_buffer: + adis_cleanup_buffer_and_trigger(&adis16136->adis, indio_dev); + return ret; +} + +static int adis16136_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis16136 *adis16136 = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis16136_stop_device(indio_dev); + + adis_cleanup_buffer_and_trigger(&adis16136->adis, indio_dev); + + return 0; +} + +static const struct spi_device_id adis16136_ids[] = { + { "adis16133", ID_ADIS16133 }, + { "adis16135", ID_ADIS16135 }, + { "adis16136", ID_ADIS16136 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16136_ids); + +static struct spi_driver adis16136_driver = { + .driver = { + .name = "adis16136", + .owner = THIS_MODULE, + }, + .id_table = adis16136_ids, + .probe = adis16136_probe, + .remove = adis16136_remove, +}; +module_spi_driver(adis16136_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices ADIS16133/ADIS16135/ADIS16136 gyroscope driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/adis16260.c b/drivers/iio/gyro/adis16260.c new file mode 100644 index 000000000..75fe0edd3 --- /dev/null +++ b/drivers/iio/gyro/adis16260.c @@ -0,0 +1,389 @@ +/* + * ADIS16260/ADIS16265 Programmable Digital Gyroscope Sensor Driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/sysfs.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/imu/adis.h> + +#define ADIS16260_STARTUP_DELAY 220 /* ms */ + +#define ADIS16260_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16260_SUPPLY_OUT 0x02 /* Power supply measurement */ +#define ADIS16260_GYRO_OUT 0x04 /* X-axis gyroscope output */ +#define ADIS16260_AUX_ADC 0x0A /* analog input channel measurement */ +#define ADIS16260_TEMP_OUT 0x0C /* internal temperature measurement */ +#define ADIS16260_ANGL_OUT 0x0E /* angle displacement */ +#define ADIS16260_GYRO_OFF 0x14 /* Calibration, offset/bias adjustment */ +#define ADIS16260_GYRO_SCALE 0x16 /* Calibration, scale adjustment */ +#define ADIS16260_ALM_MAG1 0x20 /* Alarm 1 magnitude/polarity setting */ +#define ADIS16260_ALM_MAG2 0x22 /* Alarm 2 magnitude/polarity setting */ +#define ADIS16260_ALM_SMPL1 0x24 /* Alarm 1 dynamic rate of change setting */ +#define ADIS16260_ALM_SMPL2 0x26 /* Alarm 2 dynamic rate of change setting */ +#define ADIS16260_ALM_CTRL 0x28 /* Alarm control */ +#define ADIS16260_AUX_DAC 0x30 /* Auxiliary DAC data */ +#define ADIS16260_GPIO_CTRL 0x32 /* Control, digital I/O line */ +#define ADIS16260_MSC_CTRL 0x34 /* Control, data ready, self-test settings */ +#define ADIS16260_SMPL_PRD 0x36 /* Control, internal sample rate */ +#define ADIS16260_SENS_AVG 0x38 /* Control, dynamic range, filtering */ +#define ADIS16260_SLP_CNT 0x3A /* Control, sleep mode initiation */ +#define ADIS16260_DIAG_STAT 0x3C /* Diagnostic, error flags */ +#define ADIS16260_GLOB_CMD 0x3E /* Control, global commands */ +#define ADIS16260_LOT_ID1 0x52 /* Lot Identification Code 1 */ +#define ADIS16260_LOT_ID2 0x54 /* Lot Identification Code 2 */ +#define ADIS16260_PROD_ID 0x56 /* Product identifier; + * convert to decimal = 16,265/16,260 */ +#define ADIS16260_SERIAL_NUM 0x58 /* Serial number */ + +#define ADIS16260_ERROR_ACTIVE (1<<14) +#define ADIS16260_NEW_DATA (1<<15) + +/* MSC_CTRL */ +#define ADIS16260_MSC_CTRL_MEM_TEST (1<<11) +/* Internal self-test enable */ +#define ADIS16260_MSC_CTRL_INT_SELF_TEST (1<<10) +#define ADIS16260_MSC_CTRL_NEG_SELF_TEST (1<<9) +#define ADIS16260_MSC_CTRL_POS_SELF_TEST (1<<8) +#define ADIS16260_MSC_CTRL_DATA_RDY_EN (1<<2) +#define ADIS16260_MSC_CTRL_DATA_RDY_POL_HIGH (1<<1) +#define ADIS16260_MSC_CTRL_DATA_RDY_DIO2 (1<<0) + +/* SMPL_PRD */ +/* Time base (tB): 0 = 1.953 ms, 1 = 60.54 ms */ +#define ADIS16260_SMPL_PRD_TIME_BASE (1<<7) +#define ADIS16260_SMPL_PRD_DIV_MASK 0x7F + +/* SLP_CNT */ +#define ADIS16260_SLP_CNT_POWER_OFF 0x80 + +/* DIAG_STAT */ +#define ADIS16260_DIAG_STAT_ALARM2 (1<<9) +#define ADIS16260_DIAG_STAT_ALARM1 (1<<8) +#define ADIS16260_DIAG_STAT_FLASH_CHK_BIT 6 +#define ADIS16260_DIAG_STAT_SELF_TEST_BIT 5 +#define ADIS16260_DIAG_STAT_OVERFLOW_BIT 4 +#define ADIS16260_DIAG_STAT_SPI_FAIL_BIT 3 +#define ADIS16260_DIAG_STAT_FLASH_UPT_BIT 2 +#define ADIS16260_DIAG_STAT_POWER_HIGH_BIT 1 +#define ADIS16260_DIAG_STAT_POWER_LOW_BIT 0 + +/* GLOB_CMD */ +#define ADIS16260_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16260_GLOB_CMD_FLASH_UPD (1<<3) +#define ADIS16260_GLOB_CMD_DAC_LATCH (1<<2) +#define ADIS16260_GLOB_CMD_FAC_CALIB (1<<1) +#define ADIS16260_GLOB_CMD_AUTO_NULL (1<<0) + +#define ADIS16260_SPI_SLOW (u32)(300 * 1000) +#define ADIS16260_SPI_BURST (u32)(1000 * 1000) +#define ADIS16260_SPI_FAST (u32)(2000 * 1000) + +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +#define ADIS16260_SCAN_GYRO 0 +#define ADIS16260_SCAN_SUPPLY 1 +#define ADIS16260_SCAN_AUX_ADC 2 +#define ADIS16260_SCAN_TEMP 3 +#define ADIS16260_SCAN_ANGL 4 + +/* Power down the device */ +static int adis16260_stop_device(struct iio_dev *indio_dev) +{ + struct adis *adis = iio_priv(indio_dev); + int ret; + u16 val = ADIS16260_SLP_CNT_POWER_OFF; + + ret = adis_write_reg_16(adis, ADIS16260_SLP_CNT, val); + if (ret) + dev_err(&indio_dev->dev, "problem with turning device off: SLP_CNT"); + + return ret; +} + +static const struct iio_chan_spec adis16260_channels[] = { + ADIS_GYRO_CHAN(X, ADIS16260_GYRO_OUT, ADIS16260_SCAN_GYRO, + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + BIT(IIO_CHAN_INFO_SAMP_FREQ), 14), + ADIS_INCLI_CHAN(X, ADIS16260_ANGL_OUT, ADIS16260_SCAN_ANGL, 0, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 14), + ADIS_TEMP_CHAN(ADIS16260_TEMP_OUT, ADIS16260_SCAN_TEMP, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 12), + ADIS_SUPPLY_CHAN(ADIS16260_SUPPLY_OUT, ADIS16260_SCAN_SUPPLY, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 12), + ADIS_AUX_ADC_CHAN(ADIS16260_AUX_ADC, ADIS16260_SCAN_AUX_ADC, + BIT(IIO_CHAN_INFO_SAMP_FREQ), 12), + IIO_CHAN_SOFT_TIMESTAMP(5), +}; + +static const u8 adis16260_addresses[][2] = { + [ADIS16260_SCAN_GYRO] = { ADIS16260_GYRO_OFF, ADIS16260_GYRO_SCALE }, +}; + +static int adis16260_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis *adis = iio_priv(indio_dev); + int ret; + u8 addr; + s16 val16; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, + ADIS16260_ERROR_ACTIVE, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + if (spi_get_device_id(adis->spi)->driver_data) { + /* 0.01832 degree / sec */ + *val2 = IIO_DEGREE_TO_RAD(18320); + } else { + /* 0.07326 degree / sec */ + *val2 = IIO_DEGREE_TO_RAD(73260); + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_INCLI: + *val = 0; + *val2 = IIO_DEGREE_TO_RAD(36630); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_VOLTAGE: + if (chan->channel == 0) { + *val = 1; + *val2 = 831500; /* 1.8315 mV */ + } else { + *val = 0; + *val2 = 610500; /* 610.5 uV */ + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 145; + *val2 = 300000; /* 0.1453 C */ + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = 250000 / 1453; /* 25 C = 0x00 */ + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + addr = adis16260_addresses[chan->scan_index][0]; + ret = adis_read_reg_16(adis, addr, &val16); + if (ret) + return ret; + + *val = sign_extend32(val16, 11); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + addr = adis16260_addresses[chan->scan_index][1]; + ret = adis_read_reg_16(adis, addr, &val16); + if (ret) + return ret; + + *val = val16; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = adis_read_reg_16(adis, ADIS16260_SMPL_PRD, &val16); + if (ret) + return ret; + + if (spi_get_device_id(adis->spi)->driver_data) + /* If an adis16251 */ + *val = (val16 & ADIS16260_SMPL_PRD_TIME_BASE) ? + 8 : 256; + else + *val = (val16 & ADIS16260_SMPL_PRD_TIME_BASE) ? + 66 : 2048; + *val /= (val16 & ADIS16260_SMPL_PRD_DIV_MASK) + 1; + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int adis16260_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adis *adis = iio_priv(indio_dev); + int ret; + u8 addr; + u8 t; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (val < -2048 || val >= 2048) + return -EINVAL; + + addr = adis16260_addresses[chan->scan_index][0]; + return adis_write_reg_16(adis, addr, val); + case IIO_CHAN_INFO_CALIBSCALE: + if (val < 0 || val >= 4096) + return -EINVAL; + + addr = adis16260_addresses[chan->scan_index][1]; + return adis_write_reg_16(adis, addr, val); + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&indio_dev->mlock); + if (spi_get_device_id(adis->spi)->driver_data) + t = 256 / val; + else + t = 2048 / val; + + if (t > ADIS16260_SMPL_PRD_DIV_MASK) + t = ADIS16260_SMPL_PRD_DIV_MASK; + else if (t > 0) + t--; + + if (t >= 0x0A) + adis->spi->max_speed_hz = ADIS16260_SPI_SLOW; + else + adis->spi->max_speed_hz = ADIS16260_SPI_FAST; + ret = adis_write_reg_8(adis, ADIS16260_SMPL_PRD, t); + + mutex_unlock(&indio_dev->mlock); + return ret; + } + return -EINVAL; +} + +static const struct iio_info adis16260_info = { + .read_raw = &adis16260_read_raw, + .write_raw = &adis16260_write_raw, + .update_scan_mode = adis_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static const char * const adis1620_status_error_msgs[] = { + [ADIS16260_DIAG_STAT_FLASH_CHK_BIT] = "Flash checksum error", + [ADIS16260_DIAG_STAT_SELF_TEST_BIT] = "Self test error", + [ADIS16260_DIAG_STAT_OVERFLOW_BIT] = "Sensor overrange", + [ADIS16260_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", + [ADIS16260_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", + [ADIS16260_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 5.25", + [ADIS16260_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 4.75", +}; + +static const struct adis_data adis16260_data = { + .write_delay = 30, + .read_delay = 30, + .msc_ctrl_reg = ADIS16260_MSC_CTRL, + .glob_cmd_reg = ADIS16260_GLOB_CMD, + .diag_stat_reg = ADIS16260_DIAG_STAT, + + .self_test_mask = ADIS16260_MSC_CTRL_MEM_TEST, + .startup_delay = ADIS16260_STARTUP_DELAY, + + .status_error_msgs = adis1620_status_error_msgs, + .status_error_mask = BIT(ADIS16260_DIAG_STAT_FLASH_CHK_BIT) | + BIT(ADIS16260_DIAG_STAT_SELF_TEST_BIT) | + BIT(ADIS16260_DIAG_STAT_OVERFLOW_BIT) | + BIT(ADIS16260_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16260_DIAG_STAT_FLASH_UPT_BIT) | + BIT(ADIS16260_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16260_DIAG_STAT_POWER_LOW_BIT), +}; + +static int adis16260_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adis *adis; + int ret; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adis)); + if (!indio_dev) + return -ENOMEM; + adis = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16260_info; + indio_dev->channels = adis16260_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16260_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(adis, indio_dev, spi, &adis16260_data); + if (ret) + return ret; + + ret = adis_setup_buffer_and_trigger(adis, indio_dev, NULL); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = adis_initial_startup(adis); + if (ret) + goto error_cleanup_buffer_trigger; + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer_trigger; + + return 0; + +error_cleanup_buffer_trigger: + adis_cleanup_buffer_and_trigger(adis, indio_dev); + return ret; +} + +static int adis16260_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis *adis = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis16260_stop_device(indio_dev); + adis_cleanup_buffer_and_trigger(adis, indio_dev); + + return 0; +} + +/* + * These parts do not need to be differentiated until someone adds + * support for the on chip filtering. + */ +static const struct spi_device_id adis16260_id[] = { + {"adis16260", 0}, + {"adis16265", 0}, + {"adis16250", 0}, + {"adis16255", 0}, + {"adis16251", 1}, + {} +}; +MODULE_DEVICE_TABLE(spi, adis16260_id); + +static struct spi_driver adis16260_driver = { + .driver = { + .name = "adis16260", + .owner = THIS_MODULE, + }, + .probe = adis16260_probe, + .remove = adis16260_remove, + .id_table = adis16260_id, +}; +module_spi_driver(adis16260_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16260/5 Digital Gyroscope Sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/adxrs450.c b/drivers/iio/gyro/adxrs450.c new file mode 100644 index 000000000..eb0e08ec9 --- /dev/null +++ b/drivers/iio/gyro/adxrs450.c @@ -0,0 +1,468 @@ +/* + * ADXRS450/ADXRS453 Digital Output Gyroscope Driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/list.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define ADXRS450_STARTUP_DELAY 50 /* ms */ + +/* The MSB for the spi commands */ +#define ADXRS450_SENSOR_DATA (0x20 << 24) +#define ADXRS450_WRITE_DATA (0x40 << 24) +#define ADXRS450_READ_DATA (0x80 << 24) + +#define ADXRS450_RATE1 0x00 /* Rate Registers */ +#define ADXRS450_TEMP1 0x02 /* Temperature Registers */ +#define ADXRS450_LOCST1 0x04 /* Low CST Memory Registers */ +#define ADXRS450_HICST1 0x06 /* High CST Memory Registers */ +#define ADXRS450_QUAD1 0x08 /* Quad Memory Registers */ +#define ADXRS450_FAULT1 0x0A /* Fault Registers */ +#define ADXRS450_PID1 0x0C /* Part ID Register 1 */ +#define ADXRS450_SNH 0x0E /* Serial Number Registers, 4 bytes */ +#define ADXRS450_SNL 0x10 +#define ADXRS450_DNC1 0x12 /* Dynamic Null Correction Registers */ +/* Check bits */ +#define ADXRS450_P 0x01 +#define ADXRS450_CHK 0x02 +#define ADXRS450_CST 0x04 +#define ADXRS450_PWR 0x08 +#define ADXRS450_POR 0x10 +#define ADXRS450_NVM 0x20 +#define ADXRS450_Q 0x40 +#define ADXRS450_PLL 0x80 +#define ADXRS450_UV 0x100 +#define ADXRS450_OV 0x200 +#define ADXRS450_AMP 0x400 +#define ADXRS450_FAIL 0x800 + +#define ADXRS450_WRERR_MASK (0x7 << 29) + +#define ADXRS450_MAX_RX 4 +#define ADXRS450_MAX_TX 4 + +#define ADXRS450_GET_ST(a) ((a >> 26) & 0x3) + +enum { + ID_ADXRS450, + ID_ADXRS453, +}; + +/** + * struct adxrs450_state - device instance specific data + * @us: actual spi_device + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct adxrs450_state { + struct spi_device *us; + struct mutex buf_lock; + __be32 tx ____cacheline_aligned; + __be32 rx; + +}; + +/** + * adxrs450_spi_read_reg_16() - read 2 bytes from a register pair + * @indio_dev: device associated with child of actual iio_dev + * @reg_address: the address of the lower of the two registers, which should be + * an even address, the second register's address is reg_address + 1. + * @val: somewhere to pass back the value read + **/ +static int adxrs450_spi_read_reg_16(struct iio_dev *indio_dev, + u8 reg_address, + u16 *val) +{ + struct adxrs450_state *st = iio_priv(indio_dev); + u32 tx; + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->tx, + .bits_per_word = 8, + .len = sizeof(st->tx), + .cs_change = 1, + }, { + .rx_buf = &st->rx, + .bits_per_word = 8, + .len = sizeof(st->rx), + }, + }; + + mutex_lock(&st->buf_lock); + tx = ADXRS450_READ_DATA | (reg_address << 17); + + if (!(hweight32(tx) & 1)) + tx |= ADXRS450_P; + + st->tx = cpu_to_be32(tx); + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "problem while reading 16 bit register 0x%02x\n", + reg_address); + goto error_ret; + } + + *val = (be32_to_cpu(st->rx) >> 5) & 0xFFFF; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_write_reg_16() - write 2 bytes data to a register pair + * @indio_dev: device associated with child of actual actual iio_dev + * @reg_address: the address of the lower of the two registers,which should be + * an even address, the second register's address is reg_address + 1. + * @val: value to be written. + **/ +static int adxrs450_spi_write_reg_16(struct iio_dev *indio_dev, + u8 reg_address, + u16 val) +{ + struct adxrs450_state *st = iio_priv(indio_dev); + u32 tx; + int ret; + + mutex_lock(&st->buf_lock); + tx = ADXRS450_WRITE_DATA | (reg_address << 17) | (val << 1); + + if (!(hweight32(tx) & 1)) + tx |= ADXRS450_P; + + st->tx = cpu_to_be32(tx); + ret = spi_write(st->us, &st->tx, sizeof(st->tx)); + if (ret) + dev_err(&st->us->dev, "problem while writing 16 bit register 0x%02x\n", + reg_address); + usleep_range(100, 1000); /* enforce sequential transfer delay 0.1ms */ + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_sensor_data() - read 2 bytes sensor data + * @indio_dev: device associated with child of actual iio_dev + * @val: somewhere to pass back the value read + **/ +static int adxrs450_spi_sensor_data(struct iio_dev *indio_dev, s16 *val) +{ + struct adxrs450_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->tx, + .bits_per_word = 8, + .len = sizeof(st->tx), + .cs_change = 1, + }, { + .rx_buf = &st->rx, + .bits_per_word = 8, + .len = sizeof(st->rx), + }, + }; + + mutex_lock(&st->buf_lock); + st->tx = cpu_to_be32(ADXRS450_SENSOR_DATA); + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "Problem while reading sensor data\n"); + goto error_ret; + } + + *val = (be32_to_cpu(st->rx) >> 10) & 0xFFFF; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_initial() - use for initializing procedure. + * @st: device instance specific data + * @val: somewhere to pass back the value read + * @chk: Whether to perform fault check + **/ +static int adxrs450_spi_initial(struct adxrs450_state *st, + u32 *val, char chk) +{ + int ret; + u32 tx; + struct spi_transfer xfers = { + .tx_buf = &st->tx, + .rx_buf = &st->rx, + .bits_per_word = 8, + .len = sizeof(st->tx), + }; + + mutex_lock(&st->buf_lock); + tx = ADXRS450_SENSOR_DATA; + if (chk) + tx |= (ADXRS450_CHK | ADXRS450_P); + st->tx = cpu_to_be32(tx); + ret = spi_sync_transfer(st->us, &xfers, 1); + if (ret) { + dev_err(&st->us->dev, "Problem while reading initializing data\n"); + goto error_ret; + } + + *val = be32_to_cpu(st->rx); + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/* Recommended Startup Sequence by spec */ +static int adxrs450_initial_setup(struct iio_dev *indio_dev) +{ + u32 t; + u16 data; + int ret; + struct adxrs450_state *st = iio_priv(indio_dev); + + msleep(ADXRS450_STARTUP_DELAY*2); + ret = adxrs450_spi_initial(st, &t, 1); + if (ret) + return ret; + if (t != 0x01) + dev_warn(&st->us->dev, "The initial power on response is not correct! Restart without reset?\n"); + + msleep(ADXRS450_STARTUP_DELAY); + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + + msleep(ADXRS450_STARTUP_DELAY); + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + if (((t & 0xff) | 0x01) != 0xff || ADXRS450_GET_ST(t) != 2) { + dev_err(&st->us->dev, "The second response is not correct!\n"); + return -EIO; + + } + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + if (((t & 0xff) | 0x01) != 0xff || ADXRS450_GET_ST(t) != 2) { + dev_err(&st->us->dev, "The third response is not correct!\n"); + return -EIO; + + } + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_FAULT1, &data); + if (ret) + return ret; + if (data & 0x0fff) { + dev_err(&st->us->dev, "The device is not in normal status!\n"); + return -EINVAL; + } + + return 0; +} + +static int adxrs450_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (val < -0x400 || val >= 0x400) + return -EINVAL; + ret = adxrs450_spi_write_reg_16(indio_dev, + ADXRS450_DNC1, val); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int adxrs450_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret; + s16 t; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_ANGL_VEL: + ret = adxrs450_spi_sensor_data(indio_dev, &t); + if (ret) + break; + *val = t; + ret = IIO_VAL_INT; + break; + case IIO_TEMP: + ret = adxrs450_spi_read_reg_16(indio_dev, + ADXRS450_TEMP1, &t); + if (ret) + break; + *val = (t >> 6) + 225; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = 218166; + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + *val = 200; + *val2 = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW: + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_QUAD1, &t); + if (ret) + break; + *val = t; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBBIAS: + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_DNC1, &t); + if (ret) + break; + *val = sign_extend32(t, 9); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct iio_chan_spec adxrs450_channels[2][2] = { + [ID_ADXRS450] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } + }, + [ID_ADXRS453] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW), + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } + }, +}; + +static const struct iio_info adxrs450_info = { + .driver_module = THIS_MODULE, + .read_raw = &adxrs450_read_raw, + .write_raw = &adxrs450_write_raw, +}; + +static int adxrs450_probe(struct spi_device *spi) +{ + int ret; + struct adxrs450_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + /* This is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adxrs450_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = + adxrs450_channels[spi_get_device_id(spi)->driver_data]; + indio_dev->num_channels = ARRAY_SIZE(adxrs450_channels); + indio_dev->name = spi->dev.driver->name; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = adxrs450_initial_setup(indio_dev); + if (ret) + return ret; + + return 0; +} + +static const struct spi_device_id adxrs450_id[] = { + {"adxrs450", ID_ADXRS450}, + {"adxrs453", ID_ADXRS453}, + {} +}; +MODULE_DEVICE_TABLE(spi, adxrs450_id); + +static struct spi_driver adxrs450_driver = { + .driver = { + .name = "adxrs450", + .owner = THIS_MODULE, + }, + .probe = adxrs450_probe, + .id_table = adxrs450_id, +}; +module_spi_driver(adxrs450_driver); + +MODULE_AUTHOR("Cliff Cai <cliff.cai@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Analog Devices ADXRS450/ADXRS453 Gyroscope SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/bmg160.c b/drivers/iio/gyro/bmg160.c new file mode 100644 index 000000000..4415f55d2 --- /dev/null +++ b/drivers/iio/gyro/bmg160.c @@ -0,0 +1,1269 @@ +/* + * BMG160 Gyro Sensor driver + * Copyright (c) 2014, 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. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/gpio/consumer.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/events.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define BMG160_DRV_NAME "bmg160" +#define BMG160_IRQ_NAME "bmg160_event" +#define BMG160_GPIO_NAME "gpio_int" + +#define BMG160_REG_CHIP_ID 0x00 +#define BMG160_CHIP_ID_VAL 0x0F + +#define BMG160_REG_PMU_LPW 0x11 +#define BMG160_MODE_NORMAL 0x00 +#define BMG160_MODE_DEEP_SUSPEND 0x20 +#define BMG160_MODE_SUSPEND 0x80 + +#define BMG160_REG_RANGE 0x0F + +#define BMG160_RANGE_2000DPS 0 +#define BMG160_RANGE_1000DPS 1 +#define BMG160_RANGE_500DPS 2 +#define BMG160_RANGE_250DPS 3 +#define BMG160_RANGE_125DPS 4 + +#define BMG160_REG_PMU_BW 0x10 +#define BMG160_NO_FILTER 0 +#define BMG160_DEF_BW 100 + +#define BMG160_REG_INT_MAP_0 0x17 +#define BMG160_INT_MAP_0_BIT_ANY BIT(1) + +#define BMG160_REG_INT_MAP_1 0x18 +#define BMG160_INT_MAP_1_BIT_NEW_DATA BIT(0) + +#define BMG160_REG_INT_RST_LATCH 0x21 +#define BMG160_INT_MODE_LATCH_RESET 0x80 +#define BMG160_INT_MODE_LATCH_INT 0x0F +#define BMG160_INT_MODE_NON_LATCH_INT 0x00 + +#define BMG160_REG_INT_EN_0 0x15 +#define BMG160_DATA_ENABLE_INT BIT(7) + +#define BMG160_REG_INT_EN_1 0x16 +#define BMG160_INT1_BIT_OD BIT(1) + +#define BMG160_REG_XOUT_L 0x02 +#define BMG160_AXIS_TO_REG(axis) (BMG160_REG_XOUT_L + (axis * 2)) + +#define BMG160_REG_SLOPE_THRES 0x1B +#define BMG160_SLOPE_THRES_MASK 0x0F + +#define BMG160_REG_MOTION_INTR 0x1C +#define BMG160_INT_MOTION_X BIT(0) +#define BMG160_INT_MOTION_Y BIT(1) +#define BMG160_INT_MOTION_Z BIT(2) +#define BMG160_ANY_DUR_MASK 0x30 +#define BMG160_ANY_DUR_SHIFT 4 + +#define BMG160_REG_INT_STATUS_2 0x0B +#define BMG160_ANY_MOTION_MASK 0x07 +#define BMG160_ANY_MOTION_BIT_X BIT(0) +#define BMG160_ANY_MOTION_BIT_Y BIT(1) +#define BMG160_ANY_MOTION_BIT_Z BIT(2) + +#define BMG160_REG_TEMP 0x08 +#define BMG160_TEMP_CENTER_VAL 23 + +#define BMG160_MAX_STARTUP_TIME_MS 80 + +#define BMG160_AUTO_SUSPEND_DELAY_MS 2000 + +struct bmg160_data { + struct i2c_client *client; + struct iio_trigger *dready_trig; + struct iio_trigger *motion_trig; + struct mutex mutex; + s16 buffer[8]; + u8 bw_bits; + u32 dps_range; + int ev_enable_state; + int slope_thres; + bool dready_trigger_on; + bool motion_trigger_on; + int64_t timestamp; +}; + +enum bmg160_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + +static const struct { + int val; + int bw_bits; +} bmg160_samp_freq_table[] = { {100, 0x07}, + {200, 0x06}, + {400, 0x03}, + {1000, 0x02}, + {2000, 0x01} }; + +static const struct { + int scale; + int dps_range; +} bmg160_scale_table[] = { { 1065, BMG160_RANGE_2000DPS}, + { 532, BMG160_RANGE_1000DPS}, + { 266, BMG160_RANGE_500DPS}, + { 133, BMG160_RANGE_250DPS}, + { 66, BMG160_RANGE_125DPS} }; + +static int bmg160_set_mode(struct bmg160_data *data, u8 mode) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_PMU_LPW, mode); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_pmu_lpw\n"); + return ret; + } + + return 0; +} + +static int bmg160_convert_freq_to_bit(int val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bmg160_samp_freq_table); ++i) { + if (bmg160_samp_freq_table[i].val == val) + return bmg160_samp_freq_table[i].bw_bits; + } + + return -EINVAL; +} + +static int bmg160_set_bw(struct bmg160_data *data, int val) +{ + int ret; + int bw_bits; + + bw_bits = bmg160_convert_freq_to_bit(val); + if (bw_bits < 0) + return bw_bits; + + ret = i2c_smbus_write_byte_data(data->client, BMG160_REG_PMU_BW, + bw_bits); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_pmu_bw\n"); + return ret; + } + + data->bw_bits = bw_bits; + + return 0; +} + +static int bmg160_chip_init(struct bmg160_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, BMG160_REG_CHIP_ID); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_chip_id\n"); + return ret; + } + + dev_dbg(&data->client->dev, "Chip Id %x\n", ret); + if (ret != BMG160_CHIP_ID_VAL) { + dev_err(&data->client->dev, "invalid chip %x\n", ret); + return -ENODEV; + } + + ret = bmg160_set_mode(data, BMG160_MODE_NORMAL); + if (ret < 0) + return ret; + + /* Wait upto 500 ms to be ready after changing mode */ + usleep_range(500, 1000); + + /* Set Bandwidth */ + ret = bmg160_set_bw(data, BMG160_DEF_BW); + if (ret < 0) + return ret; + + /* Set Default Range */ + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_RANGE, + BMG160_RANGE_500DPS); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_range\n"); + return ret; + } + data->dps_range = BMG160_RANGE_500DPS; + + ret = i2c_smbus_read_byte_data(data->client, BMG160_REG_SLOPE_THRES); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_slope_thres\n"); + return ret; + } + data->slope_thres = ret; + + /* Set default interrupt mode */ + ret = i2c_smbus_read_byte_data(data->client, BMG160_REG_INT_EN_1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_en_1\n"); + return ret; + } + ret &= ~BMG160_INT1_BIT_OD; + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_EN_1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_en_1\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_motion_intr\n"); + return ret; + } + + return 0; +} + +static int bmg160_set_power_state(struct bmg160_data *data, bool on) +{ +#ifdef CONFIG_PM + int ret; + + if (on) + ret = pm_runtime_get_sync(&data->client->dev); + else { + pm_runtime_mark_last_busy(&data->client->dev); + ret = pm_runtime_put_autosuspend(&data->client->dev); + } + + if (ret < 0) { + dev_err(&data->client->dev, + "Failed: bmg160_set_power_state for %d\n", on); + if (on) + pm_runtime_put_noidle(&data->client->dev); + + return ret; + } +#endif + + return 0; +} + +static int bmg160_setup_any_motion_interrupt(struct bmg160_data *data, + bool status) +{ + int ret; + + /* Enable/Disable INT_MAP0 mapping */ + ret = i2c_smbus_read_byte_data(data->client, BMG160_REG_INT_MAP_0); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_map0\n"); + return ret; + } + if (status) + ret |= BMG160_INT_MAP_0_BIT_ANY; + else + ret &= ~BMG160_INT_MAP_0_BIT_ANY; + + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_MAP_0, + ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_map0\n"); + return ret; + } + + /* Enable/Disable slope interrupts */ + if (status) { + /* Update slope thres */ + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_SLOPE_THRES, + data->slope_thres); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_slope_thres\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_MOTION_INTR, + BMG160_INT_MOTION_X | + BMG160_INT_MOTION_Y | + BMG160_INT_MOTION_Z); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_motion_intr\n"); + return ret; + } + + /* + * New data interrupt is always non-latched, + * which will have higher priority, so no need + * to set latched mode, we will be flooded anyway with INTR + */ + if (!data->dready_trigger_on) { + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_rst_latch\n"); + return ret; + } + } + + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_EN_0, + BMG160_DATA_ENABLE_INT); + + } else + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_EN_0, + 0); + + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_en0\n"); + return ret; + } + + return 0; +} + +static int bmg160_setup_new_data_interrupt(struct bmg160_data *data, + bool status) +{ + int ret; + + /* Enable/Disable INT_MAP1 mapping */ + ret = i2c_smbus_read_byte_data(data->client, BMG160_REG_INT_MAP_1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_map1\n"); + return ret; + } + + if (status) + ret |= BMG160_INT_MAP_1_BIT_NEW_DATA; + else + ret &= ~BMG160_INT_MAP_1_BIT_NEW_DATA; + + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_MAP_1, + ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_map1\n"); + return ret; + } + + if (status) { + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_NON_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_rst_latch\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_EN_0, + BMG160_DATA_ENABLE_INT); + + } else { + /* Restore interrupt mode */ + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_rst_latch\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_EN_0, + 0); + } + + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_en0\n"); + return ret; + } + + return 0; +} + +static int bmg160_get_bw(struct bmg160_data *data, int *val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bmg160_samp_freq_table); ++i) { + if (bmg160_samp_freq_table[i].bw_bits == data->bw_bits) { + *val = bmg160_samp_freq_table[i].val; + return IIO_VAL_INT; + } + } + + return -EINVAL; +} + +static int bmg160_set_scale(struct bmg160_data *data, int val) +{ + int ret, i; + + for (i = 0; i < ARRAY_SIZE(bmg160_scale_table); ++i) { + if (bmg160_scale_table[i].scale == val) { + ret = i2c_smbus_write_byte_data( + data->client, + BMG160_REG_RANGE, + bmg160_scale_table[i].dps_range); + if (ret < 0) { + dev_err(&data->client->dev, + "Error writing reg_range\n"); + return ret; + } + data->dps_range = bmg160_scale_table[i].dps_range; + return 0; + } + } + + return -EINVAL; +} + +static int bmg160_get_temp(struct bmg160_data *data, int *val) +{ + int ret; + + mutex_lock(&data->mutex); + ret = bmg160_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = i2c_smbus_read_byte_data(data->client, BMG160_REG_TEMP); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_temp\n"); + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + + *val = sign_extend32(ret, 7); + ret = bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + + return IIO_VAL_INT; +} + +static int bmg160_get_axis(struct bmg160_data *data, int axis, int *val) +{ + int ret; + + mutex_lock(&data->mutex); + ret = bmg160_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = i2c_smbus_read_word_data(data->client, BMG160_AXIS_TO_REG(axis)); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading axis %d\n", axis); + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + + *val = sign_extend32(ret, 15); + ret = bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + if (ret < 0) + return ret; + + return IIO_VAL_INT; +} + +static int bmg160_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + return bmg160_get_temp(data, val); + case IIO_ANGL_VEL: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + else + return bmg160_get_axis(data, chan->scan_index, + val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = BMG160_TEMP_CENTER_VAL; + return IIO_VAL_INT; + } else + return -EINVAL; + case IIO_CHAN_INFO_SCALE: + *val = 0; + switch (chan->type) { + case IIO_TEMP: + *val2 = 500000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ANGL_VEL: + { + int i; + + for (i = 0; i < ARRAY_SIZE(bmg160_scale_table); ++i) { + if (bmg160_scale_table[i].dps_range == + data->dps_range) { + *val2 = bmg160_scale_table[i].scale; + return IIO_VAL_INT_PLUS_MICRO; + } + } + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val2 = 0; + mutex_lock(&data->mutex); + ret = bmg160_get_bw(data, val); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } +} + +static int bmg160_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&data->mutex); + /* + * Section 4.2 of spec + * In suspend mode, the only supported operations are reading + * registers as well as writing to the (0x14) softreset + * register. Since we will be in suspend mode by default, change + * mode to power on for other writes. + */ + ret = bmg160_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + ret = bmg160_set_bw(data, val); + if (ret < 0) { + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + ret = bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_SCALE: + if (val) + return -EINVAL; + + mutex_lock(&data->mutex); + /* Refer to comments above for the suspend mode ops */ + ret = bmg160_set_power_state(data, true); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + ret = bmg160_set_scale(data, val2); + if (ret < 0) { + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + ret = bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int bmg160_read_event(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 bmg160_data *data = iio_priv(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + *val = data->slope_thres & BMG160_SLOPE_THRES_MASK; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int bmg160_write_event(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 bmg160_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + if (data->ev_enable_state) + return -EBUSY; + data->slope_thres &= ~BMG160_SLOPE_THRES_MASK; + data->slope_thres |= (val & BMG160_SLOPE_THRES_MASK); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int bmg160_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 bmg160_data *data = iio_priv(indio_dev); + + return data->ev_enable_state; +} + +static int bmg160_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 bmg160_data *data = iio_priv(indio_dev); + int ret; + + if (state && data->ev_enable_state) + return 0; + + mutex_lock(&data->mutex); + + if (!state && data->motion_trigger_on) { + data->ev_enable_state = 0; + mutex_unlock(&data->mutex); + return 0; + } + /* + * We will expect the enable and disable to do operation in + * in reverse order. This will happen here anyway as our + * resume operation uses sync mode runtime pm calls, the + * suspend operation will be delayed by autosuspend delay + * So the disable operation will still happen in reverse of + * enable operation. When runtime pm is disabled the mode + * is always on so sequence doesn't matter + */ + ret = bmg160_set_power_state(data, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + + ret = bmg160_setup_any_motion_interrupt(data, state); + if (ret < 0) { + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + + data->ev_enable_state = state; + mutex_unlock(&data->mutex); + + return 0; +} + +static int bmg160_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct bmg160_data *data = iio_priv(indio_dev); + + if (data->dready_trig != trig && data->motion_trig != trig) + return -EINVAL; + + return 0; +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("100 200 400 1000 2000"); + +static IIO_CONST_ATTR(in_anglvel_scale_available, + "0.001065 0.000532 0.000266 0.000133 0.000066"); + +static struct attribute *bmg160_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bmg160_attrs_group = { + .attrs = bmg160_attributes, +}; + +static const struct iio_event_spec bmg160_event = { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) +}; + +#define BMG160_CHANNEL(_axis) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = AXIS_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + }, \ + .event_spec = &bmg160_event, \ + .num_event_specs = 1 \ +} + +static const struct iio_chan_spec bmg160_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = -1, + }, + BMG160_CHANNEL(X), + BMG160_CHANNEL(Y), + BMG160_CHANNEL(Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_info bmg160_info = { + .attrs = &bmg160_attrs_group, + .read_raw = bmg160_read_raw, + .write_raw = bmg160_write_raw, + .read_event_value = bmg160_read_event, + .write_event_value = bmg160_write_event, + .write_event_config = bmg160_write_event_config, + .read_event_config = bmg160_read_event_config, + .validate_trigger = bmg160_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static irqreturn_t bmg160_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmg160_data *data = iio_priv(indio_dev); + int bit, ret, i = 0; + + mutex_lock(&data->mutex); + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = i2c_smbus_read_word_data(data->client, + BMG160_AXIS_TO_REG(bit)); + if (ret < 0) { + mutex_unlock(&data->mutex); + goto err; + } + data->buffer[i++] = ret; + } + mutex_unlock(&data->mutex); + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + data->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bmg160_trig_try_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + /* new data interrupts don't need ack */ + if (data->dready_trigger_on) + return 0; + + /* Set latched mode interrupt and clear any latched interrupt */ + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_rst_latch\n"); + return ret; + } + + return 0; +} + +static int bmg160_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + + if (!state && data->ev_enable_state && data->motion_trigger_on) { + data->motion_trigger_on = false; + mutex_unlock(&data->mutex); + return 0; + } + + /* + * Refer to comment in bmg160_write_event_config for + * enable/disable operation order + */ + ret = bmg160_set_power_state(data, state); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + if (data->motion_trig == trig) + ret = bmg160_setup_any_motion_interrupt(data, state); + else + ret = bmg160_setup_new_data_interrupt(data, state); + if (ret < 0) { + bmg160_set_power_state(data, false); + mutex_unlock(&data->mutex); + return ret; + } + if (data->motion_trig == trig) + data->motion_trigger_on = state; + else + data->dready_trigger_on = state; + + mutex_unlock(&data->mutex); + + return 0; +} + +static const struct iio_trigger_ops bmg160_trigger_ops = { + .set_trigger_state = bmg160_data_rdy_trigger_set_state, + .try_reenable = bmg160_trig_try_reen, + .owner = THIS_MODULE, +}; + +static irqreturn_t bmg160_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + int dir; + + ret = i2c_smbus_read_byte_data(data->client, BMG160_REG_INT_STATUS_2); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_int_status2\n"); + goto ack_intr_status; + } + + if (ret & 0x08) + dir = IIO_EV_DIR_RISING; + else + dir = IIO_EV_DIR_FALLING; + + if (ret & BMG160_ANY_MOTION_BIT_X) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ANGL_VEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_ROC, + dir), + data->timestamp); + if (ret & BMG160_ANY_MOTION_BIT_Y) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ANGL_VEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_ROC, + dir), + data->timestamp); + if (ret & BMG160_ANY_MOTION_BIT_Z) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ANGL_VEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_ROC, + dir), + data->timestamp); + +ack_intr_status: + if (!data->dready_trigger_on) { + ret = i2c_smbus_write_byte_data(data->client, + BMG160_REG_INT_RST_LATCH, + BMG160_INT_MODE_LATCH_INT | + BMG160_INT_MODE_LATCH_RESET); + if (ret < 0) + dev_err(&data->client->dev, + "Error writing reg_rst_latch\n"); + } + + return IRQ_HANDLED; +} + +static irqreturn_t bmg160_data_rdy_trig_poll(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmg160_data *data = iio_priv(indio_dev); + + data->timestamp = iio_get_time_ns(); + + if (data->dready_trigger_on) + iio_trigger_poll(data->dready_trig); + else if (data->motion_trigger_on) + iio_trigger_poll(data->motion_trig); + + if (data->ev_enable_state) + return IRQ_WAKE_THREAD; + else + return IRQ_HANDLED; + +} + +static int bmg160_gpio_probe(struct i2c_client *client, + struct bmg160_data *data) + +{ + struct device *dev; + struct gpio_desc *gpio; + int ret; + + if (!client) + return -EINVAL; + + dev = &client->dev; + + /* data ready gpio interrupt pin */ + gpio = devm_gpiod_get_index(dev, BMG160_GPIO_NAME, 0, GPIOD_IN); + if (IS_ERR(gpio)) { + dev_err(dev, "acpi gpio get index failed\n"); + return PTR_ERR(gpio); + } + + ret = gpiod_to_irq(gpio); + + dev_dbg(dev, "GPIO resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret); + + return ret; +} + +static const char *bmg160_match_acpi_device(struct device *dev) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + + return dev_name(dev); +} + +static int bmg160_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bmg160_data *data; + struct iio_dev *indio_dev; + int ret; + const char *name = NULL; + + 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; + + ret = bmg160_chip_init(data); + if (ret < 0) + return ret; + + mutex_init(&data->mutex); + + if (id) + name = id->name; + + if (ACPI_HANDLE(&client->dev)) + name = bmg160_match_acpi_device(&client->dev); + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = bmg160_channels; + indio_dev->num_channels = ARRAY_SIZE(bmg160_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmg160_info; + + if (client->irq <= 0) + client->irq = bmg160_gpio_probe(client, data); + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, + client->irq, + bmg160_data_rdy_trig_poll, + bmg160_event_handler, + IRQF_TRIGGER_RISING, + BMG160_IRQ_NAME, + indio_dev); + if (ret) + return ret; + + data->dready_trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", + indio_dev->name, + indio_dev->id); + if (!data->dready_trig) + return -ENOMEM; + + data->motion_trig = devm_iio_trigger_alloc(&client->dev, + "%s-any-motion-dev%d", + indio_dev->name, + indio_dev->id); + if (!data->motion_trig) + return -ENOMEM; + + data->dready_trig->dev.parent = &client->dev; + data->dready_trig->ops = &bmg160_trigger_ops; + iio_trigger_set_drvdata(data->dready_trig, indio_dev); + ret = iio_trigger_register(data->dready_trig); + if (ret) + return ret; + + data->motion_trig->dev.parent = &client->dev; + data->motion_trig->ops = &bmg160_trigger_ops; + iio_trigger_set_drvdata(data->motion_trig, indio_dev); + ret = iio_trigger_register(data->motion_trig); + if (ret) { + data->motion_trig = NULL; + goto err_trigger_unregister; + } + + ret = iio_triggered_buffer_setup(indio_dev, + NULL, + bmg160_trigger_handler, + NULL); + if (ret < 0) { + dev_err(&client->dev, + "iio triggered buffer setup failed\n"); + goto err_trigger_unregister; + } + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret) + goto err_iio_unregister; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + BMG160_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + return 0; + +err_iio_unregister: + iio_device_unregister(indio_dev); +err_buffer_cleanup: + if (data->dready_trig) + iio_triggered_buffer_cleanup(indio_dev); +err_trigger_unregister: + if (data->dready_trig) + iio_trigger_unregister(data->dready_trig); + if (data->motion_trig) + iio_trigger_unregister(data->motion_trig); + + return ret; +} + +static int bmg160_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bmg160_data *data = iio_priv(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + iio_device_unregister(indio_dev); + + if (data->dready_trig) { + iio_triggered_buffer_cleanup(indio_dev); + iio_trigger_unregister(data->dready_trig); + iio_trigger_unregister(data->motion_trig); + } + + mutex_lock(&data->mutex); + bmg160_set_mode(data, BMG160_MODE_DEEP_SUSPEND); + mutex_unlock(&data->mutex); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bmg160_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bmg160_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + bmg160_set_mode(data, BMG160_MODE_SUSPEND); + mutex_unlock(&data->mutex); + + return 0; +} + +static int bmg160_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bmg160_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + if (data->dready_trigger_on || data->motion_trigger_on || + data->ev_enable_state) + bmg160_set_mode(data, BMG160_MODE_NORMAL); + mutex_unlock(&data->mutex); + + return 0; +} +#endif + +#ifdef CONFIG_PM +static int bmg160_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + ret = bmg160_set_mode(data, BMG160_MODE_SUSPEND); + if (ret < 0) { + dev_err(&data->client->dev, "set mode failed\n"); + return -EAGAIN; + } + + return 0; +} + +static int bmg160_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct bmg160_data *data = iio_priv(indio_dev); + int ret; + + ret = bmg160_set_mode(data, BMG160_MODE_NORMAL); + if (ret < 0) + return ret; + + msleep_interruptible(BMG160_MAX_STARTUP_TIME_MS); + + return 0; +} +#endif + +static const struct dev_pm_ops bmg160_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(bmg160_suspend, bmg160_resume) + SET_RUNTIME_PM_OPS(bmg160_runtime_suspend, + bmg160_runtime_resume, NULL) +}; + +static const struct acpi_device_id bmg160_acpi_match[] = { + {"BMG0160", 0}, + {"BMI055B", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(acpi, bmg160_acpi_match); + +static const struct i2c_device_id bmg160_id[] = { + {"bmg160", 0}, + {"bmi055_gyro", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, bmg160_id); + +static struct i2c_driver bmg160_driver = { + .driver = { + .name = BMG160_DRV_NAME, + .acpi_match_table = ACPI_PTR(bmg160_acpi_match), + .pm = &bmg160_pm_ops, + }, + .probe = bmg160_probe, + .remove = bmg160_remove, + .id_table = bmg160_id, +}; +module_i2c_driver(bmg160_driver); + +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("BMG160 Gyro driver"); diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c new file mode 100644 index 000000000..b5883b6f4 --- /dev/null +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c @@ -0,0 +1,422 @@ +/* + * HID Sensors Driver + * Copyright (c) 2012, 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; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum gyro_3d_channel { + CHANNEL_SCAN_INDEX_X, + CHANNEL_SCAN_INDEX_Y, + CHANNEL_SCAN_INDEX_Z, + GYRO_3D_CHANNEL_MAX, +}; + +struct gyro_3d_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info gyro[GYRO_3D_CHANNEL_MAX]; + u32 gyro_val[GYRO_3D_CHANNEL_MAX]; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +static const u32 gyro_3d_addresses[GYRO_3D_CHANNEL_MAX] = { + HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS, + HID_USAGE_SENSOR_ANGL_VELOCITY_Y_AXIS, + HID_USAGE_SENSOR_ANGL_VELOCITY_Z_AXIS +}; + +/* Channel definitions */ +static const struct iio_chan_spec gyro_3d_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .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), + .scan_index = CHANNEL_SCAN_INDEX_X, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .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), + .scan_index = CHANNEL_SCAN_INDEX_Y, + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .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), + .scan_index = CHANNEL_SCAN_INDEX_Z, + } +}; + +/* Adjust channel real bits based on report descriptor */ +static void gyro_3d_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 u32 */ + channels[channel].scan_type.storagebits = sizeof(u32) * 8; +} + +/* Channel read_raw handler */ +static int gyro_3d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct gyro_3d_state *gyro_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + + *val = 0; + *val2 = 0; + switch (mask) { + case 0: + hid_sensor_power_state(&gyro_state->common_attributes, true); + report_id = gyro_state->gyro[chan->scan_index].report_id; + address = gyro_3d_addresses[chan->scan_index]; + if (report_id >= 0) + *val = sensor_hub_input_attr_get_raw_value( + gyro_state->common_attributes.hsdev, + HID_USAGE_SENSOR_GYRO_3D, address, + report_id, + SENSOR_HUB_SYNC); + else { + *val = 0; + hid_sensor_power_state(&gyro_state->common_attributes, + false); + return -EINVAL; + } + hid_sensor_power_state(&gyro_state->common_attributes, false); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = gyro_state->scale_pre_decml; + *val2 = gyro_state->scale_post_decml; + ret_type = gyro_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = gyro_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &gyro_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &gyro_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int gyro_3d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct gyro_3d_state *gyro_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &gyro_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &gyro_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info gyro_3d_info = { + .driver_module = THIS_MODULE, + .read_raw = &gyro_3d_read_raw, + .write_raw = &gyro_3d_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data, + int len) +{ + dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); + iio_push_to_buffers(indio_dev, data); +} + +/* Callback handler to send event after all samples are received and captured */ +static int gyro_3d_proc_event(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct gyro_3d_state *gyro_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "gyro_3d_proc_event\n"); + if (atomic_read(&gyro_state->common_attributes.data_ready)) + hid_sensor_push_data(indio_dev, + gyro_state->gyro_val, + sizeof(gyro_state->gyro_val)); + + return 0; +} + +/* Capture samples in local storage */ +static int gyro_3d_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct gyro_3d_state *gyro_state = iio_priv(indio_dev); + int offset; + int ret = -EINVAL; + + switch (usage_id) { + case HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS: + case HID_USAGE_SENSOR_ANGL_VELOCITY_Y_AXIS: + case HID_USAGE_SENSOR_ANGL_VELOCITY_Z_AXIS: + offset = usage_id - HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS; + gyro_state->gyro_val[CHANNEL_SCAN_INDEX_X + offset] = + *(u32 *)raw_data; + ret = 0; + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int gyro_3d_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + unsigned usage_id, + struct gyro_3d_state *st) +{ + int ret; + int i; + + for (i = 0; i <= CHANNEL_SCAN_INDEX_Z; ++i) { + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ANGL_VELOCITY_X_AXIS + i, + &st->gyro[CHANNEL_SCAN_INDEX_X + i]); + if (ret < 0) + break; + gyro_3d_adjust_channel_bit_mask(channels, + CHANNEL_SCAN_INDEX_X + i, + st->gyro[CHANNEL_SCAN_INDEX_X + i].size); + } + dev_dbg(&pdev->dev, "gyro_3d %x:%x, %x:%x, %x:%x\n", + st->gyro[0].index, + st->gyro[0].report_id, + st->gyro[1].index, st->gyro[1].report_id, + st->gyro[2].index, st->gyro[2].report_id); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_GYRO_3D, + &st->gyro[CHANNEL_SCAN_INDEX_X], + &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_ANGL_VELOCITY, + &st->common_attributes.sensitivity); + dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", + st->common_attributes.sensitivity.index, + st->common_attributes.sensitivity.report_id); + } + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_gyro_3d_probe(struct platform_device *pdev) +{ + int ret = 0; + static const char *name = "gyro_3d"; + struct iio_dev *indio_dev; + struct gyro_3d_state *gyro_state; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_chan_spec *channels; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*gyro_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + gyro_state = iio_priv(indio_dev); + gyro_state->common_attributes.hsdev = hsdev; + gyro_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_GYRO_3D, + &gyro_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + channels = kmemdup(gyro_3d_channels, sizeof(gyro_3d_channels), + GFP_KERNEL); + if (!channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = gyro_3d_parse_report(pdev, hsdev, channels, + HID_USAGE_SENSOR_GYRO_3D, gyro_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + goto error_free_dev_mem; + } + + indio_dev->channels = channels; + indio_dev->num_channels = ARRAY_SIZE(gyro_3d_channels); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &gyro_3d_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to initialize trigger buffer\n"); + goto error_free_dev_mem; + } + atomic_set(&gyro_state->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, name, + &gyro_state->common_attributes); + if (ret < 0) { + dev_err(&pdev->dev, "trigger setup failed\n"); + goto error_unreg_buffer_funcs; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_trigger; + } + + gyro_state->callbacks.send_event = gyro_3d_proc_event; + gyro_state->callbacks.capture_sample = gyro_3d_capture_sample; + gyro_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D, + &gyro_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_iio_unreg; + } + + return ret; + +error_iio_unreg: + iio_device_unregister(indio_dev); +error_remove_trigger: + hid_sensor_remove_trigger(&gyro_state->common_attributes); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: + kfree(indio_dev->channels); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_gyro_3d_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct gyro_3d_state *gyro_state = iio_priv(indio_dev); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(&gyro_state->common_attributes); + iio_triggered_buffer_cleanup(indio_dev); + kfree(indio_dev->channels); + + return 0; +} + +static struct platform_device_id hid_gyro_3d_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200076", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_gyro_3d_ids); + +static struct platform_driver hid_gyro_3d_platform_driver = { + .id_table = hid_gyro_3d_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_gyro_3d_probe, + .remove = hid_gyro_3d_remove, +}; +module_platform_driver(hid_gyro_3d_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Gyroscope 3D"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/gyro/itg3200_buffer.c b/drivers/iio/gyro/itg3200_buffer.c new file mode 100644 index 000000000..eef50e91f --- /dev/null +++ b/drivers/iio/gyro/itg3200_buffer.c @@ -0,0 +1,153 @@ +/* + * itg3200_buffer.c -- support InvenSense ITG3200 + * Digital 3-Axis Gyroscope driver + * + * Copyright (c) 2011 Christian Strobel <christian.strobel@iis.fraunhofer.de> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> + * Copyright (c) 2012 Thorsten Nowak <thorsten.nowak@iis.fraunhofer.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/gyro/itg3200.h> + + +static int itg3200_read_all_channels(struct i2c_client *i2c, __be16 *buf) +{ + u8 tx = 0x80 | ITG3200_REG_TEMP_OUT_H; + struct i2c_msg msg[2] = { + { + .addr = i2c->addr, + .flags = i2c->flags, + .len = 1, + .buf = &tx, + }, + { + .addr = i2c->addr, + .flags = i2c->flags | I2C_M_RD, + .len = ITG3200_SCAN_ELEMENTS * sizeof(s16), + .buf = (char *)&buf, + }, + }; + + return i2c_transfer(i2c->adapter, msg, 2); +} + +static irqreturn_t itg3200_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct itg3200 *st = iio_priv(indio_dev); + __be16 buf[ITG3200_SCAN_ELEMENTS + sizeof(s64)/sizeof(u16)]; + + int ret = itg3200_read_all_channels(st->i2c, buf); + if (ret < 0) + goto error_ret; + + iio_push_to_buffers_with_timestamp(indio_dev, buf, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + +error_ret: + return IRQ_HANDLED; +} + +int itg3200_buffer_configure(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + itg3200_trigger_handler, NULL); +} + +void itg3200_buffer_unconfigure(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + + +static int itg3200_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + int ret; + u8 msc; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_IRQ_CONFIG, &msc); + if (ret) + goto error_ret; + + if (state) + msc |= ITG3200_IRQ_DATA_RDY_ENABLE; + else + msc &= ~ITG3200_IRQ_DATA_RDY_ENABLE; + + ret = itg3200_write_reg_8(indio_dev, ITG3200_REG_IRQ_CONFIG, msc); + if (ret) + goto error_ret; + +error_ret: + return ret; + +} + +static const struct iio_trigger_ops itg3200_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &itg3200_data_rdy_trigger_set_state, +}; + +int itg3200_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct itg3200 *st = iio_priv(indio_dev); + + st->trig = iio_trigger_alloc("%s-dev%d", indio_dev->name, + indio_dev->id); + if (!st->trig) + return -ENOMEM; + + ret = request_irq(st->i2c->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "itg3200_data_rdy", + st->trig); + if (ret) + goto error_free_trig; + + + st->trig->dev.parent = &st->i2c->dev; + st->trig->ops = &itg3200_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = iio_trigger_register(st->trig); + if (ret) + goto error_free_irq; + + /* select default trigger */ + indio_dev->trig = iio_trigger_get(st->trig); + + return 0; + +error_free_irq: + free_irq(st->i2c->irq, st->trig); +error_free_trig: + iio_trigger_free(st->trig); + return ret; +} + +void itg3200_remove_trigger(struct iio_dev *indio_dev) +{ + struct itg3200 *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->i2c->irq, st->trig); + iio_trigger_free(st->trig); +} diff --git a/drivers/iio/gyro/itg3200_core.c b/drivers/iio/gyro/itg3200_core.c new file mode 100644 index 000000000..f0fd94055 --- /dev/null +++ b/drivers/iio/gyro/itg3200_core.c @@ -0,0 +1,395 @@ +/* + * itg3200_core.c -- support InvenSense ITG3200 + * Digital 3-Axis Gyroscope driver + * + * Copyright (c) 2011 Christian Strobel <christian.strobel@iis.fraunhofer.de> + * Copyright (c) 2011 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> + * Copyright (c) 2012 Thorsten Nowak <thorsten.nowak@iis.fraunhofer.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * - Support digital low pass filter + * - Support power management + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/module.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/buffer.h> + +#include <linux/iio/gyro/itg3200.h> + + +int itg3200_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, u8 val) +{ + struct itg3200 *st = iio_priv(indio_dev); + + return i2c_smbus_write_byte_data(st->i2c, 0x80 | reg_address, val); +} + +int itg3200_read_reg_8(struct iio_dev *indio_dev, + u8 reg_address, u8 *val) +{ + struct itg3200 *st = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(st->i2c, reg_address); + if (ret < 0) + return ret; + *val = ret; + return 0; +} + +static int itg3200_read_reg_s16(struct iio_dev *indio_dev, u8 lower_reg_address, + int *val) +{ + struct itg3200 *st = iio_priv(indio_dev); + struct i2c_client *client = st->i2c; + int ret; + s16 out; + + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = client->flags, + .len = 1, + .buf = (char *)&lower_reg_address, + }, + { + .addr = client->addr, + .flags = client->flags | I2C_M_RD, + .len = 2, + .buf = (char *)&out, + }, + }; + + lower_reg_address |= 0x80; + ret = i2c_transfer(client->adapter, msg, 2); + be16_to_cpus(&out); + *val = out; + + return (ret == 2) ? 0 : ret; +} + +static int itg3200_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long info) +{ + int ret = 0; + u8 reg; + u8 regval; + + switch (info) { + case IIO_CHAN_INFO_RAW: + reg = (u8)chan->address; + ret = itg3200_read_reg_s16(indio_dev, reg, val); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (chan->type == IIO_TEMP) + *val2 = 1000000000/280; + else + *val2 = 1214142; /* (1 / 14,375) * (PI / 180) */ + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + /* Only the temperature channel has an offset */ + *val = 23000; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, ®val); + if (ret) + return ret; + + *val = (regval & ITG3200_DLPF_CFG_MASK) ? 1000 : 8000; + + ret = itg3200_read_reg_8(indio_dev, + ITG3200_REG_SAMPLE_RATE_DIV, + ®val); + if (ret) + return ret; + + *val /= regval + 1; + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int itg3200_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret; + u8 t; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val == 0 || val2 != 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, &t); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + t = ((t & ITG3200_DLPF_CFG_MASK) ? 1000u : 8000u) / val - 1; + + ret = itg3200_write_reg_8(indio_dev, + ITG3200_REG_SAMPLE_RATE_DIV, + t); + + mutex_unlock(&indio_dev->mlock); + return ret; + + default: + return -EINVAL; + } +} + +/* + * Reset device and internal registers to the power-up-default settings + * Use the gyro clock as reference, as suggested by the datasheet + */ +static int itg3200_reset(struct iio_dev *indio_dev) +{ + struct itg3200 *st = iio_priv(indio_dev); + int ret; + + dev_dbg(&st->i2c->dev, "reset device"); + + ret = itg3200_write_reg_8(indio_dev, + ITG3200_REG_POWER_MANAGEMENT, + ITG3200_RESET); + if (ret) { + dev_err(&st->i2c->dev, "error resetting device"); + goto error_ret; + } + + /* Wait for PLL (1ms according to datasheet) */ + udelay(1500); + + ret = itg3200_write_reg_8(indio_dev, + ITG3200_REG_IRQ_CONFIG, + ITG3200_IRQ_ACTIVE_HIGH | + ITG3200_IRQ_PUSH_PULL | + ITG3200_IRQ_LATCH_50US_PULSE | + ITG3200_IRQ_LATCH_CLEAR_ANY); + + if (ret) + dev_err(&st->i2c->dev, "error init device"); + +error_ret: + return ret; +} + +/* itg3200_enable_full_scale() - Disables the digital low pass filter */ +static int itg3200_enable_full_scale(struct iio_dev *indio_dev) +{ + u8 val; + int ret; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, &val); + if (ret) + goto err_ret; + + val |= ITG3200_DLPF_FS_SEL_2000; + return itg3200_write_reg_8(indio_dev, ITG3200_REG_DLPF, val); + +err_ret: + return ret; +} + +static int itg3200_initial_setup(struct iio_dev *indio_dev) +{ + struct itg3200 *st = iio_priv(indio_dev); + int ret; + u8 val; + + ret = itg3200_reset(indio_dev); + if (ret) + goto err_ret; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_ADDRESS, &val); + if (ret) + goto err_ret; + + if (((val >> 1) & 0x3f) != 0x34) { + dev_err(&st->i2c->dev, "invalid reg value 0x%02x", val); + ret = -ENXIO; + goto err_ret; + } + + ret = itg3200_enable_full_scale(indio_dev); +err_ret: + return ret; +} + +#define ITG3200_ST \ + { .sign = 's', .realbits = 16, .storagebits = 16, .endianness = IIO_BE } + +#define ITG3200_GYRO_CHAN(_mod) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _mod, \ + .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), \ + .address = ITG3200_REG_GYRO_ ## _mod ## OUT_H, \ + .scan_index = ITG3200_SCAN_GYRO_ ## _mod, \ + .scan_type = ITG3200_ST, \ +} + +static const struct iio_chan_spec itg3200_channels[] = { + { + .type = IIO_TEMP, + .channel2 = IIO_NO_MOD, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .address = ITG3200_REG_TEMP_OUT_H, + .scan_index = ITG3200_SCAN_TEMP, + .scan_type = ITG3200_ST, + }, + ITG3200_GYRO_CHAN(X), + ITG3200_GYRO_CHAN(Y), + ITG3200_GYRO_CHAN(Z), + IIO_CHAN_SOFT_TIMESTAMP(ITG3200_SCAN_ELEMENTS), +}; + +static const struct iio_info itg3200_info = { + .read_raw = &itg3200_read_raw, + .write_raw = &itg3200_write_raw, + .driver_module = THIS_MODULE, +}; + +static const unsigned long itg3200_available_scan_masks[] = { 0xffffffff, 0x0 }; + +static int itg3200_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct itg3200 *st; + struct iio_dev *indio_dev; + + dev_dbg(&client->dev, "probe I2C dev with IRQ %i", client->irq); + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + st->i2c = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = client->dev.driver->name; + indio_dev->channels = itg3200_channels; + indio_dev->num_channels = ARRAY_SIZE(itg3200_channels); + indio_dev->available_scan_masks = itg3200_available_scan_masks; + indio_dev->info = &itg3200_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = itg3200_buffer_configure(indio_dev); + if (ret) + return ret; + + if (client->irq) { + ret = itg3200_probe_trigger(indio_dev); + if (ret) + goto error_unconfigure_buffer; + } + + ret = itg3200_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + if (client->irq) + itg3200_remove_trigger(indio_dev); +error_unconfigure_buffer: + itg3200_buffer_unconfigure(indio_dev); + return ret; +} + +static int itg3200_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + if (client->irq) + itg3200_remove_trigger(indio_dev); + + itg3200_buffer_unconfigure(indio_dev); + + return 0; +} + +static int __maybe_unused itg3200_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct itg3200 *st = iio_priv(indio_dev); + + dev_dbg(&st->i2c->dev, "suspend device"); + + return itg3200_write_reg_8(indio_dev, ITG3200_REG_POWER_MANAGEMENT, + ITG3200_SLEEP); +} + +static int __maybe_unused itg3200_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return itg3200_initial_setup(indio_dev); +} + +static SIMPLE_DEV_PM_OPS(itg3200_pm_ops, itg3200_suspend, itg3200_resume); + +static const struct i2c_device_id itg3200_id[] = { + { "itg3200", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, itg3200_id); + +static struct i2c_driver itg3200_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "itg3200", + .pm = &itg3200_pm_ops, + }, + .id_table = itg3200_id, + .probe = itg3200_probe, + .remove = itg3200_remove, +}; + +module_i2c_driver(itg3200_driver); + +MODULE_AUTHOR("Christian Strobel <christian.strobel@iis.fraunhofer.de>"); +MODULE_DESCRIPTION("ITG3200 Gyroscope I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/ssp_gyro_sensor.c b/drivers/iio/gyro/ssp_gyro_sensor.c new file mode 100644 index 000000000..0a8afdd21 --- /dev/null +++ b/drivers/iio/gyro/ssp_gyro_sensor.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * 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. + * + */ + +#include <linux/iio/common/ssp_sensors.h> +#include <linux/iio/iio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include "../common/ssp_sensors/ssp_iio_sensor.h" + +#define SSP_CHANNEL_COUNT 3 + +#define SSP_GYROSCOPE_NAME "ssp-gyroscope" +static const char ssp_gyro_name[] = SSP_GYROSCOPE_NAME; + +enum ssp_gyro_3d_channel { + SSP_CHANNEL_SCAN_INDEX_X, + SSP_CHANNEL_SCAN_INDEX_Y, + SSP_CHANNEL_SCAN_INDEX_Z, + SSP_CHANNEL_SCAN_INDEX_TIME, +}; + +static int ssp_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + u32 t; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + t = ssp_get_sensor_delay(data, SSP_GYROSCOPE_SENSOR); + ssp_convert_to_freq(t, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + + return -EINVAL; +} + +static int ssp_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + int ret; + struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ssp_convert_to_time(val, val2); + ret = ssp_change_delay(data, SSP_GYROSCOPE_SENSOR, ret); + if (ret < 0) + dev_err(&indio_dev->dev, "gyro sensor enable fail\n"); + + return ret; + default: + break; + } + + return -EINVAL; +} + +static struct iio_info ssp_gyro_iio_info = { + .read_raw = &ssp_gyro_read_raw, + .write_raw = &ssp_gyro_write_raw, +}; + +static const unsigned long ssp_gyro_scan_mask[] = { 0x07, 0, }; + +static const struct iio_chan_spec ssp_gyro_channels[] = { + SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_X, SSP_CHANNEL_SCAN_INDEX_X), + SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_Y, SSP_CHANNEL_SCAN_INDEX_Y), + SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_Z, SSP_CHANNEL_SCAN_INDEX_Z), + SSP_CHAN_TIMESTAMP(SSP_CHANNEL_SCAN_INDEX_TIME), +}; + +static int ssp_process_gyro_data(struct iio_dev *indio_dev, void *buf, + int64_t timestamp) +{ + return ssp_common_process_data(indio_dev, buf, SSP_GYROSCOPE_SIZE, + timestamp); +} + +static const struct iio_buffer_setup_ops ssp_gyro_buffer_ops = { + .postenable = &ssp_common_buffer_postenable, + .postdisable = &ssp_common_buffer_postdisable, +}; + +static int ssp_gyro_probe(struct platform_device *pdev) +{ + int ret; + struct iio_dev *indio_dev; + struct ssp_sensor_data *spd; + struct iio_buffer *buffer; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*spd)); + if (!indio_dev) + return -ENOMEM; + + spd = iio_priv(indio_dev); + + spd->process_data = ssp_process_gyro_data; + spd->type = SSP_GYROSCOPE_SENSOR; + + indio_dev->name = ssp_gyro_name; + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &ssp_gyro_iio_info; + indio_dev->modes = INDIO_BUFFER_SOFTWARE; + indio_dev->channels = ssp_gyro_channels; + indio_dev->num_channels = ARRAY_SIZE(ssp_gyro_channels); + indio_dev->available_scan_masks = ssp_gyro_scan_mask; + + buffer = devm_iio_kfifo_allocate(&pdev->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->setup_ops = &ssp_gyro_buffer_ops; + + platform_set_drvdata(pdev, indio_dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + return ret; + + /* ssp registering should be done after all iio setup */ + ssp_register_consumer(indio_dev, SSP_GYROSCOPE_SENSOR); + + return 0; +} + +static int ssp_gyro_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + + return 0; +} + +static struct platform_driver ssp_gyro_driver = { + .driver = { + .name = SSP_GYROSCOPE_NAME, + }, + .probe = ssp_gyro_probe, + .remove = ssp_gyro_remove, +}; + +module_platform_driver(ssp_gyro_driver); + +MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>"); +MODULE_DESCRIPTION("Samsung sensorhub gyroscopes driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/gyro/st_gyro.h b/drivers/iio/gyro/st_gyro.h new file mode 100644 index 000000000..5353d6328 --- /dev/null +++ b/drivers/iio/gyro/st_gyro.h @@ -0,0 +1,52 @@ +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * v. 1.0.0 + * Licensed under the GPL-2. + */ + +#ifndef ST_GYRO_H +#define ST_GYRO_H + +#include <linux/types.h> +#include <linux/iio/common/st_sensors.h> + +#define L3G4200D_GYRO_DEV_NAME "l3g4200d" +#define LSM330D_GYRO_DEV_NAME "lsm330d_gyro" +#define LSM330DL_GYRO_DEV_NAME "lsm330dl_gyro" +#define LSM330DLC_GYRO_DEV_NAME "lsm330dlc_gyro" +#define L3GD20_GYRO_DEV_NAME "l3gd20" +#define L3G4IS_GYRO_DEV_NAME "l3g4is_ui" +#define LSM330_GYRO_DEV_NAME "lsm330_gyro" + +/** + * struct st_sensors_platform_data - gyro platform data + * @drdy_int_pin: DRDY on gyros is available only on INT2 pin. + */ +static const struct st_sensors_platform_data gyro_pdata = { + .drdy_int_pin = 2, +}; + +int st_gyro_common_probe(struct iio_dev *indio_dev); +void st_gyro_common_remove(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +int st_gyro_allocate_ring(struct iio_dev *indio_dev); +void st_gyro_deallocate_ring(struct iio_dev *indio_dev); +int st_gyro_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_GYRO_TRIGGER_SET_STATE (&st_gyro_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_gyro_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +static inline void st_gyro_deallocate_ring(struct iio_dev *indio_dev) +{ +} +#define ST_GYRO_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_GYRO_H */ diff --git a/drivers/iio/gyro/st_gyro_buffer.c b/drivers/iio/gyro/st_gyro_buffer.c new file mode 100644 index 000000000..d67b17b6a --- /dev/null +++ b/drivers/iio/gyro/st_gyro_buffer.c @@ -0,0 +1,105 @@ +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_gyro.h" + +int st_gyro_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + + return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_gyro_buffer_preenable(struct iio_dev *indio_dev) +{ + return st_sensors_set_enable(indio_dev, true); +} + +static int st_gyro_buffer_postenable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *gdata = iio_priv(indio_dev); + + gdata->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (gdata->buffer_data == NULL) { + err = -ENOMEM; + goto allocate_memory_error; + } + + err = st_sensors_set_axis_enable(indio_dev, + (u8)indio_dev->active_scan_mask[0]); + if (err < 0) + goto st_gyro_buffer_postenable_error; + + err = iio_triggered_buffer_postenable(indio_dev); + if (err < 0) + goto st_gyro_buffer_postenable_error; + + return err; + +st_gyro_buffer_postenable_error: + kfree(gdata->buffer_data); +allocate_memory_error: + return err; +} + +static int st_gyro_buffer_predisable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *gdata = iio_priv(indio_dev); + + err = iio_triggered_buffer_predisable(indio_dev); + if (err < 0) + goto st_gyro_buffer_predisable_error; + + err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); + if (err < 0) + goto st_gyro_buffer_predisable_error; + + err = st_sensors_set_enable(indio_dev, false); + +st_gyro_buffer_predisable_error: + kfree(gdata->buffer_data); + return err; +} + +static const struct iio_buffer_setup_ops st_gyro_buffer_setup_ops = { + .preenable = &st_gyro_buffer_preenable, + .postenable = &st_gyro_buffer_postenable, + .predisable = &st_gyro_buffer_predisable, +}; + +int st_gyro_allocate_ring(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &st_sensors_trigger_handler, &st_gyro_buffer_setup_ops); +} + +void st_gyro_deallocate_ring(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes buffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/st_gyro_core.c b/drivers/iio/gyro/st_gyro_core.c new file mode 100644 index 000000000..ffe96642b --- /dev/null +++ b/drivers/iio/gyro/st_gyro_core.c @@ -0,0 +1,473 @@ +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_gyro.h" + +#define ST_GYRO_NUMBER_DATA_CHANNELS 3 + +/* DEFAULT VALUE FOR SENSORS */ +#define ST_GYRO_DEFAULT_OUT_X_L_ADDR 0x28 +#define ST_GYRO_DEFAULT_OUT_Y_L_ADDR 0x2a +#define ST_GYRO_DEFAULT_OUT_Z_L_ADDR 0x2c + +/* FULLSCALE */ +#define ST_GYRO_FS_AVL_250DPS 250 +#define ST_GYRO_FS_AVL_500DPS 500 +#define ST_GYRO_FS_AVL_2000DPS 2000 + +/* CUSTOM VALUES FOR SENSOR 1 */ +#define ST_GYRO_1_WAI_EXP 0xd3 +#define ST_GYRO_1_ODR_ADDR 0x20 +#define ST_GYRO_1_ODR_MASK 0xc0 +#define ST_GYRO_1_ODR_AVL_100HZ_VAL 0x00 +#define ST_GYRO_1_ODR_AVL_200HZ_VAL 0x01 +#define ST_GYRO_1_ODR_AVL_400HZ_VAL 0x02 +#define ST_GYRO_1_ODR_AVL_800HZ_VAL 0x03 +#define ST_GYRO_1_PW_ADDR 0x20 +#define ST_GYRO_1_PW_MASK 0x08 +#define ST_GYRO_1_FS_ADDR 0x23 +#define ST_GYRO_1_FS_MASK 0x30 +#define ST_GYRO_1_FS_AVL_250_VAL 0x00 +#define ST_GYRO_1_FS_AVL_500_VAL 0x01 +#define ST_GYRO_1_FS_AVL_2000_VAL 0x02 +#define ST_GYRO_1_FS_AVL_250_GAIN IIO_DEGREE_TO_RAD(8750) +#define ST_GYRO_1_FS_AVL_500_GAIN IIO_DEGREE_TO_RAD(17500) +#define ST_GYRO_1_FS_AVL_2000_GAIN IIO_DEGREE_TO_RAD(70000) +#define ST_GYRO_1_BDU_ADDR 0x23 +#define ST_GYRO_1_BDU_MASK 0x80 +#define ST_GYRO_1_DRDY_IRQ_ADDR 0x22 +#define ST_GYRO_1_DRDY_IRQ_INT2_MASK 0x08 +#define ST_GYRO_1_MULTIREAD_BIT true + +/* CUSTOM VALUES FOR SENSOR 2 */ +#define ST_GYRO_2_WAI_EXP 0xd4 +#define ST_GYRO_2_ODR_ADDR 0x20 +#define ST_GYRO_2_ODR_MASK 0xc0 +#define ST_GYRO_2_ODR_AVL_95HZ_VAL 0x00 +#define ST_GYRO_2_ODR_AVL_190HZ_VAL 0x01 +#define ST_GYRO_2_ODR_AVL_380HZ_VAL 0x02 +#define ST_GYRO_2_ODR_AVL_760HZ_VAL 0x03 +#define ST_GYRO_2_PW_ADDR 0x20 +#define ST_GYRO_2_PW_MASK 0x08 +#define ST_GYRO_2_FS_ADDR 0x23 +#define ST_GYRO_2_FS_MASK 0x30 +#define ST_GYRO_2_FS_AVL_250_VAL 0x00 +#define ST_GYRO_2_FS_AVL_500_VAL 0x01 +#define ST_GYRO_2_FS_AVL_2000_VAL 0x02 +#define ST_GYRO_2_FS_AVL_250_GAIN IIO_DEGREE_TO_RAD(8750) +#define ST_GYRO_2_FS_AVL_500_GAIN IIO_DEGREE_TO_RAD(17500) +#define ST_GYRO_2_FS_AVL_2000_GAIN IIO_DEGREE_TO_RAD(70000) +#define ST_GYRO_2_BDU_ADDR 0x23 +#define ST_GYRO_2_BDU_MASK 0x80 +#define ST_GYRO_2_DRDY_IRQ_ADDR 0x22 +#define ST_GYRO_2_DRDY_IRQ_INT2_MASK 0x08 +#define ST_GYRO_2_MULTIREAD_BIT true + +/* CUSTOM VALUES FOR SENSOR 3 */ +#define ST_GYRO_3_WAI_EXP 0xd7 +#define ST_GYRO_3_ODR_ADDR 0x20 +#define ST_GYRO_3_ODR_MASK 0xc0 +#define ST_GYRO_3_ODR_AVL_95HZ_VAL 0x00 +#define ST_GYRO_3_ODR_AVL_190HZ_VAL 0x01 +#define ST_GYRO_3_ODR_AVL_380HZ_VAL 0x02 +#define ST_GYRO_3_ODR_AVL_760HZ_VAL 0x03 +#define ST_GYRO_3_PW_ADDR 0x20 +#define ST_GYRO_3_PW_MASK 0x08 +#define ST_GYRO_3_FS_ADDR 0x23 +#define ST_GYRO_3_FS_MASK 0x30 +#define ST_GYRO_3_FS_AVL_250_VAL 0x00 +#define ST_GYRO_3_FS_AVL_500_VAL 0x01 +#define ST_GYRO_3_FS_AVL_2000_VAL 0x02 +#define ST_GYRO_3_FS_AVL_250_GAIN IIO_DEGREE_TO_RAD(8750) +#define ST_GYRO_3_FS_AVL_500_GAIN IIO_DEGREE_TO_RAD(17500) +#define ST_GYRO_3_FS_AVL_2000_GAIN IIO_DEGREE_TO_RAD(70000) +#define ST_GYRO_3_BDU_ADDR 0x23 +#define ST_GYRO_3_BDU_MASK 0x80 +#define ST_GYRO_3_DRDY_IRQ_ADDR 0x22 +#define ST_GYRO_3_DRDY_IRQ_INT2_MASK 0x08 +#define ST_GYRO_3_MULTIREAD_BIT true + + +static const struct iio_chan_spec st_gyro_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_ANGL_VEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16, + ST_GYRO_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ANGL_VEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16, + ST_GYRO_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ANGL_VEL, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16, + ST_GYRO_DEFAULT_OUT_Z_L_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct st_sensor_settings st_gyro_sensors_settings[] = { + { + .wai = ST_GYRO_1_WAI_EXP, + .sensors_supported = { + [0] = L3G4200D_GYRO_DEV_NAME, + [1] = LSM330DL_GYRO_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_gyro_16bit_channels, + .odr = { + .addr = ST_GYRO_1_ODR_ADDR, + .mask = ST_GYRO_1_ODR_MASK, + .odr_avl = { + { 100, ST_GYRO_1_ODR_AVL_100HZ_VAL, }, + { 200, ST_GYRO_1_ODR_AVL_200HZ_VAL, }, + { 400, ST_GYRO_1_ODR_AVL_400HZ_VAL, }, + { 800, ST_GYRO_1_ODR_AVL_800HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_GYRO_1_PW_ADDR, + .mask = ST_GYRO_1_PW_MASK, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = ST_GYRO_1_FS_ADDR, + .mask = ST_GYRO_1_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_GYRO_FS_AVL_250DPS, + .value = ST_GYRO_1_FS_AVL_250_VAL, + .gain = ST_GYRO_1_FS_AVL_250_GAIN, + }, + [1] = { + .num = ST_GYRO_FS_AVL_500DPS, + .value = ST_GYRO_1_FS_AVL_500_VAL, + .gain = ST_GYRO_1_FS_AVL_500_GAIN, + }, + [2] = { + .num = ST_GYRO_FS_AVL_2000DPS, + .value = ST_GYRO_1_FS_AVL_2000_VAL, + .gain = ST_GYRO_1_FS_AVL_2000_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_GYRO_1_BDU_ADDR, + .mask = ST_GYRO_1_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_GYRO_1_DRDY_IRQ_ADDR, + .mask_int2 = ST_GYRO_1_DRDY_IRQ_INT2_MASK, + }, + .multi_read_bit = ST_GYRO_1_MULTIREAD_BIT, + .bootime = 2, + }, + { + .wai = ST_GYRO_2_WAI_EXP, + .sensors_supported = { + [0] = L3GD20_GYRO_DEV_NAME, + [1] = LSM330D_GYRO_DEV_NAME, + [2] = LSM330DLC_GYRO_DEV_NAME, + [3] = L3G4IS_GYRO_DEV_NAME, + [4] = LSM330_GYRO_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_gyro_16bit_channels, + .odr = { + .addr = ST_GYRO_2_ODR_ADDR, + .mask = ST_GYRO_2_ODR_MASK, + .odr_avl = { + { 95, ST_GYRO_2_ODR_AVL_95HZ_VAL, }, + { 190, ST_GYRO_2_ODR_AVL_190HZ_VAL, }, + { 380, ST_GYRO_2_ODR_AVL_380HZ_VAL, }, + { 760, ST_GYRO_2_ODR_AVL_760HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_GYRO_2_PW_ADDR, + .mask = ST_GYRO_2_PW_MASK, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = ST_GYRO_2_FS_ADDR, + .mask = ST_GYRO_2_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_GYRO_FS_AVL_250DPS, + .value = ST_GYRO_2_FS_AVL_250_VAL, + .gain = ST_GYRO_2_FS_AVL_250_GAIN, + }, + [1] = { + .num = ST_GYRO_FS_AVL_500DPS, + .value = ST_GYRO_2_FS_AVL_500_VAL, + .gain = ST_GYRO_2_FS_AVL_500_GAIN, + }, + [2] = { + .num = ST_GYRO_FS_AVL_2000DPS, + .value = ST_GYRO_2_FS_AVL_2000_VAL, + .gain = ST_GYRO_2_FS_AVL_2000_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_GYRO_2_BDU_ADDR, + .mask = ST_GYRO_2_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_GYRO_2_DRDY_IRQ_ADDR, + .mask_int2 = ST_GYRO_2_DRDY_IRQ_INT2_MASK, + }, + .multi_read_bit = ST_GYRO_2_MULTIREAD_BIT, + .bootime = 2, + }, + { + .wai = ST_GYRO_3_WAI_EXP, + .sensors_supported = { + [0] = L3GD20_GYRO_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_gyro_16bit_channels, + .odr = { + .addr = ST_GYRO_3_ODR_ADDR, + .mask = ST_GYRO_3_ODR_MASK, + .odr_avl = { + { 95, ST_GYRO_3_ODR_AVL_95HZ_VAL, }, + { 190, ST_GYRO_3_ODR_AVL_190HZ_VAL, }, + { 380, ST_GYRO_3_ODR_AVL_380HZ_VAL, }, + { 760, ST_GYRO_3_ODR_AVL_760HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_GYRO_3_PW_ADDR, + .mask = ST_GYRO_3_PW_MASK, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = ST_GYRO_3_FS_ADDR, + .mask = ST_GYRO_3_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_GYRO_FS_AVL_250DPS, + .value = ST_GYRO_3_FS_AVL_250_VAL, + .gain = ST_GYRO_3_FS_AVL_250_GAIN, + }, + [1] = { + .num = ST_GYRO_FS_AVL_500DPS, + .value = ST_GYRO_3_FS_AVL_500_VAL, + .gain = ST_GYRO_3_FS_AVL_500_GAIN, + }, + [2] = { + .num = ST_GYRO_FS_AVL_2000DPS, + .value = ST_GYRO_3_FS_AVL_2000_VAL, + .gain = ST_GYRO_3_FS_AVL_2000_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_GYRO_3_BDU_ADDR, + .mask = ST_GYRO_3_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_GYRO_3_DRDY_IRQ_ADDR, + .mask_int2 = ST_GYRO_3_DRDY_IRQ_INT2_MASK, + }, + .multi_read_bit = ST_GYRO_3_MULTIREAD_BIT, + .bootime = 2, + }, +}; + +static int st_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + struct st_sensor_data *gdata = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = st_sensors_read_info_raw(indio_dev, ch, val); + if (err < 0) + goto read_error; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = gdata->current_fullscale->gain; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = gdata->odr; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static int st_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + int err; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + err = st_sensors_set_fullscale_by_gain(indio_dev, val2); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + mutex_lock(&indio_dev->mlock); + err = st_sensors_set_odr(indio_dev, val); + mutex_unlock(&indio_dev->mlock); + return err; + default: + err = -EINVAL; + } + + return err; +} + +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); +static ST_SENSORS_DEV_ATTR_SCALE_AVAIL(in_anglvel_scale_available); + +static struct attribute *st_gyro_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_anglvel_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_gyro_attribute_group = { + .attrs = st_gyro_attributes, +}; + +static const struct iio_info gyro_info = { + .driver_module = THIS_MODULE, + .attrs = &st_gyro_attribute_group, + .read_raw = &st_gyro_read_raw, + .write_raw = &st_gyro_write_raw, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_gyro_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = ST_GYRO_TRIGGER_SET_STATE, +}; +#define ST_GYRO_TRIGGER_OPS (&st_gyro_trigger_ops) +#else +#define ST_GYRO_TRIGGER_OPS NULL +#endif + +int st_gyro_common_probe(struct iio_dev *indio_dev) +{ + struct st_sensor_data *gdata = iio_priv(indio_dev); + int irq = gdata->get_irq_data_ready(indio_dev); + int err; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &gyro_info; + mutex_init(&gdata->tb.buf_lock); + + st_sensors_power_enable(indio_dev); + + err = st_sensors_check_device_support(indio_dev, + ARRAY_SIZE(st_gyro_sensors_settings), + st_gyro_sensors_settings); + if (err < 0) + return err; + + gdata->num_data_channels = ST_GYRO_NUMBER_DATA_CHANNELS; + gdata->multiread_bit = gdata->sensor_settings->multi_read_bit; + indio_dev->channels = gdata->sensor_settings->ch; + indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + + gdata->current_fullscale = (struct st_sensor_fullscale_avl *) + &gdata->sensor_settings->fs.fs_avl[0]; + gdata->odr = gdata->sensor_settings->odr.odr_avl[0].hz; + + err = st_sensors_init_sensor(indio_dev, + (struct st_sensors_platform_data *)&gyro_pdata); + if (err < 0) + return err; + + err = st_gyro_allocate_ring(indio_dev); + if (err < 0) + return err; + + if (irq > 0) { + err = st_sensors_allocate_trigger(indio_dev, + ST_GYRO_TRIGGER_OPS); + if (err < 0) + goto st_gyro_probe_trigger_error; + } + + err = iio_device_register(indio_dev); + if (err) + goto st_gyro_device_register_error; + + dev_info(&indio_dev->dev, "registered gyroscope %s\n", + indio_dev->name); + + return 0; + +st_gyro_device_register_error: + if (irq > 0) + st_sensors_deallocate_trigger(indio_dev); +st_gyro_probe_trigger_error: + st_gyro_deallocate_ring(indio_dev); + + return err; +} +EXPORT_SYMBOL(st_gyro_common_probe); + +void st_gyro_common_remove(struct iio_dev *indio_dev) +{ + struct st_sensor_data *gdata = iio_priv(indio_dev); + + st_sensors_power_disable(indio_dev); + + iio_device_unregister(indio_dev); + if (gdata->get_irq_data_ready(indio_dev) > 0) + st_sensors_deallocate_trigger(indio_dev); + + st_gyro_deallocate_ring(indio_dev); +} +EXPORT_SYMBOL(st_gyro_common_remove); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/st_gyro_i2c.c b/drivers/iio/gyro/st_gyro_i2c.c new file mode 100644 index 000000000..64480b16c --- /dev/null +++ b/drivers/iio/gyro/st_gyro_i2c.c @@ -0,0 +1,114 @@ +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_i2c.h> +#include "st_gyro.h" + +#ifdef CONFIG_OF +static const struct of_device_id st_gyro_of_match[] = { + { + .compatible = "st,l3g4200d-gyro", + .data = L3G4200D_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330d-gyro", + .data = LSM330D_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330dl-gyro", + .data = LSM330DL_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330dlc-gyro", + .data = LSM330DLC_GYRO_DEV_NAME, + }, + { + .compatible = "st,l3gd20-gyro", + .data = L3GD20_GYRO_DEV_NAME, + }, + { + .compatible = "st,l3g4is-gyro", + .data = L3G4IS_GYRO_DEV_NAME, + }, + { + .compatible = "st,lsm330-gyro", + .data = LSM330_GYRO_DEV_NAME, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_gyro_of_match); +#else +#define st_gyro_of_match NULL +#endif + +static int st_gyro_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *gdata; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*gdata)); + if (!indio_dev) + return -ENOMEM; + + gdata = iio_priv(indio_dev); + st_sensors_of_i2c_probe(client, st_gyro_of_match); + + st_sensors_i2c_configure(indio_dev, client, gdata); + + err = st_gyro_common_probe(indio_dev); + if (err < 0) + return err; + + return 0; +} + +static int st_gyro_i2c_remove(struct i2c_client *client) +{ + st_gyro_common_remove(i2c_get_clientdata(client)); + + return 0; +} + +static const struct i2c_device_id st_gyro_id_table[] = { + { L3G4200D_GYRO_DEV_NAME }, + { LSM330D_GYRO_DEV_NAME }, + { LSM330DL_GYRO_DEV_NAME }, + { LSM330DLC_GYRO_DEV_NAME }, + { L3GD20_GYRO_DEV_NAME }, + { L3G4IS_GYRO_DEV_NAME }, + { LSM330_GYRO_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, st_gyro_id_table); + +static struct i2c_driver st_gyro_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-gyro-i2c", + .of_match_table = of_match_ptr(st_gyro_of_match), + }, + .probe = st_gyro_i2c_probe, + .remove = st_gyro_i2c_remove, + .id_table = st_gyro_id_table, +}; +module_i2c_driver(st_gyro_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/gyro/st_gyro_spi.c b/drivers/iio/gyro/st_gyro_spi.c new file mode 100644 index 000000000..e59bead6b --- /dev/null +++ b/drivers/iio/gyro/st_gyro_spi.c @@ -0,0 +1,74 @@ +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_spi.h> +#include "st_gyro.h" + +static int st_gyro_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *gdata; + int err; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*gdata)); + if (!indio_dev) + return -ENOMEM; + + gdata = iio_priv(indio_dev); + + st_sensors_spi_configure(indio_dev, spi, gdata); + + err = st_gyro_common_probe(indio_dev); + if (err < 0) + return err; + + return 0; +} + +static int st_gyro_spi_remove(struct spi_device *spi) +{ + st_gyro_common_remove(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id st_gyro_id_table[] = { + { L3G4200D_GYRO_DEV_NAME }, + { LSM330D_GYRO_DEV_NAME }, + { LSM330DL_GYRO_DEV_NAME }, + { LSM330DLC_GYRO_DEV_NAME }, + { L3GD20_GYRO_DEV_NAME }, + { L3G4IS_GYRO_DEV_NAME }, + { LSM330_GYRO_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(spi, st_gyro_id_table); + +static struct spi_driver st_gyro_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-gyro-spi", + }, + .probe = st_gyro_spi_probe, + .remove = st_gyro_spi_remove, + .id_table = st_gyro_id_table, +}; +module_spi_driver(st_gyro_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics gyroscopes spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig new file mode 100644 index 000000000..4813b793b --- /dev/null +++ b/drivers/iio/humidity/Kconfig @@ -0,0 +1,35 @@ +# +# humidity sensor drivers +# +menu "Humidity sensors" + +config DHT11 + tristate "DHT11 (and compatible sensors) driver" + depends on GPIOLIB + help + This driver supports reading data via a single interrupt + generating GPIO line. Currently tested are DHT11 and DHT22. + Other sensors should work as well as long as they speak the + same protocol. + +config SI7005 + tristate "SI7005 relative humidity and temperature sensor" + depends on I2C + help + Say yes here to build support for the Silabs Si7005 relative + humidity and temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called si7005. + +config SI7020 + tristate "Si7013/20/21 Relative Humidity and Temperature Sensors" + depends on I2C + help + Say yes here to build support for the Silicon Labs Si7013/20/21 + Relative Humidity and Temperature Sensors. + + To compile this driver as a module, choose M here: the module + will be called si7020. + +endmenu diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile new file mode 100644 index 000000000..86e2d26e9 --- /dev/null +++ b/drivers/iio/humidity/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for IIO humidity sensor drivers +# + +obj-$(CONFIG_DHT11) += dht11.o +obj-$(CONFIG_SI7005) += si7005.o +obj-$(CONFIG_SI7020) += si7020.o diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c new file mode 100644 index 000000000..7d79a1ac5 --- /dev/null +++ b/drivers/iio/humidity/dht11.c @@ -0,0 +1,306 @@ +/* + * DHT11/DHT22 bit banging GPIO driver + * + * Copyright (c) Harald Geyer <harald@ccbib.org> + * + * 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. + */ + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/printk.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/sysfs.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/wait.h> +#include <linux/bitops.h> +#include <linux/completion.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> + +#include <linux/iio/iio.h> + +#define DRIVER_NAME "dht11" + +#define DHT11_DATA_VALID_TIME 2000000000 /* 2s in ns */ + +#define DHT11_EDGES_PREAMBLE 2 +#define DHT11_BITS_PER_READ 40 +/* + * Note that when reading the sensor actually 84 edges are detected, but + * since the last edge is not significant, we only store 83: + */ +#define DHT11_EDGES_PER_READ (2*DHT11_BITS_PER_READ + DHT11_EDGES_PREAMBLE + 1) + +/* Data transmission timing (nano seconds) */ +#define DHT11_START_TRANSMISSION 18 /* ms */ +#define DHT11_SENSOR_RESPONSE 80000 +#define DHT11_START_BIT 50000 +#define DHT11_DATA_BIT_LOW 27000 +#define DHT11_DATA_BIT_HIGH 70000 + +struct dht11 { + struct device *dev; + + int gpio; + int irq; + + struct completion completion; + struct mutex lock; + + s64 timestamp; + int temperature; + int humidity; + + /* num_edges: -1 means "no transmission in progress" */ + int num_edges; + struct {s64 ts; int value; } edges[DHT11_EDGES_PER_READ]; +}; + +static unsigned char dht11_decode_byte(int *timing, int threshold) +{ + unsigned char ret = 0; + int i; + + for (i = 0; i < 8; ++i) { + ret <<= 1; + if (timing[i] >= threshold) + ++ret; + } + + return ret; +} + +static int dht11_decode(struct dht11 *dht11, int offset) +{ + int i, t, timing[DHT11_BITS_PER_READ], threshold, + timeres = DHT11_SENSOR_RESPONSE; + unsigned char temp_int, temp_dec, hum_int, hum_dec, checksum; + + /* Calculate timestamp resolution */ + for (i = 1; i < dht11->num_edges; ++i) { + t = dht11->edges[i].ts - dht11->edges[i-1].ts; + if (t > 0 && t < timeres) + timeres = t; + } + if (2*timeres > DHT11_DATA_BIT_HIGH) { + pr_err("dht11: timeresolution %d too bad for decoding\n", + timeres); + return -EIO; + } + threshold = DHT11_DATA_BIT_HIGH / timeres; + if (DHT11_DATA_BIT_LOW/timeres + 1 >= threshold) + pr_err("dht11: WARNING: decoding ambiguous\n"); + + /* scale down with timeres and check validity */ + for (i = 0; i < DHT11_BITS_PER_READ; ++i) { + t = dht11->edges[offset + 2*i + 2].ts - + dht11->edges[offset + 2*i + 1].ts; + if (!dht11->edges[offset + 2*i + 1].value) + return -EIO; /* lost synchronisation */ + timing[i] = t / timeres; + } + + hum_int = dht11_decode_byte(timing, threshold); + hum_dec = dht11_decode_byte(&timing[8], threshold); + temp_int = dht11_decode_byte(&timing[16], threshold); + temp_dec = dht11_decode_byte(&timing[24], threshold); + checksum = dht11_decode_byte(&timing[32], threshold); + + if (((hum_int + hum_dec + temp_int + temp_dec) & 0xff) != checksum) + return -EIO; + + dht11->timestamp = iio_get_time_ns(); + if (hum_int < 20) { /* DHT22 */ + dht11->temperature = (((temp_int & 0x7f) << 8) + temp_dec) * + ((temp_int & 0x80) ? -100 : 100); + dht11->humidity = ((hum_int << 8) + hum_dec) * 100; + } else if (temp_dec == 0 && hum_dec == 0) { /* DHT11 */ + dht11->temperature = temp_int * 1000; + dht11->humidity = hum_int * 1000; + } else { + dev_err(dht11->dev, + "Don't know how to decode data: %d %d %d %d\n", + hum_int, hum_dec, temp_int, temp_dec); + return -EIO; + } + + return 0; +} + +/* + * IRQ handler called on GPIO edges + */ +static irqreturn_t dht11_handle_irq(int irq, void *data) +{ + struct iio_dev *iio = data; + struct dht11 *dht11 = iio_priv(iio); + + /* TODO: Consider making the handler safe for IRQ sharing */ + if (dht11->num_edges < DHT11_EDGES_PER_READ && dht11->num_edges >= 0) { + dht11->edges[dht11->num_edges].ts = iio_get_time_ns(); + dht11->edges[dht11->num_edges++].value = + gpio_get_value(dht11->gpio); + + if (dht11->num_edges >= DHT11_EDGES_PER_READ) + complete(&dht11->completion); + } + + return IRQ_HANDLED; +} + +static int dht11_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long m) +{ + struct dht11 *dht11 = iio_priv(iio_dev); + int ret; + + mutex_lock(&dht11->lock); + if (dht11->timestamp + DHT11_DATA_VALID_TIME < iio_get_time_ns()) { + reinit_completion(&dht11->completion); + + dht11->num_edges = 0; + ret = gpio_direction_output(dht11->gpio, 0); + if (ret) + goto err; + msleep(DHT11_START_TRANSMISSION); + ret = gpio_direction_input(dht11->gpio); + if (ret) + goto err; + + ret = request_irq(dht11->irq, dht11_handle_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + iio_dev->name, iio_dev); + if (ret) + goto err; + + ret = wait_for_completion_killable_timeout(&dht11->completion, + HZ); + + free_irq(dht11->irq, iio_dev); + + if (ret == 0 && dht11->num_edges < DHT11_EDGES_PER_READ - 1) { + dev_err(&iio_dev->dev, + "Only %d signal edges detected\n", + dht11->num_edges); + ret = -ETIMEDOUT; + } + if (ret < 0) + goto err; + + ret = dht11_decode(dht11, + dht11->num_edges == DHT11_EDGES_PER_READ ? + DHT11_EDGES_PREAMBLE : + DHT11_EDGES_PREAMBLE - 2); + if (ret) + goto err; + } + + ret = IIO_VAL_INT; + if (chan->type == IIO_TEMP) + *val = dht11->temperature; + else if (chan->type == IIO_HUMIDITYRELATIVE) + *val = dht11->humidity; + else + ret = -EINVAL; +err: + dht11->num_edges = -1; + mutex_unlock(&dht11->lock); + return ret; +} + +static const struct iio_info dht11_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = dht11_read_raw, +}; + +static const struct iio_chan_spec dht11_chan_spec[] = { + { .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), }, + { .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), } +}; + +static const struct of_device_id dht11_dt_ids[] = { + { .compatible = "dht11", }, + { } +}; +MODULE_DEVICE_TABLE(of, dht11_dt_ids); + +static int dht11_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct dht11 *dht11; + struct iio_dev *iio; + int ret; + + iio = devm_iio_device_alloc(dev, sizeof(*dht11)); + if (!iio) { + dev_err(dev, "Failed to allocate IIO device\n"); + return -ENOMEM; + } + + dht11 = iio_priv(iio); + dht11->dev = dev; + + dht11->gpio = ret = of_get_gpio(node, 0); + if (ret < 0) + return ret; + ret = devm_gpio_request_one(dev, dht11->gpio, GPIOF_IN, pdev->name); + if (ret) + return ret; + + dht11->irq = gpio_to_irq(dht11->gpio); + if (dht11->irq < 0) { + dev_err(dev, "GPIO %d has no interrupt\n", dht11->gpio); + return -EINVAL; + } + + dht11->timestamp = iio_get_time_ns() - DHT11_DATA_VALID_TIME - 1; + dht11->num_edges = -1; + + platform_set_drvdata(pdev, iio); + + init_completion(&dht11->completion); + mutex_init(&dht11->lock); + iio->name = pdev->name; + iio->dev.parent = &pdev->dev; + iio->info = &dht11_iio_info; + iio->modes = INDIO_DIRECT_MODE; + iio->channels = dht11_chan_spec; + iio->num_channels = ARRAY_SIZE(dht11_chan_spec); + + return devm_iio_device_register(dev, iio); +} + +static struct platform_driver dht11_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = dht11_dt_ids, + }, + .probe = dht11_probe, +}; + +module_platform_driver(dht11_driver); + +MODULE_AUTHOR("Harald Geyer <harald@ccbib.org>"); +MODULE_DESCRIPTION("DHT11 humidity/temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/humidity/si7005.c b/drivers/iio/humidity/si7005.c new file mode 100644 index 000000000..bdd586e6d --- /dev/null +++ b/drivers/iio/humidity/si7005.c @@ -0,0 +1,189 @@ +/* + * si7005.c - Support for Silabs Si7005 humidity and temperature sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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 0x40) + * + * TODO: heater, fast mode, processed mode (temp. / linearity compensation) + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/pm.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define SI7005_STATUS 0x00 +#define SI7005_DATA 0x01 /* 16-bit, MSB */ +#define SI7005_CONFIG 0x03 +#define SI7005_ID 0x11 + +#define SI7005_STATUS_NRDY BIT(0) +#define SI7005_CONFIG_TEMP BIT(4) +#define SI7005_CONFIG_START BIT(0) + +#define SI7005_ID_7005 0x50 +#define SI7005_ID_7015 0xf0 + +struct si7005_data { + struct i2c_client *client; + struct mutex lock; + u8 config; +}; + +static int si7005_read_measurement(struct si7005_data *data, bool temp) +{ + int tries = 50; + int ret; + + mutex_lock(&data->lock); + + ret = i2c_smbus_write_byte_data(data->client, SI7005_CONFIG, + data->config | SI7005_CONFIG_START | + (temp ? SI7005_CONFIG_TEMP : 0)); + if (ret < 0) + goto done; + + while (tries-- > 0) { + msleep(20); + ret = i2c_smbus_read_byte_data(data->client, SI7005_STATUS); + if (ret < 0) + goto done; + if (!(ret & SI7005_STATUS_NRDY)) + break; + } + if (tries < 0) { + ret = -EIO; + goto done; + } + + ret = i2c_smbus_read_word_swapped(data->client, SI7005_DATA); + +done: + mutex_unlock(&data->lock); + + return ret; +} + +static int si7005_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct si7005_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = si7005_read_measurement(data, chan->type == IIO_TEMP); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + *val = 7; + *val2 = 812500; + } else { + *val = 3; + *val2 = 906250; + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) + *val = -50 * 32 * 4; + else + *val = -24 * 16 * 16; + return IIO_VAL_INT; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec si7005_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + } +}; + +static const struct iio_info si7005_info = { + .read_raw = si7005_read_raw, + .driver_module = THIS_MODULE, +}; + +static int si7005_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct si7005_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + 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; + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &si7005_info; + + indio_dev->channels = si7005_channels; + indio_dev->num_channels = ARRAY_SIZE(si7005_channels); + + ret = i2c_smbus_read_byte_data(client, SI7005_ID); + if (ret < 0) + return ret; + if (ret != SI7005_ID_7005 && ret != SI7005_ID_7015) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(client, SI7005_CONFIG); + if (ret < 0) + return ret; + data->config = ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id si7005_id[] = { + { "si7005", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si7005_id); + +static struct i2c_driver si7005_driver = { + .driver = { + .name = "si7005", + .owner = THIS_MODULE, + }, + .probe = si7005_probe, + .id_table = si7005_id, +}; +module_i2c_driver(si7005_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Silabs Si7005 humidity and temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/humidity/si7020.c b/drivers/iio/humidity/si7020.c new file mode 100644 index 000000000..fa3b809af --- /dev/null +++ b/drivers/iio/humidity/si7020.c @@ -0,0 +1,161 @@ +/* + * si7020.c - Silicon Labs Si7013/20/21 Relative Humidity and Temp Sensors + * Copyright (c) 2013,2014 Uplogix, Inc. + * David Barksdale <dbarksdale@uplogix.com> + * + * 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 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. + */ + +/* + * The Silicon Labs Si7013/20/21 Relative Humidity and Temperature Sensors + * are i2c devices which have an identical programming interface for + * measuring relative humidity and temperature. The Si7013 has an additional + * temperature input which this driver does not support. + * + * Data Sheets: + * Si7013: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7013.pdf + * Si7020: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7020.pdf + * Si7021: http://www.silabs.com/Support%20Documents/TechnicalDocs/Si7021.pdf + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sysfs.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* Measure Relative Humidity, Hold Master Mode */ +#define SI7020CMD_RH_HOLD 0xE5 +/* Measure Temperature, Hold Master Mode */ +#define SI7020CMD_TEMP_HOLD 0xE3 +/* Software Reset */ +#define SI7020CMD_RESET 0xFE + +static int si7020_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct i2c_client **client = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_data(*client, + chan->type == IIO_TEMP ? + SI7020CMD_TEMP_HOLD : + SI7020CMD_RH_HOLD); + if (ret < 0) + return ret; + *val = ret >> 2; + if (chan->type == IIO_HUMIDITYRELATIVE) + *val &= GENMASK(11, 0); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) + *val = 175720; /* = 175.72 * 1000 */ + else + *val = 125 * 1000; + *val2 = 65536 >> 2; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_OFFSET: + /* + * Since iio_convert_raw_to_processed_unlocked assumes offset + * is an integer we have to round these values and lose + * accuracy. + * Relative humidity will be 0.0032959% too high and + * temperature will be 0.00277344 degrees too high. + * This is no big deal because it's within the accuracy of the + * sensor. + */ + if (chan->type == IIO_TEMP) + *val = -4368; /* = -46.85 * (65536 >> 2) / 175.72 */ + else + *val = -786; /* = -6 * (65536 >> 2) / 125 */ + return IIO_VAL_INT; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec si7020_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET), + } +}; + +static const struct iio_info si7020_info = { + .read_raw = si7020_read_raw, + .driver_module = THIS_MODULE, +}; + +static int si7020_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct i2c_client **data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -ENODEV; + + /* Reset device, loads default settings. */ + ret = i2c_smbus_write_byte(client, SI7020CMD_RESET); + if (ret < 0) + return ret; + /* Wait the maximum power-up time after software reset. */ + msleep(15); + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + *data = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &si7020_info; + indio_dev->channels = si7020_channels; + indio_dev->num_channels = ARRAY_SIZE(si7020_channels); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id si7020_id[] = { + { "si7020", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, si7020_id); + +static struct i2c_driver si7020_driver = { + .driver.name = "si7020", + .probe = si7020_probe, + .id_table = si7020_id, +}; + +module_i2c_driver(si7020_driver); +MODULE_DESCRIPTION("Silicon Labs Si7013/20/21 Relative Humidity and Temperature Sensors"); +MODULE_AUTHOR("David Barksdale <dbarksdale@uplogix.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/iio_core.h b/drivers/iio/iio_core.h new file mode 100644 index 000000000..359883525 --- /dev/null +++ b/drivers/iio/iio_core.h @@ -0,0 +1,82 @@ +/* The industrial I/O core function defs. + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * These definitions are meant for use only within the IIO core, not individual + * drivers. + */ + +#ifndef _IIO_CORE_H_ +#define _IIO_CORE_H_ +#include <linux/kernel.h> +#include <linux/device.h> + +struct iio_chan_spec; +struct iio_dev; + +extern struct device_type iio_device_type; + +int __iio_add_chan_devattr(const char *postfix, + struct iio_chan_spec const *chan, + ssize_t (*func)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*writefunc)(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len), + u64 mask, + enum iio_shared_by shared_by, + struct device *dev, + struct list_head *attr_list); +void iio_free_chan_devattr_list(struct list_head *attr_list); + +ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals); + +/* Event interface flags */ +#define IIO_BUSY_BIT_POS 1 + +#ifdef CONFIG_IIO_BUFFER +struct poll_table_struct; + +unsigned int iio_buffer_poll(struct file *filp, + struct poll_table_struct *wait); +ssize_t iio_buffer_read_first_n_outer(struct file *filp, char __user *buf, + size_t n, loff_t *f_ps); + +int iio_buffer_alloc_sysfs_and_mask(struct iio_dev *indio_dev); +void iio_buffer_free_sysfs_and_mask(struct iio_dev *indio_dev); + +#define iio_buffer_poll_addr (&iio_buffer_poll) +#define iio_buffer_read_first_n_outer_addr (&iio_buffer_read_first_n_outer) + +void iio_disable_all_buffers(struct iio_dev *indio_dev); +void iio_buffer_wakeup_poll(struct iio_dev *indio_dev); + +#else + +#define iio_buffer_poll_addr NULL +#define iio_buffer_read_first_n_outer_addr NULL + +static inline int iio_buffer_alloc_sysfs_and_mask(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void iio_buffer_free_sysfs_and_mask(struct iio_dev *indio_dev) {} + +static inline void iio_disable_all_buffers(struct iio_dev *indio_dev) {} +static inline void iio_buffer_wakeup_poll(struct iio_dev *indio_dev) {} + +#endif + +int iio_device_register_eventset(struct iio_dev *indio_dev); +void iio_device_unregister_eventset(struct iio_dev *indio_dev); +void iio_device_wakeup_eventset(struct iio_dev *indio_dev); +int iio_event_getfd(struct iio_dev *indio_dev); + +#endif diff --git a/drivers/iio/iio_core_trigger.h b/drivers/iio/iio_core_trigger.h new file mode 100644 index 000000000..1fdb1e4ea --- /dev/null +++ b/drivers/iio/iio_core_trigger.h @@ -0,0 +1,43 @@ + +/* The industrial I/O core, trigger consumer handling functions + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#ifdef CONFIG_IIO_TRIGGER +/** + * iio_device_register_trigger_consumer() - set up an iio_dev to use triggers + * @indio_dev: iio_dev associated with the device that will consume the trigger + **/ +void iio_device_register_trigger_consumer(struct iio_dev *indio_dev); + +/** + * iio_device_unregister_trigger_consumer() - reverse the registration process + * @indio_dev: iio_dev associated with the device that consumed the trigger + **/ +void iio_device_unregister_trigger_consumer(struct iio_dev *indio_dev); + +#else + +/** + * iio_device_register_trigger_consumer() - set up an iio_dev to use triggers + * @indio_dev: iio_dev associated with the device that will consume the trigger + **/ +static int iio_device_register_trigger_consumer(struct iio_dev *indio_dev) +{ + return 0; +} + +/** + * iio_device_unregister_trigger_consumer() - reverse the registration process + * @indio_dev: iio_dev associated with the device that consumed the trigger + **/ +static void iio_device_unregister_trigger_consumer(struct iio_dev *indio_dev) +{ +} + +#endif /* CONFIG_TRIGGER_CONSUMER */ diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig new file mode 100644 index 000000000..5e610f7de --- /dev/null +++ b/drivers/iio/imu/Kconfig @@ -0,0 +1,53 @@ +# +# IIO imu drivers configuration +# +# When adding new entries keep the list in alphabetical order + +menu "Inertial measurement units" + +config ADIS16400 + tristate "Analog Devices ADIS16400 and similar IMU SPI driver" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say yes here to build support for Analog Devices adis16300, adis16344, + adis16350, adis16354, adis16355, adis16360, adis16362, adis16364, + adis16365, adis16400 and adis16405 triaxial inertial sensors + (adis16400 series also have magnetometers). + +config ADIS16480 + tristate "Analog Devices ADIS16480 and similar IMU driver" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say yes here to build support for Analog Devices ADIS16375, ADIS16480, + ADIS16485, ADIS16488 inertial sensors. + +config KMX61 + tristate "Kionix KMX61 6-axis accelerometer and magnetometer" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for Kionix KMX61 6-axis + accelerometer and magnetometer. + To compile this driver as module, choose M here: the module will + be called kmx61. + +source "drivers/iio/imu/inv_mpu6050/Kconfig" + +endmenu + +config IIO_ADIS_LIB + tristate + help + A set of IO helper functions for the Analog Devices ADIS* device family. + +config IIO_ADIS_LIB_BUFFER + bool + select IIO_TRIGGERED_BUFFER + help + A set of buffer helper functions for the Analog Devices ADIS* device + family. diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile new file mode 100644 index 000000000..e1e6e3d70 --- /dev/null +++ b/drivers/iio/imu/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for Inertial Measurement Units +# + +# When adding new entries keep the list in alphabetical order +adis16400-y := adis16400_core.o +adis16400-$(CONFIG_IIO_BUFFER) += adis16400_buffer.o +obj-$(CONFIG_ADIS16400) += adis16400.o +obj-$(CONFIG_ADIS16480) += adis16480.o + +adis_lib-y += adis.o +adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_trigger.o +adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o +obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o + +obj-y += inv_mpu6050/ + +obj-$(CONFIG_KMX61) += kmx61.o diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c new file mode 100644 index 000000000..911255d41 --- /dev/null +++ b/drivers/iio/imu/adis.c @@ -0,0 +1,440 @@ +/* + * Common library for ADIS16XXX devices + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/module.h> +#include <asm/unaligned.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/imu/adis.h> + +#define ADIS_MSC_CTRL_DATA_RDY_EN BIT(2) +#define ADIS_MSC_CTRL_DATA_RDY_POL_HIGH BIT(1) +#define ADIS_MSC_CTRL_DATA_RDY_DIO2 BIT(0) +#define ADIS_GLOB_CMD_SW_RESET BIT(7) + +int adis_write_reg(struct adis *adis, unsigned int reg, + unsigned int value, unsigned int size) +{ + unsigned int page = reg / ADIS_PAGE_SIZE; + int ret, i; + struct spi_message msg; + struct spi_transfer xfers[] = { + { + .tx_buf = adis->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = adis->data->write_delay, + }, { + .tx_buf = adis->tx + 2, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = adis->data->write_delay, + }, { + .tx_buf = adis->tx + 4, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = adis->data->write_delay, + }, { + .tx_buf = adis->tx + 6, + .bits_per_word = 8, + .len = 2, + .delay_usecs = adis->data->write_delay, + }, { + .tx_buf = adis->tx + 8, + .bits_per_word = 8, + .len = 2, + .delay_usecs = adis->data->write_delay, + }, + }; + + mutex_lock(&adis->txrx_lock); + + spi_message_init(&msg); + + if (adis->current_page != page) { + adis->tx[0] = ADIS_WRITE_REG(ADIS_REG_PAGE_ID); + adis->tx[1] = page; + spi_message_add_tail(&xfers[0], &msg); + } + + switch (size) { + case 4: + adis->tx[8] = ADIS_WRITE_REG(reg + 3); + adis->tx[9] = (value >> 24) & 0xff; + adis->tx[6] = ADIS_WRITE_REG(reg + 2); + adis->tx[7] = (value >> 16) & 0xff; + case 2: + adis->tx[4] = ADIS_WRITE_REG(reg + 1); + adis->tx[5] = (value >> 8) & 0xff; + case 1: + adis->tx[2] = ADIS_WRITE_REG(reg); + adis->tx[3] = value & 0xff; + break; + default: + ret = -EINVAL; + goto out_unlock; + } + + xfers[size].cs_change = 0; + + for (i = 1; i <= size; i++) + spi_message_add_tail(&xfers[i], &msg); + + ret = spi_sync(adis->spi, &msg); + if (ret) { + dev_err(&adis->spi->dev, "Failed to write register 0x%02X: %d\n", + reg, ret); + } else { + adis->current_page = page; + } + +out_unlock: + mutex_unlock(&adis->txrx_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(adis_write_reg); + +/** + * adis_read_reg() - read 2 bytes from a 16-bit register + * @adis: The adis device + * @reg: The address of the lower of the two registers + * @val: The value read back from the device + */ +int adis_read_reg(struct adis *adis, unsigned int reg, + unsigned int *val, unsigned int size) +{ + unsigned int page = reg / ADIS_PAGE_SIZE; + struct spi_message msg; + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = adis->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = adis->data->write_delay, + }, { + .tx_buf = adis->tx + 2, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = adis->data->read_delay, + }, { + .tx_buf = adis->tx + 4, + .rx_buf = adis->rx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = adis->data->read_delay, + }, { + .rx_buf = adis->rx + 2, + .bits_per_word = 8, + .len = 2, + .delay_usecs = adis->data->read_delay, + }, + }; + + mutex_lock(&adis->txrx_lock); + spi_message_init(&msg); + + if (adis->current_page != page) { + adis->tx[0] = ADIS_WRITE_REG(ADIS_REG_PAGE_ID); + adis->tx[1] = page; + spi_message_add_tail(&xfers[0], &msg); + } + + switch (size) { + case 4: + adis->tx[2] = ADIS_READ_REG(reg + 2); + adis->tx[3] = 0; + spi_message_add_tail(&xfers[1], &msg); + case 2: + adis->tx[4] = ADIS_READ_REG(reg); + adis->tx[5] = 0; + spi_message_add_tail(&xfers[2], &msg); + spi_message_add_tail(&xfers[3], &msg); + break; + default: + ret = -EINVAL; + goto out_unlock; + } + + ret = spi_sync(adis->spi, &msg); + if (ret) { + dev_err(&adis->spi->dev, "Failed to read register 0x%02X: %d\n", + reg, ret); + goto out_unlock; + } else { + adis->current_page = page; + } + + switch (size) { + case 4: + *val = get_unaligned_be32(adis->rx); + break; + case 2: + *val = get_unaligned_be16(adis->rx + 2); + break; + } + +out_unlock: + mutex_unlock(&adis->txrx_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(adis_read_reg); + +#ifdef CONFIG_DEBUG_FS + +int adis_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, unsigned int *readval) +{ + struct adis *adis = iio_device_get_drvdata(indio_dev); + + if (readval) { + uint16_t val16; + int ret; + + ret = adis_read_reg_16(adis, reg, &val16); + *readval = val16; + + return ret; + } else { + return adis_write_reg_16(adis, reg, writeval); + } +} +EXPORT_SYMBOL(adis_debugfs_reg_access); + +#endif + +/** + * adis_enable_irq() - Enable or disable data ready IRQ + * @adis: The adis device + * @enable: Whether to enable the IRQ + * + * Returns 0 on success, negative error code otherwise + */ +int adis_enable_irq(struct adis *adis, bool enable) +{ + int ret = 0; + uint16_t msc; + + if (adis->data->enable_irq) + return adis->data->enable_irq(adis, enable); + + ret = adis_read_reg_16(adis, adis->data->msc_ctrl_reg, &msc); + if (ret) + goto error_ret; + + msc |= ADIS_MSC_CTRL_DATA_RDY_POL_HIGH; + msc &= ~ADIS_MSC_CTRL_DATA_RDY_DIO2; + if (enable) + msc |= ADIS_MSC_CTRL_DATA_RDY_EN; + else + msc &= ~ADIS_MSC_CTRL_DATA_RDY_EN; + + ret = adis_write_reg_16(adis, adis->data->msc_ctrl_reg, msc); + +error_ret: + return ret; +} +EXPORT_SYMBOL(adis_enable_irq); + +/** + * adis_check_status() - Check the device for error conditions + * @adis: The adis device + * + * Returns 0 on success, a negative error code otherwise + */ +int adis_check_status(struct adis *adis) +{ + uint16_t status; + int ret; + int i; + + ret = adis_read_reg_16(adis, adis->data->diag_stat_reg, &status); + if (ret < 0) + return ret; + + status &= adis->data->status_error_mask; + + if (status == 0) + return 0; + + for (i = 0; i < 16; ++i) { + if (status & BIT(i)) { + dev_err(&adis->spi->dev, "%s.\n", + adis->data->status_error_msgs[i]); + } + } + + return -EIO; +} +EXPORT_SYMBOL_GPL(adis_check_status); + +/** + * adis_reset() - Reset the device + * @adis: The adis device + * + * Returns 0 on success, a negative error code otherwise + */ +int adis_reset(struct adis *adis) +{ + int ret; + + ret = adis_write_reg_8(adis, adis->data->glob_cmd_reg, + ADIS_GLOB_CMD_SW_RESET); + if (ret) + dev_err(&adis->spi->dev, "Failed to reset device: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(adis_reset); + +static int adis_self_test(struct adis *adis) +{ + int ret; + + ret = adis_write_reg_16(adis, adis->data->msc_ctrl_reg, + adis->data->self_test_mask); + if (ret) { + dev_err(&adis->spi->dev, "Failed to initiate self test: %d\n", + ret); + return ret; + } + + msleep(adis->data->startup_delay); + + return adis_check_status(adis); +} + +/** + * adis_inital_startup() - Performs device self-test + * @adis: The adis device + * + * Returns 0 if the device is operational, a negative error code otherwise. + * + * This function should be called early on in the device initialization sequence + * to ensure that the device is in a sane and known state and that it is usable. + */ +int adis_initial_startup(struct adis *adis) +{ + int ret; + + ret = adis_self_test(adis); + if (ret) { + dev_err(&adis->spi->dev, "Self-test failed, trying reset.\n"); + adis_reset(adis); + msleep(adis->data->startup_delay); + ret = adis_self_test(adis); + if (ret) { + dev_err(&adis->spi->dev, "Second self-test failed, giving up.\n"); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(adis_initial_startup); + +/** + * adis_single_conversion() - Performs a single sample conversion + * @indio_dev: The IIO device + * @chan: The IIO channel + * @error_mask: Mask for the error bit + * @val: Result of the conversion + * + * Returns IIO_VAL_INT on success, a negative error code otherwise. + * + * The function performs a single conversion on a given channel and post + * processes the value accordingly to the channel spec. If a error_mask is given + * the function will check if the mask is set in the returned raw value. If it + * is set the function will perform a self-check. If the device does not report + * a error bit in the channels raw value set error_mask to 0. + */ +int adis_single_conversion(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int error_mask, int *val) +{ + struct adis *adis = iio_device_get_drvdata(indio_dev); + unsigned int uval; + int ret; + + mutex_lock(&indio_dev->mlock); + + ret = adis_read_reg(adis, chan->address, &uval, + chan->scan_type.storagebits / 8); + if (ret) + goto err_unlock; + + if (uval & error_mask) { + ret = adis_check_status(adis); + if (ret) + goto err_unlock; + } + + if (chan->scan_type.sign == 's') + *val = sign_extend32(uval, chan->scan_type.realbits - 1); + else + *val = uval & ((1 << chan->scan_type.realbits) - 1); + + ret = IIO_VAL_INT; +err_unlock: + mutex_unlock(&indio_dev->mlock); + return ret; +} +EXPORT_SYMBOL_GPL(adis_single_conversion); + +/** + * adis_init() - Initialize adis device structure + * @adis: The adis device + * @indio_dev: The iio device + * @spi: The spi device + * @data: Chip specific data + * + * Returns 0 on success, a negative error code otherwise. + * + * This function must be called, before any other adis helper function may be + * called. + */ +int adis_init(struct adis *adis, struct iio_dev *indio_dev, + struct spi_device *spi, const struct adis_data *data) +{ + mutex_init(&adis->txrx_lock); + adis->spi = spi; + adis->data = data; + iio_device_set_drvdata(indio_dev, adis); + + if (data->has_paging) { + /* Need to set the page before first read/write */ + adis->current_page = -1; + } else { + /* Page will always be 0 */ + adis->current_page = 0; + } + + return adis_enable_irq(adis, false); +} +EXPORT_SYMBOL_GPL(adis_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Common library code for ADIS16XXX devices"); diff --git a/drivers/iio/imu/adis16400.h b/drivers/iio/imu/adis16400.h new file mode 100644 index 000000000..73b189c1c --- /dev/null +++ b/drivers/iio/imu/adis16400.h @@ -0,0 +1,215 @@ +/* + * adis16400.h support Analog Devices ADIS16400 + * 3d 18g accelerometers, + * 3d gyroscopes, + * 3d 2.5gauss magnetometers via SPI + * + * Copyright (c) 2009 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> + * Copyright (c) 2007 Jonathan Cameron <jic23@kernel.org> + * + * Loosely based upon lis3l02dq.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef SPI_ADIS16400_H_ +#define SPI_ADIS16400_H_ + +#include <linux/iio/imu/adis.h> + +#define ADIS16400_STARTUP_DELAY 290 /* ms */ +#define ADIS16400_MTEST_DELAY 90 /* ms */ + +#define ADIS16400_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16400_SUPPLY_OUT 0x02 /* Power supply measurement */ +#define ADIS16400_XGYRO_OUT 0x04 /* X-axis gyroscope output */ +#define ADIS16400_YGYRO_OUT 0x06 /* Y-axis gyroscope output */ +#define ADIS16400_ZGYRO_OUT 0x08 /* Z-axis gyroscope output */ +#define ADIS16400_XACCL_OUT 0x0A /* X-axis accelerometer output */ +#define ADIS16400_YACCL_OUT 0x0C /* Y-axis accelerometer output */ +#define ADIS16400_ZACCL_OUT 0x0E /* Z-axis accelerometer output */ +#define ADIS16400_XMAGN_OUT 0x10 /* X-axis magnetometer measurement */ +#define ADIS16400_YMAGN_OUT 0x12 /* Y-axis magnetometer measurement */ +#define ADIS16400_ZMAGN_OUT 0x14 /* Z-axis magnetometer measurement */ +#define ADIS16400_TEMP_OUT 0x16 /* Temperature output */ +#define ADIS16400_AUX_ADC 0x18 /* Auxiliary ADC measurement */ + +#define ADIS16350_XTEMP_OUT 0x10 /* X-axis gyroscope temperature measurement */ +#define ADIS16350_YTEMP_OUT 0x12 /* Y-axis gyroscope temperature measurement */ +#define ADIS16350_ZTEMP_OUT 0x14 /* Z-axis gyroscope temperature measurement */ + +#define ADIS16300_PITCH_OUT 0x12 /* X axis inclinometer output measurement */ +#define ADIS16300_ROLL_OUT 0x14 /* Y axis inclinometer output measurement */ +#define ADIS16300_AUX_ADC 0x16 /* Auxiliary ADC measurement */ + +#define ADIS16448_BARO_OUT 0x16 /* Barometric pressure output */ +#define ADIS16448_TEMP_OUT 0x18 /* Temperature output */ + +/* Calibration parameters */ +#define ADIS16400_XGYRO_OFF 0x1A /* X-axis gyroscope bias offset factor */ +#define ADIS16400_YGYRO_OFF 0x1C /* Y-axis gyroscope bias offset factor */ +#define ADIS16400_ZGYRO_OFF 0x1E /* Z-axis gyroscope bias offset factor */ +#define ADIS16400_XACCL_OFF 0x20 /* X-axis acceleration bias offset factor */ +#define ADIS16400_YACCL_OFF 0x22 /* Y-axis acceleration bias offset factor */ +#define ADIS16400_ZACCL_OFF 0x24 /* Z-axis acceleration bias offset factor */ +#define ADIS16400_XMAGN_HIF 0x26 /* X-axis magnetometer, hard-iron factor */ +#define ADIS16400_YMAGN_HIF 0x28 /* Y-axis magnetometer, hard-iron factor */ +#define ADIS16400_ZMAGN_HIF 0x2A /* Z-axis magnetometer, hard-iron factor */ +#define ADIS16400_XMAGN_SIF 0x2C /* X-axis magnetometer, soft-iron factor */ +#define ADIS16400_YMAGN_SIF 0x2E /* Y-axis magnetometer, soft-iron factor */ +#define ADIS16400_ZMAGN_SIF 0x30 /* Z-axis magnetometer, soft-iron factor */ + +#define ADIS16400_GPIO_CTRL 0x32 /* Auxiliary digital input/output control */ +#define ADIS16400_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16400_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16400_SENS_AVG 0x38 /* Dynamic range and digital filter control */ +#define ADIS16400_SLP_CNT 0x3A /* Sleep mode control */ +#define ADIS16400_DIAG_STAT 0x3C /* System status */ + +/* Alarm functions */ +#define ADIS16400_GLOB_CMD 0x3E /* System command */ +#define ADIS16400_ALM_MAG1 0x40 /* Alarm 1 amplitude threshold */ +#define ADIS16400_ALM_MAG2 0x42 /* Alarm 2 amplitude threshold */ +#define ADIS16400_ALM_SMPL1 0x44 /* Alarm 1 sample size */ +#define ADIS16400_ALM_SMPL2 0x46 /* Alarm 2 sample size */ +#define ADIS16400_ALM_CTRL 0x48 /* Alarm control */ +#define ADIS16400_AUX_DAC 0x4A /* Auxiliary DAC data */ + +#define ADIS16334_LOT_ID1 0x52 /* Lot identification code 1 */ +#define ADIS16334_LOT_ID2 0x54 /* Lot identification code 2 */ +#define ADIS16400_PRODUCT_ID 0x56 /* Product identifier */ +#define ADIS16334_SERIAL_NUMBER 0x58 /* Serial number, lot specific */ + +#define ADIS16400_ERROR_ACTIVE (1<<14) +#define ADIS16400_NEW_DATA (1<<14) + +/* MSC_CTRL */ +#define ADIS16400_MSC_CTRL_MEM_TEST (1<<11) +#define ADIS16400_MSC_CTRL_INT_SELF_TEST (1<<10) +#define ADIS16400_MSC_CTRL_NEG_SELF_TEST (1<<9) +#define ADIS16400_MSC_CTRL_POS_SELF_TEST (1<<8) +#define ADIS16400_MSC_CTRL_GYRO_BIAS (1<<7) +#define ADIS16400_MSC_CTRL_ACCL_ALIGN (1<<6) +#define ADIS16400_MSC_CTRL_DATA_RDY_EN (1<<2) +#define ADIS16400_MSC_CTRL_DATA_RDY_POL_HIGH (1<<1) +#define ADIS16400_MSC_CTRL_DATA_RDY_DIO2 (1<<0) + +/* SMPL_PRD */ +#define ADIS16400_SMPL_PRD_TIME_BASE (1<<7) +#define ADIS16400_SMPL_PRD_DIV_MASK 0x7F + +/* DIAG_STAT */ +#define ADIS16400_DIAG_STAT_ZACCL_FAIL 15 +#define ADIS16400_DIAG_STAT_YACCL_FAIL 14 +#define ADIS16400_DIAG_STAT_XACCL_FAIL 13 +#define ADIS16400_DIAG_STAT_XGYRO_FAIL 12 +#define ADIS16400_DIAG_STAT_YGYRO_FAIL 11 +#define ADIS16400_DIAG_STAT_ZGYRO_FAIL 10 +#define ADIS16400_DIAG_STAT_ALARM2 9 +#define ADIS16400_DIAG_STAT_ALARM1 8 +#define ADIS16400_DIAG_STAT_FLASH_CHK 6 +#define ADIS16400_DIAG_STAT_SELF_TEST 5 +#define ADIS16400_DIAG_STAT_OVERFLOW 4 +#define ADIS16400_DIAG_STAT_SPI_FAIL 3 +#define ADIS16400_DIAG_STAT_FLASH_UPT 2 +#define ADIS16400_DIAG_STAT_POWER_HIGH 1 +#define ADIS16400_DIAG_STAT_POWER_LOW 0 + +/* GLOB_CMD */ +#define ADIS16400_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16400_GLOB_CMD_P_AUTO_NULL (1<<4) +#define ADIS16400_GLOB_CMD_FLASH_UPD (1<<3) +#define ADIS16400_GLOB_CMD_DAC_LATCH (1<<2) +#define ADIS16400_GLOB_CMD_FAC_CALIB (1<<1) +#define ADIS16400_GLOB_CMD_AUTO_NULL (1<<0) + +/* SLP_CNT */ +#define ADIS16400_SLP_CNT_POWER_OFF (1<<8) + +#define ADIS16334_RATE_DIV_SHIFT 8 +#define ADIS16334_RATE_INT_CLK BIT(0) + +#define ADIS16400_SPI_SLOW (u32)(300 * 1000) +#define ADIS16400_SPI_BURST (u32)(1000 * 1000) +#define ADIS16400_SPI_FAST (u32)(2000 * 1000) + +#define ADIS16400_HAS_PROD_ID BIT(0) +#define ADIS16400_NO_BURST BIT(1) +#define ADIS16400_HAS_SLOW_MODE BIT(2) +#define ADIS16400_HAS_SERIAL_NUMBER BIT(3) +#define ADIS16400_BURST_DIAG_STAT BIT(4) + +struct adis16400_state; + +struct adis16400_chip_info { + const struct iio_chan_spec *channels; + const int num_channels; + const long flags; + unsigned int gyro_scale_micro; + unsigned int accel_scale_micro; + int temp_scale_nano; + int temp_offset; + int (*set_freq)(struct adis16400_state *st, unsigned int freq); + int (*get_freq)(struct adis16400_state *st); +}; + +/** + * struct adis16400_state - device instance specific data + * @variant: chip variant info + * @filt_int: integer part of requested filter frequency + * @adis: adis device + **/ +struct adis16400_state { + struct adis16400_chip_info *variant; + int filt_int; + + struct adis adis; + unsigned long avail_scan_mask[2]; +}; + +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +enum { + ADIS16400_SCAN_SUPPLY, + ADIS16400_SCAN_GYRO_X, + ADIS16400_SCAN_GYRO_Y, + ADIS16400_SCAN_GYRO_Z, + ADIS16400_SCAN_ACC_X, + ADIS16400_SCAN_ACC_Y, + ADIS16400_SCAN_ACC_Z, + ADIS16400_SCAN_MAGN_X, + ADIS16400_SCAN_MAGN_Y, + ADIS16400_SCAN_MAGN_Z, + ADIS16400_SCAN_BARO, + ADIS16350_SCAN_TEMP_X, + ADIS16350_SCAN_TEMP_Y, + ADIS16350_SCAN_TEMP_Z, + ADIS16300_SCAN_INCLI_X, + ADIS16300_SCAN_INCLI_Y, + ADIS16400_SCAN_ADC, + ADIS16400_SCAN_TIMESTAMP, +}; + +#ifdef CONFIG_IIO_BUFFER + +ssize_t adis16400_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + + +int adis16400_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask); +irqreturn_t adis16400_trigger_handler(int irq, void *p); + +#else /* CONFIG_IIO_BUFFER */ + +#define adis16400_update_scan_mode NULL +#define adis16400_trigger_handler NULL + +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* SPI_ADIS16400_H_ */ diff --git a/drivers/iio/imu/adis16400_buffer.c b/drivers/iio/imu/adis16400_buffer.c new file mode 100644 index 000000000..90c24a23c --- /dev/null +++ b/drivers/iio/imu/adis16400_buffer.c @@ -0,0 +1,100 @@ +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/bitops.h> +#include <linux/export.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +#include "adis16400.h" + +int adis16400_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct adis16400_state *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + unsigned int burst_length; + u8 *tx; + + if (st->variant->flags & ADIS16400_NO_BURST) + return adis_update_scan_mode(indio_dev, scan_mask); + + kfree(adis->xfer); + kfree(adis->buffer); + + /* All but the timestamp channel */ + burst_length = (indio_dev->num_channels - 1) * sizeof(u16); + if (st->variant->flags & ADIS16400_BURST_DIAG_STAT) + burst_length += sizeof(u16); + + adis->xfer = kcalloc(2, sizeof(*adis->xfer), GFP_KERNEL); + if (!adis->xfer) + return -ENOMEM; + + adis->buffer = kzalloc(burst_length + sizeof(u16), GFP_KERNEL); + if (!adis->buffer) + return -ENOMEM; + + tx = adis->buffer + burst_length; + tx[0] = ADIS_READ_REG(ADIS16400_GLOB_CMD); + tx[1] = 0; + + adis->xfer[0].tx_buf = tx; + adis->xfer[0].bits_per_word = 8; + adis->xfer[0].len = 2; + adis->xfer[1].rx_buf = adis->buffer; + adis->xfer[1].bits_per_word = 8; + adis->xfer[1].len = burst_length; + + spi_message_init(&adis->msg); + spi_message_add_tail(&adis->xfer[0], &adis->msg); + spi_message_add_tail(&adis->xfer[1], &adis->msg); + + return 0; +} + +irqreturn_t adis16400_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16400_state *st = iio_priv(indio_dev); + struct adis *adis = &st->adis; + u32 old_speed_hz = st->adis.spi->max_speed_hz; + void *buffer; + int ret; + + if (!adis->buffer) + return -ENOMEM; + + if (!(st->variant->flags & ADIS16400_NO_BURST) && + st->adis.spi->max_speed_hz > ADIS16400_SPI_BURST) { + st->adis.spi->max_speed_hz = ADIS16400_SPI_BURST; + spi_setup(st->adis.spi); + } + + ret = spi_sync(adis->spi, &adis->msg); + if (ret) + dev_err(&adis->spi->dev, "Failed to read data: %d\n", ret); + + if (!(st->variant->flags & ADIS16400_NO_BURST)) { + st->adis.spi->max_speed_hz = old_speed_hz; + spi_setup(st->adis.spi); + } + + if (st->variant->flags & ADIS16400_BURST_DIAG_STAT) + buffer = adis->buffer + sizeof(u16); + else + buffer = adis->buffer; + + iio_push_to_buffers_with_timestamp(indio_dev, buffer, + pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} diff --git a/drivers/iio/imu/adis16400_core.c b/drivers/iio/imu/adis16400_core.c new file mode 100644 index 000000000..2fd68f221 --- /dev/null +++ b/drivers/iio/imu/adis16400_core.c @@ -0,0 +1,957 @@ +/* + * adis16400.c support Analog Devices ADIS16400/5 + * 3d 2g Linear Accelerometers, + * 3d Gyroscopes, + * 3d Magnetometers via SPI + * + * Copyright (c) 2009 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> + * Copyright (c) 2007 Jonathan Cameron <jic23@kernel.org> + * Copyright (c) 2011 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/bitops.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> + +#include "adis16400.h" + +#ifdef CONFIG_DEBUG_FS + +static ssize_t adis16400_show_serial_number(struct file *file, + char __user *userbuf, size_t count, loff_t *ppos) +{ + struct adis16400_state *st = file->private_data; + u16 lot1, lot2, serial_number; + char buf[16]; + size_t len; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16334_LOT_ID1, &lot1); + if (ret < 0) + return ret; + + ret = adis_read_reg_16(&st->adis, ADIS16334_LOT_ID2, &lot2); + if (ret < 0) + return ret; + + ret = adis_read_reg_16(&st->adis, ADIS16334_SERIAL_NUMBER, + &serial_number); + if (ret < 0) + return ret; + + len = snprintf(buf, sizeof(buf), "%.4x-%.4x-%.4x\n", lot1, lot2, + serial_number); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16400_serial_number_fops = { + .open = simple_open, + .read = adis16400_show_serial_number, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static int adis16400_show_product_id(void *arg, u64 *val) +{ + struct adis16400_state *st = arg; + uint16_t prod_id; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16400_PRODUCT_ID, &prod_id); + if (ret < 0) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(adis16400_product_id_fops, + adis16400_show_product_id, NULL, "%lld\n"); + +static int adis16400_show_flash_count(void *arg, u64 *val) +{ + struct adis16400_state *st = arg; + uint16_t flash_count; + int ret; + + ret = adis_read_reg_16(&st->adis, ADIS16400_FLASH_CNT, &flash_count); + if (ret < 0) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(adis16400_flash_count_fops, + adis16400_show_flash_count, NULL, "%lld\n"); + +static int adis16400_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + + if (st->variant->flags & ADIS16400_HAS_SERIAL_NUMBER) + debugfs_create_file("serial_number", 0400, + indio_dev->debugfs_dentry, st, + &adis16400_serial_number_fops); + if (st->variant->flags & ADIS16400_HAS_PROD_ID) + debugfs_create_file("product_id", 0400, + indio_dev->debugfs_dentry, st, + &adis16400_product_id_fops); + debugfs_create_file("flash_count", 0400, indio_dev->debugfs_dentry, + st, &adis16400_flash_count_fops); + + return 0; +} + +#else + +static int adis16400_debugfs_init(struct iio_dev *indio_dev) +{ + return 0; +} + +#endif + +enum adis16400_chip_variant { + ADIS16300, + ADIS16334, + ADIS16350, + ADIS16360, + ADIS16362, + ADIS16364, + ADIS16400, + ADIS16448, +}; + +static int adis16334_get_freq(struct adis16400_state *st) +{ + int ret; + uint16_t t; + + ret = adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &t); + if (ret < 0) + return ret; + + t >>= ADIS16334_RATE_DIV_SHIFT; + + return 819200 >> t; +} + +static int adis16334_set_freq(struct adis16400_state *st, unsigned int freq) +{ + unsigned int t; + + if (freq < 819200) + t = ilog2(819200 / freq); + else + t = 0; + + if (t > 0x31) + t = 0x31; + + t <<= ADIS16334_RATE_DIV_SHIFT; + t |= ADIS16334_RATE_INT_CLK; + + return adis_write_reg_16(&st->adis, ADIS16400_SMPL_PRD, t); +} + +static int adis16400_get_freq(struct adis16400_state *st) +{ + int sps, ret; + uint16_t t; + + ret = adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &t); + if (ret < 0) + return ret; + + sps = (t & ADIS16400_SMPL_PRD_TIME_BASE) ? 52851 : 1638404; + sps /= (t & ADIS16400_SMPL_PRD_DIV_MASK) + 1; + + return sps; +} + +static int adis16400_set_freq(struct adis16400_state *st, unsigned int freq) +{ + unsigned int t; + uint8_t val = 0; + + t = 1638404 / freq; + if (t >= 128) { + val |= ADIS16400_SMPL_PRD_TIME_BASE; + t = 52851 / freq; + if (t >= 128) + t = 127; + } else if (t != 0) { + t--; + } + + val |= t; + + if (t >= 0x0A || (val & ADIS16400_SMPL_PRD_TIME_BASE)) + st->adis.spi->max_speed_hz = ADIS16400_SPI_SLOW; + else + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + + return adis_write_reg_8(&st->adis, ADIS16400_SMPL_PRD, val); +} + +static const unsigned adis16400_3db_divisors[] = { + [0] = 2, /* Special case */ + [1] = 6, + [2] = 12, + [3] = 25, + [4] = 50, + [5] = 100, + [6] = 200, + [7] = 200, /* Not a valid setting */ +}; + +static int adis16400_set_filter(struct iio_dev *indio_dev, int sps, int val) +{ + struct adis16400_state *st = iio_priv(indio_dev); + uint16_t val16; + int i, ret; + + for (i = ARRAY_SIZE(adis16400_3db_divisors) - 1; i >= 1; i--) { + if (sps / adis16400_3db_divisors[i] >= val) + break; + } + + ret = adis_read_reg_16(&st->adis, ADIS16400_SENS_AVG, &val16); + if (ret < 0) + return ret; + + ret = adis_write_reg_16(&st->adis, ADIS16400_SENS_AVG, + (val16 & ~0x07) | i); + return ret; +} + +/* Power down the device */ +static int adis16400_stop_device(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int ret; + + ret = adis_write_reg_16(&st->adis, ADIS16400_SLP_CNT, + ADIS16400_SLP_CNT_POWER_OFF); + if (ret) + dev_err(&indio_dev->dev, + "problem with turning device off: SLP_CNT"); + + return ret; +} + +static int adis16400_initial_setup(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + uint16_t prod_id, smp_prd; + unsigned int device_id; + int ret; + + /* use low spi speed for init if the device has a slow mode */ + if (st->variant->flags & ADIS16400_HAS_SLOW_MODE) + st->adis.spi->max_speed_hz = ADIS16400_SPI_SLOW; + else + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + st->adis.spi->mode = SPI_MODE_3; + spi_setup(st->adis.spi); + + ret = adis_initial_startup(&st->adis); + if (ret) + return ret; + + if (st->variant->flags & ADIS16400_HAS_PROD_ID) { + ret = adis_read_reg_16(&st->adis, + ADIS16400_PRODUCT_ID, &prod_id); + if (ret) + goto err_ret; + + sscanf(indio_dev->name, "adis%u\n", &device_id); + + if (prod_id != device_id) + dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", + device_id, prod_id); + + dev_info(&indio_dev->dev, "%s: prod_id 0x%04x at CS%d (irq %d)\n", + indio_dev->name, prod_id, + st->adis.spi->chip_select, st->adis.spi->irq); + } + /* use high spi speed if possible */ + if (st->variant->flags & ADIS16400_HAS_SLOW_MODE) { + ret = adis_read_reg_16(&st->adis, ADIS16400_SMPL_PRD, &smp_prd); + if (ret) + goto err_ret; + + if ((smp_prd & ADIS16400_SMPL_PRD_DIV_MASK) < 0x0A) { + st->adis.spi->max_speed_hz = ADIS16400_SPI_FAST; + spi_setup(st->adis.spi); + } + } + +err_ret: + return ret; +} + +static const uint8_t adis16400_addresses[] = { + [ADIS16400_SCAN_GYRO_X] = ADIS16400_XGYRO_OFF, + [ADIS16400_SCAN_GYRO_Y] = ADIS16400_YGYRO_OFF, + [ADIS16400_SCAN_GYRO_Z] = ADIS16400_ZGYRO_OFF, + [ADIS16400_SCAN_ACC_X] = ADIS16400_XACCL_OFF, + [ADIS16400_SCAN_ACC_Y] = ADIS16400_YACCL_OFF, + [ADIS16400_SCAN_ACC_Z] = ADIS16400_ZACCL_OFF, +}; + +static int adis16400_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int ret, sps; + + switch (info) { + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&indio_dev->mlock); + ret = adis_write_reg_16(&st->adis, + adis16400_addresses[chan->scan_index], val); + mutex_unlock(&indio_dev->mlock); + return ret; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + /* + * Need to cache values so we can update if the frequency + * changes. + */ + mutex_lock(&indio_dev->mlock); + st->filt_int = val; + /* Work out update to current value */ + sps = st->variant->get_freq(st); + if (sps < 0) { + mutex_unlock(&indio_dev->mlock); + return sps; + } + + ret = adis16400_set_filter(indio_dev, sps, + val * 1000 + val2 / 1000); + mutex_unlock(&indio_dev->mlock); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + sps = val * 1000 + val2 / 1000; + + if (sps <= 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = st->variant->set_freq(st, sps); + mutex_unlock(&indio_dev->mlock); + return ret; + default: + return -EINVAL; + } +} + +static int adis16400_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int16_t val16; + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = st->variant->gyro_scale_micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) { + *val = 2; + *val2 = 418000; /* 2.418 mV */ + } else { + *val = 0; + *val2 = 805800; /* 805.8 uV */ + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = st->variant->accel_scale_micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MAGN: + *val = 0; + *val2 = 500; /* 0.5 mgauss */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = st->variant->temp_scale_nano / 1000000; + *val2 = (st->variant->temp_scale_nano % 1000000); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PRESSURE: + /* 20 uBar = 0.002kPascal */ + *val = 0; + *val2 = 2000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&indio_dev->mlock); + ret = adis_read_reg_16(&st->adis, + adis16400_addresses[chan->scan_index], &val16); + mutex_unlock(&indio_dev->mlock); + if (ret) + return ret; + val16 = sign_extend32(val16, 11); + *val = val16; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + /* currently only temperature */ + *val = st->variant->temp_offset; + return IIO_VAL_INT; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + mutex_lock(&indio_dev->mlock); + /* Need both the number of taps and the sampling frequency */ + ret = adis_read_reg_16(&st->adis, + ADIS16400_SENS_AVG, + &val16); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + ret = st->variant->get_freq(st); + if (ret >= 0) { + ret /= adis16400_3db_divisors[val16 & 0x07]; + *val = ret / 1000; + *val2 = (ret % 1000) * 1000; + } + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = st->variant->get_freq(st); + if (ret < 0) + return ret; + *val = ret / 1000; + *val2 = (ret % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +#define ADIS16400_VOLTAGE_CHAN(addr, bits, name, si, chn) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = chn, \ + .extend_name = name, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = (si), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_SUPPLY_CHAN(addr, bits) \ + ADIS16400_VOLTAGE_CHAN(addr, bits, "supply", ADIS16400_SCAN_SUPPLY, 0) + +#define ADIS16400_AUX_ADC_CHAN(addr, bits) \ + ADIS16400_VOLTAGE_CHAN(addr, bits, NULL, ADIS16400_SCAN_ADC, 1) + +#define ADIS16400_GYRO_CHAN(mod, addr, bits) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = addr, \ + .scan_index = ADIS16400_SCAN_GYRO_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_ACCEL_CHAN(mod, addr, bits) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = ADIS16400_SCAN_ACC_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_MAGN_CHAN(mod, addr, bits) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = ADIS16400_SCAN_MAGN_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_MOD_TEMP_NAME_X "x" +#define ADIS16400_MOD_TEMP_NAME_Y "y" +#define ADIS16400_MOD_TEMP_NAME_Z "z" + +#define ADIS16400_MOD_TEMP_CHAN(mod, addr, bits) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .extend_name = ADIS16400_MOD_TEMP_NAME_ ## mod, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = ADIS16350_SCAN_TEMP_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_TEMP_CHAN(addr, bits) { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (addr), \ + .scan_index = ADIS16350_SCAN_TEMP_X, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADIS16400_INCLI_CHAN(mod, addr, bits) { \ + .type = IIO_INCLI, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## mod, \ + .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), \ + .address = (addr), \ + .scan_index = ADIS16300_SCAN_INCLI_ ## mod, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = 16, \ + .shift = 0, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec adis16400_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 14), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16400_TEMP_OUT, 12), + ADIS16400_AUX_ADC_CHAN(ADIS16400_AUX_ADC, 12), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec adis16448_channels[] = { + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 16), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 16), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 16), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 16), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 16), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 16), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 16), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 16), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 16), + { + .type = IIO_PRESSURE, + .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), + .address = ADIS16448_BARO_OUT, + .scan_index = ADIS16400_SCAN_BARO, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + ADIS16400_TEMP_CHAN(ADIS16448_TEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec adis16350_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 12), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_MAGN_CHAN(X, ADIS16400_XMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Y, ADIS16400_YMAGN_OUT, 14), + ADIS16400_MAGN_CHAN(Z, ADIS16400_ZMAGN_OUT, 14), + ADIS16400_AUX_ADC_CHAN(ADIS16300_AUX_ADC, 12), + ADIS16400_MOD_TEMP_CHAN(X, ADIS16350_XTEMP_OUT, 12), + ADIS16400_MOD_TEMP_CHAN(Y, ADIS16350_YTEMP_OUT, 12), + ADIS16400_MOD_TEMP_CHAN(Z, ADIS16350_ZTEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec adis16300_channels[] = { + ADIS16400_SUPPLY_CHAN(ADIS16400_SUPPLY_OUT, 12), + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16350_XTEMP_OUT, 12), + ADIS16400_AUX_ADC_CHAN(ADIS16300_AUX_ADC, 12), + ADIS16400_INCLI_CHAN(X, ADIS16300_PITCH_OUT, 13), + ADIS16400_INCLI_CHAN(Y, ADIS16300_ROLL_OUT, 13), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static const struct iio_chan_spec adis16334_channels[] = { + ADIS16400_GYRO_CHAN(X, ADIS16400_XGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Y, ADIS16400_YGYRO_OUT, 14), + ADIS16400_GYRO_CHAN(Z, ADIS16400_ZGYRO_OUT, 14), + ADIS16400_ACCEL_CHAN(X, ADIS16400_XACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Y, ADIS16400_YACCL_OUT, 14), + ADIS16400_ACCEL_CHAN(Z, ADIS16400_ZACCL_OUT, 14), + ADIS16400_TEMP_CHAN(ADIS16350_XTEMP_OUT, 12), + IIO_CHAN_SOFT_TIMESTAMP(ADIS16400_SCAN_TIMESTAMP), +}; + +static struct adis16400_chip_info adis16400_chips[] = { + [ADIS16300] = { + .channels = adis16300_channels, + .num_channels = ARRAY_SIZE(adis16300_channels), + .flags = ADIS16400_HAS_SLOW_MODE, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = 5884, + .temp_scale_nano = 140000000, /* 0.14 C */ + .temp_offset = 25000000 / 140000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16334] = { + .channels = adis16334_channels, + .num_channels = ARRAY_SIZE(adis16334_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_NO_BURST | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(1000), /* 1 mg */ + .temp_scale_nano = 67850000, /* 0.06785 C */ + .temp_offset = 25000000 / 67850, /* 25 C = 0x00 */ + .set_freq = adis16334_set_freq, + .get_freq = adis16334_get_freq, + }, + [ADIS16350] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .gyro_scale_micro = IIO_DEGREE_TO_RAD(73260), /* 0.07326 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(2522), /* 0.002522 g */ + .temp_scale_nano = 145300000, /* 0.1453 C */ + .temp_offset = 25000000 / 145300, /* 25 C = 0x00 */ + .flags = ADIS16400_NO_BURST | ADIS16400_HAS_SLOW_MODE, + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16360] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(3333), /* 3.333 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16362] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(333), /* 0.333 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16364] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE | + ADIS16400_HAS_SERIAL_NUMBER, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(1000), /* 1 mg */ + .temp_scale_nano = 136000000, /* 0.136 C */ + .temp_offset = 25000000 / 136000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16400] = { + .channels = adis16400_channels, + .num_channels = ARRAY_SIZE(adis16400_channels), + .flags = ADIS16400_HAS_PROD_ID | ADIS16400_HAS_SLOW_MODE, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(50000), /* 0.05 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(3333), /* 3.333 mg */ + .temp_scale_nano = 140000000, /* 0.14 C */ + .temp_offset = 25000000 / 140000, /* 25 C = 0x00 */ + .set_freq = adis16400_set_freq, + .get_freq = adis16400_get_freq, + }, + [ADIS16448] = { + .channels = adis16448_channels, + .num_channels = ARRAY_SIZE(adis16448_channels), + .flags = ADIS16400_HAS_PROD_ID | + ADIS16400_HAS_SERIAL_NUMBER | + ADIS16400_BURST_DIAG_STAT, + .gyro_scale_micro = IIO_DEGREE_TO_RAD(10000), /* 0.01 deg/s */ + .accel_scale_micro = IIO_G_TO_M_S_2(833), /* 1/1200 g */ + .temp_scale_nano = 73860000, /* 0.07386 C */ + .temp_offset = 31000000 / 73860, /* 31 C = 0x00 */ + .set_freq = adis16334_set_freq, + .get_freq = adis16334_get_freq, + } +}; + +static const struct iio_info adis16400_info = { + .driver_module = THIS_MODULE, + .read_raw = &adis16400_read_raw, + .write_raw = &adis16400_write_raw, + .update_scan_mode = adis16400_update_scan_mode, + .debugfs_reg_access = adis_debugfs_reg_access, +}; + +static const char * const adis16400_status_error_msgs[] = { + [ADIS16400_DIAG_STAT_ZACCL_FAIL] = "Z-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_YACCL_FAIL] = "Y-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_XACCL_FAIL] = "X-axis accelerometer self-test failure", + [ADIS16400_DIAG_STAT_XGYRO_FAIL] = "X-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_YGYRO_FAIL] = "Y-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_ZGYRO_FAIL] = "Z-axis gyroscope self-test failure", + [ADIS16400_DIAG_STAT_ALARM2] = "Alarm 2 active", + [ADIS16400_DIAG_STAT_ALARM1] = "Alarm 1 active", + [ADIS16400_DIAG_STAT_FLASH_CHK] = "Flash checksum error", + [ADIS16400_DIAG_STAT_SELF_TEST] = "Self test error", + [ADIS16400_DIAG_STAT_OVERFLOW] = "Sensor overrange", + [ADIS16400_DIAG_STAT_SPI_FAIL] = "SPI failure", + [ADIS16400_DIAG_STAT_FLASH_UPT] = "Flash update failed", + [ADIS16400_DIAG_STAT_POWER_HIGH] = "Power supply above 5.25V", + [ADIS16400_DIAG_STAT_POWER_LOW] = "Power supply below 4.75V", +}; + +static const struct adis_data adis16400_data = { + .msc_ctrl_reg = ADIS16400_MSC_CTRL, + .glob_cmd_reg = ADIS16400_GLOB_CMD, + .diag_stat_reg = ADIS16400_DIAG_STAT, + + .read_delay = 50, + .write_delay = 50, + + .self_test_mask = ADIS16400_MSC_CTRL_MEM_TEST, + .startup_delay = ADIS16400_STARTUP_DELAY, + + .status_error_msgs = adis16400_status_error_msgs, + .status_error_mask = BIT(ADIS16400_DIAG_STAT_ZACCL_FAIL) | + BIT(ADIS16400_DIAG_STAT_YACCL_FAIL) | + BIT(ADIS16400_DIAG_STAT_XACCL_FAIL) | + BIT(ADIS16400_DIAG_STAT_XGYRO_FAIL) | + BIT(ADIS16400_DIAG_STAT_YGYRO_FAIL) | + BIT(ADIS16400_DIAG_STAT_ZGYRO_FAIL) | + BIT(ADIS16400_DIAG_STAT_ALARM2) | + BIT(ADIS16400_DIAG_STAT_ALARM1) | + BIT(ADIS16400_DIAG_STAT_FLASH_CHK) | + BIT(ADIS16400_DIAG_STAT_SELF_TEST) | + BIT(ADIS16400_DIAG_STAT_OVERFLOW) | + BIT(ADIS16400_DIAG_STAT_SPI_FAIL) | + BIT(ADIS16400_DIAG_STAT_FLASH_UPT) | + BIT(ADIS16400_DIAG_STAT_POWER_HIGH) | + BIT(ADIS16400_DIAG_STAT_POWER_LOW), +}; + +static void adis16400_setup_chan_mask(struct adis16400_state *st) +{ + const struct adis16400_chip_info *chip_info = st->variant; + unsigned i; + + for (i = 0; i < chip_info->num_channels; i++) { + const struct iio_chan_spec *ch = &chip_info->channels[i]; + + if (ch->scan_index >= 0 && + ch->scan_index != ADIS16400_SCAN_TIMESTAMP) + st->avail_scan_mask[0] |= BIT(ch->scan_index); + } +} + +static int adis16400_probe(struct spi_device *spi) +{ + struct adis16400_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + /* setup the industrialio driver allocated elements */ + st->variant = &adis16400_chips[spi_get_device_id(spi)->driver_data]; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = st->variant->channels; + indio_dev->num_channels = st->variant->num_channels; + indio_dev->info = &adis16400_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (!(st->variant->flags & ADIS16400_NO_BURST)) { + adis16400_setup_chan_mask(st); + indio_dev->available_scan_masks = st->avail_scan_mask; + } + + ret = adis_init(&st->adis, indio_dev, spi, &adis16400_data); + if (ret) + return ret; + + ret = adis_setup_buffer_and_trigger(&st->adis, indio_dev, + adis16400_trigger_handler); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = adis16400_initial_setup(indio_dev); + if (ret) + goto error_cleanup_buffer; + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer; + + adis16400_debugfs_init(indio_dev); + return 0; + +error_cleanup_buffer: + adis_cleanup_buffer_and_trigger(&st->adis, indio_dev); + return ret; +} + +static int adis16400_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis16400_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis16400_stop_device(indio_dev); + + adis_cleanup_buffer_and_trigger(&st->adis, indio_dev); + + return 0; +} + +static const struct spi_device_id adis16400_id[] = { + {"adis16300", ADIS16300}, + {"adis16334", ADIS16334}, + {"adis16350", ADIS16350}, + {"adis16354", ADIS16350}, + {"adis16355", ADIS16350}, + {"adis16360", ADIS16360}, + {"adis16362", ADIS16362}, + {"adis16364", ADIS16364}, + {"adis16365", ADIS16360}, + {"adis16400", ADIS16400}, + {"adis16405", ADIS16400}, + {"adis16448", ADIS16448}, + {} +}; +MODULE_DEVICE_TABLE(spi, adis16400_id); + +static struct spi_driver adis16400_driver = { + .driver = { + .name = "adis16400", + .owner = THIS_MODULE, + }, + .id_table = adis16400_id, + .probe = adis16400_probe, + .remove = adis16400_remove, +}; +module_spi_driver(adis16400_driver); + +MODULE_AUTHOR("Manuel Stahl <manuel.stahl@iis.fraunhofer.de>"); +MODULE_DESCRIPTION("Analog Devices ADIS16400/5 IMU SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/adis16480.c b/drivers/iio/imu/adis16480.c new file mode 100644 index 000000000..989605dd6 --- /dev/null +++ b/drivers/iio/imu/adis16480.c @@ -0,0 +1,882 @@ +/* + * ADIS16480 and similar IMUs driver + * + * Copyright 2012 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/imu/adis.h> + +#include <linux/debugfs.h> + +#define ADIS16480_PAGE_SIZE 0x80 + +#define ADIS16480_REG(page, reg) ((page) * ADIS16480_PAGE_SIZE + (reg)) + +#define ADIS16480_REG_PAGE_ID 0x00 /* Same address on each page */ +#define ADIS16480_REG_SEQ_CNT ADIS16480_REG(0x00, 0x06) +#define ADIS16480_REG_SYS_E_FLA ADIS16480_REG(0x00, 0x08) +#define ADIS16480_REG_DIAG_STS ADIS16480_REG(0x00, 0x0A) +#define ADIS16480_REG_ALM_STS ADIS16480_REG(0x00, 0x0C) +#define ADIS16480_REG_TEMP_OUT ADIS16480_REG(0x00, 0x0E) +#define ADIS16480_REG_X_GYRO_OUT ADIS16480_REG(0x00, 0x10) +#define ADIS16480_REG_Y_GYRO_OUT ADIS16480_REG(0x00, 0x14) +#define ADIS16480_REG_Z_GYRO_OUT ADIS16480_REG(0x00, 0x18) +#define ADIS16480_REG_X_ACCEL_OUT ADIS16480_REG(0x00, 0x1C) +#define ADIS16480_REG_Y_ACCEL_OUT ADIS16480_REG(0x00, 0x20) +#define ADIS16480_REG_Z_ACCEL_OUT ADIS16480_REG(0x00, 0x24) +#define ADIS16480_REG_X_MAGN_OUT ADIS16480_REG(0x00, 0x28) +#define ADIS16480_REG_Y_MAGN_OUT ADIS16480_REG(0x00, 0x2A) +#define ADIS16480_REG_Z_MAGN_OUT ADIS16480_REG(0x00, 0x2C) +#define ADIS16480_REG_BAROM_OUT ADIS16480_REG(0x00, 0x2E) +#define ADIS16480_REG_X_DELTAANG_OUT ADIS16480_REG(0x00, 0x40) +#define ADIS16480_REG_Y_DELTAANG_OUT ADIS16480_REG(0x00, 0x44) +#define ADIS16480_REG_Z_DELTAANG_OUT ADIS16480_REG(0x00, 0x48) +#define ADIS16480_REG_X_DELTAVEL_OUT ADIS16480_REG(0x00, 0x4C) +#define ADIS16480_REG_Y_DELTAVEL_OUT ADIS16480_REG(0x00, 0x50) +#define ADIS16480_REG_Z_DELTAVEL_OUT ADIS16480_REG(0x00, 0x54) +#define ADIS16480_REG_PROD_ID ADIS16480_REG(0x00, 0x7E) + +#define ADIS16480_REG_X_GYRO_SCALE ADIS16480_REG(0x02, 0x04) +#define ADIS16480_REG_Y_GYRO_SCALE ADIS16480_REG(0x02, 0x06) +#define ADIS16480_REG_Z_GYRO_SCALE ADIS16480_REG(0x02, 0x08) +#define ADIS16480_REG_X_ACCEL_SCALE ADIS16480_REG(0x02, 0x0A) +#define ADIS16480_REG_Y_ACCEL_SCALE ADIS16480_REG(0x02, 0x0C) +#define ADIS16480_REG_Z_ACCEL_SCALE ADIS16480_REG(0x02, 0x0E) +#define ADIS16480_REG_X_GYRO_BIAS ADIS16480_REG(0x02, 0x10) +#define ADIS16480_REG_Y_GYRO_BIAS ADIS16480_REG(0x02, 0x14) +#define ADIS16480_REG_Z_GYRO_BIAS ADIS16480_REG(0x02, 0x18) +#define ADIS16480_REG_X_ACCEL_BIAS ADIS16480_REG(0x02, 0x1C) +#define ADIS16480_REG_Y_ACCEL_BIAS ADIS16480_REG(0x02, 0x20) +#define ADIS16480_REG_Z_ACCEL_BIAS ADIS16480_REG(0x02, 0x24) +#define ADIS16480_REG_X_HARD_IRON ADIS16480_REG(0x02, 0x28) +#define ADIS16480_REG_Y_HARD_IRON ADIS16480_REG(0x02, 0x2A) +#define ADIS16480_REG_Z_HARD_IRON ADIS16480_REG(0x02, 0x2C) +#define ADIS16480_REG_BAROM_BIAS ADIS16480_REG(0x02, 0x40) +#define ADIS16480_REG_FLASH_CNT ADIS16480_REG(0x02, 0x7C) + +#define ADIS16480_REG_GLOB_CMD ADIS16480_REG(0x03, 0x02) +#define ADIS16480_REG_FNCTIO_CTRL ADIS16480_REG(0x03, 0x06) +#define ADIS16480_REG_GPIO_CTRL ADIS16480_REG(0x03, 0x08) +#define ADIS16480_REG_CONFIG ADIS16480_REG(0x03, 0x0A) +#define ADIS16480_REG_DEC_RATE ADIS16480_REG(0x03, 0x0C) +#define ADIS16480_REG_SLP_CNT ADIS16480_REG(0x03, 0x10) +#define ADIS16480_REG_FILTER_BNK0 ADIS16480_REG(0x03, 0x16) +#define ADIS16480_REG_FILTER_BNK1 ADIS16480_REG(0x03, 0x18) +#define ADIS16480_REG_ALM_CNFG0 ADIS16480_REG(0x03, 0x20) +#define ADIS16480_REG_ALM_CNFG1 ADIS16480_REG(0x03, 0x22) +#define ADIS16480_REG_ALM_CNFG2 ADIS16480_REG(0x03, 0x24) +#define ADIS16480_REG_XG_ALM_MAGN ADIS16480_REG(0x03, 0x28) +#define ADIS16480_REG_YG_ALM_MAGN ADIS16480_REG(0x03, 0x2A) +#define ADIS16480_REG_ZG_ALM_MAGN ADIS16480_REG(0x03, 0x2C) +#define ADIS16480_REG_XA_ALM_MAGN ADIS16480_REG(0x03, 0x2E) +#define ADIS16480_REG_YA_ALM_MAGN ADIS16480_REG(0x03, 0x30) +#define ADIS16480_REG_ZA_ALM_MAGN ADIS16480_REG(0x03, 0x32) +#define ADIS16480_REG_XM_ALM_MAGN ADIS16480_REG(0x03, 0x34) +#define ADIS16480_REG_YM_ALM_MAGN ADIS16480_REG(0x03, 0x36) +#define ADIS16480_REG_ZM_ALM_MAGN ADIS16480_REG(0x03, 0x38) +#define ADIS16480_REG_BR_ALM_MAGN ADIS16480_REG(0x03, 0x3A) +#define ADIS16480_REG_FIRM_REV ADIS16480_REG(0x03, 0x78) +#define ADIS16480_REG_FIRM_DM ADIS16480_REG(0x03, 0x7A) +#define ADIS16480_REG_FIRM_Y ADIS16480_REG(0x03, 0x7C) + +#define ADIS16480_REG_SERIAL_NUM ADIS16480_REG(0x04, 0x20) + +/* Each filter coefficent bank spans two pages */ +#define ADIS16480_FIR_COEF(page) (x < 60 ? ADIS16480_REG(page, (x) + 8) : \ + ADIS16480_REG((page) + 1, (x) - 60 + 8)) +#define ADIS16480_FIR_COEF_A(x) ADIS16480_FIR_COEF(0x05, (x)) +#define ADIS16480_FIR_COEF_B(x) ADIS16480_FIR_COEF(0x07, (x)) +#define ADIS16480_FIR_COEF_C(x) ADIS16480_FIR_COEF(0x09, (x)) +#define ADIS16480_FIR_COEF_D(x) ADIS16480_FIR_COEF(0x0B, (x)) + +struct adis16480_chip_info { + unsigned int num_channels; + const struct iio_chan_spec *channels; +}; + +struct adis16480 { + const struct adis16480_chip_info *chip_info; + + struct adis adis; +}; + +#ifdef CONFIG_DEBUG_FS + +static ssize_t adis16480_show_firmware_revision(struct file *file, + char __user *userbuf, size_t count, loff_t *ppos) +{ + struct adis16480 *adis16480 = file->private_data; + char buf[7]; + size_t len; + u16 rev; + int ret; + + ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_FIRM_REV, &rev); + if (ret < 0) + return ret; + + len = scnprintf(buf, sizeof(buf), "%x.%x\n", rev >> 8, rev & 0xff); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16480_firmware_revision_fops = { + .open = simple_open, + .read = adis16480_show_firmware_revision, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static ssize_t adis16480_show_firmware_date(struct file *file, + char __user *userbuf, size_t count, loff_t *ppos) +{ + struct adis16480 *adis16480 = file->private_data; + u16 md, year; + char buf[12]; + size_t len; + int ret; + + ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_FIRM_Y, &year); + if (ret < 0) + return ret; + + ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_FIRM_DM, &md); + if (ret < 0) + return ret; + + len = snprintf(buf, sizeof(buf), "%.2x-%.2x-%.4x\n", + md >> 8, md & 0xff, year); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static const struct file_operations adis16480_firmware_date_fops = { + .open = simple_open, + .read = adis16480_show_firmware_date, + .llseek = default_llseek, + .owner = THIS_MODULE, +}; + +static int adis16480_show_serial_number(void *arg, u64 *val) +{ + struct adis16480 *adis16480 = arg; + u16 serial; + int ret; + + ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_SERIAL_NUM, + &serial); + if (ret < 0) + return ret; + + *val = serial; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(adis16480_serial_number_fops, + adis16480_show_serial_number, NULL, "0x%.4llx\n"); + +static int adis16480_show_product_id(void *arg, u64 *val) +{ + struct adis16480 *adis16480 = arg; + u16 prod_id; + int ret; + + ret = adis_read_reg_16(&adis16480->adis, ADIS16480_REG_PROD_ID, + &prod_id); + if (ret < 0) + return ret; + + *val = prod_id; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(adis16480_product_id_fops, + adis16480_show_product_id, NULL, "%llu\n"); + +static int adis16480_show_flash_count(void *arg, u64 *val) +{ + struct adis16480 *adis16480 = arg; + u32 flash_count; + int ret; + + ret = adis_read_reg_32(&adis16480->adis, ADIS16480_REG_FLASH_CNT, + &flash_count); + if (ret < 0) + return ret; + + *val = flash_count; + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(adis16480_flash_count_fops, + adis16480_show_flash_count, NULL, "%lld\n"); + +static int adis16480_debugfs_init(struct iio_dev *indio_dev) +{ + struct adis16480 *adis16480 = iio_priv(indio_dev); + + debugfs_create_file("firmware_revision", 0400, + indio_dev->debugfs_dentry, adis16480, + &adis16480_firmware_revision_fops); + debugfs_create_file("firmware_date", 0400, indio_dev->debugfs_dentry, + adis16480, &adis16480_firmware_date_fops); + debugfs_create_file("serial_number", 0400, indio_dev->debugfs_dentry, + adis16480, &adis16480_serial_number_fops); + debugfs_create_file("product_id", 0400, indio_dev->debugfs_dentry, + adis16480, &adis16480_product_id_fops); + debugfs_create_file("flash_count", 0400, indio_dev->debugfs_dentry, + adis16480, &adis16480_flash_count_fops); + + return 0; +} + +#else + +static int adis16480_debugfs_init(struct iio_dev *indio_dev) +{ + return 0; +} + +#endif + +static int adis16480_set_freq(struct iio_dev *indio_dev, int val, int val2) +{ + struct adis16480 *st = iio_priv(indio_dev); + unsigned int t; + + t = val * 1000 + val2 / 1000; + if (t <= 0) + return -EINVAL; + + t = 2460000 / t; + if (t > 2048) + t = 2048; + + if (t != 0) + t--; + + return adis_write_reg_16(&st->adis, ADIS16480_REG_DEC_RATE, t); +} + +static int adis16480_get_freq(struct iio_dev *indio_dev, int *val, int *val2) +{ + struct adis16480 *st = iio_priv(indio_dev); + uint16_t t; + int ret; + unsigned freq; + + ret = adis_read_reg_16(&st->adis, ADIS16480_REG_DEC_RATE, &t); + if (ret < 0) + return ret; + + freq = 2460000 / (t + 1); + *val = freq / 1000; + *val2 = (freq % 1000) * 1000; + + return IIO_VAL_INT_PLUS_MICRO; +} + +enum { + ADIS16480_SCAN_GYRO_X, + ADIS16480_SCAN_GYRO_Y, + ADIS16480_SCAN_GYRO_Z, + ADIS16480_SCAN_ACCEL_X, + ADIS16480_SCAN_ACCEL_Y, + ADIS16480_SCAN_ACCEL_Z, + ADIS16480_SCAN_MAGN_X, + ADIS16480_SCAN_MAGN_Y, + ADIS16480_SCAN_MAGN_Z, + ADIS16480_SCAN_BARO, + ADIS16480_SCAN_TEMP, +}; + +static const unsigned int adis16480_calibbias_regs[] = { + [ADIS16480_SCAN_GYRO_X] = ADIS16480_REG_X_GYRO_BIAS, + [ADIS16480_SCAN_GYRO_Y] = ADIS16480_REG_Y_GYRO_BIAS, + [ADIS16480_SCAN_GYRO_Z] = ADIS16480_REG_Z_GYRO_BIAS, + [ADIS16480_SCAN_ACCEL_X] = ADIS16480_REG_X_ACCEL_BIAS, + [ADIS16480_SCAN_ACCEL_Y] = ADIS16480_REG_Y_ACCEL_BIAS, + [ADIS16480_SCAN_ACCEL_Z] = ADIS16480_REG_Z_ACCEL_BIAS, + [ADIS16480_SCAN_MAGN_X] = ADIS16480_REG_X_HARD_IRON, + [ADIS16480_SCAN_MAGN_Y] = ADIS16480_REG_Y_HARD_IRON, + [ADIS16480_SCAN_MAGN_Z] = ADIS16480_REG_Z_HARD_IRON, + [ADIS16480_SCAN_BARO] = ADIS16480_REG_BAROM_BIAS, +}; + +static const unsigned int adis16480_calibscale_regs[] = { + [ADIS16480_SCAN_GYRO_X] = ADIS16480_REG_X_GYRO_SCALE, + [ADIS16480_SCAN_GYRO_Y] = ADIS16480_REG_Y_GYRO_SCALE, + [ADIS16480_SCAN_GYRO_Z] = ADIS16480_REG_Z_GYRO_SCALE, + [ADIS16480_SCAN_ACCEL_X] = ADIS16480_REG_X_ACCEL_SCALE, + [ADIS16480_SCAN_ACCEL_Y] = ADIS16480_REG_Y_ACCEL_SCALE, + [ADIS16480_SCAN_ACCEL_Z] = ADIS16480_REG_Z_ACCEL_SCALE, +}; + +static int adis16480_set_calibbias(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int bias) +{ + unsigned int reg = adis16480_calibbias_regs[chan->scan_index]; + struct adis16480 *st = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_MAGN: + case IIO_PRESSURE: + if (bias < -0x8000 || bias >= 0x8000) + return -EINVAL; + return adis_write_reg_16(&st->adis, reg, bias); + case IIO_ANGL_VEL: + case IIO_ACCEL: + return adis_write_reg_32(&st->adis, reg, bias); + default: + break; + } + + return -EINVAL; +} + +static int adis16480_get_calibbias(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *bias) +{ + unsigned int reg = adis16480_calibbias_regs[chan->scan_index]; + struct adis16480 *st = iio_priv(indio_dev); + uint16_t val16; + uint32_t val32; + int ret; + + switch (chan->type) { + case IIO_MAGN: + case IIO_PRESSURE: + ret = adis_read_reg_16(&st->adis, reg, &val16); + *bias = sign_extend32(val16, 15); + break; + case IIO_ANGL_VEL: + case IIO_ACCEL: + ret = adis_read_reg_32(&st->adis, reg, &val32); + *bias = sign_extend32(val32, 31); + break; + default: + ret = -EINVAL; + } + + if (ret < 0) + return ret; + + return IIO_VAL_INT; +} + +static int adis16480_set_calibscale(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int scale) +{ + unsigned int reg = adis16480_calibscale_regs[chan->scan_index]; + struct adis16480 *st = iio_priv(indio_dev); + + if (scale < -0x8000 || scale >= 0x8000) + return -EINVAL; + + return adis_write_reg_16(&st->adis, reg, scale); +} + +static int adis16480_get_calibscale(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *scale) +{ + unsigned int reg = adis16480_calibscale_regs[chan->scan_index]; + struct adis16480 *st = iio_priv(indio_dev); + uint16_t val16; + int ret; + + ret = adis_read_reg_16(&st->adis, reg, &val16); + if (ret < 0) + return ret; + + *scale = sign_extend32(val16, 15); + return IIO_VAL_INT; +} + +static const unsigned int adis16480_def_filter_freqs[] = { + 310, + 55, + 275, + 63, +}; + +static const unsigned int ad16480_filter_data[][2] = { + [ADIS16480_SCAN_GYRO_X] = { ADIS16480_REG_FILTER_BNK0, 0 }, + [ADIS16480_SCAN_GYRO_Y] = { ADIS16480_REG_FILTER_BNK0, 3 }, + [ADIS16480_SCAN_GYRO_Z] = { ADIS16480_REG_FILTER_BNK0, 6 }, + [ADIS16480_SCAN_ACCEL_X] = { ADIS16480_REG_FILTER_BNK0, 9 }, + [ADIS16480_SCAN_ACCEL_Y] = { ADIS16480_REG_FILTER_BNK0, 12 }, + [ADIS16480_SCAN_ACCEL_Z] = { ADIS16480_REG_FILTER_BNK1, 0 }, + [ADIS16480_SCAN_MAGN_X] = { ADIS16480_REG_FILTER_BNK1, 3 }, + [ADIS16480_SCAN_MAGN_Y] = { ADIS16480_REG_FILTER_BNK1, 6 }, + [ADIS16480_SCAN_MAGN_Z] = { ADIS16480_REG_FILTER_BNK1, 9 }, +}; + +static int adis16480_get_filter_freq(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *freq) +{ + struct adis16480 *st = iio_priv(indio_dev); + unsigned int enable_mask, offset, reg; + uint16_t val; + int ret; + + reg = ad16480_filter_data[chan->scan_index][0]; + offset = ad16480_filter_data[chan->scan_index][1]; + enable_mask = BIT(offset + 2); + + ret = adis_read_reg_16(&st->adis, reg, &val); + if (ret < 0) + return ret; + + if (!(val & enable_mask)) + *freq = 0; + else + *freq = adis16480_def_filter_freqs[(val >> offset) & 0x3]; + + return IIO_VAL_INT; +} + +static int adis16480_set_filter_freq(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int freq) +{ + struct adis16480 *st = iio_priv(indio_dev); + unsigned int enable_mask, offset, reg; + unsigned int diff, best_diff; + unsigned int i, best_freq; + uint16_t val; + int ret; + + reg = ad16480_filter_data[chan->scan_index][0]; + offset = ad16480_filter_data[chan->scan_index][1]; + enable_mask = BIT(offset + 2); + + ret = adis_read_reg_16(&st->adis, reg, &val); + if (ret < 0) + return ret; + + if (freq == 0) { + val &= ~enable_mask; + } else { + best_freq = 0; + best_diff = 310; + for (i = 0; i < ARRAY_SIZE(adis16480_def_filter_freqs); i++) { + if (adis16480_def_filter_freqs[i] >= freq) { + diff = adis16480_def_filter_freqs[i] - freq; + if (diff < best_diff) { + best_diff = diff; + best_freq = i; + } + } + } + + val &= ~(0x3 << offset); + val |= best_freq << offset; + val |= enable_mask; + } + + return adis_write_reg_16(&st->adis, reg, val); +} + +static int adis16480_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, int *val2, long info) +{ + switch (info) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, 0, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = IIO_DEGREE_TO_RAD(20000); /* 0.02 degree/sec */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = IIO_G_TO_M_S_2(800); /* 0.8 mg */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MAGN: + *val = 0; + *val2 = 100; /* 0.0001 gauss */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 5; + *val2 = 650000; /* 5.65 milli degree Celsius */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PRESSURE: + *val = 0; + *val2 = 4000; /* 40ubar = 0.004 kPa */ + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + /* Only the temperature channel has a offset */ + *val = 4425; /* 25 degree Celsius = 0x0000 */ + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + return adis16480_get_calibbias(indio_dev, chan, val); + case IIO_CHAN_INFO_CALIBSCALE: + return adis16480_get_calibscale(indio_dev, chan, val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return adis16480_get_filter_freq(indio_dev, chan, val); + case IIO_CHAN_INFO_SAMP_FREQ: + return adis16480_get_freq(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int adis16480_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int val, int val2, long info) +{ + switch (info) { + case IIO_CHAN_INFO_CALIBBIAS: + return adis16480_set_calibbias(indio_dev, chan, val); + case IIO_CHAN_INFO_CALIBSCALE: + return adis16480_set_calibscale(indio_dev, chan, val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return adis16480_set_filter_freq(indio_dev, chan, val); + case IIO_CHAN_INFO_SAMP_FREQ: + return adis16480_set_freq(indio_dev, val, val2); + + default: + return -EINVAL; + } +} + +#define ADIS16480_MOD_CHANNEL(_type, _mod, _address, _si, _info_sep, _bits) \ + { \ + .type = (_type), \ + .modified = 1, \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + _info_sep, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = (_address), \ + .scan_index = (_si), \ + .scan_type = { \ + .sign = 's', \ + .realbits = (_bits), \ + .storagebits = (_bits), \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16480_GYRO_CHANNEL(_mod) \ + ADIS16480_MOD_CHANNEL(IIO_ANGL_VEL, IIO_MOD_ ## _mod, \ + ADIS16480_REG_ ## _mod ## _GYRO_OUT, ADIS16480_SCAN_GYRO_ ## _mod, \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + 32) + +#define ADIS16480_ACCEL_CHANNEL(_mod) \ + ADIS16480_MOD_CHANNEL(IIO_ACCEL, IIO_MOD_ ## _mod, \ + ADIS16480_REG_ ## _mod ## _ACCEL_OUT, ADIS16480_SCAN_ACCEL_ ## _mod, \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + 32) + +#define ADIS16480_MAGN_CHANNEL(_mod) \ + ADIS16480_MOD_CHANNEL(IIO_MAGN, IIO_MOD_ ## _mod, \ + ADIS16480_REG_ ## _mod ## _MAGN_OUT, ADIS16480_SCAN_MAGN_ ## _mod, \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + 16) + +#define ADIS16480_PRESSURE_CHANNEL() \ + { \ + .type = IIO_PRESSURE, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = ADIS16480_REG_BAROM_OUT, \ + .scan_index = ADIS16480_SCAN_BARO, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + } + +#define ADIS16480_TEMP_CHANNEL() { \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = ADIS16480_REG_TEMP_OUT, \ + .scan_index = ADIS16480_SCAN_TEMP, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec adis16480_channels[] = { + ADIS16480_GYRO_CHANNEL(X), + ADIS16480_GYRO_CHANNEL(Y), + ADIS16480_GYRO_CHANNEL(Z), + ADIS16480_ACCEL_CHANNEL(X), + ADIS16480_ACCEL_CHANNEL(Y), + ADIS16480_ACCEL_CHANNEL(Z), + ADIS16480_MAGN_CHANNEL(X), + ADIS16480_MAGN_CHANNEL(Y), + ADIS16480_MAGN_CHANNEL(Z), + ADIS16480_PRESSURE_CHANNEL(), + ADIS16480_TEMP_CHANNEL(), + IIO_CHAN_SOFT_TIMESTAMP(11) +}; + +static const struct iio_chan_spec adis16485_channels[] = { + ADIS16480_GYRO_CHANNEL(X), + ADIS16480_GYRO_CHANNEL(Y), + ADIS16480_GYRO_CHANNEL(Z), + ADIS16480_ACCEL_CHANNEL(X), + ADIS16480_ACCEL_CHANNEL(Y), + ADIS16480_ACCEL_CHANNEL(Z), + ADIS16480_TEMP_CHANNEL(), + IIO_CHAN_SOFT_TIMESTAMP(7) +}; + +enum adis16480_variant { + ADIS16375, + ADIS16480, + ADIS16485, + ADIS16488, +}; + +static const struct adis16480_chip_info adis16480_chip_info[] = { + [ADIS16375] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + }, + [ADIS16480] = { + .channels = adis16480_channels, + .num_channels = ARRAY_SIZE(adis16480_channels), + }, + [ADIS16485] = { + .channels = adis16485_channels, + .num_channels = ARRAY_SIZE(adis16485_channels), + }, + [ADIS16488] = { + .channels = adis16480_channels, + .num_channels = ARRAY_SIZE(adis16480_channels), + }, +}; + +static const struct iio_info adis16480_info = { + .read_raw = &adis16480_read_raw, + .write_raw = &adis16480_write_raw, + .update_scan_mode = adis_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static int adis16480_stop_device(struct iio_dev *indio_dev) +{ + struct adis16480 *st = iio_priv(indio_dev); + int ret; + + ret = adis_write_reg_16(&st->adis, ADIS16480_REG_SLP_CNT, BIT(9)); + if (ret) + dev_err(&indio_dev->dev, + "Could not power down device: %d\n", ret); + + return ret; +} + +static int adis16480_enable_irq(struct adis *adis, bool enable) +{ + return adis_write_reg_16(adis, ADIS16480_REG_FNCTIO_CTRL, + enable ? BIT(3) : 0); +} + +static int adis16480_initial_setup(struct iio_dev *indio_dev) +{ + struct adis16480 *st = iio_priv(indio_dev); + uint16_t prod_id; + unsigned int device_id; + int ret; + + adis_reset(&st->adis); + msleep(70); + + ret = adis_write_reg_16(&st->adis, ADIS16480_REG_GLOB_CMD, BIT(1)); + if (ret) + return ret; + msleep(30); + + ret = adis_check_status(&st->adis); + if (ret) + return ret; + + ret = adis_read_reg_16(&st->adis, ADIS16480_REG_PROD_ID, &prod_id); + if (ret) + return ret; + + sscanf(indio_dev->name, "adis%u\n", &device_id); + + if (prod_id != device_id) + dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", + device_id, prod_id); + + return 0; +} + +#define ADIS16480_DIAG_STAT_XGYRO_FAIL 0 +#define ADIS16480_DIAG_STAT_YGYRO_FAIL 1 +#define ADIS16480_DIAG_STAT_ZGYRO_FAIL 2 +#define ADIS16480_DIAG_STAT_XACCL_FAIL 3 +#define ADIS16480_DIAG_STAT_YACCL_FAIL 4 +#define ADIS16480_DIAG_STAT_ZACCL_FAIL 5 +#define ADIS16480_DIAG_STAT_XMAGN_FAIL 8 +#define ADIS16480_DIAG_STAT_YMAGN_FAIL 9 +#define ADIS16480_DIAG_STAT_ZMAGN_FAIL 10 +#define ADIS16480_DIAG_STAT_BARO_FAIL 11 + +static const char * const adis16480_status_error_msgs[] = { + [ADIS16480_DIAG_STAT_XGYRO_FAIL] = "X-axis gyroscope self-test failure", + [ADIS16480_DIAG_STAT_YGYRO_FAIL] = "Y-axis gyroscope self-test failure", + [ADIS16480_DIAG_STAT_ZGYRO_FAIL] = "Z-axis gyroscope self-test failure", + [ADIS16480_DIAG_STAT_XACCL_FAIL] = "X-axis accelerometer self-test failure", + [ADIS16480_DIAG_STAT_YACCL_FAIL] = "Y-axis accelerometer self-test failure", + [ADIS16480_DIAG_STAT_ZACCL_FAIL] = "Z-axis accelerometer self-test failure", + [ADIS16480_DIAG_STAT_XMAGN_FAIL] = "X-axis magnetometer self-test failure", + [ADIS16480_DIAG_STAT_YMAGN_FAIL] = "Y-axis magnetometer self-test failure", + [ADIS16480_DIAG_STAT_ZMAGN_FAIL] = "Z-axis magnetometer self-test failure", + [ADIS16480_DIAG_STAT_BARO_FAIL] = "Barometer self-test failure", +}; + +static const struct adis_data adis16480_data = { + .diag_stat_reg = ADIS16480_REG_DIAG_STS, + .glob_cmd_reg = ADIS16480_REG_GLOB_CMD, + .has_paging = true, + + .read_delay = 5, + .write_delay = 5, + + .status_error_msgs = adis16480_status_error_msgs, + .status_error_mask = BIT(ADIS16480_DIAG_STAT_XGYRO_FAIL) | + BIT(ADIS16480_DIAG_STAT_YGYRO_FAIL) | + BIT(ADIS16480_DIAG_STAT_ZGYRO_FAIL) | + BIT(ADIS16480_DIAG_STAT_XACCL_FAIL) | + BIT(ADIS16480_DIAG_STAT_YACCL_FAIL) | + BIT(ADIS16480_DIAG_STAT_ZACCL_FAIL) | + BIT(ADIS16480_DIAG_STAT_XMAGN_FAIL) | + BIT(ADIS16480_DIAG_STAT_YMAGN_FAIL) | + BIT(ADIS16480_DIAG_STAT_ZMAGN_FAIL) | + BIT(ADIS16480_DIAG_STAT_BARO_FAIL), + + .enable_irq = adis16480_enable_irq, +}; + +static int adis16480_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct iio_dev *indio_dev; + struct adis16480 *st; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + spi_set_drvdata(spi, indio_dev); + + st = iio_priv(indio_dev); + + st->chip_info = &adis16480_chip_info[id->driver_data]; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = &adis16480_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(&st->adis, indio_dev, spi, &adis16480_data); + if (ret) + return ret; + + ret = adis_setup_buffer_and_trigger(&st->adis, indio_dev, NULL); + if (ret) + return ret; + + ret = adis16480_initial_setup(indio_dev); + if (ret) + goto error_cleanup_buffer; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_stop_device; + + adis16480_debugfs_init(indio_dev); + + return 0; + +error_stop_device: + adis16480_stop_device(indio_dev); +error_cleanup_buffer: + adis_cleanup_buffer_and_trigger(&st->adis, indio_dev); + return ret; +} + +static int adis16480_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis16480 *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis16480_stop_device(indio_dev); + + adis_cleanup_buffer_and_trigger(&st->adis, indio_dev); + + return 0; +} + +static const struct spi_device_id adis16480_ids[] = { + { "adis16375", ADIS16375 }, + { "adis16480", ADIS16480 }, + { "adis16485", ADIS16485 }, + { "adis16488", ADIS16488 }, + { } +}; +MODULE_DEVICE_TABLE(spi, adis16480_ids); + +static struct spi_driver adis16480_driver = { + .driver = { + .name = "adis16480", + .owner = THIS_MODULE, + }, + .id_table = adis16480_ids, + .probe = adis16480_probe, + .remove = adis16480_remove, +}; +module_spi_driver(adis16480_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices ADIS16480 IMU driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/imu/adis_buffer.c b/drivers/iio/imu/adis_buffer.c new file mode 100644 index 000000000..cb32b593f --- /dev/null +++ b/drivers/iio/imu/adis_buffer.c @@ -0,0 +1,171 @@ +/* + * Common library for ADIS16XXX devices + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/export.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/imu/adis.h> + +int adis_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct adis *adis = iio_device_get_drvdata(indio_dev); + const struct iio_chan_spec *chan; + unsigned int scan_count; + unsigned int i, j; + __be16 *tx, *rx; + + kfree(adis->xfer); + kfree(adis->buffer); + + scan_count = indio_dev->scan_bytes / 2; + + adis->xfer = kcalloc(scan_count + 1, sizeof(*adis->xfer), GFP_KERNEL); + if (!adis->xfer) + return -ENOMEM; + + adis->buffer = kzalloc(indio_dev->scan_bytes * 2, GFP_KERNEL); + if (!adis->buffer) + return -ENOMEM; + + rx = adis->buffer; + tx = rx + indio_dev->scan_bytes; + + spi_message_init(&adis->msg); + + for (j = 0; j <= scan_count; j++) { + adis->xfer[j].bits_per_word = 8; + if (j != scan_count) + adis->xfer[j].cs_change = 1; + adis->xfer[j].len = 2; + adis->xfer[j].delay_usecs = adis->data->read_delay; + if (j < scan_count) + adis->xfer[j].tx_buf = &tx[j]; + if (j >= 1) + adis->xfer[j].rx_buf = &rx[j - 1]; + spi_message_add_tail(&adis->xfer[j], &adis->msg); + } + + chan = indio_dev->channels; + for (i = 0; i < indio_dev->num_channels; i++, chan++) { + if (!test_bit(chan->scan_index, scan_mask)) + continue; + if (chan->scan_type.storagebits == 32) + *tx++ = cpu_to_be16((chan->address + 2) << 8); + *tx++ = cpu_to_be16(chan->address << 8); + } + + return 0; +} +EXPORT_SYMBOL_GPL(adis_update_scan_mode); + +static irqreturn_t adis_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis *adis = iio_device_get_drvdata(indio_dev); + int ret; + + if (!adis->buffer) + return -ENOMEM; + + if (adis->data->has_paging) { + mutex_lock(&adis->txrx_lock); + if (adis->current_page != 0) { + adis->tx[0] = ADIS_WRITE_REG(ADIS_REG_PAGE_ID); + adis->tx[1] = 0; + spi_write(adis->spi, adis->tx, 2); + } + } + + ret = spi_sync(adis->spi, &adis->msg); + if (ret) + dev_err(&adis->spi->dev, "Failed to read data: %d", ret); + + + if (adis->data->has_paging) { + adis->current_page = 0; + mutex_unlock(&adis->txrx_lock); + } + + iio_push_to_buffers_with_timestamp(indio_dev, adis->buffer, + pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/** + * adis_setup_buffer_and_trigger() - Sets up buffer and trigger for the adis device + * @adis: The adis device. + * @indio_dev: The IIO device. + * @trigger_handler: Optional trigger handler, may be NULL. + * + * Returns 0 on success, a negative error code otherwise. + * + * This function sets up the buffer and trigger for a adis devices. If + * 'trigger_handler' is NULL the default trigger handler will be used. The + * default trigger handler will simply read the registers assigned to the + * currently active channels. + * + * adis_cleanup_buffer_and_trigger() should be called to free the resources + * allocated by this function. + */ +int adis_setup_buffer_and_trigger(struct adis *adis, struct iio_dev *indio_dev, + irqreturn_t (*trigger_handler)(int, void *)) +{ + int ret; + + if (!trigger_handler) + trigger_handler = adis_trigger_handler; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + trigger_handler, NULL); + if (ret) + return ret; + + if (adis->spi->irq) { + ret = adis_probe_trigger(adis, indio_dev); + if (ret) + goto error_buffer_cleanup; + } + return 0; + +error_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} +EXPORT_SYMBOL_GPL(adis_setup_buffer_and_trigger); + +/** + * adis_cleanup_buffer_and_trigger() - Free buffer and trigger resources + * @adis: The adis device. + * @indio_dev: The IIO device. + * + * Frees resources allocated by adis_setup_buffer_and_trigger() + */ +void adis_cleanup_buffer_and_trigger(struct adis *adis, + struct iio_dev *indio_dev) +{ + if (adis->spi->irq) + adis_remove_trigger(adis); + kfree(adis->buffer); + kfree(adis->xfer); + iio_triggered_buffer_cleanup(indio_dev); +} +EXPORT_SYMBOL_GPL(adis_cleanup_buffer_and_trigger); diff --git a/drivers/iio/imu/adis_trigger.c b/drivers/iio/imu/adis_trigger.c new file mode 100644 index 000000000..f53e9a803 --- /dev/null +++ b/drivers/iio/imu/adis_trigger.c @@ -0,0 +1,89 @@ +/* + * Common library for ADIS16XXX devices + * + * Copyright 2012 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/export.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/imu/adis.h> + +static int adis_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct adis *adis = iio_trigger_get_drvdata(trig); + + return adis_enable_irq(adis, state); +} + +static const struct iio_trigger_ops adis_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &adis_data_rdy_trigger_set_state, +}; + +/** + * adis_probe_trigger() - Sets up trigger for a adis device + * @adis: The adis device + * @indio_dev: The IIO device + * + * Returns 0 on success or a negative error code + * + * adis_remove_trigger() should be used to free the trigger. + */ +int adis_probe_trigger(struct adis *adis, struct iio_dev *indio_dev) +{ + int ret; + + adis->trig = iio_trigger_alloc("%s-dev%d", indio_dev->name, + indio_dev->id); + if (adis->trig == NULL) + return -ENOMEM; + + ret = request_irq(adis->spi->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + indio_dev->name, + adis->trig); + if (ret) + goto error_free_trig; + + adis->trig->dev.parent = &adis->spi->dev; + adis->trig->ops = &adis_trigger_ops; + iio_trigger_set_drvdata(adis->trig, adis); + ret = iio_trigger_register(adis->trig); + + indio_dev->trig = iio_trigger_get(adis->trig); + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(adis->spi->irq, adis->trig); +error_free_trig: + iio_trigger_free(adis->trig); + return ret; +} +EXPORT_SYMBOL_GPL(adis_probe_trigger); + +/** + * adis_remove_trigger() - Remove trigger for a adis devices + * @adis: The adis device + * + * Removes the trigger previously registered with adis_probe_trigger(). + */ +void adis_remove_trigger(struct adis *adis) +{ + iio_trigger_unregister(adis->trig); + free_irq(adis->spi->irq, adis->trig); + iio_trigger_free(adis->trig); +} +EXPORT_SYMBOL_GPL(adis_remove_trigger); diff --git a/drivers/iio/imu/inv_mpu6050/Kconfig b/drivers/iio/imu/inv_mpu6050/Kconfig new file mode 100644 index 000000000..48fbc0bc7 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/Kconfig @@ -0,0 +1,17 @@ +# +# inv-mpu6050 drivers for Invensense MPU devices and combos +# + +config INV_MPU6050_IIO + tristate "Invensense MPU6050 devices" + depends on I2C && SYSFS + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select I2C_MUX + help + This driver supports the Invensense MPU6050 devices. + This driver can also support MPU6500 in MPU6050 compatibility mode + and also in MPU6500 mode with some limitations. + It is a gyroscope/accelerometer combo device. + This driver can be built as a module. The module will be called + inv-mpu6050. diff --git a/drivers/iio/imu/inv_mpu6050/Makefile b/drivers/iio/imu/inv_mpu6050/Makefile new file mode 100644 index 000000000..f566f6a7b --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for Invensense MPU6050 device. +# + +obj-$(CONFIG_INV_MPU6050_IIO) += inv-mpu6050.o +inv-mpu6050-objs := inv_mpu_core.o inv_mpu_ring.o inv_mpu_trigger.o inv_mpu_acpi.o diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c new file mode 100644 index 000000000..1c982a56a --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_acpi.c @@ -0,0 +1,211 @@ +/* + * inv_mpu_acpi: ACPI processing for creating client devices + * Copyright (c) 2015, 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. + */ + +#ifdef CONFIG_ACPI + +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/dmi.h> +#include <linux/acpi.h> +#include "inv_mpu_iio.h" + +enum inv_mpu_product_name { + INV_MPU_NOT_MATCHED, + INV_MPU_ASUS_T100TA, +}; + +static enum inv_mpu_product_name matched_product_name; + +static int __init asus_t100_matched(const struct dmi_system_id *d) +{ + matched_product_name = INV_MPU_ASUS_T100TA; + + return 0; +} + +static const struct dmi_system_id inv_mpu_dev_list[] = { + { + .callback = asus_t100_matched, + .ident = "Asus Transformer Book T100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC"), + DMI_MATCH(DMI_PRODUCT_NAME, "T100TA"), + DMI_MATCH(DMI_PRODUCT_VERSION, "1.0"), + }, + }, + /* Add more matching tables here..*/ + {} +}; + +static int asus_acpi_get_sensor_info(struct acpi_device *adev, + struct i2c_client *client, + struct i2c_board_info *info) +{ + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + int i; + acpi_status status; + union acpi_object *cpm; + + status = acpi_evaluate_object(adev->handle, "CNF0", NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + cpm = buffer.pointer; + for (i = 0; i < cpm->package.count; ++i) { + union acpi_object *elem; + int j; + + elem = &(cpm->package.elements[i]); + for (j = 0; j < elem->package.count; ++j) { + union acpi_object *sub_elem; + + sub_elem = &(elem->package.elements[j]); + if (sub_elem->type == ACPI_TYPE_STRING) + strlcpy(info->type, sub_elem->string.pointer, + sizeof(info->type)); + else if (sub_elem->type == ACPI_TYPE_INTEGER) { + if (sub_elem->integer.value != client->addr) { + info->addr = sub_elem->integer.value; + break; /* Not a MPU6500 primary */ + } + } + } + } + + kfree(buffer.pointer); + + return cpm->package.count; +} + +static int acpi_i2c_check_resource(struct acpi_resource *ares, void *data) +{ + u32 *addr = data; + + if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) { + struct acpi_resource_i2c_serialbus *sb; + + sb = &ares->data.i2c_serial_bus; + if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_I2C) { + if (*addr) + *addr |= (sb->slave_address << 16); + else + *addr = sb->slave_address; + } + } + + /* Tell the ACPI core that we already copied this address */ + return 1; +} + +static int inv_mpu_process_acpi_config(struct i2c_client *client, + unsigned short *primary_addr, + unsigned short *secondary_addr) +{ + const struct acpi_device_id *id; + struct acpi_device *adev; + u32 i2c_addr = 0; + LIST_HEAD(resources); + int ret; + + id = acpi_match_device(client->dev.driver->acpi_match_table, + &client->dev); + if (!id) + return -ENODEV; + + adev = ACPI_COMPANION(&client->dev); + if (!adev) + return -ENODEV; + + ret = acpi_dev_get_resources(adev, &resources, + acpi_i2c_check_resource, &i2c_addr); + if (ret < 0) + return ret; + + acpi_dev_free_resource_list(&resources); + *primary_addr = i2c_addr & 0x0000ffff; + *secondary_addr = (i2c_addr & 0xffff0000) >> 16; + + return 0; +} + +int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st) +{ + + st->mux_client = NULL; + if (ACPI_HANDLE(&st->client->dev)) { + struct i2c_board_info info; + struct acpi_device *adev; + int ret = -1; + + adev = ACPI_COMPANION(&st->client->dev); + memset(&info, 0, sizeof(info)); + + dmi_check_system(inv_mpu_dev_list); + switch (matched_product_name) { + case INV_MPU_ASUS_T100TA: + ret = asus_acpi_get_sensor_info(adev, st->client, + &info); + break; + /* Add more matched product processing here */ + default: + break; + } + + if (ret < 0) { + /* No matching DMI, so create device on INV6XX type */ + unsigned short primary, secondary; + + ret = inv_mpu_process_acpi_config(st->client, &primary, + &secondary); + if (!ret && secondary) { + char *name; + + info.addr = secondary; + strlcpy(info.type, dev_name(&adev->dev), + sizeof(info.type)); + name = strchr(info.type, ':'); + if (name) + *name = '\0'; + strlcat(info.type, "-client", + sizeof(info.type)); + } else + return 0; /* no secondary addr, which is OK */ + } + st->mux_client = i2c_new_device(st->mux_adapter, &info); + if (!st->mux_client) + return -ENODEV; + + } + + return 0; +} + +void inv_mpu_acpi_delete_mux_client(struct inv_mpu6050_state *st) +{ + if (st->mux_client) + i2c_unregister_device(st->mux_client); +} +#else + +#include "inv_mpu_iio.h" + +int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st) +{ + return 0; +} + +void inv_mpu_acpi_delete_mux_client(struct inv_mpu6050_state *st) +{ +} +#endif diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c new file mode 100644 index 000000000..65ce86837 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -0,0 +1,935 @@ +/* +* Copyright (C) 2012 Invensense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/spinlock.h> +#include <linux/iio/iio.h> +#include <linux/i2c-mux.h> +#include <linux/acpi.h> +#include "inv_mpu_iio.h" + +/* + * this is the gyro scale translated from dynamic range plus/minus + * {250, 500, 1000, 2000} to rad/s + */ +static const int gyro_scale_6050[] = {133090, 266181, 532362, 1064724}; + +/* + * this is the accel scale translated from dynamic range plus/minus + * {2, 4, 8, 16} to m/s^2 + */ +static const int accel_scale[] = {598, 1196, 2392, 4785}; + +static const struct inv_mpu6050_reg_map reg_set_6050 = { + .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV, + .lpf = INV_MPU6050_REG_CONFIG, + .user_ctrl = INV_MPU6050_REG_USER_CTRL, + .fifo_en = INV_MPU6050_REG_FIFO_EN, + .gyro_config = INV_MPU6050_REG_GYRO_CONFIG, + .accl_config = INV_MPU6050_REG_ACCEL_CONFIG, + .fifo_count_h = INV_MPU6050_REG_FIFO_COUNT_H, + .fifo_r_w = INV_MPU6050_REG_FIFO_R_W, + .raw_gyro = INV_MPU6050_REG_RAW_GYRO, + .raw_accl = INV_MPU6050_REG_RAW_ACCEL, + .temperature = INV_MPU6050_REG_TEMPERATURE, + .int_enable = INV_MPU6050_REG_INT_ENABLE, + .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1, + .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2, + .int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG, +}; + +static const struct inv_mpu6050_chip_config chip_config_6050 = { + .fsr = INV_MPU6050_FSR_2000DPS, + .lpf = INV_MPU6050_FILTER_20HZ, + .fifo_rate = INV_MPU6050_INIT_FIFO_RATE, + .gyro_fifo_enable = false, + .accl_fifo_enable = false, + .accl_fs = INV_MPU6050_FS_02G, +}; + +static const struct inv_mpu6050_hw hw_info[INV_NUM_PARTS] = { + { + .num_reg = 117, + .name = "MPU6050", + .reg = ®_set_6050, + .config = &chip_config_6050, + }, +}; + +int inv_mpu6050_write_reg(struct inv_mpu6050_state *st, int reg, u8 d) +{ + return i2c_smbus_write_i2c_block_data(st->client, reg, 1, &d); +} + +/* + * The i2c read/write needs to happen in unlocked mode. As the parent + * adapter is common. If we use locked versions, it will fail as + * the mux adapter will lock the parent i2c adapter, while calling + * select/deselect functions. + */ +static int inv_mpu6050_write_reg_unlocked(struct inv_mpu6050_state *st, + u8 reg, u8 d) +{ + int ret; + u8 buf[2]; + struct i2c_msg msg[1] = { + { + .addr = st->client->addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + } + }; + + buf[0] = reg; + buf[1] = d; + ret = __i2c_transfer(st->client->adapter, msg, 1); + if (ret != 1) + return ret; + + return 0; +} + +static int inv_mpu6050_select_bypass(struct i2c_adapter *adap, void *mux_priv, + u32 chan_id) +{ + struct iio_dev *indio_dev = mux_priv; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret = 0; + + /* Use the same mutex which was used everywhere to protect power-op */ + mutex_lock(&indio_dev->mlock); + if (!st->powerup_count) { + ret = inv_mpu6050_write_reg_unlocked(st, st->reg->pwr_mgmt_1, + 0); + if (ret) + goto write_error; + + msleep(INV_MPU6050_REG_UP_TIME); + } + if (!ret) { + st->powerup_count++; + ret = inv_mpu6050_write_reg_unlocked(st, st->reg->int_pin_cfg, + st->client->irq | + INV_MPU6050_BIT_BYPASS_EN); + } +write_error: + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int inv_mpu6050_deselect_bypass(struct i2c_adapter *adap, + void *mux_priv, u32 chan_id) +{ + struct iio_dev *indio_dev = mux_priv; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + /* It doesn't really mattter, if any of the calls fails */ + inv_mpu6050_write_reg_unlocked(st, st->reg->int_pin_cfg, + st->client->irq); + st->powerup_count--; + if (!st->powerup_count) + inv_mpu6050_write_reg_unlocked(st, st->reg->pwr_mgmt_1, + INV_MPU6050_BIT_SLEEP); + mutex_unlock(&indio_dev->mlock); + + return 0; +} + +int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask) +{ + u8 d, mgmt_1; + int result; + + /* switch clock needs to be careful. Only when gyro is on, can + clock source be switched to gyro. Otherwise, it must be set to + internal clock */ + if (INV_MPU6050_BIT_PWR_GYRO_STBY == mask) { + result = i2c_smbus_read_i2c_block_data(st->client, + st->reg->pwr_mgmt_1, 1, &mgmt_1); + if (result != 1) + return result; + + mgmt_1 &= ~INV_MPU6050_BIT_CLK_MASK; + } + + if ((INV_MPU6050_BIT_PWR_GYRO_STBY == mask) && (!en)) { + /* turning off gyro requires switch to internal clock first. + Then turn off gyro engine */ + mgmt_1 |= INV_CLK_INTERNAL; + result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, mgmt_1); + if (result) + return result; + } + + result = i2c_smbus_read_i2c_block_data(st->client, + st->reg->pwr_mgmt_2, 1, &d); + if (result != 1) + return result; + if (en) + d &= ~mask; + else + d |= mask; + result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_2, d); + if (result) + return result; + + if (en) { + /* Wait for output stabilize */ + msleep(INV_MPU6050_TEMP_UP_TIME); + if (INV_MPU6050_BIT_PWR_GYRO_STBY == mask) { + /* switch internal clock to PLL */ + mgmt_1 |= INV_CLK_PLL; + result = inv_mpu6050_write_reg(st, + st->reg->pwr_mgmt_1, mgmt_1); + if (result) + return result; + } + } + + return 0; +} + +int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, bool power_on) +{ + int result = 0; + + if (power_on) { + /* Already under indio-dev->mlock mutex */ + if (!st->powerup_count) + result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, + 0); + if (!result) + st->powerup_count++; + } else { + st->powerup_count--; + if (!st->powerup_count) + result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, + INV_MPU6050_BIT_SLEEP); + } + + if (result) + return result; + + if (power_on) + msleep(INV_MPU6050_REG_UP_TIME); + + return 0; +} + +/** + * inv_mpu6050_init_config() - Initialize hardware, disable FIFO. + * + * Initial configuration: + * FSR: ± 2000DPS + * DLPF: 20Hz + * FIFO rate: 50Hz + * Clock source: Gyro PLL + */ +static int inv_mpu6050_init_config(struct iio_dev *indio_dev) +{ + int result; + u8 d; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + result = inv_mpu6050_set_power_itg(st, true); + if (result) + return result; + d = (INV_MPU6050_FSR_2000DPS << INV_MPU6050_GYRO_CONFIG_FSR_SHIFT); + result = inv_mpu6050_write_reg(st, st->reg->gyro_config, d); + if (result) + return result; + + d = INV_MPU6050_FILTER_20HZ; + result = inv_mpu6050_write_reg(st, st->reg->lpf, d); + if (result) + return result; + + d = INV_MPU6050_ONE_K_HZ / INV_MPU6050_INIT_FIFO_RATE - 1; + result = inv_mpu6050_write_reg(st, st->reg->sample_rate_div, d); + if (result) + return result; + + d = (INV_MPU6050_FS_02G << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); + result = inv_mpu6050_write_reg(st, st->reg->accl_config, d); + if (result) + return result; + + memcpy(&st->chip_config, hw_info[st->chip_type].config, + sizeof(struct inv_mpu6050_chip_config)); + result = inv_mpu6050_set_power_itg(st, false); + + return result; +} + +static int inv_mpu6050_sensor_show(struct inv_mpu6050_state *st, int reg, + int axis, int *val) +{ + int ind, result; + __be16 d; + + ind = (axis - IIO_MOD_X) * 2; + result = i2c_smbus_read_i2c_block_data(st->client, reg + ind, 2, + (u8 *)&d); + if (result != 2) + return -EINVAL; + *val = (short)be16_to_cpup(&d); + + return IIO_VAL_INT; +} + +static int inv_mpu6050_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) { + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + { + int ret, result; + + ret = IIO_VAL_INT; + result = 0; + mutex_lock(&indio_dev->mlock); + if (!st->chip_config.enable) { + result = inv_mpu6050_set_power_itg(st, true); + if (result) + goto error_read_raw; + } + /* when enable is on, power is already on */ + switch (chan->type) { + case IIO_ANGL_VEL: + if (!st->chip_config.gyro_fifo_enable || + !st->chip_config.enable) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_BIT_PWR_GYRO_STBY); + if (result) + goto error_read_raw; + } + ret = inv_mpu6050_sensor_show(st, st->reg->raw_gyro, + chan->channel2, val); + if (!st->chip_config.gyro_fifo_enable || + !st->chip_config.enable) { + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_GYRO_STBY); + if (result) + goto error_read_raw; + } + break; + case IIO_ACCEL: + if (!st->chip_config.accl_fifo_enable || + !st->chip_config.enable) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_BIT_PWR_ACCL_STBY); + if (result) + goto error_read_raw; + } + ret = inv_mpu6050_sensor_show(st, st->reg->raw_accl, + chan->channel2, val); + if (!st->chip_config.accl_fifo_enable || + !st->chip_config.enable) { + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_ACCL_STBY); + if (result) + goto error_read_raw; + } + break; + case IIO_TEMP: + /* wait for stablization */ + msleep(INV_MPU6050_SENSOR_UP_TIME); + inv_mpu6050_sensor_show(st, st->reg->temperature, + IIO_MOD_X, val); + break; + default: + ret = -EINVAL; + break; + } +error_read_raw: + if (!st->chip_config.enable) + result |= inv_mpu6050_set_power_itg(st, false); + mutex_unlock(&indio_dev->mlock); + if (result) + return result; + + return ret; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = gyro_scale_6050[st->chip_config.fsr]; + + return IIO_VAL_INT_PLUS_NANO; + case IIO_ACCEL: + *val = 0; + *val2 = accel_scale[st->chip_config.accl_fs]; + + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = INV_MPU6050_TEMP_SCALE; + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = INV_MPU6050_TEMP_OFFSET; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int inv_mpu6050_write_gyro_scale(struct inv_mpu6050_state *st, int val) +{ + int result, i; + u8 d; + + for (i = 0; i < ARRAY_SIZE(gyro_scale_6050); ++i) { + if (gyro_scale_6050[i] == val) { + d = (i << INV_MPU6050_GYRO_CONFIG_FSR_SHIFT); + result = inv_mpu6050_write_reg(st, + st->reg->gyro_config, d); + if (result) + return result; + + st->chip_config.fsr = i; + return 0; + } + } + + return -EINVAL; +} + +static int inv_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } + default: + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} +static int inv_mpu6050_write_accel_scale(struct inv_mpu6050_state *st, int val) +{ + int result, i; + u8 d; + + for (i = 0; i < ARRAY_SIZE(accel_scale); ++i) { + if (accel_scale[i] == val) { + d = (i << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); + result = inv_mpu6050_write_reg(st, + st->reg->accl_config, d); + if (result) + return result; + + st->chip_config.accl_fs = i; + return 0; + } + } + + return -EINVAL; +} + +static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) { + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int result; + + mutex_lock(&indio_dev->mlock); + /* we should only update scale when the chip is disabled, i.e., + not running */ + if (st->chip_config.enable) { + result = -EBUSY; + goto error_write_raw; + } + result = inv_mpu6050_set_power_itg(st, true); + if (result) + goto error_write_raw; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + result = inv_mpu6050_write_gyro_scale(st, val2); + break; + case IIO_ACCEL: + result = inv_mpu6050_write_accel_scale(st, val2); + break; + default: + result = -EINVAL; + break; + } + break; + default: + result = -EINVAL; + break; + } + +error_write_raw: + result |= inv_mpu6050_set_power_itg(st, false); + mutex_unlock(&indio_dev->mlock); + + return result; +} + +/** + * inv_mpu6050_set_lpf() - set low pass filer based on fifo rate. + * + * Based on the Nyquist principle, the sampling rate must + * exceed twice of the bandwidth of the signal, or there + * would be alising. This function basically search for the + * correct low pass parameters based on the fifo rate, e.g, + * sampling frequency. + */ +static int inv_mpu6050_set_lpf(struct inv_mpu6050_state *st, int rate) +{ + const int hz[] = {188, 98, 42, 20, 10, 5}; + const int d[] = {INV_MPU6050_FILTER_188HZ, INV_MPU6050_FILTER_98HZ, + INV_MPU6050_FILTER_42HZ, INV_MPU6050_FILTER_20HZ, + INV_MPU6050_FILTER_10HZ, INV_MPU6050_FILTER_5HZ}; + int i, h, result; + u8 data; + + h = (rate >> 1); + i = 0; + while ((h < hz[i]) && (i < ARRAY_SIZE(d) - 1)) + i++; + data = d[i]; + result = inv_mpu6050_write_reg(st, st->reg->lpf, data); + if (result) + return result; + st->chip_config.lpf = data; + + return 0; +} + +/** + * inv_mpu6050_fifo_rate_store() - Set fifo rate. + */ +static ssize_t inv_mpu6050_fifo_rate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + s32 fifo_rate; + u8 d; + int result; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + if (kstrtoint(buf, 10, &fifo_rate)) + return -EINVAL; + if (fifo_rate < INV_MPU6050_MIN_FIFO_RATE || + fifo_rate > INV_MPU6050_MAX_FIFO_RATE) + return -EINVAL; + if (fifo_rate == st->chip_config.fifo_rate) + return count; + + mutex_lock(&indio_dev->mlock); + if (st->chip_config.enable) { + result = -EBUSY; + goto fifo_rate_fail; + } + result = inv_mpu6050_set_power_itg(st, true); + if (result) + goto fifo_rate_fail; + + d = INV_MPU6050_ONE_K_HZ / fifo_rate - 1; + result = inv_mpu6050_write_reg(st, st->reg->sample_rate_div, d); + if (result) + goto fifo_rate_fail; + st->chip_config.fifo_rate = fifo_rate; + + result = inv_mpu6050_set_lpf(st, fifo_rate); + if (result) + goto fifo_rate_fail; + +fifo_rate_fail: + result |= inv_mpu6050_set_power_itg(st, false); + mutex_unlock(&indio_dev->mlock); + if (result) + return result; + + return count; +} + +/** + * inv_fifo_rate_show() - Get the current sampling rate. + */ +static ssize_t inv_fifo_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu6050_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%d\n", st->chip_config.fifo_rate); +} + +/** + * inv_attr_show() - calling this function will show current + * parameters. + */ +static ssize_t inv_attr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu6050_state *st = iio_priv(dev_to_iio_dev(dev)); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + s8 *m; + + switch (this_attr->address) { + /* In MPU6050, the two matrix are the same because gyro and accel + are integrated in one chip */ + case ATTR_GYRO_MATRIX: + case ATTR_ACCL_MATRIX: + m = st->plat_data.orientation; + + return sprintf(buf, "%d, %d, %d; %d, %d, %d; %d, %d, %d\n", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); + default: + return -EINVAL; + } +} + +/** + * inv_mpu6050_validate_trigger() - validate_trigger callback for invensense + * MPU6050 device. + * @indio_dev: The IIO device + * @trig: The new trigger + * + * Returns: 0 if the 'trig' matches the trigger registered by the MPU6050 + * device, -EINVAL otherwise. + */ +static int inv_mpu6050_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + if (st->trig != trig) + return -EINVAL; + + return 0; +} + +#define INV_MPU6050_CHAN(_type, _channel2, _index) \ + { \ + .type = _type, \ + .modified = 1, \ + .channel2 = _channel2, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0 , \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec inv_mpu_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU6050_SCAN_TIMESTAMP), + /* + * Note that temperature should only be via polled reading only, + * not the final scan elements output. + */ + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_OFFSET) + | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y), + INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z), + + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y), + INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z), +}; + +/* constant IIO attribute */ +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("10 20 50 100 200 500"); +static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, inv_fifo_rate_show, + inv_mpu6050_fifo_rate_store); +static IIO_DEVICE_ATTR(in_gyro_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_GYRO_MATRIX); +static IIO_DEVICE_ATTR(in_accel_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_ACCL_MATRIX); + +static struct attribute *inv_attributes[] = { + &iio_dev_attr_in_gyro_matrix.dev_attr.attr, + &iio_dev_attr_in_accel_matrix.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group inv_attribute_group = { + .attrs = inv_attributes +}; + +static const struct iio_info mpu_info = { + .driver_module = THIS_MODULE, + .read_raw = &inv_mpu6050_read_raw, + .write_raw = &inv_mpu6050_write_raw, + .write_raw_get_fmt = &inv_write_raw_get_fmt, + .attrs = &inv_attribute_group, + .validate_trigger = inv_mpu6050_validate_trigger, +}; + +/** + * inv_check_and_setup_chip() - check and setup chip. + */ +static int inv_check_and_setup_chip(struct inv_mpu6050_state *st, + const struct i2c_device_id *id) +{ + int result; + + st->chip_type = INV_MPU6050; + st->hw = &hw_info[st->chip_type]; + st->reg = hw_info[st->chip_type].reg; + + /* reset to make sure previous state are not there */ + result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, + INV_MPU6050_BIT_H_RESET); + if (result) + return result; + msleep(INV_MPU6050_POWER_UP_TIME); + /* toggle power state. After reset, the sleep bit could be on + or off depending on the OTP settings. Toggling power would + make it in a definite state as well as making the hardware + state align with the software state */ + result = inv_mpu6050_set_power_itg(st, false); + if (result) + return result; + result = inv_mpu6050_set_power_itg(st, true); + if (result) + return result; + + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_ACCL_STBY); + if (result) + return result; + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_GYRO_STBY); + if (result) + return result; + + return 0; +} + +/** + * inv_mpu_probe() - probe function. + * @client: i2c client. + * @id: i2c device id. + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_mpu_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct inv_mpu6050_state *st; + struct iio_dev *indio_dev; + struct inv_mpu6050_platform_data *pdata; + int result; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENOSYS; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->client = client; + st->powerup_count = 0; + pdata = dev_get_platdata(&client->dev); + if (pdata) + st->plat_data = *pdata; + /* power is turned on inside check chip type*/ + result = inv_check_and_setup_chip(st, id); + if (result) + return result; + + result = inv_mpu6050_init_config(indio_dev); + if (result) { + dev_err(&client->dev, + "Could not initialize device.\n"); + return result; + } + + i2c_set_clientdata(client, indio_dev); + indio_dev->dev.parent = &client->dev; + /* id will be NULL when enumerated via ACPI */ + if (id) + indio_dev->name = (char *)id->name; + else + indio_dev->name = (char *)dev_name(&client->dev); + indio_dev->channels = inv_mpu_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); + + indio_dev->info = &mpu_info; + indio_dev->modes = INDIO_BUFFER_TRIGGERED; + + result = iio_triggered_buffer_setup(indio_dev, + inv_mpu6050_irq_handler, + inv_mpu6050_read_fifo, + NULL); + if (result) { + dev_err(&st->client->dev, "configure buffer fail %d\n", + result); + return result; + } + result = inv_mpu6050_probe_trigger(indio_dev); + if (result) { + dev_err(&st->client->dev, "trigger probe fail %d\n", result); + goto out_unreg_ring; + } + + INIT_KFIFO(st->timestamps); + spin_lock_init(&st->time_stamp_lock); + result = iio_device_register(indio_dev); + if (result) { + dev_err(&st->client->dev, "IIO register fail %d\n", result); + goto out_remove_trigger; + } + + st->mux_adapter = i2c_add_mux_adapter(client->adapter, + &client->dev, + indio_dev, + 0, 0, 0, + inv_mpu6050_select_bypass, + inv_mpu6050_deselect_bypass); + if (!st->mux_adapter) { + result = -ENODEV; + goto out_unreg_device; + } + + result = inv_mpu_acpi_create_mux_client(st); + if (result) + goto out_del_mux; + + return 0; + +out_del_mux: + i2c_del_mux_adapter(st->mux_adapter); +out_unreg_device: + iio_device_unregister(indio_dev); +out_remove_trigger: + inv_mpu6050_remove_trigger(st); +out_unreg_ring: + iio_triggered_buffer_cleanup(indio_dev); + return result; +} + +static int inv_mpu_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + inv_mpu_acpi_delete_mux_client(st); + i2c_del_mux_adapter(st->mux_adapter); + iio_device_unregister(indio_dev); + inv_mpu6050_remove_trigger(st); + iio_triggered_buffer_cleanup(indio_dev); + + return 0; +} +#ifdef CONFIG_PM_SLEEP + +static int inv_mpu_resume(struct device *dev) +{ + return inv_mpu6050_set_power_itg( + iio_priv(i2c_get_clientdata(to_i2c_client(dev))), true); +} + +static int inv_mpu_suspend(struct device *dev) +{ + return inv_mpu6050_set_power_itg( + iio_priv(i2c_get_clientdata(to_i2c_client(dev))), false); +} +static SIMPLE_DEV_PM_OPS(inv_mpu_pmops, inv_mpu_suspend, inv_mpu_resume); + +#define INV_MPU6050_PMOPS (&inv_mpu_pmops) +#else +#define INV_MPU6050_PMOPS NULL +#endif /* CONFIG_PM_SLEEP */ + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_mpu_id[] = { + {"mpu6050", INV_MPU6050}, + {"mpu6500", INV_MPU6500}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, inv_mpu_id); + +static const struct acpi_device_id inv_acpi_match[] = { + {"INVN6500", 0}, + { }, +}; + +MODULE_DEVICE_TABLE(acpi, inv_acpi_match); + +static struct i2c_driver inv_mpu_driver = { + .probe = inv_mpu_probe, + .remove = inv_mpu_remove, + .id_table = inv_mpu_id, + .driver = { + .owner = THIS_MODULE, + .name = "inv-mpu6050", + .pm = INV_MPU6050_PMOPS, + .acpi_match_table = ACPI_PTR(inv_acpi_match), + }, +}; + +module_i2c_driver(inv_mpu_driver); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device MPU6050 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h new file mode 100644 index 000000000..db0a4a275 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -0,0 +1,256 @@ +/* +* Copyright (C) 2012 Invensense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ +#include <linux/i2c.h> +#include <linux/kfifo.h> +#include <linux/spinlock.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/platform_data/invensense_mpu6050.h> + +/** + * struct inv_mpu6050_reg_map - Notable registers. + * @sample_rate_div: Divider applied to gyro output rate. + * @lpf: Configures internal low pass filter. + * @user_ctrl: Enables/resets the FIFO. + * @fifo_en: Determines which data will appear in FIFO. + * @gyro_config: gyro config register. + * @accl_config: accel config register + * @fifo_count_h: Upper byte of FIFO count. + * @fifo_r_w: FIFO register. + * @raw_gyro: Address of first gyro register. + * @raw_accl: Address of first accel register. + * @temperature: temperature register + * @int_enable: Interrupt enable register. + * @pwr_mgmt_1: Controls chip's power state and clock source. + * @pwr_mgmt_2: Controls power state of individual sensors. + */ +struct inv_mpu6050_reg_map { + u8 sample_rate_div; + u8 lpf; + u8 user_ctrl; + u8 fifo_en; + u8 gyro_config; + u8 accl_config; + u8 fifo_count_h; + u8 fifo_r_w; + u8 raw_gyro; + u8 raw_accl; + u8 temperature; + u8 int_enable; + u8 pwr_mgmt_1; + u8 pwr_mgmt_2; + u8 int_pin_cfg; +}; + +/*device enum */ +enum inv_devices { + INV_MPU6050, + INV_MPU6500, + INV_NUM_PARTS +}; + +/** + * struct inv_mpu6050_chip_config - Cached chip configuration data. + * @fsr: Full scale range. + * @lpf: Digital low pass filter frequency. + * @accl_fs: accel full scale range. + * @enable: master enable state. + * @accl_fifo_enable: enable accel data output + * @gyro_fifo_enable: enable gyro data output + * @fifo_rate: FIFO update rate. + */ +struct inv_mpu6050_chip_config { + unsigned int fsr:2; + unsigned int lpf:3; + unsigned int accl_fs:2; + unsigned int enable:1; + unsigned int accl_fifo_enable:1; + unsigned int gyro_fifo_enable:1; + u16 fifo_rate; +}; + +/** + * struct inv_mpu6050_hw - Other important hardware information. + * @num_reg: Number of registers on device. + * @name: name of the chip. + * @reg: register map of the chip. + * @config: configuration of the chip. + */ +struct inv_mpu6050_hw { + u8 num_reg; + u8 *name; + const struct inv_mpu6050_reg_map *reg; + const struct inv_mpu6050_chip_config *config; +}; + +/* + * struct inv_mpu6050_state - Driver state variables. + * @TIMESTAMP_FIFO_SIZE: fifo size for timestamp. + * @trig: IIO trigger. + * @chip_config: Cached attribute information. + * @reg: Map of important registers. + * @hw: Other hardware-specific information. + * @chip_type: chip type. + * @time_stamp_lock: spin lock to time stamp. + * @client: i2c client handle. + * @plat_data: platform data. + * @timestamps: kfifo queue to store time stamp. + */ +struct inv_mpu6050_state { +#define TIMESTAMP_FIFO_SIZE 16 + struct iio_trigger *trig; + struct inv_mpu6050_chip_config chip_config; + const struct inv_mpu6050_reg_map *reg; + const struct inv_mpu6050_hw *hw; + enum inv_devices chip_type; + spinlock_t time_stamp_lock; + struct i2c_client *client; + struct i2c_adapter *mux_adapter; + struct i2c_client *mux_client; + unsigned int powerup_count; + struct inv_mpu6050_platform_data plat_data; + DECLARE_KFIFO(timestamps, long long, TIMESTAMP_FIFO_SIZE); +}; + +/*register and associated bit definition*/ +#define INV_MPU6050_REG_SAMPLE_RATE_DIV 0x19 +#define INV_MPU6050_REG_CONFIG 0x1A +#define INV_MPU6050_REG_GYRO_CONFIG 0x1B +#define INV_MPU6050_REG_ACCEL_CONFIG 0x1C + +#define INV_MPU6050_REG_FIFO_EN 0x23 +#define INV_MPU6050_BIT_ACCEL_OUT 0x08 +#define INV_MPU6050_BITS_GYRO_OUT 0x70 + +#define INV_MPU6050_REG_INT_ENABLE 0x38 +#define INV_MPU6050_BIT_DATA_RDY_EN 0x01 +#define INV_MPU6050_BIT_DMP_INT_EN 0x02 + +#define INV_MPU6050_REG_RAW_ACCEL 0x3B +#define INV_MPU6050_REG_TEMPERATURE 0x41 +#define INV_MPU6050_REG_RAW_GYRO 0x43 + +#define INV_MPU6050_REG_USER_CTRL 0x6A +#define INV_MPU6050_BIT_FIFO_RST 0x04 +#define INV_MPU6050_BIT_DMP_RST 0x08 +#define INV_MPU6050_BIT_I2C_MST_EN 0x20 +#define INV_MPU6050_BIT_FIFO_EN 0x40 +#define INV_MPU6050_BIT_DMP_EN 0x80 + +#define INV_MPU6050_REG_PWR_MGMT_1 0x6B +#define INV_MPU6050_BIT_H_RESET 0x80 +#define INV_MPU6050_BIT_SLEEP 0x40 +#define INV_MPU6050_BIT_CLK_MASK 0x7 + +#define INV_MPU6050_REG_PWR_MGMT_2 0x6C +#define INV_MPU6050_BIT_PWR_ACCL_STBY 0x38 +#define INV_MPU6050_BIT_PWR_GYRO_STBY 0x07 + +#define INV_MPU6050_REG_FIFO_COUNT_H 0x72 +#define INV_MPU6050_REG_FIFO_R_W 0x74 + +#define INV_MPU6050_BYTES_PER_3AXIS_SENSOR 6 +#define INV_MPU6050_FIFO_COUNT_BYTE 2 +#define INV_MPU6050_FIFO_THRESHOLD 500 +#define INV_MPU6050_POWER_UP_TIME 100 +#define INV_MPU6050_TEMP_UP_TIME 100 +#define INV_MPU6050_SENSOR_UP_TIME 30 +#define INV_MPU6050_REG_UP_TIME 5 + +#define INV_MPU6050_TEMP_OFFSET 12421 +#define INV_MPU6050_TEMP_SCALE 2941 +#define INV_MPU6050_MAX_GYRO_FS_PARAM 3 +#define INV_MPU6050_MAX_ACCL_FS_PARAM 3 +#define INV_MPU6050_THREE_AXIS 3 +#define INV_MPU6050_GYRO_CONFIG_FSR_SHIFT 3 +#define INV_MPU6050_ACCL_CONFIG_FSR_SHIFT 3 + +/* 6 + 6 round up and plus 8 */ +#define INV_MPU6050_OUTPUT_DATA_SIZE 24 + +#define INV_MPU6050_REG_INT_PIN_CFG 0x37 +#define INV_MPU6050_BIT_BYPASS_EN 0x2 + +/* init parameters */ +#define INV_MPU6050_INIT_FIFO_RATE 50 +#define INV_MPU6050_TIME_STAMP_TOR 5 +#define INV_MPU6050_MAX_FIFO_RATE 1000 +#define INV_MPU6050_MIN_FIFO_RATE 4 +#define INV_MPU6050_ONE_K_HZ 1000 + +/* scan element definition */ +enum inv_mpu6050_scan { + INV_MPU6050_SCAN_ACCL_X, + INV_MPU6050_SCAN_ACCL_Y, + INV_MPU6050_SCAN_ACCL_Z, + INV_MPU6050_SCAN_GYRO_X, + INV_MPU6050_SCAN_GYRO_Y, + INV_MPU6050_SCAN_GYRO_Z, + INV_MPU6050_SCAN_TIMESTAMP, +}; + +enum inv_mpu6050_filter_e { + INV_MPU6050_FILTER_256HZ_NOLPF2 = 0, + INV_MPU6050_FILTER_188HZ, + INV_MPU6050_FILTER_98HZ, + INV_MPU6050_FILTER_42HZ, + INV_MPU6050_FILTER_20HZ, + INV_MPU6050_FILTER_10HZ, + INV_MPU6050_FILTER_5HZ, + INV_MPU6050_FILTER_2100HZ_NOLPF, + NUM_MPU6050_FILTER +}; + +/* IIO attribute address */ +enum INV_MPU6050_IIO_ATTR_ADDR { + ATTR_GYRO_MATRIX, + ATTR_ACCL_MATRIX, +}; + +enum inv_mpu6050_accl_fs_e { + INV_MPU6050_FS_02G = 0, + INV_MPU6050_FS_04G, + INV_MPU6050_FS_08G, + INV_MPU6050_FS_16G, + NUM_ACCL_FSR +}; + +enum inv_mpu6050_fsr_e { + INV_MPU6050_FSR_250DPS = 0, + INV_MPU6050_FSR_500DPS, + INV_MPU6050_FSR_1000DPS, + INV_MPU6050_FSR_2000DPS, + NUM_MPU6050_FSR +}; + +enum inv_mpu6050_clock_sel_e { + INV_CLK_INTERNAL = 0, + INV_CLK_PLL, + NUM_CLK +}; + +irqreturn_t inv_mpu6050_irq_handler(int irq, void *p); +irqreturn_t inv_mpu6050_read_fifo(int irq, void *p); +int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev); +void inv_mpu6050_remove_trigger(struct inv_mpu6050_state *st); +int inv_reset_fifo(struct iio_dev *indio_dev); +int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask); +int inv_mpu6050_write_reg(struct inv_mpu6050_state *st, int reg, u8 val); +int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, bool power_on); +int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st); +void inv_mpu_acpi_delete_mux_client(struct inv_mpu6050_state *st); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c new file mode 100644 index 000000000..ba27e2775 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -0,0 +1,195 @@ +/* +* Copyright (C) 2012 Invensense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +*/ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/sysfs.h> +#include <linux/jiffies.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/poll.h> +#include "inv_mpu_iio.h" + +static void inv_clear_kfifo(struct inv_mpu6050_state *st) +{ + unsigned long flags; + + /* take the spin lock sem to avoid interrupt kick in */ + spin_lock_irqsave(&st->time_stamp_lock, flags); + kfifo_reset(&st->timestamps); + spin_unlock_irqrestore(&st->time_stamp_lock, flags); +} + +int inv_reset_fifo(struct iio_dev *indio_dev) +{ + int result; + u8 d; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + /* disable interrupt */ + result = inv_mpu6050_write_reg(st, st->reg->int_enable, 0); + if (result) { + dev_err(&st->client->dev, "int_enable failed %d\n", result); + return result; + } + /* disable the sensor output to FIFO */ + result = inv_mpu6050_write_reg(st, st->reg->fifo_en, 0); + if (result) + goto reset_fifo_fail; + /* disable fifo reading */ + result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, 0); + if (result) + goto reset_fifo_fail; + + /* reset FIFO*/ + result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, + INV_MPU6050_BIT_FIFO_RST); + if (result) + goto reset_fifo_fail; + + /* clear timestamps fifo */ + inv_clear_kfifo(st); + + /* enable interrupt */ + if (st->chip_config.accl_fifo_enable || + st->chip_config.gyro_fifo_enable) { + result = inv_mpu6050_write_reg(st, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN); + if (result) + return result; + } + /* enable FIFO reading and I2C master interface*/ + result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, + INV_MPU6050_BIT_FIFO_EN); + if (result) + goto reset_fifo_fail; + /* enable sensor output to FIFO */ + d = 0; + if (st->chip_config.gyro_fifo_enable) + d |= INV_MPU6050_BITS_GYRO_OUT; + if (st->chip_config.accl_fifo_enable) + d |= INV_MPU6050_BIT_ACCEL_OUT; + result = inv_mpu6050_write_reg(st, st->reg->fifo_en, d); + if (result) + goto reset_fifo_fail; + + return 0; + +reset_fifo_fail: + dev_err(&st->client->dev, "reset fifo failed %d\n", result); + result = inv_mpu6050_write_reg(st, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN); + + return result; +} + +/** + * inv_mpu6050_irq_handler() - Cache a timestamp at each data ready interrupt. + */ +irqreturn_t inv_mpu6050_irq_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + s64 timestamp; + + timestamp = iio_get_time_ns(); + kfifo_in_spinlocked(&st->timestamps, ×tamp, 1, + &st->time_stamp_lock); + + return IRQ_WAKE_THREAD; +} + +/** + * inv_mpu6050_read_fifo() - Transfer data from hardware FIFO to KFIFO. + */ +irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + size_t bytes_per_datum; + int result; + u8 data[INV_MPU6050_OUTPUT_DATA_SIZE]; + u16 fifo_count; + s64 timestamp; + + mutex_lock(&indio_dev->mlock); + if (!(st->chip_config.accl_fifo_enable | + st->chip_config.gyro_fifo_enable)) + goto end_session; + bytes_per_datum = 0; + if (st->chip_config.accl_fifo_enable) + bytes_per_datum += INV_MPU6050_BYTES_PER_3AXIS_SENSOR; + + if (st->chip_config.gyro_fifo_enable) + bytes_per_datum += INV_MPU6050_BYTES_PER_3AXIS_SENSOR; + + /* + * read fifo_count register to know how many bytes inside FIFO + * right now + */ + result = i2c_smbus_read_i2c_block_data(st->client, + st->reg->fifo_count_h, + INV_MPU6050_FIFO_COUNT_BYTE, data); + if (result != INV_MPU6050_FIFO_COUNT_BYTE) + goto end_session; + fifo_count = be16_to_cpup((__be16 *)(&data[0])); + if (fifo_count < bytes_per_datum) + goto end_session; + /* fifo count can't be odd number, if it is odd, reset fifo*/ + if (fifo_count & 1) + goto flush_fifo; + if (fifo_count > INV_MPU6050_FIFO_THRESHOLD) + goto flush_fifo; + /* Timestamp mismatch. */ + if (kfifo_len(&st->timestamps) > + fifo_count / bytes_per_datum + INV_MPU6050_TIME_STAMP_TOR) + goto flush_fifo; + while (fifo_count >= bytes_per_datum) { + result = i2c_smbus_read_i2c_block_data(st->client, + st->reg->fifo_r_w, + bytes_per_datum, data); + if (result != bytes_per_datum) + goto flush_fifo; + + result = kfifo_out(&st->timestamps, ×tamp, 1); + /* when there is no timestamp, put timestamp as 0 */ + if (0 == result) + timestamp = 0; + + result = iio_push_to_buffers_with_timestamp(indio_dev, data, + timestamp); + if (result) + goto flush_fifo; + fifo_count -= bytes_per_datum; + } + +end_session: + mutex_unlock(&indio_dev->mlock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; + +flush_fifo: + /* Flush HW and SW FIFOs. */ + inv_reset_fifo(indio_dev); + mutex_unlock(&indio_dev->mlock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c new file mode 100644 index 000000000..844610c3a --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -0,0 +1,150 @@ +/* +* Copyright (C) 2012 Invensense, 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 "inv_mpu_iio.h" + +static void inv_scan_query(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + st->chip_config.gyro_fifo_enable = + test_bit(INV_MPU6050_SCAN_GYRO_X, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_GYRO_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_GYRO_Z, + indio_dev->active_scan_mask); + + st->chip_config.accl_fifo_enable = + test_bit(INV_MPU6050_SCAN_ACCL_X, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_ACCL_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_ACCL_Z, + indio_dev->active_scan_mask); +} + +/** + * inv_mpu6050_set_enable() - enable chip functions. + * @indio_dev: Device driver instance. + * @enable: enable/disable + */ +static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int result; + + if (enable) { + result = inv_mpu6050_set_power_itg(st, true); + if (result) + return result; + inv_scan_query(indio_dev); + if (st->chip_config.gyro_fifo_enable) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_BIT_PWR_GYRO_STBY); + if (result) + return result; + } + if (st->chip_config.accl_fifo_enable) { + result = inv_mpu6050_switch_engine(st, true, + INV_MPU6050_BIT_PWR_ACCL_STBY); + if (result) + return result; + } + result = inv_reset_fifo(indio_dev); + if (result) + return result; + } else { + result = inv_mpu6050_write_reg(st, st->reg->fifo_en, 0); + if (result) + return result; + + result = inv_mpu6050_write_reg(st, st->reg->int_enable, 0); + if (result) + return result; + + result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, 0); + if (result) + return result; + + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_GYRO_STBY); + if (result) + return result; + + result = inv_mpu6050_switch_engine(st, false, + INV_MPU6050_BIT_PWR_ACCL_STBY); + if (result) + return result; + result = inv_mpu6050_set_power_itg(st, false); + if (result) + return result; + } + st->chip_config.enable = enable; + + return 0; +} + +/** + * inv_mpu_data_rdy_trigger_set_state() - set data ready interrupt state + * @trig: Trigger instance + * @state: Desired trigger state + */ +static int inv_mpu_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + return inv_mpu6050_set_enable(iio_trigger_get_drvdata(trig), state); +} + +static const struct iio_trigger_ops inv_mpu_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &inv_mpu_data_rdy_trigger_set_state, +}; + +int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + st->trig = devm_iio_trigger_alloc(&indio_dev->dev, + "%s-dev%d", + indio_dev->name, + indio_dev->id); + if (!st->trig) + return -ENOMEM; + + ret = devm_request_irq(&indio_dev->dev, st->client->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "inv_mpu", + st->trig); + if (ret) + return ret; + + st->trig->dev.parent = &st->client->dev; + st->trig->ops = &inv_mpu_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + + ret = iio_trigger_register(st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + return 0; +} + +void inv_mpu6050_remove_trigger(struct inv_mpu6050_state *st) +{ + iio_trigger_unregister(st->trig); +} diff --git a/drivers/iio/imu/kmx61.c b/drivers/iio/imu/kmx61.c new file mode 100644 index 000000000..462a01062 --- /dev/null +++ b/drivers/iio/imu/kmx61.c @@ -0,0 +1,1579 @@ +/* + * KMX61 - Kionix 6-axis Accelerometer/Magnetometer + * + * Copyright (c) 2014, Intel Corporation. + * + * 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 KMX61 (7-bit I2C slave address 0x0E or 0x0F). + * + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/acpi.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/trigger.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +#define KMX61_DRV_NAME "kmx61" +#define KMX61_GPIO_NAME "kmx61_int" +#define KMX61_IRQ_NAME "kmx61_event" + +#define KMX61_REG_WHO_AM_I 0x00 +#define KMX61_REG_INS1 0x01 +#define KMX61_REG_INS2 0x02 + +/* + * three 16-bit accelerometer output registers for X/Y/Z axis + * we use only XOUT_L as a base register, all other addresses + * can be obtained by applying an offset and are provided here + * only for clarity. + */ +#define KMX61_ACC_XOUT_L 0x0A +#define KMX61_ACC_XOUT_H 0x0B +#define KMX61_ACC_YOUT_L 0x0C +#define KMX61_ACC_YOUT_H 0x0D +#define KMX61_ACC_ZOUT_L 0x0E +#define KMX61_ACC_ZOUT_H 0x0F + +/* + * one 16-bit temperature output register + */ +#define KMX61_TEMP_L 0x10 +#define KMX61_TEMP_H 0x11 + +/* + * three 16-bit magnetometer output registers for X/Y/Z axis + */ +#define KMX61_MAG_XOUT_L 0x12 +#define KMX61_MAG_XOUT_H 0x13 +#define KMX61_MAG_YOUT_L 0x14 +#define KMX61_MAG_YOUT_H 0x15 +#define KMX61_MAG_ZOUT_L 0x16 +#define KMX61_MAG_ZOUT_H 0x17 + +#define KMX61_REG_INL 0x28 +#define KMX61_REG_STBY 0x29 +#define KMX61_REG_CTRL1 0x2A +#define KMX61_REG_CTRL2 0x2B +#define KMX61_REG_ODCNTL 0x2C +#define KMX61_REG_INC1 0x2D + +#define KMX61_REG_WUF_THRESH 0x3D +#define KMX61_REG_WUF_TIMER 0x3E + +#define KMX61_ACC_STBY_BIT BIT(0) +#define KMX61_MAG_STBY_BIT BIT(1) +#define KMX61_ACT_STBY_BIT BIT(7) + +#define KMX61_ALL_STBY (KMX61_ACC_STBY_BIT | KMX61_MAG_STBY_BIT) + +#define KMX61_REG_INS1_BIT_WUFS BIT(1) + +#define KMX61_REG_INS2_BIT_ZP BIT(0) +#define KMX61_REG_INS2_BIT_ZN BIT(1) +#define KMX61_REG_INS2_BIT_YP BIT(2) +#define KMX61_REG_INS2_BIT_YN BIT(3) +#define KMX61_REG_INS2_BIT_XP BIT(4) +#define KMX61_REG_INS2_BIT_XN BIT(5) + +#define KMX61_REG_CTRL1_GSEL_MASK 0x03 + +#define KMX61_REG_CTRL1_BIT_RES BIT(4) +#define KMX61_REG_CTRL1_BIT_DRDYE BIT(5) +#define KMX61_REG_CTRL1_BIT_WUFE BIT(6) +#define KMX61_REG_CTRL1_BIT_BTSE BIT(7) + +#define KMX61_REG_INC1_BIT_WUFS BIT(0) +#define KMX61_REG_INC1_BIT_DRDYM BIT(1) +#define KMX61_REG_INC1_BIT_DRDYA BIT(2) +#define KMX61_REG_INC1_BIT_IEN BIT(5) + +#define KMX61_ACC_ODR_SHIFT 0 +#define KMX61_MAG_ODR_SHIFT 4 +#define KMX61_ACC_ODR_MASK 0x0F +#define KMX61_MAG_ODR_MASK 0xF0 + +#define KMX61_OWUF_MASK 0x7 + +#define KMX61_DEFAULT_WAKE_THRESH 1 +#define KMX61_DEFAULT_WAKE_DURATION 1 + +#define KMX61_SLEEP_DELAY_MS 2000 + +#define KMX61_CHIP_ID 0x12 + +/* KMX61 devices */ +#define KMX61_ACC 0x01 +#define KMX61_MAG 0x02 + +struct kmx61_data { + struct i2c_client *client; + + /* serialize access to non-atomic ops, e.g set_mode */ + struct mutex lock; + + /* standby state */ + bool acc_stby; + bool mag_stby; + + /* power state */ + bool acc_ps; + bool mag_ps; + + /* config bits */ + u8 range; + u8 odr_bits; + u8 wake_thresh; + u8 wake_duration; + + /* accelerometer specific data */ + struct iio_dev *acc_indio_dev; + struct iio_trigger *acc_dready_trig; + struct iio_trigger *motion_trig; + bool acc_dready_trig_on; + bool motion_trig_on; + bool ev_enable_state; + + /* magnetometer specific data */ + struct iio_dev *mag_indio_dev; + struct iio_trigger *mag_dready_trig; + bool mag_dready_trig_on; +}; + +enum kmx61_range { + KMX61_RANGE_2G, + KMX61_RANGE_4G, + KMX61_RANGE_8G, +}; + +enum kmx61_axis { + KMX61_AXIS_X, + KMX61_AXIS_Y, + KMX61_AXIS_Z, +}; + +static const u16 kmx61_uscale_table[] = {9582, 19163, 38326}; + +static const struct { + int val; + int val2; +} kmx61_samp_freq_table[] = { {12, 500000}, + {25, 0}, + {50, 0}, + {100, 0}, + {200, 0}, + {400, 0}, + {800, 0}, + {1600, 0}, + {0, 781000}, + {1, 563000}, + {3, 125000}, + {6, 250000} }; + +static const struct { + int val; + int val2; + int odr_bits; +} kmx61_wake_up_odr_table[] = { {0, 781000, 0x00}, + {1, 563000, 0x01}, + {3, 125000, 0x02}, + {6, 250000, 0x03}, + {12, 500000, 0x04}, + {25, 0, 0x05}, + {50, 0, 0x06}, + {100, 0, 0x06}, + {200, 0, 0x06}, + {400, 0, 0x06}, + {800, 0, 0x06}, + {1600, 0, 0x06} }; + +static IIO_CONST_ATTR(accel_scale_available, "0.009582 0.019163 0.038326"); +static IIO_CONST_ATTR(magn_scale_available, "0.001465"); +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "0.781000 1.563000 3.125000 6.250000 12.500000 25 50 100 200 400 800"); + +static struct attribute *kmx61_acc_attributes[] = { + &iio_const_attr_accel_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static struct attribute *kmx61_mag_attributes[] = { + &iio_const_attr_magn_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group kmx61_acc_attribute_group = { + .attrs = kmx61_acc_attributes, +}; + +static const struct attribute_group kmx61_mag_attribute_group = { + .attrs = kmx61_mag_attributes, +}; + +static const struct iio_event_spec kmx61_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_PERIOD), +}; + +#define KMX61_ACC_CHAN(_axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .address = KMX61_ACC, \ + .scan_index = KMX61_AXIS_ ## _axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_LE, \ + }, \ + .event_spec = &kmx61_event, \ + .num_event_specs = 1 \ +} + +#define KMX61_MAG_CHAN(_axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _axis, \ + .address = KMX61_MAG, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = KMX61_AXIS_ ## _axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 14, \ + .storagebits = 16, \ + .shift = 2, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec kmx61_acc_channels[] = { + KMX61_ACC_CHAN(X), + KMX61_ACC_CHAN(Y), + KMX61_ACC_CHAN(Z), +}; + +static const struct iio_chan_spec kmx61_mag_channels[] = { + KMX61_MAG_CHAN(X), + KMX61_MAG_CHAN(Y), + KMX61_MAG_CHAN(Z), +}; + +static void kmx61_set_data(struct iio_dev *indio_dev, struct kmx61_data *data) +{ + struct kmx61_data **priv = iio_priv(indio_dev); + + *priv = data; +} + +static struct kmx61_data *kmx61_get_data(struct iio_dev *indio_dev) +{ + return *(struct kmx61_data **)iio_priv(indio_dev); +} + +static int kmx61_convert_freq_to_bit(int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(kmx61_samp_freq_table); i++) + if (val == kmx61_samp_freq_table[i].val && + val2 == kmx61_samp_freq_table[i].val2) + return i; + return -EINVAL; +} + +static int kmx61_convert_wake_up_odr_to_bit(int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(kmx61_wake_up_odr_table); ++i) + if (kmx61_wake_up_odr_table[i].val == val && + kmx61_wake_up_odr_table[i].val2 == val2) + return kmx61_wake_up_odr_table[i].odr_bits; + return -EINVAL; +} + +/** + * kmx61_set_mode() - set KMX61 device operating mode + * @data - kmx61 device private data pointer + * @mode - bitmask, indicating operating mode for @device + * @device - bitmask, indicating device for which @mode needs to be set + * @update - update stby bits stored in device's private @data + * + * For each sensor (accelerometer/magnetometer) there are two operating modes + * STANDBY and OPERATION. Neither accel nor magn can be disabled independently + * if they are both enabled. Internal sensors state is saved in acc_stby and + * mag_stby members of driver's private @data. + */ +static int kmx61_set_mode(struct kmx61_data *data, u8 mode, u8 device, + bool update) +{ + int ret; + int acc_stby = -1, mag_stby = -1; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_STBY); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_stby\n"); + return ret; + } + if (device & KMX61_ACC) { + if (mode & KMX61_ACC_STBY_BIT) { + ret |= KMX61_ACC_STBY_BIT; + acc_stby = 1; + } else { + ret &= ~KMX61_ACC_STBY_BIT; + acc_stby = 0; + } + } + + if (device & KMX61_MAG) { + if (mode & KMX61_MAG_STBY_BIT) { + ret |= KMX61_MAG_STBY_BIT; + mag_stby = 1; + } else { + ret &= ~KMX61_MAG_STBY_BIT; + mag_stby = 0; + } + } + + if (mode & KMX61_ACT_STBY_BIT) + ret |= KMX61_ACT_STBY_BIT; + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_STBY, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_stby\n"); + return ret; + } + + if (acc_stby != -1 && update) + data->acc_stby = acc_stby; + if (mag_stby != -1 && update) + data->mag_stby = mag_stby; + + return 0; +} + +static int kmx61_get_mode(struct kmx61_data *data, u8 *mode, u8 device) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_STBY); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_stby\n"); + return ret; + } + *mode = 0; + + if (device & KMX61_ACC) { + if (ret & KMX61_ACC_STBY_BIT) + *mode |= KMX61_ACC_STBY_BIT; + else + *mode &= ~KMX61_ACC_STBY_BIT; + } + + if (device & KMX61_MAG) { + if (ret & KMX61_MAG_STBY_BIT) + *mode |= KMX61_MAG_STBY_BIT; + else + *mode &= ~KMX61_MAG_STBY_BIT; + } + + return 0; +} + +static int kmx61_set_wake_up_odr(struct kmx61_data *data, int val, int val2) +{ + int ret, odr_bits; + + odr_bits = kmx61_convert_wake_up_odr_to_bit(val, val2); + if (odr_bits < 0) + return odr_bits; + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_CTRL2, + odr_bits); + if (ret < 0) + dev_err(&data->client->dev, "Error writing reg_ctrl2\n"); + return ret; +} + +static int kmx61_set_odr(struct kmx61_data *data, int val, int val2, u8 device) +{ + int ret; + u8 mode; + int lodr_bits, odr_bits; + + ret = kmx61_get_mode(data, &mode, KMX61_ACC | KMX61_MAG); + if (ret < 0) + return ret; + + lodr_bits = kmx61_convert_freq_to_bit(val, val2); + if (lodr_bits < 0) + return lodr_bits; + + /* To change ODR, accel and magn must be in STDBY */ + ret = kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, + true); + if (ret < 0) + return ret; + + odr_bits = 0; + if (device & KMX61_ACC) + odr_bits |= lodr_bits << KMX61_ACC_ODR_SHIFT; + if (device & KMX61_MAG) + odr_bits |= lodr_bits << KMX61_MAG_ODR_SHIFT; + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_ODCNTL, + odr_bits); + if (ret < 0) + return ret; + + data->odr_bits = odr_bits; + + if (device & KMX61_ACC) { + ret = kmx61_set_wake_up_odr(data, val, val2); + if (ret) + return ret; + } + + return kmx61_set_mode(data, mode, KMX61_ACC | KMX61_MAG, true); +} + +static int kmx61_get_odr(struct kmx61_data *data, int *val, int *val2, + u8 device) +{ + u8 lodr_bits; + + if (device & KMX61_ACC) + lodr_bits = (data->odr_bits >> KMX61_ACC_ODR_SHIFT) & + KMX61_ACC_ODR_MASK; + else if (device & KMX61_MAG) + lodr_bits = (data->odr_bits >> KMX61_MAG_ODR_SHIFT) & + KMX61_MAG_ODR_MASK; + else + return -EINVAL; + + if (lodr_bits >= ARRAY_SIZE(kmx61_samp_freq_table)) + return -EINVAL; + + *val = kmx61_samp_freq_table[lodr_bits].val; + *val2 = kmx61_samp_freq_table[lodr_bits].val2; + + return 0; +} + +static int kmx61_set_range(struct kmx61_data *data, u8 range) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + ret &= ~KMX61_REG_CTRL1_GSEL_MASK; + ret |= range & KMX61_REG_CTRL1_GSEL_MASK; + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_CTRL1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + data->range = range; + + return 0; +} + +static int kmx61_set_scale(struct kmx61_data *data, u16 uscale) +{ + int ret, i; + u8 mode; + + for (i = 0; i < ARRAY_SIZE(kmx61_uscale_table); i++) { + if (kmx61_uscale_table[i] == uscale) { + ret = kmx61_get_mode(data, &mode, + KMX61_ACC | KMX61_MAG); + if (ret < 0) + return ret; + + ret = kmx61_set_mode(data, KMX61_ALL_STBY, + KMX61_ACC | KMX61_MAG, true); + if (ret < 0) + return ret; + + ret = kmx61_set_range(data, i); + if (ret < 0) + return ret; + + return kmx61_set_mode(data, mode, + KMX61_ACC | KMX61_MAG, true); + } + } + return -EINVAL; +} + +static int kmx61_chip_init(struct kmx61_data *data) +{ + int ret, val, val2; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_WHO_AM_I); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading who_am_i\n"); + return ret; + } + + if (ret != KMX61_CHIP_ID) { + dev_err(&data->client->dev, + "Wrong chip id, got %x expected %x\n", + ret, KMX61_CHIP_ID); + return -EINVAL; + } + + /* set accel 12bit, 4g range */ + ret = kmx61_set_range(data, KMX61_RANGE_4G); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_ODCNTL); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_odcntl\n"); + return ret; + } + data->odr_bits = ret; + + /* + * set output data rate for wake up (motion detection) function + * to match data rate for accelerometer sampling + */ + ret = kmx61_get_odr(data, &val, &val2, KMX61_ACC); + if (ret < 0) + return ret; + + ret = kmx61_set_wake_up_odr(data, val, val2); + if (ret < 0) + return ret; + + /* set acc/magn to OPERATION mode */ + ret = kmx61_set_mode(data, 0, KMX61_ACC | KMX61_MAG, true); + if (ret < 0) + return ret; + + data->wake_thresh = KMX61_DEFAULT_WAKE_THRESH; + data->wake_duration = KMX61_DEFAULT_WAKE_DURATION; + + return 0; +} + +static int kmx61_setup_new_data_interrupt(struct kmx61_data *data, + bool status, u8 device) +{ + u8 mode; + int ret; + + ret = kmx61_get_mode(data, &mode, KMX61_ACC | KMX61_MAG); + if (ret < 0) + return ret; + + ret = kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, true); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INC1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (status) { + ret |= KMX61_REG_INC1_BIT_IEN; + if (device & KMX61_ACC) + ret |= KMX61_REG_INC1_BIT_DRDYA; + if (device & KMX61_MAG) + ret |= KMX61_REG_INC1_BIT_DRDYM; + } else { + ret &= ~KMX61_REG_INC1_BIT_IEN; + if (device & KMX61_ACC) + ret &= ~KMX61_REG_INC1_BIT_DRDYA; + if (device & KMX61_MAG) + ret &= ~KMX61_REG_INC1_BIT_DRDYM; + } + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_INC1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_int_ctrl1\n"); + return ret; + } + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (status) + ret |= KMX61_REG_CTRL1_BIT_DRDYE; + else + ret &= ~KMX61_REG_CTRL1_BIT_DRDYE; + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_CTRL1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + + return kmx61_set_mode(data, mode, KMX61_ACC | KMX61_MAG, true); +} + +static int kmx61_chip_update_thresholds(struct kmx61_data *data) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, + KMX61_REG_WUF_TIMER, + data->wake_duration); + if (ret < 0) { + dev_err(&data->client->dev, "Errow writing reg_wuf_timer\n"); + return ret; + } + + ret = i2c_smbus_write_byte_data(data->client, + KMX61_REG_WUF_THRESH, + data->wake_thresh); + if (ret < 0) + dev_err(&data->client->dev, "Error writing reg_wuf_thresh\n"); + + return ret; +} + +static int kmx61_setup_any_motion_interrupt(struct kmx61_data *data, + bool status) +{ + u8 mode; + int ret; + + ret = kmx61_get_mode(data, &mode, KMX61_ACC | KMX61_MAG); + if (ret < 0) + return ret; + + ret = kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, true); + if (ret < 0) + return ret; + + ret = kmx61_chip_update_thresholds(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INC1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_inc1\n"); + return ret; + } + if (status) + ret |= (KMX61_REG_INC1_BIT_IEN | KMX61_REG_INC1_BIT_WUFS); + else + ret &= ~(KMX61_REG_INC1_BIT_IEN | KMX61_REG_INC1_BIT_WUFS); + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_INC1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_inc1\n"); + return ret; + } + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_CTRL1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + return ret; + } + + if (status) + ret |= KMX61_REG_CTRL1_BIT_WUFE | KMX61_REG_CTRL1_BIT_BTSE; + else + ret &= ~(KMX61_REG_CTRL1_BIT_WUFE | KMX61_REG_CTRL1_BIT_BTSE); + + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_CTRL1, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + return ret; + } + mode |= KMX61_ACT_STBY_BIT; + return kmx61_set_mode(data, mode, KMX61_ACC | KMX61_MAG, true); +} + +/** + * kmx61_set_power_state() - set power state for kmx61 @device + * @data - kmx61 device private pointer + * @on - power state to be set for @device + * @device - bitmask indicating device for which @on state needs to be set + * + * Notice that when ACC power state needs to be set to ON and MAG is in + * OPERATION then we know that kmx61_runtime_resume was already called + * so we must set ACC OPERATION mode here. The same happens when MAG power + * state needs to be set to ON and ACC is in OPERATION. + */ +static int kmx61_set_power_state(struct kmx61_data *data, bool on, u8 device) +{ +#ifdef CONFIG_PM + int ret; + + if (device & KMX61_ACC) { + if (on && !data->acc_ps && !data->mag_stby) { + ret = kmx61_set_mode(data, 0, KMX61_ACC, true); + if (ret < 0) + return ret; + } + data->acc_ps = on; + } + if (device & KMX61_MAG) { + if (on && !data->mag_ps && !data->acc_stby) { + ret = kmx61_set_mode(data, 0, KMX61_MAG, true); + if (ret < 0) + return ret; + } + data->mag_ps = on; + } + + if (on) { + ret = pm_runtime_get_sync(&data->client->dev); + } else { + pm_runtime_mark_last_busy(&data->client->dev); + ret = pm_runtime_put_autosuspend(&data->client->dev); + } + if (ret < 0) { + dev_err(&data->client->dev, + "Failed: kmx61_set_power_state for %d, ret %d\n", + on, ret); + if (on) + pm_runtime_put_noidle(&data->client->dev); + + return ret; + } +#endif + return 0; +} + +static int kmx61_read_measurement(struct kmx61_data *data, u8 base, u8 offset) +{ + int ret; + u8 reg = base + offset * 2; + + ret = i2c_smbus_read_word_data(data->client, reg); + if (ret < 0) + dev_err(&data->client->dev, "failed to read reg at %x\n", reg); + + return ret; +} + +static int kmx61_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + u8 base_reg; + struct kmx61_data *data = kmx61_get_data(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_ACCEL: + base_reg = KMX61_ACC_XOUT_L; + break; + case IIO_MAGN: + base_reg = KMX61_MAG_XOUT_L; + break; + default: + return -EINVAL; + } + mutex_lock(&data->lock); + + ret = kmx61_set_power_state(data, true, chan->address); + if (ret) { + mutex_unlock(&data->lock); + return ret; + } + + ret = kmx61_read_measurement(data, base_reg, chan->scan_index); + if (ret < 0) { + kmx61_set_power_state(data, false, chan->address); + mutex_unlock(&data->lock); + return ret; + } + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + ret = kmx61_set_power_state(data, false, chan->address); + + mutex_unlock(&data->lock); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + *val = 0; + *val2 = kmx61_uscale_table[data->range]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MAGN: + /* 14 bits res, 1465 microGauss per magn count */ + *val = 0; + *val2 = 1465; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->type != IIO_ACCEL && chan->type != IIO_MAGN) + return -EINVAL; + + mutex_lock(&data->lock); + ret = kmx61_get_odr(data, val, val2, chan->address); + mutex_unlock(&data->lock); + if (ret) + return -EINVAL; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int kmx61_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + int ret; + struct kmx61_data *data = kmx61_get_data(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->type != IIO_ACCEL && chan->type != IIO_MAGN) + return -EINVAL; + + mutex_lock(&data->lock); + ret = kmx61_set_odr(data, val, val2, chan->address); + mutex_unlock(&data->lock); + return ret; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + if (val != 0) + return -EINVAL; + mutex_lock(&data->lock); + ret = kmx61_set_scale(data, val2); + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int kmx61_read_event(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 kmx61_data *data = kmx61_get_data(indio_dev); + + *val2 = 0; + switch (info) { + case IIO_EV_INFO_VALUE: + *val = data->wake_thresh; + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + *val = data->wake_duration; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int kmx61_write_event(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 kmx61_data *data = kmx61_get_data(indio_dev); + + if (data->ev_enable_state) + return -EBUSY; + + switch (info) { + case IIO_EV_INFO_VALUE: + data->wake_thresh = val; + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + data->wake_duration = val; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int kmx61_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 kmx61_data *data = kmx61_get_data(indio_dev); + + return data->ev_enable_state; +} + +static int kmx61_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 kmx61_data *data = kmx61_get_data(indio_dev); + int ret = 0; + + if (state && data->ev_enable_state) + return 0; + + mutex_lock(&data->lock); + + if (!state && data->motion_trig_on) { + data->ev_enable_state = false; + goto err_unlock; + } + + ret = kmx61_set_power_state(data, state, KMX61_ACC); + if (ret < 0) + goto err_unlock; + + ret = kmx61_setup_any_motion_interrupt(data, state); + if (ret < 0) { + kmx61_set_power_state(data, false, KMX61_ACC); + goto err_unlock; + } + + data->ev_enable_state = state; + +err_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static int kmx61_acc_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct kmx61_data *data = kmx61_get_data(indio_dev); + + if (data->acc_dready_trig != trig && data->motion_trig != trig) + return -EINVAL; + + return 0; +} + +static int kmx61_mag_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + struct kmx61_data *data = kmx61_get_data(indio_dev); + + if (data->mag_dready_trig != trig) + return -EINVAL; + + return 0; +} + +static const struct iio_info kmx61_acc_info = { + .driver_module = THIS_MODULE, + .read_raw = kmx61_read_raw, + .write_raw = kmx61_write_raw, + .attrs = &kmx61_acc_attribute_group, + .read_event_value = kmx61_read_event, + .write_event_value = kmx61_write_event, + .read_event_config = kmx61_read_event_config, + .write_event_config = kmx61_write_event_config, + .validate_trigger = kmx61_acc_validate_trigger, +}; + +static const struct iio_info kmx61_mag_info = { + .driver_module = THIS_MODULE, + .read_raw = kmx61_read_raw, + .write_raw = kmx61_write_raw, + .attrs = &kmx61_mag_attribute_group, + .validate_trigger = kmx61_mag_validate_trigger, +}; + + +static int kmx61_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + int ret = 0; + u8 device; + + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct kmx61_data *data = kmx61_get_data(indio_dev); + + mutex_lock(&data->lock); + + if (!state && data->ev_enable_state && data->motion_trig_on) { + data->motion_trig_on = false; + goto err_unlock; + } + + if (data->acc_dready_trig == trig || data->motion_trig == trig) + device = KMX61_ACC; + else + device = KMX61_MAG; + + ret = kmx61_set_power_state(data, state, device); + if (ret < 0) + goto err_unlock; + + if (data->acc_dready_trig == trig || data->mag_dready_trig == trig) + ret = kmx61_setup_new_data_interrupt(data, state, device); + else + ret = kmx61_setup_any_motion_interrupt(data, state); + if (ret < 0) { + kmx61_set_power_state(data, false, device); + goto err_unlock; + } + + if (data->acc_dready_trig == trig) + data->acc_dready_trig_on = state; + else if (data->mag_dready_trig == trig) + data->mag_dready_trig_on = state; + else + data->motion_trig_on = state; +err_unlock: + mutex_unlock(&data->lock); + + return ret; +} + +static int kmx61_trig_try_reenable(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct kmx61_data *data = kmx61_get_data(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INL); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_inl\n"); + return ret; + } + + return 0; +} + +static const struct iio_trigger_ops kmx61_trigger_ops = { + .set_trigger_state = kmx61_data_rdy_trigger_set_state, + .try_reenable = kmx61_trig_try_reenable, + .owner = THIS_MODULE, +}; + +static irqreturn_t kmx61_event_handler(int irq, void *private) +{ + struct kmx61_data *data = private; + struct iio_dev *indio_dev = data->acc_indio_dev; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INS1); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ins1\n"); + goto ack_intr; + } + + if (ret & KMX61_REG_INS1_BIT_WUFS) { + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INS2); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_ins2\n"); + goto ack_intr; + } + + if (ret & KMX61_REG_INS2_BIT_XN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + 0); + + if (ret & KMX61_REG_INS2_BIT_XP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + 0); + + if (ret & KMX61_REG_INS2_BIT_YN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + 0); + + if (ret & KMX61_REG_INS2_BIT_YP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + 0); + + if (ret & KMX61_REG_INS2_BIT_ZN) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + 0); + + if (ret & KMX61_REG_INS2_BIT_ZP) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + 0); + } + +ack_intr: + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_CTRL1); + if (ret < 0) + dev_err(&data->client->dev, "Error reading reg_ctrl1\n"); + + ret |= KMX61_REG_CTRL1_BIT_RES; + ret = i2c_smbus_write_byte_data(data->client, KMX61_REG_CTRL1, ret); + if (ret < 0) + dev_err(&data->client->dev, "Error writing reg_ctrl1\n"); + + ret = i2c_smbus_read_byte_data(data->client, KMX61_REG_INL); + if (ret < 0) + dev_err(&data->client->dev, "Error reading reg_inl\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t kmx61_data_rdy_trig_poll(int irq, void *private) +{ + struct kmx61_data *data = private; + + if (data->acc_dready_trig_on) + iio_trigger_poll(data->acc_dready_trig); + if (data->mag_dready_trig_on) + iio_trigger_poll(data->mag_dready_trig); + + if (data->motion_trig_on) + iio_trigger_poll(data->motion_trig); + + if (data->ev_enable_state) + return IRQ_WAKE_THREAD; + return IRQ_HANDLED; +} + +static irqreturn_t kmx61_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct kmx61_data *data = kmx61_get_data(indio_dev); + int bit, ret, i = 0; + u8 base; + s16 buffer[8]; + + if (indio_dev == data->acc_indio_dev) + base = KMX61_ACC_XOUT_L; + else + base = KMX61_MAG_XOUT_L; + + mutex_lock(&data->lock); + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = kmx61_read_measurement(data, base, bit); + if (ret < 0) { + mutex_unlock(&data->lock); + goto err; + } + buffer[i++] = ret; + } + mutex_unlock(&data->lock); + + iio_push_to_buffers(indio_dev, buffer); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const char *kmx61_match_acpi_device(struct device *dev) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + return dev_name(dev); +} + +static int kmx61_gpio_probe(struct i2c_client *client, struct kmx61_data *data) +{ + struct device *dev; + struct gpio_desc *gpio; + int ret; + + if (!client) + return -EINVAL; + + dev = &client->dev; + + /* data ready gpio interrupt pin */ + gpio = devm_gpiod_get_index(dev, KMX61_GPIO_NAME, 0, GPIOD_IN); + if (IS_ERR(gpio)) { + dev_err(dev, "acpi gpio get index failed\n"); + return PTR_ERR(gpio); + } + + ret = gpiod_to_irq(gpio); + + dev_dbg(dev, "GPIO resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret); + return ret; +} + +static struct iio_dev *kmx61_indiodev_setup(struct kmx61_data *data, + const struct iio_info *info, + const struct iio_chan_spec *chan, + int num_channels, + const char *name) +{ + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&data->client->dev, sizeof(data)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + + kmx61_set_data(indio_dev, data); + + indio_dev->dev.parent = &data->client->dev; + indio_dev->channels = chan; + indio_dev->num_channels = num_channels; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = info; + + return indio_dev; +} + +static struct iio_trigger *kmx61_trigger_setup(struct kmx61_data *data, + struct iio_dev *indio_dev, + const char *tag) +{ + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(&data->client->dev, + "%s-%s-dev%d", + indio_dev->name, + tag, + indio_dev->id); + if (!trig) + return ERR_PTR(-ENOMEM); + + trig->dev.parent = &data->client->dev; + trig->ops = &kmx61_trigger_ops; + iio_trigger_set_drvdata(trig, indio_dev); + + ret = iio_trigger_register(trig); + if (ret) + return ERR_PTR(ret); + + return trig; +} + +static int kmx61_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct kmx61_data *data; + const char *name = NULL; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + + mutex_init(&data->lock); + + if (id) + name = id->name; + else if (ACPI_HANDLE(&client->dev)) + name = kmx61_match_acpi_device(&client->dev); + else + return -ENODEV; + + data->acc_indio_dev = + kmx61_indiodev_setup(data, &kmx61_acc_info, + kmx61_acc_channels, + ARRAY_SIZE(kmx61_acc_channels), + name); + if (IS_ERR(data->acc_indio_dev)) + return PTR_ERR(data->acc_indio_dev); + + data->mag_indio_dev = + kmx61_indiodev_setup(data, &kmx61_mag_info, + kmx61_mag_channels, + ARRAY_SIZE(kmx61_mag_channels), + name); + if (IS_ERR(data->mag_indio_dev)) + return PTR_ERR(data->mag_indio_dev); + + ret = kmx61_chip_init(data); + if (ret < 0) + return ret; + + if (client->irq < 0) + client->irq = kmx61_gpio_probe(client, data); + + if (client->irq >= 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + kmx61_data_rdy_trig_poll, + kmx61_event_handler, + IRQF_TRIGGER_RISING, + KMX61_IRQ_NAME, + data); + if (ret) + goto err_chip_uninit; + + data->acc_dready_trig = + kmx61_trigger_setup(data, data->acc_indio_dev, + "dready"); + if (IS_ERR(data->acc_dready_trig)) { + ret = PTR_ERR(data->acc_dready_trig); + goto err_chip_uninit; + } + + data->mag_dready_trig = + kmx61_trigger_setup(data, data->mag_indio_dev, + "dready"); + if (IS_ERR(data->mag_dready_trig)) { + ret = PTR_ERR(data->mag_dready_trig); + goto err_trigger_unregister_acc_dready; + } + + data->motion_trig = + kmx61_trigger_setup(data, data->acc_indio_dev, + "any-motion"); + if (IS_ERR(data->motion_trig)) { + ret = PTR_ERR(data->motion_trig); + goto err_trigger_unregister_mag_dready; + } + + ret = iio_triggered_buffer_setup(data->acc_indio_dev, + &iio_pollfunc_store_time, + kmx61_trigger_handler, + NULL); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed to setup acc triggered buffer\n"); + goto err_trigger_unregister_motion; + } + + ret = iio_triggered_buffer_setup(data->mag_indio_dev, + &iio_pollfunc_store_time, + kmx61_trigger_handler, + NULL); + if (ret < 0) { + dev_err(&data->client->dev, + "Failed to setup mag triggered buffer\n"); + goto err_buffer_cleanup_acc; + } + } + + ret = iio_device_register(data->acc_indio_dev); + if (ret < 0) { + dev_err(&client->dev, "Failed to register acc iio device\n"); + goto err_buffer_cleanup_mag; + } + + ret = iio_device_register(data->mag_indio_dev); + if (ret < 0) { + dev_err(&client->dev, "Failed to register mag iio device\n"); + goto err_iio_unregister_acc; + } + + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto err_iio_unregister_mag; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, KMX61_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + + return 0; + +err_iio_unregister_mag: + iio_device_unregister(data->mag_indio_dev); +err_iio_unregister_acc: + iio_device_unregister(data->acc_indio_dev); +err_buffer_cleanup_mag: + if (client->irq >= 0) + iio_triggered_buffer_cleanup(data->mag_indio_dev); +err_buffer_cleanup_acc: + if (client->irq >= 0) + iio_triggered_buffer_cleanup(data->acc_indio_dev); +err_trigger_unregister_motion: + iio_trigger_unregister(data->motion_trig); +err_trigger_unregister_mag_dready: + iio_trigger_unregister(data->mag_dready_trig); +err_trigger_unregister_acc_dready: + iio_trigger_unregister(data->acc_dready_trig); +err_chip_uninit: + kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, true); + return ret; +} + +static int kmx61_remove(struct i2c_client *client) +{ + struct kmx61_data *data = i2c_get_clientdata(client); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + iio_device_unregister(data->acc_indio_dev); + iio_device_unregister(data->mag_indio_dev); + + if (client->irq >= 0) { + iio_triggered_buffer_cleanup(data->acc_indio_dev); + iio_triggered_buffer_cleanup(data->mag_indio_dev); + iio_trigger_unregister(data->acc_dready_trig); + iio_trigger_unregister(data->mag_dready_trig); + iio_trigger_unregister(data->motion_trig); + } + + mutex_lock(&data->lock); + kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, true); + mutex_unlock(&data->lock); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int kmx61_suspend(struct device *dev) +{ + int ret; + struct kmx61_data *data = i2c_get_clientdata(to_i2c_client(dev)); + + mutex_lock(&data->lock); + ret = kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, + false); + mutex_unlock(&data->lock); + + return ret; +} + +static int kmx61_resume(struct device *dev) +{ + u8 stby = 0; + struct kmx61_data *data = i2c_get_clientdata(to_i2c_client(dev)); + + if (data->acc_stby) + stby |= KMX61_ACC_STBY_BIT; + if (data->mag_stby) + stby |= KMX61_MAG_STBY_BIT; + + return kmx61_set_mode(data, stby, KMX61_ACC | KMX61_MAG, true); +} +#endif + +#ifdef CONFIG_PM +static int kmx61_runtime_suspend(struct device *dev) +{ + struct kmx61_data *data = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + mutex_lock(&data->lock); + ret = kmx61_set_mode(data, KMX61_ALL_STBY, KMX61_ACC | KMX61_MAG, true); + mutex_unlock(&data->lock); + + return ret; +} + +static int kmx61_runtime_resume(struct device *dev) +{ + struct kmx61_data *data = i2c_get_clientdata(to_i2c_client(dev)); + u8 stby = 0; + + if (!data->acc_ps) + stby |= KMX61_ACC_STBY_BIT; + if (!data->mag_ps) + stby |= KMX61_MAG_STBY_BIT; + + return kmx61_set_mode(data, stby, KMX61_ACC | KMX61_MAG, true); +} +#endif + +static const struct dev_pm_ops kmx61_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(kmx61_suspend, kmx61_resume) + SET_RUNTIME_PM_OPS(kmx61_runtime_suspend, kmx61_runtime_resume, NULL) +}; + +static const struct acpi_device_id kmx61_acpi_match[] = { + {"KMX61021", 0}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, kmx61_acpi_match); + +static const struct i2c_device_id kmx61_id[] = { + {"kmx611021", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, kmx61_id); + +static struct i2c_driver kmx61_driver = { + .driver = { + .name = KMX61_DRV_NAME, + .acpi_match_table = ACPI_PTR(kmx61_acpi_match), + .pm = &kmx61_pm_ops, + }, + .probe = kmx61_probe, + .remove = kmx61_remove, + .id_table = kmx61_id, +}; + +module_i2c_driver(kmx61_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("KMX61 accelerometer/magnetometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c new file mode 100644 index 000000000..df919f44d --- /dev/null +++ b/drivers/iio/industrialio-buffer.c @@ -0,0 +1,1262 @@ +/* The industrial I/O core + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Handling of buffer allocation / resizing. + * + * + * Things to look at here. + * - Better memory allocation techniques? + * - Alternative access techniques? + */ +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/sched.h> + +#include <linux/iio/iio.h> +#include "iio_core.h" +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> + +static const char * const iio_endian_prefix[] = { + [IIO_BE] = "be", + [IIO_LE] = "le", +}; + +static bool iio_buffer_is_active(struct iio_buffer *buf) +{ + return !list_empty(&buf->buffer_list); +} + +static size_t iio_buffer_data_available(struct iio_buffer *buf) +{ + return buf->access->data_available(buf); +} + +static int iio_buffer_flush_hwfifo(struct iio_dev *indio_dev, + struct iio_buffer *buf, size_t required) +{ + if (!indio_dev->info->hwfifo_flush_to_buffer) + return -ENODEV; + + return indio_dev->info->hwfifo_flush_to_buffer(indio_dev, required); +} + +static bool iio_buffer_ready(struct iio_dev *indio_dev, struct iio_buffer *buf, + size_t to_wait, int to_flush) +{ + size_t avail; + int flushed = 0; + + /* wakeup if the device was unregistered */ + if (!indio_dev->info) + return true; + + /* drain the buffer if it was disabled */ + if (!iio_buffer_is_active(buf)) { + to_wait = min_t(size_t, to_wait, 1); + to_flush = 0; + } + + avail = iio_buffer_data_available(buf); + + if (avail >= to_wait) { + /* force a flush for non-blocking reads */ + if (!to_wait && !avail && to_flush) + iio_buffer_flush_hwfifo(indio_dev, buf, to_flush); + return true; + } + + if (to_flush) + flushed = iio_buffer_flush_hwfifo(indio_dev, buf, + to_wait - avail); + if (flushed <= 0) + return false; + + if (avail + flushed >= to_wait) + return true; + + return false; +} + +/** + * iio_buffer_read_first_n_outer() - chrdev read for buffer access + * + * This function relies on all buffer implementations having an + * iio_buffer as their first element. + **/ +ssize_t iio_buffer_read_first_n_outer(struct file *filp, char __user *buf, + size_t n, loff_t *f_ps) +{ + struct iio_dev *indio_dev = filp->private_data; + struct iio_buffer *rb = indio_dev->buffer; + size_t datum_size; + size_t to_wait = 0; + size_t to_read; + int ret; + + if (!indio_dev->info) + return -ENODEV; + + if (!rb || !rb->access->read_first_n) + return -EINVAL; + + datum_size = rb->bytes_per_datum; + + /* + * If datum_size is 0 there will never be anything to read from the + * buffer, so signal end of file now. + */ + if (!datum_size) + return 0; + + to_read = min_t(size_t, n / datum_size, rb->watermark); + + if (!(filp->f_flags & O_NONBLOCK)) + to_wait = to_read; + + do { + ret = wait_event_interruptible(rb->pollq, + iio_buffer_ready(indio_dev, rb, to_wait, to_read)); + if (ret) + return ret; + + if (!indio_dev->info) + return -ENODEV; + + ret = rb->access->read_first_n(rb, n, buf); + if (ret == 0 && (filp->f_flags & O_NONBLOCK)) + ret = -EAGAIN; + } while (ret == 0); + + return ret; +} + +/** + * iio_buffer_poll() - poll the buffer to find out if it has data + */ +unsigned int iio_buffer_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct iio_dev *indio_dev = filp->private_data; + struct iio_buffer *rb = indio_dev->buffer; + + if (!indio_dev->info) + return -ENODEV; + + poll_wait(filp, &rb->pollq, wait); + if (iio_buffer_ready(indio_dev, rb, rb->watermark, 0)) + return POLLIN | POLLRDNORM; + return 0; +} + +/** + * iio_buffer_wakeup_poll - Wakes up the buffer waitqueue + * @indio_dev: The IIO device + * + * Wakes up the event waitqueue used for poll(). Should usually + * be called when the device is unregistered. + */ +void iio_buffer_wakeup_poll(struct iio_dev *indio_dev) +{ + if (!indio_dev->buffer) + return; + + wake_up(&indio_dev->buffer->pollq); +} + +void iio_buffer_init(struct iio_buffer *buffer) +{ + INIT_LIST_HEAD(&buffer->demux_list); + INIT_LIST_HEAD(&buffer->buffer_list); + init_waitqueue_head(&buffer->pollq); + kref_init(&buffer->ref); + buffer->watermark = 1; +} +EXPORT_SYMBOL(iio_buffer_init); + +static ssize_t iio_show_scan_index(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", to_iio_dev_attr(attr)->c->scan_index); +} + +static ssize_t iio_show_fixed_type(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + u8 type = this_attr->c->scan_type.endianness; + + if (type == IIO_CPU) { +#ifdef __LITTLE_ENDIAN + type = IIO_LE; +#else + type = IIO_BE; +#endif + } + if (this_attr->c->scan_type.repeat > 1) + return sprintf(buf, "%s:%c%d/%dX%d>>%u\n", + iio_endian_prefix[type], + this_attr->c->scan_type.sign, + this_attr->c->scan_type.realbits, + this_attr->c->scan_type.storagebits, + this_attr->c->scan_type.repeat, + this_attr->c->scan_type.shift); + else + return sprintf(buf, "%s:%c%d/%d>>%u\n", + iio_endian_prefix[type], + this_attr->c->scan_type.sign, + this_attr->c->scan_type.realbits, + this_attr->c->scan_type.storagebits, + this_attr->c->scan_type.shift); +} + +static ssize_t iio_scan_el_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + + /* Ensure ret is 0 or 1. */ + ret = !!test_bit(to_iio_dev_attr(attr)->address, + indio_dev->buffer->scan_mask); + + return sprintf(buf, "%d\n", ret); +} + +/* Note NULL used as error indicator as it doesn't make sense. */ +static const unsigned long *iio_scan_mask_match(const unsigned long *av_masks, + unsigned int masklength, + const unsigned long *mask) +{ + if (bitmap_empty(mask, masklength)) + return NULL; + while (*av_masks) { + if (bitmap_subset(mask, av_masks, masklength)) + return av_masks; + av_masks += BITS_TO_LONGS(masklength); + } + return NULL; +} + +static bool iio_validate_scan_mask(struct iio_dev *indio_dev, + const unsigned long *mask) +{ + if (!indio_dev->setup_ops->validate_scan_mask) + return true; + + return indio_dev->setup_ops->validate_scan_mask(indio_dev, mask); +} + +/** + * iio_scan_mask_set() - set particular bit in the scan mask + * @indio_dev: the iio device + * @buffer: the buffer whose scan mask we are interested in + * @bit: the bit to be set. + * + * Note that at this point we have no way of knowing what other + * buffers might request, hence this code only verifies that the + * individual buffers request is plausible. + */ +static int iio_scan_mask_set(struct iio_dev *indio_dev, + struct iio_buffer *buffer, int bit) +{ + const unsigned long *mask; + unsigned long *trialmask; + + trialmask = kmalloc(sizeof(*trialmask)* + BITS_TO_LONGS(indio_dev->masklength), + GFP_KERNEL); + + if (trialmask == NULL) + return -ENOMEM; + if (!indio_dev->masklength) { + WARN_ON("Trying to set scanmask prior to registering buffer\n"); + goto err_invalid_mask; + } + bitmap_copy(trialmask, buffer->scan_mask, indio_dev->masklength); + set_bit(bit, trialmask); + + if (!iio_validate_scan_mask(indio_dev, trialmask)) + goto err_invalid_mask; + + if (indio_dev->available_scan_masks) { + mask = iio_scan_mask_match(indio_dev->available_scan_masks, + indio_dev->masklength, + trialmask); + if (!mask) + goto err_invalid_mask; + } + bitmap_copy(buffer->scan_mask, trialmask, indio_dev->masklength); + + kfree(trialmask); + + return 0; + +err_invalid_mask: + kfree(trialmask); + return -EINVAL; +} + +static int iio_scan_mask_clear(struct iio_buffer *buffer, int bit) +{ + clear_bit(bit, buffer->scan_mask); + return 0; +} + +static ssize_t iio_scan_el_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + bool state; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_buffer *buffer = indio_dev->buffer; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = strtobool(buf, &state); + if (ret < 0) + return ret; + mutex_lock(&indio_dev->mlock); + if (iio_buffer_is_active(indio_dev->buffer)) { + ret = -EBUSY; + goto error_ret; + } + ret = iio_scan_mask_query(indio_dev, buffer, this_attr->address); + if (ret < 0) + goto error_ret; + if (!state && ret) { + ret = iio_scan_mask_clear(buffer, this_attr->address); + if (ret) + goto error_ret; + } else if (state && !ret) { + ret = iio_scan_mask_set(indio_dev, buffer, this_attr->address); + if (ret) + goto error_ret; + } + +error_ret: + mutex_unlock(&indio_dev->mlock); + + return ret < 0 ? ret : len; + +} + +static ssize_t iio_scan_el_ts_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + return sprintf(buf, "%d\n", indio_dev->buffer->scan_timestamp); +} + +static ssize_t iio_scan_el_ts_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool state; + + ret = strtobool(buf, &state); + if (ret < 0) + return ret; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_is_active(indio_dev->buffer)) { + ret = -EBUSY; + goto error_ret; + } + indio_dev->buffer->scan_timestamp = state; +error_ret: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static int iio_buffer_add_channel_sysfs(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + int ret, attrcount = 0; + struct iio_buffer *buffer = indio_dev->buffer; + + ret = __iio_add_chan_devattr("index", + chan, + &iio_show_scan_index, + NULL, + 0, + IIO_SEPARATE, + &indio_dev->dev, + &buffer->scan_el_dev_attr_list); + if (ret) + return ret; + attrcount++; + ret = __iio_add_chan_devattr("type", + chan, + &iio_show_fixed_type, + NULL, + 0, + 0, + &indio_dev->dev, + &buffer->scan_el_dev_attr_list); + if (ret) + return ret; + attrcount++; + if (chan->type != IIO_TIMESTAMP) + ret = __iio_add_chan_devattr("en", + chan, + &iio_scan_el_show, + &iio_scan_el_store, + chan->scan_index, + 0, + &indio_dev->dev, + &buffer->scan_el_dev_attr_list); + else + ret = __iio_add_chan_devattr("en", + chan, + &iio_scan_el_ts_show, + &iio_scan_el_ts_store, + chan->scan_index, + 0, + &indio_dev->dev, + &buffer->scan_el_dev_attr_list); + if (ret) + return ret; + attrcount++; + ret = attrcount; + return ret; +} + +static ssize_t iio_buffer_read_length(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_buffer *buffer = indio_dev->buffer; + + return sprintf(buf, "%d\n", buffer->length); +} + +static ssize_t iio_buffer_write_length(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_buffer *buffer = indio_dev->buffer; + unsigned int val; + int ret; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + if (val == buffer->length) + return len; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_is_active(indio_dev->buffer)) { + ret = -EBUSY; + } else { + buffer->access->set_length(buffer, val); + ret = 0; + } + if (ret) + goto out; + if (buffer->length && buffer->length < buffer->watermark) + buffer->watermark = buffer->length; +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static ssize_t iio_buffer_show_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + return sprintf(buf, "%d\n", iio_buffer_is_active(indio_dev->buffer)); +} + +static int iio_compute_scan_bytes(struct iio_dev *indio_dev, + const unsigned long *mask, bool timestamp) +{ + const struct iio_chan_spec *ch; + unsigned bytes = 0; + int length, i; + + /* How much space will the demuxed element take? */ + for_each_set_bit(i, mask, + indio_dev->masklength) { + ch = iio_find_channel_from_si(indio_dev, i); + if (ch->scan_type.repeat > 1) + length = ch->scan_type.storagebits / 8 * + ch->scan_type.repeat; + else + length = ch->scan_type.storagebits / 8; + bytes = ALIGN(bytes, length); + bytes += length; + } + if (timestamp) { + ch = iio_find_channel_from_si(indio_dev, + indio_dev->scan_index_timestamp); + if (ch->scan_type.repeat > 1) + length = ch->scan_type.storagebits / 8 * + ch->scan_type.repeat; + else + length = ch->scan_type.storagebits / 8; + bytes = ALIGN(bytes, length); + bytes += length; + } + return bytes; +} + +static void iio_buffer_activate(struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + iio_buffer_get(buffer); + list_add(&buffer->buffer_list, &indio_dev->buffer_list); +} + +static void iio_buffer_deactivate(struct iio_buffer *buffer) +{ + list_del_init(&buffer->buffer_list); + wake_up_interruptible(&buffer->pollq); + iio_buffer_put(buffer); +} + +void iio_disable_all_buffers(struct iio_dev *indio_dev) +{ + struct iio_buffer *buffer, *_buffer; + + if (list_empty(&indio_dev->buffer_list)) + return; + + if (indio_dev->setup_ops->predisable) + indio_dev->setup_ops->predisable(indio_dev); + + list_for_each_entry_safe(buffer, _buffer, + &indio_dev->buffer_list, buffer_list) + iio_buffer_deactivate(buffer); + + indio_dev->currentmode = INDIO_DIRECT_MODE; + if (indio_dev->setup_ops->postdisable) + indio_dev->setup_ops->postdisable(indio_dev); + + if (indio_dev->available_scan_masks == NULL) + kfree(indio_dev->active_scan_mask); +} + +static void iio_buffer_update_bytes_per_datum(struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + unsigned int bytes; + + if (!buffer->access->set_bytes_per_datum) + return; + + bytes = iio_compute_scan_bytes(indio_dev, buffer->scan_mask, + buffer->scan_timestamp); + + buffer->access->set_bytes_per_datum(buffer, bytes); +} + +static int __iio_update_buffers(struct iio_dev *indio_dev, + struct iio_buffer *insert_buffer, + struct iio_buffer *remove_buffer) +{ + int ret; + int success = 0; + struct iio_buffer *buffer; + unsigned long *compound_mask; + const unsigned long *old_mask; + + /* Wind down existing buffers - iff there are any */ + if (!list_empty(&indio_dev->buffer_list)) { + if (indio_dev->setup_ops->predisable) { + ret = indio_dev->setup_ops->predisable(indio_dev); + if (ret) + return ret; + } + indio_dev->currentmode = INDIO_DIRECT_MODE; + if (indio_dev->setup_ops->postdisable) { + ret = indio_dev->setup_ops->postdisable(indio_dev); + if (ret) + return ret; + } + } + /* Keep a copy of current setup to allow roll back */ + old_mask = indio_dev->active_scan_mask; + if (!indio_dev->available_scan_masks) + indio_dev->active_scan_mask = NULL; + + if (remove_buffer) + iio_buffer_deactivate(remove_buffer); + if (insert_buffer) + iio_buffer_activate(indio_dev, insert_buffer); + + /* If no buffers in list, we are done */ + if (list_empty(&indio_dev->buffer_list)) { + indio_dev->currentmode = INDIO_DIRECT_MODE; + if (indio_dev->available_scan_masks == NULL) + kfree(old_mask); + return 0; + } + + /* What scan mask do we actually have? */ + compound_mask = kcalloc(BITS_TO_LONGS(indio_dev->masklength), + sizeof(long), GFP_KERNEL); + if (compound_mask == NULL) { + if (indio_dev->available_scan_masks == NULL) + kfree(old_mask); + return -ENOMEM; + } + indio_dev->scan_timestamp = 0; + + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) { + bitmap_or(compound_mask, compound_mask, buffer->scan_mask, + indio_dev->masklength); + indio_dev->scan_timestamp |= buffer->scan_timestamp; + } + if (indio_dev->available_scan_masks) { + indio_dev->active_scan_mask = + iio_scan_mask_match(indio_dev->available_scan_masks, + indio_dev->masklength, + compound_mask); + if (indio_dev->active_scan_mask == NULL) { + /* + * Roll back. + * Note can only occur when adding a buffer. + */ + iio_buffer_deactivate(insert_buffer); + if (old_mask) { + indio_dev->active_scan_mask = old_mask; + success = -EINVAL; + } + else { + kfree(compound_mask); + ret = -EINVAL; + return ret; + } + } + } else { + indio_dev->active_scan_mask = compound_mask; + } + + iio_update_demux(indio_dev); + + /* Wind up again */ + if (indio_dev->setup_ops->preenable) { + ret = indio_dev->setup_ops->preenable(indio_dev); + if (ret) { + printk(KERN_ERR + "Buffer not started: buffer preenable failed (%d)\n", ret); + goto error_remove_inserted; + } + } + indio_dev->scan_bytes = + iio_compute_scan_bytes(indio_dev, + indio_dev->active_scan_mask, + indio_dev->scan_timestamp); + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) { + iio_buffer_update_bytes_per_datum(indio_dev, buffer); + if (buffer->access->request_update) { + ret = buffer->access->request_update(buffer); + if (ret) { + printk(KERN_INFO + "Buffer not started: buffer parameter update failed (%d)\n", ret); + goto error_run_postdisable; + } + } + } + if (indio_dev->info->update_scan_mode) { + ret = indio_dev->info + ->update_scan_mode(indio_dev, + indio_dev->active_scan_mask); + if (ret < 0) { + printk(KERN_INFO "Buffer not started: update scan mode failed (%d)\n", ret); + goto error_run_postdisable; + } + } + /* Definitely possible for devices to support both of these. */ + if ((indio_dev->modes & INDIO_BUFFER_TRIGGERED) && indio_dev->trig) { + indio_dev->currentmode = INDIO_BUFFER_TRIGGERED; + } else if (indio_dev->modes & INDIO_BUFFER_HARDWARE) { + indio_dev->currentmode = INDIO_BUFFER_HARDWARE; + } else if (indio_dev->modes & INDIO_BUFFER_SOFTWARE) { + indio_dev->currentmode = INDIO_BUFFER_SOFTWARE; + } else { /* Should never be reached */ + /* Can only occur on first buffer */ + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + pr_info("Buffer not started: no trigger\n"); + ret = -EINVAL; + goto error_run_postdisable; + } + + if (indio_dev->setup_ops->postenable) { + ret = indio_dev->setup_ops->postenable(indio_dev); + if (ret) { + printk(KERN_INFO + "Buffer not started: postenable failed (%d)\n", ret); + indio_dev->currentmode = INDIO_DIRECT_MODE; + if (indio_dev->setup_ops->postdisable) + indio_dev->setup_ops->postdisable(indio_dev); + goto error_disable_all_buffers; + } + } + + if (indio_dev->available_scan_masks) + kfree(compound_mask); + else + kfree(old_mask); + + return success; + +error_disable_all_buffers: + indio_dev->currentmode = INDIO_DIRECT_MODE; +error_run_postdisable: + if (indio_dev->setup_ops->postdisable) + indio_dev->setup_ops->postdisable(indio_dev); +error_remove_inserted: + if (insert_buffer) + iio_buffer_deactivate(insert_buffer); + indio_dev->active_scan_mask = old_mask; + kfree(compound_mask); + return ret; +} + +int iio_update_buffers(struct iio_dev *indio_dev, + struct iio_buffer *insert_buffer, + struct iio_buffer *remove_buffer) +{ + int ret; + + if (insert_buffer == remove_buffer) + return 0; + + mutex_lock(&indio_dev->info_exist_lock); + mutex_lock(&indio_dev->mlock); + + if (insert_buffer && iio_buffer_is_active(insert_buffer)) + insert_buffer = NULL; + + if (remove_buffer && !iio_buffer_is_active(remove_buffer)) + remove_buffer = NULL; + + if (!insert_buffer && !remove_buffer) { + ret = 0; + goto out_unlock; + } + + if (indio_dev->info == NULL) { + ret = -ENODEV; + goto out_unlock; + } + + ret = __iio_update_buffers(indio_dev, insert_buffer, remove_buffer); + +out_unlock: + mutex_unlock(&indio_dev->mlock); + mutex_unlock(&indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_update_buffers); + +static ssize_t iio_buffer_store_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + bool requested_state; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool inlist; + + ret = strtobool(buf, &requested_state); + if (ret < 0) + return ret; + + mutex_lock(&indio_dev->mlock); + + /* Find out if it is in the list */ + inlist = iio_buffer_is_active(indio_dev->buffer); + /* Already in desired state */ + if (inlist == requested_state) + goto done; + + if (requested_state) + ret = __iio_update_buffers(indio_dev, + indio_dev->buffer, NULL); + else + ret = __iio_update_buffers(indio_dev, + NULL, indio_dev->buffer); + + if (ret < 0) + goto done; +done: + mutex_unlock(&indio_dev->mlock); + return (ret < 0) ? ret : len; +} + +static const char * const iio_scan_elements_group_name = "scan_elements"; + +static ssize_t iio_buffer_show_watermark(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_buffer *buffer = indio_dev->buffer; + + return sprintf(buf, "%u\n", buffer->watermark); +} + +static ssize_t iio_buffer_store_watermark(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_buffer *buffer = indio_dev->buffer; + unsigned int val; + int ret; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + if (!val) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + if (val > buffer->length) { + ret = -EINVAL; + goto out; + } + + if (iio_buffer_is_active(indio_dev->buffer)) { + ret = -EBUSY; + goto out; + } + + buffer->watermark = val; + + if (indio_dev->info->hwfifo_set_watermark) + indio_dev->info->hwfifo_set_watermark(indio_dev, val); +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static DEVICE_ATTR(length, S_IRUGO | S_IWUSR, iio_buffer_read_length, + iio_buffer_write_length); +static struct device_attribute dev_attr_length_ro = __ATTR(length, + S_IRUGO, iio_buffer_read_length, NULL); +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, + iio_buffer_show_enable, iio_buffer_store_enable); +static DEVICE_ATTR(watermark, S_IRUGO | S_IWUSR, + iio_buffer_show_watermark, iio_buffer_store_watermark); + +static struct attribute *iio_buffer_attrs[] = { + &dev_attr_length.attr, + &dev_attr_enable.attr, + &dev_attr_watermark.attr, +}; + +int iio_buffer_alloc_sysfs_and_mask(struct iio_dev *indio_dev) +{ + struct iio_dev_attr *p; + struct attribute **attr; + struct iio_buffer *buffer = indio_dev->buffer; + int ret, i, attrn, attrcount, attrcount_orig = 0; + const struct iio_chan_spec *channels; + + if (!buffer) + return 0; + + attrcount = 0; + if (buffer->attrs) { + while (buffer->attrs[attrcount] != NULL) + attrcount++; + } + + attr = kcalloc(attrcount + ARRAY_SIZE(iio_buffer_attrs) + 1, + sizeof(struct attribute *), GFP_KERNEL); + if (!attr) + return -ENOMEM; + + memcpy(attr, iio_buffer_attrs, sizeof(iio_buffer_attrs)); + if (!buffer->access->set_length) + attr[0] = &dev_attr_length_ro.attr; + + if (buffer->attrs) + memcpy(&attr[ARRAY_SIZE(iio_buffer_attrs)], buffer->attrs, + sizeof(struct attribute *) * attrcount); + + attr[attrcount + ARRAY_SIZE(iio_buffer_attrs)] = NULL; + + buffer->buffer_group.name = "buffer"; + buffer->buffer_group.attrs = attr; + + indio_dev->groups[indio_dev->groupcounter++] = &buffer->buffer_group; + + if (buffer->scan_el_attrs != NULL) { + attr = buffer->scan_el_attrs->attrs; + while (*attr++ != NULL) + attrcount_orig++; + } + attrcount = attrcount_orig; + INIT_LIST_HEAD(&buffer->scan_el_dev_attr_list); + channels = indio_dev->channels; + if (channels) { + /* new magic */ + for (i = 0; i < indio_dev->num_channels; i++) { + if (channels[i].scan_index < 0) + continue; + + /* Establish necessary mask length */ + if (channels[i].scan_index > + (int)indio_dev->masklength - 1) + indio_dev->masklength + = channels[i].scan_index + 1; + + ret = iio_buffer_add_channel_sysfs(indio_dev, + &channels[i]); + if (ret < 0) + goto error_cleanup_dynamic; + attrcount += ret; + if (channels[i].type == IIO_TIMESTAMP) + indio_dev->scan_index_timestamp = + channels[i].scan_index; + } + if (indio_dev->masklength && buffer->scan_mask == NULL) { + buffer->scan_mask = kcalloc(BITS_TO_LONGS(indio_dev->masklength), + sizeof(*buffer->scan_mask), + GFP_KERNEL); + if (buffer->scan_mask == NULL) { + ret = -ENOMEM; + goto error_cleanup_dynamic; + } + } + } + + buffer->scan_el_group.name = iio_scan_elements_group_name; + + buffer->scan_el_group.attrs = kcalloc(attrcount + 1, + sizeof(buffer->scan_el_group.attrs[0]), + GFP_KERNEL); + if (buffer->scan_el_group.attrs == NULL) { + ret = -ENOMEM; + goto error_free_scan_mask; + } + if (buffer->scan_el_attrs) + memcpy(buffer->scan_el_group.attrs, buffer->scan_el_attrs, + sizeof(buffer->scan_el_group.attrs[0])*attrcount_orig); + attrn = attrcount_orig; + + list_for_each_entry(p, &buffer->scan_el_dev_attr_list, l) + buffer->scan_el_group.attrs[attrn++] = &p->dev_attr.attr; + indio_dev->groups[indio_dev->groupcounter++] = &buffer->scan_el_group; + + return 0; + +error_free_scan_mask: + kfree(buffer->scan_mask); +error_cleanup_dynamic: + iio_free_chan_devattr_list(&buffer->scan_el_dev_attr_list); + kfree(indio_dev->buffer->buffer_group.attrs); + + return ret; +} + +void iio_buffer_free_sysfs_and_mask(struct iio_dev *indio_dev) +{ + if (!indio_dev->buffer) + return; + + kfree(indio_dev->buffer->scan_mask); + kfree(indio_dev->buffer->buffer_group.attrs); + kfree(indio_dev->buffer->scan_el_group.attrs); + iio_free_chan_devattr_list(&indio_dev->buffer->scan_el_dev_attr_list); +} + +/** + * iio_validate_scan_mask_onehot() - Validates that exactly one channel is selected + * @indio_dev: the iio device + * @mask: scan mask to be checked + * + * Return true if exactly one bit is set in the scan mask, false otherwise. It + * can be used for devices where only one channel can be active for sampling at + * a time. + */ +bool iio_validate_scan_mask_onehot(struct iio_dev *indio_dev, + const unsigned long *mask) +{ + return bitmap_weight(mask, indio_dev->masklength) == 1; +} +EXPORT_SYMBOL_GPL(iio_validate_scan_mask_onehot); + +int iio_scan_mask_query(struct iio_dev *indio_dev, + struct iio_buffer *buffer, int bit) +{ + if (bit > indio_dev->masklength) + return -EINVAL; + + if (!buffer->scan_mask) + return 0; + + /* Ensure return value is 0 or 1. */ + return !!test_bit(bit, buffer->scan_mask); +}; +EXPORT_SYMBOL_GPL(iio_scan_mask_query); + +/** + * struct iio_demux_table() - table describing demux memcpy ops + * @from: index to copy from + * @to: index to copy to + * @length: how many bytes to copy + * @l: list head used for management + */ +struct iio_demux_table { + unsigned from; + unsigned to; + unsigned length; + struct list_head l; +}; + +static const void *iio_demux(struct iio_buffer *buffer, + const void *datain) +{ + struct iio_demux_table *t; + + if (list_empty(&buffer->demux_list)) + return datain; + list_for_each_entry(t, &buffer->demux_list, l) + memcpy(buffer->demux_bounce + t->to, + datain + t->from, t->length); + + return buffer->demux_bounce; +} + +static int iio_push_to_buffer(struct iio_buffer *buffer, const void *data) +{ + const void *dataout = iio_demux(buffer, data); + int ret; + + ret = buffer->access->store_to(buffer, dataout); + if (ret) + return ret; + + /* + * We can't just test for watermark to decide if we wake the poll queue + * because read may request less samples than the watermark. + */ + wake_up_interruptible_poll(&buffer->pollq, POLLIN | POLLRDNORM); + return 0; +} + +static void iio_buffer_demux_free(struct iio_buffer *buffer) +{ + struct iio_demux_table *p, *q; + list_for_each_entry_safe(p, q, &buffer->demux_list, l) { + list_del(&p->l); + kfree(p); + } +} + + +int iio_push_to_buffers(struct iio_dev *indio_dev, const void *data) +{ + int ret; + struct iio_buffer *buf; + + list_for_each_entry(buf, &indio_dev->buffer_list, buffer_list) { + ret = iio_push_to_buffer(buf, data); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(iio_push_to_buffers); + +static int iio_buffer_add_demux(struct iio_buffer *buffer, + struct iio_demux_table **p, unsigned int in_loc, unsigned int out_loc, + unsigned int length) +{ + + if (*p && (*p)->from + (*p)->length == in_loc && + (*p)->to + (*p)->length == out_loc) { + (*p)->length += length; + } else { + *p = kmalloc(sizeof(**p), GFP_KERNEL); + if (*p == NULL) + return -ENOMEM; + (*p)->from = in_loc; + (*p)->to = out_loc; + (*p)->length = length; + list_add_tail(&(*p)->l, &buffer->demux_list); + } + + return 0; +} + +static int iio_buffer_update_demux(struct iio_dev *indio_dev, + struct iio_buffer *buffer) +{ + const struct iio_chan_spec *ch; + int ret, in_ind = -1, out_ind, length; + unsigned in_loc = 0, out_loc = 0; + struct iio_demux_table *p = NULL; + + /* Clear out any old demux */ + iio_buffer_demux_free(buffer); + kfree(buffer->demux_bounce); + buffer->demux_bounce = NULL; + + /* First work out which scan mode we will actually have */ + if (bitmap_equal(indio_dev->active_scan_mask, + buffer->scan_mask, + indio_dev->masklength)) + return 0; + + /* Now we have the two masks, work from least sig and build up sizes */ + for_each_set_bit(out_ind, + buffer->scan_mask, + indio_dev->masklength) { + in_ind = find_next_bit(indio_dev->active_scan_mask, + indio_dev->masklength, + in_ind + 1); + while (in_ind != out_ind) { + in_ind = find_next_bit(indio_dev->active_scan_mask, + indio_dev->masklength, + in_ind + 1); + ch = iio_find_channel_from_si(indio_dev, in_ind); + if (ch->scan_type.repeat > 1) + length = ch->scan_type.storagebits / 8 * + ch->scan_type.repeat; + else + length = ch->scan_type.storagebits / 8; + /* Make sure we are aligned */ + in_loc = roundup(in_loc, length) + length; + } + ch = iio_find_channel_from_si(indio_dev, in_ind); + if (ch->scan_type.repeat > 1) + length = ch->scan_type.storagebits / 8 * + ch->scan_type.repeat; + else + length = ch->scan_type.storagebits / 8; + out_loc = roundup(out_loc, length); + in_loc = roundup(in_loc, length); + ret = iio_buffer_add_demux(buffer, &p, in_loc, out_loc, length); + if (ret) + goto error_clear_mux_table; + out_loc += length; + in_loc += length; + } + /* Relies on scan_timestamp being last */ + if (buffer->scan_timestamp) { + ch = iio_find_channel_from_si(indio_dev, + indio_dev->scan_index_timestamp); + if (ch->scan_type.repeat > 1) + length = ch->scan_type.storagebits / 8 * + ch->scan_type.repeat; + else + length = ch->scan_type.storagebits / 8; + out_loc = roundup(out_loc, length); + in_loc = roundup(in_loc, length); + ret = iio_buffer_add_demux(buffer, &p, in_loc, out_loc, length); + if (ret) + goto error_clear_mux_table; + out_loc += length; + in_loc += length; + } + buffer->demux_bounce = kzalloc(out_loc, GFP_KERNEL); + if (buffer->demux_bounce == NULL) { + ret = -ENOMEM; + goto error_clear_mux_table; + } + return 0; + +error_clear_mux_table: + iio_buffer_demux_free(buffer); + + return ret; +} + +int iio_update_demux(struct iio_dev *indio_dev) +{ + struct iio_buffer *buffer; + int ret; + + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) { + ret = iio_buffer_update_demux(indio_dev, buffer); + if (ret < 0) + goto error_clear_mux_table; + } + return 0; + +error_clear_mux_table: + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) + iio_buffer_demux_free(buffer); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_update_demux); + +/** + * iio_buffer_release() - Free a buffer's resources + * @ref: Pointer to the kref embedded in the iio_buffer struct + * + * This function is called when the last reference to the buffer has been + * dropped. It will typically free all resources allocated by the buffer. Do not + * call this function manually, always use iio_buffer_put() when done using a + * buffer. + */ +static void iio_buffer_release(struct kref *ref) +{ + struct iio_buffer *buffer = container_of(ref, struct iio_buffer, ref); + + buffer->access->release(buffer); +} + +/** + * iio_buffer_get() - Grab a reference to the buffer + * @buffer: The buffer to grab a reference for, may be NULL + * + * Returns the pointer to the buffer that was passed into the function. + */ +struct iio_buffer *iio_buffer_get(struct iio_buffer *buffer) +{ + if (buffer) + kref_get(&buffer->ref); + + return buffer; +} +EXPORT_SYMBOL_GPL(iio_buffer_get); + +/** + * iio_buffer_put() - Release the reference to the buffer + * @buffer: The buffer to release the reference for, may be NULL + */ +void iio_buffer_put(struct iio_buffer *buffer) +{ + if (buffer) + kref_put(&buffer->ref, iio_buffer_release); +} +EXPORT_SYMBOL_GPL(iio_buffer_put); diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c new file mode 100644 index 000000000..4df97f650 --- /dev/null +++ b/drivers/iio/industrialio-core.c @@ -0,0 +1,1338 @@ +/* The industrial I/O core + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Based on elements of hwmon and input subsystems. + */ + +#define pr_fmt(fmt) "iio-core: " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/idr.h> +#include <linux/kdev_t.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/cdev.h> +#include <linux/slab.h> +#include <linux/anon_inodes.h> +#include <linux/debugfs.h> +#include <linux/iio/iio.h> +#include "iio_core.h" +#include "iio_core_trigger.h" +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/buffer.h> + +/* IDA to assign each registered device a unique id */ +static DEFINE_IDA(iio_ida); + +static dev_t iio_devt; + +#define IIO_DEV_MAX 256 +struct bus_type iio_bus_type = { + .name = "iio", +}; +EXPORT_SYMBOL(iio_bus_type); + +static struct dentry *iio_debugfs_dentry; + +static const char * const iio_direction[] = { + [0] = "in", + [1] = "out", +}; + +static const char * const iio_chan_type_name_spec[] = { + [IIO_VOLTAGE] = "voltage", + [IIO_CURRENT] = "current", + [IIO_POWER] = "power", + [IIO_ACCEL] = "accel", + [IIO_ANGL_VEL] = "anglvel", + [IIO_MAGN] = "magn", + [IIO_LIGHT] = "illuminance", + [IIO_INTENSITY] = "intensity", + [IIO_PROXIMITY] = "proximity", + [IIO_TEMP] = "temp", + [IIO_INCLI] = "incli", + [IIO_ROT] = "rot", + [IIO_ANGL] = "angl", + [IIO_TIMESTAMP] = "timestamp", + [IIO_CAPACITANCE] = "capacitance", + [IIO_ALTVOLTAGE] = "altvoltage", + [IIO_CCT] = "cct", + [IIO_PRESSURE] = "pressure", + [IIO_HUMIDITYRELATIVE] = "humidityrelative", + [IIO_ACTIVITY] = "activity", + [IIO_STEPS] = "steps", + [IIO_ENERGY] = "energy", + [IIO_DISTANCE] = "distance", + [IIO_VELOCITY] = "velocity", +}; + +static const char * const iio_modifier_names[] = { + [IIO_MOD_X] = "x", + [IIO_MOD_Y] = "y", + [IIO_MOD_Z] = "z", + [IIO_MOD_ROOT_SUM_SQUARED_X_Y] = "sqrt(x^2+y^2)", + [IIO_MOD_SUM_SQUARED_X_Y_Z] = "x^2+y^2+z^2", + [IIO_MOD_LIGHT_BOTH] = "both", + [IIO_MOD_LIGHT_IR] = "ir", + [IIO_MOD_LIGHT_CLEAR] = "clear", + [IIO_MOD_LIGHT_RED] = "red", + [IIO_MOD_LIGHT_GREEN] = "green", + [IIO_MOD_LIGHT_BLUE] = "blue", + [IIO_MOD_QUATERNION] = "quaternion", + [IIO_MOD_TEMP_AMBIENT] = "ambient", + [IIO_MOD_TEMP_OBJECT] = "object", + [IIO_MOD_NORTH_MAGN] = "from_north_magnetic", + [IIO_MOD_NORTH_TRUE] = "from_north_true", + [IIO_MOD_NORTH_MAGN_TILT_COMP] = "from_north_magnetic_tilt_comp", + [IIO_MOD_NORTH_TRUE_TILT_COMP] = "from_north_true_tilt_comp", + [IIO_MOD_RUNNING] = "running", + [IIO_MOD_JOGGING] = "jogging", + [IIO_MOD_WALKING] = "walking", + [IIO_MOD_STILL] = "still", + [IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z] = "sqrt(x^2+y^2+z^2)", +}; + +/* relies on pairs of these shared then separate */ +static const char * const iio_chan_info_postfix[] = { + [IIO_CHAN_INFO_RAW] = "raw", + [IIO_CHAN_INFO_PROCESSED] = "input", + [IIO_CHAN_INFO_SCALE] = "scale", + [IIO_CHAN_INFO_OFFSET] = "offset", + [IIO_CHAN_INFO_CALIBSCALE] = "calibscale", + [IIO_CHAN_INFO_CALIBBIAS] = "calibbias", + [IIO_CHAN_INFO_PEAK] = "peak_raw", + [IIO_CHAN_INFO_PEAK_SCALE] = "peak_scale", + [IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW] = "quadrature_correction_raw", + [IIO_CHAN_INFO_AVERAGE_RAW] = "mean_raw", + [IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY] + = "filter_low_pass_3db_frequency", + [IIO_CHAN_INFO_SAMP_FREQ] = "sampling_frequency", + [IIO_CHAN_INFO_FREQUENCY] = "frequency", + [IIO_CHAN_INFO_PHASE] = "phase", + [IIO_CHAN_INFO_HARDWAREGAIN] = "hardwaregain", + [IIO_CHAN_INFO_HYSTERESIS] = "hysteresis", + [IIO_CHAN_INFO_INT_TIME] = "integration_time", + [IIO_CHAN_INFO_ENABLE] = "en", + [IIO_CHAN_INFO_CALIBHEIGHT] = "calibheight", + [IIO_CHAN_INFO_CALIBWEIGHT] = "calibweight", + [IIO_CHAN_INFO_DEBOUNCE_COUNT] = "debounce_count", + [IIO_CHAN_INFO_DEBOUNCE_TIME] = "debounce_time", +}; + +/** + * iio_find_channel_from_si() - get channel from its scan index + * @indio_dev: device + * @si: scan index to match + */ +const struct iio_chan_spec +*iio_find_channel_from_si(struct iio_dev *indio_dev, int si) +{ + int i; + + for (i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].scan_index == si) + return &indio_dev->channels[i]; + return NULL; +} + +/* This turns up an awful lot */ +ssize_t iio_read_const_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", to_iio_const_attr(attr)->string); +} +EXPORT_SYMBOL(iio_read_const_attr); + +static int __init iio_init(void) +{ + int ret; + + /* Register sysfs bus */ + ret = bus_register(&iio_bus_type); + if (ret < 0) { + pr_err("could not register bus type\n"); + goto error_nothing; + } + + ret = alloc_chrdev_region(&iio_devt, 0, IIO_DEV_MAX, "iio"); + if (ret < 0) { + pr_err("failed to allocate char dev region\n"); + goto error_unregister_bus_type; + } + + iio_debugfs_dentry = debugfs_create_dir("iio", NULL); + + return 0; + +error_unregister_bus_type: + bus_unregister(&iio_bus_type); +error_nothing: + return ret; +} + +static void __exit iio_exit(void) +{ + if (iio_devt) + unregister_chrdev_region(iio_devt, IIO_DEV_MAX); + bus_unregister(&iio_bus_type); + debugfs_remove(iio_debugfs_dentry); +} + +#if defined(CONFIG_DEBUG_FS) +static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct iio_dev *indio_dev = file->private_data; + char buf[20]; + unsigned val = 0; + ssize_t len; + int ret; + + ret = indio_dev->info->debugfs_reg_access(indio_dev, + indio_dev->cached_reg_addr, + 0, &val); + if (ret) + dev_err(indio_dev->dev.parent, "%s: read failed\n", __func__); + + len = snprintf(buf, sizeof(buf), "0x%X\n", val); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static ssize_t iio_debugfs_write_reg(struct file *file, + const char __user *userbuf, size_t count, loff_t *ppos) +{ + struct iio_dev *indio_dev = file->private_data; + unsigned reg, val; + char buf[80]; + int ret; + + count = min_t(size_t, count, (sizeof(buf)-1)); + if (copy_from_user(buf, userbuf, count)) + return -EFAULT; + + buf[count] = 0; + + ret = sscanf(buf, "%i %i", ®, &val); + + switch (ret) { + case 1: + indio_dev->cached_reg_addr = reg; + break; + case 2: + indio_dev->cached_reg_addr = reg; + ret = indio_dev->info->debugfs_reg_access(indio_dev, reg, + val, NULL); + if (ret) { + dev_err(indio_dev->dev.parent, "%s: write failed\n", + __func__); + return ret; + } + break; + default: + return -EINVAL; + } + + return count; +} + +static const struct file_operations iio_debugfs_reg_fops = { + .open = simple_open, + .read = iio_debugfs_read_reg, + .write = iio_debugfs_write_reg, +}; + +static void iio_device_unregister_debugfs(struct iio_dev *indio_dev) +{ + debugfs_remove_recursive(indio_dev->debugfs_dentry); +} + +static int iio_device_register_debugfs(struct iio_dev *indio_dev) +{ + struct dentry *d; + + if (indio_dev->info->debugfs_reg_access == NULL) + return 0; + + if (!iio_debugfs_dentry) + return 0; + + indio_dev->debugfs_dentry = + debugfs_create_dir(dev_name(&indio_dev->dev), + iio_debugfs_dentry); + if (indio_dev->debugfs_dentry == NULL) { + dev_warn(indio_dev->dev.parent, + "Failed to create debugfs directory\n"); + return -EFAULT; + } + + d = debugfs_create_file("direct_reg_access", 0644, + indio_dev->debugfs_dentry, + indio_dev, &iio_debugfs_reg_fops); + if (!d) { + iio_device_unregister_debugfs(indio_dev); + return -ENOMEM; + } + + return 0; +} +#else +static int iio_device_register_debugfs(struct iio_dev *indio_dev) +{ + return 0; +} + +static void iio_device_unregister_debugfs(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +static ssize_t iio_read_channel_ext_info(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + const struct iio_chan_spec_ext_info *ext_info; + + ext_info = &this_attr->c->ext_info[this_attr->address]; + + return ext_info->read(indio_dev, ext_info->private, this_attr->c, buf); +} + +static ssize_t iio_write_channel_ext_info(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + const struct iio_chan_spec_ext_info *ext_info; + + ext_info = &this_attr->c->ext_info[this_attr->address]; + + return ext_info->write(indio_dev, ext_info->private, + this_attr->c, buf, len); +} + +ssize_t iio_enum_available_read(struct iio_dev *indio_dev, + uintptr_t priv, const struct iio_chan_spec *chan, char *buf) +{ + const struct iio_enum *e = (const struct iio_enum *)priv; + unsigned int i; + size_t len = 0; + + if (!e->num_items) + return 0; + + for (i = 0; i < e->num_items; ++i) + len += scnprintf(buf + len, PAGE_SIZE - len, "%s ", e->items[i]); + + /* replace last space with a newline */ + buf[len - 1] = '\n'; + + return len; +} +EXPORT_SYMBOL_GPL(iio_enum_available_read); + +ssize_t iio_enum_read(struct iio_dev *indio_dev, + uintptr_t priv, const struct iio_chan_spec *chan, char *buf) +{ + const struct iio_enum *e = (const struct iio_enum *)priv; + int i; + + if (!e->get) + return -EINVAL; + + i = e->get(indio_dev, chan); + if (i < 0) + return i; + else if (i >= e->num_items) + return -EINVAL; + + return snprintf(buf, PAGE_SIZE, "%s\n", e->items[i]); +} +EXPORT_SYMBOL_GPL(iio_enum_read); + +ssize_t iio_enum_write(struct iio_dev *indio_dev, + uintptr_t priv, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + const struct iio_enum *e = (const struct iio_enum *)priv; + unsigned int i; + int ret; + + if (!e->set) + return -EINVAL; + + for (i = 0; i < e->num_items; i++) { + if (sysfs_streq(buf, e->items[i])) + break; + } + + if (i == e->num_items) + return -EINVAL; + + ret = e->set(indio_dev, chan, i); + return ret ? ret : len; +} +EXPORT_SYMBOL_GPL(iio_enum_write); + +/** + * iio_format_value() - Formats a IIO value into its string representation + * @buf: The buffer to which the formated value gets written + * @type: One of the IIO_VAL_... constants. This decides how the val and val2 + * parameters are formatted. + * @vals: pointer to the values, exact meaning depends on the type parameter. + */ +ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals) +{ + unsigned long long tmp; + bool scale_db = false; + + switch (type) { + case IIO_VAL_INT: + return sprintf(buf, "%d\n", vals[0]); + case IIO_VAL_INT_PLUS_MICRO_DB: + scale_db = true; + case IIO_VAL_INT_PLUS_MICRO: + if (vals[1] < 0) + return sprintf(buf, "-%ld.%06u%s\n", abs(vals[0]), + -vals[1], + scale_db ? " dB" : ""); + else + return sprintf(buf, "%d.%06u%s\n", vals[0], vals[1], + scale_db ? " dB" : ""); + case IIO_VAL_INT_PLUS_NANO: + if (vals[1] < 0) + return sprintf(buf, "-%ld.%09u\n", abs(vals[0]), + -vals[1]); + else + return sprintf(buf, "%d.%09u\n", vals[0], vals[1]); + case IIO_VAL_FRACTIONAL: + tmp = div_s64((s64)vals[0] * 1000000000LL, vals[1]); + vals[1] = do_div(tmp, 1000000000LL); + vals[0] = tmp; + return sprintf(buf, "%d.%09u\n", vals[0], vals[1]); + case IIO_VAL_FRACTIONAL_LOG2: + tmp = (s64)vals[0] * 1000000000LL >> vals[1]; + vals[1] = do_div(tmp, 1000000000LL); + vals[0] = tmp; + return sprintf(buf, "%d.%09u\n", vals[0], vals[1]); + case IIO_VAL_INT_MULTIPLE: + { + int i; + int len = 0; + + for (i = 0; i < size; ++i) + len += snprintf(&buf[len], PAGE_SIZE - len, "%d ", + vals[i]); + len += snprintf(&buf[len], PAGE_SIZE - len, "\n"); + return len; + } + default: + return 0; + } +} + +static ssize_t iio_read_channel_info(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int vals[INDIO_MAX_RAW_ELEMENTS]; + int ret; + int val_len = 2; + + if (indio_dev->info->read_raw_multi) + ret = indio_dev->info->read_raw_multi(indio_dev, this_attr->c, + INDIO_MAX_RAW_ELEMENTS, + vals, &val_len, + this_attr->address); + else + ret = indio_dev->info->read_raw(indio_dev, this_attr->c, + &vals[0], &vals[1], this_attr->address); + + if (ret < 0) + return ret; + + return iio_format_value(buf, ret, val_len, vals); +} + +/** + * iio_str_to_fixpoint() - Parse a fixed-point number from a string + * @str: The string to parse + * @fract_mult: Multiplier for the first decimal place, should be a power of 10 + * @integer: The integer part of the number + * @fract: The fractional part of the number + * + * Returns 0 on success, or a negative error code if the string could not be + * parsed. + */ +int iio_str_to_fixpoint(const char *str, int fract_mult, + int *integer, int *fract) +{ + int i = 0, f = 0; + bool integer_part = true, negative = false; + + if (str[0] == '-') { + negative = true; + str++; + } else if (str[0] == '+') { + str++; + } + + while (*str) { + if ('0' <= *str && *str <= '9') { + if (integer_part) { + i = i * 10 + *str - '0'; + } else { + f += fract_mult * (*str - '0'); + fract_mult /= 10; + } + } else if (*str == '\n') { + if (*(str + 1) == '\0') + break; + else + return -EINVAL; + } else if (*str == '.' && integer_part) { + integer_part = false; + } else { + return -EINVAL; + } + str++; + } + + if (negative) { + if (i) + i = -i; + else + f = -f; + } + + *integer = i; + *fract = f; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_str_to_fixpoint); + +static ssize_t iio_write_channel_info(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret, fract_mult = 100000; + int integer, fract; + + /* Assumes decimal - precision based on number of digits */ + if (!indio_dev->info->write_raw) + return -EINVAL; + + if (indio_dev->info->write_raw_get_fmt) + switch (indio_dev->info->write_raw_get_fmt(indio_dev, + this_attr->c, this_attr->address)) { + case IIO_VAL_INT_PLUS_MICRO: + fract_mult = 100000; + break; + case IIO_VAL_INT_PLUS_NANO: + fract_mult = 100000000; + break; + default: + return -EINVAL; + } + + ret = iio_str_to_fixpoint(buf, fract_mult, &integer, &fract); + if (ret) + return ret; + + ret = indio_dev->info->write_raw(indio_dev, this_attr->c, + integer, fract, this_attr->address); + if (ret) + return ret; + + return len; +} + +static +int __iio_device_attr_init(struct device_attribute *dev_attr, + const char *postfix, + struct iio_chan_spec const *chan, + ssize_t (*readfunc)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*writefunc)(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len), + enum iio_shared_by shared_by) +{ + int ret = 0; + char *name = NULL; + char *full_postfix; + sysfs_attr_init(&dev_attr->attr); + + /* Build up postfix of <extend_name>_<modifier>_postfix */ + if (chan->modified && (shared_by == IIO_SEPARATE)) { + if (chan->extend_name) + full_postfix = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_modifier_names[chan + ->channel2], + chan->extend_name, + postfix); + else + full_postfix = kasprintf(GFP_KERNEL, "%s_%s", + iio_modifier_names[chan + ->channel2], + postfix); + } else { + if (chan->extend_name == NULL || shared_by != IIO_SEPARATE) + full_postfix = kstrdup(postfix, GFP_KERNEL); + else + full_postfix = kasprintf(GFP_KERNEL, + "%s_%s", + chan->extend_name, + postfix); + } + if (full_postfix == NULL) + return -ENOMEM; + + if (chan->differential) { /* Differential can not have modifier */ + switch (shared_by) { + case IIO_SHARED_BY_ALL: + name = kasprintf(GFP_KERNEL, "%s", full_postfix); + break; + case IIO_SHARED_BY_DIR: + name = kasprintf(GFP_KERNEL, "%s_%s", + iio_direction[chan->output], + full_postfix); + break; + case IIO_SHARED_BY_TYPE: + name = kasprintf(GFP_KERNEL, "%s_%s-%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + iio_chan_type_name_spec[chan->type], + full_postfix); + break; + case IIO_SEPARATE: + if (!chan->indexed) { + WARN_ON("Differential channels must be indexed\n"); + ret = -EINVAL; + goto error_free_full_postfix; + } + name = kasprintf(GFP_KERNEL, + "%s_%s%d-%s%d_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + chan->channel, + iio_chan_type_name_spec[chan->type], + chan->channel2, + full_postfix); + break; + } + } else { /* Single ended */ + switch (shared_by) { + case IIO_SHARED_BY_ALL: + name = kasprintf(GFP_KERNEL, "%s", full_postfix); + break; + case IIO_SHARED_BY_DIR: + name = kasprintf(GFP_KERNEL, "%s_%s", + iio_direction[chan->output], + full_postfix); + break; + case IIO_SHARED_BY_TYPE: + name = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + full_postfix); + break; + + case IIO_SEPARATE: + if (chan->indexed) + name = kasprintf(GFP_KERNEL, "%s_%s%d_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + chan->channel, + full_postfix); + else + name = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + full_postfix); + break; + } + } + if (name == NULL) { + ret = -ENOMEM; + goto error_free_full_postfix; + } + dev_attr->attr.name = name; + + if (readfunc) { + dev_attr->attr.mode |= S_IRUGO; + dev_attr->show = readfunc; + } + + if (writefunc) { + dev_attr->attr.mode |= S_IWUSR; + dev_attr->store = writefunc; + } + +error_free_full_postfix: + kfree(full_postfix); + + return ret; +} + +static void __iio_device_attr_deinit(struct device_attribute *dev_attr) +{ + kfree(dev_attr->attr.name); +} + +int __iio_add_chan_devattr(const char *postfix, + struct iio_chan_spec const *chan, + ssize_t (*readfunc)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*writefunc)(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len), + u64 mask, + enum iio_shared_by shared_by, + struct device *dev, + struct list_head *attr_list) +{ + int ret; + struct iio_dev_attr *iio_attr, *t; + + iio_attr = kzalloc(sizeof(*iio_attr), GFP_KERNEL); + if (iio_attr == NULL) + return -ENOMEM; + ret = __iio_device_attr_init(&iio_attr->dev_attr, + postfix, chan, + readfunc, writefunc, shared_by); + if (ret) + goto error_iio_dev_attr_free; + iio_attr->c = chan; + iio_attr->address = mask; + list_for_each_entry(t, attr_list, l) + if (strcmp(t->dev_attr.attr.name, + iio_attr->dev_attr.attr.name) == 0) { + if (shared_by == IIO_SEPARATE) + dev_err(dev, "tried to double register : %s\n", + t->dev_attr.attr.name); + ret = -EBUSY; + goto error_device_attr_deinit; + } + list_add(&iio_attr->l, attr_list); + + return 0; + +error_device_attr_deinit: + __iio_device_attr_deinit(&iio_attr->dev_attr); +error_iio_dev_attr_free: + kfree(iio_attr); + return ret; +} + +static int iio_device_add_info_mask_type(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + enum iio_shared_by shared_by, + const long *infomask) +{ + int i, ret, attrcount = 0; + + for_each_set_bit(i, infomask, sizeof(infomask)*8) { + if (i >= ARRAY_SIZE(iio_chan_info_postfix)) + return -EINVAL; + ret = __iio_add_chan_devattr(iio_chan_info_postfix[i], + chan, + &iio_read_channel_info, + &iio_write_channel_info, + i, + shared_by, + &indio_dev->dev, + &indio_dev->channel_attr_list); + if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE)) + continue; + else if (ret < 0) + return ret; + attrcount++; + } + + return attrcount; +} + +static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + int ret, attrcount = 0; + const struct iio_chan_spec_ext_info *ext_info; + + if (chan->channel < 0) + return 0; + ret = iio_device_add_info_mask_type(indio_dev, chan, + IIO_SEPARATE, + &chan->info_mask_separate); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_info_mask_type(indio_dev, chan, + IIO_SHARED_BY_TYPE, + &chan->info_mask_shared_by_type); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_info_mask_type(indio_dev, chan, + IIO_SHARED_BY_DIR, + &chan->info_mask_shared_by_dir); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_info_mask_type(indio_dev, chan, + IIO_SHARED_BY_ALL, + &chan->info_mask_shared_by_all); + if (ret < 0) + return ret; + attrcount += ret; + + if (chan->ext_info) { + unsigned int i = 0; + for (ext_info = chan->ext_info; ext_info->name; ext_info++) { + ret = __iio_add_chan_devattr(ext_info->name, + chan, + ext_info->read ? + &iio_read_channel_ext_info : NULL, + ext_info->write ? + &iio_write_channel_ext_info : NULL, + i, + ext_info->shared, + &indio_dev->dev, + &indio_dev->channel_attr_list); + i++; + if (ret == -EBUSY && ext_info->shared) + continue; + + if (ret) + return ret; + + attrcount++; + } + } + + return attrcount; +} + +/** + * iio_free_chan_devattr_list() - Free a list of IIO device attributes + * @attr_list: List of IIO device attributes + * + * This function frees the memory allocated for each of the IIO device + * attributes in the list. + */ +void iio_free_chan_devattr_list(struct list_head *attr_list) +{ + struct iio_dev_attr *p, *n; + + list_for_each_entry_safe(p, n, attr_list, l) { + kfree(p->dev_attr.attr.name); + list_del(&p->l); + kfree(p); + } +} + +static ssize_t iio_show_dev_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + return snprintf(buf, PAGE_SIZE, "%s\n", indio_dev->name); +} + +static DEVICE_ATTR(name, S_IRUGO, iio_show_dev_name, NULL); + +static int iio_device_register_sysfs(struct iio_dev *indio_dev) +{ + int i, ret = 0, attrcount, attrn, attrcount_orig = 0; + struct iio_dev_attr *p; + struct attribute **attr; + + /* First count elements in any existing group */ + if (indio_dev->info->attrs) { + attr = indio_dev->info->attrs->attrs; + while (*attr++ != NULL) + attrcount_orig++; + } + attrcount = attrcount_orig; + /* + * New channel registration method - relies on the fact a group does + * not need to be initialized if its name is NULL. + */ + if (indio_dev->channels) + for (i = 0; i < indio_dev->num_channels; i++) { + ret = iio_device_add_channel_sysfs(indio_dev, + &indio_dev + ->channels[i]); + if (ret < 0) + goto error_clear_attrs; + attrcount += ret; + } + + if (indio_dev->name) + attrcount++; + + indio_dev->chan_attr_group.attrs = kcalloc(attrcount + 1, + sizeof(indio_dev->chan_attr_group.attrs[0]), + GFP_KERNEL); + if (indio_dev->chan_attr_group.attrs == NULL) { + ret = -ENOMEM; + goto error_clear_attrs; + } + /* Copy across original attributes */ + if (indio_dev->info->attrs) + memcpy(indio_dev->chan_attr_group.attrs, + indio_dev->info->attrs->attrs, + sizeof(indio_dev->chan_attr_group.attrs[0]) + *attrcount_orig); + attrn = attrcount_orig; + /* Add all elements from the list. */ + list_for_each_entry(p, &indio_dev->channel_attr_list, l) + indio_dev->chan_attr_group.attrs[attrn++] = &p->dev_attr.attr; + if (indio_dev->name) + indio_dev->chan_attr_group.attrs[attrn++] = &dev_attr_name.attr; + + indio_dev->groups[indio_dev->groupcounter++] = + &indio_dev->chan_attr_group; + + return 0; + +error_clear_attrs: + iio_free_chan_devattr_list(&indio_dev->channel_attr_list); + + return ret; +} + +static void iio_device_unregister_sysfs(struct iio_dev *indio_dev) +{ + + iio_free_chan_devattr_list(&indio_dev->channel_attr_list); + kfree(indio_dev->chan_attr_group.attrs); + indio_dev->chan_attr_group.attrs = NULL; +} + +static void iio_dev_release(struct device *device) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(device); + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + iio_device_unregister_trigger_consumer(indio_dev); + iio_device_unregister_eventset(indio_dev); + iio_device_unregister_sysfs(indio_dev); + + iio_buffer_put(indio_dev->buffer); + + ida_simple_remove(&iio_ida, indio_dev->id); + kfree(indio_dev); +} + +struct device_type iio_device_type = { + .name = "iio_device", + .release = iio_dev_release, +}; + +/** + * iio_device_alloc() - allocate an iio_dev from a driver + * @sizeof_priv: Space to allocate for private structure. + **/ +struct iio_dev *iio_device_alloc(int sizeof_priv) +{ + struct iio_dev *dev; + size_t alloc_size; + + alloc_size = sizeof(struct iio_dev); + if (sizeof_priv) { + alloc_size = ALIGN(alloc_size, IIO_ALIGN); + alloc_size += sizeof_priv; + } + /* ensure 32-byte alignment of whole construct ? */ + alloc_size += IIO_ALIGN - 1; + + dev = kzalloc(alloc_size, GFP_KERNEL); + + if (dev) { + dev->dev.groups = dev->groups; + dev->dev.type = &iio_device_type; + dev->dev.bus = &iio_bus_type; + device_initialize(&dev->dev); + dev_set_drvdata(&dev->dev, (void *)dev); + mutex_init(&dev->mlock); + mutex_init(&dev->info_exist_lock); + INIT_LIST_HEAD(&dev->channel_attr_list); + + dev->id = ida_simple_get(&iio_ida, 0, 0, GFP_KERNEL); + if (dev->id < 0) { + /* cannot use a dev_err as the name isn't available */ + pr_err("failed to get device id\n"); + kfree(dev); + return NULL; + } + dev_set_name(&dev->dev, "iio:device%d", dev->id); + INIT_LIST_HEAD(&dev->buffer_list); + } + + return dev; +} +EXPORT_SYMBOL(iio_device_alloc); + +/** + * iio_device_free() - free an iio_dev from a driver + * @dev: the iio_dev associated with the device + **/ +void iio_device_free(struct iio_dev *dev) +{ + if (dev) + put_device(&dev->dev); +} +EXPORT_SYMBOL(iio_device_free); + +static void devm_iio_device_release(struct device *dev, void *res) +{ + iio_device_free(*(struct iio_dev **)res); +} + +static int devm_iio_device_match(struct device *dev, void *res, void *data) +{ + struct iio_dev **r = res; + if (!r || !*r) { + WARN_ON(!r || !*r); + return 0; + } + return *r == data; +} + +/** + * devm_iio_device_alloc - Resource-managed iio_device_alloc() + * @dev: Device to allocate iio_dev for + * @sizeof_priv: Space to allocate for private structure. + * + * Managed iio_device_alloc. iio_dev allocated with this function is + * automatically freed on driver detach. + * + * If an iio_dev allocated with this function needs to be freed separately, + * devm_iio_device_free() must be used. + * + * RETURNS: + * Pointer to allocated iio_dev on success, NULL on failure. + */ +struct iio_dev *devm_iio_device_alloc(struct device *dev, int sizeof_priv) +{ + struct iio_dev **ptr, *iio_dev; + + ptr = devres_alloc(devm_iio_device_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return NULL; + + iio_dev = iio_device_alloc(sizeof_priv); + if (iio_dev) { + *ptr = iio_dev; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return iio_dev; +} +EXPORT_SYMBOL_GPL(devm_iio_device_alloc); + +/** + * devm_iio_device_free - Resource-managed iio_device_free() + * @dev: Device this iio_dev belongs to + * @iio_dev: the iio_dev associated with the device + * + * Free iio_dev allocated with devm_iio_device_alloc(). + */ +void devm_iio_device_free(struct device *dev, struct iio_dev *iio_dev) +{ + int rc; + + rc = devres_release(dev, devm_iio_device_release, + devm_iio_device_match, iio_dev); + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_iio_device_free); + +/** + * iio_chrdev_open() - chrdev file open for buffer access and ioctls + **/ +static int iio_chrdev_open(struct inode *inode, struct file *filp) +{ + struct iio_dev *indio_dev = container_of(inode->i_cdev, + struct iio_dev, chrdev); + + if (test_and_set_bit(IIO_BUSY_BIT_POS, &indio_dev->flags)) + return -EBUSY; + + iio_device_get(indio_dev); + + filp->private_data = indio_dev; + + return 0; +} + +/** + * iio_chrdev_release() - chrdev file close buffer access and ioctls + **/ +static int iio_chrdev_release(struct inode *inode, struct file *filp) +{ + struct iio_dev *indio_dev = container_of(inode->i_cdev, + struct iio_dev, chrdev); + clear_bit(IIO_BUSY_BIT_POS, &indio_dev->flags); + iio_device_put(indio_dev); + + return 0; +} + +/* Somewhat of a cross file organization violation - ioctls here are actually + * event related */ +static long iio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct iio_dev *indio_dev = filp->private_data; + int __user *ip = (int __user *)arg; + int fd; + + if (!indio_dev->info) + return -ENODEV; + + if (cmd == IIO_GET_EVENT_FD_IOCTL) { + fd = iio_event_getfd(indio_dev); + if (copy_to_user(ip, &fd, sizeof(fd))) + return -EFAULT; + return 0; + } + return -EINVAL; +} + +static const struct file_operations iio_buffer_fileops = { + .read = iio_buffer_read_first_n_outer_addr, + .release = iio_chrdev_release, + .open = iio_chrdev_open, + .poll = iio_buffer_poll_addr, + .owner = THIS_MODULE, + .llseek = noop_llseek, + .unlocked_ioctl = iio_ioctl, + .compat_ioctl = iio_ioctl, +}; + +static int iio_check_unique_scan_index(struct iio_dev *indio_dev) +{ + int i, j; + const struct iio_chan_spec *channels = indio_dev->channels; + + if (!(indio_dev->modes & INDIO_ALL_BUFFER_MODES)) + return 0; + + for (i = 0; i < indio_dev->num_channels - 1; i++) { + if (channels[i].scan_index < 0) + continue; + for (j = i + 1; j < indio_dev->num_channels; j++) + if (channels[i].scan_index == channels[j].scan_index) { + dev_err(&indio_dev->dev, + "Duplicate scan index %d\n", + channels[i].scan_index); + return -EINVAL; + } + } + + return 0; +} + +static const struct iio_buffer_setup_ops noop_ring_setup_ops; + +/** + * iio_device_register() - register a device with the IIO subsystem + * @indio_dev: Device structure filled by the device driver + **/ +int iio_device_register(struct iio_dev *indio_dev) +{ + int ret; + + /* If the calling driver did not initialize of_node, do it here */ + if (!indio_dev->dev.of_node && indio_dev->dev.parent) + indio_dev->dev.of_node = indio_dev->dev.parent->of_node; + + ret = iio_check_unique_scan_index(indio_dev); + if (ret < 0) + return ret; + + /* configure elements for the chrdev */ + indio_dev->dev.devt = MKDEV(MAJOR(iio_devt), indio_dev->id); + + ret = iio_device_register_debugfs(indio_dev); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to register debugfs interfaces\n"); + return ret; + } + + ret = iio_buffer_alloc_sysfs_and_mask(indio_dev); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to create buffer sysfs interfaces\n"); + goto error_unreg_debugfs; + } + + ret = iio_device_register_sysfs(indio_dev); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to register sysfs interfaces\n"); + goto error_buffer_free_sysfs; + } + ret = iio_device_register_eventset(indio_dev); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to register event set\n"); + goto error_free_sysfs; + } + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + iio_device_register_trigger_consumer(indio_dev); + + if ((indio_dev->modes & INDIO_ALL_BUFFER_MODES) && + indio_dev->setup_ops == NULL) + indio_dev->setup_ops = &noop_ring_setup_ops; + + cdev_init(&indio_dev->chrdev, &iio_buffer_fileops); + indio_dev->chrdev.owner = indio_dev->info->driver_module; + indio_dev->chrdev.kobj.parent = &indio_dev->dev.kobj; + ret = cdev_add(&indio_dev->chrdev, indio_dev->dev.devt, 1); + if (ret < 0) + goto error_unreg_eventset; + + ret = device_add(&indio_dev->dev); + if (ret < 0) + goto error_cdev_del; + + return 0; +error_cdev_del: + cdev_del(&indio_dev->chrdev); +error_unreg_eventset: + iio_device_unregister_eventset(indio_dev); +error_free_sysfs: + iio_device_unregister_sysfs(indio_dev); +error_buffer_free_sysfs: + iio_buffer_free_sysfs_and_mask(indio_dev); +error_unreg_debugfs: + iio_device_unregister_debugfs(indio_dev); + return ret; +} +EXPORT_SYMBOL(iio_device_register); + +/** + * iio_device_unregister() - unregister a device from the IIO subsystem + * @indio_dev: Device structure representing the device. + **/ +void iio_device_unregister(struct iio_dev *indio_dev) +{ + mutex_lock(&indio_dev->info_exist_lock); + + device_del(&indio_dev->dev); + + if (indio_dev->chrdev.dev) + cdev_del(&indio_dev->chrdev); + iio_device_unregister_debugfs(indio_dev); + + iio_disable_all_buffers(indio_dev); + + indio_dev->info = NULL; + + iio_device_wakeup_eventset(indio_dev); + iio_buffer_wakeup_poll(indio_dev); + + mutex_unlock(&indio_dev->info_exist_lock); + + iio_buffer_free_sysfs_and_mask(indio_dev); +} +EXPORT_SYMBOL(iio_device_unregister); + +static void devm_iio_device_unreg(struct device *dev, void *res) +{ + iio_device_unregister(*(struct iio_dev **)res); +} + +/** + * devm_iio_device_register - Resource-managed iio_device_register() + * @dev: Device to allocate iio_dev for + * @indio_dev: Device structure filled by the device driver + * + * Managed iio_device_register. The IIO device registered with this + * function is automatically unregistered on driver detach. This function + * calls iio_device_register() internally. Refer to that function for more + * information. + * + * If an iio_dev registered with this function needs to be unregistered + * separately, devm_iio_device_unregister() must be used. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int devm_iio_device_register(struct device *dev, struct iio_dev *indio_dev) +{ + struct iio_dev **ptr; + int ret; + + ptr = devres_alloc(devm_iio_device_unreg, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + *ptr = indio_dev; + ret = iio_device_register(indio_dev); + if (!ret) + devres_add(dev, ptr); + else + devres_free(ptr); + + return ret; +} +EXPORT_SYMBOL_GPL(devm_iio_device_register); + +/** + * devm_iio_device_unregister - Resource-managed iio_device_unregister() + * @dev: Device this iio_dev belongs to + * @indio_dev: the iio_dev associated with the device + * + * Unregister iio_dev registered with devm_iio_device_register(). + */ +void devm_iio_device_unregister(struct device *dev, struct iio_dev *indio_dev) +{ + int rc; + + rc = devres_release(dev, devm_iio_device_unreg, + devm_iio_device_match, indio_dev); + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_iio_device_unregister); + +subsys_initcall(iio_init); +module_exit(iio_exit); + +MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); +MODULE_DESCRIPTION("Industrial I/O core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/industrialio-event.c b/drivers/iio/industrialio-event.c new file mode 100644 index 000000000..a99692ba9 --- /dev/null +++ b/drivers/iio/industrialio-event.c @@ -0,0 +1,528 @@ +/* Industrial I/O event handling + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Based on elements of hwmon and input subsystems. + */ + +#include <linux/anon_inodes.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/kfifo.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/wait.h> +#include <linux/iio/iio.h> +#include "iio_core.h" +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> + +/** + * struct iio_event_interface - chrdev interface for an event line + * @wait: wait queue to allow blocking reads of events + * @det_events: list of detected events + * @dev_attr_list: list of event interface sysfs attribute + * @flags: file operations related flags including busy flag. + * @group: event interface sysfs attribute group + */ +struct iio_event_interface { + wait_queue_head_t wait; + DECLARE_KFIFO(det_events, struct iio_event_data, 16); + + struct list_head dev_attr_list; + unsigned long flags; + struct attribute_group group; + struct mutex read_lock; +}; + +/** + * iio_push_event() - try to add event to the list for userspace reading + * @indio_dev: IIO device structure + * @ev_code: What event + * @timestamp: When the event occurred + * + * Note: The caller must make sure that this function is not running + * concurrently for the same indio_dev more than once. + **/ +int iio_push_event(struct iio_dev *indio_dev, u64 ev_code, s64 timestamp) +{ + struct iio_event_interface *ev_int = indio_dev->event_interface; + struct iio_event_data ev; + int copied; + + /* Does anyone care? */ + if (test_bit(IIO_BUSY_BIT_POS, &ev_int->flags)) { + + ev.id = ev_code; + ev.timestamp = timestamp; + + copied = kfifo_put(&ev_int->det_events, ev); + if (copied != 0) + wake_up_poll(&ev_int->wait, POLLIN); + } + + return 0; +} +EXPORT_SYMBOL(iio_push_event); + +/** + * iio_event_poll() - poll the event queue to find out if it has data + */ +static unsigned int iio_event_poll(struct file *filep, + struct poll_table_struct *wait) +{ + struct iio_dev *indio_dev = filep->private_data; + struct iio_event_interface *ev_int = indio_dev->event_interface; + unsigned int events = 0; + + if (!indio_dev->info) + return -ENODEV; + + poll_wait(filep, &ev_int->wait, wait); + + if (!kfifo_is_empty(&ev_int->det_events)) + events = POLLIN | POLLRDNORM; + + return events; +} + +static ssize_t iio_event_chrdev_read(struct file *filep, + char __user *buf, + size_t count, + loff_t *f_ps) +{ + struct iio_dev *indio_dev = filep->private_data; + struct iio_event_interface *ev_int = indio_dev->event_interface; + unsigned int copied; + int ret; + + if (!indio_dev->info) + return -ENODEV; + + if (count < sizeof(struct iio_event_data)) + return -EINVAL; + + do { + if (kfifo_is_empty(&ev_int->det_events)) { + if (filep->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(ev_int->wait, + !kfifo_is_empty(&ev_int->det_events) || + indio_dev->info == NULL); + if (ret) + return ret; + if (indio_dev->info == NULL) + return -ENODEV; + } + + if (mutex_lock_interruptible(&ev_int->read_lock)) + return -ERESTARTSYS; + ret = kfifo_to_user(&ev_int->det_events, buf, count, &copied); + mutex_unlock(&ev_int->read_lock); + + if (ret) + return ret; + + /* + * If we couldn't read anything from the fifo (a different + * thread might have been faster) we either return -EAGAIN if + * the file descriptor is non-blocking, otherwise we go back to + * sleep and wait for more data to arrive. + */ + if (copied == 0 && (filep->f_flags & O_NONBLOCK)) + return -EAGAIN; + + } while (copied == 0); + + return copied; +} + +static int iio_event_chrdev_release(struct inode *inode, struct file *filep) +{ + struct iio_dev *indio_dev = filep->private_data; + struct iio_event_interface *ev_int = indio_dev->event_interface; + + clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags); + + iio_device_put(indio_dev); + + return 0; +} + +static const struct file_operations iio_event_chrdev_fileops = { + .read = iio_event_chrdev_read, + .poll = iio_event_poll, + .release = iio_event_chrdev_release, + .owner = THIS_MODULE, + .llseek = noop_llseek, +}; + +int iio_event_getfd(struct iio_dev *indio_dev) +{ + struct iio_event_interface *ev_int = indio_dev->event_interface; + int fd; + + if (ev_int == NULL) + return -ENODEV; + + if (test_and_set_bit(IIO_BUSY_BIT_POS, &ev_int->flags)) + return -EBUSY; + + iio_device_get(indio_dev); + + fd = anon_inode_getfd("iio:event", &iio_event_chrdev_fileops, + indio_dev, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags); + iio_device_put(indio_dev); + } else { + kfifo_reset_out(&ev_int->det_events); + } + + return fd; +} + +static const char * const iio_ev_type_text[] = { + [IIO_EV_TYPE_THRESH] = "thresh", + [IIO_EV_TYPE_MAG] = "mag", + [IIO_EV_TYPE_ROC] = "roc", + [IIO_EV_TYPE_THRESH_ADAPTIVE] = "thresh_adaptive", + [IIO_EV_TYPE_MAG_ADAPTIVE] = "mag_adaptive", + [IIO_EV_TYPE_CHANGE] = "change", +}; + +static const char * const iio_ev_dir_text[] = { + [IIO_EV_DIR_EITHER] = "either", + [IIO_EV_DIR_RISING] = "rising", + [IIO_EV_DIR_FALLING] = "falling" +}; + +static const char * const iio_ev_info_text[] = { + [IIO_EV_INFO_ENABLE] = "en", + [IIO_EV_INFO_VALUE] = "value", + [IIO_EV_INFO_HYSTERESIS] = "hysteresis", + [IIO_EV_INFO_PERIOD] = "period", +}; + +static enum iio_event_direction iio_ev_attr_dir(struct iio_dev_attr *attr) +{ + return attr->c->event_spec[attr->address & 0xffff].dir; +} + +static enum iio_event_type iio_ev_attr_type(struct iio_dev_attr *attr) +{ + return attr->c->event_spec[attr->address & 0xffff].type; +} + +static enum iio_event_info iio_ev_attr_info(struct iio_dev_attr *attr) +{ + return (attr->address >> 16) & 0xffff; +} + +static ssize_t iio_ev_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + bool val; + + ret = strtobool(buf, &val); + if (ret < 0) + return ret; + + ret = indio_dev->info->write_event_config(indio_dev, + this_attr->c, iio_ev_attr_type(this_attr), + iio_ev_attr_dir(this_attr), val); + + return (ret < 0) ? ret : len; +} + +static ssize_t iio_ev_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int val; + + val = indio_dev->info->read_event_config(indio_dev, + this_attr->c, iio_ev_attr_type(this_attr), + iio_ev_attr_dir(this_attr)); + if (val < 0) + return val; + else + return sprintf(buf, "%d\n", val); +} + +static ssize_t iio_ev_value_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int val, val2, val_arr[2]; + int ret; + + ret = indio_dev->info->read_event_value(indio_dev, + this_attr->c, iio_ev_attr_type(this_attr), + iio_ev_attr_dir(this_attr), iio_ev_attr_info(this_attr), + &val, &val2); + if (ret < 0) + return ret; + val_arr[0] = val; + val_arr[1] = val2; + return iio_format_value(buf, ret, 2, val_arr); +} + +static ssize_t iio_ev_value_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int val, val2; + int ret; + + if (!indio_dev->info->write_event_value) + return -EINVAL; + + ret = iio_str_to_fixpoint(buf, 100000, &val, &val2); + if (ret) + return ret; + ret = indio_dev->info->write_event_value(indio_dev, + this_attr->c, iio_ev_attr_type(this_attr), + iio_ev_attr_dir(this_attr), iio_ev_attr_info(this_attr), + val, val2); + if (ret < 0) + return ret; + + return len; +} + +static int iio_device_add_event(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int spec_index, + enum iio_event_type type, enum iio_event_direction dir, + enum iio_shared_by shared_by, const unsigned long *mask) +{ + ssize_t (*show)(struct device *, struct device_attribute *, char *); + ssize_t (*store)(struct device *, struct device_attribute *, + const char *, size_t); + unsigned int attrcount = 0; + unsigned int i; + char *postfix; + int ret; + + for_each_set_bit(i, mask, sizeof(*mask)*8) { + if (i >= ARRAY_SIZE(iio_ev_info_text)) + return -EINVAL; + if (dir != IIO_EV_DIR_NONE) + postfix = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_ev_type_text[type], + iio_ev_dir_text[dir], + iio_ev_info_text[i]); + else + postfix = kasprintf(GFP_KERNEL, "%s_%s", + iio_ev_type_text[type], + iio_ev_info_text[i]); + if (postfix == NULL) + return -ENOMEM; + + if (i == IIO_EV_INFO_ENABLE) { + show = iio_ev_state_show; + store = iio_ev_state_store; + } else { + show = iio_ev_value_show; + store = iio_ev_value_store; + } + + ret = __iio_add_chan_devattr(postfix, chan, show, store, + (i << 16) | spec_index, shared_by, &indio_dev->dev, + &indio_dev->event_interface->dev_attr_list); + kfree(postfix); + + if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE)) + continue; + + if (ret) + return ret; + + attrcount++; + } + + return attrcount; +} + +static int iio_device_add_event_sysfs(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + int ret = 0, i, attrcount = 0; + enum iio_event_direction dir; + enum iio_event_type type; + + for (i = 0; i < chan->num_event_specs; i++) { + type = chan->event_spec[i].type; + dir = chan->event_spec[i].dir; + + ret = iio_device_add_event(indio_dev, chan, i, type, dir, + IIO_SEPARATE, &chan->event_spec[i].mask_separate); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_event(indio_dev, chan, i, type, dir, + IIO_SHARED_BY_TYPE, + &chan->event_spec[i].mask_shared_by_type); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_event(indio_dev, chan, i, type, dir, + IIO_SHARED_BY_DIR, + &chan->event_spec[i].mask_shared_by_dir); + if (ret < 0) + return ret; + attrcount += ret; + + ret = iio_device_add_event(indio_dev, chan, i, type, dir, + IIO_SHARED_BY_ALL, + &chan->event_spec[i].mask_shared_by_all); + if (ret < 0) + return ret; + attrcount += ret; + } + ret = attrcount; + return ret; +} + +static inline int __iio_add_event_config_attrs(struct iio_dev *indio_dev) +{ + int j, ret, attrcount = 0; + + /* Dynamically created from the channels array */ + for (j = 0; j < indio_dev->num_channels; j++) { + ret = iio_device_add_event_sysfs(indio_dev, + &indio_dev->channels[j]); + if (ret < 0) + return ret; + attrcount += ret; + } + return attrcount; +} + +static bool iio_check_for_dynamic_events(struct iio_dev *indio_dev) +{ + int j; + + for (j = 0; j < indio_dev->num_channels; j++) { + if (indio_dev->channels[j].num_event_specs != 0) + return true; + } + return false; +} + +static void iio_setup_ev_int(struct iio_event_interface *ev_int) +{ + INIT_KFIFO(ev_int->det_events); + init_waitqueue_head(&ev_int->wait); + mutex_init(&ev_int->read_lock); +} + +static const char *iio_event_group_name = "events"; +int iio_device_register_eventset(struct iio_dev *indio_dev) +{ + struct iio_dev_attr *p; + int ret = 0, attrcount_orig = 0, attrcount, attrn; + struct attribute **attr; + + if (!(indio_dev->info->event_attrs || + iio_check_for_dynamic_events(indio_dev))) + return 0; + + indio_dev->event_interface = + kzalloc(sizeof(struct iio_event_interface), GFP_KERNEL); + if (indio_dev->event_interface == NULL) + return -ENOMEM; + + INIT_LIST_HEAD(&indio_dev->event_interface->dev_attr_list); + + iio_setup_ev_int(indio_dev->event_interface); + if (indio_dev->info->event_attrs != NULL) { + attr = indio_dev->info->event_attrs->attrs; + while (*attr++ != NULL) + attrcount_orig++; + } + attrcount = attrcount_orig; + if (indio_dev->channels) { + ret = __iio_add_event_config_attrs(indio_dev); + if (ret < 0) + goto error_free_setup_event_lines; + attrcount += ret; + } + + indio_dev->event_interface->group.name = iio_event_group_name; + indio_dev->event_interface->group.attrs = kcalloc(attrcount + 1, + sizeof(indio_dev->event_interface->group.attrs[0]), + GFP_KERNEL); + if (indio_dev->event_interface->group.attrs == NULL) { + ret = -ENOMEM; + goto error_free_setup_event_lines; + } + if (indio_dev->info->event_attrs) + memcpy(indio_dev->event_interface->group.attrs, + indio_dev->info->event_attrs->attrs, + sizeof(indio_dev->event_interface->group.attrs[0]) + *attrcount_orig); + attrn = attrcount_orig; + /* Add all elements from the list. */ + list_for_each_entry(p, + &indio_dev->event_interface->dev_attr_list, + l) + indio_dev->event_interface->group.attrs[attrn++] = + &p->dev_attr.attr; + indio_dev->groups[indio_dev->groupcounter++] = + &indio_dev->event_interface->group; + + return 0; + +error_free_setup_event_lines: + iio_free_chan_devattr_list(&indio_dev->event_interface->dev_attr_list); + kfree(indio_dev->event_interface); + indio_dev->event_interface = NULL; + return ret; +} + +/** + * iio_device_wakeup_eventset - Wakes up the event waitqueue + * @indio_dev: The IIO device + * + * Wakes up the event waitqueue used for poll() and blocking read(). + * Should usually be called when the device is unregistered. + */ +void iio_device_wakeup_eventset(struct iio_dev *indio_dev) +{ + if (indio_dev->event_interface == NULL) + return; + wake_up(&indio_dev->event_interface->wait); +} + +void iio_device_unregister_eventset(struct iio_dev *indio_dev) +{ + if (indio_dev->event_interface == NULL) + return; + iio_free_chan_devattr_list(&indio_dev->event_interface->dev_attr_list); + kfree(indio_dev->event_interface->group.attrs); + kfree(indio_dev->event_interface); +} diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c new file mode 100644 index 000000000..d31098e0c --- /dev/null +++ b/drivers/iio/industrialio-trigger.c @@ -0,0 +1,583 @@ +/* The industrial I/O core, trigger handling functions + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/idr.h> +#include <linux/err.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include "iio_core.h" +#include "iio_core_trigger.h" +#include <linux/iio/trigger_consumer.h> + +/* RFC - Question of approach + * Make the common case (single sensor single trigger) + * simple by starting trigger capture from when first sensors + * is added. + * + * Complex simultaneous start requires use of 'hold' functionality + * of the trigger. (not implemented) + * + * Any other suggestions? + */ + +static DEFINE_IDA(iio_trigger_ida); + +/* Single list of all available triggers */ +static LIST_HEAD(iio_trigger_list); +static DEFINE_MUTEX(iio_trigger_list_lock); + +/** + * iio_trigger_read_name() - retrieve useful identifying name + **/ +static ssize_t iio_trigger_read_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + return sprintf(buf, "%s\n", trig->name); +} + +static DEVICE_ATTR(name, S_IRUGO, iio_trigger_read_name, NULL); + +static struct attribute *iio_trig_dev_attrs[] = { + &dev_attr_name.attr, + NULL, +}; +ATTRIBUTE_GROUPS(iio_trig_dev); + +int iio_trigger_register(struct iio_trigger *trig_info) +{ + int ret; + + trig_info->id = ida_simple_get(&iio_trigger_ida, 0, 0, GFP_KERNEL); + if (trig_info->id < 0) + return trig_info->id; + + /* Set the name used for the sysfs directory etc */ + dev_set_name(&trig_info->dev, "trigger%ld", + (unsigned long) trig_info->id); + + ret = device_add(&trig_info->dev); + if (ret) + goto error_unregister_id; + + /* Add to list of available triggers held by the IIO core */ + mutex_lock(&iio_trigger_list_lock); + list_add_tail(&trig_info->list, &iio_trigger_list); + mutex_unlock(&iio_trigger_list_lock); + + return 0; + +error_unregister_id: + ida_simple_remove(&iio_trigger_ida, trig_info->id); + return ret; +} +EXPORT_SYMBOL(iio_trigger_register); + +void iio_trigger_unregister(struct iio_trigger *trig_info) +{ + mutex_lock(&iio_trigger_list_lock); + list_del(&trig_info->list); + mutex_unlock(&iio_trigger_list_lock); + + ida_simple_remove(&iio_trigger_ida, trig_info->id); + /* Possible issue in here */ + device_del(&trig_info->dev); +} +EXPORT_SYMBOL(iio_trigger_unregister); + +static struct iio_trigger *iio_trigger_find_by_name(const char *name, + size_t len) +{ + struct iio_trigger *trig = NULL, *iter; + + mutex_lock(&iio_trigger_list_lock); + list_for_each_entry(iter, &iio_trigger_list, list) + if (sysfs_streq(iter->name, name)) { + trig = iter; + break; + } + mutex_unlock(&iio_trigger_list_lock); + + return trig; +} + +void iio_trigger_poll(struct iio_trigger *trig) +{ + int i; + + if (!atomic_read(&trig->use_count)) { + atomic_set(&trig->use_count, CONFIG_IIO_CONSUMERS_PER_TRIGGER); + + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + if (trig->subirqs[i].enabled) + generic_handle_irq(trig->subirq_base + i); + else + iio_trigger_notify_done(trig); + } + } +} +EXPORT_SYMBOL(iio_trigger_poll); + +irqreturn_t iio_trigger_generic_data_rdy_poll(int irq, void *private) +{ + iio_trigger_poll(private); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(iio_trigger_generic_data_rdy_poll); + +void iio_trigger_poll_chained(struct iio_trigger *trig) +{ + int i; + + if (!atomic_read(&trig->use_count)) { + atomic_set(&trig->use_count, CONFIG_IIO_CONSUMERS_PER_TRIGGER); + + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + if (trig->subirqs[i].enabled) + handle_nested_irq(trig->subirq_base + i); + else + iio_trigger_notify_done(trig); + } + } +} +EXPORT_SYMBOL(iio_trigger_poll_chained); + +void iio_trigger_notify_done(struct iio_trigger *trig) +{ + if (atomic_dec_and_test(&trig->use_count) && trig->ops && + trig->ops->try_reenable) + if (trig->ops->try_reenable(trig)) + /* Missed an interrupt so launch new poll now */ + iio_trigger_poll(trig); +} +EXPORT_SYMBOL(iio_trigger_notify_done); + +/* Trigger Consumer related functions */ +static int iio_trigger_get_irq(struct iio_trigger *trig) +{ + int ret; + mutex_lock(&trig->pool_lock); + ret = bitmap_find_free_region(trig->pool, + CONFIG_IIO_CONSUMERS_PER_TRIGGER, + ilog2(1)); + mutex_unlock(&trig->pool_lock); + if (ret >= 0) + ret += trig->subirq_base; + + return ret; +} + +static void iio_trigger_put_irq(struct iio_trigger *trig, int irq) +{ + mutex_lock(&trig->pool_lock); + clear_bit(irq - trig->subirq_base, trig->pool); + mutex_unlock(&trig->pool_lock); +} + +/* Complexity in here. With certain triggers (datardy) an acknowledgement + * may be needed if the pollfuncs do not include the data read for the + * triggering device. + * This is not currently handled. Alternative of not enabling trigger unless + * the relevant function is in there may be the best option. + */ +/* Worth protecting against double additions? */ +static int iio_trigger_attach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf) +{ + int ret = 0; + bool notinuse + = bitmap_empty(trig->pool, CONFIG_IIO_CONSUMERS_PER_TRIGGER); + + /* Prevent the module from being removed whilst attached to a trigger */ + __module_get(pf->indio_dev->info->driver_module); + pf->irq = iio_trigger_get_irq(trig); + ret = request_threaded_irq(pf->irq, pf->h, pf->thread, + pf->type, pf->name, + pf); + if (ret < 0) { + module_put(pf->indio_dev->info->driver_module); + return ret; + } + + if (trig->ops && trig->ops->set_trigger_state && notinuse) { + ret = trig->ops->set_trigger_state(trig, true); + if (ret < 0) + module_put(pf->indio_dev->info->driver_module); + } + + return ret; +} + +static int iio_trigger_detach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf) +{ + int ret = 0; + bool no_other_users + = (bitmap_weight(trig->pool, + CONFIG_IIO_CONSUMERS_PER_TRIGGER) + == 1); + if (trig->ops && trig->ops->set_trigger_state && no_other_users) { + ret = trig->ops->set_trigger_state(trig, false); + if (ret) + return ret; + } + iio_trigger_put_irq(trig, pf->irq); + free_irq(pf->irq, pf); + module_put(pf->indio_dev->info->driver_module); + + return ret; +} + +irqreturn_t iio_pollfunc_store_time(int irq, void *p) +{ + struct iio_poll_func *pf = p; + pf->timestamp = iio_get_time_ns(); + return IRQ_WAKE_THREAD; +} +EXPORT_SYMBOL(iio_pollfunc_store_time); + +struct iio_poll_func +*iio_alloc_pollfunc(irqreturn_t (*h)(int irq, void *p), + irqreturn_t (*thread)(int irq, void *p), + int type, + struct iio_dev *indio_dev, + const char *fmt, + ...) +{ + va_list vargs; + struct iio_poll_func *pf; + + pf = kmalloc(sizeof *pf, GFP_KERNEL); + if (pf == NULL) + return NULL; + va_start(vargs, fmt); + pf->name = kvasprintf(GFP_KERNEL, fmt, vargs); + va_end(vargs); + if (pf->name == NULL) { + kfree(pf); + return NULL; + } + pf->h = h; + pf->thread = thread; + pf->type = type; + pf->indio_dev = indio_dev; + + return pf; +} +EXPORT_SYMBOL_GPL(iio_alloc_pollfunc); + +void iio_dealloc_pollfunc(struct iio_poll_func *pf) +{ + kfree(pf->name); + kfree(pf); +} +EXPORT_SYMBOL_GPL(iio_dealloc_pollfunc); + +/** + * iio_trigger_read_current() - trigger consumer sysfs query current trigger + * + * For trigger consumers the current_trigger interface allows the trigger + * used by the device to be queried. + **/ +static ssize_t iio_trigger_read_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + + if (indio_dev->trig) + return sprintf(buf, "%s\n", indio_dev->trig->name); + return 0; +} + +/** + * iio_trigger_write_current() - trigger consumer sysfs set current trigger + * + * For trigger consumers the current_trigger interface allows the trigger + * used for this device to be specified at run time based on the trigger's + * name. + **/ +static ssize_t iio_trigger_write_current(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct iio_trigger *oldtrig = indio_dev->trig; + struct iio_trigger *trig; + int ret; + + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + mutex_unlock(&indio_dev->mlock); + + trig = iio_trigger_find_by_name(buf, len); + if (oldtrig == trig) + return len; + + if (trig && indio_dev->info->validate_trigger) { + ret = indio_dev->info->validate_trigger(indio_dev, trig); + if (ret) + return ret; + } + + if (trig && trig->ops && trig->ops->validate_device) { + ret = trig->ops->validate_device(trig, indio_dev); + if (ret) + return ret; + } + + indio_dev->trig = trig; + + if (oldtrig) + iio_trigger_put(oldtrig); + if (indio_dev->trig) + iio_trigger_get(indio_dev->trig); + + return len; +} + +static DEVICE_ATTR(current_trigger, S_IRUGO | S_IWUSR, + iio_trigger_read_current, + iio_trigger_write_current); + +static struct attribute *iio_trigger_consumer_attrs[] = { + &dev_attr_current_trigger.attr, + NULL, +}; + +static const struct attribute_group iio_trigger_consumer_attr_group = { + .name = "trigger", + .attrs = iio_trigger_consumer_attrs, +}; + +static void iio_trig_release(struct device *device) +{ + struct iio_trigger *trig = to_iio_trigger(device); + int i; + + if (trig->subirq_base) { + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + irq_modify_status(trig->subirq_base + i, + IRQ_NOAUTOEN, + IRQ_NOREQUEST | IRQ_NOPROBE); + irq_set_chip(trig->subirq_base + i, + NULL); + irq_set_handler(trig->subirq_base + i, + NULL); + } + + irq_free_descs(trig->subirq_base, + CONFIG_IIO_CONSUMERS_PER_TRIGGER); + } + kfree(trig->name); + kfree(trig); +} + +static struct device_type iio_trig_type = { + .release = iio_trig_release, + .groups = iio_trig_dev_groups, +}; + +static void iio_trig_subirqmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_trigger *trig + = container_of(chip, + struct iio_trigger, subirq_chip); + trig->subirqs[d->irq - trig->subirq_base].enabled = false; +} + +static void iio_trig_subirqunmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_trigger *trig + = container_of(chip, + struct iio_trigger, subirq_chip); + trig->subirqs[d->irq - trig->subirq_base].enabled = true; +} + +static struct iio_trigger *viio_trigger_alloc(const char *fmt, va_list vargs) +{ + struct iio_trigger *trig; + trig = kzalloc(sizeof *trig, GFP_KERNEL); + if (trig) { + int i; + trig->dev.type = &iio_trig_type; + trig->dev.bus = &iio_bus_type; + device_initialize(&trig->dev); + + mutex_init(&trig->pool_lock); + trig->subirq_base + = irq_alloc_descs(-1, 0, + CONFIG_IIO_CONSUMERS_PER_TRIGGER, + 0); + if (trig->subirq_base < 0) { + kfree(trig); + return NULL; + } + + trig->name = kvasprintf(GFP_KERNEL, fmt, vargs); + if (trig->name == NULL) { + irq_free_descs(trig->subirq_base, + CONFIG_IIO_CONSUMERS_PER_TRIGGER); + kfree(trig); + return NULL; + } + trig->subirq_chip.name = trig->name; + trig->subirq_chip.irq_mask = &iio_trig_subirqmask; + trig->subirq_chip.irq_unmask = &iio_trig_subirqunmask; + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + irq_set_chip(trig->subirq_base + i, + &trig->subirq_chip); + irq_set_handler(trig->subirq_base + i, + &handle_simple_irq); + irq_modify_status(trig->subirq_base + i, + IRQ_NOREQUEST | IRQ_NOAUTOEN, + IRQ_NOPROBE); + } + get_device(&trig->dev); + } + + return trig; +} + +struct iio_trigger *iio_trigger_alloc(const char *fmt, ...) +{ + struct iio_trigger *trig; + va_list vargs; + + va_start(vargs, fmt); + trig = viio_trigger_alloc(fmt, vargs); + va_end(vargs); + + return trig; +} +EXPORT_SYMBOL(iio_trigger_alloc); + +void iio_trigger_free(struct iio_trigger *trig) +{ + if (trig) + put_device(&trig->dev); +} +EXPORT_SYMBOL(iio_trigger_free); + +static void devm_iio_trigger_release(struct device *dev, void *res) +{ + iio_trigger_free(*(struct iio_trigger **)res); +} + +static int devm_iio_trigger_match(struct device *dev, void *res, void *data) +{ + struct iio_trigger **r = res; + + if (!r || !*r) { + WARN_ON(!r || !*r); + return 0; + } + + return *r == data; +} + +/** + * devm_iio_trigger_alloc - Resource-managed iio_trigger_alloc() + * @dev: Device to allocate iio_trigger for + * @fmt: trigger name format. If it includes format + * specifiers, the additional arguments following + * format are formatted and inserted in the resulting + * string replacing their respective specifiers. + * + * Managed iio_trigger_alloc. iio_trigger allocated with this function is + * automatically freed on driver detach. + * + * If an iio_trigger allocated with this function needs to be freed separately, + * devm_iio_trigger_free() must be used. + * + * RETURNS: + * Pointer to allocated iio_trigger on success, NULL on failure. + */ +struct iio_trigger *devm_iio_trigger_alloc(struct device *dev, + const char *fmt, ...) +{ + struct iio_trigger **ptr, *trig; + va_list vargs; + + ptr = devres_alloc(devm_iio_trigger_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return NULL; + + /* use raw alloc_dr for kmalloc caller tracing */ + va_start(vargs, fmt); + trig = viio_trigger_alloc(fmt, vargs); + va_end(vargs); + if (trig) { + *ptr = trig; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return trig; +} +EXPORT_SYMBOL_GPL(devm_iio_trigger_alloc); + +/** + * devm_iio_trigger_free - Resource-managed iio_trigger_free() + * @dev: Device this iio_dev belongs to + * @iio_trig: the iio_trigger associated with the device + * + * Free iio_trigger allocated with devm_iio_trigger_alloc(). + */ +void devm_iio_trigger_free(struct device *dev, struct iio_trigger *iio_trig) +{ + int rc; + + rc = devres_release(dev, devm_iio_trigger_release, + devm_iio_trigger_match, iio_trig); + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_iio_trigger_free); + +void iio_device_register_trigger_consumer(struct iio_dev *indio_dev) +{ + indio_dev->groups[indio_dev->groupcounter++] = + &iio_trigger_consumer_attr_group; +} + +void iio_device_unregister_trigger_consumer(struct iio_dev *indio_dev) +{ + /* Clean up an associated but not attached trigger reference */ + if (indio_dev->trig) + iio_trigger_put(indio_dev->trig); +} + +int iio_triggered_buffer_postenable(struct iio_dev *indio_dev) +{ + return iio_trigger_attach_poll_func(indio_dev->trig, + indio_dev->pollfunc); +} +EXPORT_SYMBOL(iio_triggered_buffer_postenable); + +int iio_triggered_buffer_predisable(struct iio_dev *indio_dev) +{ + return iio_trigger_detach_poll_func(indio_dev->trig, + indio_dev->pollfunc); +} +EXPORT_SYMBOL(iio_triggered_buffer_predisable); diff --git a/drivers/iio/industrialio-triggered-buffer.c b/drivers/iio/industrialio-triggered-buffer.c new file mode 100644 index 000000000..15a5341b5 --- /dev/null +++ b/drivers/iio/industrialio-triggered-buffer.c @@ -0,0 +1,103 @@ + /* + * Copyright (c) 2012 Analog Devices, Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +/** + * iio_triggered_buffer_setup() - Setup triggered buffer and pollfunc + * @indio_dev: IIO device structure + * @pollfunc_bh: Function which will be used as pollfunc bottom half + * @pollfunc_th: Function which will be used as pollfunc top half + * @setup_ops: Buffer setup functions to use for this device. + * If NULL the default setup functions for triggered + * buffers will be used. + * + * This function combines some common tasks which will normally be performed + * when setting up a triggered buffer. It will allocate the buffer and the + * pollfunc. + * + * Before calling this function the indio_dev structure should already be + * completely initialized, but not yet registered. In practice this means that + * this function should be called right before iio_device_register(). + * + * To free the resources allocated by this function call + * iio_triggered_buffer_cleanup(). + */ +int iio_triggered_buffer_setup(struct iio_dev *indio_dev, + irqreturn_t (*pollfunc_bh)(int irq, void *p), + irqreturn_t (*pollfunc_th)(int irq, void *p), + const struct iio_buffer_setup_ops *setup_ops) +{ + struct iio_buffer *buffer; + int ret; + + buffer = iio_kfifo_allocate(); + if (!buffer) { + ret = -ENOMEM; + goto error_ret; + } + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->pollfunc = iio_alloc_pollfunc(pollfunc_bh, + pollfunc_th, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + indio_dev->name, + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_kfifo_free; + } + + /* Ring buffer functions - here trigger setup related */ + if (setup_ops) + indio_dev->setup_ops = setup_ops; + else + indio_dev->setup_ops = &iio_triggered_buffer_setup_ops; + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + return 0; + +error_kfifo_free: + iio_kfifo_free(indio_dev->buffer); +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_triggered_buffer_setup); + +/** + * iio_triggered_buffer_cleanup() - Free resources allocated by iio_triggered_buffer_setup() + * @indio_dev: IIO device structure + */ +void iio_triggered_buffer_cleanup(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_kfifo_free(indio_dev->buffer); +} +EXPORT_SYMBOL(iio_triggered_buffer_cleanup); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("IIO helper functions for setting up triggered buffers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c new file mode 100644 index 000000000..c8bad3cf8 --- /dev/null +++ b/drivers/iio/inkern.c @@ -0,0 +1,664 @@ +/* The industrial I/O core in kernel channel mapping + * + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#include <linux/err.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/of.h> + +#include <linux/iio/iio.h> +#include "iio_core.h" +#include <linux/iio/machine.h> +#include <linux/iio/driver.h> +#include <linux/iio/consumer.h> + +struct iio_map_internal { + struct iio_dev *indio_dev; + struct iio_map *map; + struct list_head l; +}; + +static LIST_HEAD(iio_map_list); +static DEFINE_MUTEX(iio_map_list_lock); + +int iio_map_array_register(struct iio_dev *indio_dev, struct iio_map *maps) +{ + int i = 0, ret = 0; + struct iio_map_internal *mapi; + + if (maps == NULL) + return 0; + + mutex_lock(&iio_map_list_lock); + while (maps[i].consumer_dev_name != NULL) { + mapi = kzalloc(sizeof(*mapi), GFP_KERNEL); + if (mapi == NULL) { + ret = -ENOMEM; + goto error_ret; + } + mapi->map = &maps[i]; + mapi->indio_dev = indio_dev; + list_add(&mapi->l, &iio_map_list); + i++; + } +error_ret: + mutex_unlock(&iio_map_list_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_map_array_register); + + +/* + * Remove all map entries associated with the given iio device + */ +int iio_map_array_unregister(struct iio_dev *indio_dev) +{ + int ret = -ENODEV; + struct iio_map_internal *mapi; + struct list_head *pos, *tmp; + + mutex_lock(&iio_map_list_lock); + list_for_each_safe(pos, tmp, &iio_map_list) { + mapi = list_entry(pos, struct iio_map_internal, l); + if (indio_dev == mapi->indio_dev) { + list_del(&mapi->l); + kfree(mapi); + ret = 0; + } + } + mutex_unlock(&iio_map_list_lock); + return ret; +} +EXPORT_SYMBOL_GPL(iio_map_array_unregister); + +static const struct iio_chan_spec +*iio_chan_spec_from_name(const struct iio_dev *indio_dev, const char *name) +{ + int i; + const struct iio_chan_spec *chan = NULL; + + for (i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].datasheet_name && + strcmp(name, indio_dev->channels[i].datasheet_name) == 0) { + chan = &indio_dev->channels[i]; + break; + } + return chan; +} + +#ifdef CONFIG_OF + +static int iio_dev_node_match(struct device *dev, void *data) +{ + return dev->of_node == data && dev->type == &iio_device_type; +} + +/** + * __of_iio_simple_xlate - translate iiospec to the IIO channel index + * @indio_dev: pointer to the iio_dev structure + * @iiospec: IIO specifier as found in the device tree + * + * This is simple translation function, suitable for the most 1:1 mapped + * channels in IIO chips. This function performs only one sanity check: + * whether IIO index is less than num_channels (that is specified in the + * iio_dev). + */ +static int __of_iio_simple_xlate(struct iio_dev *indio_dev, + const struct of_phandle_args *iiospec) +{ + if (!iiospec->args_count) + return 0; + + if (iiospec->args[0] >= indio_dev->num_channels) { + dev_err(&indio_dev->dev, "invalid channel index %u\n", + iiospec->args[0]); + return -EINVAL; + } + + return iiospec->args[0]; +} + +static int __of_iio_channel_get(struct iio_channel *channel, + struct device_node *np, int index) +{ + struct device *idev; + struct iio_dev *indio_dev; + int err; + struct of_phandle_args iiospec; + + err = of_parse_phandle_with_args(np, "io-channels", + "#io-channel-cells", + index, &iiospec); + if (err) + return err; + + idev = bus_find_device(&iio_bus_type, NULL, iiospec.np, + iio_dev_node_match); + of_node_put(iiospec.np); + if (idev == NULL) + return -EPROBE_DEFER; + + indio_dev = dev_to_iio_dev(idev); + channel->indio_dev = indio_dev; + if (indio_dev->info->of_xlate) + index = indio_dev->info->of_xlate(indio_dev, &iiospec); + else + index = __of_iio_simple_xlate(indio_dev, &iiospec); + if (index < 0) + goto err_put; + channel->channel = &indio_dev->channels[index]; + + return 0; + +err_put: + iio_device_put(indio_dev); + return index; +} + +static struct iio_channel *of_iio_channel_get(struct device_node *np, int index) +{ + struct iio_channel *channel; + int err; + + if (index < 0) + return ERR_PTR(-EINVAL); + + channel = kzalloc(sizeof(*channel), GFP_KERNEL); + if (channel == NULL) + return ERR_PTR(-ENOMEM); + + err = __of_iio_channel_get(channel, np, index); + if (err) + goto err_free_channel; + + return channel; + +err_free_channel: + kfree(channel); + return ERR_PTR(err); +} + +static struct iio_channel *of_iio_channel_get_by_name(struct device_node *np, + const char *name) +{ + struct iio_channel *chan = NULL; + + /* Walk up the tree of devices looking for a matching iio channel */ + while (np) { + int index = 0; + + /* + * For named iio channels, first look up the name in the + * "io-channel-names" property. If it cannot be found, the + * index will be an error code, and of_iio_channel_get() + * will fail. + */ + if (name) + index = of_property_match_string(np, "io-channel-names", + name); + chan = of_iio_channel_get(np, index); + if (!IS_ERR(chan) || PTR_ERR(chan) == -EPROBE_DEFER) + break; + else if (name && index >= 0) { + pr_err("ERROR: could not get IIO channel %s:%s(%i)\n", + np->full_name, name ? name : "", index); + return NULL; + } + + /* + * No matching IIO channel found on this node. + * If the parent node has a "io-channel-ranges" property, + * then we can try one of its channels. + */ + np = np->parent; + if (np && !of_get_property(np, "io-channel-ranges", NULL)) + return NULL; + } + + return chan; +} + +static struct iio_channel *of_iio_channel_get_all(struct device *dev) +{ + struct iio_channel *chans; + int i, mapind, nummaps = 0; + int ret; + + do { + ret = of_parse_phandle_with_args(dev->of_node, + "io-channels", + "#io-channel-cells", + nummaps, NULL); + if (ret < 0) + break; + } while (++nummaps); + + if (nummaps == 0) /* no error, return NULL to search map table */ + return NULL; + + /* NULL terminated array to save passing size */ + chans = kcalloc(nummaps + 1, sizeof(*chans), GFP_KERNEL); + if (chans == NULL) + return ERR_PTR(-ENOMEM); + + /* Search for OF matches */ + for (mapind = 0; mapind < nummaps; mapind++) { + ret = __of_iio_channel_get(&chans[mapind], dev->of_node, + mapind); + if (ret) + goto error_free_chans; + } + return chans; + +error_free_chans: + for (i = 0; i < mapind; i++) + iio_device_put(chans[i].indio_dev); + kfree(chans); + return ERR_PTR(ret); +} + +#else /* CONFIG_OF */ + +static inline struct iio_channel * +of_iio_channel_get_by_name(struct device_node *np, const char *name) +{ + return NULL; +} + +static inline struct iio_channel *of_iio_channel_get_all(struct device *dev) +{ + return NULL; +} + +#endif /* CONFIG_OF */ + +static struct iio_channel *iio_channel_get_sys(const char *name, + const char *channel_name) +{ + struct iio_map_internal *c_i = NULL, *c = NULL; + struct iio_channel *channel; + int err; + + if (name == NULL && channel_name == NULL) + return ERR_PTR(-ENODEV); + + /* first find matching entry the channel map */ + mutex_lock(&iio_map_list_lock); + list_for_each_entry(c_i, &iio_map_list, l) { + if ((name && strcmp(name, c_i->map->consumer_dev_name) != 0) || + (channel_name && + strcmp(channel_name, c_i->map->consumer_channel) != 0)) + continue; + c = c_i; + iio_device_get(c->indio_dev); + break; + } + mutex_unlock(&iio_map_list_lock); + if (c == NULL) + return ERR_PTR(-ENODEV); + + channel = kzalloc(sizeof(*channel), GFP_KERNEL); + if (channel == NULL) { + err = -ENOMEM; + goto error_no_mem; + } + + channel->indio_dev = c->indio_dev; + + if (c->map->adc_channel_label) { + channel->channel = + iio_chan_spec_from_name(channel->indio_dev, + c->map->adc_channel_label); + + if (channel->channel == NULL) { + err = -EINVAL; + goto error_no_chan; + } + } + + return channel; + +error_no_chan: + kfree(channel); +error_no_mem: + iio_device_put(c->indio_dev); + return ERR_PTR(err); +} + +struct iio_channel *iio_channel_get(struct device *dev, + const char *channel_name) +{ + const char *name = dev ? dev_name(dev) : NULL; + struct iio_channel *channel; + + if (dev) { + channel = of_iio_channel_get_by_name(dev->of_node, + channel_name); + if (channel != NULL) + return channel; + } + + return iio_channel_get_sys(name, channel_name); +} +EXPORT_SYMBOL_GPL(iio_channel_get); + +void iio_channel_release(struct iio_channel *channel) +{ + iio_device_put(channel->indio_dev); + kfree(channel); +} +EXPORT_SYMBOL_GPL(iio_channel_release); + +struct iio_channel *iio_channel_get_all(struct device *dev) +{ + const char *name; + struct iio_channel *chans; + struct iio_map_internal *c = NULL; + int nummaps = 0; + int mapind = 0; + int i, ret; + + if (dev == NULL) + return ERR_PTR(-EINVAL); + + chans = of_iio_channel_get_all(dev); + if (chans) + return chans; + + name = dev_name(dev); + + mutex_lock(&iio_map_list_lock); + /* first count the matching maps */ + list_for_each_entry(c, &iio_map_list, l) + if (name && strcmp(name, c->map->consumer_dev_name) != 0) + continue; + else + nummaps++; + + if (nummaps == 0) { + ret = -ENODEV; + goto error_ret; + } + + /* NULL terminated array to save passing size */ + chans = kzalloc(sizeof(*chans)*(nummaps + 1), GFP_KERNEL); + if (chans == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + /* for each map fill in the chans element */ + list_for_each_entry(c, &iio_map_list, l) { + if (name && strcmp(name, c->map->consumer_dev_name) != 0) + continue; + chans[mapind].indio_dev = c->indio_dev; + chans[mapind].data = c->map->consumer_data; + chans[mapind].channel = + iio_chan_spec_from_name(chans[mapind].indio_dev, + c->map->adc_channel_label); + if (chans[mapind].channel == NULL) { + ret = -EINVAL; + goto error_free_chans; + } + iio_device_get(chans[mapind].indio_dev); + mapind++; + } + if (mapind == 0) { + ret = -ENODEV; + goto error_free_chans; + } + mutex_unlock(&iio_map_list_lock); + + return chans; + +error_free_chans: + for (i = 0; i < nummaps; i++) + iio_device_put(chans[i].indio_dev); + kfree(chans); +error_ret: + mutex_unlock(&iio_map_list_lock); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(iio_channel_get_all); + +void iio_channel_release_all(struct iio_channel *channels) +{ + struct iio_channel *chan = &channels[0]; + + while (chan->indio_dev) { + iio_device_put(chan->indio_dev); + chan++; + } + kfree(channels); +} +EXPORT_SYMBOL_GPL(iio_channel_release_all); + +static int iio_channel_read(struct iio_channel *chan, int *val, int *val2, + enum iio_chan_info_enum info) +{ + int unused; + int vals[INDIO_MAX_RAW_ELEMENTS]; + int ret; + int val_len = 2; + + if (val2 == NULL) + val2 = &unused; + + if(!iio_channel_has_info(chan->channel, info)) + return -EINVAL; + + if (chan->indio_dev->info->read_raw_multi) { + ret = chan->indio_dev->info->read_raw_multi(chan->indio_dev, + chan->channel, INDIO_MAX_RAW_ELEMENTS, + vals, &val_len, info); + *val = vals[0]; + *val2 = vals[1]; + } else + ret = chan->indio_dev->info->read_raw(chan->indio_dev, + chan->channel, val, val2, info); + + return ret; +} + +int iio_read_channel_raw(struct iio_channel *chan, int *val) +{ + int ret; + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW); +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_read_channel_raw); + +int iio_read_channel_average_raw(struct iio_channel *chan, int *val) +{ + int ret; + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_AVERAGE_RAW); +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_read_channel_average_raw); + +static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan, + int raw, int *processed, unsigned int scale) +{ + int scale_type, scale_val, scale_val2, offset; + s64 raw64 = raw; + int ret; + + ret = iio_channel_read(chan, &offset, NULL, IIO_CHAN_INFO_OFFSET); + if (ret >= 0) + raw64 += offset; + + scale_type = iio_channel_read(chan, &scale_val, &scale_val2, + IIO_CHAN_INFO_SCALE); + if (scale_type < 0) + return scale_type; + + switch (scale_type) { + case IIO_VAL_INT: + *processed = raw64 * scale_val; + break; + case IIO_VAL_INT_PLUS_MICRO: + if (scale_val2 < 0) + *processed = -raw64 * scale_val; + else + *processed = raw64 * scale_val; + *processed += div_s64(raw64 * (s64)scale_val2 * scale, + 1000000LL); + break; + case IIO_VAL_INT_PLUS_NANO: + if (scale_val2 < 0) + *processed = -raw64 * scale_val; + else + *processed = raw64 * scale_val; + *processed += div_s64(raw64 * (s64)scale_val2 * scale, + 1000000000LL); + break; + case IIO_VAL_FRACTIONAL: + *processed = div_s64(raw64 * (s64)scale_val * scale, + scale_val2); + break; + case IIO_VAL_FRACTIONAL_LOG2: + *processed = (raw64 * (s64)scale_val * scale) >> scale_val2; + break; + default: + return -EINVAL; + } + + return 0; +} + +int iio_convert_raw_to_processed(struct iio_channel *chan, int raw, + int *processed, unsigned int scale) +{ + int ret; + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + ret = iio_convert_raw_to_processed_unlocked(chan, raw, processed, + scale); +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_convert_raw_to_processed); + +int iio_read_channel_processed(struct iio_channel *chan, int *val) +{ + int ret; + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) { + ret = iio_channel_read(chan, val, NULL, + IIO_CHAN_INFO_PROCESSED); + } else { + ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW); + if (ret < 0) + goto err_unlock; + ret = iio_convert_raw_to_processed_unlocked(chan, *val, val, 1); + } + +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_read_channel_processed); + +int iio_read_channel_scale(struct iio_channel *chan, int *val, int *val2) +{ + int ret; + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + ret = iio_channel_read(chan, val, val2, IIO_CHAN_INFO_SCALE); +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_read_channel_scale); + +int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type) +{ + int ret = 0; + /* Need to verify underlying driver has not gone away */ + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + *type = chan->channel->type; +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_get_channel_type); + +static int iio_channel_write(struct iio_channel *chan, int val, int val2, + enum iio_chan_info_enum info) +{ + return chan->indio_dev->info->write_raw(chan->indio_dev, + chan->channel, val, val2, info); +} + +int iio_write_channel_raw(struct iio_channel *chan, int val) +{ + int ret; + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + ret = iio_channel_write(chan, val, 0, IIO_CHAN_INFO_RAW); +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_write_channel_raw); diff --git a/drivers/iio/kfifo_buf.c b/drivers/iio/kfifo_buf.c new file mode 100644 index 000000000..55c267bbf --- /dev/null +++ b/drivers/iio/kfifo_buf.c @@ -0,0 +1,219 @@ +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/kfifo.h> +#include <linux/mutex.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/sched.h> +#include <linux/poll.h> + +struct iio_kfifo { + struct iio_buffer buffer; + struct kfifo kf; + struct mutex user_lock; + int update_needed; +}; + +#define iio_to_kfifo(r) container_of(r, struct iio_kfifo, buffer) + +static inline int __iio_allocate_kfifo(struct iio_kfifo *buf, + int bytes_per_datum, int length) +{ + if ((length == 0) || (bytes_per_datum == 0)) + return -EINVAL; + + return __kfifo_alloc((struct __kfifo *)&buf->kf, length, + bytes_per_datum, GFP_KERNEL); +} + +static int iio_request_update_kfifo(struct iio_buffer *r) +{ + int ret = 0; + struct iio_kfifo *buf = iio_to_kfifo(r); + + mutex_lock(&buf->user_lock); + if (buf->update_needed) { + kfifo_free(&buf->kf); + ret = __iio_allocate_kfifo(buf, buf->buffer.bytes_per_datum, + buf->buffer.length); + if (ret >= 0) + buf->update_needed = false; + } else { + kfifo_reset_out(&buf->kf); + } + mutex_unlock(&buf->user_lock); + + return ret; +} + +static int iio_mark_update_needed_kfifo(struct iio_buffer *r) +{ + struct iio_kfifo *kf = iio_to_kfifo(r); + kf->update_needed = true; + return 0; +} + +static int iio_set_bytes_per_datum_kfifo(struct iio_buffer *r, size_t bpd) +{ + if (r->bytes_per_datum != bpd) { + r->bytes_per_datum = bpd; + iio_mark_update_needed_kfifo(r); + } + return 0; +} + +static int iio_set_length_kfifo(struct iio_buffer *r, int length) +{ + /* Avoid an invalid state */ + if (length < 2) + length = 2; + if (r->length != length) { + r->length = length; + iio_mark_update_needed_kfifo(r); + } + return 0; +} + +static int iio_store_to_kfifo(struct iio_buffer *r, + const void *data) +{ + int ret; + struct iio_kfifo *kf = iio_to_kfifo(r); + ret = kfifo_in(&kf->kf, data, 1); + if (ret != 1) + return -EBUSY; + return 0; +} + +static int iio_read_first_n_kfifo(struct iio_buffer *r, + size_t n, char __user *buf) +{ + int ret, copied; + struct iio_kfifo *kf = iio_to_kfifo(r); + + if (mutex_lock_interruptible(&kf->user_lock)) + return -ERESTARTSYS; + + if (!kfifo_initialized(&kf->kf) || n < kfifo_esize(&kf->kf)) + ret = -EINVAL; + else + ret = kfifo_to_user(&kf->kf, buf, n, &copied); + mutex_unlock(&kf->user_lock); + if (ret < 0) + return ret; + + return copied; +} + +static size_t iio_kfifo_buf_data_available(struct iio_buffer *r) +{ + struct iio_kfifo *kf = iio_to_kfifo(r); + size_t samples; + + mutex_lock(&kf->user_lock); + samples = kfifo_len(&kf->kf); + mutex_unlock(&kf->user_lock); + + return samples; +} + +static void iio_kfifo_buffer_release(struct iio_buffer *buffer) +{ + struct iio_kfifo *kf = iio_to_kfifo(buffer); + + mutex_destroy(&kf->user_lock); + kfifo_free(&kf->kf); + kfree(kf); +} + +static const struct iio_buffer_access_funcs kfifo_access_funcs = { + .store_to = &iio_store_to_kfifo, + .read_first_n = &iio_read_first_n_kfifo, + .data_available = iio_kfifo_buf_data_available, + .request_update = &iio_request_update_kfifo, + .set_bytes_per_datum = &iio_set_bytes_per_datum_kfifo, + .set_length = &iio_set_length_kfifo, + .release = &iio_kfifo_buffer_release, +}; + +struct iio_buffer *iio_kfifo_allocate(void) +{ + struct iio_kfifo *kf; + + kf = kzalloc(sizeof(*kf), GFP_KERNEL); + if (!kf) + return NULL; + + kf->update_needed = true; + iio_buffer_init(&kf->buffer); + kf->buffer.access = &kfifo_access_funcs; + kf->buffer.length = 2; + mutex_init(&kf->user_lock); + + return &kf->buffer; +} +EXPORT_SYMBOL(iio_kfifo_allocate); + +void iio_kfifo_free(struct iio_buffer *r) +{ + iio_buffer_put(r); +} +EXPORT_SYMBOL(iio_kfifo_free); + +static void devm_iio_kfifo_release(struct device *dev, void *res) +{ + iio_kfifo_free(*(struct iio_buffer **)res); +} + +static int devm_iio_kfifo_match(struct device *dev, void *res, void *data) +{ + struct iio_buffer **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +/** + * devm_iio_fifo_allocate - Resource-managed iio_kfifo_allocate() + * @dev: Device to allocate kfifo buffer for + * + * RETURNS: + * Pointer to allocated iio_buffer on success, NULL on failure. + */ +struct iio_buffer *devm_iio_kfifo_allocate(struct device *dev) +{ + struct iio_buffer **ptr, *r; + + ptr = devres_alloc(devm_iio_kfifo_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return NULL; + + r = iio_kfifo_allocate(); + if (r) { + *ptr = r; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return r; +} +EXPORT_SYMBOL(devm_iio_kfifo_allocate); + +/** + * devm_iio_fifo_free - Resource-managed iio_kfifo_free() + * @dev: Device the buffer belongs to + * @r: The buffer associated with the device + */ +void devm_iio_kfifo_free(struct device *dev, struct iio_buffer *r) +{ + WARN_ON(devres_release(dev, devm_iio_kfifo_release, + devm_iio_kfifo_match, r)); +} +EXPORT_SYMBOL(devm_iio_kfifo_free); + +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig new file mode 100644 index 000000000..01a1a16ab --- /dev/null +++ b/drivers/iio/light/Kconfig @@ -0,0 +1,231 @@ +# +# Light sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Light sensors" + +config ADJD_S311 + tristate "ADJD-S311-CR999 digital color sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + If you say yes here you get support for the Avago ADJD-S311-CR999 + digital color light sensor. + + This driver can also be built as a module. If so, the module + will be called adjd_s311. + +config AL3320A + tristate "AL3320A ambient light sensor" + depends on I2C + help + Say Y here if you want to build a driver for the Dyna Image AL3320A + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called al3320a. + +config APDS9300 + tristate "APDS9300 ambient light sensor" + depends on I2C + help + Say Y here if you want to build a driver for the Avago APDS9300 + ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called apds9300. + +config CM32181 + depends on I2C + tristate "CM32181 driver" + help + Say Y here if you use cm32181. + This option enables ambient light sensor using + Capella cm32181 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm32181. + +config CM3232 + depends on I2C + tristate "CM3232 ambient light sensor" + help + Say Y here if you use cm3232. + This option enables ambient light sensor using + Capella Microsystems cm3232 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm3232. + +config CM3323 + depends on I2C + tristate "Capella CM3323 color light sensor" + help + Say Y here if you want to build a driver for Capela CM3323 + color sensor. + + To compile this driver as a module, choose M here: the module will + be called cm3323. + +config CM36651 + depends on I2C + tristate "CM36651 driver" + help + Say Y here if you use cm36651. + This option enables proximity & RGB sensor using + Capella cm36651 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm36651. + +config GP2AP020A00F + tristate "Sharp GP2AP020A00F Proximity/ALS sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IRQ_WORK + help + Say Y here if you have a Sharp GP2AP020A00F proximity/ALS combo-chip + hooked to an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called gp2ap020a00f. + +config ISL29125 + tristate "Intersil ISL29125 digital color light sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here if you want to build a driver for the Intersil ISL29125 + RGB light sensor for I2C. + + To compile this driver as a module, choose M here: the module will be + called isl29125. + +config HID_SENSOR_ALS + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID ALS" + help + Say yes here to build support for the HID SENSOR + Ambient light sensor. + + To compile this driver as a module, choose M here: the + module will be called hid-sensor-als. + +config HID_SENSOR_PROX + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID PROX" + help + Say yes here to build support for the HID SENSOR + Proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called hid-sensor-prox. + +config JSA1212 + tristate "JSA1212 ALS and proximity sensor driver" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build a IIO driver for JSA1212 + proximity & ALS sensor device. + + To compile this driver as a module, choose M here: + the module will be called jsa1212. + +config SENSORS_LM3533 + tristate "LM3533 ambient light sensor" + depends on MFD_LM3533 + help + If you say yes here you get support for the ambient light sensor + interface on National Semiconductor / TI LM3533 Lighting Power + chips. + + The sensor interface can be used to control the LEDs and backlights + of the chip through defining five light zones and three sets of + corresponding output-current values. + + The driver provides raw and mean adc readings along with the current + light zone through sysfs. A threshold event can be generated on zone + changes. The ALS-control output values can be set per zone for the + three current output channels. + +config LTR501 + tristate "LTR-501ALS-01 light sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the Lite-On LTR-501ALS-01 + ambient light and proximity sensor. + + This driver can also be built as a module. If so, the module + will be called ltr501. + +config TCS3414 + tristate "TAOS TCS3414 digital color sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the TAOS TCS3414 + family of digital color sensors. + + This driver can also be built as a module. If so, the module + will be called tcs3414. + +config TCS3472 + tristate "TAOS TCS3472 color light-to-digital converter" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for the TAOS TCS3472 + family of color light-to-digital converters with IR filter. + + This driver can also be built as a module. If so, the module + will be called tcs3472. + +config SENSORS_TSL2563 + tristate "TAOS TSL2560, TSL2561, TSL2562 and TSL2563 ambient light sensors" + depends on I2C + help + If you say yes here you get support for the Taos TSL2560, + TSL2561, TSL2562 and TSL2563 ambient light sensors. + + This driver can also be built as a module. If so, the module + will be called tsl2563. + +config TSL4531 + tristate "TAOS TSL4531 ambient light sensors" + depends on I2C + help + Say Y here if you want to build a driver for the TAOS TSL4531 family + of ambient light sensors with direct lux output. + + To compile this driver as a module, choose M here: the + module will be called tsl4531. + +config VCNL4000 + tristate "VCNL4000 combined ALS and proximity sensor" + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VCNL4000 + combined ambient light and proximity sensor. + + To compile this driver as a module, choose M here: the + module will be called vcnl4000. + +endmenu diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile new file mode 100644 index 000000000..ad7c30fe4 --- /dev/null +++ b/drivers/iio/light/Makefile @@ -0,0 +1,24 @@ +# +# Makefile for IIO Light sensors +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ADJD_S311) += adjd_s311.o +obj-$(CONFIG_AL3320A) += al3320a.o +obj-$(CONFIG_APDS9300) += apds9300.o +obj-$(CONFIG_CM32181) += cm32181.o +obj-$(CONFIG_CM3232) += cm3232.o +obj-$(CONFIG_CM3323) += cm3323.o +obj-$(CONFIG_CM36651) += cm36651.o +obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o +obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o +obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o +obj-$(CONFIG_ISL29125) += isl29125.o +obj-$(CONFIG_JSA1212) += jsa1212.o +obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o +obj-$(CONFIG_LTR501) += ltr501.o +obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o +obj-$(CONFIG_TCS3414) += tcs3414.o +obj-$(CONFIG_TCS3472) += tcs3472.o +obj-$(CONFIG_TSL4531) += tsl4531.o +obj-$(CONFIG_VCNL4000) += vcnl4000.o diff --git a/drivers/iio/light/adjd_s311.c b/drivers/iio/light/adjd_s311.c new file mode 100644 index 000000000..09ad5f1ce --- /dev/null +++ b/drivers/iio/light/adjd_s311.c @@ -0,0 +1,321 @@ +/* + * adjd_s311.c - Support for ADJD-S311-CR999 digital color sensor + * + * Copyright (C) 2012 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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. + * + * driver for ADJD-S311-CR999 digital color sensor (10-bit channels for + * red, green, blue, clear); 7-bit I2C slave address 0x74 + * + * limitations: no calibration, no offset mode, no sleep mode + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/bitmap.h> +#include <linux/err.h> +#include <linux/irq.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define ADJD_S311_DRV_NAME "adjd_s311" + +#define ADJD_S311_CTRL 0x00 +#define ADJD_S311_CONFIG 0x01 +#define ADJD_S311_CAP_RED 0x06 +#define ADJD_S311_CAP_GREEN 0x07 +#define ADJD_S311_CAP_BLUE 0x08 +#define ADJD_S311_CAP_CLEAR 0x09 +#define ADJD_S311_INT_RED 0x0a +#define ADJD_S311_INT_GREEN 0x0c +#define ADJD_S311_INT_BLUE 0x0e +#define ADJD_S311_INT_CLEAR 0x10 +#define ADJD_S311_DATA_RED 0x40 +#define ADJD_S311_DATA_GREEN 0x42 +#define ADJD_S311_DATA_BLUE 0x44 +#define ADJD_S311_DATA_CLEAR 0x46 +#define ADJD_S311_OFFSET_RED 0x48 +#define ADJD_S311_OFFSET_GREEN 0x49 +#define ADJD_S311_OFFSET_BLUE 0x4a +#define ADJD_S311_OFFSET_CLEAR 0x4b + +#define ADJD_S311_CTRL_GOFS 0x02 +#define ADJD_S311_CTRL_GSSR 0x01 +#define ADJD_S311_CAP_MASK 0x0f +#define ADJD_S311_INT_MASK 0x0fff +#define ADJD_S311_DATA_MASK 0x03ff + +struct adjd_s311_data { + struct i2c_client *client; + u16 *buffer; +}; + +enum adjd_s311_channel_idx { + IDX_RED, IDX_GREEN, IDX_BLUE, IDX_CLEAR +}; + +#define ADJD_S311_DATA_REG(chan) (ADJD_S311_DATA_RED + (chan) * 2) +#define ADJD_S311_INT_REG(chan) (ADJD_S311_INT_RED + (chan) * 2) +#define ADJD_S311_CAP_REG(chan) (ADJD_S311_CAP_RED + (chan)) + +static int adjd_s311_req_data(struct iio_dev *indio_dev) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + int tries = 10; + + int ret = i2c_smbus_write_byte_data(data->client, ADJD_S311_CTRL, + ADJD_S311_CTRL_GSSR); + if (ret < 0) + return ret; + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, ADJD_S311_CTRL); + if (ret < 0) + return ret; + if (!(ret & ADJD_S311_CTRL_GSSR)) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, + "adjd_s311_req_data() failed, data not ready\n"); + return -EIO; + } + + return 0; +} + +static int adjd_s311_read_data(struct iio_dev *indio_dev, u8 reg, int *val) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + + int ret = adjd_s311_req_data(indio_dev); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(data->client, reg); + if (ret < 0) + return ret; + + *val = ret & ADJD_S311_DATA_MASK; + + return 0; +} + +static irqreturn_t adjd_s311_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adjd_s311_data *data = iio_priv(indio_dev); + s64 time_ns = iio_get_time_ns(); + int i, j = 0; + + int ret = adjd_s311_req_data(indio_dev); + if (ret < 0) + goto done; + + for_each_set_bit(i, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = i2c_smbus_read_word_data(data->client, + ADJD_S311_DATA_REG(i)); + if (ret < 0) + goto done; + + data->buffer[j++] = ret & ADJD_S311_DATA_MASK; + } + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, time_ns); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define ADJD_S311_CHANNEL(_color, _scan_idx) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .address = (IDX_##_color), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = (IIO_MOD_LIGHT_##_color), \ + .scan_index = (_scan_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 10, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec adjd_s311_channels[] = { + ADJD_S311_CHANNEL(RED, 0), + ADJD_S311_CHANNEL(GREEN, 1), + ADJD_S311_CHANNEL(BLUE, 2), + ADJD_S311_CHANNEL(CLEAR, 3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int adjd_s311_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = adjd_s311_read_data(indio_dev, + ADJD_S311_DATA_REG(chan->address), val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = i2c_smbus_read_byte_data(data->client, + ADJD_S311_CAP_REG(chan->address)); + if (ret < 0) + return ret; + *val = ret & ADJD_S311_CAP_MASK; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + ret = i2c_smbus_read_word_data(data->client, + ADJD_S311_INT_REG(chan->address)); + if (ret < 0) + return ret; + *val = 0; + /* + * not documented, based on measurement: + * 4095 LSBs correspond to roughly 4 ms + */ + *val2 = ret & ADJD_S311_INT_MASK; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int adjd_s311_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + if (val < 0 || val > ADJD_S311_CAP_MASK) + return -EINVAL; + + return i2c_smbus_write_byte_data(data->client, + ADJD_S311_CAP_REG(chan->address), val); + case IIO_CHAN_INFO_INT_TIME: + if (val != 0 || val2 < 0 || val2 > ADJD_S311_INT_MASK) + return -EINVAL; + + return i2c_smbus_write_word_data(data->client, + ADJD_S311_INT_REG(chan->address), val2); + } + return -EINVAL; +} + +static int adjd_s311_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + + kfree(data->buffer); + data->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (data->buffer == NULL) + return -ENOMEM; + + return 0; +} + +static const struct iio_info adjd_s311_info = { + .read_raw = adjd_s311_read_raw, + .write_raw = adjd_s311_write_raw, + .update_scan_mode = adjd_s311_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static int adjd_s311_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adjd_s311_data *data; + struct iio_dev *indio_dev; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &adjd_s311_info; + indio_dev->name = ADJD_S311_DRV_NAME; + indio_dev->channels = adjd_s311_channels; + indio_dev->num_channels = ARRAY_SIZE(adjd_s311_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + err = iio_triggered_buffer_setup(indio_dev, NULL, + adjd_s311_trigger_handler, NULL); + if (err < 0) + return err; + + err = iio_device_register(indio_dev); + if (err) + goto exit_unreg_buffer; + + dev_info(&client->dev, "ADJD-S311 color sensor registered\n"); + + return 0; + +exit_unreg_buffer: + iio_triggered_buffer_cleanup(indio_dev); + return err; +} + +static int adjd_s311_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct adjd_s311_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + kfree(data->buffer); + + return 0; +} + +static const struct i2c_device_id adjd_s311_id[] = { + { "adjd_s311", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adjd_s311_id); + +static struct i2c_driver adjd_s311_driver = { + .driver = { + .name = ADJD_S311_DRV_NAME, + }, + .probe = adjd_s311_probe, + .remove = adjd_s311_remove, + .id_table = adjd_s311_id, +}; +module_i2c_driver(adjd_s311_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("ADJD-S311 color sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/al3320a.c b/drivers/iio/light/al3320a.c new file mode 100644 index 000000000..6aac6513f --- /dev/null +++ b/drivers/iio/light/al3320a.c @@ -0,0 +1,232 @@ +/* + * AL3320A - Dyna Image Ambient Light Sensor + * + * Copyright (c) 2014, Intel Corporation. + * + * 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 AL3320A (7-bit I2C slave address 0x1C). + * + * TODO: interrupt support, thresholds + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define AL3320A_DRV_NAME "al3320a" + +#define AL3320A_REG_CONFIG 0x00 +#define AL3320A_REG_STATUS 0x01 +#define AL3320A_REG_INT 0x02 +#define AL3320A_REG_WAIT 0x06 +#define AL3320A_REG_CONFIG_RANGE 0x07 +#define AL3320A_REG_PERSIST 0x08 +#define AL3320A_REG_MEAN_TIME 0x09 +#define AL3320A_REG_ADUMMY 0x0A +#define AL3320A_REG_DATA_LOW 0x22 + +#define AL3320A_REG_LOW_THRESH_LOW 0x30 +#define AL3320A_REG_LOW_THRESH_HIGH 0x31 +#define AL3320A_REG_HIGH_THRESH_LOW 0x32 +#define AL3320A_REG_HIGH_THRESH_HIGH 0x33 + +#define AL3320A_CONFIG_DISABLE 0x00 +#define AL3320A_CONFIG_ENABLE 0x01 + +#define AL3320A_GAIN_SHIFT 1 +#define AL3320A_GAIN_MASK (BIT(2) | BIT(1)) + +/* chip params default values */ +#define AL3320A_DEFAULT_MEAN_TIME 4 +#define AL3320A_DEFAULT_WAIT_TIME 0 /* no waiting */ + +#define AL3320A_SCALE_AVAILABLE "0.512 0.128 0.032 0.01" + +enum al3320a_range { + AL3320A_RANGE_1, /* 33.28 Klx */ + AL3320A_RANGE_2, /* 8.32 Klx */ + AL3320A_RANGE_3, /* 2.08 Klx */ + AL3320A_RANGE_4 /* 0.65 Klx */ +}; + +static const int al3320a_scales[][2] = { + {0, 512000}, {0, 128000}, {0, 32000}, {0, 10000} +}; + +struct al3320a_data { + struct i2c_client *client; +}; + +static const struct iio_chan_spec al3320a_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + } +}; + +static IIO_CONST_ATTR(in_illuminance_scale_available, AL3320A_SCALE_AVAILABLE); + +static struct attribute *al3320a_attributes[] = { + &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group al3320a_attribute_group = { + .attrs = al3320a_attributes, +}; + +static int al3320a_init(struct al3320a_data *data) +{ + int ret; + + /* power on */ + ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_CONFIG, + AL3320A_CONFIG_ENABLE); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_CONFIG_RANGE, + AL3320A_RANGE_3 << AL3320A_GAIN_SHIFT); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_MEAN_TIME, + AL3320A_DEFAULT_MEAN_TIME); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, AL3320A_REG_WAIT, + AL3320A_DEFAULT_WAIT_TIME); + if (ret < 0) + return ret; + + return 0; +} + +static int al3320a_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct al3320a_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * ALS ADC value is stored in two adjacent registers: + * - low byte of output is stored at AL3320A_REG_DATA_LOW + * - high byte of output is stored at AL3320A_REG_DATA_LOW + 1 + */ + ret = i2c_smbus_read_word_data(data->client, + AL3320A_REG_DATA_LOW); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = i2c_smbus_read_byte_data(data->client, + AL3320A_REG_CONFIG_RANGE); + if (ret < 0) + return ret; + + ret = (ret & AL3320A_GAIN_MASK) >> AL3320A_GAIN_SHIFT; + *val = al3320a_scales[ret][0]; + *val2 = al3320a_scales[ret][1]; + + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int al3320a_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct al3320a_data *data = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(al3320a_scales); i++) { + if (val == al3320a_scales[i][0] && + val2 == al3320a_scales[i][1]) + return i2c_smbus_write_byte_data(data->client, + AL3320A_REG_CONFIG_RANGE, + i << AL3320A_GAIN_SHIFT); + } + break; + } + return -EINVAL; +} + +static const struct iio_info al3320a_info = { + .driver_module = THIS_MODULE, + .read_raw = al3320a_read_raw, + .write_raw = al3320a_write_raw, + .attrs = &al3320a_attribute_group, +}; + +static int al3320a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct al3320a_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &al3320a_info; + indio_dev->name = AL3320A_DRV_NAME; + indio_dev->channels = al3320a_channels; + indio_dev->num_channels = ARRAY_SIZE(al3320a_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = al3320a_init(data); + if (ret < 0) { + dev_err(&client->dev, "al3320a chip init failed\n"); + return ret; + } + return devm_iio_device_register(&client->dev, indio_dev); +} + +static int al3320a_remove(struct i2c_client *client) +{ + return i2c_smbus_write_byte_data(client, AL3320A_REG_CONFIG, + AL3320A_CONFIG_DISABLE); +} + +static const struct i2c_device_id al3320a_id[] = { + {"al3320a", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, al3320a_id); + +static struct i2c_driver al3320a_driver = { + .driver = { + .name = AL3320A_DRV_NAME, + }, + .probe = al3320a_probe, + .remove = al3320a_remove, + .id_table = al3320a_id, +}; + +module_i2c_driver(al3320a_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("AL3320A Ambient Light Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/apds9300.c b/drivers/iio/light/apds9300.c new file mode 100644 index 000000000..9ddde0ca9 --- /dev/null +++ b/drivers/iio/light/apds9300.c @@ -0,0 +1,531 @@ +/* + * apds9300.c - IIO driver for Avago APDS9300 ambient light sensor + * + * Copyright 2013 Oleksandr Kravchenko <o.v.kravchenko@globallogic.com> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> + +#define APDS9300_DRV_NAME "apds9300" +#define APDS9300_IRQ_NAME "apds9300_event" + +/* Command register bits */ +#define APDS9300_CMD BIT(7) /* Select command register. Must write as 1 */ +#define APDS9300_WORD BIT(5) /* I2C write/read: if 1 word, if 0 byte */ +#define APDS9300_CLEAR BIT(6) /* Interrupt clear. Clears pending interrupt */ + +/* Register set */ +#define APDS9300_CONTROL 0x00 /* Control of basic functions */ +#define APDS9300_THRESHLOWLOW 0x02 /* Low byte of low interrupt threshold */ +#define APDS9300_THRESHHIGHLOW 0x04 /* Low byte of high interrupt threshold */ +#define APDS9300_INTERRUPT 0x06 /* Interrupt control */ +#define APDS9300_DATA0LOW 0x0c /* Low byte of ADC channel 0 */ +#define APDS9300_DATA1LOW 0x0e /* Low byte of ADC channel 1 */ + +/* Power on/off value for APDS9300_CONTROL register */ +#define APDS9300_POWER_ON 0x03 +#define APDS9300_POWER_OFF 0x00 + +/* Interrupts */ +#define APDS9300_INTR_ENABLE 0x10 +/* Interrupt Persist Function: Any value outside of threshold range */ +#define APDS9300_THRESH_INTR 0x01 + +#define APDS9300_THRESH_MAX 0xffff /* Max threshold value */ + +struct apds9300_data { + struct i2c_client *client; + struct mutex mutex; + int power_state; + int thresh_low; + int thresh_hi; + int intr_en; +}; + +/* Lux calculation */ + +/* Calculated values 1000 * (CH1/CH0)^1.4 for CH1/CH0 from 0 to 0.52 */ +static const u16 apds9300_lux_ratio[] = { + 0, 2, 4, 7, 11, 15, 19, 24, 29, 34, 40, 45, 51, 57, 64, 70, 77, 84, 91, + 98, 105, 112, 120, 128, 136, 144, 152, 160, 168, 177, 185, 194, 203, + 212, 221, 230, 239, 249, 258, 268, 277, 287, 297, 307, 317, 327, 337, + 347, 358, 368, 379, 390, 400, +}; + +static unsigned long apds9300_calculate_lux(u16 ch0, u16 ch1) +{ + unsigned long lux, tmp; + + /* avoid division by zero */ + if (ch0 == 0) + return 0; + + tmp = DIV_ROUND_UP(ch1 * 100, ch0); + if (tmp <= 52) { + lux = 3150 * ch0 - (unsigned long)DIV_ROUND_UP_ULL(ch0 + * apds9300_lux_ratio[tmp] * 5930ull, 1000); + } else if (tmp <= 65) { + lux = 2290 * ch0 - 2910 * ch1; + } else if (tmp <= 80) { + lux = 1570 * ch0 - 1800 * ch1; + } else if (tmp <= 130) { + lux = 338 * ch0 - 260 * ch1; + } else { + lux = 0; + } + + return lux / 100000; +} + +static int apds9300_get_adc_val(struct apds9300_data *data, int adc_number) +{ + int ret; + u8 flags = APDS9300_CMD | APDS9300_WORD; + + if (!data->power_state) + return -EBUSY; + + /* Select ADC0 or ADC1 data register */ + flags |= adc_number ? APDS9300_DATA1LOW : APDS9300_DATA0LOW; + + ret = i2c_smbus_read_word_data(data->client, flags); + if (ret < 0) + dev_err(&data->client->dev, + "failed to read ADC%d value\n", adc_number); + + return ret; +} + +static int apds9300_set_thresh_low(struct apds9300_data *data, int value) +{ + int ret; + + if (!data->power_state) + return -EBUSY; + + if (value > APDS9300_THRESH_MAX) + return -EINVAL; + + ret = i2c_smbus_write_word_data(data->client, APDS9300_THRESHLOWLOW + | APDS9300_CMD | APDS9300_WORD, value); + if (ret) { + dev_err(&data->client->dev, "failed to set thresh_low\n"); + return ret; + } + data->thresh_low = value; + + return 0; +} + +static int apds9300_set_thresh_hi(struct apds9300_data *data, int value) +{ + int ret; + + if (!data->power_state) + return -EBUSY; + + if (value > APDS9300_THRESH_MAX) + return -EINVAL; + + ret = i2c_smbus_write_word_data(data->client, APDS9300_THRESHHIGHLOW + | APDS9300_CMD | APDS9300_WORD, value); + if (ret) { + dev_err(&data->client->dev, "failed to set thresh_hi\n"); + return ret; + } + data->thresh_hi = value; + + return 0; +} + +static int apds9300_set_intr_state(struct apds9300_data *data, int state) +{ + int ret; + u8 cmd; + + if (!data->power_state) + return -EBUSY; + + cmd = state ? APDS9300_INTR_ENABLE | APDS9300_THRESH_INTR : 0x00; + ret = i2c_smbus_write_byte_data(data->client, + APDS9300_INTERRUPT | APDS9300_CMD, cmd); + if (ret) { + dev_err(&data->client->dev, + "failed to set interrupt state %d\n", state); + return ret; + } + data->intr_en = state; + + return 0; +} + +static int apds9300_set_power_state(struct apds9300_data *data, int state) +{ + int ret; + u8 cmd; + + cmd = state ? APDS9300_POWER_ON : APDS9300_POWER_OFF; + ret = i2c_smbus_write_byte_data(data->client, + APDS9300_CONTROL | APDS9300_CMD, cmd); + if (ret) { + dev_err(&data->client->dev, + "failed to set power state %d\n", state); + return ret; + } + data->power_state = state; + + return 0; +} + +static void apds9300_clear_intr(struct apds9300_data *data) +{ + int ret; + + ret = i2c_smbus_write_byte(data->client, APDS9300_CLEAR | APDS9300_CMD); + if (ret < 0) + dev_err(&data->client->dev, "failed to clear interrupt\n"); +} + +static int apds9300_chip_init(struct apds9300_data *data) +{ + int ret; + + /* Need to set power off to ensure that the chip is off */ + ret = apds9300_set_power_state(data, 0); + if (ret < 0) + goto err; + /* + * Probe the chip. To do so we try to power up the device and then to + * read back the 0x03 code + */ + ret = apds9300_set_power_state(data, 1); + if (ret < 0) + goto err; + ret = i2c_smbus_read_byte_data(data->client, + APDS9300_CONTROL | APDS9300_CMD); + if (ret != APDS9300_POWER_ON) { + ret = -ENODEV; + goto err; + } + /* + * Disable interrupt to ensure thai it is doesn't enable + * i.e. after device soft reset + */ + ret = apds9300_set_intr_state(data, 0); + if (ret < 0) + goto err; + + return 0; + +err: + dev_err(&data->client->dev, "failed to init the chip\n"); + return ret; +} + +static int apds9300_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + int ch0, ch1, ret = -EINVAL; + struct apds9300_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + switch (chan->type) { + case IIO_LIGHT: + ch0 = apds9300_get_adc_val(data, 0); + if (ch0 < 0) { + ret = ch0; + break; + } + ch1 = apds9300_get_adc_val(data, 1); + if (ch1 < 0) { + ret = ch1; + break; + } + *val = apds9300_calculate_lux(ch0, ch1); + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + ret = apds9300_get_adc_val(data, chan->channel); + if (ret < 0) + break; + *val = ret; + ret = IIO_VAL_INT; + break; + default: + break; + } + mutex_unlock(&data->mutex); + + return ret; +} + +static int apds9300_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 apds9300_data *data = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = data->thresh_hi; + break; + case IIO_EV_DIR_FALLING: + *val = data->thresh_low; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int apds9300_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 apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + if (dir == IIO_EV_DIR_RISING) + ret = apds9300_set_thresh_hi(data, val); + else + ret = apds9300_set_thresh_low(data, val); + mutex_unlock(&data->mutex); + + return ret; +} + +static int apds9300_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct apds9300_data *data = iio_priv(indio_dev); + + return data->intr_en; +} + +static int apds9300_write_interrupt_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 apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = apds9300_set_intr_state(data, state); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_info apds9300_info_no_irq = { + .driver_module = THIS_MODULE, + .read_raw = apds9300_read_raw, +}; + +static const struct iio_info apds9300_info = { + .driver_module = THIS_MODULE, + .read_raw = apds9300_read_raw, + .read_event_value = apds9300_read_thresh, + .write_event_value = apds9300_write_thresh, + .read_event_config = apds9300_read_interrupt_config, + .write_event_config = apds9300_write_interrupt_config, +}; + +static const struct iio_event_spec apds9300_event_spec[] = { + { + .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 apds9300_channels[] = { + { + .type = IIO_LIGHT, + .channel = 0, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .channel = 0, + .channel2 = IIO_MOD_LIGHT_BOTH, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = apds9300_event_spec, + .num_event_specs = ARRAY_SIZE(apds9300_event_spec), + }, { + .type = IIO_INTENSITY, + .channel = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, +}; + +static irqreturn_t apds9300_interrupt_handler(int irq, void *private) +{ + struct iio_dev *dev_info = private; + struct apds9300_data *data = iio_priv(dev_info); + + iio_push_event(dev_info, + IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns()); + + apds9300_clear_intr(data); + + return IRQ_HANDLED; +} + +static int apds9300_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct apds9300_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + ret = apds9300_chip_init(data); + if (ret < 0) + goto err; + + mutex_init(&data->mutex); + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = apds9300_channels; + indio_dev->num_channels = ARRAY_SIZE(apds9300_channels); + indio_dev->name = APDS9300_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) + indio_dev->info = &apds9300_info; + else + indio_dev->info = &apds9300_info_no_irq; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, apds9300_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + APDS9300_IRQ_NAME, indio_dev); + if (ret) { + dev_err(&client->dev, "irq request error %d\n", -ret); + goto err; + } + } + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto err; + + return 0; + +err: + /* Ensure that power off in case of error */ + apds9300_set_power_state(data, 0); + return ret; +} + +static int apds9300_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct apds9300_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + /* Ensure that power off and interrupts are disabled */ + apds9300_set_intr_state(data, 0); + apds9300_set_power_state(data, 0); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int apds9300_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = apds9300_set_power_state(data, 0); + mutex_unlock(&data->mutex); + + return ret; +} + +static int apds9300_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct apds9300_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = apds9300_set_power_state(data, 1); + mutex_unlock(&data->mutex); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(apds9300_pm_ops, apds9300_suspend, apds9300_resume); +#define APDS9300_PM_OPS (&apds9300_pm_ops) +#else +#define APDS9300_PM_OPS NULL +#endif + +static struct i2c_device_id apds9300_id[] = { + { APDS9300_DRV_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, apds9300_id); + +static struct i2c_driver apds9300_driver = { + .driver = { + .name = APDS9300_DRV_NAME, + .owner = THIS_MODULE, + .pm = APDS9300_PM_OPS, + }, + .probe = apds9300_probe, + .remove = apds9300_remove, + .id_table = apds9300_id, +}; + +module_i2c_driver(apds9300_driver); + +MODULE_AUTHOR("Kravchenko Oleksandr <o.v.kravchenko@globallogic.com>"); +MODULE_AUTHOR("GlobalLogic inc."); +MODULE_DESCRIPTION("APDS9300 ambient light photo sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/cm32181.c b/drivers/iio/light/cm32181.c new file mode 100644 index 000000000..5d12ae54d --- /dev/null +++ b/drivers/iio/light/cm32181.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2013 Capella Microsystems Inc. + * Author: Kevin Tsai <ktsai@capellamicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/init.h> + +/* Registers Address */ +#define CM32181_REG_ADDR_CMD 0x00 +#define CM32181_REG_ADDR_ALS 0x04 +#define CM32181_REG_ADDR_STATUS 0x06 +#define CM32181_REG_ADDR_ID 0x07 + +/* Number of Configurable Registers */ +#define CM32181_CONF_REG_NUM 0x01 + +/* CMD register */ +#define CM32181_CMD_ALS_ENABLE 0x00 +#define CM32181_CMD_ALS_DISABLE 0x01 +#define CM32181_CMD_ALS_INT_EN 0x02 + +#define CM32181_CMD_ALS_IT_SHIFT 6 +#define CM32181_CMD_ALS_IT_MASK (0x0F << CM32181_CMD_ALS_IT_SHIFT) +#define CM32181_CMD_ALS_IT_DEFAULT (0x00 << CM32181_CMD_ALS_IT_SHIFT) + +#define CM32181_CMD_ALS_SM_SHIFT 11 +#define CM32181_CMD_ALS_SM_MASK (0x03 << CM32181_CMD_ALS_SM_SHIFT) +#define CM32181_CMD_ALS_SM_DEFAULT (0x01 << CM32181_CMD_ALS_SM_SHIFT) + +#define CM32181_MLUX_PER_BIT 5 /* ALS_SM=01 IT=800ms */ +#define CM32181_MLUX_PER_BIT_BASE_IT 800000 /* Based on IT=800ms */ +#define CM32181_CALIBSCALE_DEFAULT 1000 +#define CM32181_CALIBSCALE_RESOLUTION 1000 +#define MLUX_PER_LUX 1000 + +static const u8 cm32181_reg[CM32181_CONF_REG_NUM] = { + CM32181_REG_ADDR_CMD, +}; + +static const int als_it_bits[] = {12, 8, 0, 1, 2, 3}; +static const int als_it_value[] = {25000, 50000, 100000, 200000, 400000, + 800000}; + +struct cm32181_chip { + struct i2c_client *client; + struct mutex lock; + u16 conf_regs[CM32181_CONF_REG_NUM]; + int calibscale; +}; + +/** + * cm32181_reg_init() - Initialize CM32181 registers + * @cm32181: pointer of struct cm32181. + * + * Initialize CM32181 ambient light sensor register to default values. + * + * Return: 0 for success; otherwise for error code. + */ +static int cm32181_reg_init(struct cm32181_chip *cm32181) +{ + struct i2c_client *client = cm32181->client; + int i; + s32 ret; + + ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ID); + if (ret < 0) + return ret; + + /* check device ID */ + if ((ret & 0xFF) != 0x81) + return -ENODEV; + + /* Default Values */ + cm32181->conf_regs[CM32181_REG_ADDR_CMD] = CM32181_CMD_ALS_ENABLE | + CM32181_CMD_ALS_IT_DEFAULT | CM32181_CMD_ALS_SM_DEFAULT; + cm32181->calibscale = CM32181_CALIBSCALE_DEFAULT; + + /* Initialize registers*/ + for (i = 0; i < CM32181_CONF_REG_NUM; i++) { + ret = i2c_smbus_write_word_data(client, cm32181_reg[i], + cm32181->conf_regs[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +/** + * cm32181_read_als_it() - Get sensor integration time (ms) + * @cm32181: pointer of struct cm32181 + * @val2: pointer of int to load the als_it value. + * + * Report the current integartion time by millisecond. + * + * Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. + */ +static int cm32181_read_als_it(struct cm32181_chip *cm32181, int *val2) +{ + u16 als_it; + int i; + + als_it = cm32181->conf_regs[CM32181_REG_ADDR_CMD]; + als_it &= CM32181_CMD_ALS_IT_MASK; + als_it >>= CM32181_CMD_ALS_IT_SHIFT; + for (i = 0; i < ARRAY_SIZE(als_it_bits); i++) { + if (als_it == als_it_bits[i]) { + *val2 = als_it_value[i]; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +/** + * cm32181_write_als_it() - Write sensor integration time + * @cm32181: pointer of struct cm32181. + * @val: integration time by millisecond. + * + * Convert integration time (ms) to sensor value. + * + * Return: i2c_smbus_write_word_data command return value. + */ +static int cm32181_write_als_it(struct cm32181_chip *cm32181, int val) +{ + struct i2c_client *client = cm32181->client; + u16 als_it; + int ret, i, n; + + n = ARRAY_SIZE(als_it_value); + for (i = 0; i < n; i++) + if (val <= als_it_value[i]) + break; + if (i >= n) + i = n - 1; + + als_it = als_it_bits[i]; + als_it <<= CM32181_CMD_ALS_IT_SHIFT; + + mutex_lock(&cm32181->lock); + cm32181->conf_regs[CM32181_REG_ADDR_CMD] &= + ~CM32181_CMD_ALS_IT_MASK; + cm32181->conf_regs[CM32181_REG_ADDR_CMD] |= + als_it; + ret = i2c_smbus_write_word_data(client, CM32181_REG_ADDR_CMD, + cm32181->conf_regs[CM32181_REG_ADDR_CMD]); + mutex_unlock(&cm32181->lock); + + return ret; +} + +/** + * cm32181_get_lux() - report current lux value + * @cm32181: pointer of struct cm32181. + * + * Convert sensor raw data to lux. It depends on integration + * time and calibscale variable. + * + * Return: Positive value is lux, otherwise is error code. + */ +static int cm32181_get_lux(struct cm32181_chip *cm32181) +{ + struct i2c_client *client = cm32181->client; + int ret; + int als_it; + unsigned long lux; + + ret = cm32181_read_als_it(cm32181, &als_it); + if (ret < 0) + return -EINVAL; + + lux = CM32181_MLUX_PER_BIT; + lux *= CM32181_MLUX_PER_BIT_BASE_IT; + lux /= als_it; + + ret = i2c_smbus_read_word_data(client, CM32181_REG_ADDR_ALS); + if (ret < 0) + return ret; + + lux *= ret; + lux *= cm32181->calibscale; + lux /= CM32181_CALIBSCALE_RESOLUTION; + lux /= MLUX_PER_LUX; + + if (lux > 0xFFFF) + lux = 0xFFFF; + + return lux; +} + +static int cm32181_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm32181_chip *cm32181 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = cm32181_get_lux(cm32181); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = cm32181->calibscale; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + ret = cm32181_read_als_it(cm32181, val2); + return ret; + } + + return -EINVAL; +} + +static int cm32181_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm32181_chip *cm32181 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + cm32181->calibscale = val; + return val; + case IIO_CHAN_INFO_INT_TIME: + ret = cm32181_write_als_it(cm32181, val2); + return ret; + } + + return -EINVAL; +} + +/** + * cm32181_get_it_available() - Get available ALS IT value + * @dev: pointer of struct device. + * @attr: pointer of struct device_attribute. + * @buf: pointer of return string buffer. + * + * Display the available integration time values by millisecond. + * + * Return: string length. + */ +static ssize_t cm32181_get_it_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, n, len; + + n = ARRAY_SIZE(als_it_value); + for (i = 0, len = 0; i < n; i++) + len += sprintf(buf + len, "0.%06u ", als_it_value[i]); + return len + sprintf(buf + len, "\n"); +} + +static const struct iio_chan_spec cm32181_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, + S_IRUGO, cm32181_get_it_available, NULL, 0); + +static struct attribute *cm32181_attributes[] = { + &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm32181_attribute_group = { + .attrs = cm32181_attributes +}; + +static const struct iio_info cm32181_info = { + .driver_module = THIS_MODULE, + .read_raw = &cm32181_read_raw, + .write_raw = &cm32181_write_raw, + .attrs = &cm32181_attribute_group, +}; + +static int cm32181_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cm32181_chip *cm32181; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*cm32181)); + if (!indio_dev) { + dev_err(&client->dev, "devm_iio_device_alloc failed\n"); + return -ENOMEM; + } + + cm32181 = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + cm32181->client = client; + + mutex_init(&cm32181->lock); + indio_dev->dev.parent = &client->dev; + indio_dev->channels = cm32181_channels; + indio_dev->num_channels = ARRAY_SIZE(cm32181_channels); + indio_dev->info = &cm32181_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm32181_reg_init(cm32181); + if (ret) { + dev_err(&client->dev, + "%s: register init failed\n", + __func__); + return ret; + } + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret) { + dev_err(&client->dev, + "%s: regist device failed\n", + __func__); + return ret; + } + + return 0; +} + +static const struct i2c_device_id cm32181_id[] = { + { "cm32181", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, cm32181_id); + +static const struct of_device_id cm32181_of_match[] = { + { .compatible = "capella,cm32181" }, + { } +}; + +static struct i2c_driver cm32181_driver = { + .driver = { + .name = "cm32181", + .of_match_table = of_match_ptr(cm32181_of_match), + .owner = THIS_MODULE, + }, + .id_table = cm32181_id, + .probe = cm32181_probe, +}; + +module_i2c_driver(cm32181_driver); + +MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>"); +MODULE_DESCRIPTION("CM32181 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/cm3232.c b/drivers/iio/light/cm3232.c new file mode 100644 index 000000000..39c8d99cc --- /dev/null +++ b/drivers/iio/light/cm3232.c @@ -0,0 +1,439 @@ +/* + * CM3232 Ambient Light Sensor + * + * Copyright (C) 2014-2015 Capella Microsystems Inc. + * Author: Kevin Tsai <ktsai@capellamicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + * + * IIO driver for CM3232 (7-bit I2C slave address 0x10). + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/init.h> + +/* Registers Address */ +#define CM3232_REG_ADDR_CMD 0x00 +#define CM3232_REG_ADDR_ALS 0x50 +#define CM3232_REG_ADDR_ID 0x53 + +#define CM3232_CMD_ALS_DISABLE BIT(0) + +#define CM3232_CMD_ALS_IT_SHIFT 2 +#define CM3232_CMD_ALS_IT_MASK (BIT(2) | BIT(3) | BIT(4)) +#define CM3232_CMD_ALS_IT_DEFAULT (0x01 << CM3232_CMD_ALS_IT_SHIFT) + +#define CM3232_CMD_ALS_RESET BIT(6) + +#define CM3232_CMD_DEFAULT CM3232_CMD_ALS_IT_DEFAULT + +#define CM3232_HW_ID 0x32 +#define CM3232_CALIBSCALE_DEFAULT 100000 +#define CM3232_CALIBSCALE_RESOLUTION 100000 +#define CM3232_MLUX_PER_LUX 1000 + +#define CM3232_MLUX_PER_BIT_DEFAULT 64 +#define CM3232_MLUX_PER_BIT_BASE_IT 100000 + +static const struct { + int val; + int val2; + u8 it; +} cm3232_als_it_scales[] = { + {0, 100000, 0}, /* 0.100000 */ + {0, 200000, 1}, /* 0.200000 */ + {0, 400000, 2}, /* 0.400000 */ + {0, 800000, 3}, /* 0.800000 */ + {1, 600000, 4}, /* 1.600000 */ + {3, 200000, 5}, /* 3.200000 */ +}; + +struct cm3232_als_info { + u8 regs_cmd_default; + u8 hw_id; + int calibscale; + int mlux_per_bit; + int mlux_per_bit_base_it; +}; + +static struct cm3232_als_info cm3232_als_info_default = { + .regs_cmd_default = CM3232_CMD_DEFAULT, + .hw_id = CM3232_HW_ID, + .calibscale = CM3232_CALIBSCALE_DEFAULT, + .mlux_per_bit = CM3232_MLUX_PER_BIT_DEFAULT, + .mlux_per_bit_base_it = CM3232_MLUX_PER_BIT_BASE_IT, +}; + +struct cm3232_chip { + struct i2c_client *client; + struct cm3232_als_info *als_info; + u8 regs_cmd; + u16 regs_als; +}; + +/** + * cm3232_reg_init() - Initialize CM3232 + * @chip: pointer of struct cm3232_chip. + * + * Check and initialize CM3232 ambient light sensor. + * + * Return: 0 for success; otherwise for error code. + */ +static int cm3232_reg_init(struct cm3232_chip *chip) +{ + struct i2c_client *client = chip->client; + s32 ret; + + chip->als_info = &cm3232_als_info_default; + + /* Identify device */ + ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ID); + if (ret < 0) { + dev_err(&chip->client->dev, "Error reading addr_id\n"); + return ret; + } + + if ((ret & 0xFF) != chip->als_info->hw_id) + return -ENODEV; + + /* Disable and reset device */ + chip->regs_cmd = CM3232_CMD_ALS_DISABLE | CM3232_CMD_ALS_RESET; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + if (ret < 0) { + dev_err(&chip->client->dev, "Error writing reg_cmd\n"); + return ret; + } + + /* Register default value */ + chip->regs_cmd = chip->als_info->regs_cmd_default; + + /* Configure register */ + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + if (ret < 0) + dev_err(&chip->client->dev, "Error writing reg_cmd\n"); + + return 0; +} + +/** + * cm3232_read_als_it() - Get sensor integration time + * @chip: pointer of struct cm3232_chip + * @val: pointer of int to load the integration (sec). + * @val2: pointer of int to load the integration time (microsecond). + * + * Report the current integration time. + * + * Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL. + */ +static int cm3232_read_als_it(struct cm3232_chip *chip, int *val, int *val2) +{ + u16 als_it; + int i; + + als_it = chip->regs_cmd; + als_it &= CM3232_CMD_ALS_IT_MASK; + als_it >>= CM3232_CMD_ALS_IT_SHIFT; + for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { + if (als_it == cm3232_als_it_scales[i].it) { + *val = cm3232_als_it_scales[i].val; + *val2 = cm3232_als_it_scales[i].val2; + return IIO_VAL_INT_PLUS_MICRO; + } + } + + return -EINVAL; +} + +/** + * cm3232_write_als_it() - Write sensor integration time + * @chip: pointer of struct cm3232_chip. + * @val: integration time in second. + * @val2: integration time in microsecond. + * + * Convert integration time to sensor value. + * + * Return: i2c_smbus_write_byte_data command return value. + */ +static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2) +{ + struct i2c_client *client = chip->client; + u16 als_it, cmd; + int i; + s32 ret; + + for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) { + if (val == cm3232_als_it_scales[i].val && + val2 == cm3232_als_it_scales[i].val2) { + + als_it = cm3232_als_it_scales[i].it; + als_it <<= CM3232_CMD_ALS_IT_SHIFT; + + cmd = chip->regs_cmd & ~CM3232_CMD_ALS_IT_MASK; + cmd |= als_it; + ret = i2c_smbus_write_byte_data(client, + CM3232_REG_ADDR_CMD, + cmd); + if (ret < 0) + return ret; + chip->regs_cmd = cmd; + return 0; + } + } + return -EINVAL; +} + +/** + * cm3232_get_lux() - report current lux value + * @chip: pointer of struct cm3232_chip. + * + * Convert sensor data to lux. It depends on integration + * time and calibscale variable. + * + * Return: Zero or positive value is lux, otherwise error code. + */ +static int cm3232_get_lux(struct cm3232_chip *chip) +{ + struct i2c_client *client = chip->client; + struct cm3232_als_info *als_info = chip->als_info; + int ret; + int val, val2; + int als_it; + u64 lux; + + /* Calculate mlux per bit based on als_it */ + ret = cm3232_read_als_it(chip, &val, &val2); + if (ret < 0) + return -EINVAL; + als_it = val * 1000000 + val2; + lux = (__force u64)als_info->mlux_per_bit; + lux *= als_info->mlux_per_bit_base_it; + lux = div_u64(lux, als_it); + + ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ALS); + if (ret < 0) { + dev_err(&client->dev, "Error reading reg_addr_als\n"); + return ret; + } + + chip->regs_als = (u16)ret; + lux *= chip->regs_als; + lux *= als_info->calibscale; + lux = div_u64(lux, CM3232_CALIBSCALE_RESOLUTION); + lux = div_u64(lux, CM3232_MLUX_PER_LUX); + + if (lux > 0xFFFF) + lux = 0xFFFF; + + return (int)lux; +} + +static int cm3232_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm3232_chip *chip = iio_priv(indio_dev); + struct cm3232_als_info *als_info = chip->als_info; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = cm3232_get_lux(chip); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = als_info->calibscale; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + return cm3232_read_als_it(chip, val, val2); + } + + return -EINVAL; +} + +static int cm3232_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm3232_chip *chip = iio_priv(indio_dev); + struct cm3232_als_info *als_info = chip->als_info; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + als_info->calibscale = val; + return 0; + case IIO_CHAN_INFO_INT_TIME: + return cm3232_write_als_it(chip, val, val2); + } + + return -EINVAL; +} + +/** + * cm3232_get_it_available() - Get available ALS IT value + * @dev: pointer of struct device. + * @attr: pointer of struct device_attribute. + * @buf: pointer of return string buffer. + * + * Display the available integration time in second. + * + * Return: string length. + */ +static ssize_t cm3232_get_it_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, len; + + for (i = 0, len = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ", + cm3232_als_it_scales[i].val, + cm3232_als_it_scales[i].val2); + return len + scnprintf(buf + len, PAGE_SIZE - len, "\n"); +} + +static const struct iio_chan_spec cm3232_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = + BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + } +}; + +static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, + S_IRUGO, cm3232_get_it_available, NULL, 0); + +static struct attribute *cm3232_attributes[] = { + &iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm3232_attribute_group = { + .attrs = cm3232_attributes +}; + +static const struct iio_info cm3232_info = { + .driver_module = THIS_MODULE, + .read_raw = &cm3232_read_raw, + .write_raw = &cm3232_write_raw, + .attrs = &cm3232_attribute_group, +}; + +static int cm3232_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cm3232_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + chip->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = cm3232_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3232_channels); + indio_dev->info = &cm3232_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm3232_reg_init(chip); + if (ret) { + dev_err(&client->dev, + "%s: register init failed\n", + __func__); + return ret; + } + + return iio_device_register(indio_dev); +} + +static int cm3232_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + CM3232_CMD_ALS_DISABLE); + + iio_device_unregister(indio_dev); + + return 0; +} + +static const struct i2c_device_id cm3232_id[] = { + {"cm3232", 0}, + {} +}; + +#ifdef CONFIG_PM_SLEEP +static int cm3232_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct cm3232_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int ret; + + chip->regs_cmd |= CM3232_CMD_ALS_DISABLE; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd); + + return ret; +} + +static int cm3232_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct cm3232_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int ret; + + chip->regs_cmd &= ~CM3232_CMD_ALS_DISABLE; + ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD, + chip->regs_cmd | CM3232_CMD_ALS_RESET); + + return ret; +} + +static const struct dev_pm_ops cm3232_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(cm3232_suspend, cm3232_resume)}; +#endif + +MODULE_DEVICE_TABLE(i2c, cm3232_id); + +static const struct of_device_id cm3232_of_match[] = { + {.compatible = "capella,cm3232"}, + {} +}; + +static struct i2c_driver cm3232_driver = { + .driver = { + .name = "cm3232", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(cm3232_of_match), +#ifdef CONFIG_PM_SLEEP + .pm = &cm3232_pm_ops, +#endif + }, + .id_table = cm3232_id, + .probe = cm3232_probe, + .remove = cm3232_remove, +}; + +module_i2c_driver(cm3232_driver); + +MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>"); +MODULE_DESCRIPTION("CM3232 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/cm3323.c b/drivers/iio/light/cm3323.c new file mode 100644 index 000000000..a1d4905cc --- /dev/null +++ b/drivers/iio/light/cm3323.c @@ -0,0 +1,286 @@ +/* + * CM3323 - Capella Color Light Sensor + * + * Copyright (c) 2015, Intel Corporation. + * + * 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 CM3323 (7-bit I2C slave address 0x10) + * + * TODO: calibscale to correct the lens factor + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/mutex.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define CM3323_DRV_NAME "cm3323" + +#define CM3323_CMD_CONF 0x00 +#define CM3323_CMD_RED_DATA 0x08 +#define CM3323_CMD_GREEN_DATA 0x09 +#define CM3323_CMD_BLUE_DATA 0x0A +#define CM3323_CMD_CLEAR_DATA 0x0B + +#define CM3323_CONF_SD_BIT BIT(0) /* sensor disable */ +#define CM3323_CONF_AF_BIT BIT(1) /* auto/manual force mode */ +#define CM3323_CONF_IT_MASK (BIT(4) | BIT(5) | BIT(6)) +#define CM3323_CONF_IT_SHIFT 4 + +#define CM3323_INT_TIME_AVAILABLE "0.04 0.08 0.16 0.32 0.64 1.28" + +static const struct { + int val; + int val2; +} cm3323_int_time[] = { + {0, 40000}, /* 40 ms */ + {0, 80000}, /* 80 ms */ + {0, 160000}, /* 160 ms */ + {0, 320000}, /* 320 ms */ + {0, 640000}, /* 640 ms */ + {1, 280000}, /* 1280 ms */ +}; + +struct cm3323_data { + struct i2c_client *client; + u16 reg_conf; + struct mutex mutex; +}; + +#define CM3323_COLOR_CHANNEL(_color, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .address = _addr, \ +} + +static const struct iio_chan_spec cm3323_channels[] = { + CM3323_COLOR_CHANNEL(RED, CM3323_CMD_RED_DATA), + CM3323_COLOR_CHANNEL(GREEN, CM3323_CMD_GREEN_DATA), + CM3323_COLOR_CHANNEL(BLUE, CM3323_CMD_BLUE_DATA), + CM3323_COLOR_CHANNEL(CLEAR, CM3323_CMD_CLEAR_DATA), +}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL(CM3323_INT_TIME_AVAILABLE); + +static struct attribute *cm3323_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group cm3323_attribute_group = { + .attrs = cm3323_attributes, +}; + +static int cm3323_init(struct iio_dev *indio_dev) +{ + int ret; + struct cm3323_data *data = iio_priv(indio_dev); + + ret = i2c_smbus_read_word_data(data->client, CM3323_CMD_CONF); + if (ret < 0) { + dev_err(&data->client->dev, "Error reading reg_conf\n"); + return ret; + } + + /* enable sensor and set auto force mode */ + ret &= ~(CM3323_CONF_SD_BIT | CM3323_CONF_AF_BIT); + + ret = i2c_smbus_write_word_data(data->client, CM3323_CMD_CONF, ret); + if (ret < 0) { + dev_err(&data->client->dev, "Error writing reg_conf\n"); + return ret; + } + + data->reg_conf = ret; + + return 0; +} + +static void cm3323_disable(struct iio_dev *indio_dev) +{ + int ret; + struct cm3323_data *data = iio_priv(indio_dev); + + ret = i2c_smbus_write_word_data(data->client, CM3323_CMD_CONF, + CM3323_CONF_SD_BIT); + if (ret < 0) + dev_err(&data->client->dev, "Error writing reg_conf\n"); +} + +static int cm3323_set_it_bits(struct cm3323_data *data, int val, int val2) +{ + int i, ret; + u16 reg_conf; + + for (i = 0; i < ARRAY_SIZE(cm3323_int_time); i++) { + if (val == cm3323_int_time[i].val && + val2 == cm3323_int_time[i].val2) { + reg_conf = data->reg_conf & ~CM3323_CONF_IT_MASK; + reg_conf |= i << CM3323_CONF_IT_SHIFT; + + ret = i2c_smbus_write_word_data(data->client, + CM3323_CMD_CONF, + reg_conf); + if (ret < 0) + return ret; + + data->reg_conf = reg_conf; + return 0; + } + } + return -EINVAL; +} + +static int cm3323_get_it_bits(struct cm3323_data *data) +{ + int bits; + + bits = (data->reg_conf & CM3323_CONF_IT_MASK) >> + CM3323_CONF_IT_SHIFT; + + if (bits >= ARRAY_SIZE(cm3323_int_time)) + return -EINVAL; + return bits; +} + +static int cm3323_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int i, ret; + struct cm3323_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->mutex); + ret = i2c_smbus_read_word_data(data->client, chan->address); + if (ret < 0) { + mutex_unlock(&data->mutex); + return ret; + } + *val = ret; + mutex_unlock(&data->mutex); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->mutex); + i = cm3323_get_it_bits(data); + if (i < 0) { + mutex_unlock(&data->mutex); + return -EINVAL; + } + + *val = cm3323_int_time[i].val; + *val2 = cm3323_int_time[i].val2; + mutex_unlock(&data->mutex); + + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int cm3323_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct cm3323_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->mutex); + ret = cm3323_set_it_bits(data, val, val2); + mutex_unlock(&data->mutex); + + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info cm3323_info = { + .driver_module = THIS_MODULE, + .read_raw = cm3323_read_raw, + .write_raw = cm3323_write_raw, + .attrs = &cm3323_attribute_group, +}; + +static int cm3323_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cm3323_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + mutex_init(&data->mutex); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &cm3323_info; + indio_dev->name = CM3323_DRV_NAME; + indio_dev->channels = cm3323_channels; + indio_dev->num_channels = ARRAY_SIZE(cm3323_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm3323_init(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "cm3323 chip init failed\n"); + return ret; + } + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "failed to register iio dev\n"); + goto err_init; + } + return 0; +err_init: + cm3323_disable(indio_dev); + return ret; +} + +static int cm3323_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + cm3323_disable(indio_dev); + + return 0; +} + +static const struct i2c_device_id cm3323_id[] = { + {"cm3323", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cm3323_id); + +static struct i2c_driver cm3323_driver = { + .driver = { + .name = CM3323_DRV_NAME, + }, + .probe = cm3323_probe, + .remove = cm3323_remove, + .id_table = cm3323_id, +}; + +module_i2c_driver(cm3323_driver); + +MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>"); +MODULE_DESCRIPTION("Capella CM3323 Color Light Sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/cm36651.c b/drivers/iio/light/cm36651.c new file mode 100644 index 000000000..39fc67e82 --- /dev/null +++ b/drivers/iio/light/cm36651.c @@ -0,0 +1,750 @@ +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Beomho Seo <beomho.seo@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published + * by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> + +/* Slave address 0x19 for PS of 7 bit addressing protocol for I2C */ +#define CM36651_I2C_ADDR_PS 0x19 +/* Alert Response Address */ +#define CM36651_ARA 0x0C + +/* Ambient light sensor */ +#define CM36651_CS_CONF1 0x00 +#define CM36651_CS_CONF2 0x01 +#define CM36651_ALS_WH_M 0x02 +#define CM36651_ALS_WH_L 0x03 +#define CM36651_ALS_WL_M 0x04 +#define CM36651_ALS_WL_L 0x05 +#define CM36651_CS_CONF3 0x06 +#define CM36651_CS_CONF_REG_NUM 0x02 + +/* Proximity sensor */ +#define CM36651_PS_CONF1 0x00 +#define CM36651_PS_THD 0x01 +#define CM36651_PS_CANC 0x02 +#define CM36651_PS_CONF2 0x03 +#define CM36651_PS_REG_NUM 0x04 + +/* CS_CONF1 command code */ +#define CM36651_ALS_ENABLE 0x00 +#define CM36651_ALS_DISABLE 0x01 +#define CM36651_ALS_INT_EN 0x02 +#define CM36651_ALS_THRES 0x04 + +/* CS_CONF2 command code */ +#define CM36651_CS_CONF2_DEFAULT_BIT 0x08 + +/* CS_CONF3 channel integration time */ +#define CM36651_CS_IT1 0x00 /* Integration time 80 msec */ +#define CM36651_CS_IT2 0x40 /* Integration time 160 msec */ +#define CM36651_CS_IT3 0x80 /* Integration time 320 msec */ +#define CM36651_CS_IT4 0xC0 /* Integration time 640 msec */ + +/* PS_CONF1 command code */ +#define CM36651_PS_ENABLE 0x00 +#define CM36651_PS_DISABLE 0x01 +#define CM36651_PS_INT_EN 0x02 +#define CM36651_PS_PERS2 0x04 +#define CM36651_PS_PERS3 0x08 +#define CM36651_PS_PERS4 0x0C + +/* PS_CONF1 command code: integration time */ +#define CM36651_PS_IT1 0x00 /* Integration time 0.32 msec */ +#define CM36651_PS_IT2 0x10 /* Integration time 0.42 msec */ +#define CM36651_PS_IT3 0x20 /* Integration time 0.52 msec */ +#define CM36651_PS_IT4 0x30 /* Integration time 0.64 msec */ + +/* PS_CONF1 command code: duty ratio */ +#define CM36651_PS_DR1 0x00 /* Duty ratio 1/80 */ +#define CM36651_PS_DR2 0x40 /* Duty ratio 1/160 */ +#define CM36651_PS_DR3 0x80 /* Duty ratio 1/320 */ +#define CM36651_PS_DR4 0xC0 /* Duty ratio 1/640 */ + +/* PS_THD command code */ +#define CM36651_PS_INITIAL_THD 0x05 + +/* PS_CANC command code */ +#define CM36651_PS_CANC_DEFAULT 0x00 + +/* PS_CONF2 command code */ +#define CM36651_PS_HYS1 0x00 +#define CM36651_PS_HYS2 0x01 +#define CM36651_PS_SMART_PERS_EN 0x02 +#define CM36651_PS_DIR_INT 0x04 +#define CM36651_PS_MS 0x10 + +#define CM36651_CS_COLOR_NUM 4 + +#define CM36651_CLOSE_PROXIMITY 0x32 +#define CM36651_FAR_PROXIMITY 0x33 + +#define CM36651_CS_INT_TIME_AVAIL "0.08 0.16 0.32 0.64" +#define CM36651_PS_INT_TIME_AVAIL "0.000320 0.000420 0.000520 0.000640" + +enum cm36651_operation_mode { + CM36651_LIGHT_EN, + CM36651_PROXIMITY_EN, + CM36651_PROXIMITY_EV_EN, +}; + +enum cm36651_light_channel_idx { + CM36651_LIGHT_CHANNEL_IDX_RED, + CM36651_LIGHT_CHANNEL_IDX_GREEN, + CM36651_LIGHT_CHANNEL_IDX_BLUE, + CM36651_LIGHT_CHANNEL_IDX_CLEAR, +}; + +enum cm36651_command { + CM36651_CMD_READ_RAW_LIGHT, + CM36651_CMD_READ_RAW_PROXIMITY, + CM36651_CMD_PROX_EV_EN, + CM36651_CMD_PROX_EV_DIS, +}; + +static const u8 cm36651_cs_reg[CM36651_CS_CONF_REG_NUM] = { + CM36651_CS_CONF1, + CM36651_CS_CONF2, +}; + +static const u8 cm36651_ps_reg[CM36651_PS_REG_NUM] = { + CM36651_PS_CONF1, + CM36651_PS_THD, + CM36651_PS_CANC, + CM36651_PS_CONF2, +}; + +struct cm36651_data { + const struct cm36651_platform_data *pdata; + struct i2c_client *client; + struct i2c_client *ps_client; + struct i2c_client *ara_client; + struct mutex lock; + struct regulator *vled_reg; + unsigned long flags; + int cs_int_time[CM36651_CS_COLOR_NUM]; + int ps_int_time; + u8 cs_ctrl_regs[CM36651_CS_CONF_REG_NUM]; + u8 ps_ctrl_regs[CM36651_PS_REG_NUM]; + u16 color[CM36651_CS_COLOR_NUM]; +}; + +static int cm36651_setup_reg(struct cm36651_data *cm36651) +{ + struct i2c_client *client = cm36651->client; + struct i2c_client *ps_client = cm36651->ps_client; + int i, ret; + + /* CS initialization */ + cm36651->cs_ctrl_regs[CM36651_CS_CONF1] = CM36651_ALS_ENABLE | + CM36651_ALS_THRES; + cm36651->cs_ctrl_regs[CM36651_CS_CONF2] = CM36651_CS_CONF2_DEFAULT_BIT; + + for (i = 0; i < CM36651_CS_CONF_REG_NUM; i++) { + ret = i2c_smbus_write_byte_data(client, cm36651_cs_reg[i], + cm36651->cs_ctrl_regs[i]); + if (ret < 0) + return ret; + } + + /* PS initialization */ + cm36651->ps_ctrl_regs[CM36651_PS_CONF1] = CM36651_PS_ENABLE | + CM36651_PS_IT2; + cm36651->ps_ctrl_regs[CM36651_PS_THD] = CM36651_PS_INITIAL_THD; + cm36651->ps_ctrl_regs[CM36651_PS_CANC] = CM36651_PS_CANC_DEFAULT; + cm36651->ps_ctrl_regs[CM36651_PS_CONF2] = CM36651_PS_HYS2 | + CM36651_PS_DIR_INT | CM36651_PS_SMART_PERS_EN; + + for (i = 0; i < CM36651_PS_REG_NUM; i++) { + ret = i2c_smbus_write_byte_data(ps_client, cm36651_ps_reg[i], + cm36651->ps_ctrl_regs[i]); + if (ret < 0) + return ret; + } + + /* Set shutdown mode */ + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, + CM36651_ALS_DISABLE); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(cm36651->ps_client, + CM36651_PS_CONF1, CM36651_PS_DISABLE); + if (ret < 0) + return ret; + + return 0; +} + +static int cm36651_read_output(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int *val) +{ + struct i2c_client *client = cm36651->client; + int ret = -EINVAL; + + switch (chan->type) { + case IIO_LIGHT: + *val = i2c_smbus_read_word_data(client, chan->address); + if (*val < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, + CM36651_ALS_DISABLE); + if (ret < 0) + return ret; + + ret = IIO_VAL_INT; + break; + case IIO_PROXIMITY: + *val = i2c_smbus_read_byte(cm36651->ps_client); + if (*val < 0) + return ret; + + if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { + ret = i2c_smbus_write_byte_data(cm36651->ps_client, + CM36651_PS_CONF1, CM36651_PS_DISABLE); + if (ret < 0) + return ret; + } + + ret = IIO_VAL_INT; + break; + default: + break; + } + + return ret; +} + +static irqreturn_t cm36651_irq_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct cm36651_data *cm36651 = iio_priv(indio_dev); + struct i2c_client *client = cm36651->client; + int ev_dir, ret; + u64 ev_code; + + /* + * The PS INT pin is an active low signal that PS INT move logic low + * when the object is detect. Once the MCU host received the PS INT + * "LOW" signal, the Host needs to read the data at Alert Response + * Address(ARA) to clear the PS INT signal. After clearing the PS + * INT pin, the PS INT signal toggles from low to high. + */ + ret = i2c_smbus_read_byte(cm36651->ara_client); + if (ret < 0) { + dev_err(&client->dev, + "%s: Data read failed: %d\n", __func__, ret); + return IRQ_HANDLED; + } + switch (ret) { + case CM36651_CLOSE_PROXIMITY: + ev_dir = IIO_EV_DIR_RISING; + break; + case CM36651_FAR_PROXIMITY: + ev_dir = IIO_EV_DIR_FALLING; + break; + default: + dev_err(&client->dev, + "%s: Data read wrong: %d\n", __func__, ret); + return IRQ_HANDLED; + } + + ev_code = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, + CM36651_CMD_READ_RAW_PROXIMITY, + IIO_EV_TYPE_THRESH, ev_dir); + + iio_push_event(indio_dev, ev_code, iio_get_time_ns()); + + return IRQ_HANDLED; +} + +static int cm36651_set_operation_mode(struct cm36651_data *cm36651, int cmd) +{ + struct i2c_client *client = cm36651->client; + struct i2c_client *ps_client = cm36651->ps_client; + int ret = -EINVAL; + + switch (cmd) { + case CM36651_CMD_READ_RAW_LIGHT: + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF1, + cm36651->cs_ctrl_regs[CM36651_CS_CONF1]); + break; + case CM36651_CMD_READ_RAW_PROXIMITY: + if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) + return CM36651_PROXIMITY_EV_EN; + + ret = i2c_smbus_write_byte_data(ps_client, CM36651_PS_CONF1, + cm36651->ps_ctrl_regs[CM36651_PS_CONF1]); + break; + case CM36651_CMD_PROX_EV_EN: + if (test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { + dev_err(&client->dev, + "Already proximity event enable state\n"); + return ret; + } + set_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + + ret = i2c_smbus_write_byte_data(ps_client, + cm36651_ps_reg[CM36651_PS_CONF1], + CM36651_PS_INT_EN | CM36651_PS_PERS2 | CM36651_PS_IT2); + + if (ret < 0) { + dev_err(&client->dev, "Proximity enable event failed\n"); + return ret; + } + break; + case CM36651_CMD_PROX_EV_DIS: + if (!test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags)) { + dev_err(&client->dev, + "Already proximity event disable state\n"); + return ret; + } + clear_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + ret = i2c_smbus_write_byte_data(ps_client, + CM36651_PS_CONF1, CM36651_PS_DISABLE); + break; + } + + if (ret < 0) + dev_err(&client->dev, "Write register failed\n"); + + return ret; +} + +static int cm36651_read_channel(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int *val) +{ + struct i2c_client *client = cm36651->client; + int cmd, ret; + + if (chan->type == IIO_LIGHT) + cmd = CM36651_CMD_READ_RAW_LIGHT; + else if (chan->type == IIO_PROXIMITY) + cmd = CM36651_CMD_READ_RAW_PROXIMITY; + else + return -EINVAL; + + ret = cm36651_set_operation_mode(cm36651, cmd); + if (ret < 0) { + dev_err(&client->dev, "CM36651 set operation mode failed\n"); + return ret; + } + /* Delay for work after enable operation */ + msleep(50); + ret = cm36651_read_output(cm36651, chan, val); + if (ret < 0) { + dev_err(&client->dev, "CM36651 read output failed\n"); + return ret; + } + + return ret; +} + +static int cm36651_read_int_time(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int *val2) +{ + switch (chan->type) { + case IIO_LIGHT: + if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT1) + *val2 = 80000; + else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT2) + *val2 = 160000; + else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT3) + *val2 = 320000; + else if (cm36651->cs_int_time[chan->address] == CM36651_CS_IT4) + *val2 = 640000; + else + return -EINVAL; + break; + case IIO_PROXIMITY: + if (cm36651->ps_int_time == CM36651_PS_IT1) + *val2 = 320; + else if (cm36651->ps_int_time == CM36651_PS_IT2) + *val2 = 420; + else if (cm36651->ps_int_time == CM36651_PS_IT3) + *val2 = 520; + else if (cm36651->ps_int_time == CM36651_PS_IT4) + *val2 = 640; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int cm36651_write_int_time(struct cm36651_data *cm36651, + struct iio_chan_spec const *chan, int val) +{ + struct i2c_client *client = cm36651->client; + struct i2c_client *ps_client = cm36651->ps_client; + int int_time, ret; + + switch (chan->type) { + case IIO_LIGHT: + if (val == 80000) + int_time = CM36651_CS_IT1; + else if (val == 160000) + int_time = CM36651_CS_IT2; + else if (val == 320000) + int_time = CM36651_CS_IT3; + else if (val == 640000) + int_time = CM36651_CS_IT4; + else + return -EINVAL; + + ret = i2c_smbus_write_byte_data(client, CM36651_CS_CONF3, + int_time >> 2 * (chan->address)); + if (ret < 0) { + dev_err(&client->dev, "CS integration time write failed\n"); + return ret; + } + cm36651->cs_int_time[chan->address] = int_time; + break; + case IIO_PROXIMITY: + if (val == 320) + int_time = CM36651_PS_IT1; + else if (val == 420) + int_time = CM36651_PS_IT2; + else if (val == 520) + int_time = CM36651_PS_IT3; + else if (val == 640) + int_time = CM36651_PS_IT4; + else + return -EINVAL; + + ret = i2c_smbus_write_byte_data(ps_client, + CM36651_PS_CONF1, int_time); + if (ret < 0) { + dev_err(&client->dev, "PS integration time write failed\n"); + return ret; + } + cm36651->ps_int_time = int_time; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int cm36651_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + int ret; + + mutex_lock(&cm36651->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = cm36651_read_channel(cm36651, chan, val); + break; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + ret = cm36651_read_int_time(cm36651, chan, val2); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&cm36651->lock); + + return ret; +} + +static int cm36651_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + struct i2c_client *client = cm36651->client; + int ret = -EINVAL; + + if (mask == IIO_CHAN_INFO_INT_TIME) { + ret = cm36651_write_int_time(cm36651, chan, val2); + if (ret < 0) + dev_err(&client->dev, "Integration time write failed\n"); + } + + return ret; +} + +static int cm36651_read_prox_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 cm36651_data *cm36651 = iio_priv(indio_dev); + + *val = cm36651->ps_ctrl_regs[CM36651_PS_THD]; + + return 0; +} + +static int cm36651_write_prox_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 cm36651_data *cm36651 = iio_priv(indio_dev); + struct i2c_client *client = cm36651->client; + int ret; + + if (val < 3 || val > 255) + return -EINVAL; + + cm36651->ps_ctrl_regs[CM36651_PS_THD] = val; + ret = i2c_smbus_write_byte_data(cm36651->ps_client, CM36651_PS_THD, + cm36651->ps_ctrl_regs[CM36651_PS_THD]); + + if (ret < 0) { + dev_err(&client->dev, "PS threshold write failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int cm36651_write_prox_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 cm36651_data *cm36651 = iio_priv(indio_dev); + int cmd, ret = -EINVAL; + + mutex_lock(&cm36651->lock); + + cmd = state ? CM36651_CMD_PROX_EV_EN : CM36651_CMD_PROX_EV_DIS; + ret = cm36651_set_operation_mode(cm36651, cmd); + + mutex_unlock(&cm36651->lock); + + return ret; +} + +static int cm36651_read_prox_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct cm36651_data *cm36651 = iio_priv(indio_dev); + int event_en; + + mutex_lock(&cm36651->lock); + + event_en = test_bit(CM36651_PROXIMITY_EV_EN, &cm36651->flags); + + mutex_unlock(&cm36651->lock); + + return event_en; +} + +#define CM36651_LIGHT_CHANNEL(_color, _idx) { \ + .type = IIO_LIGHT, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .address = _idx, \ + .modified = 1, \ + .channel2 = IIO_MOD_LIGHT_##_color, \ +} \ + +static const struct iio_event_spec cm36651_event_spec[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + } +}; + +static const struct iio_chan_spec cm36651_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME), + .event_spec = cm36651_event_spec, + .num_event_specs = ARRAY_SIZE(cm36651_event_spec), + }, + CM36651_LIGHT_CHANNEL(RED, CM36651_LIGHT_CHANNEL_IDX_RED), + CM36651_LIGHT_CHANNEL(GREEN, CM36651_LIGHT_CHANNEL_IDX_GREEN), + CM36651_LIGHT_CHANNEL(BLUE, CM36651_LIGHT_CHANNEL_IDX_BLUE), + CM36651_LIGHT_CHANNEL(CLEAR, CM36651_LIGHT_CHANNEL_IDX_CLEAR), +}; + +static IIO_CONST_ATTR(in_illuminance_integration_time_available, + CM36651_CS_INT_TIME_AVAIL); +static IIO_CONST_ATTR(in_proximity_integration_time_available, + CM36651_PS_INT_TIME_AVAIL); + +static struct attribute *cm36651_attributes[] = { + &iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr, + &iio_const_attr_in_proximity_integration_time_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group cm36651_attribute_group = { + .attrs = cm36651_attributes +}; + +static const struct iio_info cm36651_info = { + .driver_module = THIS_MODULE, + .read_raw = &cm36651_read_raw, + .write_raw = &cm36651_write_raw, + .read_event_value = &cm36651_read_prox_thresh, + .write_event_value = &cm36651_write_prox_thresh, + .read_event_config = &cm36651_read_prox_event_config, + .write_event_config = &cm36651_write_prox_event_config, + .attrs = &cm36651_attribute_group, +}; + +static int cm36651_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cm36651_data *cm36651; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*cm36651)); + if (!indio_dev) + return -ENOMEM; + + cm36651 = iio_priv(indio_dev); + + cm36651->vled_reg = devm_regulator_get(&client->dev, "vled"); + if (IS_ERR(cm36651->vled_reg)) { + dev_err(&client->dev, "get regulator vled failed\n"); + return PTR_ERR(cm36651->vled_reg); + } + + ret = regulator_enable(cm36651->vled_reg); + if (ret) { + dev_err(&client->dev, "enable regulator vled failed\n"); + return ret; + } + + i2c_set_clientdata(client, indio_dev); + + cm36651->client = client; + cm36651->ps_client = i2c_new_dummy(client->adapter, + CM36651_I2C_ADDR_PS); + if (!cm36651->ps_client) { + dev_err(&client->dev, "%s: new i2c device failed\n", __func__); + ret = -ENODEV; + goto error_disable_reg; + } + + cm36651->ara_client = i2c_new_dummy(client->adapter, CM36651_ARA); + if (!cm36651->ara_client) { + dev_err(&client->dev, "%s: new i2c device failed\n", __func__); + ret = -ENODEV; + goto error_i2c_unregister_ps; + } + + mutex_init(&cm36651->lock); + indio_dev->dev.parent = &client->dev; + indio_dev->channels = cm36651_channels; + indio_dev->num_channels = ARRAY_SIZE(cm36651_channels); + indio_dev->info = &cm36651_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = cm36651_setup_reg(cm36651); + if (ret) { + dev_err(&client->dev, "%s: register setup failed\n", __func__); + goto error_i2c_unregister_ara; + } + + ret = request_threaded_irq(client->irq, NULL, cm36651_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "cm36651", indio_dev); + if (ret) { + dev_err(&client->dev, "%s: request irq failed\n", __func__); + goto error_i2c_unregister_ara; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&client->dev, "%s: regist device failed\n", __func__); + goto error_free_irq; + } + + return 0; + +error_free_irq: + free_irq(client->irq, indio_dev); +error_i2c_unregister_ara: + i2c_unregister_device(cm36651->ara_client); +error_i2c_unregister_ps: + i2c_unregister_device(cm36651->ps_client); +error_disable_reg: + regulator_disable(cm36651->vled_reg); + return ret; +} + +static int cm36651_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct cm36651_data *cm36651 = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(cm36651->vled_reg); + free_irq(client->irq, indio_dev); + i2c_unregister_device(cm36651->ps_client); + i2c_unregister_device(cm36651->ara_client); + + return 0; +} + +static const struct i2c_device_id cm36651_id[] = { + { "cm36651", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, cm36651_id); + +static const struct of_device_id cm36651_of_match[] = { + { .compatible = "capella,cm36651" }, + { } +}; + +static struct i2c_driver cm36651_driver = { + .driver = { + .name = "cm36651", + .of_match_table = cm36651_of_match, + .owner = THIS_MODULE, + }, + .probe = cm36651_probe, + .remove = cm36651_remove, + .id_table = cm36651_id, +}; + +module_i2c_driver(cm36651_driver); + +MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>"); +MODULE_DESCRIPTION("CM36651 proximity/ambient light sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/gp2ap020a00f.c b/drivers/iio/light/gp2ap020a00f.c new file mode 100644 index 000000000..32b644983 --- /dev/null +++ b/drivers/iio/light/gp2ap020a00f.c @@ -0,0 +1,1654 @@ +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Jacek Anaszewski <j.anaszewski@samsung.com> + * + * IIO features supported by the driver: + * + * Read-only raw channels: + * - illuminance_clear [lux] + * - illuminance_ir + * - proximity + * + * Triggered buffer: + * - illuminance_clear + * - illuminance_ir + * - proximity + * + * Events: + * - illuminance_clear (rising and falling) + * - proximity (rising and falling) + * - both falling and rising thresholds for the proximity events + * must be set to the values greater than 0. + * + * The driver supports triggered buffers for all the three + * channels as well as high and low threshold events for the + * illuminance_clear and proxmimity channels. Triggers + * can be enabled simultaneously with both illuminance_clear + * events. Proximity events cannot be enabled simultaneously + * with any triggers or illuminance events. Enabling/disabling + * one of the proximity events automatically enables/disables + * the other one. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irq_work.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <asm/unaligned.h> +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define GP2A_I2C_NAME "gp2ap020a00f" + +/* Registers */ +#define GP2AP020A00F_OP_REG 0x00 /* Basic operations */ +#define GP2AP020A00F_ALS_REG 0x01 /* ALS related settings */ +#define GP2AP020A00F_PS_REG 0x02 /* PS related settings */ +#define GP2AP020A00F_LED_REG 0x03 /* LED reg */ +#define GP2AP020A00F_TL_L_REG 0x04 /* ALS: Threshold low LSB */ +#define GP2AP020A00F_TL_H_REG 0x05 /* ALS: Threshold low MSB */ +#define GP2AP020A00F_TH_L_REG 0x06 /* ALS: Threshold high LSB */ +#define GP2AP020A00F_TH_H_REG 0x07 /* ALS: Threshold high MSB */ +#define GP2AP020A00F_PL_L_REG 0x08 /* PS: Threshold low LSB */ +#define GP2AP020A00F_PL_H_REG 0x09 /* PS: Threshold low MSB */ +#define GP2AP020A00F_PH_L_REG 0x0a /* PS: Threshold high LSB */ +#define GP2AP020A00F_PH_H_REG 0x0b /* PS: Threshold high MSB */ +#define GP2AP020A00F_D0_L_REG 0x0c /* ALS result: Clear/Illuminance LSB */ +#define GP2AP020A00F_D0_H_REG 0x0d /* ALS result: Clear/Illuminance MSB */ +#define GP2AP020A00F_D1_L_REG 0x0e /* ALS result: IR LSB */ +#define GP2AP020A00F_D1_H_REG 0x0f /* ALS result: IR LSB */ +#define GP2AP020A00F_D2_L_REG 0x10 /* PS result LSB */ +#define GP2AP020A00F_D2_H_REG 0x11 /* PS result MSB */ +#define GP2AP020A00F_NUM_REGS 0x12 /* Number of registers */ + +/* OP_REG bits */ +#define GP2AP020A00F_OP3_MASK 0x80 /* Software shutdown */ +#define GP2AP020A00F_OP3_SHUTDOWN 0x00 +#define GP2AP020A00F_OP3_OPERATION 0x80 +#define GP2AP020A00F_OP2_MASK 0x40 /* Auto shutdown/Continuous mode */ +#define GP2AP020A00F_OP2_AUTO_SHUTDOWN 0x00 +#define GP2AP020A00F_OP2_CONT_OPERATION 0x40 +#define GP2AP020A00F_OP_MASK 0x30 /* Operating mode selection */ +#define GP2AP020A00F_OP_ALS_AND_PS 0x00 +#define GP2AP020A00F_OP_ALS 0x10 +#define GP2AP020A00F_OP_PS 0x20 +#define GP2AP020A00F_OP_DEBUG 0x30 +#define GP2AP020A00F_PROX_MASK 0x08 /* PS: detection/non-detection */ +#define GP2AP020A00F_PROX_NON_DETECT 0x00 +#define GP2AP020A00F_PROX_DETECT 0x08 +#define GP2AP020A00F_FLAG_P 0x04 /* PS: interrupt result */ +#define GP2AP020A00F_FLAG_A 0x02 /* ALS: interrupt result */ +#define GP2AP020A00F_TYPE_MASK 0x01 /* Output data type selection */ +#define GP2AP020A00F_TYPE_MANUAL_CALC 0x00 +#define GP2AP020A00F_TYPE_AUTO_CALC 0x01 + +/* ALS_REG bits */ +#define GP2AP020A00F_PRST_MASK 0xc0 /* Number of measurement cycles */ +#define GP2AP020A00F_PRST_ONCE 0x00 +#define GP2AP020A00F_PRST_4_CYCLES 0x40 +#define GP2AP020A00F_PRST_8_CYCLES 0x80 +#define GP2AP020A00F_PRST_16_CYCLES 0xc0 +#define GP2AP020A00F_RES_A_MASK 0x38 /* ALS: Resolution */ +#define GP2AP020A00F_RES_A_800ms 0x00 +#define GP2AP020A00F_RES_A_400ms 0x08 +#define GP2AP020A00F_RES_A_200ms 0x10 +#define GP2AP020A00F_RES_A_100ms 0x18 +#define GP2AP020A00F_RES_A_25ms 0x20 +#define GP2AP020A00F_RES_A_6_25ms 0x28 +#define GP2AP020A00F_RES_A_1_56ms 0x30 +#define GP2AP020A00F_RES_A_0_39ms 0x38 +#define GP2AP020A00F_RANGE_A_MASK 0x07 /* ALS: Max measurable range */ +#define GP2AP020A00F_RANGE_A_x1 0x00 +#define GP2AP020A00F_RANGE_A_x2 0x01 +#define GP2AP020A00F_RANGE_A_x4 0x02 +#define GP2AP020A00F_RANGE_A_x8 0x03 +#define GP2AP020A00F_RANGE_A_x16 0x04 +#define GP2AP020A00F_RANGE_A_x32 0x05 +#define GP2AP020A00F_RANGE_A_x64 0x06 +#define GP2AP020A00F_RANGE_A_x128 0x07 + +/* PS_REG bits */ +#define GP2AP020A00F_ALC_MASK 0x80 /* Auto light cancel */ +#define GP2AP020A00F_ALC_ON 0x80 +#define GP2AP020A00F_ALC_OFF 0x00 +#define GP2AP020A00F_INTTYPE_MASK 0x40 /* Interrupt type setting */ +#define GP2AP020A00F_INTTYPE_LEVEL 0x00 +#define GP2AP020A00F_INTTYPE_PULSE 0x40 +#define GP2AP020A00F_RES_P_MASK 0x38 /* PS: Resolution */ +#define GP2AP020A00F_RES_P_800ms_x2 0x00 +#define GP2AP020A00F_RES_P_400ms_x2 0x08 +#define GP2AP020A00F_RES_P_200ms_x2 0x10 +#define GP2AP020A00F_RES_P_100ms_x2 0x18 +#define GP2AP020A00F_RES_P_25ms_x2 0x20 +#define GP2AP020A00F_RES_P_6_25ms_x2 0x28 +#define GP2AP020A00F_RES_P_1_56ms_x2 0x30 +#define GP2AP020A00F_RES_P_0_39ms_x2 0x38 +#define GP2AP020A00F_RANGE_P_MASK 0x07 /* PS: Max measurable range */ +#define GP2AP020A00F_RANGE_P_x1 0x00 +#define GP2AP020A00F_RANGE_P_x2 0x01 +#define GP2AP020A00F_RANGE_P_x4 0x02 +#define GP2AP020A00F_RANGE_P_x8 0x03 +#define GP2AP020A00F_RANGE_P_x16 0x04 +#define GP2AP020A00F_RANGE_P_x32 0x05 +#define GP2AP020A00F_RANGE_P_x64 0x06 +#define GP2AP020A00F_RANGE_P_x128 0x07 + +/* LED reg bits */ +#define GP2AP020A00F_INTVAL_MASK 0xc0 /* Intermittent operating */ +#define GP2AP020A00F_INTVAL_0 0x00 +#define GP2AP020A00F_INTVAL_4 0x40 +#define GP2AP020A00F_INTVAL_8 0x80 +#define GP2AP020A00F_INTVAL_16 0xc0 +#define GP2AP020A00F_IS_MASK 0x30 /* ILED drive peak current */ +#define GP2AP020A00F_IS_13_8mA 0x00 +#define GP2AP020A00F_IS_27_5mA 0x10 +#define GP2AP020A00F_IS_55mA 0x20 +#define GP2AP020A00F_IS_110mA 0x30 +#define GP2AP020A00F_PIN_MASK 0x0c /* INT terminal setting */ +#define GP2AP020A00F_PIN_ALS_OR_PS 0x00 +#define GP2AP020A00F_PIN_ALS 0x04 +#define GP2AP020A00F_PIN_PS 0x08 +#define GP2AP020A00F_PIN_PS_DETECT 0x0c +#define GP2AP020A00F_FREQ_MASK 0x02 /* LED modulation frequency */ +#define GP2AP020A00F_FREQ_327_5kHz 0x00 +#define GP2AP020A00F_FREQ_81_8kHz 0x02 +#define GP2AP020A00F_RST 0x01 /* Software reset */ + +#define GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR 0 +#define GP2AP020A00F_SCAN_MODE_LIGHT_IR 1 +#define GP2AP020A00F_SCAN_MODE_PROXIMITY 2 +#define GP2AP020A00F_CHAN_TIMESTAMP 3 + +#define GP2AP020A00F_DATA_READY_TIMEOUT msecs_to_jiffies(1000) +#define GP2AP020A00F_DATA_REG(chan) (GP2AP020A00F_D0_L_REG + \ + (chan) * 2) +#define GP2AP020A00F_THRESH_REG(th_val_id) (GP2AP020A00F_TL_L_REG + \ + (th_val_id) * 2) +#define GP2AP020A00F_THRESH_VAL_ID(reg_addr) ((reg_addr - 4) / 2) + +#define GP2AP020A00F_SUBTRACT_MODE 0 +#define GP2AP020A00F_ADD_MODE 1 + +#define GP2AP020A00F_MAX_CHANNELS 3 + +enum gp2ap020a00f_opmode { + GP2AP020A00F_OPMODE_READ_RAW_CLEAR, + GP2AP020A00F_OPMODE_READ_RAW_IR, + GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_OPMODE_PS, + GP2AP020A00F_OPMODE_ALS_AND_PS, + GP2AP020A00F_OPMODE_PROX_DETECT, + GP2AP020A00F_OPMODE_SHUTDOWN, + GP2AP020A00F_NUM_OPMODES, +}; + +enum gp2ap020a00f_cmd { + GP2AP020A00F_CMD_READ_RAW_CLEAR, + GP2AP020A00F_CMD_READ_RAW_IR, + GP2AP020A00F_CMD_READ_RAW_PROXIMITY, + GP2AP020A00F_CMD_TRIGGER_CLEAR_EN, + GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS, + GP2AP020A00F_CMD_TRIGGER_IR_EN, + GP2AP020A00F_CMD_TRIGGER_IR_DIS, + GP2AP020A00F_CMD_TRIGGER_PROX_EN, + GP2AP020A00F_CMD_TRIGGER_PROX_DIS, + GP2AP020A00F_CMD_ALS_HIGH_EV_EN, + GP2AP020A00F_CMD_ALS_HIGH_EV_DIS, + GP2AP020A00F_CMD_ALS_LOW_EV_EN, + GP2AP020A00F_CMD_ALS_LOW_EV_DIS, + GP2AP020A00F_CMD_PROX_HIGH_EV_EN, + GP2AP020A00F_CMD_PROX_HIGH_EV_DIS, + GP2AP020A00F_CMD_PROX_LOW_EV_EN, + GP2AP020A00F_CMD_PROX_LOW_EV_DIS, +}; + +enum gp2ap020a00f_flags { + GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, + GP2AP020A00F_FLAG_ALS_IR_TRIGGER, + GP2AP020A00F_FLAG_PROX_TRIGGER, + GP2AP020A00F_FLAG_PROX_RISING_EV, + GP2AP020A00F_FLAG_PROX_FALLING_EV, + GP2AP020A00F_FLAG_ALS_RISING_EV, + GP2AP020A00F_FLAG_ALS_FALLING_EV, + GP2AP020A00F_FLAG_LUX_MODE_HI, + GP2AP020A00F_FLAG_DATA_READY, +}; + +enum gp2ap020a00f_thresh_val_id { + GP2AP020A00F_THRESH_TL, + GP2AP020A00F_THRESH_TH, + GP2AP020A00F_THRESH_PL, + GP2AP020A00F_THRESH_PH, +}; + +struct gp2ap020a00f_data { + const struct gp2ap020a00f_platform_data *pdata; + struct i2c_client *client; + struct mutex lock; + char *buffer; + struct regulator *vled_reg; + unsigned long flags; + enum gp2ap020a00f_opmode cur_opmode; + struct iio_trigger *trig; + struct regmap *regmap; + unsigned int thresh_val[4]; + u8 debug_reg_addr; + struct irq_work work; + wait_queue_head_t data_ready_queue; +}; + +static const u8 gp2ap020a00f_reg_init_tab[] = { + [GP2AP020A00F_OP_REG] = GP2AP020A00F_OP3_SHUTDOWN, + [GP2AP020A00F_ALS_REG] = GP2AP020A00F_RES_A_25ms | + GP2AP020A00F_RANGE_A_x8, + [GP2AP020A00F_PS_REG] = GP2AP020A00F_ALC_ON | + GP2AP020A00F_RES_P_1_56ms_x2 | + GP2AP020A00F_RANGE_P_x4, + [GP2AP020A00F_LED_REG] = GP2AP020A00F_INTVAL_0 | + GP2AP020A00F_IS_110mA | + GP2AP020A00F_FREQ_327_5kHz, + [GP2AP020A00F_TL_L_REG] = 0, + [GP2AP020A00F_TL_H_REG] = 0, + [GP2AP020A00F_TH_L_REG] = 0, + [GP2AP020A00F_TH_H_REG] = 0, + [GP2AP020A00F_PL_L_REG] = 0, + [GP2AP020A00F_PL_H_REG] = 0, + [GP2AP020A00F_PH_L_REG] = 0, + [GP2AP020A00F_PH_H_REG] = 0, +}; + +static bool gp2ap020a00f_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case GP2AP020A00F_OP_REG: + case GP2AP020A00F_D0_L_REG: + case GP2AP020A00F_D0_H_REG: + case GP2AP020A00F_D1_L_REG: + case GP2AP020A00F_D1_H_REG: + case GP2AP020A00F_D2_L_REG: + case GP2AP020A00F_D2_H_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config gp2ap020a00f_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = GP2AP020A00F_D2_H_REG, + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = gp2ap020a00f_is_volatile_reg, +}; + +static const struct gp2ap020a00f_mutable_config_regs { + u8 op_reg; + u8 als_reg; + u8 ps_reg; + u8 led_reg; +} opmode_regs_settings[GP2AP020A00F_NUM_OPMODES] = { + [GP2AP020A00F_OPMODE_READ_RAW_CLEAR] = { + GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_AUTO_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS + }, + [GP2AP020A00F_OPMODE_READ_RAW_IR] = { + GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS + }, + [GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY] = { + GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_PS + }, + [GP2AP020A00F_OPMODE_PROX_DETECT] = { + GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_4_CYCLES, + GP2AP020A00F_INTTYPE_PULSE, + GP2AP020A00F_PIN_PS_DETECT + }, + [GP2AP020A00F_OPMODE_ALS] = { + GP2AP020A00F_OP_ALS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_AUTO_CALC, + GP2AP020A00F_PRST_ONCE, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS + }, + [GP2AP020A00F_OPMODE_PS] = { + GP2AP020A00F_OP_PS | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_MANUAL_CALC, + GP2AP020A00F_PRST_4_CYCLES, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_PS + }, + [GP2AP020A00F_OPMODE_ALS_AND_PS] = { + GP2AP020A00F_OP_ALS_AND_PS + | GP2AP020A00F_OP2_CONT_OPERATION + | GP2AP020A00F_OP3_OPERATION + | GP2AP020A00F_TYPE_AUTO_CALC, + GP2AP020A00F_PRST_4_CYCLES, + GP2AP020A00F_INTTYPE_LEVEL, + GP2AP020A00F_PIN_ALS_OR_PS + }, + [GP2AP020A00F_OPMODE_SHUTDOWN] = { GP2AP020A00F_OP3_SHUTDOWN, }, +}; + +static int gp2ap020a00f_set_operation_mode(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_opmode op) +{ + unsigned int op_reg_val; + int err; + + if (op != GP2AP020A00F_OPMODE_SHUTDOWN) { + err = regmap_read(data->regmap, GP2AP020A00F_OP_REG, + &op_reg_val); + if (err < 0) + return err; + /* + * Shutdown the device if the operation being executed entails + * mode transition. + */ + if ((opmode_regs_settings[op].op_reg & GP2AP020A00F_OP_MASK) != + (op_reg_val & GP2AP020A00F_OP_MASK)) { + /* set shutdown mode */ + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, GP2AP020A00F_OP3_MASK, + GP2AP020A00F_OP3_SHUTDOWN); + if (err < 0) + return err; + } + + err = regmap_update_bits(data->regmap, GP2AP020A00F_ALS_REG, + GP2AP020A00F_PRST_MASK, opmode_regs_settings[op] + .als_reg); + if (err < 0) + return err; + + err = regmap_update_bits(data->regmap, GP2AP020A00F_PS_REG, + GP2AP020A00F_INTTYPE_MASK, opmode_regs_settings[op] + .ps_reg); + if (err < 0) + return err; + + err = regmap_update_bits(data->regmap, GP2AP020A00F_LED_REG, + GP2AP020A00F_PIN_MASK, opmode_regs_settings[op] + .led_reg); + if (err < 0) + return err; + } + + /* Set OP_REG and apply operation mode (power on / off) */ + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, + GP2AP020A00F_OP_MASK | GP2AP020A00F_OP2_MASK | + GP2AP020A00F_OP3_MASK | GP2AP020A00F_TYPE_MASK, + opmode_regs_settings[op].op_reg); + if (err < 0) + return err; + + data->cur_opmode = op; + + return 0; +} + +static bool gp2ap020a00f_als_enabled(struct gp2ap020a00f_data *data) +{ + return test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); +} + +static bool gp2ap020a00f_prox_detect_enabled(struct gp2ap020a00f_data *data) +{ + return test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags) || + test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); +} + +static int gp2ap020a00f_write_event_threshold(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_thresh_val_id th_val_id, + bool enable) +{ + __le16 thresh_buf = 0; + unsigned int thresh_reg_val; + + if (!enable) + thresh_reg_val = 0; + else if (test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags) && + th_val_id != GP2AP020A00F_THRESH_PL && + th_val_id != GP2AP020A00F_THRESH_PH) + /* + * For the high lux mode ALS threshold has to be scaled down + * to allow for proper comparison with the output value. + */ + thresh_reg_val = data->thresh_val[th_val_id] / 16; + else + thresh_reg_val = data->thresh_val[th_val_id] > 16000 ? + 16000 : + data->thresh_val[th_val_id]; + + thresh_buf = cpu_to_le16(thresh_reg_val); + + return regmap_bulk_write(data->regmap, + GP2AP020A00F_THRESH_REG(th_val_id), + (u8 *)&thresh_buf, 2); +} + +static int gp2ap020a00f_alter_opmode(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_opmode diff_mode, int add_sub) +{ + enum gp2ap020a00f_opmode new_mode; + + if (diff_mode != GP2AP020A00F_OPMODE_ALS && + diff_mode != GP2AP020A00F_OPMODE_PS) + return -EINVAL; + + if (add_sub == GP2AP020A00F_ADD_MODE) { + if (data->cur_opmode == GP2AP020A00F_OPMODE_SHUTDOWN) + new_mode = diff_mode; + else + new_mode = GP2AP020A00F_OPMODE_ALS_AND_PS; + } else { + if (data->cur_opmode == GP2AP020A00F_OPMODE_ALS_AND_PS) + new_mode = (diff_mode == GP2AP020A00F_OPMODE_ALS) ? + GP2AP020A00F_OPMODE_PS : + GP2AP020A00F_OPMODE_ALS; + else + new_mode = GP2AP020A00F_OPMODE_SHUTDOWN; + } + + return gp2ap020a00f_set_operation_mode(data, new_mode); +} + +static int gp2ap020a00f_exec_cmd(struct gp2ap020a00f_data *data, + enum gp2ap020a00f_cmd cmd) +{ + int err = 0; + + switch (cmd) { + case GP2AP020A00F_CMD_READ_RAW_CLEAR: + if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) + return -EBUSY; + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_READ_RAW_CLEAR); + break; + case GP2AP020A00F_CMD_READ_RAW_IR: + if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) + return -EBUSY; + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_READ_RAW_IR); + break; + case GP2AP020A00F_CMD_READ_RAW_PROXIMITY: + if (data->cur_opmode != GP2AP020A00F_OPMODE_SHUTDOWN) + return -EBUSY; + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY); + break; + case GP2AP020A00F_CMD_TRIGGER_CLEAR_EN: + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + set_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags); + break; + case GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS: + clear_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &data->flags); + if (gp2ap020a00f_als_enabled(data)) + break; + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + break; + case GP2AP020A00F_CMD_TRIGGER_IR_EN: + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + set_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags); + break; + case GP2AP020A00F_CMD_TRIGGER_IR_DIS: + clear_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &data->flags); + if (gp2ap020a00f_als_enabled(data)) + break; + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + break; + case GP2AP020A00F_CMD_TRIGGER_PROX_EN: + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_PS, + GP2AP020A00F_ADD_MODE); + set_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &data->flags); + break; + case GP2AP020A00F_CMD_TRIGGER_PROX_DIS: + clear_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &data->flags); + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_PS, + GP2AP020A00F_SUBTRACT_MODE); + break; + case GP2AP020A00F_CMD_ALS_HIGH_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) + return 0; + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, true); + break; + case GP2AP020A00F_CMD_ALS_HIGH_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags); + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + if (err < 0) + return err; + } + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, false); + break; + case GP2AP020A00F_CMD_ALS_LOW_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) + return 0; + if (data->cur_opmode == GP2AP020A00F_OPMODE_PROX_DETECT) + return -EBUSY; + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_ADD_MODE); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, true); + break; + case GP2AP020A00F_CMD_ALS_LOW_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags); + if (!gp2ap020a00f_als_enabled(data)) { + err = gp2ap020a00f_alter_opmode(data, + GP2AP020A00F_OPMODE_ALS, + GP2AP020A00F_SUBTRACT_MODE); + if (err < 0) + return err; + } + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, false); + break; + case GP2AP020A00F_CMD_PROX_HIGH_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags)) + return 0; + if (gp2ap020a00f_als_enabled(data) || + data->cur_opmode == GP2AP020A00F_OPMODE_PS) + return -EBUSY; + if (!gp2ap020a00f_prox_detect_enabled(data)) { + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_PROX_DETECT); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PH, true); + break; + case GP2AP020A00F_CMD_PROX_HIGH_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, &data->flags); + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + return err; + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PH, false); + break; + case GP2AP020A00F_CMD_PROX_LOW_EV_EN: + if (test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags)) + return 0; + if (gp2ap020a00f_als_enabled(data) || + data->cur_opmode == GP2AP020A00F_OPMODE_PS) + return -EBUSY; + if (!gp2ap020a00f_prox_detect_enabled(data)) { + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_PROX_DETECT); + if (err < 0) + return err; + } + set_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PL, true); + break; + case GP2AP020A00F_CMD_PROX_LOW_EV_DIS: + if (!test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags)) + return 0; + clear_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, &data->flags); + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + return err; + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_PL, false); + break; + } + + return err; +} + +static int wait_conversion_complete_irq(struct gp2ap020a00f_data *data) +{ + int ret; + + ret = wait_event_timeout(data->data_ready_queue, + test_bit(GP2AP020A00F_FLAG_DATA_READY, + &data->flags), + GP2AP020A00F_DATA_READY_TIMEOUT); + clear_bit(GP2AP020A00F_FLAG_DATA_READY, &data->flags); + + return ret > 0 ? 0 : -ETIME; +} + +static int gp2ap020a00f_read_output(struct gp2ap020a00f_data *data, + unsigned int output_reg, int *val) +{ + u8 reg_buf[2]; + int err; + + err = wait_conversion_complete_irq(data); + if (err < 0) + dev_dbg(&data->client->dev, "data ready timeout\n"); + + err = regmap_bulk_read(data->regmap, output_reg, reg_buf, 2); + if (err < 0) + return err; + + *val = le16_to_cpup((__le16 *)reg_buf); + + return err; +} + +static bool gp2ap020a00f_adjust_lux_mode(struct gp2ap020a00f_data *data, + int output_val) +{ + u8 new_range = 0xff; + int err; + + if (!test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags)) { + if (output_val > 16000) { + set_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags); + new_range = GP2AP020A00F_RANGE_A_x128; + } + } else { + if (output_val < 1000) { + clear_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags); + new_range = GP2AP020A00F_RANGE_A_x8; + } + } + + if (new_range != 0xff) { + /* Clear als threshold registers to avoid spurious + * events caused by lux mode transition. + */ + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, false); + if (err < 0) { + dev_err(&data->client->dev, + "Clearing als threshold register failed.\n"); + return false; + } + + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, false); + if (err < 0) { + dev_err(&data->client->dev, + "Clearing als threshold register failed.\n"); + return false; + } + + /* Change lux mode */ + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, + GP2AP020A00F_OP3_MASK, + GP2AP020A00F_OP3_SHUTDOWN); + + if (err < 0) { + dev_err(&data->client->dev, + "Shutting down the device failed.\n"); + return false; + } + + err = regmap_update_bits(data->regmap, + GP2AP020A00F_ALS_REG, + GP2AP020A00F_RANGE_A_MASK, + new_range); + + if (err < 0) { + dev_err(&data->client->dev, + "Adjusting device lux mode failed.\n"); + return false; + } + + err = regmap_update_bits(data->regmap, + GP2AP020A00F_OP_REG, + GP2AP020A00F_OP3_MASK, + GP2AP020A00F_OP3_OPERATION); + + if (err < 0) { + dev_err(&data->client->dev, + "Powering up the device failed.\n"); + return false; + } + + /* Adjust als threshold register values to the new lux mode */ + if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &data->flags)) { + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TH, true); + if (err < 0) { + dev_err(&data->client->dev, + "Adjusting als threshold value failed.\n"); + return false; + } + } + + if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &data->flags)) { + err = gp2ap020a00f_write_event_threshold(data, + GP2AP020A00F_THRESH_TL, true); + if (err < 0) { + dev_err(&data->client->dev, + "Adjusting als threshold value failed.\n"); + return false; + } + } + + return true; + } + + return false; +} + +static void gp2ap020a00f_output_to_lux(struct gp2ap020a00f_data *data, + int *output_val) +{ + if (test_bit(GP2AP020A00F_FLAG_LUX_MODE_HI, &data->flags)) + *output_val *= 16; +} + +static void gp2ap020a00f_iio_trigger_work(struct irq_work *work) +{ + struct gp2ap020a00f_data *data = + container_of(work, struct gp2ap020a00f_data, work); + + iio_trigger_poll(data->trig); +} + +static irqreturn_t gp2ap020a00f_prox_sensing_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct gp2ap020a00f_data *priv = iio_priv(indio_dev); + unsigned int op_reg_val; + int ret; + + /* Read interrupt flags */ + ret = regmap_read(priv->regmap, GP2AP020A00F_OP_REG, &op_reg_val); + if (ret < 0) + return IRQ_HANDLED; + + if (gp2ap020a00f_prox_detect_enabled(priv)) { + if (op_reg_val & GP2AP020A00F_PROX_DETECT) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_PROXIMITY, + GP2AP020A00F_SCAN_MODE_PROXIMITY, + IIO_EV_TYPE_ROC, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + } else { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE( + IIO_PROXIMITY, + GP2AP020A00F_SCAN_MODE_PROXIMITY, + IIO_EV_TYPE_ROC, + IIO_EV_DIR_FALLING), + iio_get_time_ns()); + } + } + + return IRQ_HANDLED; +} + +static irqreturn_t gp2ap020a00f_thresh_event_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct gp2ap020a00f_data *priv = iio_priv(indio_dev); + u8 op_reg_flags, d0_reg_buf[2]; + unsigned int output_val, op_reg_val; + int thresh_val_id, ret; + + /* Read interrupt flags */ + ret = regmap_read(priv->regmap, GP2AP020A00F_OP_REG, + &op_reg_val); + if (ret < 0) + goto done; + + op_reg_flags = op_reg_val & (GP2AP020A00F_FLAG_A | GP2AP020A00F_FLAG_P + | GP2AP020A00F_PROX_DETECT); + + op_reg_val &= (~GP2AP020A00F_FLAG_A & ~GP2AP020A00F_FLAG_P + & ~GP2AP020A00F_PROX_DETECT); + + /* Clear interrupt flags (if not in INTTYPE_PULSE mode) */ + if (priv->cur_opmode != GP2AP020A00F_OPMODE_PROX_DETECT) { + ret = regmap_write(priv->regmap, GP2AP020A00F_OP_REG, + op_reg_val); + if (ret < 0) + goto done; + } + + if (op_reg_flags & GP2AP020A00F_FLAG_A) { + /* Check D0 register to assess if the lux mode + * transition is required. + */ + ret = regmap_bulk_read(priv->regmap, GP2AP020A00F_D0_L_REG, + d0_reg_buf, 2); + if (ret < 0) + goto done; + + output_val = le16_to_cpup((__le16 *)d0_reg_buf); + + if (gp2ap020a00f_adjust_lux_mode(priv, output_val)) + goto done; + + gp2ap020a00f_output_to_lux(priv, &output_val); + + /* + * We need to check output value to distinguish + * between high and low ambient light threshold event. + */ + if (test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, &priv->flags)) { + thresh_val_id = + GP2AP020A00F_THRESH_VAL_ID(GP2AP020A00F_TH_L_REG); + if (output_val > priv->thresh_val[thresh_val_id]) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE( + IIO_LIGHT, + GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + } + + if (test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, &priv->flags)) { + thresh_val_id = + GP2AP020A00F_THRESH_VAL_ID(GP2AP020A00F_TL_L_REG); + if (output_val < priv->thresh_val[thresh_val_id]) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE( + IIO_LIGHT, + GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, + IIO_MOD_LIGHT_CLEAR, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns()); + } + } + + if (priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_CLEAR || + priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_IR || + priv->cur_opmode == GP2AP020A00F_OPMODE_READ_RAW_PROXIMITY) { + set_bit(GP2AP020A00F_FLAG_DATA_READY, &priv->flags); + wake_up(&priv->data_ready_queue); + goto done; + } + + if (test_bit(GP2AP020A00F_FLAG_ALS_CLEAR_TRIGGER, &priv->flags) || + test_bit(GP2AP020A00F_FLAG_ALS_IR_TRIGGER, &priv->flags) || + test_bit(GP2AP020A00F_FLAG_PROX_TRIGGER, &priv->flags)) + /* This fires off the trigger. */ + irq_work_queue(&priv->work); + +done: + return IRQ_HANDLED; +} + +static irqreturn_t gp2ap020a00f_trigger_handler(int irq, void *data) +{ + struct iio_poll_func *pf = data; + struct iio_dev *indio_dev = pf->indio_dev; + struct gp2ap020a00f_data *priv = iio_priv(indio_dev); + size_t d_size = 0; + int i, out_val, ret; + + for_each_set_bit(i, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = regmap_bulk_read(priv->regmap, + GP2AP020A00F_DATA_REG(i), + &priv->buffer[d_size], 2); + if (ret < 0) + goto done; + + if (i == GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR || + i == GP2AP020A00F_SCAN_MODE_LIGHT_IR) { + out_val = le16_to_cpup((__le16 *)&priv->buffer[d_size]); + gp2ap020a00f_output_to_lux(priv, &out_val); + + put_unaligned_le32(out_val, &priv->buffer[d_size]); + d_size += 4; + } else { + d_size += 2; + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, priv->buffer, + pf->timestamp); +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static u8 gp2ap020a00f_get_thresh_reg(const struct iio_chan_spec *chan, + enum iio_event_direction event_dir) +{ + switch (chan->type) { + case IIO_PROXIMITY: + if (event_dir == IIO_EV_DIR_RISING) + return GP2AP020A00F_PH_L_REG; + else + return GP2AP020A00F_PL_L_REG; + case IIO_LIGHT: + if (event_dir == IIO_EV_DIR_RISING) + return GP2AP020A00F_TH_L_REG; + else + return GP2AP020A00F_TL_L_REG; + default: + break; + } + + return -EINVAL; +} + +static int gp2ap020a00f_write_event_val(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 gp2ap020a00f_data *data = iio_priv(indio_dev); + bool event_en = false; + u8 thresh_val_id; + u8 thresh_reg_l; + int err = 0; + + mutex_lock(&data->lock); + + thresh_reg_l = gp2ap020a00f_get_thresh_reg(chan, dir); + thresh_val_id = GP2AP020A00F_THRESH_VAL_ID(thresh_reg_l); + + if (thresh_val_id > GP2AP020A00F_THRESH_PH) { + err = -EINVAL; + goto error_unlock; + } + + switch (thresh_reg_l) { + case GP2AP020A00F_TH_L_REG: + event_en = test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, + &data->flags); + break; + case GP2AP020A00F_TL_L_REG: + event_en = test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, + &data->flags); + break; + case GP2AP020A00F_PH_L_REG: + if (val == 0) { + err = -EINVAL; + goto error_unlock; + } + event_en = test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, + &data->flags); + break; + case GP2AP020A00F_PL_L_REG: + if (val == 0) { + err = -EINVAL; + goto error_unlock; + } + event_en = test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, + &data->flags); + break; + } + + data->thresh_val[thresh_val_id] = val; + err = gp2ap020a00f_write_event_threshold(data, thresh_val_id, + event_en); +error_unlock: + mutex_unlock(&data->lock); + + return err; +} + +static int gp2ap020a00f_read_event_val(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 gp2ap020a00f_data *data = iio_priv(indio_dev); + u8 thresh_reg_l; + int err = IIO_VAL_INT; + + mutex_lock(&data->lock); + + thresh_reg_l = gp2ap020a00f_get_thresh_reg(chan, dir); + + if (thresh_reg_l > GP2AP020A00F_PH_L_REG) { + err = -EINVAL; + goto error_unlock; + } + + *val = data->thresh_val[GP2AP020A00F_THRESH_VAL_ID(thresh_reg_l)]; + +error_unlock: + mutex_unlock(&data->lock); + + return err; +} + +static int gp2ap020a00f_write_prox_event_config(struct iio_dev *indio_dev, + int state) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + enum gp2ap020a00f_cmd cmd_high_ev, cmd_low_ev; + int err; + + cmd_high_ev = state ? GP2AP020A00F_CMD_PROX_HIGH_EV_EN : + GP2AP020A00F_CMD_PROX_HIGH_EV_DIS; + cmd_low_ev = state ? GP2AP020A00F_CMD_PROX_LOW_EV_EN : + GP2AP020A00F_CMD_PROX_LOW_EV_DIS; + + /* + * In order to enable proximity detection feature in the device + * both high and low threshold registers have to be written + * with different values, greater than zero. + */ + if (state) { + if (data->thresh_val[GP2AP020A00F_THRESH_PL] == 0) + return -EINVAL; + + if (data->thresh_val[GP2AP020A00F_THRESH_PH] == 0) + return -EINVAL; + } + + err = gp2ap020a00f_exec_cmd(data, cmd_high_ev); + if (err < 0) + return err; + + err = gp2ap020a00f_exec_cmd(data, cmd_low_ev); + if (err < 0) + return err; + + free_irq(data->client->irq, indio_dev); + + if (state) + err = request_threaded_irq(data->client->irq, NULL, + &gp2ap020a00f_prox_sensing_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "gp2ap020a00f_prox_sensing", + indio_dev); + else { + err = request_threaded_irq(data->client->irq, NULL, + &gp2ap020a00f_thresh_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "gp2ap020a00f_thresh_event", + indio_dev); + } + + return err; +} + +static int gp2ap020a00f_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 gp2ap020a00f_data *data = iio_priv(indio_dev); + enum gp2ap020a00f_cmd cmd; + int err; + + mutex_lock(&data->lock); + + switch (chan->type) { + case IIO_PROXIMITY: + err = gp2ap020a00f_write_prox_event_config(indio_dev, state); + break; + case IIO_LIGHT: + if (dir == IIO_EV_DIR_RISING) { + cmd = state ? GP2AP020A00F_CMD_ALS_HIGH_EV_EN : + GP2AP020A00F_CMD_ALS_HIGH_EV_DIS; + err = gp2ap020a00f_exec_cmd(data, cmd); + } else { + cmd = state ? GP2AP020A00F_CMD_ALS_LOW_EV_EN : + GP2AP020A00F_CMD_ALS_LOW_EV_DIS; + err = gp2ap020a00f_exec_cmd(data, cmd); + } + break; + default: + err = -EINVAL; + } + + mutex_unlock(&data->lock); + + return err; +} + +static int gp2ap020a00f_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 gp2ap020a00f_data *data = iio_priv(indio_dev); + int event_en = 0; + + mutex_lock(&data->lock); + + switch (chan->type) { + case IIO_PROXIMITY: + if (dir == IIO_EV_DIR_RISING) + event_en = test_bit(GP2AP020A00F_FLAG_PROX_RISING_EV, + &data->flags); + else + event_en = test_bit(GP2AP020A00F_FLAG_PROX_FALLING_EV, + &data->flags); + break; + case IIO_LIGHT: + if (dir == IIO_EV_DIR_RISING) + event_en = test_bit(GP2AP020A00F_FLAG_ALS_RISING_EV, + &data->flags); + else + event_en = test_bit(GP2AP020A00F_FLAG_ALS_FALLING_EV, + &data->flags); + break; + default: + event_en = -EINVAL; + break; + } + + mutex_unlock(&data->lock); + + return event_en; +} + +static int gp2ap020a00f_read_channel(struct gp2ap020a00f_data *data, + struct iio_chan_spec const *chan, int *val) +{ + enum gp2ap020a00f_cmd cmd; + int err; + + switch (chan->scan_index) { + case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: + cmd = GP2AP020A00F_CMD_READ_RAW_CLEAR; + break; + case GP2AP020A00F_SCAN_MODE_LIGHT_IR: + cmd = GP2AP020A00F_CMD_READ_RAW_IR; + break; + case GP2AP020A00F_SCAN_MODE_PROXIMITY: + cmd = GP2AP020A00F_CMD_READ_RAW_PROXIMITY; + break; + default: + return -EINVAL; + } + + err = gp2ap020a00f_exec_cmd(data, cmd); + if (err < 0) { + dev_err(&data->client->dev, + "gp2ap020a00f_exec_cmd failed\n"); + goto error_ret; + } + + err = gp2ap020a00f_read_output(data, chan->address, val); + if (err < 0) + dev_err(&data->client->dev, + "gp2ap020a00f_read_output failed\n"); + + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + dev_err(&data->client->dev, + "Failed to shut down the device.\n"); + + if (cmd == GP2AP020A00F_CMD_READ_RAW_CLEAR || + cmd == GP2AP020A00F_CMD_READ_RAW_IR) + gp2ap020a00f_output_to_lux(data, val); + +error_ret: + return err; +} + +static int gp2ap020a00f_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int err = -EINVAL; + + mutex_lock(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) { + err = -EBUSY; + goto error_unlock; + } + + err = gp2ap020a00f_read_channel(data, chan, val); + break; + } + +error_unlock: + mutex_unlock(&data->lock); + + return err < 0 ? err : IIO_VAL_INT; +} + +static const struct iio_event_spec gp2ap020a00f_event_spec_light[] = { + { + .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 gp2ap020a00f_event_spec_prox[] = { + { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec gp2ap020a00f_channels[] = { + { + .type = IIO_LIGHT, + .channel2 = IIO_MOD_LIGHT_CLEAR, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type = { + .sign = 'u', + .realbits = 24, + .shift = 0, + .storagebits = 32, + .endianness = IIO_LE, + }, + .scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR, + .address = GP2AP020A00F_D0_L_REG, + .event_spec = gp2ap020a00f_event_spec_light, + .num_event_specs = ARRAY_SIZE(gp2ap020a00f_event_spec_light), + }, + { + .type = IIO_LIGHT, + .channel2 = IIO_MOD_LIGHT_IR, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type = { + .sign = 'u', + .realbits = 24, + .shift = 0, + .storagebits = 32, + .endianness = IIO_LE, + }, + .scan_index = GP2AP020A00F_SCAN_MODE_LIGHT_IR, + .address = GP2AP020A00F_D1_L_REG, + }, + { + .type = IIO_PROXIMITY, + .modified = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_type = { + .sign = 'u', + .realbits = 16, + .shift = 0, + .storagebits = 16, + .endianness = IIO_LE, + }, + .scan_index = GP2AP020A00F_SCAN_MODE_PROXIMITY, + .address = GP2AP020A00F_D2_L_REG, + .event_spec = gp2ap020a00f_event_spec_prox, + .num_event_specs = ARRAY_SIZE(gp2ap020a00f_event_spec_prox), + }, + IIO_CHAN_SOFT_TIMESTAMP(GP2AP020A00F_CHAN_TIMESTAMP), +}; + +static const struct iio_info gp2ap020a00f_info = { + .read_raw = &gp2ap020a00f_read_raw, + .read_event_value = &gp2ap020a00f_read_event_val, + .read_event_config = &gp2ap020a00f_read_event_config, + .write_event_value = &gp2ap020a00f_write_event_val, + .write_event_config = &gp2ap020a00f_write_event_config, + .driver_module = THIS_MODULE, +}; + +static int gp2ap020a00f_buffer_postenable(struct iio_dev *indio_dev) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int i, err = 0; + + mutex_lock(&data->lock); + + /* + * Enable triggers according to the scan_mask. Enabling either + * LIGHT_CLEAR or LIGHT_IR scan mode results in enabling ALS + * module in the device, which generates samples in both D0 (clear) + * and D1 (ir) registers. As the two registers are bound to the + * two separate IIO channels they are treated in the driver logic + * as if they were controlled independently. + */ + for_each_set_bit(i, indio_dev->active_scan_mask, + indio_dev->masklength) { + switch (i) { + case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_CLEAR_EN); + break; + case GP2AP020A00F_SCAN_MODE_LIGHT_IR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_IR_EN); + break; + case GP2AP020A00F_SCAN_MODE_PROXIMITY: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_PROX_EN); + break; + } + } + + if (err < 0) + goto error_unlock; + + data->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!data->buffer) { + err = -ENOMEM; + goto error_unlock; + } + + err = iio_triggered_buffer_postenable(indio_dev); + +error_unlock: + mutex_unlock(&data->lock); + + return err; +} + +static int gp2ap020a00f_buffer_predisable(struct iio_dev *indio_dev) +{ + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int i, err; + + mutex_lock(&data->lock); + + err = iio_triggered_buffer_predisable(indio_dev); + if (err < 0) + goto error_unlock; + + for_each_set_bit(i, indio_dev->active_scan_mask, + indio_dev->masklength) { + switch (i) { + case GP2AP020A00F_SCAN_MODE_LIGHT_CLEAR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_CLEAR_DIS); + break; + case GP2AP020A00F_SCAN_MODE_LIGHT_IR: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_IR_DIS); + break; + case GP2AP020A00F_SCAN_MODE_PROXIMITY: + err = gp2ap020a00f_exec_cmd(data, + GP2AP020A00F_CMD_TRIGGER_PROX_DIS); + break; + } + } + + if (err == 0) + kfree(data->buffer); + +error_unlock: + mutex_unlock(&data->lock); + + return err; +} + +static const struct iio_buffer_setup_ops gp2ap020a00f_buffer_setup_ops = { + .postenable = &gp2ap020a00f_buffer_postenable, + .predisable = &gp2ap020a00f_buffer_predisable, +}; + +static const struct iio_trigger_ops gp2ap020a00f_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int gp2ap020a00f_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct gp2ap020a00f_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->vled_reg = devm_regulator_get(&client->dev, "vled"); + if (IS_ERR(data->vled_reg)) + return PTR_ERR(data->vled_reg); + + err = regulator_enable(data->vled_reg); + if (err) + return err; + + regmap = devm_regmap_init_i2c(client, &gp2ap020a00f_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Regmap initialization failed.\n"); + err = PTR_ERR(regmap); + goto error_regulator_disable; + } + + /* Initialize device registers */ + err = regmap_bulk_write(regmap, GP2AP020A00F_OP_REG, + gp2ap020a00f_reg_init_tab, + ARRAY_SIZE(gp2ap020a00f_reg_init_tab)); + + if (err < 0) { + dev_err(&client->dev, "Device initialization failed.\n"); + goto error_regulator_disable; + } + + i2c_set_clientdata(client, indio_dev); + + data->client = client; + data->cur_opmode = GP2AP020A00F_OPMODE_SHUTDOWN; + data->regmap = regmap; + init_waitqueue_head(&data->data_ready_queue); + + mutex_init(&data->lock); + indio_dev->dev.parent = &client->dev; + indio_dev->channels = gp2ap020a00f_channels; + indio_dev->num_channels = ARRAY_SIZE(gp2ap020a00f_channels); + indio_dev->info = &gp2ap020a00f_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Allocate buffer */ + err = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &gp2ap020a00f_trigger_handler, &gp2ap020a00f_buffer_setup_ops); + if (err < 0) + goto error_regulator_disable; + + /* Allocate trigger */ + data->trig = devm_iio_trigger_alloc(&client->dev, "%s-trigger", + indio_dev->name); + if (data->trig == NULL) { + err = -ENOMEM; + dev_err(&indio_dev->dev, "Failed to allocate iio trigger.\n"); + goto error_uninit_buffer; + } + + /* This needs to be requested here for read_raw calls to work. */ + err = request_threaded_irq(client->irq, NULL, + &gp2ap020a00f_thresh_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "gp2ap020a00f_als_event", + indio_dev); + if (err < 0) { + dev_err(&client->dev, "Irq request failed.\n"); + goto error_uninit_buffer; + } + + data->trig->ops = &gp2ap020a00f_trigger_ops; + data->trig->dev.parent = &data->client->dev; + + init_irq_work(&data->work, gp2ap020a00f_iio_trigger_work); + + err = iio_trigger_register(data->trig); + if (err < 0) { + dev_err(&client->dev, "Failed to register iio trigger.\n"); + goto error_free_irq; + } + + err = iio_device_register(indio_dev); + if (err < 0) + goto error_trigger_unregister; + + return 0; + +error_trigger_unregister: + iio_trigger_unregister(data->trig); +error_free_irq: + free_irq(client->irq, indio_dev); +error_uninit_buffer: + iio_triggered_buffer_cleanup(indio_dev); +error_regulator_disable: + regulator_disable(data->vled_reg); + + return err; +} + +static int gp2ap020a00f_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct gp2ap020a00f_data *data = iio_priv(indio_dev); + int err; + + err = gp2ap020a00f_set_operation_mode(data, + GP2AP020A00F_OPMODE_SHUTDOWN); + if (err < 0) + dev_err(&indio_dev->dev, "Failed to power off the device.\n"); + + iio_device_unregister(indio_dev); + iio_trigger_unregister(data->trig); + free_irq(client->irq, indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + regulator_disable(data->vled_reg); + + return 0; +} + +static const struct i2c_device_id gp2ap020a00f_id[] = { + { GP2A_I2C_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, gp2ap020a00f_id); + +#ifdef CONFIG_OF +static const struct of_device_id gp2ap020a00f_of_match[] = { + { .compatible = "sharp,gp2ap020a00f" }, + { } +}; +#endif + +static struct i2c_driver gp2ap020a00f_driver = { + .driver = { + .name = GP2A_I2C_NAME, + .of_match_table = of_match_ptr(gp2ap020a00f_of_match), + .owner = THIS_MODULE, + }, + .probe = gp2ap020a00f_probe, + .remove = gp2ap020a00f_remove, + .id_table = gp2ap020a00f_id, +}; + +module_i2c_driver(gp2ap020a00f_driver); + +MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>"); +MODULE_DESCRIPTION("Sharp GP2AP020A00F Proximity/ALS sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c new file mode 100644 index 000000000..1609ecdd0 --- /dev/null +++ b/drivers/iio/light/hid-sensor-als.c @@ -0,0 +1,386 @@ +/* + * HID Sensors Driver + * Copyright (c) 2012, 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; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +#define CHANNEL_SCAN_INDEX_ILLUM 0 + +struct als_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info als_illum; + u32 illum; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +/* Channel definitions */ +static const struct iio_chan_spec als_channels[] = { + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .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), + .scan_index = CHANNEL_SCAN_INDEX_ILLUM, + } +}; + +/* Adjust channel real bits based on report descriptor */ +static void als_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 u32 */ + channels[channel].scan_type.storagebits = sizeof(u32) * 8; +} + +/* Channel read_raw handler */ +static int als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct als_state *als_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + + *val = 0; + *val2 = 0; + switch (mask) { + case 0: + switch (chan->scan_index) { + case CHANNEL_SCAN_INDEX_ILLUM: + report_id = als_state->als_illum.report_id; + address = + HID_USAGE_SENSOR_LIGHT_ILLUM; + break; + default: + report_id = -1; + break; + } + if (report_id >= 0) { + hid_sensor_power_state(&als_state->common_attributes, + true); + *val = sensor_hub_input_attr_get_raw_value( + als_state->common_attributes.hsdev, + HID_USAGE_SENSOR_ALS, address, + report_id, + SENSOR_HUB_SYNC); + hid_sensor_power_state(&als_state->common_attributes, + false); + } else { + *val = 0; + return -EINVAL; + } + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = als_state->scale_pre_decml; + *val2 = als_state->scale_post_decml; + ret_type = als_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = als_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &als_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &als_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int als_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct als_state *als_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &als_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &als_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info als_info = { + .driver_module = THIS_MODULE, + .read_raw = &als_read_raw, + .write_raw = &als_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data, + int len) +{ + dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); + iio_push_to_buffers(indio_dev, data); +} + +/* Callback handler to send event after all samples are received and captured */ +static int als_proc_event(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct als_state *als_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "als_proc_event\n"); + if (atomic_read(&als_state->common_attributes.data_ready)) + hid_sensor_push_data(indio_dev, + &als_state->illum, + sizeof(als_state->illum)); + + return 0; +} + +/* Capture samples in local storage */ +static int als_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct als_state *als_state = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (usage_id) { + case HID_USAGE_SENSOR_LIGHT_ILLUM: + als_state->illum = *(u32 *)raw_data; + ret = 0; + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int als_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + unsigned usage_id, + struct als_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_LIGHT_ILLUM, + &st->als_illum); + if (ret < 0) + return ret; + als_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_ILLUM, + st->als_illum.size); + + dev_dbg(&pdev->dev, "als %x:%x\n", st->als_illum.index, + st->als_illum.report_id); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_ALS, + &st->als_illum, + &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_LIGHT, + &st->common_attributes.sensitivity); + dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", + st->common_attributes.sensitivity.index, + st->common_attributes.sensitivity.report_id); + } + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_als_probe(struct platform_device *pdev) +{ + int ret = 0; + static const char *name = "als"; + struct iio_dev *indio_dev; + struct als_state *als_state; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_chan_spec *channels; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct als_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + als_state = iio_priv(indio_dev); + als_state->common_attributes.hsdev = hsdev; + als_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_ALS, + &als_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + channels = kmemdup(als_channels, sizeof(als_channels), GFP_KERNEL); + if (!channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = als_parse_report(pdev, hsdev, channels, + HID_USAGE_SENSOR_ALS, als_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + goto error_free_dev_mem; + } + + indio_dev->channels = channels; + indio_dev->num_channels = + ARRAY_SIZE(als_channels); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &als_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to initialize trigger buffer\n"); + goto error_free_dev_mem; + } + atomic_set(&als_state->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, name, + &als_state->common_attributes); + if (ret < 0) { + dev_err(&pdev->dev, "trigger setup failed\n"); + goto error_unreg_buffer_funcs; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_trigger; + } + + als_state->callbacks.send_event = als_proc_event; + als_state->callbacks.capture_sample = als_capture_sample; + als_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_ALS, + &als_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_iio_unreg; + } + + return ret; + +error_iio_unreg: + iio_device_unregister(indio_dev); +error_remove_trigger: + hid_sensor_remove_trigger(&als_state->common_attributes); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: + kfree(indio_dev->channels); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_als_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct als_state *als_state = iio_priv(indio_dev); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ALS); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(&als_state->common_attributes); + iio_triggered_buffer_cleanup(indio_dev); + kfree(indio_dev->channels); + + return 0; +} + +static struct platform_device_id hid_als_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200041", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_als_ids); + +static struct platform_driver hid_als_platform_driver = { + .id_table = hid_als_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_als_probe, + .remove = hid_als_remove, +}; +module_platform_driver(hid_als_platform_driver); + +MODULE_DESCRIPTION("HID Sensor ALS"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/hid-sensor-prox.c b/drivers/iio/light/hid-sensor-prox.c new file mode 100644 index 000000000..ef60bae73 --- /dev/null +++ b/drivers/iio/light/hid-sensor-prox.c @@ -0,0 +1,375 @@ +/* + * HID Sensors Driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +#define CHANNEL_SCAN_INDEX_PRESENCE 0 + +struct prox_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info prox_attr; + u32 human_presence; +}; + +/* Channel definitions */ +static const struct iio_chan_spec prox_channels[] = { + { + .type = IIO_PROXIMITY, + .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), + .scan_index = CHANNEL_SCAN_INDEX_PRESENCE, + } +}; + +/* Adjust channel real bits based on report descriptor */ +static void prox_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 u32 */ + channels[channel].scan_type.storagebits = sizeof(u32) * 8; +} + +/* Channel read_raw handler */ +static int prox_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct prox_state *prox_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->scan_index) { + case CHANNEL_SCAN_INDEX_PRESENCE: + report_id = prox_state->prox_attr.report_id; + address = + HID_USAGE_SENSOR_HUMAN_PRESENCE; + break; + default: + report_id = -1; + break; + } + if (report_id >= 0) { + hid_sensor_power_state(&prox_state->common_attributes, + true); + *val = sensor_hub_input_attr_get_raw_value( + prox_state->common_attributes.hsdev, + HID_USAGE_SENSOR_PROX, address, + report_id, + SENSOR_HUB_SYNC); + hid_sensor_power_state(&prox_state->common_attributes, + false); + } else { + *val = 0; + return -EINVAL; + } + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = prox_state->prox_attr.units; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_OFFSET: + *val = hid_sensor_convert_exponent( + prox_state->prox_attr.unit_expo); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &prox_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &prox_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int prox_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct prox_state *prox_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &prox_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &prox_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info prox_info = { + .driver_module = THIS_MODULE, + .read_raw = &prox_read_raw, + .write_raw = &prox_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data, + int len) +{ + dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); + iio_push_to_buffers(indio_dev, data); +} + +/* Callback handler to send event after all samples are received and captured */ +static int prox_proc_event(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct prox_state *prox_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "prox_proc_event\n"); + if (atomic_read(&prox_state->common_attributes.data_ready)) + hid_sensor_push_data(indio_dev, + &prox_state->human_presence, + sizeof(prox_state->human_presence)); + + return 0; +} + +/* Capture samples in local storage */ +static int prox_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct prox_state *prox_state = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (usage_id) { + case HID_USAGE_SENSOR_HUMAN_PRESENCE: + prox_state->human_presence = *(u32 *)raw_data; + ret = 0; + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int prox_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + unsigned usage_id, + struct prox_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_HUMAN_PRESENCE, + &st->prox_attr); + if (ret < 0) + return ret; + prox_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_PRESENCE, + st->prox_attr.size); + + dev_dbg(&pdev->dev, "prox %x:%x\n", st->prox_attr.index, + st->prox_attr.report_id); + + /* 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_PRESENCE, + &st->common_attributes.sensitivity); + dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", + st->common_attributes.sensitivity.index, + st->common_attributes.sensitivity.report_id); + } + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_prox_probe(struct platform_device *pdev) +{ + int ret = 0; + static const char *name = "prox"; + struct iio_dev *indio_dev; + struct prox_state *prox_state; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct prox_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + prox_state = iio_priv(indio_dev); + prox_state->common_attributes.hsdev = hsdev; + prox_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_PROX, + &prox_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + indio_dev->channels = kmemdup(prox_channels, sizeof(prox_channels), + GFP_KERNEL); + if (!indio_dev->channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = prox_parse_report(pdev, hsdev, + (struct iio_chan_spec *)indio_dev->channels, + HID_USAGE_SENSOR_PROX, prox_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + goto error_free_dev_mem; + } + + indio_dev->num_channels = + ARRAY_SIZE(prox_channels); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &prox_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to initialize trigger buffer\n"); + goto error_free_dev_mem; + } + atomic_set(&prox_state->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, name, + &prox_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "trigger setup failed\n"); + goto error_unreg_buffer_funcs; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_trigger; + } + + prox_state->callbacks.send_event = prox_proc_event; + prox_state->callbacks.capture_sample = prox_capture_sample; + prox_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_PROX, + &prox_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_iio_unreg; + } + + return ret; + +error_iio_unreg: + iio_device_unregister(indio_dev); +error_remove_trigger: + hid_sensor_remove_trigger(&prox_state->common_attributes); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: + kfree(indio_dev->channels); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_prox_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct prox_state *prox_state = iio_priv(indio_dev); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PROX); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(&prox_state->common_attributes); + iio_triggered_buffer_cleanup(indio_dev); + kfree(indio_dev->channels); + + return 0; +} + +static struct platform_device_id hid_prox_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200011", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_prox_ids); + +static struct platform_driver hid_prox_platform_driver = { + .id_table = hid_prox_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_prox_probe, + .remove = hid_prox_remove, +}; +module_platform_driver(hid_prox_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Proximity"); +MODULE_AUTHOR("Archana Patni <archana.patni@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/isl29125.c b/drivers/iio/light/isl29125.c new file mode 100644 index 000000000..c82f4a6f8 --- /dev/null +++ b/drivers/iio/light/isl29125.c @@ -0,0 +1,347 @@ +/* + * isl29125.c - Support for Intersil ISL29125 RGB light sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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. + * + * RGB light sensor with 16-bit channels for red, green, blue); + * 7-bit I2C slave address 0x44 + * + * TODO: interrupt support, IR compensation, thresholds, 12bit + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/pm.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define ISL29125_DRV_NAME "isl29125" + +#define ISL29125_DEVICE_ID 0x00 +#define ISL29125_CONF1 0x01 +#define ISL29125_CONF2 0x02 +#define ISL29125_CONF3 0x03 +#define ISL29125_STATUS 0x08 +#define ISL29125_GREEN_DATA 0x09 +#define ISL29125_RED_DATA 0x0b +#define ISL29125_BLUE_DATA 0x0d + +#define ISL29125_ID 0x7d + +#define ISL29125_MODE_MASK GENMASK(2, 0) +#define ISL29125_MODE_PD 0x0 +#define ISL29125_MODE_G 0x1 +#define ISL29125_MODE_R 0x2 +#define ISL29125_MODE_B 0x3 +#define ISL29125_MODE_RGB 0x5 + +#define ISL29125_MODE_RANGE BIT(3) + +#define ISL29125_STATUS_CONV BIT(1) + +struct isl29125_data { + struct i2c_client *client; + struct mutex lock; + u8 conf1; + u16 buffer[8]; /* 3x 16-bit, padding, 8 bytes timestamp */ +}; + +#define ISL29125_CHANNEL(_color, _si) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const struct iio_chan_spec isl29125_channels[] = { + ISL29125_CHANNEL(GREEN, 0), + ISL29125_CHANNEL(RED, 1), + ISL29125_CHANNEL(BLUE, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct { + u8 mode, data; +} isl29125_regs[] = { + {ISL29125_MODE_G, ISL29125_GREEN_DATA}, + {ISL29125_MODE_R, ISL29125_RED_DATA}, + {ISL29125_MODE_B, ISL29125_BLUE_DATA}, +}; + +static int isl29125_read_data(struct isl29125_data *data, int si) +{ + int tries = 5; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1 | isl29125_regs[si].mode); + if (ret < 0) + return ret; + + msleep(101); + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, ISL29125_STATUS); + if (ret < 0) + goto fail; + if (ret & ISL29125_STATUS_CONV) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + ret = -EIO; + goto fail; + } + + ret = i2c_smbus_read_word_data(data->client, isl29125_regs[si].data); + +fail: + i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, data->conf1); + return ret; +} + +static int isl29125_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct isl29125_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + mutex_lock(&data->lock); + ret = isl29125_read_data(data, chan->scan_index); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (data->conf1 & ISL29125_MODE_RANGE) + *val2 = 152590; /* 10k lux full range */ + else + *val2 = 5722; /* 375 lux full range */ + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int isl29125_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct isl29125_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val != 0) + return -EINVAL; + if (val2 == 152590) + data->conf1 |= ISL29125_MODE_RANGE; + else if (val2 == 5722) + data->conf1 &= ~ISL29125_MODE_RANGE; + else + return -EINVAL; + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); + default: + return -EINVAL; + } +} + +static irqreturn_t isl29125_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct isl29125_data *data = iio_priv(indio_dev); + int i, j = 0; + + for_each_set_bit(i, indio_dev->active_scan_mask, + indio_dev->masklength) { + int ret = i2c_smbus_read_word_data(data->client, + isl29125_regs[i].data); + if (ret < 0) + goto done; + + data->buffer[j++] = ret; + } + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_info isl29125_info = { + .read_raw = isl29125_read_raw, + .write_raw = isl29125_write_raw, + .driver_module = THIS_MODULE, +}; + +static int isl29125_buffer_preenable(struct iio_dev *indio_dev) +{ + struct isl29125_data *data = iio_priv(indio_dev); + + data->conf1 |= ISL29125_MODE_RGB; + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); +} + +static int isl29125_buffer_predisable(struct iio_dev *indio_dev) +{ + struct isl29125_data *data = iio_priv(indio_dev); + int ret; + + ret = iio_triggered_buffer_predisable(indio_dev); + if (ret < 0) + return ret; + + data->conf1 &= ~ISL29125_MODE_MASK; + data->conf1 |= ISL29125_MODE_PD; + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); +} + +static const struct iio_buffer_setup_ops isl29125_buffer_setup_ops = { + .preenable = isl29125_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = isl29125_buffer_predisable, +}; + +static int isl29125_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isl29125_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &isl29125_info; + indio_dev->name = ISL29125_DRV_NAME; + indio_dev->channels = isl29125_channels; + indio_dev->num_channels = ARRAY_SIZE(isl29125_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, ISL29125_DEVICE_ID); + if (ret < 0) + return ret; + if (ret != ISL29125_ID) + return -ENODEV; + + data->conf1 = ISL29125_MODE_PD | ISL29125_MODE_RANGE; + ret = i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, ISL29125_STATUS, 0); + if (ret < 0) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + isl29125_trigger_handler, &isl29125_buffer_setup_ops); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +static int isl29125_powerdown(struct isl29125_data *data) +{ + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + (data->conf1 & ~ISL29125_MODE_MASK) | ISL29125_MODE_PD); +} + +static int isl29125_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + isl29125_powerdown(iio_priv(indio_dev)); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int isl29125_suspend(struct device *dev) +{ + struct isl29125_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return isl29125_powerdown(data); +} + +static int isl29125_resume(struct device *dev) +{ + struct isl29125_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return i2c_smbus_write_byte_data(data->client, ISL29125_CONF1, + data->conf1); +} +#endif + +static SIMPLE_DEV_PM_OPS(isl29125_pm_ops, isl29125_suspend, isl29125_resume); + +static const struct i2c_device_id isl29125_id[] = { + { "isl29125", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl29125_id); + +static struct i2c_driver isl29125_driver = { + .driver = { + .name = ISL29125_DRV_NAME, + .pm = &isl29125_pm_ops, + .owner = THIS_MODULE, + }, + .probe = isl29125_probe, + .remove = isl29125_remove, + .id_table = isl29125_id, +}; +module_i2c_driver(isl29125_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("ISL29125 RGB light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/jsa1212.c b/drivers/iio/light/jsa1212.c new file mode 100644 index 000000000..3a3af89be --- /dev/null +++ b/drivers/iio/light/jsa1212.c @@ -0,0 +1,471 @@ +/* + * JSA1212 Ambient Light & Proximity Sensor Driver + * + * Copyright (c) 2014, 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. + * + * JSA1212 I2C slave address: 0x44(ADDR tied to GND), 0x45(ADDR tied to VDD) + * + * TODO: Interrupt support, thresholds, range support. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/acpi.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +/* JSA1212 reg address */ +#define JSA1212_CONF_REG 0x01 +#define JSA1212_INT_REG 0x02 +#define JSA1212_PXS_LT_REG 0x03 +#define JSA1212_PXS_HT_REG 0x04 +#define JSA1212_ALS_TH1_REG 0x05 +#define JSA1212_ALS_TH2_REG 0x06 +#define JSA1212_ALS_TH3_REG 0x07 +#define JSA1212_PXS_DATA_REG 0x08 +#define JSA1212_ALS_DT1_REG 0x09 +#define JSA1212_ALS_DT2_REG 0x0A +#define JSA1212_ALS_RNG_REG 0x0B +#define JSA1212_MAX_REG 0x0C + +/* JSA1212 reg masks */ +#define JSA1212_CONF_MASK 0xFF +#define JSA1212_INT_MASK 0xFF +#define JSA1212_PXS_LT_MASK 0xFF +#define JSA1212_PXS_HT_MASK 0xFF +#define JSA1212_ALS_TH1_MASK 0xFF +#define JSA1212_ALS_TH2_LT_MASK 0x0F +#define JSA1212_ALS_TH2_HT_MASK 0xF0 +#define JSA1212_ALS_TH3_MASK 0xFF +#define JSA1212_PXS_DATA_MASK 0xFF +#define JSA1212_ALS_DATA_MASK 0x0FFF +#define JSA1212_ALS_DT1_MASK 0xFF +#define JSA1212_ALS_DT2_MASK 0x0F +#define JSA1212_ALS_RNG_MASK 0x07 + +/* JSA1212 CONF REG bits */ +#define JSA1212_CONF_PXS_MASK 0x80 +#define JSA1212_CONF_PXS_ENABLE 0x80 +#define JSA1212_CONF_PXS_DISABLE 0x00 +#define JSA1212_CONF_ALS_MASK 0x04 +#define JSA1212_CONF_ALS_ENABLE 0x04 +#define JSA1212_CONF_ALS_DISABLE 0x00 +#define JSA1212_CONF_IRDR_MASK 0x08 +/* Proxmity sensing IRDR current sink settings */ +#define JSA1212_CONF_IRDR_200MA 0x08 +#define JSA1212_CONF_IRDR_100MA 0x00 +#define JSA1212_CONF_PXS_SLP_MASK 0x70 +#define JSA1212_CONF_PXS_SLP_0MS 0x70 +#define JSA1212_CONF_PXS_SLP_12MS 0x60 +#define JSA1212_CONF_PXS_SLP_50MS 0x50 +#define JSA1212_CONF_PXS_SLP_75MS 0x40 +#define JSA1212_CONF_PXS_SLP_100MS 0x30 +#define JSA1212_CONF_PXS_SLP_200MS 0x20 +#define JSA1212_CONF_PXS_SLP_400MS 0x10 +#define JSA1212_CONF_PXS_SLP_800MS 0x00 + +/* JSA1212 INT REG bits */ +#define JSA1212_INT_CTRL_MASK 0x01 +#define JSA1212_INT_CTRL_EITHER 0x00 +#define JSA1212_INT_CTRL_BOTH 0x01 +#define JSA1212_INT_ALS_PRST_MASK 0x06 +#define JSA1212_INT_ALS_PRST_1CONV 0x00 +#define JSA1212_INT_ALS_PRST_4CONV 0x02 +#define JSA1212_INT_ALS_PRST_8CONV 0x04 +#define JSA1212_INT_ALS_PRST_16CONV 0x06 +#define JSA1212_INT_ALS_FLAG_MASK 0x08 +#define JSA1212_INT_ALS_FLAG_CLR 0x00 +#define JSA1212_INT_PXS_PRST_MASK 0x60 +#define JSA1212_INT_PXS_PRST_1CONV 0x00 +#define JSA1212_INT_PXS_PRST_4CONV 0x20 +#define JSA1212_INT_PXS_PRST_8CONV 0x40 +#define JSA1212_INT_PXS_PRST_16CONV 0x60 +#define JSA1212_INT_PXS_FLAG_MASK 0x80 +#define JSA1212_INT_PXS_FLAG_CLR 0x00 + +/* JSA1212 ALS RNG REG bits */ +#define JSA1212_ALS_RNG_0_2048 0x00 +#define JSA1212_ALS_RNG_0_1024 0x01 +#define JSA1212_ALS_RNG_0_512 0x02 +#define JSA1212_ALS_RNG_0_256 0x03 +#define JSA1212_ALS_RNG_0_128 0x04 + +/* JSA1212 INT threshold range */ +#define JSA1212_ALS_TH_MIN 0x0000 +#define JSA1212_ALS_TH_MAX 0x0FFF +#define JSA1212_PXS_TH_MIN 0x00 +#define JSA1212_PXS_TH_MAX 0xFF + +#define JSA1212_ALS_DELAY_MS 200 +#define JSA1212_PXS_DELAY_MS 100 + +#define JSA1212_DRIVER_NAME "jsa1212" +#define JSA1212_REGMAP_NAME "jsa1212_regmap" + +enum jsa1212_op_mode { + JSA1212_OPMODE_ALS_EN, + JSA1212_OPMODE_PXS_EN, +}; + +struct jsa1212_data { + struct i2c_client *client; + struct mutex lock; + u8 als_rng_idx; + bool als_en; /* ALS enable status */ + bool pxs_en; /* proximity enable status */ + struct regmap *regmap; +}; + +/* ALS range idx to val mapping */ +static const int jsa1212_als_range_val[] = {2048, 1024, 512, 256, 128, + 128, 128, 128}; + +/* Enables or disables ALS function based on status */ +static int jsa1212_als_enable(struct jsa1212_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG, + JSA1212_CONF_ALS_MASK, + status); + if (ret < 0) + return ret; + + data->als_en = !!status; + + return 0; +} + +/* Enables or disables PXS function based on status */ +static int jsa1212_pxs_enable(struct jsa1212_data *data, u8 status) +{ + int ret; + + ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG, + JSA1212_CONF_PXS_MASK, + status); + if (ret < 0) + return ret; + + data->pxs_en = !!status; + + return 0; +} + +static int jsa1212_read_als_data(struct jsa1212_data *data, + unsigned int *val) +{ + int ret; + __le16 als_data; + + ret = jsa1212_als_enable(data, JSA1212_CONF_ALS_ENABLE); + if (ret < 0) + return ret; + + /* Delay for data output */ + msleep(JSA1212_ALS_DELAY_MS); + + /* Read 12 bit data */ + ret = regmap_bulk_read(data->regmap, JSA1212_ALS_DT1_REG, &als_data, 2); + if (ret < 0) { + dev_err(&data->client->dev, "als data read err\n"); + goto als_data_read_err; + } + + *val = le16_to_cpu(als_data); + +als_data_read_err: + return jsa1212_als_enable(data, JSA1212_CONF_ALS_DISABLE); +} + +static int jsa1212_read_pxs_data(struct jsa1212_data *data, + unsigned int *val) +{ + int ret; + unsigned int pxs_data; + + ret = jsa1212_pxs_enable(data, JSA1212_CONF_PXS_ENABLE); + if (ret < 0) + return ret; + + /* Delay for data output */ + msleep(JSA1212_PXS_DELAY_MS); + + /* Read out all data */ + ret = regmap_read(data->regmap, JSA1212_PXS_DATA_REG, &pxs_data); + if (ret < 0) { + dev_err(&data->client->dev, "pxs data read err\n"); + goto pxs_data_read_err; + } + + *val = pxs_data & JSA1212_PXS_DATA_MASK; + +pxs_data_read_err: + return jsa1212_pxs_enable(data, JSA1212_CONF_PXS_DISABLE); +} + +static int jsa1212_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct jsa1212_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + switch (chan->type) { + case IIO_LIGHT: + ret = jsa1212_read_als_data(data, val); + break; + case IIO_PROXIMITY: + ret = jsa1212_read_pxs_data(data, val); + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&data->lock); + return ret < 0 ? ret : IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_LIGHT: + *val = jsa1212_als_range_val[data->als_rng_idx]; + *val2 = BIT(12); /* Max 12 bit value */ + return IIO_VAL_FRACTIONAL; + default: + break; + } + break; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_chan_spec jsa1212_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static const struct iio_info jsa1212_info = { + .driver_module = THIS_MODULE, + .read_raw = &jsa1212_read_raw, +}; + +static int jsa1212_chip_init(struct jsa1212_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, JSA1212_CONF_REG, + (JSA1212_CONF_PXS_SLP_50MS | + JSA1212_CONF_IRDR_200MA)); + if (ret < 0) + return ret; + + ret = regmap_write(data->regmap, JSA1212_INT_REG, + JSA1212_INT_ALS_PRST_4CONV); + if (ret < 0) + return ret; + + data->als_rng_idx = JSA1212_ALS_RNG_0_2048; + + return 0; +} + +static bool jsa1212_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case JSA1212_PXS_DATA_REG: + case JSA1212_ALS_DT1_REG: + case JSA1212_ALS_DT2_REG: + case JSA1212_INT_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config jsa1212_regmap_config = { + .name = JSA1212_REGMAP_NAME, + .reg_bits = 8, + .val_bits = 8, + .max_register = JSA1212_MAX_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = jsa1212_is_volatile_reg, +}; + +static int jsa1212_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct jsa1212_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &jsa1212_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Regmap initialization failed.\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + mutex_init(&data->lock); + + ret = jsa1212_chip_init(data); + if (ret < 0) + return ret; + + indio_dev->dev.parent = &client->dev; + indio_dev->channels = jsa1212_channels; + indio_dev->num_channels = ARRAY_SIZE(jsa1212_channels); + indio_dev->name = JSA1212_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->info = &jsa1212_info; + + ret = iio_device_register(indio_dev); + if (ret < 0) + dev_err(&client->dev, "%s: register device failed\n", __func__); + + return ret; +} + + /* power off the device */ +static int jsa1212_power_off(struct jsa1212_data *data) +{ + int ret; + + mutex_lock(&data->lock); + + ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG, + JSA1212_CONF_ALS_MASK | + JSA1212_CONF_PXS_MASK, + JSA1212_CONF_ALS_DISABLE | + JSA1212_CONF_PXS_DISABLE); + + if (ret < 0) + dev_err(&data->client->dev, "power off cmd failed\n"); + + mutex_unlock(&data->lock); + + return ret; +} + +static int jsa1212_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct jsa1212_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + return jsa1212_power_off(data); +} + +#ifdef CONFIG_PM_SLEEP +static int jsa1212_suspend(struct device *dev) +{ + struct jsa1212_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return jsa1212_power_off(data); +} + +static int jsa1212_resume(struct device *dev) +{ + int ret = 0; + struct jsa1212_data *data; + + data = iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + mutex_lock(&data->lock); + + if (data->als_en) { + ret = jsa1212_als_enable(data, JSA1212_CONF_ALS_ENABLE); + if (ret < 0) { + dev_err(dev, "als resume failed\n"); + goto unlock_and_ret; + } + } + + if (data->pxs_en) { + ret = jsa1212_pxs_enable(data, JSA1212_CONF_PXS_ENABLE); + if (ret < 0) + dev_err(dev, "pxs resume failed\n"); + } + +unlock_and_ret: + mutex_unlock(&data->lock); + return ret; +} + +static SIMPLE_DEV_PM_OPS(jsa1212_pm_ops, jsa1212_suspend, jsa1212_resume); + +#define JSA1212_PM_OPS (&jsa1212_pm_ops) +#else +#define JSA1212_PM_OPS NULL +#endif + +static const struct acpi_device_id jsa1212_acpi_match[] = { + {"JSA1212", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, jsa1212_acpi_match); + +static const struct i2c_device_id jsa1212_id[] = { + { JSA1212_DRIVER_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, jsa1212_id); + +static struct i2c_driver jsa1212_driver = { + .driver = { + .name = JSA1212_DRIVER_NAME, + .pm = JSA1212_PM_OPS, + .owner = THIS_MODULE, + .acpi_match_table = ACPI_PTR(jsa1212_acpi_match), + }, + .probe = jsa1212_probe, + .remove = jsa1212_remove, + .id_table = jsa1212_id, +}; +module_i2c_driver(jsa1212_driver); + +MODULE_AUTHOR("Sathya Kuppuswamy <sathyanarayanan.kuppuswamy@linux.intel.com>"); +MODULE_DESCRIPTION("JSA1212 proximity/ambient light sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c new file mode 100644 index 000000000..076bc46fa --- /dev/null +++ b/drivers/iio/light/lm3533-als.c @@ -0,0 +1,927 @@ +/* + * lm3533-als.c -- LM3533 Ambient Light Sensor driver + * + * Copyright (C) 2011-2012 Texas Instruments + * + * Author: Johan Hovold <jhovold@gmail.com> + * + * 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. + */ + +#include <linux/atomic.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <linux/mfd/lm3533.h> + + +#define LM3533_ALS_RESISTOR_MIN 1 +#define LM3533_ALS_RESISTOR_MAX 127 +#define LM3533_ALS_CHANNEL_CURRENT_MAX 2 +#define LM3533_ALS_THRESH_MAX 3 +#define LM3533_ALS_ZONE_MAX 4 + +#define LM3533_REG_ALS_RESISTOR_SELECT 0x30 +#define LM3533_REG_ALS_CONF 0x31 +#define LM3533_REG_ALS_ZONE_INFO 0x34 +#define LM3533_REG_ALS_READ_ADC_RAW 0x37 +#define LM3533_REG_ALS_READ_ADC_AVERAGE 0x38 +#define LM3533_REG_ALS_BOUNDARY_BASE 0x50 +#define LM3533_REG_ALS_TARGET_BASE 0x60 + +#define LM3533_ALS_ENABLE_MASK 0x01 +#define LM3533_ALS_INPUT_MODE_MASK 0x02 +#define LM3533_ALS_INT_ENABLE_MASK 0x01 + +#define LM3533_ALS_ZONE_SHIFT 2 +#define LM3533_ALS_ZONE_MASK 0x1c + +#define LM3533_ALS_FLAG_INT_ENABLED 1 + + +struct lm3533_als { + struct lm3533 *lm3533; + struct platform_device *pdev; + + unsigned long flags; + int irq; + + atomic_t zone; + struct mutex thresh_mutex; +}; + + +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average, + int *adc) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + u8 val; + int ret; + + if (average) + reg = LM3533_REG_ALS_READ_ADC_AVERAGE; + else + reg = LM3533_REG_ALS_READ_ADC_RAW; + + ret = lm3533_read(als->lm3533, reg, &val); + if (ret) { + dev_err(&indio_dev->dev, "failed to read adc\n"); + return ret; + } + + *adc = val; + + return 0; +} + +static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 val; + int ret; + + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val); + if (ret) { + dev_err(&indio_dev->dev, "failed to read zone\n"); + return ret; + } + + val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT; + *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX); + + return 0; +} + +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone) +{ + struct lm3533_als *als = iio_priv(indio_dev); + int ret; + + if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) { + *zone = atomic_read(&als->zone); + } else { + ret = _lm3533_als_get_zone(indio_dev, zone); + if (ret) + return ret; + } + + return 0; +} + +/* + * channel output channel 0..2 + * zone zone 0..4 + */ +static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone) +{ + return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone; +} + +static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel, + unsigned zone, u8 *val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + int ret; + + if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX) + return -EINVAL; + + if (zone > LM3533_ALS_ZONE_MAX) + return -EINVAL; + + reg = lm3533_als_get_target_reg(channel, zone); + ret = lm3533_read(als->lm3533, reg, val); + if (ret) + dev_err(&indio_dev->dev, "failed to get target current\n"); + + return ret; +} + +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel, + unsigned zone, u8 val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + int ret; + + if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX) + return -EINVAL; + + if (zone > LM3533_ALS_ZONE_MAX) + return -EINVAL; + + reg = lm3533_als_get_target_reg(channel, zone); + ret = lm3533_write(als->lm3533, reg, val); + if (ret) + dev_err(&indio_dev->dev, "failed to set target current\n"); + + return ret; +} + +static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel, + int *val) +{ + u8 zone; + u8 target; + int ret; + + ret = lm3533_als_get_zone(indio_dev, &zone); + if (ret) + return ret; + + ret = lm3533_als_get_target(indio_dev, channel, zone, &target); + if (ret) + return ret; + + *val = target; + + return 0; +} + +static int lm3533_als_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + + switch (mask) { + case 0: + switch (chan->type) { + case IIO_LIGHT: + ret = lm3533_als_get_adc(indio_dev, false, val); + break; + case IIO_CURRENT: + ret = lm3533_als_get_current(indio_dev, chan->channel, + val); + break; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_AVERAGE_RAW: + ret = lm3533_als_get_adc(indio_dev, true, val); + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + return IIO_VAL_INT; +} + +#define CHANNEL_CURRENT(_channel) \ + { \ + .type = IIO_CURRENT, \ + .channel = _channel, \ + .indexed = true, \ + .output = true, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + } + +static const struct iio_chan_spec lm3533_als_channels[] = { + { + .type = IIO_LIGHT, + .channel = 0, + .indexed = true, + .info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) | + BIT(IIO_CHAN_INFO_RAW), + }, + CHANNEL_CURRENT(0), + CHANNEL_CURRENT(1), + CHANNEL_CURRENT(2), +}; + +static irqreturn_t lm3533_als_isr(int irq, void *dev_id) +{ + + struct iio_dev *indio_dev = dev_id; + struct lm3533_als *als = iio_priv(indio_dev); + u8 zone; + int ret; + + /* Clear interrupt by reading the ALS zone register. */ + ret = _lm3533_als_get_zone(indio_dev, &zone); + if (ret) + goto out; + + atomic_set(&als->zone, zone); + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns()); +out: + return IRQ_HANDLED; +} + +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 mask = LM3533_ALS_INT_ENABLE_MASK; + u8 val; + int ret; + + if (enable) + val = mask; + else + val = 0; + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask); + if (ret) { + dev_err(&indio_dev->dev, "failed to set int mode %d\n", + enable); + return ret; + } + + return 0; +} + +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 mask = LM3533_ALS_INT_ENABLE_MASK; + u8 val; + int ret; + + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val); + if (ret) { + dev_err(&indio_dev->dev, "failed to get int mode\n"); + return ret; + } + + *enable = !!(val & mask); + + return 0; +} + +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising) +{ + u8 offset = !raising; + + return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset; +} + +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr, + bool raising, u8 *val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 reg; + int ret; + + if (nr > LM3533_ALS_THRESH_MAX) + return -EINVAL; + + reg = lm3533_als_get_threshold_reg(nr, raising); + ret = lm3533_read(als->lm3533, reg, val); + if (ret) + dev_err(&indio_dev->dev, "failed to get threshold\n"); + + return ret; +} + +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr, + bool raising, u8 val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 val2; + u8 reg, reg2; + int ret; + + if (nr > LM3533_ALS_THRESH_MAX) + return -EINVAL; + + reg = lm3533_als_get_threshold_reg(nr, raising); + reg2 = lm3533_als_get_threshold_reg(nr, !raising); + + mutex_lock(&als->thresh_mutex); + ret = lm3533_read(als->lm3533, reg2, &val2); + if (ret) { + dev_err(&indio_dev->dev, "failed to get threshold\n"); + goto out; + } + /* + * This device does not allow negative hysteresis (in fact, it uses + * whichever value is smaller as the lower bound) so we need to make + * sure that thresh_falling <= thresh_raising. + */ + if ((raising && (val < val2)) || (!raising && (val > val2))) { + ret = -EINVAL; + goto out; + } + + ret = lm3533_write(als->lm3533, reg, val); + if (ret) { + dev_err(&indio_dev->dev, "failed to set threshold\n"); + goto out; + } +out: + mutex_unlock(&als->thresh_mutex); + + return ret; +} + +static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr, + u8 *val) +{ + struct lm3533_als *als = iio_priv(indio_dev); + u8 falling; + u8 raising; + int ret; + + if (nr > LM3533_ALS_THRESH_MAX) + return -EINVAL; + + mutex_lock(&als->thresh_mutex); + ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling); + if (ret) + goto out; + ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising); + if (ret) + goto out; + + *val = raising - falling; +out: + mutex_unlock(&als->thresh_mutex); + + return ret; +} + +static ssize_t show_thresh_either_en(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als *als = iio_priv(indio_dev); + int enable; + int ret; + + if (als->irq) { + ret = lm3533_als_get_int_mode(indio_dev, &enable); + if (ret) + return ret; + } else { + enable = 0; + } + + return scnprintf(buf, PAGE_SIZE, "%u\n", enable); +} + +static ssize_t store_thresh_either_en(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als *als = iio_priv(indio_dev); + unsigned long enable; + bool int_enabled; + u8 zone; + int ret; + + if (!als->irq) + return -EBUSY; + + if (kstrtoul(buf, 0, &enable)) + return -EINVAL; + + int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + + if (enable && !int_enabled) { + ret = lm3533_als_get_zone(indio_dev, &zone); + if (ret) + return ret; + + atomic_set(&als->zone, zone); + + set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + } + + ret = lm3533_als_set_int_mode(indio_dev, enable); + if (ret) { + if (!int_enabled) + clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + + return ret; + } + + if (!enable) + clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags); + + return len; +} + +static ssize_t show_zone(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + u8 zone; + int ret; + + ret = lm3533_als_get_zone(indio_dev, &zone); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%u\n", zone); +} + +enum lm3533_als_attribute_type { + LM3533_ATTR_TYPE_HYSTERESIS, + LM3533_ATTR_TYPE_TARGET, + LM3533_ATTR_TYPE_THRESH_FALLING, + LM3533_ATTR_TYPE_THRESH_RAISING, +}; + +struct lm3533_als_attribute { + struct device_attribute dev_attr; + enum lm3533_als_attribute_type type; + u8 val1; + u8 val2; +}; + +static inline struct lm3533_als_attribute * +to_lm3533_als_attr(struct device_attribute *attr) +{ + return container_of(attr, struct lm3533_als_attribute, dev_attr); +} + +static ssize_t show_als_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr); + u8 val; + int ret; + + switch (als_attr->type) { + case LM3533_ATTR_TYPE_HYSTERESIS: + ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1, + &val); + break; + case LM3533_ATTR_TYPE_TARGET: + ret = lm3533_als_get_target(indio_dev, als_attr->val1, + als_attr->val2, &val); + break; + case LM3533_ATTR_TYPE_THRESH_FALLING: + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1, + false, &val); + break; + case LM3533_ATTR_TYPE_THRESH_RAISING: + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1, + true, &val); + break; + default: + ret = -ENXIO; + } + + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t store_als_attr(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr); + u8 val; + int ret; + + if (kstrtou8(buf, 0, &val)) + return -EINVAL; + + switch (als_attr->type) { + case LM3533_ATTR_TYPE_TARGET: + ret = lm3533_als_set_target(indio_dev, als_attr->val1, + als_attr->val2, val); + break; + case LM3533_ATTR_TYPE_THRESH_FALLING: + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1, + false, val); + break; + case LM3533_ATTR_TYPE_THRESH_RAISING: + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1, + true, val); + break; + default: + ret = -ENXIO; + } + + if (ret) + return ret; + + return len; +} + +#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \ + { .dev_attr = __ATTR(_name, _mode, _show, _store), \ + .type = _type, \ + .val1 = _val1, \ + .val2 = _val2 } + +#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \ + struct lm3533_als_attribute lm3533_als_attr_##_name = \ + ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) + +#define ALS_TARGET_ATTR_RW(_channel, _zone) \ + LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw, \ + S_IRUGO | S_IWUSR, \ + show_als_attr, store_als_attr, \ + LM3533_ATTR_TYPE_TARGET, _channel, _zone) +/* + * ALS output current values (ALS mapper targets) + * + * out_current[0-2]_current[0-4]_raw 0-255 + */ +static ALS_TARGET_ATTR_RW(0, 0); +static ALS_TARGET_ATTR_RW(0, 1); +static ALS_TARGET_ATTR_RW(0, 2); +static ALS_TARGET_ATTR_RW(0, 3); +static ALS_TARGET_ATTR_RW(0, 4); + +static ALS_TARGET_ATTR_RW(1, 0); +static ALS_TARGET_ATTR_RW(1, 1); +static ALS_TARGET_ATTR_RW(1, 2); +static ALS_TARGET_ATTR_RW(1, 3); +static ALS_TARGET_ATTR_RW(1, 4); + +static ALS_TARGET_ATTR_RW(2, 0); +static ALS_TARGET_ATTR_RW(2, 1); +static ALS_TARGET_ATTR_RW(2, 2); +static ALS_TARGET_ATTR_RW(2, 3); +static ALS_TARGET_ATTR_RW(2, 4); + +#define ALS_THRESH_FALLING_ATTR_RW(_nr) \ + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value, \ + S_IRUGO | S_IWUSR, \ + show_als_attr, store_als_attr, \ + LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0) + +#define ALS_THRESH_RAISING_ATTR_RW(_nr) \ + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value, \ + S_IRUGO | S_IWUSR, \ + show_als_attr, store_als_attr, \ + LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0) +/* + * ALS Zone thresholds (boundaries) + * + * in_illuminance0_thresh[0-3]_falling_value 0-255 + * in_illuminance0_thresh[0-3]_raising_value 0-255 + */ +static ALS_THRESH_FALLING_ATTR_RW(0); +static ALS_THRESH_FALLING_ATTR_RW(1); +static ALS_THRESH_FALLING_ATTR_RW(2); +static ALS_THRESH_FALLING_ATTR_RW(3); + +static ALS_THRESH_RAISING_ATTR_RW(0); +static ALS_THRESH_RAISING_ATTR_RW(1); +static ALS_THRESH_RAISING_ATTR_RW(2); +static ALS_THRESH_RAISING_ATTR_RW(3); + +#define ALS_HYSTERESIS_ATTR_RO(_nr) \ + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis, \ + S_IRUGO, show_als_attr, NULL, \ + LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0) +/* + * ALS Zone threshold hysteresis + * + * threshY_hysteresis = threshY_raising - threshY_falling + * + * in_illuminance0_thresh[0-3]_hysteresis 0-255 + * in_illuminance0_thresh[0-3]_hysteresis 0-255 + */ +static ALS_HYSTERESIS_ATTR_RO(0); +static ALS_HYSTERESIS_ATTR_RO(1); +static ALS_HYSTERESIS_ATTR_RO(2); +static ALS_HYSTERESIS_ATTR_RO(3); + +#define ILLUMINANCE_ATTR_RO(_name) \ + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL) +#define ILLUMINANCE_ATTR_RW(_name) \ + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR, \ + show_##_name, store_##_name) +/* + * ALS Zone threshold-event enable + * + * in_illuminance0_thresh_either_en 0,1 + */ +static ILLUMINANCE_ATTR_RW(thresh_either_en); + +/* + * ALS Current Zone + * + * in_illuminance0_zone 0-4 + */ +static ILLUMINANCE_ATTR_RO(zone); + +static struct attribute *lm3533_als_event_attributes[] = { + &dev_attr_in_illuminance0_thresh_either_en.attr, + &lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr, + &lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr, + NULL +}; + +static struct attribute_group lm3533_als_event_attribute_group = { + .attrs = lm3533_als_event_attributes +}; + +static struct attribute *lm3533_als_attributes[] = { + &dev_attr_in_illuminance0_zone.attr, + &lm3533_als_attr_out_current0_current0_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current1_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current2_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current3_raw.dev_attr.attr, + &lm3533_als_attr_out_current0_current4_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current0_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current1_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current2_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current3_raw.dev_attr.attr, + &lm3533_als_attr_out_current1_current4_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current0_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current1_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current2_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current3_raw.dev_attr.attr, + &lm3533_als_attr_out_current2_current4_raw.dev_attr.attr, + NULL +}; + +static struct attribute_group lm3533_als_attribute_group = { + .attrs = lm3533_als_attributes +}; + +static int lm3533_als_set_input_mode(struct lm3533_als *als, bool pwm_mode) +{ + u8 mask = LM3533_ALS_INPUT_MODE_MASK; + u8 val; + int ret; + + if (pwm_mode) + val = mask; /* pwm input */ + else + val = 0; /* analog input */ + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask); + if (ret) { + dev_err(&als->pdev->dev, "failed to set input mode %d\n", + pwm_mode); + return ret; + } + + return 0; +} + +static int lm3533_als_set_resistor(struct lm3533_als *als, u8 val) +{ + int ret; + + if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX) + return -EINVAL; + + ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val); + if (ret) { + dev_err(&als->pdev->dev, "failed to set resistor\n"); + return ret; + } + + return 0; +} + +static int lm3533_als_setup(struct lm3533_als *als, + struct lm3533_als_platform_data *pdata) +{ + int ret; + + ret = lm3533_als_set_input_mode(als, pdata->pwm_mode); + if (ret) + return ret; + + /* ALS input is always high impedance in PWM-mode. */ + if (!pdata->pwm_mode) { + ret = lm3533_als_set_resistor(als, pdata->r_select); + if (ret) + return ret; + } + + return 0; +} + +static int lm3533_als_setup_irq(struct lm3533_als *als, void *dev) +{ + u8 mask = LM3533_ALS_INT_ENABLE_MASK; + int ret; + + /* Make sure interrupts are disabled. */ + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask); + if (ret) { + dev_err(&als->pdev->dev, "failed to disable interrupts\n"); + return ret; + } + + ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + dev_name(&als->pdev->dev), dev); + if (ret) { + dev_err(&als->pdev->dev, "failed to request irq %d\n", + als->irq); + return ret; + } + + return 0; +} + +static int lm3533_als_enable(struct lm3533_als *als) +{ + u8 mask = LM3533_ALS_ENABLE_MASK; + int ret; + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask); + if (ret) + dev_err(&als->pdev->dev, "failed to enable ALS\n"); + + return ret; +} + +static int lm3533_als_disable(struct lm3533_als *als) +{ + u8 mask = LM3533_ALS_ENABLE_MASK; + int ret; + + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask); + if (ret) + dev_err(&als->pdev->dev, "failed to disable ALS\n"); + + return ret; +} + +static const struct iio_info lm3533_als_info = { + .attrs = &lm3533_als_attribute_group, + .event_attrs = &lm3533_als_event_attribute_group, + .driver_module = THIS_MODULE, + .read_raw = &lm3533_als_read_raw, +}; + +static int lm3533_als_probe(struct platform_device *pdev) +{ + struct lm3533 *lm3533; + struct lm3533_als_platform_data *pdata; + struct lm3533_als *als; + struct iio_dev *indio_dev; + int ret; + + lm3533 = dev_get_drvdata(pdev->dev.parent); + if (!lm3533) + return -EINVAL; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*als)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &lm3533_als_info; + indio_dev->channels = lm3533_als_channels; + indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels); + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = pdev->dev.parent; + indio_dev->modes = INDIO_DIRECT_MODE; + + als = iio_priv(indio_dev); + als->lm3533 = lm3533; + als->pdev = pdev; + als->irq = lm3533->irq; + atomic_set(&als->zone, 0); + mutex_init(&als->thresh_mutex); + + platform_set_drvdata(pdev, indio_dev); + + if (als->irq) { + ret = lm3533_als_setup_irq(als, indio_dev); + if (ret) + return ret; + } + + ret = lm3533_als_setup(als, pdata); + if (ret) + goto err_free_irq; + + ret = lm3533_als_enable(als); + if (ret) + goto err_free_irq; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "failed to register ALS\n"); + goto err_disable; + } + + return 0; + +err_disable: + lm3533_als_disable(als); +err_free_irq: + if (als->irq) + free_irq(als->irq, indio_dev); + + return ret; +} + +static int lm3533_als_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct lm3533_als *als = iio_priv(indio_dev); + + lm3533_als_set_int_mode(indio_dev, false); + iio_device_unregister(indio_dev); + lm3533_als_disable(als); + if (als->irq) + free_irq(als->irq, indio_dev); + + return 0; +} + +static struct platform_driver lm3533_als_driver = { + .driver = { + .name = "lm3533-als", + }, + .probe = lm3533_als_probe, + .remove = lm3533_als_remove, +}; +module_platform_driver(lm3533_als_driver); + +MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>"); +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lm3533-als"); diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c new file mode 100644 index 000000000..78b87839c --- /dev/null +++ b/drivers/iio/light/ltr501.c @@ -0,0 +1,447 @@ +/* + * ltr501.c - Support for Lite-On LTR501 ambient light and proximity sensor + * + * Copyright 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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 0x23 + * + * TODO: interrupt, threshold, measurement rate, IR LED characteristics + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define LTR501_DRV_NAME "ltr501" + +#define LTR501_ALS_CONTR 0x80 /* ALS operation mode, SW reset */ +#define LTR501_PS_CONTR 0x81 /* PS operation mode */ +#define LTR501_PART_ID 0x86 +#define LTR501_MANUFAC_ID 0x87 +#define LTR501_ALS_DATA1 0x88 /* 16-bit, little endian */ +#define LTR501_ALS_DATA0 0x8a /* 16-bit, little endian */ +#define LTR501_ALS_PS_STATUS 0x8c +#define LTR501_PS_DATA 0x8d /* 16-bit, little endian */ + +#define LTR501_ALS_CONTR_SW_RESET BIT(2) +#define LTR501_CONTR_PS_GAIN_MASK (BIT(3) | BIT(2)) +#define LTR501_CONTR_PS_GAIN_SHIFT 2 +#define LTR501_CONTR_ALS_GAIN_MASK BIT(3) +#define LTR501_CONTR_ACTIVE BIT(1) + +#define LTR501_STATUS_ALS_RDY BIT(2) +#define LTR501_STATUS_PS_RDY BIT(0) + +#define LTR501_PS_DATA_MASK 0x7ff + +struct ltr501_data { + struct i2c_client *client; + struct mutex lock_als, lock_ps; + u8 als_contr, ps_contr; +}; + +static int ltr501_drdy(struct ltr501_data *data, u8 drdy_mask) +{ + int tries = 100; + int ret; + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, + LTR501_ALS_PS_STATUS); + if (ret < 0) + return ret; + if ((ret & drdy_mask) == drdy_mask) + return 0; + msleep(25); + } + + dev_err(&data->client->dev, "ltr501_drdy() failed, data not ready\n"); + return -EIO; +} + +static int ltr501_read_als(struct ltr501_data *data, __le16 buf[2]) +{ + int ret = ltr501_drdy(data, LTR501_STATUS_ALS_RDY); + if (ret < 0) + return ret; + /* always read both ALS channels in given order */ + return i2c_smbus_read_i2c_block_data(data->client, + LTR501_ALS_DATA1, 2 * sizeof(__le16), (u8 *) buf); +} + +static int ltr501_read_ps(struct ltr501_data *data) +{ + int ret = ltr501_drdy(data, LTR501_STATUS_PS_RDY); + if (ret < 0) + return ret; + return i2c_smbus_read_word_data(data->client, LTR501_PS_DATA); +} + +#define LTR501_INTENSITY_CHANNEL(_idx, _addr, _mod, _shared) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .address = (_addr), \ + .channel2 = (_mod), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = (_shared), \ + .scan_index = (_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +static const struct iio_chan_spec ltr501_channels[] = { + LTR501_INTENSITY_CHANNEL(0, LTR501_ALS_DATA0, IIO_MOD_LIGHT_BOTH, 0), + LTR501_INTENSITY_CHANNEL(1, LTR501_ALS_DATA1, IIO_MOD_LIGHT_IR, + BIT(IIO_CHAN_INFO_SCALE)), + { + .type = IIO_PROXIMITY, + .address = LTR501_PS_DATA, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 2, + .scan_type = { + .sign = 'u', + .realbits = 11, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const int ltr501_ps_gain[4][2] = { + {1, 0}, {0, 250000}, {0, 125000}, {0, 62500} +}; + +static int ltr501_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ltr501_data *data = iio_priv(indio_dev); + __le16 buf[2]; + int ret, i; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + switch (chan->type) { + case IIO_INTENSITY: + mutex_lock(&data->lock_als); + ret = ltr501_read_als(data, buf); + mutex_unlock(&data->lock_als); + if (ret < 0) + return ret; + *val = le16_to_cpu(chan->address == LTR501_ALS_DATA1 ? + buf[0] : buf[1]); + return IIO_VAL_INT; + case IIO_PROXIMITY: + mutex_lock(&data->lock_ps); + ret = ltr501_read_ps(data); + mutex_unlock(&data->lock_ps); + if (ret < 0) + return ret; + *val = ret & LTR501_PS_DATA_MASK; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + if (data->als_contr & LTR501_CONTR_ALS_GAIN_MASK) { + *val = 0; + *val2 = 5000; + return IIO_VAL_INT_PLUS_MICRO; + } else { + *val = 1; + *val2 = 0; + return IIO_VAL_INT; + } + case IIO_PROXIMITY: + i = (data->ps_contr & LTR501_CONTR_PS_GAIN_MASK) >> + LTR501_CONTR_PS_GAIN_SHIFT; + *val = ltr501_ps_gain[i][0]; + *val2 = ltr501_ps_gain[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + } + return -EINVAL; +} + +static int ltr501_get_ps_gain_index(int val, int val2) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ltr501_ps_gain); i++) + if (val == ltr501_ps_gain[i][0] && val2 == ltr501_ps_gain[i][1]) + return i; + + return -1; +} + +static int ltr501_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ltr501_data *data = iio_priv(indio_dev); + int i; + + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_INTENSITY: + if (val == 0 && val2 == 5000) + data->als_contr |= LTR501_CONTR_ALS_GAIN_MASK; + else if (val == 1 && val2 == 0) + data->als_contr &= ~LTR501_CONTR_ALS_GAIN_MASK; + else + return -EINVAL; + return i2c_smbus_write_byte_data(data->client, + LTR501_ALS_CONTR, data->als_contr); + case IIO_PROXIMITY: + i = ltr501_get_ps_gain_index(val, val2); + if (i < 0) + return -EINVAL; + data->ps_contr &= ~LTR501_CONTR_PS_GAIN_MASK; + data->ps_contr |= i << LTR501_CONTR_PS_GAIN_SHIFT; + return i2c_smbus_write_byte_data(data->client, + LTR501_PS_CONTR, data->ps_contr); + default: + return -EINVAL; + } + } + return -EINVAL; +} + +static IIO_CONST_ATTR(in_proximity_scale_available, "1 0.25 0.125 0.0625"); +static IIO_CONST_ATTR(in_intensity_scale_available, "1 0.005"); + +static struct attribute *ltr501_attributes[] = { + &iio_const_attr_in_proximity_scale_available.dev_attr.attr, + &iio_const_attr_in_intensity_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ltr501_attribute_group = { + .attrs = ltr501_attributes, +}; + +static const struct iio_info ltr501_info = { + .read_raw = ltr501_read_raw, + .write_raw = ltr501_write_raw, + .attrs = <r501_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ltr501_write_contr(struct i2c_client *client, u8 als_val, u8 ps_val) +{ + int ret = i2c_smbus_write_byte_data(client, LTR501_ALS_CONTR, als_val); + if (ret < 0) + return ret; + + return i2c_smbus_write_byte_data(client, LTR501_PS_CONTR, ps_val); +} + +static irqreturn_t ltr501_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ltr501_data *data = iio_priv(indio_dev); + u16 buf[8]; + __le16 als_buf[2]; + u8 mask = 0; + int j = 0; + int ret; + + memset(buf, 0, sizeof(buf)); + + /* figure out which data needs to be ready */ + if (test_bit(0, indio_dev->active_scan_mask) || + test_bit(1, indio_dev->active_scan_mask)) + mask |= LTR501_STATUS_ALS_RDY; + if (test_bit(2, indio_dev->active_scan_mask)) + mask |= LTR501_STATUS_PS_RDY; + + ret = ltr501_drdy(data, mask); + if (ret < 0) + goto done; + + if (mask & LTR501_STATUS_ALS_RDY) { + ret = i2c_smbus_read_i2c_block_data(data->client, + LTR501_ALS_DATA1, sizeof(als_buf), (u8 *) als_buf); + if (ret < 0) + return ret; + if (test_bit(0, indio_dev->active_scan_mask)) + buf[j++] = le16_to_cpu(als_buf[1]); + if (test_bit(1, indio_dev->active_scan_mask)) + buf[j++] = le16_to_cpu(als_buf[0]); + } + + if (mask & LTR501_STATUS_PS_RDY) { + ret = i2c_smbus_read_word_data(data->client, LTR501_PS_DATA); + if (ret < 0) + goto done; + buf[j++] = ret & LTR501_PS_DATA_MASK; + } + + iio_push_to_buffers_with_timestamp(indio_dev, buf, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ltr501_init(struct ltr501_data *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(data->client, LTR501_ALS_CONTR); + if (ret < 0) + return ret; + data->als_contr = ret | LTR501_CONTR_ACTIVE; + + ret = i2c_smbus_read_byte_data(data->client, LTR501_PS_CONTR); + if (ret < 0) + return ret; + data->ps_contr = ret | LTR501_CONTR_ACTIVE; + + return ltr501_write_contr(data->client, data->als_contr, + data->ps_contr); +} + +static int ltr501_powerdown(struct ltr501_data *data) +{ + return ltr501_write_contr(data->client, + data->als_contr & ~LTR501_CONTR_ACTIVE, + data->ps_contr & ~LTR501_CONTR_ACTIVE); +} + +static int ltr501_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ltr501_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock_als); + mutex_init(&data->lock_ps); + + ret = i2c_smbus_read_byte_data(data->client, LTR501_PART_ID); + if (ret < 0) + return ret; + if ((ret >> 4) != 0x8) + return -ENODEV; + + indio_dev->dev.parent = &client->dev; + indio_dev->info = <r501_info; + indio_dev->channels = ltr501_channels; + indio_dev->num_channels = ARRAY_SIZE(ltr501_channels); + indio_dev->name = LTR501_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ltr501_init(data); + if (ret < 0) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + ltr501_trigger_handler, NULL); + if (ret) + goto powerdown_on_error; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_buffer; + + return 0; + +error_unreg_buffer: + iio_triggered_buffer_cleanup(indio_dev); +powerdown_on_error: + ltr501_powerdown(data); + return ret; +} + +static int ltr501_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + ltr501_powerdown(iio_priv(indio_dev)); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int ltr501_suspend(struct device *dev) +{ + struct ltr501_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return ltr501_powerdown(data); +} + +static int ltr501_resume(struct device *dev) +{ + struct ltr501_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return ltr501_write_contr(data->client, data->als_contr, + data->ps_contr); +} +#endif + +static SIMPLE_DEV_PM_OPS(ltr501_pm_ops, ltr501_suspend, ltr501_resume); + +static const struct i2c_device_id ltr501_id[] = { + { "ltr501", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltr501_id); + +static struct i2c_driver ltr501_driver = { + .driver = { + .name = LTR501_DRV_NAME, + .pm = <r501_pm_ops, + .owner = THIS_MODULE, + }, + .probe = ltr501_probe, + .remove = ltr501_remove, + .id_table = ltr501_id, +}; + +module_i2c_driver(ltr501_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Lite-On LTR501 ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c new file mode 100644 index 000000000..f8b1df018 --- /dev/null +++ b/drivers/iio/light/tcs3414.c @@ -0,0 +1,405 @@ +/* + * tcs3414.c - Support for TAOS TCS3414 digital color sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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. + * + * Digital color sensor with 16-bit channels for red, green, blue, clear); + * 7-bit I2C slave address 0x39 (TCS3414) or 0x29, 0x49, 0x59 (TCS3413, + * TCS3415, TCS3416, resp.) + * + * TODO: sync, interrupt support, thresholds, prescaler + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/pm.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define TCS3414_DRV_NAME "tcs3414" + +#define TCS3414_COMMAND BIT(7) +#define TCS3414_COMMAND_WORD (TCS3414_COMMAND | BIT(5)) + +#define TCS3414_CONTROL (TCS3414_COMMAND | 0x00) +#define TCS3414_TIMING (TCS3414_COMMAND | 0x01) +#define TCS3414_ID (TCS3414_COMMAND | 0x04) +#define TCS3414_GAIN (TCS3414_COMMAND | 0x07) +#define TCS3414_DATA_GREEN (TCS3414_COMMAND_WORD | 0x10) +#define TCS3414_DATA_RED (TCS3414_COMMAND_WORD | 0x12) +#define TCS3414_DATA_BLUE (TCS3414_COMMAND_WORD | 0x14) +#define TCS3414_DATA_CLEAR (TCS3414_COMMAND_WORD | 0x16) + +#define TCS3414_CONTROL_ADC_VALID BIT(4) +#define TCS3414_CONTROL_ADC_EN BIT(1) +#define TCS3414_CONTROL_POWER BIT(0) + +#define TCS3414_INTEG_MASK GENMASK(1, 0) +#define TCS3414_INTEG_12MS 0x0 +#define TCS3414_INTEG_100MS 0x1 +#define TCS3414_INTEG_400MS 0x2 + +#define TCS3414_GAIN_MASK GENMASK(5, 4) +#define TCS3414_GAIN_SHIFT 4 + +struct tcs3414_data { + struct i2c_client *client; + struct mutex lock; + u8 control; + u8 gain; + u8 timing; + u16 buffer[8]; /* 4x 16-bit + 8 bytes timestamp */ +}; + +#define TCS3414_CHANNEL(_color, _si, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .address = _addr, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +/* scale factors: 1/gain */ +static const int tcs3414_scales[][2] = { + {1, 0}, {0, 250000}, {0, 62500}, {0, 15625} +}; + +/* integration time in ms */ +static const int tcs3414_times[] = { 12, 100, 400 }; + +static const struct iio_chan_spec tcs3414_channels[] = { + TCS3414_CHANNEL(GREEN, 0, TCS3414_DATA_GREEN), + TCS3414_CHANNEL(RED, 1, TCS3414_DATA_RED), + TCS3414_CHANNEL(BLUE, 2, TCS3414_DATA_BLUE), + TCS3414_CHANNEL(CLEAR, 3, TCS3414_DATA_CLEAR), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int tcs3414_req_data(struct tcs3414_data *data) +{ + int tries = 25; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control | TCS3414_CONTROL_ADC_EN); + if (ret < 0) + return ret; + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, TCS3414_CONTROL); + if (ret < 0) + return ret; + if (ret & TCS3414_CONTROL_ADC_VALID) + break; + msleep(20); + } + + ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); + if (ret < 0) + return ret; + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int tcs3414_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + mutex_lock(&data->lock); + ret = tcs3414_req_data(data); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + ret = i2c_smbus_read_word_data(data->client, chan->address); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + i = (data->gain & TCS3414_GAIN_MASK) >> TCS3414_GAIN_SHIFT; + *val = tcs3414_scales[i][0]; + *val2 = tcs3414_scales[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = tcs3414_times[data->timing & TCS3414_INTEG_MASK] * 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int tcs3414_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(tcs3414_scales); i++) { + if (val == tcs3414_scales[i][0] && + val2 == tcs3414_scales[i][1]) { + data->gain &= ~TCS3414_GAIN_MASK; + data->gain |= i << TCS3414_GAIN_SHIFT; + return i2c_smbus_write_byte_data( + data->client, TCS3414_GAIN, + data->gain); + } + } + return -EINVAL; + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(tcs3414_times); i++) { + if (val2 == tcs3414_times[i] * 1000) { + data->timing &= ~TCS3414_INTEG_MASK; + data->timing |= i; + return i2c_smbus_write_byte_data( + data->client, TCS3414_TIMING, + data->timing); + } + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static irqreturn_t tcs3414_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tcs3414_data *data = iio_priv(indio_dev); + int i, j = 0; + + for_each_set_bit(i, indio_dev->active_scan_mask, + indio_dev->masklength) { + int ret = i2c_smbus_read_word_data(data->client, + TCS3414_DATA_GREEN + 2*i); + if (ret < 0) + goto done; + + data->buffer[j++] = ret; + } + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static IIO_CONST_ATTR(scale_available, "1 0.25 0.0625 0.015625"); +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.012 0.1 0.4"); + +static struct attribute *tcs3414_attributes[] = { + &iio_const_attr_scale_available.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tcs3414_attribute_group = { + .attrs = tcs3414_attributes, +}; + +static const struct iio_info tcs3414_info = { + .read_raw = tcs3414_read_raw, + .write_raw = tcs3414_write_raw, + .attrs = &tcs3414_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int tcs3414_buffer_preenable(struct iio_dev *indio_dev) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + + data->control |= TCS3414_CONTROL_ADC_EN; + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); +} + +static int tcs3414_buffer_predisable(struct iio_dev *indio_dev) +{ + struct tcs3414_data *data = iio_priv(indio_dev); + int ret; + + ret = iio_triggered_buffer_predisable(indio_dev); + if (ret < 0) + return ret; + + data->control &= ~TCS3414_CONTROL_ADC_EN; + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); +} + +static const struct iio_buffer_setup_ops tcs3414_buffer_setup_ops = { + .preenable = tcs3414_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = tcs3414_buffer_predisable, +}; + +static int tcs3414_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tcs3414_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &tcs3414_info; + indio_dev->name = TCS3414_DRV_NAME; + indio_dev->channels = tcs3414_channels; + indio_dev->num_channels = ARRAY_SIZE(tcs3414_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, TCS3414_ID); + if (ret < 0) + return ret; + + switch (ret & 0xf0) { + case 0x00: + dev_info(&client->dev, "TCS3404 found\n"); + break; + case 0x10: + dev_info(&client->dev, "TCS3413/14/15/16 found\n"); + break; + default: + return -ENODEV; + } + + data->control = TCS3414_CONTROL_POWER; + ret = i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); + if (ret < 0) + return ret; + + data->timing = TCS3414_INTEG_12MS; /* free running */ + ret = i2c_smbus_write_byte_data(data->client, TCS3414_TIMING, + data->timing); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3414_GAIN); + if (ret < 0) + return ret; + data->gain = ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + tcs3414_trigger_handler, &tcs3414_buffer_setup_ops); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +static int tcs3414_powerdown(struct tcs3414_data *data) +{ + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control & ~(TCS3414_CONTROL_POWER | + TCS3414_CONTROL_ADC_EN)); +} + +static int tcs3414_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + tcs3414_powerdown(iio_priv(indio_dev)); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tcs3414_suspend(struct device *dev) +{ + struct tcs3414_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return tcs3414_powerdown(data); +} + +static int tcs3414_resume(struct device *dev) +{ + struct tcs3414_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return i2c_smbus_write_byte_data(data->client, TCS3414_CONTROL, + data->control); +} +#endif + +static SIMPLE_DEV_PM_OPS(tcs3414_pm_ops, tcs3414_suspend, tcs3414_resume); + +static const struct i2c_device_id tcs3414_id[] = { + { "tcs3414", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcs3414_id); + +static struct i2c_driver tcs3414_driver = { + .driver = { + .name = TCS3414_DRV_NAME, + .pm = &tcs3414_pm_ops, + .owner = THIS_MODULE, + }, + .probe = tcs3414_probe, + .remove = tcs3414_remove, + .id_table = tcs3414_id, +}; +module_i2c_driver(tcs3414_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("TCS3414 digital color sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c new file mode 100644 index 000000000..752569985 --- /dev/null +++ b/drivers/iio/light/tcs3472.c @@ -0,0 +1,379 @@ +/* + * tcs3472.c - Support for TAOS TCS3472 color light-to-digital converter + * + * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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. + * + * Color light sensor with 16-bit channels for red, green, blue, clear); + * 7-bit I2C slave address 0x39 (TCS34721, TCS34723) or 0x29 (TCS34725, + * TCS34727) + * + * TODO: interrupt support, thresholds, wait time + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/pm.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> + +#define TCS3472_DRV_NAME "tcs3472" + +#define TCS3472_COMMAND BIT(7) +#define TCS3472_AUTO_INCR BIT(5) + +#define TCS3472_ENABLE (TCS3472_COMMAND | 0x00) +#define TCS3472_ATIME (TCS3472_COMMAND | 0x01) +#define TCS3472_WTIME (TCS3472_COMMAND | 0x03) +#define TCS3472_AILT (TCS3472_COMMAND | 0x04) +#define TCS3472_AIHT (TCS3472_COMMAND | 0x06) +#define TCS3472_PERS (TCS3472_COMMAND | 0x0c) +#define TCS3472_CONFIG (TCS3472_COMMAND | 0x0d) +#define TCS3472_CONTROL (TCS3472_COMMAND | 0x0f) +#define TCS3472_ID (TCS3472_COMMAND | 0x12) +#define TCS3472_STATUS (TCS3472_COMMAND | 0x13) +#define TCS3472_CDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x14) +#define TCS3472_RDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x16) +#define TCS3472_GDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x18) +#define TCS3472_BDATA (TCS3472_COMMAND | TCS3472_AUTO_INCR | 0x1a) + +#define TCS3472_STATUS_AVALID BIT(0) +#define TCS3472_ENABLE_AEN BIT(1) +#define TCS3472_ENABLE_PON BIT(0) +#define TCS3472_CONTROL_AGAIN_MASK (BIT(0) | BIT(1)) + +struct tcs3472_data { + struct i2c_client *client; + struct mutex lock; + u8 enable; + u8 control; + u8 atime; + u16 buffer[8]; /* 4 16-bit channels + 64-bit timestamp */ +}; + +#define TCS3472_CHANNEL(_color, _si, _addr) { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .channel2 = IIO_MOD_LIGHT_##_color, \ + .address = _addr, \ + .scan_index = _si, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ +} + +static const int tcs3472_agains[] = { 1, 4, 16, 60 }; + +static const struct iio_chan_spec tcs3472_channels[] = { + TCS3472_CHANNEL(CLEAR, 0, TCS3472_CDATA), + TCS3472_CHANNEL(RED, 1, TCS3472_RDATA), + TCS3472_CHANNEL(GREEN, 2, TCS3472_GDATA), + TCS3472_CHANNEL(BLUE, 3, TCS3472_BDATA), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int tcs3472_req_data(struct tcs3472_data *data) +{ + int tries = 50; + int ret; + + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, TCS3472_STATUS); + if (ret < 0) + return ret; + if (ret & TCS3472_STATUS_AVALID) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int tcs3472_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + mutex_lock(&data->lock); + ret = tcs3472_req_data(data); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + ret = i2c_smbus_read_word_data(data->client, chan->address); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + *val = tcs3472_agains[data->control & + TCS3472_CONTROL_AGAIN_MASK]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = (256 - data->atime) * 2400; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int tcs3472_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tcs3472_data *data = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (val2 != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(tcs3472_agains); i++) { + if (val == tcs3472_agains[i]) { + data->control &= ~TCS3472_CONTROL_AGAIN_MASK; + data->control |= i; + return i2c_smbus_write_byte_data( + data->client, TCS3472_CONTROL, + data->control); + } + } + return -EINVAL; + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + for (i = 0; i < 256; i++) { + if (val2 == (256 - i) * 2400) { + data->atime = i; + return i2c_smbus_write_word_data( + data->client, TCS3472_ATIME, + data->atime); + } + + } + return -EINVAL; + } + return -EINVAL; +} + +static irqreturn_t tcs3472_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct tcs3472_data *data = iio_priv(indio_dev); + int i, j = 0; + + int ret = tcs3472_req_data(data); + if (ret < 0) + goto done; + + for_each_set_bit(i, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = i2c_smbus_read_word_data(data->client, + TCS3472_CDATA + 2*i); + if (ret < 0) + goto done; + + data->buffer[j++] = ret; + } + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static ssize_t tcs3472_show_int_time_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + size_t len = 0; + int i; + + for (i = 1; i <= 256; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06d ", + 2400 * i); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_CONST_ATTR(calibscale_available, "1 4 16 60"); +static IIO_DEV_ATTR_INT_TIME_AVAIL(tcs3472_show_int_time_available); + +static struct attribute *tcs3472_attributes[] = { + &iio_const_attr_calibscale_available.dev_attr.attr, + &iio_dev_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tcs3472_attribute_group = { + .attrs = tcs3472_attributes, +}; + +static const struct iio_info tcs3472_info = { + .read_raw = tcs3472_read_raw, + .write_raw = tcs3472_write_raw, + .attrs = &tcs3472_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int tcs3472_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tcs3472_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &tcs3472_info; + indio_dev->name = TCS3472_DRV_NAME; + indio_dev->channels = tcs3472_channels; + indio_dev->num_channels = ARRAY_SIZE(tcs3472_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_ID); + if (ret < 0) + return ret; + + if (ret == 0x44) + dev_info(&client->dev, "TCS34721/34725 found\n"); + else if (ret == 0x4d) + dev_info(&client->dev, "TCS34723/34727 found\n"); + else + return -ENODEV; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_CONTROL); + if (ret < 0) + return ret; + data->control = ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_ATIME); + if (ret < 0) + return ret; + data->atime = ret; + + ret = i2c_smbus_read_byte_data(data->client, TCS3472_ENABLE); + if (ret < 0) + return ret; + + /* enable device */ + data->enable = ret | TCS3472_ENABLE_PON | TCS3472_ENABLE_AEN; + ret = i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + data->enable); + if (ret < 0) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + tcs3472_trigger_handler, NULL); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +static int tcs3472_powerdown(struct tcs3472_data *data) +{ + return i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + data->enable & ~(TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON)); +} + +static int tcs3472_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + tcs3472_powerdown(iio_priv(indio_dev)); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tcs3472_suspend(struct device *dev) +{ + struct tcs3472_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return tcs3472_powerdown(data); +} + +static int tcs3472_resume(struct device *dev) +{ + struct tcs3472_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return i2c_smbus_write_byte_data(data->client, TCS3472_ENABLE, + data->enable | (TCS3472_ENABLE_AEN | TCS3472_ENABLE_PON)); +} +#endif + +static SIMPLE_DEV_PM_OPS(tcs3472_pm_ops, tcs3472_suspend, tcs3472_resume); + +static const struct i2c_device_id tcs3472_id[] = { + { "tcs3472", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcs3472_id); + +static struct i2c_driver tcs3472_driver = { + .driver = { + .name = TCS3472_DRV_NAME, + .pm = &tcs3472_pm_ops, + .owner = THIS_MODULE, + }, + .probe = tcs3472_probe, + .remove = tcs3472_remove, + .id_table = tcs3472_id, +}; +module_i2c_driver(tcs3472_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("TCS3472 color light sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c new file mode 100644 index 000000000..94daa9fc1 --- /dev/null +++ b/drivers/iio/light/tsl2563.c @@ -0,0 +1,901 @@ +/* + * drivers/iio/light/tsl2563.c + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Timo O. Karjalainen <timo.o.karjalainen@nokia.com> + * Contact: Amit Kucheria <amit.kucheria@verdurent.com> + * + * Converted to IIO driver + * Amit Kucheria <amit.kucheria@verdurent.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/sched.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/err.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/platform_data/tsl2563.h> + +/* Use this many bits for fraction part. */ +#define ADC_FRAC_BITS 14 + +/* Given number of 1/10000's in ADC_FRAC_BITS precision. */ +#define FRAC10K(f) (((f) * (1L << (ADC_FRAC_BITS))) / (10000)) + +/* Bits used for fraction in calibration coefficients.*/ +#define CALIB_FRAC_BITS 10 +/* 0.5 in CALIB_FRAC_BITS precision */ +#define CALIB_FRAC_HALF (1 << (CALIB_FRAC_BITS - 1)) +/* Make a fraction from a number n that was multiplied with b. */ +#define CALIB_FRAC(n, b) (((n) << CALIB_FRAC_BITS) / (b)) +/* Decimal 10^(digits in sysfs presentation) */ +#define CALIB_BASE_SYSFS 1000 + +#define TSL2563_CMD 0x80 +#define TSL2563_CLEARINT 0x40 + +#define TSL2563_REG_CTRL 0x00 +#define TSL2563_REG_TIMING 0x01 +#define TSL2563_REG_LOWLOW 0x02 /* data0 low threshold, 2 bytes */ +#define TSL2563_REG_LOWHIGH 0x03 +#define TSL2563_REG_HIGHLOW 0x04 /* data0 high threshold, 2 bytes */ +#define TSL2563_REG_HIGHHIGH 0x05 +#define TSL2563_REG_INT 0x06 +#define TSL2563_REG_ID 0x0a +#define TSL2563_REG_DATA0LOW 0x0c /* broadband sensor value, 2 bytes */ +#define TSL2563_REG_DATA0HIGH 0x0d +#define TSL2563_REG_DATA1LOW 0x0e /* infrared sensor value, 2 bytes */ +#define TSL2563_REG_DATA1HIGH 0x0f + +#define TSL2563_CMD_POWER_ON 0x03 +#define TSL2563_CMD_POWER_OFF 0x00 +#define TSL2563_CTRL_POWER_MASK 0x03 + +#define TSL2563_TIMING_13MS 0x00 +#define TSL2563_TIMING_100MS 0x01 +#define TSL2563_TIMING_400MS 0x02 +#define TSL2563_TIMING_MASK 0x03 +#define TSL2563_TIMING_GAIN16 0x10 +#define TSL2563_TIMING_GAIN1 0x00 + +#define TSL2563_INT_DISBLED 0x00 +#define TSL2563_INT_LEVEL 0x10 +#define TSL2563_INT_PERSIST(n) ((n) & 0x0F) + +struct tsl2563_gainlevel_coeff { + u8 gaintime; + u16 min; + u16 max; +}; + +static const struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = { + { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16, + .min = 0, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1, + .min = 2048, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1, + .min = 4095, + .max = 37177, + }, { + .gaintime = TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1, + .min = 3000, + .max = 65535, + }, +}; + +struct tsl2563_chip { + struct mutex lock; + struct i2c_client *client; + struct delayed_work poweroff_work; + + /* Remember state for suspend and resume functions */ + bool suspended; + + struct tsl2563_gainlevel_coeff const *gainlevel; + + u16 low_thres; + u16 high_thres; + u8 intr; + bool int_enabled; + + /* Calibration coefficients */ + u32 calib0; + u32 calib1; + int cover_comp_gain; + + /* Cache current values, to be returned while suspended */ + u32 data0; + u32 data1; +}; + +static int tsl2563_set_power(struct tsl2563_chip *chip, int on) +{ + struct i2c_client *client = chip->client; + u8 cmd; + + cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF; + return i2c_smbus_write_byte_data(client, + TSL2563_CMD | TSL2563_REG_CTRL, cmd); +} + +/* + * Return value is 0 for off, 1 for on, or a negative error + * code if reading failed. + */ +static int tsl2563_get_power(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_CTRL); + if (ret < 0) + return ret; + + return (ret & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON; +} + +static int tsl2563_configure(struct tsl2563_chip *chip) +{ + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_HIGHLOW, + chip->high_thres & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_HIGHHIGH, + (chip->high_thres >> 8) & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_LOWLOW, + chip->low_thres & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_LOWHIGH, + (chip->low_thres >> 8) & 0xFF); +/* + * Interrupt register is automatically written anyway if it is relevant + * so is not here. + */ +error_ret: + return ret; +} + +static void tsl2563_poweroff_work(struct work_struct *work) +{ + struct tsl2563_chip *chip = + container_of(work, struct tsl2563_chip, poweroff_work.work); + tsl2563_set_power(chip, 0); +} + +static int tsl2563_detect(struct tsl2563_chip *chip) +{ + int ret; + + ret = tsl2563_set_power(chip, 1); + if (ret) + return ret; + + ret = tsl2563_get_power(chip); + if (ret < 0) + return ret; + + return ret ? 0 : -ENODEV; +} + +static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_ID); + if (ret < 0) + return ret; + + *id = ret; + + return 0; +} + +/* + * "Normalized" ADC value is one obtained with 400ms of integration time and + * 16x gain. This function returns the number of bits of shift needed to + * convert between normalized values and HW values obtained using given + * timing and gain settings. + */ +static int adc_shiftbits(u8 timing) +{ + int shift = 0; + + switch (timing & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + shift += 5; + break; + case TSL2563_TIMING_100MS: + shift += 2; + break; + case TSL2563_TIMING_400MS: + /* no-op */ + break; + } + + if (!(timing & TSL2563_TIMING_GAIN16)) + shift += 4; + + return shift; +} + +/* Convert a HW ADC value to normalized scale. */ +static u32 normalize_adc(u16 adc, u8 timing) +{ + return adc << adc_shiftbits(timing); +} + +static void tsl2563_wait_adc(struct tsl2563_chip *chip) +{ + unsigned int delay; + + switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + delay = 14; + break; + case TSL2563_TIMING_100MS: + delay = 101; + break; + default: + delay = 402; + } + /* + * TODO: Make sure that we wait at least required delay but why we + * have to extend it one tick more? + */ + schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2); +} + +static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc) +{ + struct i2c_client *client = chip->client; + + if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) { + + (adc > chip->gainlevel->max) ? + chip->gainlevel++ : chip->gainlevel--; + + i2c_smbus_write_byte_data(client, + TSL2563_CMD | TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + + tsl2563_wait_adc(chip); + tsl2563_wait_adc(chip); + + return 1; + } else + return 0; +} + +static int tsl2563_get_adc(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + u16 adc0, adc1; + int retry = 1; + int ret = 0; + + if (chip->suspended) + goto out; + + if (!chip->int_enabled) { + cancel_delayed_work(&chip->poweroff_work); + + if (!tsl2563_get_power(chip)) { + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + ret = tsl2563_configure(chip); + if (ret) + goto out; + tsl2563_wait_adc(chip); + } + } + + while (retry) { + ret = i2c_smbus_read_word_data(client, + TSL2563_CMD | TSL2563_REG_DATA0LOW); + if (ret < 0) + goto out; + adc0 = ret; + + ret = i2c_smbus_read_word_data(client, + TSL2563_CMD | TSL2563_REG_DATA1LOW); + if (ret < 0) + goto out; + adc1 = ret; + + retry = tsl2563_adjust_gainlevel(chip, adc0); + } + + chip->data0 = normalize_adc(adc0, chip->gainlevel->gaintime); + chip->data1 = normalize_adc(adc1, chip->gainlevel->gaintime); + + if (!chip->int_enabled) + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + + ret = 0; +out: + return ret; +} + +static inline int calib_to_sysfs(u32 calib) +{ + return (int) (((calib * CALIB_BASE_SYSFS) + + CALIB_FRAC_HALF) >> CALIB_FRAC_BITS); +} + +static inline u32 calib_from_sysfs(int value) +{ + return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS; +} + +/* + * Conversions between lux and ADC values. + * + * The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are + * appropriate constants. Different constants are needed for different + * kinds of light, determined by the ratio adc1/adc0 (basically the ratio + * of the intensities in infrared and visible wavelengths). lux_table below + * lists the upper threshold of the adc1/adc0 ratio and the corresponding + * constants. + */ + +struct tsl2563_lux_coeff { + unsigned long ch_ratio; + unsigned long ch0_coeff; + unsigned long ch1_coeff; +}; + +static const struct tsl2563_lux_coeff lux_table[] = { + { + .ch_ratio = FRAC10K(1300), + .ch0_coeff = FRAC10K(315), + .ch1_coeff = FRAC10K(262), + }, { + .ch_ratio = FRAC10K(2600), + .ch0_coeff = FRAC10K(337), + .ch1_coeff = FRAC10K(430), + }, { + .ch_ratio = FRAC10K(3900), + .ch0_coeff = FRAC10K(363), + .ch1_coeff = FRAC10K(529), + }, { + .ch_ratio = FRAC10K(5200), + .ch0_coeff = FRAC10K(392), + .ch1_coeff = FRAC10K(605), + }, { + .ch_ratio = FRAC10K(6500), + .ch0_coeff = FRAC10K(229), + .ch1_coeff = FRAC10K(291), + }, { + .ch_ratio = FRAC10K(8000), + .ch0_coeff = FRAC10K(157), + .ch1_coeff = FRAC10K(180), + }, { + .ch_ratio = FRAC10K(13000), + .ch0_coeff = FRAC10K(34), + .ch1_coeff = FRAC10K(26), + }, { + .ch_ratio = ULONG_MAX, + .ch0_coeff = 0, + .ch1_coeff = 0, + }, +}; + +/* Convert normalized, scaled ADC values to lux. */ +static unsigned int adc_to_lux(u32 adc0, u32 adc1) +{ + const struct tsl2563_lux_coeff *lp = lux_table; + unsigned long ratio, lux, ch0 = adc0, ch1 = adc1; + + ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX; + + while (lp->ch_ratio < ratio) + lp++; + + lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff; + + return (unsigned int) (lux >> ADC_FRAC_BITS); +} + +/* Apply calibration coefficient to ADC count. */ +static u32 calib_adc(u32 adc, u32 calib) +{ + unsigned long scaled = adc; + + scaled *= calib; + scaled >>= CALIB_FRAC_BITS; + + return (u32) scaled; +} + +static int tsl2563_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_CALIBSCALE) + return -EINVAL; + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + chip->calib0 = calib_from_sysfs(val); + else if (chan->channel2 == IIO_MOD_LIGHT_IR) + chip->calib1 = calib_from_sysfs(val); + else + return -EINVAL; + + return 0; +} + +static int tsl2563_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret = -EINVAL; + u32 calib0, calib1; + struct tsl2563_chip *chip = iio_priv(indio_dev); + + mutex_lock(&chip->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = tsl2563_get_adc(chip); + if (ret) + goto error_ret; + calib0 = calib_adc(chip->data0, chip->calib0) * + chip->cover_comp_gain; + calib1 = calib_adc(chip->data1, chip->calib1) * + chip->cover_comp_gain; + *val = adc_to_lux(calib0, calib1); + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + ret = tsl2563_get_adc(chip); + if (ret) + goto error_ret; + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = chip->data0; + else + *val = chip->data1; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->channel2 == IIO_MOD_LIGHT_BOTH) + *val = calib_to_sysfs(chip->calib0); + else + *val = calib_to_sysfs(chip->calib1); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + goto error_ret; + } + +error_ret: + mutex_unlock(&chip->lock); + return ret; +} + +static const struct iio_event_spec tsl2563_events[] = { + { + .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 tsl2563_channels[] = { + { + .type = IIO_LIGHT, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .channel = 0, + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2563_events, + .num_event_specs = ARRAY_SIZE(tsl2563_events), + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + } +}; + +static int tsl2563_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 tsl2563_chip *chip = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->high_thres; + break; + case IIO_EV_DIR_FALLING: + *val = chip->low_thres; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int tsl2563_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 tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + u8 address; + + if (dir == IIO_EV_DIR_RISING) + address = TSL2563_REG_HIGHLOW; + else + address = TSL2563_REG_LOWLOW; + mutex_lock(&chip->lock); + ret = i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | address, + val & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | (address + 1), + (val >> 8) & 0xFF); + if (dir == IIO_EV_DIR_RISING) + chip->high_thres = val; + else + chip->low_thres = val; + +error_ret: + mutex_unlock(&chip->lock); + + return ret; +} + +static irqreturn_t tsl2563_event_handler(int irq, void *private) +{ + struct iio_dev *dev_info = private; + struct tsl2563_chip *chip = iio_priv(dev_info); + + iio_push_event(dev_info, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns()); + + /* clear the interrupt and push the event */ + i2c_smbus_write_byte(chip->client, TSL2563_CMD | TSL2563_CLEARINT); + return IRQ_HANDLED; +} + +static int tsl2563_write_interrupt_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 tsl2563_chip *chip = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&chip->lock); + if (state && !(chip->intr & 0x30)) { + chip->intr &= ~0x30; + chip->intr |= 0x10; + /* ensure the chip is actually on */ + cancel_delayed_work(&chip->poweroff_work); + if (!tsl2563_get_power(chip)) { + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + ret = tsl2563_configure(chip); + if (ret) + goto out; + } + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + chip->int_enabled = true; + } + + if (!state && (chip->intr & 0x30)) { + chip->intr &= ~0x30; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + chip->int_enabled = false; + /* now the interrupt is not enabled, we can go to sleep */ + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + } +out: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tsl2563_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + ret = i2c_smbus_read_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT); + mutex_unlock(&chip->lock); + if (ret < 0) + return ret; + + return !!(ret & 0x30); +} + +static const struct iio_info tsl2563_info_no_irq = { + .driver_module = THIS_MODULE, + .read_raw = &tsl2563_read_raw, + .write_raw = &tsl2563_write_raw, +}; + +static const struct iio_info tsl2563_info = { + .driver_module = THIS_MODULE, + .read_raw = &tsl2563_read_raw, + .write_raw = &tsl2563_write_raw, + .read_event_value = &tsl2563_read_thresh, + .write_event_value = &tsl2563_write_thresh, + .read_event_config = &tsl2563_read_interrupt_config, + .write_event_config = &tsl2563_write_interrupt_config, +}; + +static int tsl2563_probe(struct i2c_client *client, + const struct i2c_device_id *device_id) +{ + struct iio_dev *indio_dev; + struct tsl2563_chip *chip; + struct tsl2563_platform_data *pdata = client->dev.platform_data; + struct device_node *np = client->dev.of_node; + int err = 0; + u8 id = 0; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, chip); + chip->client = client; + + err = tsl2563_detect(chip); + if (err) { + dev_err(&client->dev, "detect error %d\n", -err); + return err; + } + + err = tsl2563_read_id(chip, &id); + if (err) { + dev_err(&client->dev, "read id error %d\n", -err); + return err; + } + + mutex_init(&chip->lock); + + /* Default values used until userspace says otherwise */ + chip->low_thres = 0x0; + chip->high_thres = 0xffff; + chip->gainlevel = tsl2563_gainlevel_table; + chip->intr = TSL2563_INT_PERSIST(4); + chip->calib0 = calib_from_sysfs(CALIB_BASE_SYSFS); + chip->calib1 = calib_from_sysfs(CALIB_BASE_SYSFS); + + if (pdata) + chip->cover_comp_gain = pdata->cover_comp_gain; + else if (np) + of_property_read_u32(np, "amstaos,cover-comp-gain", + &chip->cover_comp_gain); + else + chip->cover_comp_gain = 1; + + dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f); + indio_dev->name = client->name; + indio_dev->channels = tsl2563_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2563_channels); + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) + indio_dev->info = &tsl2563_info; + else + indio_dev->info = &tsl2563_info_no_irq; + + if (client->irq) { + err = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + &tsl2563_event_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "tsl2563_event", + indio_dev); + if (err) { + dev_err(&client->dev, "irq request error %d\n", -err); + return err; + } + } + + err = tsl2563_configure(chip); + if (err) { + dev_err(&client->dev, "configure error %d\n", -err); + return err; + } + + INIT_DELAYED_WORK(&chip->poweroff_work, tsl2563_poweroff_work); + + /* The interrupt cannot yet be enabled so this is fine without lock */ + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + + err = iio_device_register(indio_dev); + if (err) { + dev_err(&client->dev, "iio registration error %d\n", -err); + goto fail; + } + + return 0; + +fail: + cancel_delayed_work(&chip->poweroff_work); + flush_scheduled_work(); + return err; +} + +static int tsl2563_remove(struct i2c_client *client) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(client); + struct iio_dev *indio_dev = iio_priv_to_dev(chip); + + iio_device_unregister(indio_dev); + if (!chip->int_enabled) + cancel_delayed_work(&chip->poweroff_work); + /* Ensure that interrupts are disabled - then flush any bottom halves */ + chip->intr &= ~0x30; + i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + flush_scheduled_work(); + tsl2563_set_power(chip, 0); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tsl2563_suspend(struct device *dev) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 0); + if (ret) + goto out; + + chip->suspended = true; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static int tsl2563_resume(struct device *dev) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + + ret = tsl2563_configure(chip); + if (ret) + goto out; + + chip->suspended = false; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static SIMPLE_DEV_PM_OPS(tsl2563_pm_ops, tsl2563_suspend, tsl2563_resume); +#define TSL2563_PM_OPS (&tsl2563_pm_ops) +#else +#define TSL2563_PM_OPS NULL +#endif + +static const struct i2c_device_id tsl2563_id[] = { + { "tsl2560", 0 }, + { "tsl2561", 1 }, + { "tsl2562", 2 }, + { "tsl2563", 3 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, tsl2563_id); + +static struct i2c_driver tsl2563_i2c_driver = { + .driver = { + .name = "tsl2563", + .pm = TSL2563_PM_OPS, + }, + .probe = tsl2563_probe, + .remove = tsl2563_remove, + .id_table = tsl2563_id, +}; +module_i2c_driver(tsl2563_i2c_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("tsl2563 light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/tsl4531.c b/drivers/iio/light/tsl4531.c new file mode 100644 index 000000000..0763b8632 --- /dev/null +++ b/drivers/iio/light/tsl4531.c @@ -0,0 +1,261 @@ +/* + * tsl4531.c - Support for TAOS TSL4531 ambient light sensor + * + * Copyright 2013 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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 the TSL4531x family + * TSL45311/TSL45313: 7-bit I2C slave address 0x39 + * TSL45315/TSL45317: 7-bit I2C slave address 0x29 + * + * TODO: single cycle measurement + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define TSL4531_DRV_NAME "tsl4531" + +#define TCS3472_COMMAND BIT(7) + +#define TSL4531_CONTROL (TCS3472_COMMAND | 0x00) +#define TSL4531_CONFIG (TCS3472_COMMAND | 0x01) +#define TSL4531_DATA (TCS3472_COMMAND | 0x04) +#define TSL4531_ID (TCS3472_COMMAND | 0x0a) + +/* operating modes in control register */ +#define TSL4531_MODE_POWERDOWN 0x00 +#define TSL4531_MODE_SINGLE_ADC 0x02 +#define TSL4531_MODE_NORMAL 0x03 + +/* integration time control in config register */ +#define TSL4531_TCNTRL_400MS 0x00 +#define TSL4531_TCNTRL_200MS 0x01 +#define TSL4531_TCNTRL_100MS 0x02 + +/* part number in id register */ +#define TSL45311_ID 0x8 +#define TSL45313_ID 0x9 +#define TSL45315_ID 0xa +#define TSL45317_ID 0xb +#define TSL4531_ID_SHIFT 4 + +struct tsl4531_data { + struct i2c_client *client; + struct mutex lock; + int int_time; +}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.1 0.2 0.4"); + +static struct attribute *tsl4531_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tsl4531_attribute_group = { + .attrs = tsl4531_attributes, +}; + +static const struct iio_chan_spec tsl4531_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME) + } +}; + +static int tsl4531_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tsl4531_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_data(data->client, + TSL4531_DATA); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* 0.. 1x, 1 .. 2x, 2 .. 4x */ + *val = 1 << data->int_time; + return IIO_VAL_INT; + case IIO_CHAN_INFO_INT_TIME: + if (data->int_time == 0) + *val2 = 400000; + else if (data->int_time == 1) + *val2 = 200000; + else if (data->int_time == 2) + *val2 = 100000; + else + return -EINVAL; + *val = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int tsl4531_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tsl4531_data *data = iio_priv(indio_dev); + int int_time, ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + if (val2 == 400000) + int_time = 0; + else if (val2 == 200000) + int_time = 1; + else if (val2 == 100000) + int_time = 2; + else + return -EINVAL; + mutex_lock(&data->lock); + ret = i2c_smbus_write_byte_data(data->client, + TSL4531_CONFIG, int_time); + if (ret >= 0) + data->int_time = int_time; + mutex_unlock(&data->lock); + return ret; + default: + return -EINVAL; + } +} + +static const struct iio_info tsl4531_info = { + .read_raw = tsl4531_read_raw, + .write_raw = tsl4531_write_raw, + .attrs = &tsl4531_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int tsl4531_check_id(struct i2c_client *client) +{ + int ret = i2c_smbus_read_byte_data(client, TSL4531_ID); + if (ret < 0) + return ret; + + switch (ret >> TSL4531_ID_SHIFT) { + case TSL45311_ID: + case TSL45313_ID: + case TSL45315_ID: + case TSL45317_ID: + return 1; + default: + return 0; + } +} + +static int tsl4531_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tsl4531_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + mutex_init(&data->lock); + + if (!tsl4531_check_id(client)) { + dev_err(&client->dev, "no TSL4531 sensor\n"); + return -ENODEV; + } + + ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONTROL, + TSL4531_MODE_NORMAL); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, TSL4531_CONFIG, + TSL4531_TCNTRL_400MS); + if (ret < 0) + return ret; + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &tsl4531_info; + indio_dev->channels = tsl4531_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl4531_channels); + indio_dev->name = TSL4531_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + return iio_device_register(indio_dev); +} + +static int tsl4531_powerdown(struct i2c_client *client) +{ + return i2c_smbus_write_byte_data(client, TSL4531_CONTROL, + TSL4531_MODE_POWERDOWN); +} + +static int tsl4531_remove(struct i2c_client *client) +{ + iio_device_unregister(i2c_get_clientdata(client)); + tsl4531_powerdown(client); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tsl4531_suspend(struct device *dev) +{ + return tsl4531_powerdown(to_i2c_client(dev)); +} + +static int tsl4531_resume(struct device *dev) +{ + return i2c_smbus_write_byte_data(to_i2c_client(dev), TSL4531_CONTROL, + TSL4531_MODE_NORMAL); +} + +static SIMPLE_DEV_PM_OPS(tsl4531_pm_ops, tsl4531_suspend, tsl4531_resume); +#define TSL4531_PM_OPS (&tsl4531_pm_ops) +#else +#define TSL4531_PM_OPS NULL +#endif + +static const struct i2c_device_id tsl4531_id[] = { + { "tsl4531", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tsl4531_id); + +static struct i2c_driver tsl4531_driver = { + .driver = { + .name = TSL4531_DRV_NAME, + .pm = TSL4531_PM_OPS, + .owner = THIS_MODULE, + }, + .probe = tsl4531_probe, + .remove = tsl4531_remove, + .id_table = tsl4531_id, +}; + +module_i2c_driver(tsl4531_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("TAOS TSL4531 ambient light sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c new file mode 100644 index 000000000..d948c4778 --- /dev/null +++ b/drivers/iio/light/vcnl4000.c @@ -0,0 +1,198 @@ +/* + * vcnl4000.c - Support for Vishay VCNL4000 combined ambient light and + * proximity sensor + * + * Copyright 2012 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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 VCNL4000 (7-bit I2C slave address 0x13) + * + * TODO: + * allow to adjust IR current + * proximity threshold and event handling + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/delay.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define VCNL4000_DRV_NAME "vcnl4000" + +#define VCNL4000_COMMAND 0x80 /* Command register */ +#define VCNL4000_PROD_REV 0x81 /* Product ID and Revision ID */ +#define VCNL4000_LED_CURRENT 0x83 /* IR LED current for proximity mode */ +#define VCNL4000_AL_PARAM 0x84 /* Ambient light parameter register */ +#define VCNL4000_AL_RESULT_HI 0x85 /* Ambient light result register, MSB */ +#define VCNL4000_AL_RESULT_LO 0x86 /* Ambient light result register, LSB */ +#define VCNL4000_PS_RESULT_HI 0x87 /* Proximity result register, MSB */ +#define VCNL4000_PS_RESULT_LO 0x88 /* Proximity result register, LSB */ +#define VCNL4000_PS_MEAS_FREQ 0x89 /* Proximity test signal frequency */ +#define VCNL4000_PS_MOD_ADJ 0x8a /* Proximity modulator timing adjustment */ + +/* Bit masks for COMMAND register */ +#define VCNL4000_AL_RDY 0x40 /* ALS data ready? */ +#define VCNL4000_PS_RDY 0x20 /* proximity data ready? */ +#define VCNL4000_AL_OD 0x10 /* start on-demand ALS measurement */ +#define VCNL4000_PS_OD 0x08 /* start on-demand proximity measurement */ + +struct vcnl4000_data { + struct i2c_client *client; +}; + +static const struct i2c_device_id vcnl4000_id[] = { + { "vcnl4000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vcnl4000_id); + +static int vcnl4000_measure(struct vcnl4000_data *data, u8 req_mask, + u8 rdy_mask, u8 data_reg, int *val) +{ + int tries = 20; + __be16 buf; + int ret; + + ret = i2c_smbus_write_byte_data(data->client, VCNL4000_COMMAND, + req_mask); + if (ret < 0) + return ret; + + /* wait for data to become ready */ + while (tries--) { + ret = i2c_smbus_read_byte_data(data->client, VCNL4000_COMMAND); + if (ret < 0) + return ret; + if (ret & rdy_mask) + break; + msleep(20); /* measurement takes up to 100 ms */ + } + + if (tries < 0) { + dev_err(&data->client->dev, + "vcnl4000_measure() failed, data not ready\n"); + return -EIO; + } + + ret = i2c_smbus_read_i2c_block_data(data->client, + data_reg, sizeof(buf), (u8 *) &buf); + if (ret < 0) + return ret; + + *val = be16_to_cpu(buf); + + return 0; +} + +static const struct iio_chan_spec vcnl4000_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static int vcnl4000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret = -EINVAL; + struct vcnl4000_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + ret = vcnl4000_measure(data, + VCNL4000_AL_OD, VCNL4000_AL_RDY, + VCNL4000_AL_RESULT_HI, val); + if (ret < 0) + return ret; + ret = IIO_VAL_INT; + break; + case IIO_PROXIMITY: + ret = vcnl4000_measure(data, + VCNL4000_PS_OD, VCNL4000_PS_RDY, + VCNL4000_PS_RESULT_HI, val); + if (ret < 0) + return ret; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_LIGHT) { + *val = 0; + *val2 = 250000; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + + return ret; +} + +static const struct iio_info vcnl4000_info = { + .read_raw = vcnl4000_read_raw, + .driver_module = THIS_MODULE, +}; + +static int vcnl4000_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct vcnl4000_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + ret = i2c_smbus_read_byte_data(data->client, VCNL4000_PROD_REV); + if (ret < 0) + return ret; + + dev_info(&client->dev, "VCNL4000 Ambient light/proximity sensor, Prod %02x, Rev: %02x\n", + ret >> 4, ret & 0xf); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &vcnl4000_info; + indio_dev->channels = vcnl4000_channels; + indio_dev->num_channels = ARRAY_SIZE(vcnl4000_channels); + indio_dev->name = VCNL4000_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static struct i2c_driver vcnl4000_driver = { + .driver = { + .name = VCNL4000_DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = vcnl4000_probe, + .id_table = vcnl4000_id, +}; + +module_i2c_driver(vcnl4000_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Vishay VCNL4000 proximity/ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig new file mode 100644 index 000000000..a5d6de72c --- /dev/null +++ b/drivers/iio/magnetometer/Kconfig @@ -0,0 +1,79 @@ +# +# Magnetometer sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Magnetometer sensors" + +config AK8975 + tristate "Asahi Kasei AK 3-Axis Magnetometer" + depends on I2C + depends on GPIOLIB + help + Say yes here to build support for Asahi Kasei AK8975, AK8963, + AK09911 or AK09912 3-Axis Magnetometer. + + To compile this driver as a module, choose M here: the module + will be called ak8975. + +config AK09911 + tristate "Asahi Kasei AK09911 3-axis Compass" + depends on I2C + depends on GPIOLIB + select AK8975 + help + Deprecated: AK09911 is now supported by AK8975 driver. + +config MAG3110 + tristate "Freescale MAG3110 3-Axis Magnetometer" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Freescale MAG3110 3-Axis + magnetometer. + + To compile this driver as a module, choose M here: the module + will be called mag3110. + +config HID_SENSOR_MAGNETOMETER_3D + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID Magenetometer 3D" + help + Say yes here to build support for the HID SENSOR + Magnetometer 3D. + +config IIO_ST_MAGN_3AXIS + tristate "STMicroelectronics magnetometers 3-Axis Driver" + depends on (I2C || SPI_MASTER) && SYSFS + select IIO_ST_SENSORS_CORE + select IIO_ST_MAGN_I2C_3AXIS if (I2C) + select IIO_ST_MAGN_SPI_3AXIS if (SPI_MASTER) + select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) + help + Say yes here to build support for STMicroelectronics magnetometers: + LSM303DLHC, LSM303DLM, LIS3MDL. + + This driver can also be built as a module. If so, these modules + will be created: + - st_magn (core functions for the driver [it is mandatory]); + - st_magn_i2c (necessary for the I2C devices [optional*]); + - st_magn_spi (necessary for the SPI devices [optional*]); + + (*) one of these is necessary to do something. + +config IIO_ST_MAGN_I2C_3AXIS + tristate + depends on IIO_ST_MAGN_3AXIS + depends on IIO_ST_SENSORS_I2C + +config IIO_ST_MAGN_SPI_3AXIS + tristate + depends on IIO_ST_MAGN_3AXIS + depends on IIO_ST_SENSORS_SPI + +endmenu diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile new file mode 100644 index 000000000..0f5d3c985 --- /dev/null +++ b/drivers/iio/magnetometer/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for industrial I/O Magnetometer sensor drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AK8975) += ak8975.o +obj-$(CONFIG_MAG3110) += mag3110.o +obj-$(CONFIG_HID_SENSOR_MAGNETOMETER_3D) += hid-sensor-magn-3d.o + +obj-$(CONFIG_IIO_ST_MAGN_3AXIS) += st_magn.o +st_magn-y := st_magn_core.o +st_magn-$(CONFIG_IIO_BUFFER) += st_magn_buffer.o + +obj-$(CONFIG_IIO_ST_MAGN_I2C_3AXIS) += st_magn_i2c.o +obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c new file mode 100644 index 000000000..b13936dac --- /dev/null +++ b/drivers/iio/magnetometer/ak8975.c @@ -0,0 +1,850 @@ +/* + * A sensor driver for the magnetometer AK8975. + * + * Magnetic compass sensor driver for monitoring magnetic flux information. + * + * Copyright (c) 2010, NVIDIA Corporation. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/acpi.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +/* + * Register definitions, as well as various shifts and masks to get at the + * individual fields of the registers. + */ +#define AK8975_REG_WIA 0x00 +#define AK8975_DEVICE_ID 0x48 + +#define AK8975_REG_INFO 0x01 + +#define AK8975_REG_ST1 0x02 +#define AK8975_REG_ST1_DRDY_SHIFT 0 +#define AK8975_REG_ST1_DRDY_MASK (1 << AK8975_REG_ST1_DRDY_SHIFT) + +#define AK8975_REG_HXL 0x03 +#define AK8975_REG_HXH 0x04 +#define AK8975_REG_HYL 0x05 +#define AK8975_REG_HYH 0x06 +#define AK8975_REG_HZL 0x07 +#define AK8975_REG_HZH 0x08 +#define AK8975_REG_ST2 0x09 +#define AK8975_REG_ST2_DERR_SHIFT 2 +#define AK8975_REG_ST2_DERR_MASK (1 << AK8975_REG_ST2_DERR_SHIFT) + +#define AK8975_REG_ST2_HOFL_SHIFT 3 +#define AK8975_REG_ST2_HOFL_MASK (1 << AK8975_REG_ST2_HOFL_SHIFT) + +#define AK8975_REG_CNTL 0x0A +#define AK8975_REG_CNTL_MODE_SHIFT 0 +#define AK8975_REG_CNTL_MODE_MASK (0xF << AK8975_REG_CNTL_MODE_SHIFT) +#define AK8975_REG_CNTL_MODE_POWER_DOWN 0x00 +#define AK8975_REG_CNTL_MODE_ONCE 0x01 +#define AK8975_REG_CNTL_MODE_SELF_TEST 0x08 +#define AK8975_REG_CNTL_MODE_FUSE_ROM 0x0F + +#define AK8975_REG_RSVC 0x0B +#define AK8975_REG_ASTC 0x0C +#define AK8975_REG_TS1 0x0D +#define AK8975_REG_TS2 0x0E +#define AK8975_REG_I2CDIS 0x0F +#define AK8975_REG_ASAX 0x10 +#define AK8975_REG_ASAY 0x11 +#define AK8975_REG_ASAZ 0x12 + +#define AK8975_MAX_REGS AK8975_REG_ASAZ + +/* + * AK09912 Register definitions + */ +#define AK09912_REG_WIA1 0x00 +#define AK09912_REG_WIA2 0x01 +#define AK09912_DEVICE_ID 0x04 +#define AK09911_DEVICE_ID 0x05 + +#define AK09911_REG_INFO1 0x02 +#define AK09911_REG_INFO2 0x03 + +#define AK09912_REG_ST1 0x10 + +#define AK09912_REG_ST1_DRDY_SHIFT 0 +#define AK09912_REG_ST1_DRDY_MASK (1 << AK09912_REG_ST1_DRDY_SHIFT) + +#define AK09912_REG_HXL 0x11 +#define AK09912_REG_HXH 0x12 +#define AK09912_REG_HYL 0x13 +#define AK09912_REG_HYH 0x14 +#define AK09912_REG_HZL 0x15 +#define AK09912_REG_HZH 0x16 +#define AK09912_REG_TMPS 0x17 + +#define AK09912_REG_ST2 0x18 +#define AK09912_REG_ST2_HOFL_SHIFT 3 +#define AK09912_REG_ST2_HOFL_MASK (1 << AK09912_REG_ST2_HOFL_SHIFT) + +#define AK09912_REG_CNTL1 0x30 + +#define AK09912_REG_CNTL2 0x31 +#define AK09912_REG_CNTL_MODE_POWER_DOWN 0x00 +#define AK09912_REG_CNTL_MODE_ONCE 0x01 +#define AK09912_REG_CNTL_MODE_SELF_TEST 0x10 +#define AK09912_REG_CNTL_MODE_FUSE_ROM 0x1F +#define AK09912_REG_CNTL2_MODE_SHIFT 0 +#define AK09912_REG_CNTL2_MODE_MASK (0x1F << AK09912_REG_CNTL2_MODE_SHIFT) + +#define AK09912_REG_CNTL3 0x32 + +#define AK09912_REG_TS1 0x33 +#define AK09912_REG_TS2 0x34 +#define AK09912_REG_TS3 0x35 +#define AK09912_REG_I2CDIS 0x36 +#define AK09912_REG_TS4 0x37 + +#define AK09912_REG_ASAX 0x60 +#define AK09912_REG_ASAY 0x61 +#define AK09912_REG_ASAZ 0x62 + +#define AK09912_MAX_REGS AK09912_REG_ASAZ + +/* + * Miscellaneous values. + */ +#define AK8975_MAX_CONVERSION_TIMEOUT 500 +#define AK8975_CONVERSION_DONE_POLL_TIME 10 +#define AK8975_DATA_READY_TIMEOUT ((100*HZ)/1000) + +/* + * Precalculate scale factor (in Gauss units) for each axis and + * store in the device data. + * + * This scale factor is axis-dependent, and is derived from 3 calibration + * factors ASA(x), ASA(y), and ASA(z). + * + * These ASA values are read from the sensor device at start of day, and + * cached in the device context struct. + * + * Adjusting the flux value with the sensitivity adjustment value should be + * done via the following formula: + * + * Hadj = H * ( ( ( (ASA-128)*0.5 ) / 128 ) + 1 ) + * where H is the raw value, ASA is the sensitivity adjustment, and Hadj + * is the resultant adjusted value. + * + * We reduce the formula to: + * + * Hadj = H * (ASA + 128) / 256 + * + * H is in the range of -4096 to 4095. The magnetometer has a range of + * +-1229uT. To go from the raw value to uT is: + * + * HuT = H * 1229/4096, or roughly, 3/10. + * + * Since 1uT = 0.01 gauss, our final scale factor becomes: + * + * Hadj = H * ((ASA + 128) / 256) * 3/10 * 1/100 + * Hadj = H * ((ASA + 128) * 0.003) / 256 + * + * Since ASA doesn't change, we cache the resultant scale factor into the + * device context in ak8975_setup(). + * + * Given we use IIO_VAL_INT_PLUS_MICRO bit when displaying the scale, we + * multiply the stored scale value by 1e6. + */ +static long ak8975_raw_to_gauss(u16 data) +{ + return (((long)data + 128) * 3000) / 256; +} + +/* + * For AK8963 and AK09911, same calculation, but the device is less sensitive: + * + * H is in the range of +-8190. The magnetometer has a range of + * +-4912uT. To go from the raw value to uT is: + * + * HuT = H * 4912/8190, or roughly, 6/10, instead of 3/10. + */ + +static long ak8963_09911_raw_to_gauss(u16 data) +{ + return (((long)data + 128) * 6000) / 256; +} + +/* + * For AK09912, same calculation, except the device is more sensitive: + * + * H is in the range of -32752 to 32752. The magnetometer has a range of + * +-4912uT. To go from the raw value to uT is: + * + * HuT = H * 4912/32752, or roughly, 3/20, instead of 3/10. + */ +static long ak09912_raw_to_gauss(u16 data) +{ + return (((long)data + 128) * 1500) / 256; +} + +/* Compatible Asahi Kasei Compass parts */ +enum asahi_compass_chipset { + AK8975, + AK8963, + AK09911, + AK09912, + AK_MAX_TYPE +}; + +enum ak_ctrl_reg_addr { + ST1, + ST2, + CNTL, + ASA_BASE, + MAX_REGS, + REGS_END, +}; + +enum ak_ctrl_reg_mask { + ST1_DRDY, + ST2_HOFL, + ST2_DERR, + CNTL_MODE, + MASK_END, +}; + +enum ak_ctrl_mode { + POWER_DOWN, + MODE_ONCE, + SELF_TEST, + FUSE_ROM, + MODE_END, +}; + +struct ak_def { + enum asahi_compass_chipset type; + long (*raw_to_gauss)(u16 data); + u16 range; + u8 ctrl_regs[REGS_END]; + u8 ctrl_masks[MASK_END]; + u8 ctrl_modes[MODE_END]; + u8 data_regs[3]; +}; + +static struct ak_def ak_def_array[AK_MAX_TYPE] = { + { + .type = AK8975, + .raw_to_gauss = ak8975_raw_to_gauss, + .range = 4096, + .ctrl_regs = { + AK8975_REG_ST1, + AK8975_REG_ST2, + AK8975_REG_CNTL, + AK8975_REG_ASAX, + AK8975_MAX_REGS}, + .ctrl_masks = { + AK8975_REG_ST1_DRDY_MASK, + AK8975_REG_ST2_HOFL_MASK, + AK8975_REG_ST2_DERR_MASK, + AK8975_REG_CNTL_MODE_MASK}, + .ctrl_modes = { + AK8975_REG_CNTL_MODE_POWER_DOWN, + AK8975_REG_CNTL_MODE_ONCE, + AK8975_REG_CNTL_MODE_SELF_TEST, + AK8975_REG_CNTL_MODE_FUSE_ROM}, + .data_regs = { + AK8975_REG_HXL, + AK8975_REG_HYL, + AK8975_REG_HZL}, + }, + { + .type = AK8963, + .raw_to_gauss = ak8963_09911_raw_to_gauss, + .range = 8190, + .ctrl_regs = { + AK8975_REG_ST1, + AK8975_REG_ST2, + AK8975_REG_CNTL, + AK8975_REG_ASAX, + AK8975_MAX_REGS}, + .ctrl_masks = { + AK8975_REG_ST1_DRDY_MASK, + AK8975_REG_ST2_HOFL_MASK, + 0, + AK8975_REG_CNTL_MODE_MASK}, + .ctrl_modes = { + AK8975_REG_CNTL_MODE_POWER_DOWN, + AK8975_REG_CNTL_MODE_ONCE, + AK8975_REG_CNTL_MODE_SELF_TEST, + AK8975_REG_CNTL_MODE_FUSE_ROM}, + .data_regs = { + AK8975_REG_HXL, + AK8975_REG_HYL, + AK8975_REG_HZL}, + }, + { + .type = AK09911, + .raw_to_gauss = ak8963_09911_raw_to_gauss, + .range = 8192, + .ctrl_regs = { + AK09912_REG_ST1, + AK09912_REG_ST2, + AK09912_REG_CNTL2, + AK09912_REG_ASAX, + AK09912_MAX_REGS}, + .ctrl_masks = { + AK09912_REG_ST1_DRDY_MASK, + AK09912_REG_ST2_HOFL_MASK, + 0, + AK09912_REG_CNTL2_MODE_MASK}, + .ctrl_modes = { + AK09912_REG_CNTL_MODE_POWER_DOWN, + AK09912_REG_CNTL_MODE_ONCE, + AK09912_REG_CNTL_MODE_SELF_TEST, + AK09912_REG_CNTL_MODE_FUSE_ROM}, + .data_regs = { + AK09912_REG_HXL, + AK09912_REG_HYL, + AK09912_REG_HZL}, + }, + { + .type = AK09912, + .raw_to_gauss = ak09912_raw_to_gauss, + .range = 32752, + .ctrl_regs = { + AK09912_REG_ST1, + AK09912_REG_ST2, + AK09912_REG_CNTL2, + AK09912_REG_ASAX, + AK09912_MAX_REGS}, + .ctrl_masks = { + AK09912_REG_ST1_DRDY_MASK, + AK09912_REG_ST2_HOFL_MASK, + 0, + AK09912_REG_CNTL2_MODE_MASK}, + .ctrl_modes = { + AK09912_REG_CNTL_MODE_POWER_DOWN, + AK09912_REG_CNTL_MODE_ONCE, + AK09912_REG_CNTL_MODE_SELF_TEST, + AK09912_REG_CNTL_MODE_FUSE_ROM}, + .data_regs = { + AK09912_REG_HXL, + AK09912_REG_HYL, + AK09912_REG_HZL}, + } +}; + +/* + * Per-instance context data for the device. + */ +struct ak8975_data { + struct i2c_client *client; + struct ak_def *def; + struct attribute_group attrs; + struct mutex lock; + u8 asa[3]; + long raw_to_gauss[3]; + int eoc_gpio; + int eoc_irq; + wait_queue_head_t data_ready_queue; + unsigned long flags; + u8 cntl_cache; +}; + +/* + * Return 0 if the i2c device is the one we expect. + * return a negative error number otherwise + */ +static int ak8975_who_i_am(struct i2c_client *client, + enum asahi_compass_chipset type) +{ + u8 wia_val[2]; + int ret; + + /* + * Signature for each device: + * Device | WIA1 | WIA2 + * AK09912 | DEVICE_ID | AK09912_DEVICE_ID + * AK09911 | DEVICE_ID | AK09911_DEVICE_ID + * AK8975 | DEVICE_ID | NA + * AK8963 | DEVICE_ID | NA + */ + ret = i2c_smbus_read_i2c_block_data(client, AK09912_REG_WIA1, + 2, wia_val); + if (ret < 0) { + dev_err(&client->dev, "Error reading WIA\n"); + return ret; + } + + if (wia_val[0] != AK8975_DEVICE_ID) + return -ENODEV; + + switch (type) { + case AK8975: + case AK8963: + return 0; + case AK09911: + if (wia_val[1] == AK09911_DEVICE_ID) + return 0; + break; + case AK09912: + if (wia_val[1] == AK09912_DEVICE_ID) + return 0; + break; + default: + dev_err(&client->dev, "Type %d unknown\n", type); + } + return -ENODEV; +} + +/* + * Helper function to write to CNTL register. + */ +static int ak8975_set_mode(struct ak8975_data *data, enum ak_ctrl_mode mode) +{ + u8 regval; + int ret; + + regval = (data->cntl_cache & ~data->def->ctrl_masks[CNTL_MODE]) | + data->def->ctrl_modes[mode]; + ret = i2c_smbus_write_byte_data(data->client, + data->def->ctrl_regs[CNTL], regval); + if (ret < 0) { + return ret; + } + data->cntl_cache = regval; + /* After mode change wait atleast 100us */ + usleep_range(100, 500); + + return 0; +} + +/* + * Handle data ready irq + */ +static irqreturn_t ak8975_irq_handler(int irq, void *data) +{ + struct ak8975_data *ak8975 = data; + + set_bit(0, &ak8975->flags); + wake_up(&ak8975->data_ready_queue); + + return IRQ_HANDLED; +} + +/* + * Install data ready interrupt handler + */ +static int ak8975_setup_irq(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + int rc; + int irq; + + if (client->irq) + irq = client->irq; + else + irq = gpio_to_irq(data->eoc_gpio); + + rc = devm_request_irq(&client->dev, irq, ak8975_irq_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + dev_name(&client->dev), data); + if (rc < 0) { + dev_err(&client->dev, + "irq %d request failed, (gpio %d): %d\n", + irq, data->eoc_gpio, rc); + return rc; + } + + init_waitqueue_head(&data->data_ready_queue); + clear_bit(0, &data->flags); + data->eoc_irq = irq; + + return rc; +} + + +/* + * Perform some start-of-day setup, including reading the asa calibration + * values and caching them. + */ +static int ak8975_setup(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ak8975_data *data = iio_priv(indio_dev); + int ret; + + /* Write the fused rom access mode. */ + ret = ak8975_set_mode(data, FUSE_ROM); + if (ret < 0) { + dev_err(&client->dev, "Error in setting fuse access mode\n"); + return ret; + } + + /* Get asa data and store in the device data. */ + ret = i2c_smbus_read_i2c_block_data(client, + data->def->ctrl_regs[ASA_BASE], + 3, data->asa); + if (ret < 0) { + dev_err(&client->dev, "Not able to read asa data\n"); + return ret; + } + + /* After reading fuse ROM data set power-down mode */ + ret = ak8975_set_mode(data, POWER_DOWN); + if (ret < 0) { + dev_err(&client->dev, "Error in setting power-down mode\n"); + return ret; + } + + if (data->eoc_gpio > 0 || client->irq > 0) { + ret = ak8975_setup_irq(data); + if (ret < 0) { + dev_err(&client->dev, + "Error setting data ready interrupt\n"); + return ret; + } + } + + data->raw_to_gauss[0] = data->def->raw_to_gauss(data->asa[0]); + data->raw_to_gauss[1] = data->def->raw_to_gauss(data->asa[1]); + data->raw_to_gauss[2] = data->def->raw_to_gauss(data->asa[2]); + + return 0; +} + +static int wait_conversion_complete_gpio(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + u32 timeout_ms = AK8975_MAX_CONVERSION_TIMEOUT; + int ret; + + /* Wait for the conversion to complete. */ + while (timeout_ms) { + msleep(AK8975_CONVERSION_DONE_POLL_TIME); + if (gpio_get_value(data->eoc_gpio)) + break; + timeout_ms -= AK8975_CONVERSION_DONE_POLL_TIME; + } + if (!timeout_ms) { + dev_err(&client->dev, "Conversion timeout happened\n"); + return -EINVAL; + } + + ret = i2c_smbus_read_byte_data(client, data->def->ctrl_regs[ST1]); + if (ret < 0) + dev_err(&client->dev, "Error in reading ST1\n"); + + return ret; +} + +static int wait_conversion_complete_polled(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + u8 read_status; + u32 timeout_ms = AK8975_MAX_CONVERSION_TIMEOUT; + int ret; + + /* Wait for the conversion to complete. */ + while (timeout_ms) { + msleep(AK8975_CONVERSION_DONE_POLL_TIME); + ret = i2c_smbus_read_byte_data(client, + data->def->ctrl_regs[ST1]); + if (ret < 0) { + dev_err(&client->dev, "Error in reading ST1\n"); + return ret; + } + read_status = ret; + if (read_status) + break; + timeout_ms -= AK8975_CONVERSION_DONE_POLL_TIME; + } + if (!timeout_ms) { + dev_err(&client->dev, "Conversion timeout happened\n"); + return -EINVAL; + } + + return read_status; +} + +/* Returns 0 if the end of conversion interrupt occured or -ETIME otherwise */ +static int wait_conversion_complete_interrupt(struct ak8975_data *data) +{ + int ret; + + ret = wait_event_timeout(data->data_ready_queue, + test_bit(0, &data->flags), + AK8975_DATA_READY_TIMEOUT); + clear_bit(0, &data->flags); + + return ret > 0 ? 0 : -ETIME; +} + +/* + * Emits the raw flux value for the x, y, or z axis. + */ +static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) +{ + struct ak8975_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + int ret; + + mutex_lock(&data->lock); + + /* Set up the device for taking a sample. */ + ret = ak8975_set_mode(data, MODE_ONCE); + if (ret < 0) { + dev_err(&client->dev, "Error in setting operating mode\n"); + goto exit; + } + + /* Wait for the conversion to complete. */ + if (data->eoc_irq) + ret = wait_conversion_complete_interrupt(data); + else if (gpio_is_valid(data->eoc_gpio)) + ret = wait_conversion_complete_gpio(data); + else + ret = wait_conversion_complete_polled(data); + if (ret < 0) + goto exit; + + /* This will be executed only for non-interrupt based waiting case */ + if (ret & data->def->ctrl_masks[ST1_DRDY]) { + ret = i2c_smbus_read_byte_data(client, + data->def->ctrl_regs[ST2]); + if (ret < 0) { + dev_err(&client->dev, "Error in reading ST2\n"); + goto exit; + } + if (ret & (data->def->ctrl_masks[ST2_DERR] | + data->def->ctrl_masks[ST2_HOFL])) { + dev_err(&client->dev, "ST2 status error 0x%x\n", ret); + ret = -EINVAL; + goto exit; + } + } + + /* Read the flux value from the appropriate register + (the register is specified in the iio device attributes). */ + ret = i2c_smbus_read_word_data(client, data->def->data_regs[index]); + if (ret < 0) { + dev_err(&client->dev, "Read axis data fails\n"); + goto exit; + } + + mutex_unlock(&data->lock); + + /* Clamp to valid range. */ + *val = clamp_t(s16, ret, -data->def->range, data->def->range); + return IIO_VAL_INT; + +exit: + mutex_unlock(&data->lock); + return ret; +} + +static int ak8975_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct ak8975_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return ak8975_read_axis(indio_dev, chan->address, val); + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = data->raw_to_gauss[chan->address]; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +#define AK8975_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + } + +static const struct iio_chan_spec ak8975_channels[] = { + AK8975_CHANNEL(X, 0), AK8975_CHANNEL(Y, 1), AK8975_CHANNEL(Z, 2), +}; + +static const struct iio_info ak8975_info = { + .read_raw = &ak8975_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct acpi_device_id ak_acpi_match[] = { + {"AK8975", AK8975}, + {"AK8963", AK8963}, + {"INVN6500", AK8963}, + {"AK09911", AK09911}, + {"AK09912", AK09912}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ak_acpi_match); + +static const char *ak8975_match_acpi_device(struct device *dev, + enum asahi_compass_chipset *chipset) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + *chipset = (int)id->driver_data; + + return dev_name(dev); +} + +static int ak8975_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ak8975_data *data; + struct iio_dev *indio_dev; + int eoc_gpio; + int err; + const char *name = NULL; + enum asahi_compass_chipset chipset; + + /* Grab and set up the supplied GPIO. */ + if (client->dev.platform_data) + eoc_gpio = *(int *)(client->dev.platform_data); + else if (client->dev.of_node) + eoc_gpio = of_get_gpio(client->dev.of_node, 0); + else + eoc_gpio = -1; + + if (eoc_gpio == -EPROBE_DEFER) + return -EPROBE_DEFER; + + /* We may not have a GPIO based IRQ to scan, that is fine, we will + poll if so */ + if (gpio_is_valid(eoc_gpio)) { + err = devm_gpio_request_one(&client->dev, eoc_gpio, + GPIOF_IN, "ak_8975"); + if (err < 0) { + dev_err(&client->dev, + "failed to request GPIO %d, error %d\n", + eoc_gpio, err); + return err; + } + } + + /* Register with IIO */ + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->client = client; + data->eoc_gpio = eoc_gpio; + data->eoc_irq = 0; + + /* id will be NULL when enumerated via ACPI */ + if (id) { + chipset = (enum asahi_compass_chipset)(id->driver_data); + name = id->name; + } else if (ACPI_HANDLE(&client->dev)) + name = ak8975_match_acpi_device(&client->dev, &chipset); + else + return -ENOSYS; + + if (chipset >= AK_MAX_TYPE) { + dev_err(&client->dev, "AKM device type unsupported: %d\n", + chipset); + return -ENODEV; + } + + data->def = &ak_def_array[chipset]; + err = ak8975_who_i_am(client, data->def->type); + if (err < 0) { + dev_err(&client->dev, "Unexpected device\n"); + return err; + } + dev_dbg(&client->dev, "Asahi compass chip %s\n", name); + + /* Perform some basic start-of-day setup of the device. */ + err = ak8975_setup(client); + if (err < 0) { + dev_err(&client->dev, "%s initialization fails\n", name); + return err; + } + + mutex_init(&data->lock); + indio_dev->dev.parent = &client->dev; + indio_dev->channels = ak8975_channels; + indio_dev->num_channels = ARRAY_SIZE(ak8975_channels); + indio_dev->info = &ak8975_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = name; + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id ak8975_id[] = { + {"ak8975", AK8975}, + {"ak8963", AK8963}, + {"AK8963", AK8963}, + {"ak09911", AK09911}, + {"ak09912", AK09912}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ak8975_id); + +static const struct of_device_id ak8975_of_match[] = { + { .compatible = "asahi-kasei,ak8975", }, + { .compatible = "ak8975", }, + { .compatible = "asahi-kasei,ak8963", }, + { .compatible = "ak8963", }, + { .compatible = "asahi-kasei,ak09911", }, + { .compatible = "ak09911", }, + { .compatible = "asahi-kasei,ak09912", }, + { .compatible = "ak09912", }, + {} +}; +MODULE_DEVICE_TABLE(of, ak8975_of_match); + +static struct i2c_driver ak8975_driver = { + .driver = { + .name = "ak8975", + .of_match_table = of_match_ptr(ak8975_of_match), + .acpi_match_table = ACPI_PTR(ak_acpi_match), + }, + .probe = ak8975_probe, + .id_table = ak8975_id, +}; +module_i2c_driver(ak8975_driver); + +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_DESCRIPTION("AK8975 magnetometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c new file mode 100644 index 000000000..4f9c0be24 --- /dev/null +++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c @@ -0,0 +1,535 @@ +/* + * HID Sensors Driver + * Copyright (c) 2012, 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; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum magn_3d_channel { + CHANNEL_SCAN_INDEX_X, + CHANNEL_SCAN_INDEX_Y, + CHANNEL_SCAN_INDEX_Z, + CHANNEL_SCAN_INDEX_NORTH_MAGN_TILT_COMP, + CHANNEL_SCAN_INDEX_NORTH_TRUE_TILT_COMP, + CHANNEL_SCAN_INDEX_NORTH_MAGN, + CHANNEL_SCAN_INDEX_NORTH_TRUE, + MAGN_3D_CHANNEL_MAX, +}; + +struct magn_3d_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info magn[MAGN_3D_CHANNEL_MAX]; + + /* dynamically sized array to hold sensor values */ + u32 *iio_vals; + /* array of pointers to sensor value */ + u32 *magn_val_addr[MAGN_3D_CHANNEL_MAX]; + + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +static const u32 magn_3d_addresses[MAGN_3D_CHANNEL_MAX] = { + HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_X_AXIS, + HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Y_AXIS, + HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Z_AXIS, + HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH, + HID_USAGE_SENSOR_ORIENT_COMP_TRUE_NORTH, + HID_USAGE_SENSOR_ORIENT_MAGN_NORTH, + HID_USAGE_SENSOR_ORIENT_TRUE_NORTH, +}; + +/* Channel definitions */ +static const struct iio_chan_spec magn_3d_channels[] = { + { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_X, + .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), + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Y, + .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), + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Z, + .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), + }, { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_NORTH_MAGN_TILT_COMP, + .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), + }, { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_NORTH_TRUE_TILT_COMP, + .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), + }, { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_NORTH_MAGN, + .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), + }, { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_NORTH_TRUE, + .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), + } +}; + +/* Adjust channel real bits based on report descriptor */ +static void magn_3d_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 u32 */ + channels[channel].scan_type.storagebits = sizeof(u32) * 8; +} + +/* Channel read_raw handler */ +static int magn_3d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct magn_3d_state *magn_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + + *val = 0; + *val2 = 0; + switch (mask) { + case 0: + hid_sensor_power_state(&magn_state->common_attributes, true); + report_id = + magn_state->magn[chan->address].report_id; + address = magn_3d_addresses[chan->address]; + if (report_id >= 0) + *val = sensor_hub_input_attr_get_raw_value( + magn_state->common_attributes.hsdev, + HID_USAGE_SENSOR_COMPASS_3D, address, + report_id, + SENSOR_HUB_SYNC); + else { + *val = 0; + hid_sensor_power_state(&magn_state->common_attributes, + false); + return -EINVAL; + } + hid_sensor_power_state(&magn_state->common_attributes, false); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = magn_state->scale_pre_decml; + *val2 = magn_state->scale_post_decml; + ret_type = magn_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = magn_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &magn_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &magn_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int magn_3d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct magn_3d_state *magn_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &magn_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &magn_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info magn_3d_info = { + .driver_module = THIS_MODULE, + .read_raw = &magn_3d_read_raw, + .write_raw = &magn_3d_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data) +{ + dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); + iio_push_to_buffers(indio_dev, data); +} + +/* Callback handler to send event after all samples are received and captured */ +static int magn_3d_proc_event(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct magn_3d_state *magn_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "magn_3d_proc_event\n"); + if (atomic_read(&magn_state->common_attributes.data_ready)) + hid_sensor_push_data(indio_dev, magn_state->iio_vals); + + return 0; +} + +/* Capture samples in local storage */ +static int magn_3d_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct magn_3d_state *magn_state = iio_priv(indio_dev); + int offset; + int ret = 0; + u32 *iio_val = NULL; + + switch (usage_id) { + case HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_X_AXIS: + case HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Y_AXIS: + case HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_Z_AXIS: + offset = (usage_id - HID_USAGE_SENSOR_ORIENT_MAGN_FLUX_X_AXIS) + + CHANNEL_SCAN_INDEX_X; + break; + case HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH: + case HID_USAGE_SENSOR_ORIENT_COMP_TRUE_NORTH: + case HID_USAGE_SENSOR_ORIENT_MAGN_NORTH: + case HID_USAGE_SENSOR_ORIENT_TRUE_NORTH: + offset = (usage_id - HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH) + + CHANNEL_SCAN_INDEX_NORTH_MAGN_TILT_COMP; + break; + default: + return -EINVAL; + } + + iio_val = magn_state->magn_val_addr[offset]; + + if (iio_val != NULL) + *iio_val = *((u32 *)raw_data); + else + ret = -EINVAL; + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int magn_3d_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec **channels, + int *chan_count, + unsigned usage_id, + struct magn_3d_state *st) +{ + int i; + int attr_count = 0; + struct iio_chan_spec *_channels; + + /* Scan for each usage attribute supported */ + for (i = 0; i < MAGN_3D_CHANNEL_MAX; i++) { + int status; + u32 address = magn_3d_addresses[i]; + + /* Check if usage attribute exists in the sensor hub device */ + status = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + address, + &(st->magn[i])); + if (!status) + attr_count++; + } + + if (attr_count <= 0) { + dev_err(&pdev->dev, + "failed to find any supported usage attributes in report\n"); + return -EINVAL; + } + + dev_dbg(&pdev->dev, "magn_3d Found %d usage attributes\n", + attr_count); + dev_dbg(&pdev->dev, "magn_3d X: %x:%x Y: %x:%x Z: %x:%x\n", + st->magn[0].index, + st->magn[0].report_id, + st->magn[1].index, st->magn[1].report_id, + st->magn[2].index, st->magn[2].report_id); + + /* Setup IIO channel array */ + _channels = devm_kcalloc(&pdev->dev, attr_count, + sizeof(struct iio_chan_spec), + GFP_KERNEL); + if (!_channels) { + dev_err(&pdev->dev, + "failed to allocate space for iio channels\n"); + return -ENOMEM; + } + + st->iio_vals = devm_kcalloc(&pdev->dev, attr_count, + sizeof(u32), + GFP_KERNEL); + if (!st->iio_vals) { + dev_err(&pdev->dev, + "failed to allocate space for iio values array\n"); + return -ENOMEM; + } + + for (i = 0, *chan_count = 0; + i < MAGN_3D_CHANNEL_MAX && *chan_count < attr_count; + i++){ + if (st->magn[i].index >= 0) { + /* Setup IIO channel struct */ + (_channels[*chan_count]) = magn_3d_channels[i]; + (_channels[*chan_count]).scan_index = *chan_count; + (_channels[*chan_count]).address = i; + + /* Set magn_val_addr to iio value address */ + st->magn_val_addr[i] = &(st->iio_vals[*chan_count]); + magn_3d_adjust_channel_bit_mask(_channels, + *chan_count, + st->magn[i].size); + (*chan_count)++; + } + } + + if (*chan_count <= 0) { + dev_err(&pdev->dev, + "failed to find any magnetic channels setup\n"); + return -EINVAL; + } + + *channels = _channels; + + dev_dbg(&pdev->dev, "magn_3d Setup %d IIO channels\n", + *chan_count); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_COMPASS_3D, + &st->magn[CHANNEL_SCAN_INDEX_X], + &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_ORIENTATION, + &st->common_attributes.sensitivity); + dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", + st->common_attributes.sensitivity.index, + st->common_attributes.sensitivity.report_id); + } + + return 0; +} + +/* Function to initialize the processing for usage id */ +static int hid_magn_3d_probe(struct platform_device *pdev) +{ + int ret = 0; + static char *name = "magn_3d"; + struct iio_dev *indio_dev; + struct magn_3d_state *magn_state; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_chan_spec *channels; + int chan_count = 0; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct magn_3d_state)); + if (indio_dev == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + + magn_state = iio_priv(indio_dev); + magn_state->common_attributes.hsdev = hsdev; + magn_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_COMPASS_3D, + &magn_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + ret = magn_3d_parse_report(pdev, hsdev, + &channels, &chan_count, + HID_USAGE_SENSOR_COMPASS_3D, magn_state); + if (ret) { + dev_err(&pdev->dev, "failed to parse report\n"); + return ret; + } + + indio_dev->channels = channels; + indio_dev->num_channels = chan_count; + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &magn_3d_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to initialize trigger buffer\n"); + return ret; + } + atomic_set(&magn_state->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, name, + &magn_state->common_attributes); + if (ret < 0) { + dev_err(&pdev->dev, "trigger setup failed\n"); + goto error_unreg_buffer_funcs; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_trigger; + } + + magn_state->callbacks.send_event = magn_3d_proc_event; + magn_state->callbacks.capture_sample = magn_3d_capture_sample; + magn_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D, + &magn_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_iio_unreg; + } + + return ret; + +error_iio_unreg: + iio_device_unregister(indio_dev); +error_remove_trigger: + hid_sensor_remove_trigger(&magn_state->common_attributes); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_magn_3d_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct magn_3d_state *magn_state = iio_priv(indio_dev); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(&magn_state->common_attributes); + iio_triggered_buffer_cleanup(indio_dev); + + return 0; +} + +static struct platform_device_id hid_magn_3d_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200083", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_magn_3d_ids); + +static struct platform_driver hid_magn_3d_platform_driver = { + .id_table = hid_magn_3d_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_magn_3d_probe, + .remove = hid_magn_3d_remove, +}; +module_platform_driver(hid_magn_3d_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Magnetometer 3D"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/mag3110.c b/drivers/iio/magnetometer/mag3110.c new file mode 100644 index 000000000..261d51742 --- /dev/null +++ b/drivers/iio/magnetometer/mag3110.c @@ -0,0 +1,440 @@ +/* + * mag3110.c - Support for Freescale MAG3110 magnetometer sensor + * + * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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 0x0e) + * + * TODO: irq, user offset, oversampling, continuous mode + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/delay.h> + +#define MAG3110_STATUS 0x00 +#define MAG3110_OUT_X 0x01 /* MSB first */ +#define MAG3110_OUT_Y 0x03 +#define MAG3110_OUT_Z 0x05 +#define MAG3110_WHO_AM_I 0x07 +#define MAG3110_OFF_X 0x09 /* MSB first */ +#define MAG3110_OFF_Y 0x0b +#define MAG3110_OFF_Z 0x0d +#define MAG3110_DIE_TEMP 0x0f +#define MAG3110_CTRL_REG1 0x10 +#define MAG3110_CTRL_REG2 0x11 + +#define MAG3110_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0)) + +#define MAG3110_CTRL_DR_MASK (BIT(7) | BIT(6) | BIT(5)) +#define MAG3110_CTRL_DR_SHIFT 5 +#define MAG3110_CTRL_DR_DEFAULT 0 + +#define MAG3110_CTRL_TM BIT(1) /* trigger single measurement */ +#define MAG3110_CTRL_AC BIT(0) /* continuous measurements */ + +#define MAG3110_CTRL_AUTO_MRST_EN BIT(7) /* magnetic auto-reset */ +#define MAG3110_CTRL_RAW BIT(5) /* measurements not user-offset corrected */ + +#define MAG3110_DEVICE_ID 0xc4 + +/* Each client has this additional data */ +struct mag3110_data { + struct i2c_client *client; + struct mutex lock; + u8 ctrl_reg1; +}; + +static int mag3110_request(struct mag3110_data *data) +{ + int ret, tries = 150; + + /* trigger measurement */ + ret = i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1, + data->ctrl_reg1 | MAG3110_CTRL_TM); + if (ret < 0) + return ret; + + while (tries-- > 0) { + ret = i2c_smbus_read_byte_data(data->client, MAG3110_STATUS); + if (ret < 0) + return ret; + /* wait for data ready */ + if ((ret & MAG3110_STATUS_DRDY) == MAG3110_STATUS_DRDY) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int mag3110_read(struct mag3110_data *data, __be16 buf[3]) +{ + int ret; + + mutex_lock(&data->lock); + ret = mag3110_request(data); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + ret = i2c_smbus_read_i2c_block_data(data->client, + MAG3110_OUT_X, 3 * sizeof(__be16), (u8 *) buf); + mutex_unlock(&data->lock); + + return ret; +} + +static ssize_t mag3110_show_int_plus_micros(char *buf, + const int (*vals)[2], int n) +{ + size_t len = 0; + + while (n-- > 0) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%d.%06d ", vals[n][0], vals[n][1]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static int mag3110_get_int_plus_micros_index(const int (*vals)[2], int n, + int val, int val2) +{ + while (n-- > 0) + if (val == vals[n][0] && val2 == vals[n][1]) + return n; + + return -EINVAL; +} + +static const int mag3110_samp_freq[8][2] = { + {80, 0}, {40, 0}, {20, 0}, {10, 0}, {5, 0}, {2, 500000}, + {1, 250000}, {0, 625000} +}; + +static ssize_t mag3110_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return mag3110_show_int_plus_micros(buf, mag3110_samp_freq, 8); +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(mag3110_show_samp_freq_avail); + +static int mag3110_get_samp_freq_index(struct mag3110_data *data, + int val, int val2) +{ + return mag3110_get_int_plus_micros_index(mag3110_samp_freq, 8, val, + val2); +} + +static int mag3110_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mag3110_data *data = iio_priv(indio_dev); + __be16 buffer[3]; + int i, ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + switch (chan->type) { + case IIO_MAGN: /* in 0.1 uT / LSB */ + ret = mag3110_read(data, buffer); + if (ret < 0) + return ret; + *val = sign_extend32( + be16_to_cpu(buffer[chan->scan_index]), 15); + return IIO_VAL_INT; + case IIO_TEMP: /* in 1 C / LSB */ + mutex_lock(&data->lock); + ret = mag3110_request(data); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + ret = i2c_smbus_read_byte_data(data->client, + MAG3110_DIE_TEMP); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 7); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 1000; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + i = data->ctrl_reg1 >> MAG3110_CTRL_DR_SHIFT; + *val = mag3110_samp_freq[i][0]; + *val2 = mag3110_samp_freq[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + ret = i2c_smbus_read_word_swapped(data->client, + MAG3110_OFF_X + 2 * chan->scan_index); + if (ret < 0) + return ret; + *val = sign_extend32(ret >> 1, 14); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int mag3110_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mag3110_data *data = iio_priv(indio_dev); + int rate; + + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rate = mag3110_get_samp_freq_index(data, val, val2); + if (rate < 0) + return -EINVAL; + + data->ctrl_reg1 &= ~MAG3110_CTRL_DR_MASK; + data->ctrl_reg1 |= rate << MAG3110_CTRL_DR_SHIFT; + return i2c_smbus_write_byte_data(data->client, + MAG3110_CTRL_REG1, data->ctrl_reg1); + case IIO_CHAN_INFO_CALIBBIAS: + if (val < -10000 || val > 10000) + return -EINVAL; + return i2c_smbus_write_word_swapped(data->client, + MAG3110_OFF_X + 2 * chan->scan_index, val << 1); + default: + return -EINVAL; + } +} + +static irqreturn_t mag3110_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mag3110_data *data = iio_priv(indio_dev); + u8 buffer[16]; /* 3 16-bit channels + 1 byte temp + padding + ts */ + int ret; + + ret = mag3110_read(data, (__be16 *) buffer); + if (ret < 0) + goto done; + + if (test_bit(3, indio_dev->active_scan_mask)) { + ret = i2c_smbus_read_byte_data(data->client, + MAG3110_DIE_TEMP); + if (ret < 0) + goto done; + buffer[6] = ret; + } + + iio_push_to_buffers_with_timestamp(indio_dev, buffer, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +#define MAG3110_CHANNEL(axis, idx) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ +} + +static const struct iio_chan_spec mag3110_channels[] = { + MAG3110_CHANNEL(X, 0), + MAG3110_CHANNEL(Y, 1), + MAG3110_CHANNEL(Z, 2), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 3, + .scan_type = { + .sign = 's', + .realbits = 8, + .storagebits = 8, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static struct attribute *mag3110_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mag3110_group = { + .attrs = mag3110_attributes, +}; + +static const struct iio_info mag3110_info = { + .attrs = &mag3110_group, + .read_raw = &mag3110_read_raw, + .write_raw = &mag3110_write_raw, + .driver_module = THIS_MODULE, +}; + +static const unsigned long mag3110_scan_masks[] = {0x7, 0xf, 0}; + +static int mag3110_standby(struct mag3110_data *data) +{ + return i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1, + data->ctrl_reg1 & ~MAG3110_CTRL_AC); +} + +static int mag3110_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mag3110_data *data; + struct iio_dev *indio_dev; + int ret; + + ret = i2c_smbus_read_byte_data(client, MAG3110_WHO_AM_I); + if (ret < 0) + return ret; + if (ret != MAG3110_DEVICE_ID) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &mag3110_info; + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mag3110_channels; + indio_dev->num_channels = ARRAY_SIZE(mag3110_channels); + indio_dev->available_scan_masks = mag3110_scan_masks; + + data->ctrl_reg1 = MAG3110_CTRL_DR_DEFAULT << MAG3110_CTRL_DR_SHIFT; + ret = i2c_smbus_write_byte_data(client, MAG3110_CTRL_REG1, + data->ctrl_reg1); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, MAG3110_CTRL_REG2, + MAG3110_CTRL_AUTO_MRST_EN); + if (ret < 0) + goto standby_on_error; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + mag3110_trigger_handler, NULL); + if (ret < 0) + goto standby_on_error; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +standby_on_error: + mag3110_standby(iio_priv(indio_dev)); + return ret; +} + +static int mag3110_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + mag3110_standby(iio_priv(indio_dev)); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mag3110_suspend(struct device *dev) +{ + return mag3110_standby(iio_priv(i2c_get_clientdata( + to_i2c_client(dev)))); +} + +static int mag3110_resume(struct device *dev) +{ + struct mag3110_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return i2c_smbus_write_byte_data(data->client, MAG3110_CTRL_REG1, + data->ctrl_reg1); +} + +static SIMPLE_DEV_PM_OPS(mag3110_pm_ops, mag3110_suspend, mag3110_resume); +#define MAG3110_PM_OPS (&mag3110_pm_ops) +#else +#define MAG3110_PM_OPS NULL +#endif + +static const struct i2c_device_id mag3110_id[] = { + { "mag3110", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mag3110_id); + +static struct i2c_driver mag3110_driver = { + .driver = { + .name = "mag3110", + .pm = MAG3110_PM_OPS, + }, + .probe = mag3110_probe, + .remove = mag3110_remove, + .id_table = mag3110_id, +}; +module_i2c_driver(mag3110_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Freescale MAG3110 magnetometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/st_magn.h b/drivers/iio/magnetometer/st_magn.h new file mode 100644 index 000000000..7e81d00ef --- /dev/null +++ b/drivers/iio/magnetometer/st_magn.h @@ -0,0 +1,45 @@ +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * v. 1.0.0 + * Licensed under the GPL-2. + */ + +#ifndef ST_MAGN_H +#define ST_MAGN_H + +#include <linux/types.h> +#include <linux/iio/common/st_sensors.h> + +#define LSM303DLHC_MAGN_DEV_NAME "lsm303dlhc_magn" +#define LSM303DLM_MAGN_DEV_NAME "lsm303dlm_magn" +#define LIS3MDL_MAGN_DEV_NAME "lis3mdl" + +int st_magn_common_probe(struct iio_dev *indio_dev); +void st_magn_common_remove(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +int st_magn_allocate_ring(struct iio_dev *indio_dev); +void st_magn_deallocate_ring(struct iio_dev *indio_dev); +#else /* CONFIG_IIO_BUFFER */ +static inline int st_magn_probe_trigger(struct iio_dev *indio_dev, int irq) +{ + return 0; +} +static inline void st_magn_remove_trigger(struct iio_dev *indio_dev, int irq) +{ + return; +} +static inline int st_magn_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +static inline void st_magn_deallocate_ring(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_MAGN_H */ diff --git a/drivers/iio/magnetometer/st_magn_buffer.c b/drivers/iio/magnetometer/st_magn_buffer.c new file mode 100644 index 000000000..bf427dc0d --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_buffer.c @@ -0,0 +1,89 @@ +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_magn.h" + +static int st_magn_buffer_preenable(struct iio_dev *indio_dev) +{ + return st_sensors_set_enable(indio_dev, true); +} + +static int st_magn_buffer_postenable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *mdata = iio_priv(indio_dev); + + mdata->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (mdata->buffer_data == NULL) { + err = -ENOMEM; + goto allocate_memory_error; + } + + err = iio_triggered_buffer_postenable(indio_dev); + if (err < 0) + goto st_magn_buffer_postenable_error; + + return err; + +st_magn_buffer_postenable_error: + kfree(mdata->buffer_data); +allocate_memory_error: + return err; +} + +static int st_magn_buffer_predisable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *mdata = iio_priv(indio_dev); + + err = iio_triggered_buffer_predisable(indio_dev); + if (err < 0) + goto st_magn_buffer_predisable_error; + + err = st_sensors_set_enable(indio_dev, false); + +st_magn_buffer_predisable_error: + kfree(mdata->buffer_data); + return err; +} + +static const struct iio_buffer_setup_ops st_magn_buffer_setup_ops = { + .preenable = &st_magn_buffer_preenable, + .postenable = &st_magn_buffer_postenable, + .predisable = &st_magn_buffer_predisable, +}; + +int st_magn_allocate_ring(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &st_sensors_trigger_handler, &st_magn_buffer_setup_ops); +} + +void st_magn_deallocate_ring(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics magnetometers buffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/magnetometer/st_magn_core.c b/drivers/iio/magnetometer/st_magn_core.c new file mode 100644 index 000000000..2e56f812a --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_core.c @@ -0,0 +1,440 @@ +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_magn.h" + +#define ST_MAGN_NUMBER_DATA_CHANNELS 3 + +/* DEFAULT VALUE FOR SENSORS */ +#define ST_MAGN_DEFAULT_OUT_X_H_ADDR 0X03 +#define ST_MAGN_DEFAULT_OUT_Y_H_ADDR 0X07 +#define ST_MAGN_DEFAULT_OUT_Z_H_ADDR 0X05 + +/* FULLSCALE */ +#define ST_MAGN_FS_AVL_1300MG 1300 +#define ST_MAGN_FS_AVL_1900MG 1900 +#define ST_MAGN_FS_AVL_2500MG 2500 +#define ST_MAGN_FS_AVL_4000MG 4000 +#define ST_MAGN_FS_AVL_4700MG 4700 +#define ST_MAGN_FS_AVL_5600MG 5600 +#define ST_MAGN_FS_AVL_8000MG 8000 +#define ST_MAGN_FS_AVL_8100MG 8100 +#define ST_MAGN_FS_AVL_12000MG 12000 +#define ST_MAGN_FS_AVL_16000MG 16000 + +/* CUSTOM VALUES FOR SENSOR 1 */ +#define ST_MAGN_1_WAI_EXP 0x3c +#define ST_MAGN_1_ODR_ADDR 0x00 +#define ST_MAGN_1_ODR_MASK 0x1c +#define ST_MAGN_1_ODR_AVL_1HZ_VAL 0x00 +#define ST_MAGN_1_ODR_AVL_2HZ_VAL 0x01 +#define ST_MAGN_1_ODR_AVL_3HZ_VAL 0x02 +#define ST_MAGN_1_ODR_AVL_8HZ_VAL 0x03 +#define ST_MAGN_1_ODR_AVL_15HZ_VAL 0x04 +#define ST_MAGN_1_ODR_AVL_30HZ_VAL 0x05 +#define ST_MAGN_1_ODR_AVL_75HZ_VAL 0x06 +#define ST_MAGN_1_ODR_AVL_220HZ_VAL 0x07 +#define ST_MAGN_1_PW_ADDR 0x02 +#define ST_MAGN_1_PW_MASK 0x03 +#define ST_MAGN_1_PW_ON 0x00 +#define ST_MAGN_1_PW_OFF 0x03 +#define ST_MAGN_1_FS_ADDR 0x01 +#define ST_MAGN_1_FS_MASK 0xe0 +#define ST_MAGN_1_FS_AVL_1300_VAL 0x01 +#define ST_MAGN_1_FS_AVL_1900_VAL 0x02 +#define ST_MAGN_1_FS_AVL_2500_VAL 0x03 +#define ST_MAGN_1_FS_AVL_4000_VAL 0x04 +#define ST_MAGN_1_FS_AVL_4700_VAL 0x05 +#define ST_MAGN_1_FS_AVL_5600_VAL 0x06 +#define ST_MAGN_1_FS_AVL_8100_VAL 0x07 +#define ST_MAGN_1_FS_AVL_1300_GAIN_XY 909 +#define ST_MAGN_1_FS_AVL_1900_GAIN_XY 1169 +#define ST_MAGN_1_FS_AVL_2500_GAIN_XY 1492 +#define ST_MAGN_1_FS_AVL_4000_GAIN_XY 2222 +#define ST_MAGN_1_FS_AVL_4700_GAIN_XY 2500 +#define ST_MAGN_1_FS_AVL_5600_GAIN_XY 3030 +#define ST_MAGN_1_FS_AVL_8100_GAIN_XY 4347 +#define ST_MAGN_1_FS_AVL_1300_GAIN_Z 1020 +#define ST_MAGN_1_FS_AVL_1900_GAIN_Z 1315 +#define ST_MAGN_1_FS_AVL_2500_GAIN_Z 1666 +#define ST_MAGN_1_FS_AVL_4000_GAIN_Z 2500 +#define ST_MAGN_1_FS_AVL_4700_GAIN_Z 2816 +#define ST_MAGN_1_FS_AVL_5600_GAIN_Z 3389 +#define ST_MAGN_1_FS_AVL_8100_GAIN_Z 4878 +#define ST_MAGN_1_MULTIREAD_BIT false + +/* CUSTOM VALUES FOR SENSOR 2 */ +#define ST_MAGN_2_WAI_EXP 0x3d +#define ST_MAGN_2_ODR_ADDR 0x20 +#define ST_MAGN_2_ODR_MASK 0x1c +#define ST_MAGN_2_ODR_AVL_1HZ_VAL 0x00 +#define ST_MAGN_2_ODR_AVL_2HZ_VAL 0x01 +#define ST_MAGN_2_ODR_AVL_3HZ_VAL 0x02 +#define ST_MAGN_2_ODR_AVL_5HZ_VAL 0x03 +#define ST_MAGN_2_ODR_AVL_10HZ_VAL 0x04 +#define ST_MAGN_2_ODR_AVL_20HZ_VAL 0x05 +#define ST_MAGN_2_ODR_AVL_40HZ_VAL 0x06 +#define ST_MAGN_2_ODR_AVL_80HZ_VAL 0x07 +#define ST_MAGN_2_PW_ADDR 0x22 +#define ST_MAGN_2_PW_MASK 0x03 +#define ST_MAGN_2_PW_ON 0x00 +#define ST_MAGN_2_PW_OFF 0x03 +#define ST_MAGN_2_FS_ADDR 0x21 +#define ST_MAGN_2_FS_MASK 0x60 +#define ST_MAGN_2_FS_AVL_4000_VAL 0x00 +#define ST_MAGN_2_FS_AVL_8000_VAL 0x01 +#define ST_MAGN_2_FS_AVL_12000_VAL 0x02 +#define ST_MAGN_2_FS_AVL_16000_VAL 0x03 +#define ST_MAGN_2_FS_AVL_4000_GAIN 146 +#define ST_MAGN_2_FS_AVL_8000_GAIN 292 +#define ST_MAGN_2_FS_AVL_12000_GAIN 438 +#define ST_MAGN_2_FS_AVL_16000_GAIN 584 +#define ST_MAGN_2_MULTIREAD_BIT false +#define ST_MAGN_2_OUT_X_L_ADDR 0x28 +#define ST_MAGN_2_OUT_Y_L_ADDR 0x2a +#define ST_MAGN_2_OUT_Z_L_ADDR 0x2c + +static const struct iio_chan_spec st_magn_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_BE, 16, 16, + ST_MAGN_DEFAULT_OUT_X_H_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_BE, 16, 16, + ST_MAGN_DEFAULT_OUT_Y_H_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_BE, 16, 16, + ST_MAGN_DEFAULT_OUT_Z_H_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_magn_2_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_X, 1, IIO_MOD_X, 's', IIO_LE, 16, 16, + ST_MAGN_2_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Y, 1, IIO_MOD_Y, 's', IIO_LE, 16, 16, + ST_MAGN_2_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + ST_SENSORS_SCAN_Z, 1, IIO_MOD_Z, 's', IIO_LE, 16, 16, + ST_MAGN_2_OUT_Z_L_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct st_sensor_settings st_magn_sensors_settings[] = { + { + .wai = ST_MAGN_1_WAI_EXP, + .sensors_supported = { + [0] = LSM303DLHC_MAGN_DEV_NAME, + [1] = LSM303DLM_MAGN_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_magn_16bit_channels, + .odr = { + .addr = ST_MAGN_1_ODR_ADDR, + .mask = ST_MAGN_1_ODR_MASK, + .odr_avl = { + { 1, ST_MAGN_1_ODR_AVL_1HZ_VAL, }, + { 2, ST_MAGN_1_ODR_AVL_2HZ_VAL, }, + { 3, ST_MAGN_1_ODR_AVL_3HZ_VAL, }, + { 8, ST_MAGN_1_ODR_AVL_8HZ_VAL, }, + { 15, ST_MAGN_1_ODR_AVL_15HZ_VAL, }, + { 30, ST_MAGN_1_ODR_AVL_30HZ_VAL, }, + { 75, ST_MAGN_1_ODR_AVL_75HZ_VAL, }, + { 220, ST_MAGN_1_ODR_AVL_220HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_MAGN_1_PW_ADDR, + .mask = ST_MAGN_1_PW_MASK, + .value_on = ST_MAGN_1_PW_ON, + .value_off = ST_MAGN_1_PW_OFF, + }, + .fs = { + .addr = ST_MAGN_1_FS_ADDR, + .mask = ST_MAGN_1_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_MAGN_FS_AVL_1300MG, + .value = ST_MAGN_1_FS_AVL_1300_VAL, + .gain = ST_MAGN_1_FS_AVL_1300_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_1300_GAIN_Z, + }, + [1] = { + .num = ST_MAGN_FS_AVL_1900MG, + .value = ST_MAGN_1_FS_AVL_1900_VAL, + .gain = ST_MAGN_1_FS_AVL_1900_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_1900_GAIN_Z, + }, + [2] = { + .num = ST_MAGN_FS_AVL_2500MG, + .value = ST_MAGN_1_FS_AVL_2500_VAL, + .gain = ST_MAGN_1_FS_AVL_2500_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_2500_GAIN_Z, + }, + [3] = { + .num = ST_MAGN_FS_AVL_4000MG, + .value = ST_MAGN_1_FS_AVL_4000_VAL, + .gain = ST_MAGN_1_FS_AVL_4000_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_4000_GAIN_Z, + }, + [4] = { + .num = ST_MAGN_FS_AVL_4700MG, + .value = ST_MAGN_1_FS_AVL_4700_VAL, + .gain = ST_MAGN_1_FS_AVL_4700_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_4700_GAIN_Z, + }, + [5] = { + .num = ST_MAGN_FS_AVL_5600MG, + .value = ST_MAGN_1_FS_AVL_5600_VAL, + .gain = ST_MAGN_1_FS_AVL_5600_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_5600_GAIN_Z, + }, + [6] = { + .num = ST_MAGN_FS_AVL_8100MG, + .value = ST_MAGN_1_FS_AVL_8100_VAL, + .gain = ST_MAGN_1_FS_AVL_8100_GAIN_XY, + .gain2 = ST_MAGN_1_FS_AVL_8100_GAIN_Z, + }, + }, + }, + .multi_read_bit = ST_MAGN_1_MULTIREAD_BIT, + .bootime = 2, + }, + { + .wai = ST_MAGN_2_WAI_EXP, + .sensors_supported = { + [0] = LIS3MDL_MAGN_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_magn_2_16bit_channels, + .odr = { + .addr = ST_MAGN_2_ODR_ADDR, + .mask = ST_MAGN_2_ODR_MASK, + .odr_avl = { + { 1, ST_MAGN_2_ODR_AVL_1HZ_VAL, }, + { 2, ST_MAGN_2_ODR_AVL_2HZ_VAL, }, + { 3, ST_MAGN_2_ODR_AVL_3HZ_VAL, }, + { 5, ST_MAGN_2_ODR_AVL_5HZ_VAL, }, + { 10, ST_MAGN_2_ODR_AVL_10HZ_VAL, }, + { 20, ST_MAGN_2_ODR_AVL_20HZ_VAL, }, + { 40, ST_MAGN_2_ODR_AVL_40HZ_VAL, }, + { 80, ST_MAGN_2_ODR_AVL_80HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_MAGN_2_PW_ADDR, + .mask = ST_MAGN_2_PW_MASK, + .value_on = ST_MAGN_2_PW_ON, + .value_off = ST_MAGN_2_PW_OFF, + }, + .fs = { + .addr = ST_MAGN_2_FS_ADDR, + .mask = ST_MAGN_2_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_MAGN_FS_AVL_4000MG, + .value = ST_MAGN_2_FS_AVL_4000_VAL, + .gain = ST_MAGN_2_FS_AVL_4000_GAIN, + }, + [1] = { + .num = ST_MAGN_FS_AVL_8000MG, + .value = ST_MAGN_2_FS_AVL_8000_VAL, + .gain = ST_MAGN_2_FS_AVL_8000_GAIN, + }, + [2] = { + .num = ST_MAGN_FS_AVL_12000MG, + .value = ST_MAGN_2_FS_AVL_12000_VAL, + .gain = ST_MAGN_2_FS_AVL_12000_GAIN, + }, + [3] = { + .num = ST_MAGN_FS_AVL_16000MG, + .value = ST_MAGN_2_FS_AVL_16000_VAL, + .gain = ST_MAGN_2_FS_AVL_16000_GAIN, + }, + }, + }, + .multi_read_bit = ST_MAGN_2_MULTIREAD_BIT, + .bootime = 2, + }, +}; + +static int st_magn_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + struct st_sensor_data *mdata = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = st_sensors_read_info_raw(indio_dev, ch, val); + if (err < 0) + goto read_error; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if ((ch->scan_index == ST_SENSORS_SCAN_Z) && + (mdata->current_fullscale->gain2 != 0)) + *val2 = mdata->current_fullscale->gain2; + else + *val2 = mdata->current_fullscale->gain; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = mdata->odr; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static int st_magn_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + int err; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + err = st_sensors_set_fullscale_by_gain(indio_dev, val2); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + mutex_lock(&indio_dev->mlock); + err = st_sensors_set_odr(indio_dev, val); + mutex_unlock(&indio_dev->mlock); + return err; + default: + err = -EINVAL; + } + + return err; +} + +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); +static ST_SENSORS_DEV_ATTR_SCALE_AVAIL(in_magn_scale_available); + +static struct attribute *st_magn_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_magn_scale_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_magn_attribute_group = { + .attrs = st_magn_attributes, +}; + +static const struct iio_info magn_info = { + .driver_module = THIS_MODULE, + .attrs = &st_magn_attribute_group, + .read_raw = &st_magn_read_raw, + .write_raw = &st_magn_write_raw, +}; + +int st_magn_common_probe(struct iio_dev *indio_dev) +{ + struct st_sensor_data *mdata = iio_priv(indio_dev); + int irq = mdata->get_irq_data_ready(indio_dev); + int err; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &magn_info; + mutex_init(&mdata->tb.buf_lock); + + st_sensors_power_enable(indio_dev); + + err = st_sensors_check_device_support(indio_dev, + ARRAY_SIZE(st_magn_sensors_settings), + st_magn_sensors_settings); + if (err < 0) + return err; + + mdata->num_data_channels = ST_MAGN_NUMBER_DATA_CHANNELS; + mdata->multiread_bit = mdata->sensor_settings->multi_read_bit; + indio_dev->channels = mdata->sensor_settings->ch; + indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + + mdata->current_fullscale = (struct st_sensor_fullscale_avl *) + &mdata->sensor_settings->fs.fs_avl[0]; + mdata->odr = mdata->sensor_settings->odr.odr_avl[0].hz; + + err = st_sensors_init_sensor(indio_dev, NULL); + if (err < 0) + return err; + + err = st_magn_allocate_ring(indio_dev); + if (err < 0) + return err; + + if (irq > 0) { + err = st_sensors_allocate_trigger(indio_dev, NULL); + if (err < 0) + goto st_magn_probe_trigger_error; + } + + err = iio_device_register(indio_dev); + if (err) + goto st_magn_device_register_error; + + dev_info(&indio_dev->dev, "registered magnetometer %s\n", + indio_dev->name); + + return 0; + +st_magn_device_register_error: + if (irq > 0) + st_sensors_deallocate_trigger(indio_dev); +st_magn_probe_trigger_error: + st_magn_deallocate_ring(indio_dev); + + return err; +} +EXPORT_SYMBOL(st_magn_common_probe); + +void st_magn_common_remove(struct iio_dev *indio_dev) +{ + struct st_sensor_data *mdata = iio_priv(indio_dev); + + st_sensors_power_disable(indio_dev); + + iio_device_unregister(indio_dev); + if (mdata->get_irq_data_ready(indio_dev) > 0) + st_sensors_deallocate_trigger(indio_dev); + + st_magn_deallocate_ring(indio_dev); +} +EXPORT_SYMBOL(st_magn_common_remove); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics magnetometers driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/magnetometer/st_magn_i2c.c b/drivers/iio/magnetometer/st_magn_i2c.c new file mode 100644 index 000000000..92e5c1545 --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_i2c.c @@ -0,0 +1,95 @@ +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_i2c.h> +#include "st_magn.h" + +#ifdef CONFIG_OF +static const struct of_device_id st_magn_of_match[] = { + { + .compatible = "st,lsm303dlhc-magn", + .data = LSM303DLHC_MAGN_DEV_NAME, + }, + { + .compatible = "st,lsm303dlm-magn", + .data = LSM303DLM_MAGN_DEV_NAME, + }, + { + .compatible = "st,lis3mdl-magn", + .data = LIS3MDL_MAGN_DEV_NAME, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_magn_of_match); +#else +#define st_magn_of_match NULL +#endif + +static int st_magn_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *mdata; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mdata)); + if (!indio_dev) + return -ENOMEM; + + mdata = iio_priv(indio_dev); + st_sensors_of_i2c_probe(client, st_magn_of_match); + + st_sensors_i2c_configure(indio_dev, client, mdata); + + err = st_magn_common_probe(indio_dev); + if (err < 0) + return err; + + return 0; +} + +static int st_magn_i2c_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + st_magn_common_remove(indio_dev); + + return 0; +} + +static const struct i2c_device_id st_magn_id_table[] = { + { LSM303DLHC_MAGN_DEV_NAME }, + { LSM303DLM_MAGN_DEV_NAME }, + { LIS3MDL_MAGN_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, st_magn_id_table); + +static struct i2c_driver st_magn_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-magn-i2c", + .of_match_table = of_match_ptr(st_magn_of_match), + }, + .probe = st_magn_i2c_probe, + .remove = st_magn_i2c_remove, + .id_table = st_magn_id_table, +}; +module_i2c_driver(st_magn_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics magnetometers i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/magnetometer/st_magn_spi.c b/drivers/iio/magnetometer/st_magn_spi.c new file mode 100644 index 000000000..7adacf160 --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_spi.c @@ -0,0 +1,71 @@ +/* + * STMicroelectronics magnetometers driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_spi.h> +#include "st_magn.h" + +static int st_magn_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *mdata; + int err; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*mdata)); + if (!indio_dev) + return -ENOMEM; + + mdata = iio_priv(indio_dev); + + st_sensors_spi_configure(indio_dev, spi, mdata); + + err = st_magn_common_probe(indio_dev); + if (err < 0) + return err; + + return 0; +} + +static int st_magn_spi_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + st_magn_common_remove(indio_dev); + + return 0; +} + +static const struct spi_device_id st_magn_id_table[] = { + { LSM303DLHC_MAGN_DEV_NAME }, + { LSM303DLM_MAGN_DEV_NAME }, + { LIS3MDL_MAGN_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(spi, st_magn_id_table); + +static struct spi_driver st_magn_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-magn-spi", + }, + .probe = st_magn_spi_probe, + .remove = st_magn_spi_remove, + .id_table = st_magn_id_table, +}; +module_spi_driver(st_magn_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics magnetometers spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/orientation/Kconfig b/drivers/iio/orientation/Kconfig new file mode 100644 index 000000000..e3aa1e58d --- /dev/null +++ b/drivers/iio/orientation/Kconfig @@ -0,0 +1,31 @@ +# +# Inclinometer sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Inclinometer sensors" + +config HID_SENSOR_INCLINOMETER_3D + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID Inclinometer 3D" + help + Say yes here to build support for the HID SENSOR + Inclinometer 3D. + +config HID_SENSOR_DEVICE_ROTATION + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID Device Rotation" + help + Say yes here to build support for the HID SENSOR + device rotation. The output of a device rotation sensor + is presented using quaternion format. + +endmenu diff --git a/drivers/iio/orientation/Makefile b/drivers/iio/orientation/Makefile new file mode 100644 index 000000000..4734dabbd --- /dev/null +++ b/drivers/iio/orientation/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for industrial I/O Inclinometer sensor drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_HID_SENSOR_INCLINOMETER_3D) += hid-sensor-incl-3d.o +obj-$(CONFIG_HID_SENSOR_DEVICE_ROTATION) += hid-sensor-rotation.o diff --git a/drivers/iio/orientation/hid-sensor-incl-3d.c b/drivers/iio/orientation/hid-sensor-incl-3d.c new file mode 100644 index 000000000..5930fa32a --- /dev/null +++ b/drivers/iio/orientation/hid-sensor-incl-3d.c @@ -0,0 +1,442 @@ +/* + * HID Sensors Driver + * Copyright (c) 2013, 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; if not, write to the Free Software Foundation, Inc. + * + */ + +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +enum incl_3d_channel { + CHANNEL_SCAN_INDEX_X, + CHANNEL_SCAN_INDEX_Y, + CHANNEL_SCAN_INDEX_Z, + INCLI_3D_CHANNEL_MAX, +}; + +struct incl_3d_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info incl[INCLI_3D_CHANNEL_MAX]; + u32 incl_val[INCLI_3D_CHANNEL_MAX]; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +static const u32 incl_3d_addresses[INCLI_3D_CHANNEL_MAX] = { + HID_USAGE_SENSOR_ORIENT_TILT_X, + HID_USAGE_SENSOR_ORIENT_TILT_Y, + HID_USAGE_SENSOR_ORIENT_TILT_Z +}; + +/* Channel definitions */ +static const struct iio_chan_spec incl_3d_channels[] = { + { + .type = IIO_INCLI, + .modified = 1, + .channel2 = IIO_MOD_X, + .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), + .scan_index = CHANNEL_SCAN_INDEX_X, + }, { + .type = IIO_INCLI, + .modified = 1, + .channel2 = IIO_MOD_Y, + .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), + .scan_index = CHANNEL_SCAN_INDEX_Y, + }, { + .type = IIO_INCLI, + .modified = 1, + .channel2 = IIO_MOD_Z, + .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), + .scan_index = CHANNEL_SCAN_INDEX_Z, + } +}; + +/* Adjust channel real bits based on report descriptor */ +static void incl_3d_adjust_channel_bit_mask(struct iio_chan_spec *chan, + int size) +{ + chan->scan_type.sign = 's'; + /* Real storage bits will change based on the report desc. */ + chan->scan_type.realbits = size * 8; + /* Maximum size of a sample to capture is u32 */ + chan->scan_type.storagebits = sizeof(u32) * 8; +} + +/* Channel read_raw handler */ +static int incl_3d_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct incl_3d_state *incl_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + hid_sensor_power_state(&incl_state->common_attributes, true); + report_id = + incl_state->incl[chan->scan_index].report_id; + address = incl_3d_addresses[chan->scan_index]; + if (report_id >= 0) + *val = sensor_hub_input_attr_get_raw_value( + incl_state->common_attributes.hsdev, + HID_USAGE_SENSOR_INCLINOMETER_3D, address, + report_id, + SENSOR_HUB_SYNC); + else { + hid_sensor_power_state(&incl_state->common_attributes, + false); + return -EINVAL; + } + hid_sensor_power_state(&incl_state->common_attributes, false); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = incl_state->scale_pre_decml; + *val2 = incl_state->scale_post_decml; + ret_type = incl_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = incl_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &incl_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &incl_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int incl_3d_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct incl_3d_state *incl_state = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &incl_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &incl_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info incl_3d_info = { + .driver_module = THIS_MODULE, + .read_raw = &incl_3d_read_raw, + .write_raw = &incl_3d_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, u8 *data, int len) +{ + dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); + iio_push_to_buffers(indio_dev, (u8 *)data); +} + +/* Callback handler to send event after all samples are received and captured */ +static int incl_3d_proc_event(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct incl_3d_state *incl_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "incl_3d_proc_event\n"); + if (atomic_read(&incl_state->common_attributes.data_ready)) + hid_sensor_push_data(indio_dev, + (u8 *)incl_state->incl_val, + sizeof(incl_state->incl_val)); + + return 0; +} + +/* Capture samples in local storage */ +static int incl_3d_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct incl_3d_state *incl_state = iio_priv(indio_dev); + int ret = 0; + + switch (usage_id) { + case HID_USAGE_SENSOR_ORIENT_TILT_X: + incl_state->incl_val[CHANNEL_SCAN_INDEX_X] = *(u32 *)raw_data; + break; + case HID_USAGE_SENSOR_ORIENT_TILT_Y: + incl_state->incl_val[CHANNEL_SCAN_INDEX_Y] = *(u32 *)raw_data; + break; + case HID_USAGE_SENSOR_ORIENT_TILT_Z: + incl_state->incl_val[CHANNEL_SCAN_INDEX_Z] = *(u32 *)raw_data; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int incl_3d_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + unsigned usage_id, + struct incl_3d_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ORIENT_TILT_X, + &st->incl[CHANNEL_SCAN_INDEX_X]); + if (ret) + return ret; + incl_3d_adjust_channel_bit_mask(&channels[CHANNEL_SCAN_INDEX_X], + st->incl[CHANNEL_SCAN_INDEX_X].size); + + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ORIENT_TILT_Y, + &st->incl[CHANNEL_SCAN_INDEX_Y]); + if (ret) + return ret; + incl_3d_adjust_channel_bit_mask(&channels[CHANNEL_SCAN_INDEX_Y], + st->incl[CHANNEL_SCAN_INDEX_Y].size); + + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ORIENT_TILT_Z, + &st->incl[CHANNEL_SCAN_INDEX_Z]); + if (ret) + return ret; + incl_3d_adjust_channel_bit_mask(&channels[CHANNEL_SCAN_INDEX_Z], + st->incl[CHANNEL_SCAN_INDEX_Z].size); + + dev_dbg(&pdev->dev, "incl_3d %x:%x, %x:%x, %x:%x\n", + st->incl[0].index, + st->incl[0].report_id, + st->incl[1].index, st->incl[1].report_id, + st->incl[2].index, st->incl[2].report_id); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_INCLINOMETER_3D, + &st->incl[CHANNEL_SCAN_INDEX_X], + &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_ORIENTATION, + &st->common_attributes.sensitivity); + dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", + st->common_attributes.sensitivity.index, + st->common_attributes.sensitivity.report_id); + } + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_incl_3d_probe(struct platform_device *pdev) +{ + int ret; + static char *name = "incli_3d"; + struct iio_dev *indio_dev; + struct incl_3d_state *incl_state; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_chan_spec *channels; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct incl_3d_state)); + if (indio_dev == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + + incl_state = iio_priv(indio_dev); + incl_state->common_attributes.hsdev = hsdev; + incl_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_INCLINOMETER_3D, + &incl_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + channels = kmemdup(incl_3d_channels, sizeof(incl_3d_channels), + GFP_KERNEL); + if (!channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = incl_3d_parse_report(pdev, hsdev, channels, + HID_USAGE_SENSOR_INCLINOMETER_3D, incl_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + goto error_free_dev_mem; + } + + indio_dev->channels = channels; + indio_dev->num_channels = ARRAY_SIZE(incl_3d_channels); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &incl_3d_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to initialize trigger buffer\n"); + goto error_free_dev_mem; + } + atomic_set(&incl_state->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, name, + &incl_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "trigger setup failed\n"); + goto error_unreg_buffer_funcs; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_trigger; + } + + incl_state->callbacks.send_event = incl_3d_proc_event; + incl_state->callbacks.capture_sample = incl_3d_capture_sample; + incl_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, + HID_USAGE_SENSOR_INCLINOMETER_3D, + &incl_state->callbacks); + if (ret) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_iio_unreg; + } + + return 0; + +error_iio_unreg: + iio_device_unregister(indio_dev); +error_remove_trigger: + hid_sensor_remove_trigger(&incl_state->common_attributes); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: + kfree(indio_dev->channels); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_incl_3d_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct incl_3d_state *incl_state = iio_priv(indio_dev); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_INCLINOMETER_3D); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(&incl_state->common_attributes); + iio_triggered_buffer_cleanup(indio_dev); + kfree(indio_dev->channels); + + return 0; +} + +static struct platform_device_id hid_incl_3d_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200086", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_incl_3d_ids); + +static struct platform_driver hid_incl_3d_platform_driver = { + .id_table = hid_incl_3d_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_incl_3d_probe, + .remove = hid_incl_3d_remove, +}; +module_platform_driver(hid_incl_3d_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Inclinometer 3D"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/orientation/hid-sensor-rotation.c b/drivers/iio/orientation/hid-sensor-rotation.c new file mode 100644 index 000000000..4afb6c79c --- /dev/null +++ b/drivers/iio/orientation/hid-sensor-rotation.c @@ -0,0 +1,345 @@ +/* + * HID Sensors Driver + * Copyright (c) 2014, 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. + */ + +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +struct dev_rot_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info quaternion; + u32 sampled_vals[4]; +}; + +/* Channel definitions */ +static const struct iio_chan_spec dev_rot_channels[] = { + { + .type = IIO_ROT, + .modified = 1, + .channel2 = IIO_MOD_QUATERNION, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_HYSTERESIS) + } +}; + +/* Adjust channel real bits based on report descriptor */ +static void dev_rot_adjust_channel_bit_mask(struct iio_chan_spec *chan, + int size) +{ + chan->scan_type.sign = 's'; + /* Real storage bits will change based on the report desc. */ + chan->scan_type.realbits = size * 8; + /* Maximum size of a sample to capture is u32 */ + chan->scan_type.storagebits = sizeof(u32) * 8; + chan->scan_type.repeat = 4; +} + +/* Channel read_raw handler */ +static int dev_rot_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int size, int *vals, int *val_len, + long mask) +{ + struct dev_rot_state *rot_state = iio_priv(indio_dev); + int ret_type; + int i; + + vals[0] = 0; + vals[1] = 0; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (size >= 4) { + for (i = 0; i < 4; ++i) + vals[i] = rot_state->sampled_vals[i]; + ret_type = IIO_VAL_INT_MULTIPLE; + *val_len = 4; + } else + ret_type = -EINVAL; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &rot_state->common_attributes, &vals[0], &vals[1]); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &rot_state->common_attributes, &vals[0], &vals[1]); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int dev_rot_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct dev_rot_state *rot_state = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &rot_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &rot_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info dev_rot_info = { + .driver_module = THIS_MODULE, + .read_raw_multi = &dev_rot_read_raw, + .write_raw = &dev_rot_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, u8 *data, int len) +{ + dev_dbg(&indio_dev->dev, "hid_sensor_push_data >>\n"); + iio_push_to_buffers(indio_dev, (u8 *)data); + dev_dbg(&indio_dev->dev, "hid_sensor_push_data <<\n"); + +} + +/* Callback handler to send event after all samples are received and captured */ +static int dev_rot_proc_event(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct dev_rot_state *rot_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "dev_rot_proc_event\n"); + if (atomic_read(&rot_state->common_attributes.data_ready)) + hid_sensor_push_data(indio_dev, + (u8 *)rot_state->sampled_vals, + sizeof(rot_state->sampled_vals)); + + return 0; +} + +/* Capture samples in local storage */ +static int dev_rot_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct dev_rot_state *rot_state = iio_priv(indio_dev); + + if (usage_id == HID_USAGE_SENSOR_ORIENT_QUATERNION) { + memcpy(rot_state->sampled_vals, raw_data, + sizeof(rot_state->sampled_vals)); + dev_dbg(&indio_dev->dev, "Recd Quat len:%zu::%zu\n", raw_len, + sizeof(rot_state->sampled_vals)); + } + + return 0; +} + +/* Parse report which is specific to an usage id*/ +static int dev_rot_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + unsigned usage_id, + struct dev_rot_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, + HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ORIENT_QUATERNION, + &st->quaternion); + if (ret) + return ret; + + dev_rot_adjust_channel_bit_mask(&channels[0], + st->quaternion.size / 4); + + dev_dbg(&pdev->dev, "dev_rot %x:%x\n", st->quaternion.index, + st->quaternion.report_id); + + dev_dbg(&pdev->dev, "dev_rot: attrib size %d\n", + st->quaternion.size); + + /* 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_ORIENTATION, + &st->common_attributes.sensitivity); + dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", + st->common_attributes.sensitivity.index, + st->common_attributes.sensitivity.report_id); + } + + return 0; +} + +/* Function to initialize the processing for usage id */ +static int hid_dev_rot_probe(struct platform_device *pdev) +{ + int ret; + static char *name = "dev_rotation"; + struct iio_dev *indio_dev; + struct dev_rot_state *rot_state; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_chan_spec *channels; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct dev_rot_state)); + if (indio_dev == NULL) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + + rot_state = iio_priv(indio_dev); + rot_state->common_attributes.hsdev = hsdev; + rot_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_DEVICE_ORIENTATION, + &rot_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + channels = devm_kmemdup(&pdev->dev, dev_rot_channels, + sizeof(dev_rot_channels), GFP_KERNEL); + if (!channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = dev_rot_parse_report(pdev, hsdev, channels, + HID_USAGE_SENSOR_DEVICE_ORIENTATION, rot_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + return ret; + } + + indio_dev->channels = channels; + indio_dev->num_channels = ARRAY_SIZE(dev_rot_channels); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &dev_rot_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to initialize trigger buffer\n"); + return ret; + } + atomic_set(&rot_state->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, name, + &rot_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "trigger setup failed\n"); + goto error_unreg_buffer_funcs; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_trigger; + } + + rot_state->callbacks.send_event = dev_rot_proc_event; + rot_state->callbacks.capture_sample = dev_rot_capture_sample; + rot_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, + HID_USAGE_SENSOR_DEVICE_ORIENTATION, + &rot_state->callbacks); + if (ret) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_iio_unreg; + } + + return 0; + +error_iio_unreg: + iio_device_unregister(indio_dev); +error_remove_trigger: + hid_sensor_remove_trigger(&rot_state->common_attributes); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_dev_rot_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct dev_rot_state *rot_state = iio_priv(indio_dev); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_DEVICE_ORIENTATION); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(&rot_state->common_attributes); + iio_triggered_buffer_cleanup(indio_dev); + + return 0; +} + +static struct platform_device_id hid_dev_rot_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-20008a", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_dev_rot_ids); + +static struct platform_driver hid_dev_rot_platform_driver = { + .id_table = hid_dev_rot_ids, + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = hid_dev_rot_probe, + .remove = hid_dev_rot_remove, +}; +module_platform_driver(hid_dev_rot_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Device Rotation"); +MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig new file mode 100644 index 000000000..fa6295041 --- /dev/null +++ b/drivers/iio/pressure/Kconfig @@ -0,0 +1,121 @@ +# +# Pressure drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Pressure sensors" + +config BMP280 + tristate "Bosch Sensortec BMP280 pressure sensor driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for Bosch Sensortec BMP280 + pressure and temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called bmp280. + +config HID_SENSOR_PRESS + depends on HID_SENSOR_HUB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select HID_SENSOR_IIO_COMMON + select HID_SENSOR_IIO_TRIGGER + tristate "HID PRESS" + help + Say yes here to build support for the HID SENSOR + Pressure driver + + To compile this driver as a module, choose M here: the module + will be called hid-sensor-press. + +config MPL115 + tristate "Freescale MPL115A2 pressure sensor driver" + depends on I2C + help + Say yes here to build support for the Freescale MPL115A2 + pressure sensor connected via I2C. + + To compile this driver as a module, choose M here: the module + will be called mpl115. + +config MPL3115 + tristate "Freescale MPL3115A2 pressure sensor driver" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for the Freescale MPL3115A2 + pressure sensor / altimeter. + + To compile this driver as a module, choose M here: the module + will be called mpl3115. + +config MS5611 + tristate "Measurement Specialities MS5611 pressure sensor driver" + help + Say Y here to build support for the Measurement Specialities + MS5611 pressure and temperature sensor. + + To compile this driver as a module, choose M here: the module will + be called ms5611_core. + +config MS5611_I2C + tristate "support I2C bus connection" + depends on I2C && MS5611 + help + Say Y here to build I2C bus support for MS5611. + + To compile this driver as a module, choose M here: the module will + be called ms5611_i2c. + +config MS5611_SPI + tristate "support SPI bus connection" + depends on SPI_MASTER && MS5611 + help + Say Y here to build SPI bus support for MS5611. + + To compile this driver as a module, choose M here: the module will + be called ms5611_spi. + +config IIO_ST_PRESS + tristate "STMicroelectronics pressure sensor Driver" + depends on (I2C || SPI_MASTER) && SYSFS + select IIO_ST_SENSORS_CORE + select IIO_ST_PRESS_I2C if (I2C) + select IIO_ST_PRESS_SPI if (SPI_MASTER) + select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) + help + Say yes here to build support for STMicroelectronics pressure + sensors: LPS001WP, LPS25H, LPS331AP. + + This driver can also be built as a module. If so, these modules + will be created: + - st_pressure (core functions for the driver [it is mandatory]); + - st_pressure_i2c (necessary for the I2C devices [optional*]); + - st_pressure_spi (necessary for the SPI devices [optional*]); + + (*) one of these is necessary to do something. + +config IIO_ST_PRESS_I2C + tristate + depends on IIO_ST_PRESS + depends on IIO_ST_SENSORS_I2C + +config IIO_ST_PRESS_SPI + tristate + depends on IIO_ST_PRESS + depends on IIO_ST_SENSORS_SPI + +config T5403 + tristate "EPCOS T5403 digital barometric pressure sensor driver" + depends on I2C + help + Say yes here to build support for the EPCOS T5403 pressure sensor + connected via I2C. + + To compile this driver as a module, choose M here: the module + will be called t5403. + +endmenu diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile new file mode 100644 index 000000000..a4f98f8d9 --- /dev/null +++ b/drivers/iio/pressure/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for industrial I/O pressure drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_BMP280) += bmp280.o +obj-$(CONFIG_HID_SENSOR_PRESS) += hid-sensor-press.o +obj-$(CONFIG_MPL115) += mpl115.o +obj-$(CONFIG_MPL3115) += mpl3115.o +obj-$(CONFIG_MS5611) += ms5611_core.o +obj-$(CONFIG_MS5611_I2C) += ms5611_i2c.o +obj-$(CONFIG_MS5611_SPI) += ms5611_spi.o +obj-$(CONFIG_IIO_ST_PRESS) += st_pressure.o +st_pressure-y := st_pressure_core.o +st_pressure-$(CONFIG_IIO_BUFFER) += st_pressure_buffer.o +obj-$(CONFIG_T5403) += t5403.o + +obj-$(CONFIG_IIO_ST_PRESS_I2C) += st_pressure_i2c.o +obj-$(CONFIG_IIO_ST_PRESS_SPI) += st_pressure_spi.o diff --git a/drivers/iio/pressure/bmp280.c b/drivers/iio/pressure/bmp280.c new file mode 100644 index 000000000..a2602d8dd --- /dev/null +++ b/drivers/iio/pressure/bmp280.c @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2014 Intel Corporation + * + * Driver for Bosch Sensortec BMP280 digital pressure sensor. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) "bmp280: " fmt + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/acpi.h> +#include <linux/regmap.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define BMP280_REG_TEMP_XLSB 0xFC +#define BMP280_REG_TEMP_LSB 0xFB +#define BMP280_REG_TEMP_MSB 0xFA +#define BMP280_REG_PRESS_XLSB 0xF9 +#define BMP280_REG_PRESS_LSB 0xF8 +#define BMP280_REG_PRESS_MSB 0xF7 + +#define BMP280_REG_CONFIG 0xF5 +#define BMP280_REG_CTRL_MEAS 0xF4 +#define BMP280_REG_STATUS 0xF3 +#define BMP280_REG_RESET 0xE0 +#define BMP280_REG_ID 0xD0 + +#define BMP280_REG_COMP_TEMP_START 0x88 +#define BMP280_COMP_TEMP_REG_COUNT 6 + +#define BMP280_REG_COMP_PRESS_START 0x8E +#define BMP280_COMP_PRESS_REG_COUNT 18 + +#define BMP280_FILTER_MASK (BIT(4) | BIT(3) | BIT(2)) +#define BMP280_FILTER_OFF 0 +#define BMP280_FILTER_2X BIT(2) +#define BMP280_FILTER_4X BIT(3) +#define BMP280_FILTER_8X (BIT(3) | BIT(2)) +#define BMP280_FILTER_16X BIT(4) + +#define BMP280_OSRS_TEMP_MASK (BIT(7) | BIT(6) | BIT(5)) +#define BMP280_OSRS_TEMP_SKIP 0 +#define BMP280_OSRS_TEMP_1X BIT(5) +#define BMP280_OSRS_TEMP_2X BIT(6) +#define BMP280_OSRS_TEMP_4X (BIT(6) | BIT(5)) +#define BMP280_OSRS_TEMP_8X BIT(7) +#define BMP280_OSRS_TEMP_16X (BIT(7) | BIT(5)) + +#define BMP280_OSRS_PRESS_MASK (BIT(4) | BIT(3) | BIT(2)) +#define BMP280_OSRS_PRESS_SKIP 0 +#define BMP280_OSRS_PRESS_1X BIT(2) +#define BMP280_OSRS_PRESS_2X BIT(3) +#define BMP280_OSRS_PRESS_4X (BIT(3) | BIT(2)) +#define BMP280_OSRS_PRESS_8X BIT(4) +#define BMP280_OSRS_PRESS_16X (BIT(4) | BIT(2)) + +#define BMP280_MODE_MASK (BIT(1) | BIT(0)) +#define BMP280_MODE_SLEEP 0 +#define BMP280_MODE_FORCED BIT(0) +#define BMP280_MODE_NORMAL (BIT(1) | BIT(0)) + +#define BMP280_CHIP_ID 0x58 +#define BMP280_SOFT_RESET_VAL 0xB6 + +struct bmp280_data { + struct i2c_client *client; + struct mutex lock; + struct regmap *regmap; + + /* + * Carryover value from temperature conversion, used in pressure + * calculation. + */ + s32 t_fine; +}; + +/* + * These enums are used for indexing into the array of compensation + * parameters. + */ +enum { T1, T2, T3 }; +enum { P1, P2, P3, P4, P5, P6, P7, P8, P9 }; + +static const struct iio_chan_spec bmp280_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static bool bmp280_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_CONFIG: + case BMP280_REG_CTRL_MEAS: + case BMP280_REG_RESET: + return true; + default: + return false; + }; +} + +static bool bmp280_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_TEMP_XLSB: + case BMP280_REG_TEMP_LSB: + case BMP280_REG_TEMP_MSB: + case BMP280_REG_PRESS_XLSB: + case BMP280_REG_PRESS_LSB: + case BMP280_REG_PRESS_MSB: + case BMP280_REG_STATUS: + return true; + default: + return false; + } +} + +static const struct regmap_config bmp280_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMP280_REG_TEMP_XLSB, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmp280_is_writeable_reg, + .volatile_reg = bmp280_is_volatile_reg, +}; + +/* + * Returns temperature in DegC, resolution is 0.01 DegC. Output value of + * "5123" equals 51.23 DegC. t_fine carries fine temperature as global + * value. + * + * Taken from datasheet, Section 3.11.3, "Compensation formula". + */ +static s32 bmp280_compensate_temp(struct bmp280_data *data, + s32 adc_temp) +{ + int ret; + s32 var1, var2; + __le16 buf[BMP280_COMP_TEMP_REG_COUNT / 2]; + + ret = regmap_bulk_read(data->regmap, BMP280_REG_COMP_TEMP_START, + buf, BMP280_COMP_TEMP_REG_COUNT); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to read temperature calibration parameters\n"); + return ret; + } + + /* + * The double casts are necessary because le16_to_cpu returns an + * unsigned 16-bit value. Casting that value directly to a + * signed 32-bit will not do proper sign extension. + * + * Conversely, T1 and P1 are unsigned values, so they can be + * cast straight to the larger type. + */ + var1 = (((adc_temp >> 3) - ((s32)le16_to_cpu(buf[T1]) << 1)) * + ((s32)(s16)le16_to_cpu(buf[T2]))) >> 11; + var2 = (((((adc_temp >> 4) - ((s32)le16_to_cpu(buf[T1]))) * + ((adc_temp >> 4) - ((s32)le16_to_cpu(buf[T1])))) >> 12) * + ((s32)(s16)le16_to_cpu(buf[T3]))) >> 14; + data->t_fine = var1 + var2; + + return (data->t_fine * 5 + 128) >> 8; +} + +/* + * Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 + * integer bits and 8 fractional bits). Output value of "24674867" + * represents 24674867/256 = 96386.2 Pa = 963.862 hPa + * + * Taken from datasheet, Section 3.11.3, "Compensation formula". + */ +static u32 bmp280_compensate_press(struct bmp280_data *data, + s32 adc_press) +{ + int ret; + s64 var1, var2, p; + __le16 buf[BMP280_COMP_PRESS_REG_COUNT / 2]; + + ret = regmap_bulk_read(data->regmap, BMP280_REG_COMP_PRESS_START, + buf, BMP280_COMP_PRESS_REG_COUNT); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to read pressure calibration parameters\n"); + return ret; + } + + var1 = ((s64)data->t_fine) - 128000; + var2 = var1 * var1 * (s64)(s16)le16_to_cpu(buf[P6]); + var2 += (var1 * (s64)(s16)le16_to_cpu(buf[P5])) << 17; + var2 += ((s64)(s16)le16_to_cpu(buf[P4])) << 35; + var1 = ((var1 * var1 * (s64)(s16)le16_to_cpu(buf[P3])) >> 8) + + ((var1 * (s64)(s16)le16_to_cpu(buf[P2])) << 12); + var1 = ((((s64)1) << 47) + var1) * ((s64)le16_to_cpu(buf[P1])) >> 33; + + if (var1 == 0) + return 0; + + p = ((((s64)1048576 - adc_press) << 31) - var2) * 3125; + p = div64_s64(p, var1); + var1 = (((s64)(s16)le16_to_cpu(buf[P9])) * (p >> 13) * (p >> 13)) >> 25; + var2 = (((s64)(s16)le16_to_cpu(buf[P8])) * p) >> 19; + p = ((p + var1 + var2) >> 8) + (((s64)(s16)le16_to_cpu(buf[P7])) << 4); + + return (u32)p; +} + +static int bmp280_read_temp(struct bmp280_data *data, + int *val) +{ + int ret; + __be32 tmp = 0; + s32 adc_temp, comp_temp; + + ret = regmap_bulk_read(data->regmap, BMP280_REG_TEMP_MSB, + (u8 *) &tmp, 3); + if (ret < 0) { + dev_err(&data->client->dev, "failed to read temperature\n"); + return ret; + } + + adc_temp = be32_to_cpu(tmp) >> 12; + comp_temp = bmp280_compensate_temp(data, adc_temp); + + /* + * val might be NULL if we're called by the read_press routine, + * who only cares about the carry over t_fine value. + */ + if (val) { + *val = comp_temp * 10; + return IIO_VAL_INT; + } + + return 0; +} + +static int bmp280_read_press(struct bmp280_data *data, + int *val, int *val2) +{ + int ret; + __be32 tmp = 0; + s32 adc_press; + u32 comp_press; + + /* Read and compensate temperature so we get a reading of t_fine. */ + ret = bmp280_read_temp(data, NULL); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, BMP280_REG_PRESS_MSB, + (u8 *) &tmp, 3); + if (ret < 0) { + dev_err(&data->client->dev, "failed to read pressure\n"); + return ret; + } + + adc_press = be32_to_cpu(tmp) >> 12; + comp_press = bmp280_compensate_press(data, adc_press); + + *val = comp_press; + *val2 = 256000; + + return IIO_VAL_FRACTIONAL; +} + +static int bmp280_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct bmp280_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_PRESSURE: + ret = bmp280_read_press(data, val, val2); + break; + case IIO_TEMP: + ret = bmp280_read_temp(data, val); + break; + default: + ret = -EINVAL; + break; + } + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&data->lock); + + return ret; +} + +static const struct iio_info bmp280_info = { + .driver_module = THIS_MODULE, + .read_raw = &bmp280_read_raw, +}; + +static int bmp280_chip_init(struct bmp280_data *data) +{ + int ret; + + ret = regmap_update_bits(data->regmap, BMP280_REG_CTRL_MEAS, + BMP280_OSRS_TEMP_MASK | + BMP280_OSRS_PRESS_MASK | + BMP280_MODE_MASK, + BMP280_OSRS_TEMP_2X | + BMP280_OSRS_PRESS_16X | + BMP280_MODE_NORMAL); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to write ctrl_meas register\n"); + return ret; + } + + ret = regmap_update_bits(data->regmap, BMP280_REG_CONFIG, + BMP280_FILTER_MASK, + BMP280_FILTER_4X); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to write config register\n"); + return ret; + } + + return ret; +} + +static int bmp280_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct iio_dev *indio_dev; + struct bmp280_data *data; + unsigned int chip_id; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + mutex_init(&data->lock); + data->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->channels = bmp280_channels; + indio_dev->num_channels = ARRAY_SIZE(bmp280_channels); + indio_dev->info = &bmp280_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + data->regmap = devm_regmap_init_i2c(client, &bmp280_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "failed to allocate register map\n"); + return PTR_ERR(data->regmap); + } + + ret = regmap_read(data->regmap, BMP280_REG_ID, &chip_id); + if (ret < 0) + return ret; + if (chip_id != BMP280_CHIP_ID) { + dev_err(&client->dev, "bad chip id. expected %x got %x\n", + BMP280_CHIP_ID, chip_id); + return -EINVAL; + } + + ret = bmp280_chip_init(data); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct acpi_device_id bmp280_acpi_match[] = { + {"BMP0280", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, bmp280_acpi_match); + +static const struct i2c_device_id bmp280_id[] = { + {"bmp280", 0}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, bmp280_id); + +static struct i2c_driver bmp280_driver = { + .driver = { + .name = "bmp280", + .acpi_match_table = ACPI_PTR(bmp280_acpi_match), + }, + .probe = bmp280_probe, + .id_table = bmp280_id, +}; +module_i2c_driver(bmp280_driver); + +MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>"); +MODULE_DESCRIPTION("Driver for Bosch Sensortec BMP280 pressure and temperature sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/hid-sensor-press.c b/drivers/iio/pressure/hid-sensor-press.c new file mode 100644 index 000000000..3cf0bd67d --- /dev/null +++ b/drivers/iio/pressure/hid-sensor-press.c @@ -0,0 +1,385 @@ +/* + * HID Sensors Driver + * Copyright (c) 2014, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include "../common/hid-sensors/hid-sensor-trigger.h" + +#define CHANNEL_SCAN_INDEX_PRESSURE 0 + +struct press_state { + struct hid_sensor_hub_callbacks callbacks; + struct hid_sensor_common common_attributes; + struct hid_sensor_hub_attribute_info press_attr; + u32 press_data; + int scale_pre_decml; + int scale_post_decml; + int scale_precision; + int value_offset; +}; + +/* Channel definitions */ +static const struct iio_chan_spec press_channels[] = { + { + .type = IIO_PRESSURE, + .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), + .scan_index = CHANNEL_SCAN_INDEX_PRESSURE, + } +}; + +/* Adjust channel real bits based on report descriptor */ +static void press_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 u32 */ + channels[channel].scan_type.storagebits = sizeof(u32) * 8; +} + +/* Channel read_raw handler */ +static int press_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct press_state *press_state = iio_priv(indio_dev); + int report_id = -1; + u32 address; + int ret_type; + + *val = 0; + *val2 = 0; + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->scan_index) { + case CHANNEL_SCAN_INDEX_PRESSURE: + report_id = press_state->press_attr.report_id; + address = + HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE; + break; + default: + report_id = -1; + break; + } + if (report_id >= 0) { + hid_sensor_power_state(&press_state->common_attributes, + true); + *val = sensor_hub_input_attr_get_raw_value( + press_state->common_attributes.hsdev, + HID_USAGE_SENSOR_PRESSURE, address, + report_id, + SENSOR_HUB_SYNC); + hid_sensor_power_state(&press_state->common_attributes, + false); + } else { + *val = 0; + return -EINVAL; + } + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = press_state->scale_pre_decml; + *val2 = press_state->scale_post_decml; + ret_type = press_state->scale_precision; + break; + case IIO_CHAN_INFO_OFFSET: + *val = press_state->value_offset; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret_type = hid_sensor_read_samp_freq_value( + &press_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret_type = hid_sensor_read_raw_hyst_value( + &press_state->common_attributes, val, val2); + break; + default: + ret_type = -EINVAL; + break; + } + + return ret_type; +} + +/* Channel write_raw handler */ +static int press_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct press_state *press_state = iio_priv(indio_dev); + int ret = 0; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_write_samp_freq_value( + &press_state->common_attributes, val, val2); + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_write_raw_hyst_value( + &press_state->common_attributes, val, val2); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info press_info = { + .driver_module = THIS_MODULE, + .read_raw = &press_read_raw, + .write_raw = &press_write_raw, +}; + +/* Function to push data to buffer */ +static void hid_sensor_push_data(struct iio_dev *indio_dev, const void *data, + int len) +{ + dev_dbg(&indio_dev->dev, "hid_sensor_push_data\n"); + iio_push_to_buffers(indio_dev, data); +} + +/* Callback handler to send event after all samples are received and captured */ +static int press_proc_event(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct press_state *press_state = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "press_proc_event\n"); + if (atomic_read(&press_state->common_attributes.data_ready)) + hid_sensor_push_data(indio_dev, + &press_state->press_data, + sizeof(press_state->press_data)); + + return 0; +} + +/* Capture samples in local storage */ +static int press_capture_sample(struct hid_sensor_hub_device *hsdev, + unsigned usage_id, + size_t raw_len, char *raw_data, + void *priv) +{ + struct iio_dev *indio_dev = platform_get_drvdata(priv); + struct press_state *press_state = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (usage_id) { + case HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE: + press_state->press_data = *(u32 *)raw_data; + ret = 0; + break; + default: + break; + } + + return ret; +} + +/* Parse report which is specific to an usage id*/ +static int press_parse_report(struct platform_device *pdev, + struct hid_sensor_hub_device *hsdev, + struct iio_chan_spec *channels, + unsigned usage_id, + struct press_state *st) +{ + int ret; + + ret = sensor_hub_input_get_attribute_info(hsdev, HID_INPUT_REPORT, + usage_id, + HID_USAGE_SENSOR_ATMOSPHERIC_PRESSURE, + &st->press_attr); + if (ret < 0) + return ret; + press_adjust_channel_bit_mask(channels, CHANNEL_SCAN_INDEX_PRESSURE, + st->press_attr.size); + + dev_dbg(&pdev->dev, "press %x:%x\n", st->press_attr.index, + st->press_attr.report_id); + + st->scale_precision = hid_sensor_format_scale( + HID_USAGE_SENSOR_PRESSURE, + &st->press_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_ATMOSPHERIC_PRESSURE, + &st->common_attributes.sensitivity); + dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n", + st->common_attributes.sensitivity.index, + st->common_attributes.sensitivity.report_id); + } + return ret; +} + +/* Function to initialize the processing for usage id */ +static int hid_press_probe(struct platform_device *pdev) +{ + int ret = 0; + static const char *name = "press"; + struct iio_dev *indio_dev; + struct press_state *press_state; + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_chan_spec *channels; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct press_state)); + if (!indio_dev) + return -ENOMEM; + platform_set_drvdata(pdev, indio_dev); + + press_state = iio_priv(indio_dev); + press_state->common_attributes.hsdev = hsdev; + press_state->common_attributes.pdev = pdev; + + ret = hid_sensor_parse_common_attributes(hsdev, + HID_USAGE_SENSOR_PRESSURE, + &press_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "failed to setup common attributes\n"); + return ret; + } + + channels = kmemdup(press_channels, sizeof(press_channels), GFP_KERNEL); + if (!channels) { + dev_err(&pdev->dev, "failed to duplicate channels\n"); + return -ENOMEM; + } + + ret = press_parse_report(pdev, hsdev, channels, + HID_USAGE_SENSOR_PRESSURE, press_state); + if (ret) { + dev_err(&pdev->dev, "failed to setup attributes\n"); + goto error_free_dev_mem; + } + + indio_dev->channels = channels; + indio_dev->num_channels = + ARRAY_SIZE(press_channels); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &press_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + NULL, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to initialize trigger buffer\n"); + goto error_free_dev_mem; + } + atomic_set(&press_state->common_attributes.data_ready, 0); + ret = hid_sensor_setup_trigger(indio_dev, name, + &press_state->common_attributes); + if (ret) { + dev_err(&pdev->dev, "trigger setup failed\n"); + goto error_unreg_buffer_funcs; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "device register failed\n"); + goto error_remove_trigger; + } + + press_state->callbacks.send_event = press_proc_event; + press_state->callbacks.capture_sample = press_capture_sample; + press_state->callbacks.pdev = pdev; + ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_PRESSURE, + &press_state->callbacks); + if (ret < 0) { + dev_err(&pdev->dev, "callback reg failed\n"); + goto error_iio_unreg; + } + + return ret; + +error_iio_unreg: + iio_device_unregister(indio_dev); +error_remove_trigger: + hid_sensor_remove_trigger(&press_state->common_attributes); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: + kfree(indio_dev->channels); + return ret; +} + +/* Function to deinitialize the processing for usage id */ +static int hid_press_remove(struct platform_device *pdev) +{ + struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct press_state *press_state = iio_priv(indio_dev); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PRESSURE); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(&press_state->common_attributes); + iio_triggered_buffer_cleanup(indio_dev); + kfree(indio_dev->channels); + + return 0; +} + +static struct platform_device_id hid_press_ids[] = { + { + /* Format: HID-SENSOR-usage_id_in_hex_lowercase */ + .name = "HID-SENSOR-200031", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, hid_press_ids); + +static struct platform_driver hid_press_platform_driver = { + .id_table = hid_press_ids, + .driver = { + .name = KBUILD_MODNAME, + .pm = &hid_sensor_pm_ops, + }, + .probe = hid_press_probe, + .remove = hid_press_remove, +}; +module_platform_driver(hid_press_platform_driver); + +MODULE_DESCRIPTION("HID Sensor Pressure"); +MODULE_AUTHOR("Archana Patni <archana.patni@intel.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/mpl115.c b/drivers/iio/pressure/mpl115.c new file mode 100644 index 000000000..f5ecd6e19 --- /dev/null +++ b/drivers/iio/pressure/mpl115.c @@ -0,0 +1,211 @@ +/* + * mpl115.c - Support for Freescale MPL115A2 pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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 0x60) + * + * TODO: shutdown pin + * + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/delay.h> + +#define MPL115_PADC 0x00 /* pressure ADC output value, MSB first, 10 bit */ +#define MPL115_TADC 0x02 /* temperature ADC output value, MSB first, 10 bit */ +#define MPL115_A0 0x04 /* 12 bit integer, 3 bit fraction */ +#define MPL115_B1 0x06 /* 2 bit integer, 13 bit fraction */ +#define MPL115_B2 0x08 /* 1 bit integer, 14 bit fraction */ +#define MPL115_C12 0x0a /* 0 bit integer, 13 bit fraction */ +#define MPL115_CONVERT 0x12 /* convert temperature and pressure */ + +struct mpl115_data { + struct i2c_client *client; + struct mutex lock; + s16 a0; + s16 b1, b2; + s16 c12; +}; + +static int mpl115_request(struct mpl115_data *data) +{ + int ret = i2c_smbus_write_byte_data(data->client, MPL115_CONVERT, 0); + if (ret < 0) + return ret; + + usleep_range(3000, 4000); + + return 0; +} + +static int mpl115_comp_pressure(struct mpl115_data *data, int *val, int *val2) +{ + int ret; + u16 padc, tadc; + int a1, y1, pcomp; + unsigned kpa; + + mutex_lock(&data->lock); + ret = mpl115_request(data); + if (ret < 0) + goto done; + + ret = i2c_smbus_read_word_swapped(data->client, MPL115_PADC); + if (ret < 0) + goto done; + padc = ret >> 6; + + ret = i2c_smbus_read_word_swapped(data->client, MPL115_TADC); + if (ret < 0) + goto done; + tadc = ret >> 6; + + /* see Freescale AN3785 */ + a1 = data->b1 + ((data->c12 * tadc) >> 11); + y1 = (data->a0 << 10) + a1 * padc; + + /* compensated pressure with 4 fractional bits */ + pcomp = (y1 + ((data->b2 * (int) tadc) >> 1)) >> 9; + + kpa = pcomp * (115 - 50) / 1023 + (50 << 4); + *val = kpa >> 4; + *val2 = (kpa & 15) * (1000000 >> 4); +done: + mutex_unlock(&data->lock); + return ret; +} + +static int mpl115_read_temp(struct mpl115_data *data) +{ + int ret; + + mutex_lock(&data->lock); + ret = mpl115_request(data); + if (ret < 0) + goto done; + ret = i2c_smbus_read_word_swapped(data->client, MPL115_TADC); +done: + mutex_unlock(&data->lock); + return ret; +} + +static int mpl115_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mpl115_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = mpl115_comp_pressure(data, val, val2); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + /* temperature -5.35 C / LSB, 472 LSB is 25 C */ + ret = mpl115_read_temp(data); + if (ret < 0) + return ret; + *val = ret >> 6; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = 605; + *val2 = 750000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + *val = -186; + *val2 = 915888; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static const struct iio_chan_spec mpl115_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct iio_info mpl115_info = { + .read_raw = &mpl115_read_raw, + .driver_module = THIS_MODULE, +}; + +static int mpl115_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mpl115_data *data; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &mpl115_info; + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mpl115_channels; + indio_dev->num_channels = ARRAY_SIZE(mpl115_channels); + + ret = i2c_smbus_read_word_swapped(data->client, MPL115_A0); + if (ret < 0) + return ret; + data->a0 = ret; + ret = i2c_smbus_read_word_swapped(data->client, MPL115_B1); + if (ret < 0) + return ret; + data->b1 = ret; + ret = i2c_smbus_read_word_swapped(data->client, MPL115_B2); + if (ret < 0) + return ret; + data->b2 = ret; + ret = i2c_smbus_read_word_swapped(data->client, MPL115_C12); + if (ret < 0) + return ret; + data->c12 = ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id mpl115_id[] = { + { "mpl115", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpl115_id); + +static struct i2c_driver mpl115_driver = { + .driver = { + .name = "mpl115", + }, + .probe = mpl115_probe, + .id_table = mpl115_id, +}; +module_i2c_driver(mpl115_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Freescale MPL115 pressure/temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c new file mode 100644 index 000000000..01b2e0b18 --- /dev/null +++ b/drivers/iio/pressure/mpl3115.c @@ -0,0 +1,329 @@ +/* + * mpl3115.c - Support for Freescale MPL3115A2 pressure/temperature sensor + * + * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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 0x60) + * + * TODO: FIFO buffer, altimeter mode, oversampling, continuous mode, + * interrupts, user offset correction, raw mode + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/delay.h> + +#define MPL3115_STATUS 0x00 +#define MPL3115_OUT_PRESS 0x01 /* MSB first, 20 bit */ +#define MPL3115_OUT_TEMP 0x04 /* MSB first, 12 bit */ +#define MPL3115_WHO_AM_I 0x0c +#define MPL3115_CTRL_REG1 0x26 + +#define MPL3115_DEVICE_ID 0xc4 + +#define MPL3115_STATUS_PRESS_RDY BIT(2) +#define MPL3115_STATUS_TEMP_RDY BIT(1) + +#define MPL3115_CTRL_RESET BIT(2) /* software reset */ +#define MPL3115_CTRL_OST BIT(1) /* initiate measurement */ +#define MPL3115_CTRL_ACTIVE BIT(0) /* continuous measurement */ +#define MPL3115_CTRL_OS_258MS (BIT(5) | BIT(4)) /* 64x oversampling */ + +struct mpl3115_data { + struct i2c_client *client; + struct mutex lock; + u8 ctrl_reg1; +}; + +static int mpl3115_request(struct mpl3115_data *data) +{ + int ret, tries = 15; + + /* trigger measurement */ + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1 | MPL3115_CTRL_OST); + if (ret < 0) + return ret; + + while (tries-- > 0) { + ret = i2c_smbus_read_byte_data(data->client, MPL3115_CTRL_REG1); + if (ret < 0) + return ret; + /* wait for data ready, i.e. OST cleared */ + if (!(ret & MPL3115_CTRL_OST)) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(&data->client->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +static int mpl3115_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + __be32 tmp = 0; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + switch (chan->type) { + case IIO_PRESSURE: /* in 0.25 pascal / LSB */ + mutex_lock(&data->lock); + ret = mpl3115_request(data); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_PRESS, 3, (u8 *) &tmp); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = be32_to_cpu(tmp) >> 12; + return IIO_VAL_INT; + case IIO_TEMP: /* in 0.0625 celsius / LSB */ + mutex_lock(&data->lock); + ret = mpl3115_request(data); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_TEMP, 2, (u8 *) &tmp); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = sign_extend32(be32_to_cpu(tmp) >> 20, 11); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + *val = 0; + *val2 = 250; /* want kilopascal */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = 62500; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + } + return -EINVAL; +} + +static irqreturn_t mpl3115_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mpl3115_data *data = iio_priv(indio_dev); + u8 buffer[16]; /* 32-bit channel + 16-bit channel + padding + ts */ + int ret, pos = 0; + + mutex_lock(&data->lock); + ret = mpl3115_request(data); + if (ret < 0) { + mutex_unlock(&data->lock); + goto done; + } + + memset(buffer, 0, sizeof(buffer)); + if (test_bit(0, indio_dev->active_scan_mask)) { + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_PRESS, 3, &buffer[pos]); + if (ret < 0) { + mutex_unlock(&data->lock); + goto done; + } + pos += 4; + } + + if (test_bit(1, indio_dev->active_scan_mask)) { + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_TEMP, 2, &buffer[pos]); + if (ret < 0) { + mutex_unlock(&data->lock); + goto done; + } + } + mutex_unlock(&data->lock); + + iio_push_to_buffers_with_timestamp(indio_dev, buffer, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static const struct iio_chan_spec mpl3115_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 20, + .storagebits = 32, + .shift = 12, + .endianness = IIO_BE, + } + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .shift = 4, + .endianness = IIO_BE, + } + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +static const struct iio_info mpl3115_info = { + .read_raw = &mpl3115_read_raw, + .driver_module = THIS_MODULE, +}; + +static int mpl3115_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mpl3115_data *data; + struct iio_dev *indio_dev; + int ret; + + ret = i2c_smbus_read_byte_data(client, MPL3115_WHO_AM_I); + if (ret < 0) + return ret; + if (ret != MPL3115_DEVICE_ID) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &mpl3115_info; + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mpl3115_channels; + indio_dev->num_channels = ARRAY_SIZE(mpl3115_channels); + + /* software reset, I2C transfer is aborted (fails) */ + i2c_smbus_write_byte_data(client, MPL3115_CTRL_REG1, + MPL3115_CTRL_RESET); + msleep(50); + + data->ctrl_reg1 = MPL3115_CTRL_OS_258MS; + ret = i2c_smbus_write_byte_data(client, MPL3115_CTRL_REG1, + data->ctrl_reg1); + if (ret < 0) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + mpl3115_trigger_handler, NULL); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +static int mpl3115_standby(struct mpl3115_data *data) +{ + return i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1 & ~MPL3115_CTRL_ACTIVE); +} + +static int mpl3115_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + mpl3115_standby(iio_priv(indio_dev)); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mpl3115_suspend(struct device *dev) +{ + return mpl3115_standby(iio_priv(i2c_get_clientdata( + to_i2c_client(dev)))); +} + +static int mpl3115_resume(struct device *dev) +{ + struct mpl3115_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + + return i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1); +} + +static SIMPLE_DEV_PM_OPS(mpl3115_pm_ops, mpl3115_suspend, mpl3115_resume); +#define MPL3115_PM_OPS (&mpl3115_pm_ops) +#else +#define MPL3115_PM_OPS NULL +#endif + +static const struct i2c_device_id mpl3115_id[] = { + { "mpl3115", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpl3115_id); + +static struct i2c_driver mpl3115_driver = { + .driver = { + .name = "mpl3115", + .pm = MPL3115_PM_OPS, + }, + .probe = mpl3115_probe, + .remove = mpl3115_remove, + .id_table = mpl3115_id, +}; +module_i2c_driver(mpl3115_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("Freescale MPL3115 pressure/temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/ms5611.h b/drivers/iio/pressure/ms5611.h new file mode 100644 index 000000000..099c6cdea --- /dev/null +++ b/drivers/iio/pressure/ms5611.h @@ -0,0 +1,44 @@ +/* + * MS5611 pressure and temperature sensor driver + * + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _MS5611_H +#define _MS5611_H + +#include <linux/device.h> +#include <linux/iio/iio.h> +#include <linux/mutex.h> + +#define MS5611_RESET 0x1e +#define MS5611_READ_ADC 0x00 +#define MS5611_READ_PROM_WORD 0xA0 +#define MS5611_START_TEMP_CONV 0x58 +#define MS5611_START_PRESSURE_CONV 0x48 + +#define MS5611_CONV_TIME_MIN 9040 +#define MS5611_CONV_TIME_MAX 10000 + +#define MS5611_PROM_WORDS_NB 8 + +struct ms5611_state { + void *client; + struct mutex lock; + + int (*reset)(struct device *dev); + int (*read_prom_word)(struct device *dev, int index, u16 *word); + int (*read_adc_temp_and_pressure)(struct device *dev, + s32 *temp, s32 *pressure); + + u16 prom[MS5611_PROM_WORDS_NB]; +}; + +int ms5611_probe(struct iio_dev *indio_dev, struct device *dev); + +#endif /* _MS5611_H */ diff --git a/drivers/iio/pressure/ms5611_core.c b/drivers/iio/pressure/ms5611_core.c new file mode 100644 index 000000000..e42c8531d --- /dev/null +++ b/drivers/iio/pressure/ms5611_core.c @@ -0,0 +1,215 @@ +/* + * MS5611 pressure and temperature sensor driver + * + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Data sheet: + * http://www.meas-spec.com/downloads/MS5611-01BA03.pdf + * + */ + +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/delay.h> + +#include "ms5611.h" + +static bool ms5611_prom_is_valid(u16 *prom, size_t len) +{ + int i, j; + uint16_t crc = 0, crc_orig = prom[7] & 0x000F; + + prom[7] &= 0xFF00; + + for (i = 0; i < len * 2; i++) { + if (i % 2 == 1) + crc ^= prom[i >> 1] & 0x00FF; + else + crc ^= prom[i >> 1] >> 8; + + for (j = 0; j < 8; j++) { + if (crc & 0x8000) + crc = (crc << 1) ^ 0x3000; + else + crc <<= 1; + } + } + + crc = (crc >> 12) & 0x000F; + + return crc_orig != 0x0000 && crc == crc_orig; +} + +static int ms5611_read_prom(struct iio_dev *indio_dev) +{ + int ret, i; + struct ms5611_state *st = iio_priv(indio_dev); + + for (i = 0; i < MS5611_PROM_WORDS_NB; i++) { + ret = st->read_prom_word(&indio_dev->dev, i, &st->prom[i]); + if (ret < 0) { + dev_err(&indio_dev->dev, + "failed to read prom at %d\n", i); + return ret; + } + } + + if (!ms5611_prom_is_valid(st->prom, MS5611_PROM_WORDS_NB)) { + dev_err(&indio_dev->dev, "PROM integrity check failed\n"); + return -ENODEV; + } + + return 0; +} + +static int ms5611_read_temp_and_pressure(struct iio_dev *indio_dev, + s32 *temp, s32 *pressure) +{ + int ret; + s32 t, p; + s64 off, sens, dt; + struct ms5611_state *st = iio_priv(indio_dev); + + ret = st->read_adc_temp_and_pressure(&indio_dev->dev, &t, &p); + if (ret < 0) { + dev_err(&indio_dev->dev, + "failed to read temperature and pressure\n"); + return ret; + } + + dt = t - (st->prom[5] << 8); + off = ((s64)st->prom[2] << 16) + ((st->prom[4] * dt) >> 7); + sens = ((s64)st->prom[1] << 15) + ((st->prom[3] * dt) >> 8); + + t = 2000 + ((st->prom[6] * dt) >> 23); + if (t < 2000) { + s64 off2, sens2, t2; + + t2 = (dt * dt) >> 31; + off2 = (5 * (t - 2000) * (t - 2000)) >> 1; + sens2 = off2 >> 1; + + if (t < -1500) { + s64 tmp = (t + 1500) * (t + 1500); + + off2 += 7 * tmp; + sens2 += (11 * tmp) >> 1; + } + + t -= t2; + off -= off2; + sens -= sens2; + } + + *temp = t; + *pressure = (((p * sens) >> 21) - off) >> 15; + + return 0; +} + +static int ms5611_reset(struct iio_dev *indio_dev) +{ + int ret; + struct ms5611_state *st = iio_priv(indio_dev); + + ret = st->reset(&indio_dev->dev); + if (ret < 0) { + dev_err(&indio_dev->dev, "failed to reset device\n"); + return ret; + } + + usleep_range(3000, 4000); + + return 0; +} + +static int ms5611_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + s32 temp, pressure; + struct ms5611_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&st->lock); + ret = ms5611_read_temp_and_pressure(indio_dev, + &temp, &pressure); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + + switch (chan->type) { + case IIO_TEMP: + *val = temp * 10; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = pressure / 1000; + *val2 = (pressure % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static const struct iio_chan_spec ms5611_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE) + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE) + } +}; + +static const struct iio_info ms5611_info = { + .read_raw = &ms5611_read_raw, + .driver_module = THIS_MODULE, +}; + +static int ms5611_init(struct iio_dev *indio_dev) +{ + int ret; + + ret = ms5611_reset(indio_dev); + if (ret < 0) + return ret; + + return ms5611_read_prom(indio_dev); +} + +int ms5611_probe(struct iio_dev *indio_dev, struct device *dev) +{ + int ret; + struct ms5611_state *st = iio_priv(indio_dev); + + mutex_init(&st->lock); + indio_dev->dev.parent = dev; + indio_dev->name = dev->driver->name; + indio_dev->info = &ms5611_info; + indio_dev->channels = ms5611_channels; + indio_dev->num_channels = ARRAY_SIZE(ms5611_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ms5611_init(indio_dev); + if (ret < 0) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL(ms5611_probe); + +MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>"); +MODULE_DESCRIPTION("MS5611 core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/ms5611_i2c.c b/drivers/iio/pressure/ms5611_i2c.c new file mode 100644 index 000000000..748fd9aca --- /dev/null +++ b/drivers/iio/pressure/ms5611_i2c.c @@ -0,0 +1,128 @@ +/* + * MS5611 pressure and temperature sensor driver (I2C bus) + * + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 7-bit I2C slave addresses: + * + * 0x77 (CSB pin low) + * 0x76 (CSB pin high) + * + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> + +#include "ms5611.h" + +static int ms5611_i2c_reset(struct device *dev) +{ + struct ms5611_state *st = iio_priv(dev_to_iio_dev(dev)); + + return i2c_smbus_write_byte(st->client, MS5611_RESET); +} + +static int ms5611_i2c_read_prom_word(struct device *dev, int index, u16 *word) +{ + int ret; + struct ms5611_state *st = iio_priv(dev_to_iio_dev(dev)); + + ret = i2c_smbus_read_word_swapped(st->client, + MS5611_READ_PROM_WORD + (index << 1)); + if (ret < 0) + return ret; + + *word = ret; + + return 0; +} + +static int ms5611_i2c_read_adc(struct ms5611_state *st, s32 *val) +{ + int ret; + u8 buf[3]; + + ret = i2c_smbus_read_i2c_block_data(st->client, MS5611_READ_ADC, + 3, buf); + if (ret < 0) + return ret; + + *val = (buf[0] << 16) | (buf[1] << 8) | buf[2]; + + return 0; +} + +static int ms5611_i2c_read_adc_temp_and_pressure(struct device *dev, + s32 *temp, s32 *pressure) +{ + int ret; + struct ms5611_state *st = iio_priv(dev_to_iio_dev(dev)); + + ret = i2c_smbus_write_byte(st->client, MS5611_START_TEMP_CONV); + if (ret < 0) + return ret; + + usleep_range(MS5611_CONV_TIME_MIN, MS5611_CONV_TIME_MAX); + + ret = ms5611_i2c_read_adc(st, temp); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte(st->client, MS5611_START_PRESSURE_CONV); + if (ret < 0) + return ret; + + usleep_range(MS5611_CONV_TIME_MIN, MS5611_CONV_TIME_MAX); + + return ms5611_i2c_read_adc(st, pressure); +} + +static int ms5611_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ms5611_state *st; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_READ_WORD_DATA | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->reset = ms5611_i2c_reset; + st->read_prom_word = ms5611_i2c_read_prom_word; + st->read_adc_temp_and_pressure = ms5611_i2c_read_adc_temp_and_pressure; + st->client = client; + + return ms5611_probe(indio_dev, &client->dev); +} + +static const struct i2c_device_id ms5611_id[] = { + { "ms5611", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ms5611_id); + +static struct i2c_driver ms5611_driver = { + .driver = { + .name = "ms5611", + .owner = THIS_MODULE, + }, + .id_table = ms5611_id, + .probe = ms5611_i2c_probe, +}; +module_i2c_driver(ms5611_driver); + +MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>"); +MODULE_DESCRIPTION("MS5611 i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/ms5611_spi.c b/drivers/iio/pressure/ms5611_spi.c new file mode 100644 index 000000000..976726fd4 --- /dev/null +++ b/drivers/iio/pressure/ms5611_spi.c @@ -0,0 +1,127 @@ +/* + * MS5611 pressure and temperature sensor driver (SPI bus) + * + * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/spi/spi.h> + +#include "ms5611.h" + +static int ms5611_spi_reset(struct device *dev) +{ + u8 cmd = MS5611_RESET; + struct ms5611_state *st = iio_priv(dev_to_iio_dev(dev)); + + return spi_write_then_read(st->client, &cmd, 1, NULL, 0); +} + +static int ms5611_spi_read_prom_word(struct device *dev, int index, u16 *word) +{ + int ret; + struct ms5611_state *st = iio_priv(dev_to_iio_dev(dev)); + + ret = spi_w8r16be(st->client, MS5611_READ_PROM_WORD + (index << 1)); + if (ret < 0) + return ret; + + *word = ret; + + return 0; +} + +static int ms5611_spi_read_adc(struct device *dev, s32 *val) +{ + int ret; + u8 buf[3] = { MS5611_READ_ADC }; + struct ms5611_state *st = iio_priv(dev_to_iio_dev(dev)); + + ret = spi_write_then_read(st->client, buf, 1, buf, 3); + if (ret < 0) + return ret; + + *val = (buf[0] << 16) | (buf[1] << 8) | buf[2]; + + return 0; +} + +static int ms5611_spi_read_adc_temp_and_pressure(struct device *dev, + s32 *temp, s32 *pressure) +{ + u8 cmd; + int ret; + struct ms5611_state *st = iio_priv(dev_to_iio_dev(dev)); + + cmd = MS5611_START_TEMP_CONV; + ret = spi_write_then_read(st->client, &cmd, 1, NULL, 0); + if (ret < 0) + return ret; + + usleep_range(MS5611_CONV_TIME_MIN, MS5611_CONV_TIME_MAX); + + ret = ms5611_spi_read_adc(dev, temp); + if (ret < 0) + return ret; + + cmd = MS5611_START_PRESSURE_CONV; + ret = spi_write_then_read(st->client, &cmd, 1, NULL, 0); + if (ret < 0) + return ret; + + usleep_range(MS5611_CONV_TIME_MIN, MS5611_CONV_TIME_MAX); + + return ms5611_spi_read_adc(dev, pressure); +} + +static int ms5611_spi_probe(struct spi_device *spi) +{ + int ret; + struct ms5611_state *st; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + spi->mode = SPI_MODE_0; + spi->max_speed_hz = 20000000; + spi->bits_per_word = 8; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + st = iio_priv(indio_dev); + st->reset = ms5611_spi_reset; + st->read_prom_word = ms5611_spi_read_prom_word; + st->read_adc_temp_and_pressure = ms5611_spi_read_adc_temp_and_pressure; + st->client = spi; + + return ms5611_probe(indio_dev, &spi->dev); +} + +static const struct spi_device_id ms5611_id[] = { + { "ms5611", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ms5611_id); + +static struct spi_driver ms5611_driver = { + .driver = { + .name = "ms5611", + .owner = THIS_MODULE, + }, + .id_table = ms5611_id, + .probe = ms5611_spi_probe, +}; +module_spi_driver(ms5611_driver); + +MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>"); +MODULE_DESCRIPTION("MS5611 spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/st_pressure.h b/drivers/iio/pressure/st_pressure.h new file mode 100644 index 000000000..f5f414900 --- /dev/null +++ b/drivers/iio/pressure/st_pressure.h @@ -0,0 +1,49 @@ +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * v. 1.0.0 + * Licensed under the GPL-2. + */ + +#ifndef ST_PRESS_H +#define ST_PRESS_H + +#include <linux/types.h> +#include <linux/iio/common/st_sensors.h> + +#define LPS001WP_PRESS_DEV_NAME "lps001wp" +#define LPS25H_PRESS_DEV_NAME "lps25h" +#define LPS331AP_PRESS_DEV_NAME "lps331ap" + +/** + * struct st_sensors_platform_data - default press platform data + * @drdy_int_pin: default press DRDY is available on INT1 pin. + */ +static const struct st_sensors_platform_data default_press_pdata = { + .drdy_int_pin = 1, +}; + +int st_press_common_probe(struct iio_dev *indio_dev); +void st_press_common_remove(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +int st_press_allocate_ring(struct iio_dev *indio_dev); +void st_press_deallocate_ring(struct iio_dev *indio_dev); +int st_press_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_PRESS_TRIGGER_SET_STATE (&st_press_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_press_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void st_press_deallocate_ring(struct iio_dev *indio_dev) +{ +} +#define ST_PRESS_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_PRESS_H */ diff --git a/drivers/iio/pressure/st_pressure_buffer.c b/drivers/iio/pressure/st_pressure_buffer.c new file mode 100644 index 000000000..2ff53f222 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_buffer.c @@ -0,0 +1,96 @@ +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_pressure.h" + +int st_press_trig_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + + return st_sensors_set_dataready_irq(indio_dev, state); +} + +static int st_press_buffer_preenable(struct iio_dev *indio_dev) +{ + return st_sensors_set_enable(indio_dev, true); +} + +static int st_press_buffer_postenable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *press_data = iio_priv(indio_dev); + + press_data->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (press_data->buffer_data == NULL) { + err = -ENOMEM; + goto allocate_memory_error; + } + + err = iio_triggered_buffer_postenable(indio_dev); + if (err < 0) + goto st_press_buffer_postenable_error; + + return err; + +st_press_buffer_postenable_error: + kfree(press_data->buffer_data); +allocate_memory_error: + return err; +} + +static int st_press_buffer_predisable(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *press_data = iio_priv(indio_dev); + + err = iio_triggered_buffer_predisable(indio_dev); + if (err < 0) + goto st_press_buffer_predisable_error; + + err = st_sensors_set_enable(indio_dev, false); + +st_press_buffer_predisable_error: + kfree(press_data->buffer_data); + return err; +} + +static const struct iio_buffer_setup_ops st_press_buffer_setup_ops = { + .preenable = &st_press_buffer_preenable, + .postenable = &st_press_buffer_postenable, + .predisable = &st_press_buffer_predisable, +}; + +int st_press_allocate_ring(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &st_sensors_trigger_handler, &st_press_buffer_setup_ops); +} + +void st_press_deallocate_ring(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics pressures buffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/st_pressure_core.c b/drivers/iio/pressure/st_pressure_core.c new file mode 100644 index 000000000..e881fa629 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_core.c @@ -0,0 +1,498 @@ +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/buffer.h> +#include <asm/unaligned.h> + +#include <linux/iio/common/st_sensors.h> +#include "st_pressure.h" + +#define ST_PRESS_LSB_PER_MBAR 4096UL +#define ST_PRESS_KPASCAL_NANO_SCALE (100000000UL / \ + ST_PRESS_LSB_PER_MBAR) +#define ST_PRESS_LSB_PER_CELSIUS 480UL +#define ST_PRESS_CELSIUS_NANO_SCALE (1000000000UL / \ + ST_PRESS_LSB_PER_CELSIUS) +#define ST_PRESS_NUMBER_DATA_CHANNELS 1 + +/* FULLSCALE */ +#define ST_PRESS_FS_AVL_1260MB 1260 + +#define ST_PRESS_1_OUT_XL_ADDR 0x28 +#define ST_TEMP_1_OUT_L_ADDR 0x2b + +/* CUSTOM VALUES FOR LPS331AP SENSOR */ +#define ST_PRESS_LPS331AP_WAI_EXP 0xbb +#define ST_PRESS_LPS331AP_ODR_ADDR 0x20 +#define ST_PRESS_LPS331AP_ODR_MASK 0x70 +#define ST_PRESS_LPS331AP_ODR_AVL_1HZ_VAL 0x01 +#define ST_PRESS_LPS331AP_ODR_AVL_7HZ_VAL 0x05 +#define ST_PRESS_LPS331AP_ODR_AVL_13HZ_VAL 0x06 +#define ST_PRESS_LPS331AP_ODR_AVL_25HZ_VAL 0x07 +#define ST_PRESS_LPS331AP_PW_ADDR 0x20 +#define ST_PRESS_LPS331AP_PW_MASK 0x80 +#define ST_PRESS_LPS331AP_FS_ADDR 0x23 +#define ST_PRESS_LPS331AP_FS_MASK 0x30 +#define ST_PRESS_LPS331AP_FS_AVL_1260_VAL 0x00 +#define ST_PRESS_LPS331AP_FS_AVL_1260_GAIN ST_PRESS_KPASCAL_NANO_SCALE +#define ST_PRESS_LPS331AP_FS_AVL_TEMP_GAIN ST_PRESS_CELSIUS_NANO_SCALE +#define ST_PRESS_LPS331AP_BDU_ADDR 0x20 +#define ST_PRESS_LPS331AP_BDU_MASK 0x04 +#define ST_PRESS_LPS331AP_DRDY_IRQ_ADDR 0x22 +#define ST_PRESS_LPS331AP_DRDY_IRQ_INT1_MASK 0x04 +#define ST_PRESS_LPS331AP_DRDY_IRQ_INT2_MASK 0x20 +#define ST_PRESS_LPS331AP_MULTIREAD_BIT true +#define ST_PRESS_LPS331AP_TEMP_OFFSET 42500 + +/* CUSTOM VALUES FOR LPS001WP SENSOR */ +#define ST_PRESS_LPS001WP_WAI_EXP 0xba +#define ST_PRESS_LPS001WP_ODR_ADDR 0x20 +#define ST_PRESS_LPS001WP_ODR_MASK 0x30 +#define ST_PRESS_LPS001WP_ODR_AVL_1HZ_VAL 0x01 +#define ST_PRESS_LPS001WP_ODR_AVL_7HZ_VAL 0x02 +#define ST_PRESS_LPS001WP_ODR_AVL_13HZ_VAL 0x03 +#define ST_PRESS_LPS001WP_PW_ADDR 0x20 +#define ST_PRESS_LPS001WP_PW_MASK 0x40 +#define ST_PRESS_LPS001WP_BDU_ADDR 0x20 +#define ST_PRESS_LPS001WP_BDU_MASK 0x04 +#define ST_PRESS_LPS001WP_MULTIREAD_BIT true +#define ST_PRESS_LPS001WP_OUT_L_ADDR 0x28 +#define ST_TEMP_LPS001WP_OUT_L_ADDR 0x2a + +/* CUSTOM VALUES FOR LPS25H SENSOR */ +#define ST_PRESS_LPS25H_WAI_EXP 0xbd +#define ST_PRESS_LPS25H_ODR_ADDR 0x20 +#define ST_PRESS_LPS25H_ODR_MASK 0x70 +#define ST_PRESS_LPS25H_ODR_AVL_1HZ_VAL 0x01 +#define ST_PRESS_LPS25H_ODR_AVL_7HZ_VAL 0x02 +#define ST_PRESS_LPS25H_ODR_AVL_13HZ_VAL 0x03 +#define ST_PRESS_LPS25H_ODR_AVL_25HZ_VAL 0x04 +#define ST_PRESS_LPS25H_PW_ADDR 0x20 +#define ST_PRESS_LPS25H_PW_MASK 0x80 +#define ST_PRESS_LPS25H_FS_ADDR 0x00 +#define ST_PRESS_LPS25H_FS_MASK 0x00 +#define ST_PRESS_LPS25H_FS_AVL_1260_VAL 0x00 +#define ST_PRESS_LPS25H_FS_AVL_1260_GAIN ST_PRESS_KPASCAL_NANO_SCALE +#define ST_PRESS_LPS25H_FS_AVL_TEMP_GAIN ST_PRESS_CELSIUS_NANO_SCALE +#define ST_PRESS_LPS25H_BDU_ADDR 0x20 +#define ST_PRESS_LPS25H_BDU_MASK 0x04 +#define ST_PRESS_LPS25H_DRDY_IRQ_ADDR 0x23 +#define ST_PRESS_LPS25H_DRDY_IRQ_INT1_MASK 0x01 +#define ST_PRESS_LPS25H_DRDY_IRQ_INT2_MASK 0x10 +#define ST_PRESS_LPS25H_MULTIREAD_BIT true +#define ST_PRESS_LPS25H_TEMP_OFFSET 42500 +#define ST_PRESS_LPS25H_OUT_XL_ADDR 0x28 +#define ST_TEMP_LPS25H_OUT_L_ADDR 0x2b + +static const struct iio_chan_spec st_press_1_channels[] = { + { + .type = IIO_PRESSURE, + .channel2 = IIO_NO_MOD, + .address = ST_PRESS_1_OUT_XL_ADDR, + .scan_index = ST_SENSORS_SCAN_X, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 24, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .modified = 0, + }, + { + .type = IIO_TEMP, + .channel2 = IIO_NO_MOD, + .address = ST_TEMP_1_OUT_L_ADDR, + .scan_index = -1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .modified = 0, + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +static const struct iio_chan_spec st_press_lps001wp_channels[] = { + { + .type = IIO_PRESSURE, + .channel2 = IIO_NO_MOD, + .address = ST_PRESS_LPS001WP_OUT_L_ADDR, + .scan_index = ST_SENSORS_SCAN_X, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .modified = 0, + }, + { + .type = IIO_TEMP, + .channel2 = IIO_NO_MOD, + .address = ST_TEMP_LPS001WP_OUT_L_ADDR, + .scan_index = -1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET), + .modified = 0, + }, + IIO_CHAN_SOFT_TIMESTAMP(1) +}; + +static const struct st_sensor_settings st_press_sensors_settings[] = { + { + .wai = ST_PRESS_LPS331AP_WAI_EXP, + .sensors_supported = { + [0] = LPS331AP_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_1_channels, + .num_ch = ARRAY_SIZE(st_press_1_channels), + .odr = { + .addr = ST_PRESS_LPS331AP_ODR_ADDR, + .mask = ST_PRESS_LPS331AP_ODR_MASK, + .odr_avl = { + { 1, ST_PRESS_LPS331AP_ODR_AVL_1HZ_VAL, }, + { 7, ST_PRESS_LPS331AP_ODR_AVL_7HZ_VAL, }, + { 13, ST_PRESS_LPS331AP_ODR_AVL_13HZ_VAL, }, + { 25, ST_PRESS_LPS331AP_ODR_AVL_25HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_PRESS_LPS331AP_PW_ADDR, + .mask = ST_PRESS_LPS331AP_PW_MASK, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .addr = ST_PRESS_LPS331AP_FS_ADDR, + .mask = ST_PRESS_LPS331AP_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .value = ST_PRESS_LPS331AP_FS_AVL_1260_VAL, + .gain = ST_PRESS_LPS331AP_FS_AVL_1260_GAIN, + .gain2 = ST_PRESS_LPS331AP_FS_AVL_TEMP_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_PRESS_LPS331AP_BDU_ADDR, + .mask = ST_PRESS_LPS331AP_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_PRESS_LPS331AP_DRDY_IRQ_ADDR, + .mask_int1 = ST_PRESS_LPS331AP_DRDY_IRQ_INT1_MASK, + .mask_int2 = ST_PRESS_LPS331AP_DRDY_IRQ_INT2_MASK, + }, + .multi_read_bit = ST_PRESS_LPS331AP_MULTIREAD_BIT, + .bootime = 2, + }, + { + .wai = ST_PRESS_LPS001WP_WAI_EXP, + .sensors_supported = { + [0] = LPS001WP_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_lps001wp_channels, + .num_ch = ARRAY_SIZE(st_press_lps001wp_channels), + .odr = { + .addr = ST_PRESS_LPS001WP_ODR_ADDR, + .mask = ST_PRESS_LPS001WP_ODR_MASK, + .odr_avl = { + { 1, ST_PRESS_LPS001WP_ODR_AVL_1HZ_VAL, }, + { 7, ST_PRESS_LPS001WP_ODR_AVL_7HZ_VAL, }, + { 13, ST_PRESS_LPS001WP_ODR_AVL_13HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_PRESS_LPS001WP_PW_ADDR, + .mask = ST_PRESS_LPS001WP_PW_MASK, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .addr = 0, + }, + .bdu = { + .addr = ST_PRESS_LPS001WP_BDU_ADDR, + .mask = ST_PRESS_LPS001WP_BDU_MASK, + }, + .drdy_irq = { + .addr = 0, + }, + .multi_read_bit = ST_PRESS_LPS001WP_MULTIREAD_BIT, + .bootime = 2, + }, + { + .wai = ST_PRESS_LPS25H_WAI_EXP, + .sensors_supported = { + [0] = LPS25H_PRESS_DEV_NAME, + }, + .ch = (struct iio_chan_spec *)st_press_1_channels, + .num_ch = ARRAY_SIZE(st_press_1_channels), + .odr = { + .addr = ST_PRESS_LPS25H_ODR_ADDR, + .mask = ST_PRESS_LPS25H_ODR_MASK, + .odr_avl = { + { 1, ST_PRESS_LPS25H_ODR_AVL_1HZ_VAL, }, + { 7, ST_PRESS_LPS25H_ODR_AVL_7HZ_VAL, }, + { 13, ST_PRESS_LPS25H_ODR_AVL_13HZ_VAL, }, + { 25, ST_PRESS_LPS25H_ODR_AVL_25HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_PRESS_LPS25H_PW_ADDR, + .mask = ST_PRESS_LPS25H_PW_MASK, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .fs = { + .addr = ST_PRESS_LPS25H_FS_ADDR, + .mask = ST_PRESS_LPS25H_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_PRESS_FS_AVL_1260MB, + .value = ST_PRESS_LPS25H_FS_AVL_1260_VAL, + .gain = ST_PRESS_LPS25H_FS_AVL_1260_GAIN, + .gain2 = ST_PRESS_LPS25H_FS_AVL_TEMP_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_PRESS_LPS25H_BDU_ADDR, + .mask = ST_PRESS_LPS25H_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_PRESS_LPS25H_DRDY_IRQ_ADDR, + .mask_int1 = ST_PRESS_LPS25H_DRDY_IRQ_INT1_MASK, + .mask_int2 = ST_PRESS_LPS25H_DRDY_IRQ_INT2_MASK, + }, + .multi_read_bit = ST_PRESS_LPS25H_MULTIREAD_BIT, + .bootime = 2, + }, +}; + +static int st_press_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, + int val, + int val2, + long mask) +{ + int err; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val2) + return -EINVAL; + mutex_lock(&indio_dev->mlock); + err = st_sensors_set_odr(indio_dev, val); + mutex_unlock(&indio_dev->mlock); + return err; + default: + return -EINVAL; + } +} + +static int st_press_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + struct st_sensor_data *press_data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = st_sensors_read_info_raw(indio_dev, ch, val); + if (err < 0) + goto read_error; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + + switch (ch->type) { + case IIO_PRESSURE: + *val2 = press_data->current_fullscale->gain; + break; + case IIO_TEMP: + *val2 = press_data->current_fullscale->gain2; + break; + default: + err = -EINVAL; + goto read_error; + } + + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + switch (ch->type) { + case IIO_TEMP: + *val = 425; + *val2 = 10; + break; + default: + err = -EINVAL; + goto read_error; + } + + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = press_data->odr; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +read_error: + return err; +} + +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); + +static struct attribute *st_press_attributes[] = { + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group st_press_attribute_group = { + .attrs = st_press_attributes, +}; + +static const struct iio_info press_info = { + .driver_module = THIS_MODULE, + .attrs = &st_press_attribute_group, + .read_raw = &st_press_read_raw, + .write_raw = &st_press_write_raw, +}; + +#ifdef CONFIG_IIO_TRIGGER +static const struct iio_trigger_ops st_press_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = ST_PRESS_TRIGGER_SET_STATE, +}; +#define ST_PRESS_TRIGGER_OPS (&st_press_trigger_ops) +#else +#define ST_PRESS_TRIGGER_OPS NULL +#endif + +int st_press_common_probe(struct iio_dev *indio_dev) +{ + struct st_sensor_data *press_data = iio_priv(indio_dev); + int irq = press_data->get_irq_data_ready(indio_dev); + int err; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &press_info; + mutex_init(&press_data->tb.buf_lock); + + st_sensors_power_enable(indio_dev); + + err = st_sensors_check_device_support(indio_dev, + ARRAY_SIZE(st_press_sensors_settings), + st_press_sensors_settings); + if (err < 0) + return err; + + press_data->num_data_channels = ST_PRESS_NUMBER_DATA_CHANNELS; + press_data->multiread_bit = press_data->sensor_settings->multi_read_bit; + indio_dev->channels = press_data->sensor_settings->ch; + indio_dev->num_channels = press_data->sensor_settings->num_ch; + + if (press_data->sensor_settings->fs.addr != 0) + press_data->current_fullscale = + (struct st_sensor_fullscale_avl *) + &press_data->sensor_settings->fs.fs_avl[0]; + + press_data->odr = press_data->sensor_settings->odr.odr_avl[0].hz; + + /* Some devices don't support a data ready pin. */ + if (!press_data->dev->platform_data && + press_data->sensor_settings->drdy_irq.addr) + press_data->dev->platform_data = + (struct st_sensors_platform_data *)&default_press_pdata; + + err = st_sensors_init_sensor(indio_dev, press_data->dev->platform_data); + if (err < 0) + return err; + + err = st_press_allocate_ring(indio_dev); + if (err < 0) + return err; + + if (irq > 0) { + err = st_sensors_allocate_trigger(indio_dev, + ST_PRESS_TRIGGER_OPS); + if (err < 0) + goto st_press_probe_trigger_error; + } + + err = iio_device_register(indio_dev); + if (err) + goto st_press_device_register_error; + + dev_info(&indio_dev->dev, "registered pressure sensor %s\n", + indio_dev->name); + + return err; + +st_press_device_register_error: + if (irq > 0) + st_sensors_deallocate_trigger(indio_dev); +st_press_probe_trigger_error: + st_press_deallocate_ring(indio_dev); + + return err; +} +EXPORT_SYMBOL(st_press_common_probe); + +void st_press_common_remove(struct iio_dev *indio_dev) +{ + struct st_sensor_data *press_data = iio_priv(indio_dev); + + st_sensors_power_disable(indio_dev); + + iio_device_unregister(indio_dev); + if (press_data->get_irq_data_ready(indio_dev) > 0) + st_sensors_deallocate_trigger(indio_dev); + + st_press_deallocate_ring(indio_dev); +} +EXPORT_SYMBOL(st_press_common_remove); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics pressures driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/st_pressure_i2c.c b/drivers/iio/pressure/st_pressure_i2c.c new file mode 100644 index 000000000..137788bba --- /dev/null +++ b/drivers/iio/pressure/st_pressure_i2c.c @@ -0,0 +1,94 @@ +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_i2c.h> +#include "st_pressure.h" + +#ifdef CONFIG_OF +static const struct of_device_id st_press_of_match[] = { + { + .compatible = "st,lps001wp-press", + .data = LPS001WP_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps25h-press", + .data = LPS25H_PRESS_DEV_NAME, + }, + { + .compatible = "st,lps331ap-press", + .data = LPS331AP_PRESS_DEV_NAME, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_press_of_match); +#else +#define st_press_of_match NULL +#endif + +static int st_press_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *press_data; + int err; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*press_data)); + if (!indio_dev) + return -ENOMEM; + + press_data = iio_priv(indio_dev); + st_sensors_of_i2c_probe(client, st_press_of_match); + + st_sensors_i2c_configure(indio_dev, client, press_data); + + err = st_press_common_probe(indio_dev); + if (err < 0) + return err; + + return 0; +} + +static int st_press_i2c_remove(struct i2c_client *client) +{ + st_press_common_remove(i2c_get_clientdata(client)); + + return 0; +} + +static const struct i2c_device_id st_press_id_table[] = { + { LPS001WP_PRESS_DEV_NAME }, + { LPS25H_PRESS_DEV_NAME }, + { LPS331AP_PRESS_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, st_press_id_table); + +static struct i2c_driver st_press_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-press-i2c", + .of_match_table = of_match_ptr(st_press_of_match), + }, + .probe = st_press_i2c_probe, + .remove = st_press_i2c_remove, + .id_table = st_press_id_table, +}; +module_i2c_driver(st_press_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics pressures i2c driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/st_pressure_spi.c b/drivers/iio/pressure/st_pressure_spi.c new file mode 100644 index 000000000..1ffa6d4d3 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_spi.c @@ -0,0 +1,70 @@ +/* + * STMicroelectronics pressures driver + * + * Copyright 2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> + +#include <linux/iio/common/st_sensors.h> +#include <linux/iio/common/st_sensors_spi.h> +#include "st_pressure.h" + +static int st_press_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct st_sensor_data *press_data; + int err; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*press_data)); + if (indio_dev == NULL) + return -ENOMEM; + + press_data = iio_priv(indio_dev); + + st_sensors_spi_configure(indio_dev, spi, press_data); + + err = st_press_common_probe(indio_dev); + if (err < 0) + return err; + + return 0; +} + +static int st_press_spi_remove(struct spi_device *spi) +{ + st_press_common_remove(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id st_press_id_table[] = { + { LPS001WP_PRESS_DEV_NAME }, + { LPS25H_PRESS_DEV_NAME }, + { LPS331AP_PRESS_DEV_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(spi, st_press_id_table); + +static struct spi_driver st_press_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "st-press-spi", + }, + .probe = st_press_spi_probe, + .remove = st_press_spi_remove, + .id_table = st_press_id_table, +}; +module_spi_driver(st_press_driver); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics pressures spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/t5403.c b/drivers/iio/pressure/t5403.c new file mode 100644 index 000000000..e11cd3938 --- /dev/null +++ b/drivers/iio/pressure/t5403.c @@ -0,0 +1,275 @@ +/* + * t5403.c - Support for EPCOS T5403 pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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 0x77) + * + * TODO: end-of-conversion irq + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/delay.h> + +#define T5403_DATA 0xf5 /* data, LSB first, 16 bit */ +#define T5403_CALIB_DATA 0x8e /* 10 calibration coeff., LSB first, 16 bit */ +#define T5403_SLAVE_ADDR 0x88 /* I2C slave address, 0x77 */ +#define T5403_COMMAND 0xf1 + +/* command bits */ +#define T5403_MODE_SHIFT 3 /* conversion time: 2, 8, 16, 66 ms */ +#define T5403_PT BIT(1) /* 0 .. pressure, 1 .. temperature measurement */ +#define T5403_SCO BIT(0) /* start conversion */ + +#define T5403_MODE_LOW 0 +#define T5403_MODE_STANDARD 1 +#define T5403_MODE_HIGH 2 +#define T5403_MODE_ULTRA_HIGH 3 + +#define T5403_I2C_MASK (~BIT(7)) +#define T5403_I2C_ADDR 0x77 + +static const int t5403_pressure_conv_ms[] = {2, 8, 16, 66}; + +struct t5403_data { + struct i2c_client *client; + struct mutex lock; + int mode; + __le16 c[10]; +}; + +#define T5403_C_U16(i) le16_to_cpu(data->c[(i) - 1]) +#define T5403_C(i) sign_extend32(T5403_C_U16(i), 15) + +static int t5403_read(struct t5403_data *data, bool pressure) +{ + int wait_time = 3; /* wakeup time in ms */ + + int ret = i2c_smbus_write_byte_data(data->client, T5403_COMMAND, + (pressure ? (data->mode << T5403_MODE_SHIFT) : T5403_PT) | + T5403_SCO); + if (ret < 0) + return ret; + + wait_time += pressure ? t5403_pressure_conv_ms[data->mode] : 2; + + msleep(wait_time); + + return i2c_smbus_read_word_data(data->client, T5403_DATA); +} + +static int t5403_comp_pressure(struct t5403_data *data, int *val, int *val2) +{ + int ret; + s16 t_r; + u16 p_r; + s32 S, O, X; + + mutex_lock(&data->lock); + + ret = t5403_read(data, false); + if (ret < 0) + goto done; + t_r = ret; + + ret = t5403_read(data, true); + if (ret < 0) + goto done; + p_r = ret; + + /* see EPCOS application note */ + S = T5403_C_U16(3) + (s32) T5403_C_U16(4) * t_r / 0x20000 + + T5403_C(5) * t_r / 0x8000 * t_r / 0x80000 + + T5403_C(9) * t_r / 0x8000 * t_r / 0x8000 * t_r / 0x10000; + + O = T5403_C(6) * 0x4000 + T5403_C(7) * t_r / 8 + + T5403_C(8) * t_r / 0x8000 * t_r / 16 + + T5403_C(9) * t_r / 0x8000 * t_r / 0x10000 * t_r; + + X = (S * p_r + O) / 0x4000; + + X += ((X - 75000) * (X - 75000) / 0x10000 - 9537) * + T5403_C(10) / 0x10000; + + *val = X / 1000; + *val2 = (X % 1000) * 1000; + +done: + mutex_unlock(&data->lock); + return ret; +} + +static int t5403_comp_temp(struct t5403_data *data, int *val) +{ + int ret; + s16 t_r; + + mutex_lock(&data->lock); + ret = t5403_read(data, false); + if (ret < 0) + goto done; + t_r = ret; + + /* see EPCOS application note */ + *val = ((s32) T5403_C_U16(1) * t_r / 0x100 + + (s32) T5403_C_U16(2) * 0x40) * 1000 / 0x10000; + +done: + mutex_unlock(&data->lock); + return ret; +} + +static int t5403_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct t5403_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_PRESSURE: + ret = t5403_comp_pressure(data, val, val2); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + ret = t5403_comp_temp(data, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = t5403_pressure_conv_ms[data->mode] * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int t5403_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct t5403_data *data = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + if (val != 0) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(t5403_pressure_conv_ms); i++) + if (val2 == t5403_pressure_conv_ms[i] * 1000) { + mutex_lock(&data->lock); + data->mode = i; + mutex_unlock(&data->lock); + return 0; + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec t5403_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_INT_TIME), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, +}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.002 0.008 0.016 0.066"); + +static struct attribute *t5403_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group t5403_attribute_group = { + .attrs = t5403_attributes, +}; + +static const struct iio_info t5403_info = { + .read_raw = &t5403_read_raw, + .write_raw = &t5403_write_raw, + .attrs = &t5403_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int t5403_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct t5403_data *data; + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + ret = i2c_smbus_read_byte_data(client, T5403_SLAVE_ADDR); + if (ret < 0) + return ret; + if ((ret & T5403_I2C_MASK) != T5403_I2C_ADDR) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->lock); + + i2c_set_clientdata(client, indio_dev); + indio_dev->info = &t5403_info; + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = t5403_channels; + indio_dev->num_channels = ARRAY_SIZE(t5403_channels); + + data->mode = T5403_MODE_STANDARD; + + ret = i2c_smbus_read_i2c_block_data(data->client, T5403_CALIB_DATA, + sizeof(data->c), (u8 *) data->c); + if (ret < 0) + return ret; + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id t5403_id[] = { + { "t5403", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, t5403_id); + +static struct i2c_driver t5403_driver = { + .driver = { + .name = "t5403", + }, + .probe = t5403_probe, + .id_table = t5403_id, +}; +module_i2c_driver(t5403_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("EPCOS T5403 pressure/temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig new file mode 100644 index 000000000..41a8d8ffa --- /dev/null +++ b/drivers/iio/proximity/Kconfig @@ -0,0 +1,36 @@ +# +# Proximity sensors +# + +menu "Lightning sensors" + +config AS3935 + tristate "AS3935 Franklin lightning sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on SPI + help + Say Y here to build SPI interface support for the Austrian + Microsystems AS3935 lightning detection sensor. + + To compile this driver as a module, choose M here: the + module will be called as3935 + +endmenu + +menu "Proximity sensors" + +config SX9500 + tristate "SX9500 Semtech proximity sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select REGMAP_I2C + depends on I2C + help + Say Y here to build a driver for Semtech's SX9500 capacitive + proximity/button sensor. + + To compile this driver as a module, choose M here: the + module will be called sx9500. + +endmenu diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile new file mode 100644 index 000000000..9818dc562 --- /dev/null +++ b/drivers/iio/proximity/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for IIO proximity sensors +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AS3935) += as3935.o +obj-$(CONFIG_SX9500) += sx9500.o diff --git a/drivers/iio/proximity/as3935.c b/drivers/iio/proximity/as3935.c new file mode 100644 index 000000000..bc0d68efd --- /dev/null +++ b/drivers/iio/proximity/as3935.c @@ -0,0 +1,458 @@ +/* + * as3935.c - Support for AS3935 Franklin lightning sensor + * + * Copyright (C) 2014 Matt Ranostay <mranostay@gmail.com> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/spi/spi.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/buffer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/of_gpio.h> + + +#define AS3935_AFE_GAIN 0x00 +#define AS3935_AFE_MASK 0x3F +#define AS3935_AFE_GAIN_MAX 0x1F +#define AS3935_AFE_PWR_BIT BIT(0) + +#define AS3935_INT 0x03 +#define AS3935_INT_MASK 0x07 +#define AS3935_EVENT_INT BIT(3) +#define AS3935_NOISE_INT BIT(1) + +#define AS3935_DATA 0x07 +#define AS3935_DATA_MASK 0x3F + +#define AS3935_TUNE_CAP 0x08 +#define AS3935_CALIBRATE 0x3D + +#define AS3935_WRITE_DATA BIT(15) +#define AS3935_READ_DATA BIT(14) +#define AS3935_ADDRESS(x) ((x) << 8) + +#define MAX_PF_CAP 120 +#define TUNE_CAP_DIV 8 + +struct as3935_state { + struct spi_device *spi; + struct iio_trigger *trig; + struct mutex lock; + struct delayed_work work; + + u32 tune_cap; + u8 buf[2] ____cacheline_aligned; +}; + +static const struct iio_chan_spec as3935_channels[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 6, + .storagebits = 8, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static int as3935_read(struct as3935_state *st, unsigned int reg, int *val) +{ + u8 cmd; + int ret; + + cmd = (AS3935_READ_DATA | AS3935_ADDRESS(reg)) >> 8; + ret = spi_w8r8(st->spi, cmd); + if (ret < 0) + return ret; + *val = ret; + + return 0; +} + +static int as3935_write(struct as3935_state *st, + unsigned int reg, + unsigned int val) +{ + u8 *buf = st->buf; + + buf[0] = (AS3935_WRITE_DATA | AS3935_ADDRESS(reg)) >> 8; + buf[1] = val; + + return spi_write(st->spi, buf, 2); +} + +static ssize_t as3935_sensor_sensitivity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct as3935_state *st = iio_priv(dev_to_iio_dev(dev)); + int val, ret; + + ret = as3935_read(st, AS3935_AFE_GAIN, &val); + if (ret) + return ret; + val = (val & AS3935_AFE_MASK) >> 1; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t as3935_sensor_sensitivity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct as3935_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned long val; + int ret; + + ret = kstrtoul((const char *) buf, 10, &val); + if (ret) + return -EINVAL; + + if (val > AS3935_AFE_GAIN_MAX) + return -EINVAL; + + as3935_write(st, AS3935_AFE_GAIN, val << 1); + + return len; +} + +static IIO_DEVICE_ATTR(sensor_sensitivity, S_IRUGO | S_IWUSR, + as3935_sensor_sensitivity_show, as3935_sensor_sensitivity_store, 0); + + +static struct attribute *as3935_attributes[] = { + &iio_dev_attr_sensor_sensitivity.dev_attr.attr, + NULL, +}; + +static struct attribute_group as3935_attribute_group = { + .attrs = as3935_attributes, +}; + +static int as3935_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct as3935_state *st = iio_priv(indio_dev); + int ret; + + + switch (m) { + case IIO_CHAN_INFO_PROCESSED: + case IIO_CHAN_INFO_RAW: + *val2 = 0; + ret = as3935_read(st, AS3935_DATA, val); + if (ret) + return ret; + + if (m == IIO_CHAN_INFO_RAW) + return IIO_VAL_INT; + + /* storm out of range */ + if (*val == AS3935_DATA_MASK) + return -EINVAL; + *val *= 1000; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static const struct iio_info as3935_info = { + .driver_module = THIS_MODULE, + .attrs = &as3935_attribute_group, + .read_raw = &as3935_read_raw, +}; + +static irqreturn_t as3935_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct as3935_state *st = iio_priv(indio_dev); + int val, ret; + + ret = as3935_read(st, AS3935_DATA, &val); + if (ret) + goto err_read; + val &= AS3935_DATA_MASK; + val *= 1000; + + iio_push_to_buffers_with_timestamp(indio_dev, &val, pf->timestamp); +err_read: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops iio_interrupt_trigger_ops = { + .owner = THIS_MODULE, +}; + +static void as3935_event_work(struct work_struct *work) +{ + struct as3935_state *st; + int val; + + st = container_of(work, struct as3935_state, work.work); + + as3935_read(st, AS3935_INT, &val); + val &= AS3935_INT_MASK; + + switch (val) { + case AS3935_EVENT_INT: + iio_trigger_poll(st->trig); + break; + case AS3935_NOISE_INT: + dev_warn(&st->spi->dev, "noise level is too high"); + break; + } +} + +static irqreturn_t as3935_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct as3935_state *st = iio_priv(indio_dev); + + /* + * Delay work for >2 milliseconds after an interrupt to allow + * estimated distance to recalculated. + */ + + schedule_delayed_work(&st->work, msecs_to_jiffies(3)); + + return IRQ_HANDLED; +} + +static void calibrate_as3935(struct as3935_state *st) +{ + mutex_lock(&st->lock); + + /* mask disturber interrupt bit */ + as3935_write(st, AS3935_INT, BIT(5)); + + as3935_write(st, AS3935_CALIBRATE, 0x96); + as3935_write(st, AS3935_TUNE_CAP, + BIT(5) | (st->tune_cap / TUNE_CAP_DIV)); + + mdelay(2); + as3935_write(st, AS3935_TUNE_CAP, (st->tune_cap / TUNE_CAP_DIV)); + + mutex_unlock(&st->lock); +} + +#ifdef CONFIG_PM_SLEEP +static int as3935_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct as3935_state *st = iio_priv(indio_dev); + int val, ret; + + mutex_lock(&st->lock); + ret = as3935_read(st, AS3935_AFE_GAIN, &val); + if (ret) + goto err_suspend; + val |= AS3935_AFE_PWR_BIT; + + ret = as3935_write(st, AS3935_AFE_GAIN, val); + +err_suspend: + mutex_unlock(&st->lock); + + return ret; +} + +static int as3935_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct as3935_state *st = iio_priv(indio_dev); + int val, ret; + + mutex_lock(&st->lock); + ret = as3935_read(st, AS3935_AFE_GAIN, &val); + if (ret) + goto err_resume; + val &= ~AS3935_AFE_PWR_BIT; + ret = as3935_write(st, AS3935_AFE_GAIN, val); + +err_resume: + mutex_unlock(&st->lock); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(as3935_pm_ops, as3935_suspend, as3935_resume); +#define AS3935_PM_OPS (&as3935_pm_ops) + +#else +#define AS3935_PM_OPS NULL +#endif + +static int as3935_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct iio_trigger *trig; + struct as3935_state *st; + struct device_node *np = spi->dev.of_node; + int ret; + + /* Be sure lightning event interrupt is specified */ + if (!spi->irq) { + dev_err(&spi->dev, "unable to get event interrupt\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + st->tune_cap = 0; + + spi_set_drvdata(spi, indio_dev); + mutex_init(&st->lock); + INIT_DELAYED_WORK(&st->work, as3935_event_work); + + ret = of_property_read_u32(np, + "ams,tuning-capacitor-pf", &st->tune_cap); + if (ret) { + st->tune_cap = 0; + dev_warn(&spi->dev, + "no tuning-capacitor-pf set, defaulting to %d", + st->tune_cap); + } + + if (st->tune_cap > MAX_PF_CAP) { + dev_err(&spi->dev, + "wrong tuning-capacitor-pf setting of %d\n", + st->tune_cap); + return -EINVAL; + } + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = as3935_channels; + indio_dev->num_channels = ARRAY_SIZE(as3935_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &as3935_info; + + trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d", + indio_dev->name, indio_dev->id); + + if (!trig) + return -ENOMEM; + + st->trig = trig; + trig->dev.parent = indio_dev->dev.parent; + iio_trigger_set_drvdata(trig, indio_dev); + trig->ops = &iio_interrupt_trigger_ops; + + ret = iio_trigger_register(trig); + if (ret) { + dev_err(&spi->dev, "failed to register trigger\n"); + return ret; + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + &as3935_trigger_handler, NULL); + + if (ret) { + dev_err(&spi->dev, "cannot setup iio trigger\n"); + goto unregister_trigger; + } + + calibrate_as3935(st); + + ret = devm_request_irq(&spi->dev, spi->irq, + &as3935_interrupt_handler, + IRQF_TRIGGER_RISING, + dev_name(&spi->dev), + indio_dev); + + if (ret) { + dev_err(&spi->dev, "unable to request irq\n"); + goto unregister_buffer; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&spi->dev, "unable to register device\n"); + goto unregister_buffer; + } + return 0; + +unregister_buffer: + iio_triggered_buffer_cleanup(indio_dev); + +unregister_trigger: + iio_trigger_unregister(st->trig); + + return ret; +} + +static int as3935_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct as3935_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + iio_trigger_unregister(st->trig); + + return 0; +} + +static const struct spi_device_id as3935_id[] = { + {"as3935", 0}, + {}, +}; +MODULE_DEVICE_TABLE(spi, as3935_id); + +static struct spi_driver as3935_driver = { + .driver = { + .name = "as3935", + .owner = THIS_MODULE, + .pm = AS3935_PM_OPS, + }, + .probe = as3935_probe, + .remove = as3935_remove, + .id_table = as3935_id, +}; +module_spi_driver(as3935_driver); + +MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>"); +MODULE_DESCRIPTION("AS3935 lightning sensor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:as3935"); diff --git a/drivers/iio/proximity/sx9500.c b/drivers/iio/proximity/sx9500.c new file mode 100644 index 000000000..bd26a484a --- /dev/null +++ b/drivers/iio/proximity/sx9500.c @@ -0,0 +1,748 @@ +/* + * Copyright (c) 2014 Intel Corporation + * + * Driver for Semtech's SX9500 capacitive proximity/button solution. + * Datasheet available at + * <http://www.semtech.com/images/datasheet/sx9500.pdf>. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/acpi.h> +#include <linux/gpio/consumer.h> +#include <linux/regmap.h> + +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/trigger.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +#define SX9500_DRIVER_NAME "sx9500" +#define SX9500_IRQ_NAME "sx9500_event" +#define SX9500_GPIO_NAME "sx9500_gpio" + +/* Register definitions. */ +#define SX9500_REG_IRQ_SRC 0x00 +#define SX9500_REG_STAT 0x01 +#define SX9500_REG_IRQ_MSK 0x03 + +#define SX9500_REG_PROX_CTRL0 0x06 +#define SX9500_REG_PROX_CTRL1 0x07 +#define SX9500_REG_PROX_CTRL2 0x08 +#define SX9500_REG_PROX_CTRL3 0x09 +#define SX9500_REG_PROX_CTRL4 0x0a +#define SX9500_REG_PROX_CTRL5 0x0b +#define SX9500_REG_PROX_CTRL6 0x0c +#define SX9500_REG_PROX_CTRL7 0x0d +#define SX9500_REG_PROX_CTRL8 0x0e + +#define SX9500_REG_SENSOR_SEL 0x20 +#define SX9500_REG_USE_MSB 0x21 +#define SX9500_REG_USE_LSB 0x22 +#define SX9500_REG_AVG_MSB 0x23 +#define SX9500_REG_AVG_LSB 0x24 +#define SX9500_REG_DIFF_MSB 0x25 +#define SX9500_REG_DIFF_LSB 0x26 +#define SX9500_REG_OFFSET_MSB 0x27 +#define SX9500_REG_OFFSET_LSB 0x28 + +#define SX9500_REG_RESET 0x7f + +/* Write this to REG_RESET to do a soft reset. */ +#define SX9500_SOFT_RESET 0xde + +#define SX9500_SCAN_PERIOD_MASK GENMASK(6, 4) +#define SX9500_SCAN_PERIOD_SHIFT 4 + +/* + * These serve for identifying IRQ source in the IRQ_SRC register, and + * also for masking the IRQs in the IRQ_MSK register. + */ +#define SX9500_CLOSE_IRQ BIT(6) +#define SX9500_FAR_IRQ BIT(5) +#define SX9500_CONVDONE_IRQ BIT(3) + +#define SX9500_PROXSTAT_SHIFT 4 + +#define SX9500_NUM_CHANNELS 4 + +struct sx9500_data { + struct mutex mutex; + struct i2c_client *client; + struct iio_trigger *trig; + struct regmap *regmap; + /* + * Last reading of the proximity status for each channel. We + * only send an event to user space when this changes. + */ + bool prox_stat[SX9500_NUM_CHANNELS]; + bool event_enabled[SX9500_NUM_CHANNELS]; + bool trigger_enabled; + u16 *buffer; +}; + +static const struct iio_event_spec sx9500_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define SX9500_CHANNEL(idx) \ + { \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .channel = idx, \ + .event_spec = sx9500_events, \ + .num_event_specs = ARRAY_SIZE(sx9500_events), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0, \ + }, \ + } + +static const struct iio_chan_spec sx9500_channels[] = { + SX9500_CHANNEL(0), + SX9500_CHANNEL(1), + SX9500_CHANNEL(2), + SX9500_CHANNEL(3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct { + int val; + int val2; +} sx9500_samp_freq_table[] = { + {33, 333333}, + {16, 666666}, + {11, 111111}, + {8, 333333}, + {6, 666666}, + {5, 0}, + {3, 333333}, + {2, 500000}, +}; + +static const struct regmap_range sx9500_writable_reg_ranges[] = { + regmap_reg_range(SX9500_REG_IRQ_MSK, SX9500_REG_IRQ_MSK), + regmap_reg_range(SX9500_REG_PROX_CTRL0, SX9500_REG_PROX_CTRL8), + regmap_reg_range(SX9500_REG_SENSOR_SEL, SX9500_REG_SENSOR_SEL), + regmap_reg_range(SX9500_REG_OFFSET_MSB, SX9500_REG_OFFSET_LSB), + regmap_reg_range(SX9500_REG_RESET, SX9500_REG_RESET), +}; + +static const struct regmap_access_table sx9500_writeable_regs = { + .yes_ranges = sx9500_writable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9500_writable_reg_ranges), +}; + +/* + * All allocated registers are readable, so we just list unallocated + * ones. + */ +static const struct regmap_range sx9500_non_readable_reg_ranges[] = { + regmap_reg_range(SX9500_REG_STAT + 1, SX9500_REG_STAT + 1), + regmap_reg_range(SX9500_REG_IRQ_MSK + 1, SX9500_REG_PROX_CTRL0 - 1), + regmap_reg_range(SX9500_REG_PROX_CTRL8 + 1, SX9500_REG_SENSOR_SEL - 1), + regmap_reg_range(SX9500_REG_OFFSET_LSB + 1, SX9500_REG_RESET - 1), +}; + +static const struct regmap_access_table sx9500_readable_regs = { + .no_ranges = sx9500_non_readable_reg_ranges, + .n_no_ranges = ARRAY_SIZE(sx9500_non_readable_reg_ranges), +}; + +static const struct regmap_range sx9500_volatile_reg_ranges[] = { + regmap_reg_range(SX9500_REG_IRQ_SRC, SX9500_REG_STAT), + regmap_reg_range(SX9500_REG_USE_MSB, SX9500_REG_OFFSET_LSB), + regmap_reg_range(SX9500_REG_RESET, SX9500_REG_RESET), +}; + +static const struct regmap_access_table sx9500_volatile_regs = { + .yes_ranges = sx9500_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx9500_volatile_reg_ranges), +}; + +static const struct regmap_config sx9500_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = SX9500_REG_RESET, + .cache_type = REGCACHE_RBTREE, + + .wr_table = &sx9500_writeable_regs, + .rd_table = &sx9500_readable_regs, + .volatile_table = &sx9500_volatile_regs, +}; + +static int sx9500_read_proximity(struct sx9500_data *data, + const struct iio_chan_spec *chan, + int *val) +{ + int ret; + __be16 regval; + + ret = regmap_write(data->regmap, SX9500_REG_SENSOR_SEL, chan->channel); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, SX9500_REG_USE_MSB, ®val, 2); + if (ret < 0) + return ret; + + *val = be16_to_cpu(regval); + + return IIO_VAL_INT; +} + +static int sx9500_read_samp_freq(struct sx9500_data *data, + int *val, int *val2) +{ + int ret; + unsigned int regval; + + mutex_lock(&data->mutex); + ret = regmap_read(data->regmap, SX9500_REG_PROX_CTRL0, ®val); + mutex_unlock(&data->mutex); + + if (ret < 0) + return ret; + + regval = (regval & SX9500_SCAN_PERIOD_MASK) >> SX9500_SCAN_PERIOD_SHIFT; + *val = sx9500_samp_freq_table[regval].val; + *val2 = sx9500_samp_freq_table[regval].val2; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int sx9500_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct sx9500_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_PROXIMITY: + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + mutex_lock(&data->mutex); + ret = sx9500_read_proximity(data, chan, val); + mutex_unlock(&data->mutex); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9500_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int sx9500_set_samp_freq(struct sx9500_data *data, + int val, int val2) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(sx9500_samp_freq_table); i++) + if (val == sx9500_samp_freq_table[i].val && + val2 == sx9500_samp_freq_table[i].val2) + break; + + if (i == ARRAY_SIZE(sx9500_samp_freq_table)) + return -EINVAL; + + mutex_lock(&data->mutex); + + ret = regmap_update_bits(data->regmap, SX9500_REG_PROX_CTRL0, + SX9500_SCAN_PERIOD_MASK, + i << SX9500_SCAN_PERIOD_SHIFT); + + mutex_unlock(&data->mutex); + + return ret; +} + +static int sx9500_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct sx9500_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_PROXIMITY: + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9500_set_samp_freq(data, val, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static irqreturn_t sx9500_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sx9500_data *data = iio_priv(indio_dev); + + if (data->trigger_enabled) + iio_trigger_poll(data->trig); + + /* + * Even if no event is enabled, we need to wake the thread to + * clear the interrupt state by reading SX9500_REG_IRQ_SRC. It + * is not possible to do that here because regmap_read takes a + * mutex. + */ + return IRQ_WAKE_THREAD; +} + +static irqreturn_t sx9500_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sx9500_data *data = iio_priv(indio_dev); + int ret; + unsigned int val, chan; + + mutex_lock(&data->mutex); + + ret = regmap_read(data->regmap, SX9500_REG_IRQ_SRC, &val); + if (ret < 0) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + goto out; + } + + if (!(val & (SX9500_CLOSE_IRQ | SX9500_FAR_IRQ))) + goto out; + + ret = regmap_read(data->regmap, SX9500_REG_STAT, &val); + if (ret < 0) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + goto out; + } + + val >>= SX9500_PROXSTAT_SHIFT; + for (chan = 0; chan < SX9500_NUM_CHANNELS; chan++) { + int dir; + u64 ev; + bool new_prox = val & BIT(chan); + + if (!data->event_enabled[chan]) + continue; + if (new_prox == data->prox_stat[chan]) + /* No change on this channel. */ + continue; + + dir = new_prox ? IIO_EV_DIR_FALLING : + IIO_EV_DIR_RISING; + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, + chan, + IIO_EV_TYPE_THRESH, + dir); + iio_push_event(indio_dev, ev, iio_get_time_ns()); + data->prox_stat[chan] = new_prox; + } + +out: + mutex_unlock(&data->mutex); + + return IRQ_HANDLED; +} + +static int sx9500_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 sx9500_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH || + dir != IIO_EV_DIR_EITHER) + return -EINVAL; + + return data->event_enabled[chan->channel]; +} + +static int sx9500_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 sx9500_data *data = iio_priv(indio_dev); + int ret, i; + bool any_active = false; + unsigned int irqmask; + + if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH || + dir != IIO_EV_DIR_EITHER) + return -EINVAL; + + mutex_lock(&data->mutex); + + data->event_enabled[chan->channel] = state; + + for (i = 0; i < SX9500_NUM_CHANNELS; i++) + if (data->event_enabled[i]) { + any_active = true; + break; + } + + irqmask = SX9500_CLOSE_IRQ | SX9500_FAR_IRQ; + if (any_active) + ret = regmap_update_bits(data->regmap, SX9500_REG_IRQ_MSK, + irqmask, irqmask); + else + ret = regmap_update_bits(data->regmap, SX9500_REG_IRQ_MSK, + irqmask, 0); + + mutex_unlock(&data->mutex); + + return ret; +} + +static int sx9500_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct sx9500_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + kfree(data->buffer); + data->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); + mutex_unlock(&data->mutex); + + if (data->buffer == NULL) + return -ENOMEM; + + return 0; +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "2.500000 3.333333 5 6.666666 8.333333 11.111111 16.666666 33.333333"); + +static struct attribute *sx9500_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group sx9500_attribute_group = { + .attrs = sx9500_attributes, +}; + +static const struct iio_info sx9500_info = { + .driver_module = THIS_MODULE, + .attrs = &sx9500_attribute_group, + .read_raw = &sx9500_read_raw, + .write_raw = &sx9500_write_raw, + .read_event_config = &sx9500_read_event_config, + .write_event_config = &sx9500_write_event_config, + .update_scan_mode = &sx9500_update_scan_mode, +}; + +static int sx9500_set_trigger_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct sx9500_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + + ret = regmap_update_bits(data->regmap, SX9500_REG_IRQ_MSK, + SX9500_CONVDONE_IRQ, + state ? SX9500_CONVDONE_IRQ : 0); + if (ret == 0) + data->trigger_enabled = state; + + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_trigger_ops sx9500_trigger_ops = { + .set_trigger_state = sx9500_set_trigger_state, + .owner = THIS_MODULE, +}; + +static irqreturn_t sx9500_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct sx9500_data *data = iio_priv(indio_dev); + int val, bit, ret, i = 0; + + mutex_lock(&data->mutex); + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = sx9500_read_proximity(data, &indio_dev->channels[bit], + &val); + if (ret < 0) + goto out; + + data->buffer[i++] = val; + } + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns()); + +out: + mutex_unlock(&data->mutex); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +struct sx9500_reg_default { + u8 reg; + u8 def; +}; + +static const struct sx9500_reg_default sx9500_default_regs[] = { + { + .reg = SX9500_REG_PROX_CTRL1, + /* Shield enabled, small range. */ + .def = 0x43, + }, + { + .reg = SX9500_REG_PROX_CTRL2, + /* x8 gain, 167kHz frequency, finest resolution. */ + .def = 0x77, + }, + { + .reg = SX9500_REG_PROX_CTRL3, + /* Doze enabled, 2x scan period doze, no raw filter. */ + .def = 0x40, + }, + { + .reg = SX9500_REG_PROX_CTRL4, + /* Average threshold. */ + .def = 0x30, + }, + { + .reg = SX9500_REG_PROX_CTRL5, + /* + * Debouncer off, lowest average negative filter, + * highest average postive filter. + */ + .def = 0x0f, + }, + { + .reg = SX9500_REG_PROX_CTRL6, + /* Proximity detection threshold: 280 */ + .def = 0x0e, + }, + { + .reg = SX9500_REG_PROX_CTRL7, + /* + * No automatic compensation, compensate each pin + * independently, proximity hysteresis: 32, close + * debouncer off, far debouncer off. + */ + .def = 0x00, + }, + { + .reg = SX9500_REG_PROX_CTRL8, + /* No stuck timeout, no periodic compensation. */ + .def = 0x00, + }, + { + .reg = SX9500_REG_PROX_CTRL0, + /* Scan period: 30ms, all sensors enabled. */ + .def = 0x0f, + }, +}; + +static int sx9500_init_device(struct iio_dev *indio_dev) +{ + struct sx9500_data *data = iio_priv(indio_dev); + int ret, i; + unsigned int val; + + ret = regmap_write(data->regmap, SX9500_REG_IRQ_MSK, 0); + if (ret < 0) + return ret; + + ret = regmap_write(data->regmap, SX9500_REG_RESET, + SX9500_SOFT_RESET); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, SX9500_REG_IRQ_SRC, &val); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(sx9500_default_regs); i++) { + ret = regmap_write(data->regmap, + sx9500_default_regs[i].reg, + sx9500_default_regs[i].def); + if (ret < 0) + return ret; + } + + return 0; +} + +static int sx9500_gpio_probe(struct i2c_client *client, + struct sx9500_data *data) +{ + struct device *dev; + struct gpio_desc *gpio; + int ret; + + if (!client) + return -EINVAL; + + dev = &client->dev; + + /* data ready gpio interrupt pin */ + gpio = devm_gpiod_get_index(dev, SX9500_GPIO_NAME, 0, GPIOD_IN); + if (IS_ERR(gpio)) { + dev_err(dev, "acpi gpio get index failed\n"); + return PTR_ERR(gpio); + } + + ret = gpiod_to_irq(gpio); + + dev_dbg(dev, "GPIO resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret); + + return ret; +} + +static int sx9500_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct iio_dev *indio_dev; + struct sx9500_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->mutex); + data->trigger_enabled = false; + + data->regmap = devm_regmap_init_i2c(client, &sx9500_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + sx9500_init_device(indio_dev); + + indio_dev->dev.parent = &client->dev; + indio_dev->name = SX9500_DRIVER_NAME; + indio_dev->channels = sx9500_channels; + indio_dev->num_channels = ARRAY_SIZE(sx9500_channels); + indio_dev->info = &sx9500_info; + indio_dev->modes = INDIO_DIRECT_MODE; + i2c_set_clientdata(client, indio_dev); + + if (client->irq <= 0) + client->irq = sx9500_gpio_probe(client, data); + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + sx9500_irq_handler, sx9500_irq_thread_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + SX9500_IRQ_NAME, indio_dev); + if (ret < 0) + return ret; + + data->trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", indio_dev->name, indio_dev->id); + if (!data->trig) + return -ENOMEM; + + data->trig->dev.parent = &client->dev; + data->trig->ops = &sx9500_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = iio_trigger_register(data->trig); + if (ret) + return ret; + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + sx9500_trigger_handler, NULL); + if (ret < 0) + goto out_trigger_unregister; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_buffer_cleanup; + + return 0; + +out_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +out_trigger_unregister: + if (client->irq > 0) + iio_trigger_unregister(data->trig); + + return ret; +} + +static int sx9500_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct sx9500_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + if (client->irq > 0) + iio_trigger_unregister(data->trig); + kfree(data->buffer); + + return 0; +} + +static const struct acpi_device_id sx9500_acpi_match[] = { + {"SSX9500", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, sx9500_acpi_match); + +static const struct i2c_device_id sx9500_id[] = { + {"sx9500", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, sx9500_id); + +static struct i2c_driver sx9500_driver = { + .driver = { + .name = SX9500_DRIVER_NAME, + .acpi_match_table = ACPI_PTR(sx9500_acpi_match), + }, + .probe = sx9500_probe, + .remove = sx9500_remove, + .id_table = sx9500_id, +}; +module_i2c_driver(sx9500_driver); + +MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>"); +MODULE_DESCRIPTION("Driver for Semtech SX9500 proximity sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig new file mode 100644 index 000000000..21feaa466 --- /dev/null +++ b/drivers/iio/temperature/Kconfig @@ -0,0 +1,26 @@ +# +# Temperature sensor drivers +# +menu "Temperature sensors" + +config MLX90614 + tristate "MLX90614 contact-less infrared sensor" + depends on I2C + help + If you say yes here you get support for the Melexis + MLX90614 contact-less infrared sensor connected with I2C. + + This driver can also be built as a module. If so, the module will + be called mlx90614. + +config TMP006 + tristate "TMP006 infrared thermopile sensor" + depends on I2C + help + If you say yes here you get support for the Texas Instruments + TMP006 infrared thermopile sensor. + + This driver can also be built as a module. If so, the module will + be called tmp006. + +endmenu diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile new file mode 100644 index 000000000..40710a811 --- /dev/null +++ b/drivers/iio/temperature/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for industrial I/O temperature drivers +# + +obj-$(CONFIG_MLX90614) += mlx90614.o +obj-$(CONFIG_TMP006) += tmp006.o diff --git a/drivers/iio/temperature/mlx90614.c b/drivers/iio/temperature/mlx90614.c new file mode 100644 index 000000000..a112fc9ab --- /dev/null +++ b/drivers/iio/temperature/mlx90614.c @@ -0,0 +1,221 @@ +/* + * mlx90614.c - Support for Melexis MLX90614 contactless IR temperature sensor + * + * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net> + * Copyright (c) 2015 Essensium NV + * + * 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. + * + * Driver for the Melexis MLX90614 I2C 16-bit IR thermopile sensor + * + * (7-bit I2C slave address 0x5a, 100KHz bus speed only!) + * + * TODO: sleep mode, configuration EEPROM + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/module.h> + +#include <linux/iio/iio.h> + +#define MLX90614_OP_RAM 0x00 +#define MLX90614_OP_EEPROM 0x20 +#define MLX90614_OP_SLEEP 0xff + +/* RAM offsets with 16-bit data, MSB first */ +#define MLX90614_RAW1 (MLX90614_OP_RAM | 0x04) /* raw data IR channel 1 */ +#define MLX90614_RAW2 (MLX90614_OP_RAM | 0x05) /* raw data IR channel 2 */ +#define MLX90614_TA (MLX90614_OP_RAM | 0x06) /* ambient temperature */ +#define MLX90614_TOBJ1 (MLX90614_OP_RAM | 0x07) /* object 1 temperature */ +#define MLX90614_TOBJ2 (MLX90614_OP_RAM | 0x08) /* object 2 temperature */ + +/* EEPROM offsets with 16-bit data, MSB first */ +#define MLX90614_EMISSIVITY (MLX90614_OP_EEPROM | 0x04) /* emissivity correction coefficient */ +#define MLX90614_CONFIG (MLX90614_OP_EEPROM | 0x05) /* configuration register */ + +/* Control bits in configuration register */ +#define MLX90614_CONFIG_IIR_SHIFT 0 /* IIR coefficient */ +#define MLX90614_CONFIG_IIR_MASK (0x7 << MLX90614_CONFIG_IIR_SHIFT) +#define MLX90614_CONFIG_DUAL_SHIFT 6 /* single (0) or dual (1) IR sensor */ +#define MLX90614_CONFIG_DUAL_MASK (1 << MLX90614_CONFIG_DUAL_SHIFT) +#define MLX90614_CONFIG_FIR_SHIFT 8 /* FIR coefficient */ +#define MLX90614_CONFIG_FIR_MASK (0x7 << MLX90614_CONFIG_FIR_SHIFT) +#define MLX90614_CONFIG_GAIN_SHIFT 11 /* gain */ +#define MLX90614_CONFIG_GAIN_MASK (0x7 << MLX90614_CONFIG_GAIN_SHIFT) + +/* Timings (in ms) */ +#define MLX90614_TIMING_EEPROM 20 /* time for EEPROM write/erase to complete */ +#define MLX90614_TIMING_WAKEUP 34 /* time to hold SDA low for wake-up */ +#define MLX90614_TIMING_STARTUP 250 /* time before first data after wake-up */ + +struct mlx90614_data { + struct i2c_client *client; +}; + +static int mlx90614_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct mlx90614_data *data = iio_priv(indio_dev); + u8 cmd; + s32 ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: /* 0.02K / LSB */ + switch (channel->channel2) { + case IIO_MOD_TEMP_AMBIENT: + cmd = MLX90614_TA; + break; + case IIO_MOD_TEMP_OBJECT: + switch (channel->channel) { + case 0: + cmd = MLX90614_TOBJ1; + break; + case 1: + cmd = MLX90614_TOBJ2; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + ret = i2c_smbus_read_word_data(data->client, cmd); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = 13657; + *val2 = 500000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + *val = 20; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec mlx90614_channels[] = { + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_AMBIENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .modified = 1, + .channel2 = IIO_MOD_TEMP_OBJECT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .indexed = 1, + .modified = 1, + .channel = 1, + .channel2 = IIO_MOD_TEMP_OBJECT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static const struct iio_info mlx90614_info = { + .read_raw = mlx90614_read_raw, + .driver_module = THIS_MODULE, +}; + +/* Return 0 for single sensor, 1 for dual sensor, <0 on error. */ +static int mlx90614_probe_num_ir_sensors(struct i2c_client *client) +{ + s32 ret; + + ret = i2c_smbus_read_word_data(client, MLX90614_CONFIG); + + if (ret < 0) + return ret; + + return (ret & MLX90614_CONFIG_DUAL_MASK) ? 1 : 0; +} + +static int mlx90614_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct mlx90614_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + 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; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mlx90614_info; + + ret = mlx90614_probe_num_ir_sensors(client); + switch (ret) { + case 0: + dev_dbg(&client->dev, "Found single sensor"); + indio_dev->channels = mlx90614_channels; + indio_dev->num_channels = 2; + break; + case 1: + dev_dbg(&client->dev, "Found dual sensor"); + indio_dev->channels = mlx90614_channels; + indio_dev->num_channels = 3; + break; + default: + return ret; + } + + return iio_device_register(indio_dev); +} + +static int mlx90614_remove(struct i2c_client *client) +{ + iio_device_unregister(i2c_get_clientdata(client)); + + return 0; +} + +static const struct i2c_device_id mlx90614_id[] = { + { "mlx90614", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mlx90614_id); + +static struct i2c_driver mlx90614_driver = { + .driver = { + .name = "mlx90614", + .owner = THIS_MODULE, + }, + .probe = mlx90614_probe, + .remove = mlx90614_remove, + .id_table = mlx90614_id, +}; +module_i2c_driver(mlx90614_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_AUTHOR("Vianney le Clément de Saint-Marcq <vianney.leclement@essensium.com>"); +MODULE_DESCRIPTION("Melexis MLX90614 contactless IR temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/temperature/tmp006.c b/drivers/iio/temperature/tmp006.c new file mode 100644 index 000000000..7a8050996 --- /dev/null +++ b/drivers/iio/temperature/tmp006.c @@ -0,0 +1,293 @@ +/* + * tmp006.c - Support for TI TMP006 IR thermopile sensor + * + * Copyright (c) 2013 Peter Meerwald <pmeerw@pmeerw.net> + * + * 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. + * + * Driver for the Texas Instruments I2C 16-bit IR thermopile sensor + * + * (7-bit I2C slave address 0x40, changeable via ADR pins) + * + * TODO: data ready irq + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/bitops.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define TMP006_VOBJECT 0x00 +#define TMP006_TAMBIENT 0x01 +#define TMP006_CONFIG 0x02 +#define TMP006_MANUFACTURER_ID 0xfe +#define TMP006_DEVICE_ID 0xff + +#define TMP006_TAMBIENT_SHIFT 2 + +#define TMP006_CONFIG_RESET BIT(15) +#define TMP006_CONFIG_DRDY_EN BIT(8) +#define TMP006_CONFIG_DRDY BIT(7) + +#define TMP006_CONFIG_MOD_MASK 0x7000 + +#define TMP006_CONFIG_CR_MASK 0x0e00 +#define TMP006_CONFIG_CR_SHIFT 9 + +#define MANUFACTURER_MAGIC 0x5449 +#define DEVICE_MAGIC 0x0067 + +struct tmp006_data { + struct i2c_client *client; + u16 config; +}; + +static int tmp006_read_measurement(struct tmp006_data *data, u8 reg) +{ + s32 ret; + int tries = 50; + + while (tries-- > 0) { + ret = i2c_smbus_read_word_swapped(data->client, + TMP006_CONFIG); + if (ret < 0) + return ret; + if (ret & TMP006_CONFIG_DRDY) + break; + msleep(100); + } + + if (tries < 0) + return -EIO; + + return i2c_smbus_read_word_swapped(data->client, reg); +} + +static const int tmp006_freqs[5][2] = { {4, 0}, {2, 0}, {1, 0}, + {0, 500000}, {0, 250000} }; + +static int tmp006_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct tmp006_data *data = iio_priv(indio_dev); + s32 ret; + int cr; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (channel->type == IIO_VOLTAGE) { + /* LSB is 156.25 nV */ + ret = tmp006_read_measurement(data, TMP006_VOBJECT); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 15); + } else if (channel->type == IIO_TEMP) { + /* LSB is 0.03125 degrees Celsius */ + ret = tmp006_read_measurement(data, TMP006_TAMBIENT); + if (ret < 0) + return ret; + *val = sign_extend32(ret, 15) >> TMP006_TAMBIENT_SHIFT; + } else { + break; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (channel->type == IIO_VOLTAGE) { + *val = 0; + *val2 = 156250; + } else if (channel->type == IIO_TEMP) { + *val = 31; + *val2 = 250000; + } else { + break; + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + cr = (data->config & TMP006_CONFIG_CR_MASK) + >> TMP006_CONFIG_CR_SHIFT; + *val = tmp006_freqs[cr][0]; + *val2 = tmp006_freqs[cr][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + + return -EINVAL; +} + +static int tmp006_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct tmp006_data *data = iio_priv(indio_dev); + int i; + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(tmp006_freqs); i++) + if ((val == tmp006_freqs[i][0]) && + (val2 == tmp006_freqs[i][1])) { + data->config &= ~TMP006_CONFIG_CR_MASK; + data->config |= i << TMP006_CONFIG_CR_SHIFT; + + return i2c_smbus_write_word_swapped(data->client, + TMP006_CONFIG, + data->config); + + } + return -EINVAL; +} + +static IIO_CONST_ATTR(sampling_frequency_available, "4 2 1 0.5 0.25"); + +static struct attribute *tmp006_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group tmp006_attribute_group = { + .attrs = tmp006_attributes, +}; + +static const struct iio_chan_spec tmp006_channels[] = { + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + } +}; + +static const struct iio_info tmp006_info = { + .read_raw = tmp006_read_raw, + .write_raw = tmp006_write_raw, + .attrs = &tmp006_attribute_group, + .driver_module = THIS_MODULE, +}; + +static bool tmp006_check_identification(struct i2c_client *client) +{ + int mid, did; + + mid = i2c_smbus_read_word_swapped(client, TMP006_MANUFACTURER_ID); + if (mid < 0) + return false; + + did = i2c_smbus_read_word_swapped(client, TMP006_DEVICE_ID); + if (did < 0) + return false; + + return mid == MANUFACTURER_MAGIC && did == DEVICE_MAGIC; +} + +static int tmp006_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct tmp006_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + if (!tmp006_check_identification(client)) { + dev_err(&client->dev, "no TMP006 sensor\n"); + 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; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &tmp006_info; + + indio_dev->channels = tmp006_channels; + indio_dev->num_channels = ARRAY_SIZE(tmp006_channels); + + ret = i2c_smbus_read_word_swapped(data->client, TMP006_CONFIG); + if (ret < 0) + return ret; + data->config = ret; + + return iio_device_register(indio_dev); +} + +static int tmp006_powerdown(struct tmp006_data *data) +{ + return i2c_smbus_write_word_swapped(data->client, TMP006_CONFIG, + data->config & ~TMP006_CONFIG_MOD_MASK); +} + +static int tmp006_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + tmp006_powerdown(iio_priv(indio_dev)); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tmp006_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + return tmp006_powerdown(iio_priv(indio_dev)); +} + +static int tmp006_resume(struct device *dev) +{ + struct tmp006_data *data = iio_priv(i2c_get_clientdata( + to_i2c_client(dev))); + return i2c_smbus_write_word_swapped(data->client, TMP006_CONFIG, + data->config | TMP006_CONFIG_MOD_MASK); +} +#endif + +static SIMPLE_DEV_PM_OPS(tmp006_pm_ops, tmp006_suspend, tmp006_resume); + +static const struct i2c_device_id tmp006_id[] = { + { "tmp006", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp006_id); + +static struct i2c_driver tmp006_driver = { + .driver = { + .name = "tmp006", + .pm = &tmp006_pm_ops, + .owner = THIS_MODULE, + }, + .probe = tmp006_probe, + .remove = tmp006_remove, + .id_table = tmp006_id, +}; +module_i2c_driver(tmp006_driver); + +MODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>"); +MODULE_DESCRIPTION("TI TMP006 IR thermopile sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig new file mode 100644 index 000000000..79996123a --- /dev/null +++ b/drivers/iio/trigger/Kconfig @@ -0,0 +1,28 @@ +# +# Industrial I/O standalone triggers +# +# When adding new entries keep the list in alphabetical order + +menu "Triggers - standalone" + +config IIO_INTERRUPT_TRIGGER + tristate "Generic interrupt trigger" + help + Provides support for using an interrupt of any type as an IIO + trigger. This may be provided by a gpio driver for example. + + To compile this driver as a module, choose M here: the + module will be called iio-trig-interrupt. + +config IIO_SYSFS_TRIGGER + tristate "SYSFS trigger" + depends on SYSFS + select IRQ_WORK + help + Provides support for using SYSFS entries as IIO triggers. + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called iio-trig-sysfs. + +endmenu diff --git a/drivers/iio/trigger/Makefile b/drivers/iio/trigger/Makefile new file mode 100644 index 000000000..0694daeca --- /dev/null +++ b/drivers/iio/trigger/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for triggers not associated with iio-devices +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_IIO_INTERRUPT_TRIGGER) += iio-trig-interrupt.o +obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o diff --git a/drivers/iio/trigger/iio-trig-interrupt.c b/drivers/iio/trigger/iio-trig-interrupt.c new file mode 100644 index 000000000..572bc6f02 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-interrupt.c @@ -0,0 +1,119 @@ +/* + * Industrial I/O - generic interrupt based trigger support + * + * Copyright (c) 2008-2013 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/slab.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> + + +struct iio_interrupt_trigger_info { + unsigned int irq; +}; + +static irqreturn_t iio_interrupt_trigger_poll(int irq, void *private) +{ + iio_trigger_poll(private); + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops iio_interrupt_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int iio_interrupt_trigger_probe(struct platform_device *pdev) +{ + struct iio_interrupt_trigger_info *trig_info; + struct iio_trigger *trig; + unsigned long irqflags; + struct resource *irq_res; + int irq, ret = 0; + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + + if (irq_res == NULL) + return -ENODEV; + + irqflags = (irq_res->flags & IRQF_TRIGGER_MASK) | IRQF_SHARED; + + irq = irq_res->start; + + trig = iio_trigger_alloc("irqtrig%d", irq); + if (!trig) { + ret = -ENOMEM; + goto error_ret; + } + + trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); + if (!trig_info) { + ret = -ENOMEM; + goto error_put_trigger; + } + iio_trigger_set_drvdata(trig, trig_info); + trig_info->irq = irq; + trig->ops = &iio_interrupt_trigger_ops; + ret = request_irq(irq, iio_interrupt_trigger_poll, + irqflags, trig->name, trig); + if (ret) { + dev_err(&pdev->dev, + "request IRQ-%d failed", irq); + goto error_free_trig_info; + } + + ret = iio_trigger_register(trig); + if (ret) + goto error_release_irq; + platform_set_drvdata(pdev, trig); + + return 0; + +/* First clean up the partly allocated trigger */ +error_release_irq: + free_irq(irq, trig); +error_free_trig_info: + kfree(trig_info); +error_put_trigger: + iio_trigger_put(trig); +error_ret: + return ret; +} + +static int iio_interrupt_trigger_remove(struct platform_device *pdev) +{ + struct iio_trigger *trig; + struct iio_interrupt_trigger_info *trig_info; + + trig = platform_get_drvdata(pdev); + trig_info = iio_trigger_get_drvdata(trig); + iio_trigger_unregister(trig); + free_irq(trig_info->irq, trig); + kfree(trig_info); + iio_trigger_put(trig); + + return 0; +} + +static struct platform_driver iio_interrupt_trigger_driver = { + .probe = iio_interrupt_trigger_probe, + .remove = iio_interrupt_trigger_remove, + .driver = { + .name = "iio_interrupt_trigger", + }, +}; + +module_platform_driver(iio_interrupt_trigger_driver); + +MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); +MODULE_DESCRIPTION("Interrupt trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/trigger/iio-trig-sysfs.c b/drivers/iio/trigger/iio-trig-sysfs.c new file mode 100644 index 000000000..3dfab2bc6 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-sysfs.c @@ -0,0 +1,229 @@ +/* + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/irq_work.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> + +struct iio_sysfs_trig { + struct iio_trigger *trig; + struct irq_work work; + int id; + struct list_head l; +}; + +static LIST_HEAD(iio_sysfs_trig_list); +static DEFINE_MUTEX(iio_sysfs_trig_list_mut); + +static int iio_sysfs_trigger_probe(int id); +static ssize_t iio_sysfs_trig_add(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + unsigned long input; + + ret = kstrtoul(buf, 10, &input); + if (ret) + return ret; + ret = iio_sysfs_trigger_probe(input); + if (ret) + return ret; + return len; +} +static DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add); + +static int iio_sysfs_trigger_remove(int id); +static ssize_t iio_sysfs_trig_remove(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + unsigned long input; + + ret = kstrtoul(buf, 10, &input); + if (ret) + return ret; + ret = iio_sysfs_trigger_remove(input); + if (ret) + return ret; + return len; +} + +static DEVICE_ATTR(remove_trigger, S_IWUSR, NULL, &iio_sysfs_trig_remove); + +static struct attribute *iio_sysfs_trig_attrs[] = { + &dev_attr_add_trigger.attr, + &dev_attr_remove_trigger.attr, + NULL, +}; + +static const struct attribute_group iio_sysfs_trig_group = { + .attrs = iio_sysfs_trig_attrs, +}; + +static const struct attribute_group *iio_sysfs_trig_groups[] = { + &iio_sysfs_trig_group, + NULL +}; + + +/* Nothing to actually do upon release */ +static void iio_trigger_sysfs_release(struct device *dev) +{ +} + +static struct device iio_sysfs_trig_dev = { + .bus = &iio_bus_type, + .groups = iio_sysfs_trig_groups, + .release = &iio_trigger_sysfs_release, +}; + +static void iio_sysfs_trigger_work(struct irq_work *work) +{ + struct iio_sysfs_trig *trig = container_of(work, struct iio_sysfs_trig, + work); + + iio_trigger_poll(trig->trig); +} + +static ssize_t iio_sysfs_trigger_poll(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_sysfs_trig *sysfs_trig = iio_trigger_get_drvdata(trig); + + irq_work_queue(&sysfs_trig->work); + + return count; +} + +static DEVICE_ATTR(trigger_now, S_IWUSR, NULL, iio_sysfs_trigger_poll); + +static struct attribute *iio_sysfs_trigger_attrs[] = { + &dev_attr_trigger_now.attr, + NULL, +}; + +static const struct attribute_group iio_sysfs_trigger_attr_group = { + .attrs = iio_sysfs_trigger_attrs, +}; + +static const struct attribute_group *iio_sysfs_trigger_attr_groups[] = { + &iio_sysfs_trigger_attr_group, + NULL +}; + +static const struct iio_trigger_ops iio_sysfs_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int iio_sysfs_trigger_probe(int id) +{ + struct iio_sysfs_trig *t; + int ret; + bool foundit = false; + + mutex_lock(&iio_sysfs_trig_list_mut); + list_for_each_entry(t, &iio_sysfs_trig_list, l) + if (id == t->id) { + foundit = true; + break; + } + if (foundit) { + ret = -EINVAL; + goto out1; + } + t = kmalloc(sizeof(*t), GFP_KERNEL); + if (t == NULL) { + ret = -ENOMEM; + goto out1; + } + t->id = id; + t->trig = iio_trigger_alloc("sysfstrig%d", id); + if (!t->trig) { + ret = -ENOMEM; + goto free_t; + } + + t->trig->dev.groups = iio_sysfs_trigger_attr_groups; + t->trig->ops = &iio_sysfs_trigger_ops; + t->trig->dev.parent = &iio_sysfs_trig_dev; + iio_trigger_set_drvdata(t->trig, t); + + init_irq_work(&t->work, iio_sysfs_trigger_work); + + ret = iio_trigger_register(t->trig); + if (ret) + goto out2; + list_add(&t->l, &iio_sysfs_trig_list); + __module_get(THIS_MODULE); + mutex_unlock(&iio_sysfs_trig_list_mut); + return 0; + +out2: + iio_trigger_put(t->trig); +free_t: + kfree(t); +out1: + mutex_unlock(&iio_sysfs_trig_list_mut); + return ret; +} + +static int iio_sysfs_trigger_remove(int id) +{ + bool foundit = false; + struct iio_sysfs_trig *t; + + mutex_lock(&iio_sysfs_trig_list_mut); + list_for_each_entry(t, &iio_sysfs_trig_list, l) + if (id == t->id) { + foundit = true; + break; + } + if (!foundit) { + mutex_unlock(&iio_sysfs_trig_list_mut); + return -EINVAL; + } + + iio_trigger_unregister(t->trig); + iio_trigger_free(t->trig); + + list_del(&t->l); + kfree(t); + module_put(THIS_MODULE); + mutex_unlock(&iio_sysfs_trig_list_mut); + return 0; +} + + +static int __init iio_sysfs_trig_init(void) +{ + device_initialize(&iio_sysfs_trig_dev); + dev_set_name(&iio_sysfs_trig_dev, "iio_sysfs_trigger"); + return device_add(&iio_sysfs_trig_dev); +} +module_init(iio_sysfs_trig_init); + +static void __exit iio_sysfs_trig_exit(void) +{ + device_unregister(&iio_sysfs_trig_dev); +} +module_exit(iio_sysfs_trig_exit); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Sysfs based trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iio-trig-sysfs"); |