diff options
Diffstat (limited to 'drivers/iio')
174 files changed, 56214 insertions, 0 deletions
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig new file mode 100644 index 00000000000..345395e9dc6 --- /dev/null +++ b/drivers/iio/Kconfig @@ -0,0 +1,81 @@ +# +# 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 +boolean "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 +	select IIO_TRIGGER +	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 +	boolean "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 00000000000..698afc2d17c --- /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 00000000000..1e120fa1e15 --- /dev/null +++ b/drivers/iio/accel/Kconfig @@ -0,0 +1,80 @@ +# +# Accelerometer drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Accelerometers" + +config BMA180 +	tristate "Bosch BMA180 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 +	  triaxial acceleration sensor. + +	  To compile this driver as a module, choose M here: the +	  module will be called bma180. + +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. + +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. + +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. + +endmenu diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile new file mode 100644 index 00000000000..dc0e379c259 --- /dev/null +++ b/drivers/iio/accel/Makefile @@ -0,0 +1,16 @@ +# +# Makefile for industrial I/O accelerometer drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_BMA180) += bma180.o +obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o +obj-$(CONFIG_KXSD9)	+= kxsd9.o +obj-$(CONFIG_MMA8452)	+= mma8452.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 00000000000..a077cc86421 --- /dev/null +++ b/drivers/iio/accel/bma180.c @@ -0,0 +1,680 @@ +/* + * bma180.c - IIO driver for Bosch BMA180 triaxial acceleration sensor + * + * Copyright 2013 Oleksandr Kravchenko <x0199363@ti.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/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" + +/* 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_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 BMA_180_ID_REG_VAL	0x03 + +/* Chip power modes */ +#define BMA180_LOW_NOISE	0x00 +#define BMA180_LOW_POWER	0x03 + +#define BMA180_LOW_NOISE_STR	"low_noise" +#define BMA180_LOW_POWER_STR	"low_power" + +/* Defaults values */ +#define BMA180_DEF_PMODE	0 +#define BMA180_DEF_BW		20 +#define BMA180_DEF_SCALE	2452 + +/* Available values for sysfs */ +#define BMA180_FLP_FREQ_AVAILABLE \ +	"10 20 40 75 150 300" +#define BMA180_SCALE_AVAILABLE \ +	"0.001275 0.001863 0.002452 0.003727 0.004903 0.009709 0.019417" + +struct bma180_data { +	struct i2c_client *client; +	struct iio_trigger *trig; +	struct mutex mutex; +	int sleep_state; +	int scale; +	int bw; +	int pmode; +	char *buff; +}; + +enum bma180_axis { +	AXIS_X, +	AXIS_Y, +	AXIS_Z, +}; + +static int bw_table[] = { 10, 20, 40, 75, 150, 300 }; /* Hz */ +static int scale_table[] = { 1275, 1863, 2452, 3727, 4903, 9709, 19417 }; + +static int bma180_get_acc_reg(struct bma180_data *data, enum bma180_axis axis) +{ +	u8 reg = BMA180_ACC_X_LSB + axis * 2; +	int ret; + +	if (data->sleep_state) +		return -EBUSY; + +	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; +} + +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, BMA180_CTRL_REG0, BMA180_RESET_INT, 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, int state) +{ +	u8 reg_val = state ? BMA180_NEW_DATA_INT : 0x00; +	int ret = i2c_smbus_write_byte_data(data->client, BMA180_CTRL_REG3, +			reg_val); + +	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, int state) +{ +	int ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_SLEEP, 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, int 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 < ARRAY_SIZE(bw_table); ++i) { +		if (bw_table[i] == val) { +			ret = bma180_set_bits(data, +					BMA180_BW_TCS, BMA180_BW, 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 < ARRAY_SIZE(scale_table); ++i) +		if (scale_table[i] == val) { +			ret = bma180_set_bits(data, +					BMA180_OFFSET_LSB1, BMA180_RANGE, 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, int mode) +{ +	u8 reg_val = mode ? BMA180_LOW_POWER : BMA180_LOW_NOISE; +	int ret = bma180_set_bits(data, BMA180_TCO_Z, BMA180_MODE_CONFIG, +			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, +			BMA180_RESET, 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) +		goto err; +	if (ret != BMA_180_ID_REG_VAL) { +		ret = -ENODEV; +		goto err; +	} + +	ret = bma180_soft_reset(data); +	if (ret) +		goto err; +	/* +	 * No serial transaction should occur within minimum 10 us +	 * after soft_reset command +	 */ +	msleep(20); + +	ret = bma180_set_bits(data, BMA180_CTRL_REG0, BMA180_DIS_WAKE_UP, 1); +	if (ret) +		goto err; +	ret = bma180_set_ee_writing_state(data, 1); +	if (ret) +		goto err; +	ret = bma180_set_new_data_intr_state(data, 0); +	if (ret) +		goto err; +	ret = bma180_set_bits(data, BMA180_OFFSET_LSB1, BMA180_SMP_SKIP, 1); +	if (ret) +		goto err; +	ret = bma180_set_pmode(data, BMA180_DEF_PMODE); +	if (ret) +		goto err; +	ret = bma180_set_bw(data, BMA180_DEF_BW); +	if (ret) +		goto err; +	ret = bma180_set_scale(data, BMA180_DEF_SCALE); +	if (ret) +		goto err; + +	return 0; + +err: +	dev_err(&data->client->dev, "failed to init the chip\n"); +	return ret; +} + +static void bma180_chip_disable(struct bma180_data *data) +{ +	if (bma180_set_new_data_intr_state(data, 0)) +		goto err; +	if (bma180_set_ee_writing_state(data, 0)) +		goto err; +	if (bma180_set_sleep_state(data, 1)) +		goto err; + +	return; + +err: +	dev_err(&data->client->dev, "failed to disable the chip\n"); +} + +static IIO_CONST_ATTR(in_accel_filter_low_pass_3db_frequency_available, +		BMA180_FLP_FREQ_AVAILABLE); +static IIO_CONST_ATTR(in_accel_scale_available, BMA180_SCALE_AVAILABLE); + +static struct attribute *bma180_attributes[] = { +	&iio_const_attr_in_accel_filter_low_pass_3db_frequency_available.dev_attr.attr, +	&iio_const_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)) +			ret = -EBUSY; +		else +			ret = bma180_get_acc_reg(data, chan->scan_index); +		mutex_unlock(&data->mutex); +		if (ret < 0) +			return ret; +		*val = (s16)ret >> chan->scan_type.shift; +		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: +		*val = 0; +		*val2 = data->scale; +		return IIO_VAL_INT_PLUS_MICRO; +	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 int bma180_update_scan_mode(struct iio_dev *indio_dev, +		const unsigned long *scan_mask) +{ +	struct bma180_data *data = iio_priv(indio_dev); + +	if (data->buff) +		devm_kfree(&indio_dev->dev, data->buff); +	data->buff = devm_kzalloc(&indio_dev->dev, +			indio_dev->scan_bytes, GFP_KERNEL); +	if (!data->buff) +		return -ENOMEM; + +	return 0; +} + +static const struct iio_info bma180_info = { +	.attrs			= &bma180_attrs_group, +	.read_raw		= bma180_read_raw, +	.write_raw		= bma180_write_raw, +	.update_scan_mode	= bma180_update_scan_mode, +	.driver_module		= THIS_MODULE, +}; + +static const char * const bma180_power_modes[] = { +	BMA180_LOW_NOISE_STR, +	BMA180_LOW_POWER_STR, +}; + +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_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_LOW_PASS_FILTER_3DB_FREQUENCY),	\ +	.scan_index = AXIS_##_axis,					\ +	.scan_type = {							\ +		.sign = 's',						\ +		.realbits = 14,						\ +		.storagebits = 16,					\ +		.shift = 2,						\ +	},								\ +	.ext_info = bma180_ext_info,					\ +} + +static const struct iio_chan_spec bma180_channels[] = { +	BMA180_CHANNEL(X), +	BMA180_CHANNEL(Y), +	BMA180_CHANNEL(Z), +	IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +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->buffer->scan_mask, +			 indio_dev->masklength) { +		ret = bma180_get_acc_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; +	struct iio_trigger *trig; +	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 = bma180_chip_init(data); +	if (ret < 0) +		goto err_chip_disable; + +	mutex_init(&data->mutex); + +	indio_dev->dev.parent = &client->dev; +	indio_dev->channels = bma180_channels; +	indio_dev->num_channels = ARRAY_SIZE(bma180_channels); +	indio_dev->name = BMA180_DRV_NAME; +	indio_dev->modes = INDIO_DIRECT_MODE; +	indio_dev->info = &bma180_info; + +	trig = iio_trigger_alloc("%s-dev%d", indio_dev->name, indio_dev->id); +	if (!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_IRQ_NAME, trig); +	if (ret) { +		dev_err(&client->dev, "unable to request IRQ\n"); +		goto err_trigger_free; +	} + +	trig->dev.parent = &client->dev; +	trig->ops = &bma180_trigger_ops; +	iio_trigger_set_drvdata(trig, indio_dev); +	data->trig = trig; +	indio_dev->trig = trig; + +	ret = iio_trigger_register(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: +	iio_trigger_unregister(trig); +err_trigger_free: +	iio_trigger_free(trig); +err_chip_disable: +	bma180_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); +	iio_trigger_unregister(data->trig); +	iio_trigger_free(data->trig); + +	mutex_lock(&data->mutex); +	bma180_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, 1); +	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, 0); +	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_id[] = { +	{ BMA180_DRV_NAME, 0 }, +	{ } +}; + +MODULE_DEVICE_TABLE(i2c, bma180_id); + +static struct i2c_driver bma180_driver = { +	.driver = { +		.name	= BMA180_DRV_NAME, +		.owner	= THIS_MODULE, +		.pm	= BMA180_PM_OPS, +	}, +	.probe		= bma180_probe, +	.remove		= bma180_remove, +	.id_table	= bma180_id, +}; + +module_i2c_driver(bma180_driver); + +MODULE_AUTHOR("Kravchenko Oleksandr <x0199363@ti.com>"); +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("Bosch BMA180 triaxial acceleration sensor"); +MODULE_LICENSE("GPL"); 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 00000000000..54e464e4bb7 --- /dev/null +++ b/drivers/iio/accel/hid-sensor-accel-3d.c @@ -0,0 +1,431 @@ +/* + * 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; +	s32 poll_value; + +	*val = 0; +	*val2 = 0; +	switch (mask) { +	case 0: +		poll_value = hid_sensor_read_poll_value( +					&accel_state->common_attributes); +		if (poll_value < 0) +			return -EINVAL; + +		hid_sensor_power_state(&accel_state->common_attributes, true); +		msleep_interruptible(poll_value * 2); +		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); +		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, +		.owner	= THIS_MODULE, +	}, +	.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/kxsd9.c b/drivers/iio/accel/kxsd9.c new file mode 100644 index 00000000000..98ba761cbb9 --- /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 00000000000..2a5fa9a436e --- /dev/null +++ b/drivers/iio/accel/mma8452.c @@ -0,0 +1,445 @@ +/* + * 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 struct i2c_driver mma8452_driver = { +	.driver = { +		.name	= "mma8452", +		.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/st_accel.h b/drivers/iio/accel/st_accel.h new file mode 100644 index 00000000000..c3877630b2e --- /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 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, +					struct st_sensors_platform_data *pdata); +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 00000000000..a1e642ee13d --- /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 00000000000..a2abf7c2ce3 --- /dev/null +++ b/drivers/iio/accel/st_accel_core.c @@ -0,0 +1,532 @@ +/* + * 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 + +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_sensors st_accel_sensors[] = { +	{ +		.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, +	}, +}; + +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; +	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; +	default: +		return -EINVAL; +	} + +	return err; +} + +static ST_SENSOR_DEV_ATTR_SAMP_FREQ(); +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, +	&iio_dev_attr_sampling_frequency.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_sensors_platform_data *plat_data) +{ +	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; + +	st_sensors_power_enable(indio_dev); + +	err = st_sensors_check_device_support(indio_dev, +				ARRAY_SIZE(st_accel_sensors), st_accel_sensors); +	if (err < 0) +		return err; + +	adata->num_data_channels = ST_ACCEL_NUMBER_DATA_CHANNELS; +	adata->multiread_bit = adata->sensor->multi_read_bit; +	indio_dev->channels = adata->sensor->ch; +	indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + +	adata->current_fullscale = (struct st_sensor_fullscale_avl *) +						&adata->sensor->fs.fs_avl[0]; +	adata->odr = adata->sensor->odr.odr_avl[0].hz; + +	if (!plat_data) +		plat_data = +			(struct st_sensors_platform_data *)&default_accel_pdata; + +	err = st_sensors_init_sensor(indio_dev, plat_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 00000000000..d7bedbdfc81 --- /dev/null +++ b/drivers/iio/accel/st_accel_i2c.c @@ -0,0 +1,79 @@ +/* + * 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" + +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); +	adata->dev = &client->dev; + +	st_sensors_i2c_configure(indio_dev, client, adata); + +	err = st_accel_common_probe(indio_dev, client->dev.platform_data); +	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", +	}, +	.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 00000000000..195639646e3 --- /dev/null +++ b/drivers/iio/accel/st_accel_spi.c @@ -0,0 +1,78 @@ +/* + * 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); +	adata->dev = &spi->dev; + +	st_sensors_spi_configure(indio_dev, spi, adata); + +	err = st_accel_common_probe(indio_dev, spi->dev.platform_data); +	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 00000000000..a80d23628f1 --- /dev/null +++ b/drivers/iio/adc/Kconfig @@ -0,0 +1,263 @@ +# +# 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 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 EXYNOS_ADC +	tristate "Exynos ADC driver support" +	depends on ARCH_EXYNOS || (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 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 MCP3204/08" +	depends on SPI +	help +	  Say yes here to build support for Microchip Technology's 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 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_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 00000000000..9d60f2deaaa --- /dev/null +++ b/drivers/iio/adc/Makefile @@ -0,0 +1,30 @@ +# +# 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_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_EXYNOS_ADC) += exynos_adc.o +obj-$(CONFIG_LP8788_ADC) += lp8788_adc.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_TI_ADC081C) += ti-adc081c.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 00000000000..70f78c3062a --- /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/ad7298.c b/drivers/iio/adc/ad7298.c new file mode 100644 index 00000000000..2a3b65c74af --- /dev/null +++ b/drivers/iio/adc/ad7298.c @@ -0,0 +1,394 @@ +/* + * 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/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	(1 << 15) /* write to the control register */ +#define AD7298_REPEAT	(1 << 14) /* repeated conversion enable */ +#define AD7298_CH(x)	(1 << (13 - (x))) /* channel select */ +#define AD7298_TSENSE	(1 << 5) /* temperature conversion enable */ +#define AD7298_EXTREF	(1 << 2) /* external reference enable */ +#define AD7298_TAVG	(1 << 1) /* temperature sensor averaging enable */ +#define AD7298_PDD	(1 << 0) /* partial power down enable */ + +#define AD7298_MAX_CHAN		8 +#define AD7298_BITS		12 +#define AD7298_STORAGE_BITS	16 +#define AD7298_INTREF_mV	2500 + +#define AD7298_CH_TEMP		9 + +#define RES_MASK(bits)	((1 << (bits)) - 1) + +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 & RES_MASK(AD7298_BITS); + +		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 00000000000..d141d452c3d --- /dev/null +++ b/drivers/iio/adc/ad7476.c @@ -0,0 +1,316 @@ +/* + * 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/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 RES_MASK(bits)	((1 << (bits)) - 1) + +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) & +			RES_MASK(st->chip_info->channel[0].scan_type.realbits); +		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 00000000000..c19f8fd1b4b --- /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 00000000000..4dddeabdfbb --- /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 simialr ADCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ad7887.c b/drivers/iio/adc/ad7887.c new file mode 100644 index 00000000000..749a6cadab8 --- /dev/null +++ b/drivers/iio/adc/ad7887.c @@ -0,0 +1,370 @@ +/* + * 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/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		(1 << 5) /* on-chip reference disable */ +#define AD7887_DUAL		(1 << 4) /* dual-channel mode */ +#define AD7887_CH_AIN1		(1 << 3) /* convert on channel 1, DUAL=1 */ +#define AD7887_CH_AIN0		(0 << 3) /* 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, +}; + +#define RES_MASK(bits)	((1 << (bits)) - 1) + +/** + * 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 &= RES_MASK(chan->scan_type.realbits); +		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 00000000000..28732c28e81 --- /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 00000000000..6eba301ee03 --- /dev/null +++ b/drivers/iio/adc/ad799x.c @@ -0,0 +1,795 @@ +/* + * 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/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 +#define AD799X_STORAGEBITS			16 +/* + * 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				0x08 +#define AD7998_ALERT_EN				0x04 +#define AD7998_BUSY_ALERT			0x02 +#define AD7998_BUSY_ALERT_POL			0x01 + +#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				0x7 +#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			0x80 +#define AD7997_8_READ_SEQUENCE			0x70 +/* TODO: move this into a common header */ +#define RES_MASK(bits)	((1 << (bits)) - 1) + +enum { +	ad7991, +	ad7995, +	ad7999, +	ad7992, +	ad7993, +	ad7994, +	ad7997, +	ad7998 +}; + +/** + * struct ad799x_chip_info - chip specific information + * @channel:		channel specification + * @num_channels:	number of channels + * @monitor_mode:	whether the chip supports monitor interrupts + * @default_config:	device default configuration + * @event_attrs:	pointer to the monitor event attribute group + */ +struct ad799x_chip_info { +	struct iio_chan_spec		channel[9]; +	int				num_channels; +	u16				default_config; +	const struct iio_info		*info; +}; + +struct ad799x_state { +	struct i2c_client		*client; +	const struct ad799x_chip_info	*chip_info; +	struct regulator		*reg; +	struct regulator		*vref; +	unsigned			id; +	u16				config; + +	u8				*rx_buf; +	unsigned int			transfer_size; +}; + +/** + * 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; +} + +/* + * ad799x register access by I2C + */ +static int ad799x_i2c_read16(struct ad799x_state *st, u8 reg, u16 *data) +{ +	struct i2c_client *client = st->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 = (u16)ret; + +	return 0; +} + +static int ad799x_i2c_read8(struct ad799x_state *st, u8 reg, u8 *data) +{ +	struct i2c_client *client = st->client; +	int ret = 0; + +	ret = i2c_smbus_read_byte_data(client, reg); +	if (ret < 0) { +		dev_err(&client->dev, "I2C read error\n"); +		return ret; +	} + +	*data = (u8)ret; + +	return 0; +} + +static int ad799x_i2c_write16(struct ad799x_state *st, u8 reg, u16 data) +{ +	struct i2c_client *client = st->client; +	int ret = 0; + +	ret = i2c_smbus_write_word_swapped(client, reg, data); +	if (ret < 0) +		dev_err(&client->dev, "I2C write error\n"); + +	return ret; +} + +static int ad799x_i2c_write8(struct ad799x_state *st, u8 reg, u8 data) +{ +	struct i2c_client *client = st->client; +	int ret = 0; + +	ret = i2c_smbus_write_byte_data(client, reg, data); +	if (ret < 0) +		dev_err(&client->dev, "I2C write error\n"); + +	return ret; +} + +static int ad7997_8_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 ad7997: +	case ad7998: +		return ad799x_i2c_write16(st, AD7998_CONF_REG, +			st->config | (*scan_mask << AD799X_CHANNEL_SHIFT)); +	default: +		break; +	} + +	return 0; +} + +static int ad799x_scan_direct(struct ad799x_state *st, unsigned ch) +{ +	u16 rxbuf; +	u8 cmd; +	int ret; + +	switch (st->id) { +	case ad7991: +	case ad7995: +	case ad7999: +		cmd = st->config | ((1 << ch) << AD799X_CHANNEL_SHIFT); +		break; +	case ad7992: +	case ad7993: +	case ad7994: +		cmd = (1 << ch) << AD799X_CHANNEL_SHIFT; +		break; +	case ad7997: +	case ad7998: +		cmd = (ch << AD799X_CHANNEL_SHIFT) | AD7997_8_READ_SINGLE; +		break; +	default: +		return -EINVAL; +	} + +	ret = ad799x_i2c_read16(st, cmd, &rxbuf); +	if (ret < 0) +		return ret; + +	return rxbuf; +} + +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) & +			RES_MASK(chan->scan_type.realbits); +		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; +	u8 val; +	ret = ad799x_i2c_read8(st, AD7998_CYCLE_TMR_REG, &val); +	if (ret) +		return ret; + +	val &= AD7998_CYC_MASK; + +	return sprintf(buf, "%u\n", ad7998_frequencies[val]); +} + +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; +	u8 t; + +	ret = kstrtol(buf, 10, &val); +	if (ret) +		return ret; + +	mutex_lock(&indio_dev->mlock); +	ret = ad799x_i2c_read8(st, AD7998_CYCLE_TMR_REG, &t); +	if (ret) +		goto error_ret_mutex; +	/* Wipe the bits clean */ +	t &= ~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; +	} +	t |= i; +	ret = ad799x_i2c_write8(st, AD7998_CYCLE_TMR_REG, t); + +error_ret_mutex: +	mutex_unlock(&indio_dev->mlock); + +	return ret ? ret : len; +} + +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) +{ +	return 1; +} + +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 > RES_MASK(chan->scan_type.realbits)) +		return -EINVAL; + +	mutex_lock(&indio_dev->mlock); +	ret = ad799x_i2c_write16(st, 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); +	u16 valin; + +	mutex_lock(&indio_dev->mlock); +	ret = ad799x_i2c_read16(st, ad799x_threshold_reg(chan, dir, info), +		&valin); +	mutex_unlock(&indio_dev->mlock); +	if (ret < 0) +		return ret; +	*val = (valin >> chan->scan_type.shift) & +		RES_MASK(chan->scan_type.realbits); + +	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); +	u8 status; +	int i, ret; + +	ret = ad799x_i2c_read8(st, AD7998_ALERT_STAT_REG, &status); +	if (ret) +		goto done; + +	if (!status) +		goto done; + +	ad799x_i2c_write8(st, AD7998_ALERT_STAT_REG, AD7998_ALERT_STAT_CLEAR); + +	for (i = 0; i < 8; i++) { +		if (status & (1 << 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_info = { +	.read_raw = &ad799x_read_raw, +	.event_attrs = &ad799x_event_attrs_group, +	.read_event_config = &ad799x_read_event_config, +	.read_event_value = &ad799x_read_event_value, +	.write_event_value = &ad799x_write_event_value, +	.driver_module = THIS_MODULE, +	.update_scan_mode = ad7997_8_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] = { +		.channel = { +			AD799X_CHANNEL(0, 12), +			AD799X_CHANNEL(1, 12), +			AD799X_CHANNEL(2, 12), +			AD799X_CHANNEL(3, 12), +			IIO_CHAN_SOFT_TIMESTAMP(4), +		}, +		.num_channels = 5, +		.info = &ad7991_info, +	}, +	[ad7995] = { +		.channel = { +			AD799X_CHANNEL(0, 10), +			AD799X_CHANNEL(1, 10), +			AD799X_CHANNEL(2, 10), +			AD799X_CHANNEL(3, 10), +			IIO_CHAN_SOFT_TIMESTAMP(4), +		}, +		.num_channels = 5, +		.info = &ad7991_info, +	}, +	[ad7999] = { +		.channel = { +			AD799X_CHANNEL(0, 8), +			AD799X_CHANNEL(1, 8), +			AD799X_CHANNEL(2, 8), +			AD799X_CHANNEL(3, 8), +			IIO_CHAN_SOFT_TIMESTAMP(4), +		}, +		.num_channels = 5, +		.info = &ad7991_info, +	}, +	[ad7992] = { +		.channel = { +			AD799X_CHANNEL_WITH_EVENTS(0, 12), +			AD799X_CHANNEL_WITH_EVENTS(1, 12), +			IIO_CHAN_SOFT_TIMESTAMP(3), +		}, +		.num_channels = 3, +		.default_config = AD7998_ALERT_EN, +		.info = &ad7993_4_7_8_info, +	}, +	[ad7993] = { +		.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), +		}, +		.num_channels = 5, +		.default_config = AD7998_ALERT_EN, +		.info = &ad7993_4_7_8_info, +	}, +	[ad7994] = { +		.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), +		}, +		.num_channels = 5, +		.default_config = AD7998_ALERT_EN, +		.info = &ad7993_4_7_8_info, +	}, +	[ad7997] = { +		.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), +		}, +		.num_channels = 9, +		.default_config = AD7998_ALERT_EN, +		.info = &ad7993_4_7_8_info, +	}, +	[ad7998] = { +		.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), +		}, +		.num_channels = 9, +		.default_config = AD7998_ALERT_EN, +		.info = &ad7993_4_7_8_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; + +	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; +	st->chip_info = &ad799x_chip_info_tbl[st->id]; +	st->config = st->chip_info->default_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_info->info; + +	indio_dev->modes = INDIO_DIRECT_MODE; +	indio_dev->channels = st->chip_info->channel; +	indio_dev->num_channels = st->chip_info->num_channels; + +	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 00000000000..9a4e0e32a77 --- /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, iio_get_time_ns()); + +	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 = 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 00000000000..2b6a9ce9927 --- /dev/null +++ b/drivers/iio/adc/at91_adc.c @@ -0,0 +1,1437 @@ +/* + * 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)(u8 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; +	struct mutex		lock; +	u8			num_channels; +	void __iomem		*reg_base; +	struct at91_adc_reg_desc *registers; +	u8			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 */ +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, iio_get_time_ns()); +	} else { +		st->last_value = at91_adc_readl(st, AT91_ADC_LCDR); +		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 & st->registers->drdy_mask) +		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 & st->registers->drdy_mask) +		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 iio_buffer *buffer = idev->buffer; +	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, buffer->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, buffer->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); + +		at91_adc_writel(st, AT91_ADC_CHER, +				AT91_ADC_CH(chan->channel)); +		at91_adc_writel(st, AT91_ADC_IER, st->registers->drdy_mask); +		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, st->registers->drdy_mask); + +		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(u8 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(u8 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/exynos_adc.c b/drivers/iio/adc/exynos_adc.c new file mode 100644 index 00000000000..010578f1d76 --- /dev/null +++ b/drivers/iio/adc/exynos_adc.c @@ -0,0 +1,456 @@ +/* + *  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/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> + +enum adc_version { +	ADC_V1, +	ADC_V2 +}; + +/* 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) + +/* 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 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 + +/* Bit definitions common for ADC_V1 and ADC_V2 */ +#define ADC_CON_EN_START	(1u << 0) +#define ADC_DATX_MASK		0xFFF + +#define EXYNOS_ADC_TIMEOUT	(msecs_to_jiffies(100)) + +struct exynos_adc { +	void __iomem		*regs; +	void __iomem		*enable_reg; +	struct clk		*clk; +	unsigned int		irq; +	struct regulator	*vdd; + +	struct completion	completion; + +	u32			value; +	unsigned int            version; +}; + +static const struct of_device_id exynos_adc_match[] = { +	{ .compatible = "samsung,exynos-adc-v1", .data = (void *)ADC_V1 }, +	{ .compatible = "samsung,exynos-adc-v2", .data = (void *)ADC_V2 }, +	{}, +}; +MODULE_DEVICE_TABLE(of, exynos_adc_match); + +static inline unsigned int exynos_adc_get_version(struct platform_device *pdev) +{ +	const struct of_device_id *match; + +	match = of_match_node(exynos_adc_match, pdev->dev.of_node); +	return (unsigned int)match->data; +} + +static void exynos_adc_hw_init(struct exynos_adc *info) +{ +	u32 con1, con2; + +	if (info->version == ADC_V2) { +		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)); +	} else { +		/* 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 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; +	u32 con1, con2; +	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->version == ADC_V2) { +		con2 = readl(ADC_V2_CON2(info->regs)); +		con2 &= ~ADC_V2_CON2_ACH_MASK; +		con2 |= ADC_V2_CON2_ACH_SEL(chan->address); +		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)); +	} else { +		writel(chan->address, ADC_V1_MUX(info->regs)); + +		con1 = readl(ADC_V1_CON(info->regs)); +		writel(con1 | ADC_CON_EN_START, +				ADC_V1_CON(info->regs)); +	} + +	timeout = wait_for_completion_timeout +			(&info->completion, EXYNOS_ADC_TIMEOUT); +	if (timeout == 0) { +		dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); +		exynos_adc_hw_init(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; + +	/* Read value */ +	info->value = readl(ADC_V1_DATX(info->regs)) & +						ADC_DATX_MASK; +	/* clear irq */ +	if (info->version == ADC_V2) +		writel(1, ADC_V2_INT_ST(info->regs)); +	else +		writel(1, ADC_V1_INTCLR(info->regs)); + +	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); + +	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); + +	mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); +	info->enable_reg = devm_ioremap_resource(&pdev->dev, mem); +	if (IS_ERR(info->enable_reg)) +		return PTR_ERR(info->enable_reg); + +	irq = platform_get_irq(pdev, 0); +	if (irq < 0) { +		dev_err(&pdev->dev, "no irq resource?\n"); +		return irq; +	} + +	info->irq = irq; + +	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); +	} + +	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 = clk_prepare_enable(info->clk); +	if (ret) +		goto err_disable_reg; + +	writel(1, info->enable_reg); + +	info->version = exynos_adc_get_version(pdev); + +	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; + +	if (info->version == ADC_V1) +		indio_dev->num_channels = MAX_ADC_V1_CHANNELS; +	else +		indio_dev->num_channels = MAX_ADC_V2_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; + +	exynos_adc_hw_init(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: +	writel(0, info->enable_reg); +	clk_disable_unprepare(info->clk); +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); +	writel(0, info->enable_reg); +	clk_disable_unprepare(info->clk); +	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); +	u32 con; + +	if (info->version == ADC_V2) { +		con = readl(ADC_V2_CON1(info->regs)); +		con &= ~ADC_CON_EN_START; +		writel(con, ADC_V2_CON1(info->regs)); +	} else { +		con = readl(ADC_V1_CON(info->regs)); +		con |= ADC_V1_CON_STANDBY; +		writel(con, ADC_V1_CON(info->regs)); +	} + +	writel(0, info->enable_reg); +	clk_disable_unprepare(info->clk); +	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 = clk_prepare_enable(info->clk); +	if (ret) +		return ret; + +	writel(1, info->enable_reg); +	exynos_adc_hw_init(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", +		.owner	= THIS_MODULE, +		.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 00000000000..5c8c91595f4 --- /dev/null +++ b/drivers/iio/adc/lp8788_adc.c @@ -0,0 +1,255 @@ +/* + * 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, +		.owner = THIS_MODULE, +	}, +}; +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/max1363.c b/drivers/iio/adc/max1363.c new file mode 100644 index 00000000000..1b3b74be5c2 --- /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 00000000000..28a086e4877 --- /dev/null +++ b/drivers/iio/adc/mcp320x.c @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2013 Oskar Andero <oskar.andero@gmail.com> + * + * Driver for Microchip Technology's MCP3204 and MCP3208 ADC chips. + * Datasheet can be found here: + * http://ww1.microchip.com/downloads/en/devicedoc/21298c.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> + +#define MCP_SINGLE_ENDED	(1 << 3) +#define MCP_START_BIT		(1 << 4) + +enum { +	mcp3204, +	mcp3208, +}; + +struct mcp320x { +	struct spi_device *spi; +	struct spi_message msg; +	struct spi_transfer transfer[2]; + +	u8 tx_buf; +	u8 rx_buf[2]; + +	struct regulator *reg; +	struct mutex lock; +}; + +static int mcp320x_adc_conversion(struct mcp320x *adc, u8 msg) +{ +	int ret; + +	adc->tx_buf = msg; +	ret = spi_sync(adc->spi, &adc->msg); +	if (ret < 0) +		return ret; + +	return ((adc->rx_buf[0] & 0x3f) << 6)  | +		(adc->rx_buf[1] >> 2); +} + +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; + +	mutex_lock(&adc->lock); + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		if (channel->differential) +			ret = mcp320x_adc_conversion(adc, +				MCP_START_BIT | channel->address); +		else +			ret = mcp320x_adc_conversion(adc, +				MCP_START_BIT | MCP_SINGLE_ENDED | +				channel->address); +		if (ret < 0) +			goto out; + +		*val = ret; +		ret = IIO_VAL_INT; +		break; + +	case IIO_CHAN_INFO_SCALE: +		/* Digital output code = (4096 * Vin) / Vref */ +		ret = regulator_get_voltage(adc->reg); +		if (ret < 0) +			goto out; + +		*val = ret / 1000; +		*val2 = 12; +		ret = IIO_VAL_FRACTIONAL_LOG2; +		break; + +	default: +		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 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, +}; + +struct mcp3208_chip_info { +	const struct iio_chan_spec *channels; +	unsigned int num_channels; +}; + +static const struct mcp3208_chip_info mcp3208_chip_infos[] = { +	[mcp3204] = { +		.channels = mcp3204_channels, +		.num_channels = ARRAY_SIZE(mcp3204_channels) +	}, +	[mcp3208] = { +		.channels = mcp3208_channels, +		.num_channels = ARRAY_SIZE(mcp3208_channels) +	}, +}; + +static int mcp320x_probe(struct spi_device *spi) +{ +	struct iio_dev *indio_dev; +	struct mcp320x *adc; +	const struct mcp3208_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 = &mcp3208_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; +} + +static const struct spi_device_id mcp320x_id[] = { +	{ "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 MCP3204/08"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/mcp3422.c b/drivers/iio/adc/mcp3422.c new file mode 100644 index 00000000000..51672256072 --- /dev/null +++ b/drivers/iio/adc/mcp3422.c @@ -0,0 +1,426 @@ +/* + * 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), \ +	} + +/* LSB is in nV to eliminate floating point */ +static const u32 rates_to_lsb[] = {1000000, 250000, 62500, 15625}; + +/* + *  scales calculated as: + *  rates_to_lsb[sample_rate] / (1 << pga); + *  pga is 1 for 0, 2 + */ + +static const int mcp3422_scales[4][4] = { +	{ 1000000, 250000, 62500, 15625 }, +	{ 500000 , 125000, 31250, 7812 }, +	{ 250000 , 62500 , 15625, 3906 }, +	{ 125000 , 31250 , 7812 , 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 00000000000..b58d6302521 --- /dev/null +++ b/drivers/iio/adc/men_z188_adc.c @@ -0,0 +1,172 @@ +/* + * 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 00000000000..e525aa6475c --- /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/ti-adc081c.c b/drivers/iio/adc/ti-adc081c.c new file mode 100644 index 00000000000..b3a82b4d1a7 --- /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_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c new file mode 100644 index 00000000000..d5dc4c6ce86 --- /dev/null +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -0,0 +1,559 @@ +/* + * 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; + +	/* +	 * There are 16 configurable steps and 8 analog input +	 * lines available which are shared between Touchscreen and ADC. +	 * +	 * Steps backwards i.e. from 16 towards 0 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. +	 */ + +	steps = TOTAL_STEPS - adc_dev->channels; +	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); +	struct iio_buffer *buffer = indio_dev->buffer; +	unsigned int enb = 0; +	u8 bit; + +	tiadc_step_config(indio_dev); +	for_each_set_bit(bit, buffer->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(indio_dev); +	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; + +	ret = iio_buffer_register(indio_dev, +				  indio_dev->channels, +				  indio_dev->num_channels); +	if (ret) +		goto error_free_irq; + +	return 0; + +error_free_irq: +	free_irq(irq, indio_dev); +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); +	iio_buffer_unregister(indio_dev); +} + + +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", +		.owner	= THIS_MODULE, +		.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 00000000000..eb86786e698 --- /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, "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", +		   .owner = THIS_MODULE, +		   .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 00000000000..15282f148b3 --- /dev/null +++ b/drivers/iio/adc/twl6030-gpadc.c @@ -0,0 +1,1010 @@ +/* + * 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, +		.owner	= THIS_MODULE, +		.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 00000000000..44799eb5930 --- /dev/null +++ b/drivers/iio/adc/vf610_adc.c @@ -0,0 +1,711 @@ +/* + * 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) & 0xF) +#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; + +	struct completion completion; +}; + +#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),	\ +} + +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), +	/* sentinel */ +}; + +/* + * ADC sample frequency, unit is ADCK cycles. + * ADC 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 + * + * By default, enable 12 bit resolution mode, clock source + * set to ipg clock, So get below frequency group: + */ +static const u32 vf610_sample_freq_avail[5] = +{1941176, 559332, 286957, 145374, 73171}; + +static inline void vf610_adc_cfg_init(struct vf610_adc *info) +{ +	/* set default Configuration for ADC controller */ +	info->adc_feature.clk_sel = VF610_ADCIOC_BUSCLK_SET; +	info->adc_feature.vol_ref = VF610_ADCIOC_VR_VREF_SET; + +	info->adc_feature.calibration = true; +	info->adc_feature.ovwren = true; + +	info->adc_feature.clk_div = 1; +	info->adc_feature.res_mode = 12; +	info->adc_feature.sample_rate = 1; +	info->adc_feature.lpm = true; +} + +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; +	int timeout; + +	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); + +	timeout = wait_for_completion_timeout +			(&info->completion, VF610_ADC_TIMEOUT); +	if (timeout == 0) +		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); + +	/* low power configuration */ +	cfg_data &= ~VF610_ADC_ADLPC_EN; +	if (adc_feature->lpm) +		cfg_data |= VF610_ADC_ADLPC_EN; + +	/* disable high speed */ +	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 IIO_CONST_ATTR_SAMP_FREQ_AVAIL("1941176, 559332, 286957, 145374, 73171"); + +static struct attribute *vf610_attributes[] = { +	&iio_const_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: +		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; +		} + +		*val = info->value; +		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 = vf610_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(vf610_sample_freq_avail); +				i++) +				if (val == vf610_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 -EINVAL; +	} + +	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)); +		ret = PTR_ERR(info->clk); +		return ret; +	} + +	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) +		return ret; + +	vf610_adc_hw_init(info); + +	return 0; +} +#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, +		.owner  = THIS_MODULE, +		.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 00000000000..9acf6b6d705 --- /dev/null +++ b/drivers/iio/adc/viperboard_adc.c @@ -0,0 +1,158 @@ +/* + *  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", +		.owner	= THIS_MODULE, +	}, +	.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 00000000000..ab52be29141 --- /dev/null +++ b/drivers/iio/adc/xilinx-xadc-core.c @@ -0,0 +1,1333 @@ +/* + * 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, 0); + +	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_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 = '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_VCCINT, "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->scan_index = 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)) +			goto err_triggered_buffer_cleanup; +		xadc->samplerate_trigger = xadc_alloc_trigger(indio_dev, +			"samplerate"); +		if (IS_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", +		.owner = THIS_MODULE, +		.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 00000000000..3e7f0d7a80c --- /dev/null +++ b/drivers/iio/adc/xilinx-xadc-events.c @@ -0,0 +1,254 @@ +/* + * 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; +	unsigned int offset; + +	/* Temperature threshold error, we don't handle this yet */ +	if (event == 0) +		return; + +	if (event < 4) +		offset = event; +	else +		offset = event + 4; + +	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 00000000000..c7487e8d7f8 --- /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	0x2b +#define XADC_REG_MIN_VCCPAUX	0x2c +#define XADC_REG_MIN_VCCO_DDR	0x2d + +#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 00000000000..e9c5f2cd925 --- /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 00000000000..8da4b787898 --- /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 00000000000..ba6f6a91dff --- /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, char unsigned 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 00000000000..eb46e728aa2 --- /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 00000000000..0b6e97d18fa --- /dev/null +++ b/drivers/iio/common/Kconfig @@ -0,0 +1,6 @@ +# +# IIO common modules +# + +source "drivers/iio/common/hid-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 00000000000..3112df0060e --- /dev/null +++ b/drivers/iio/common/Makefile @@ -0,0 +1,11 @@ +# +# 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 += st_sensors/ diff --git a/drivers/iio/common/hid-sensors/Kconfig b/drivers/iio/common/hid-sensors/Kconfig new file mode 100644 index 00000000000..39188b72cd3 --- /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 00000000000..22e7c5a8232 --- /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 00000000000..403dd3d8986 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -0,0 +1,396 @@ +/* + * 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> + +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 */ +} static 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, &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, &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, 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, &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, 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); + +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 00000000000..a3109a6f4d8 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -0,0 +1,138 @@ +/* + * 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/trigger.h> +#include <linux/iio/sysfs.h> +#include "hid-sensor-trigger.h" + +int hid_sensor_power_state(struct hid_sensor_common *st, bool state) +{ +	int state_val; +	int report_val; + +	if (state) { +		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); +	} else { +		if (!atomic_dec_and_test(&st->data_ready)) +			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, +					(s32)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, +					(s32)report_val); +	} + +	sensor_hub_get_feature(st->hsdev, st->power_state.report_id, +					st->power_state.index, +					&state_val); +	return 0; +} +EXPORT_SYMBOL(hid_sensor_power_state); + +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; +	} +	indio_dev->trig = attrb->trigger = trig; + +	return ret; + +error_free_trig: +	iio_trigger_free(trig); +error_ret: +	return ret; +} +EXPORT_SYMBOL(hid_sensor_setup_trigger); + +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 00000000000..0f8e78c249d --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h @@ -0,0 +1,27 @@ +/* + * 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 + +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/st_sensors/Kconfig b/drivers/iio/common/st_sensors/Kconfig new file mode 100644 index 00000000000..865f1ca33eb --- /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 00000000000..9f3e24f3024 --- /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 00000000000..1665c8e4b62 --- /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 * num_data_channels; 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 00000000000..e8b932fed70 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -0,0 +1,541 @@ +/* + * 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 <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_sensors *sensor, +			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->odr.odr_avl[i].hz == 0) +			goto st_sensors_match_odr_error; + +		if (sensor->odr.odr_avl[i].hz == odr) { +			odr_out->hz = sensor->odr.odr_avl[i].hz; +			odr_out->value = sensor->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, odr, &odr_out); +	if (err < 0) +		goto st_sensors_match_odr_error; + +	if ((sdata->sensor->odr.addr == sdata->sensor->pw.addr) && +			(sdata->sensor->odr.mask == sdata->sensor->pw.mask)) { +		if (sdata->enabled == true) { +			err = st_sensors_write_data_with_mask(indio_dev, +				sdata->sensor->odr.addr, +				sdata->sensor->odr.mask, +				odr_out.value); +		} else { +			err = 0; +		} +	} else { +		err = st_sensors_write_data_with_mask(indio_dev, +			sdata->sensor->odr.addr, sdata->sensor->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_sensors *sensor, +					unsigned int fs, int *index_fs_avl) +{ +	int i, ret = -EINVAL; + +	for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) { +		if (sensor->fs.fs_avl[i].num == 0) +			goto st_sensors_match_odr_error; + +		if (sensor->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, fs, &i); +	if (err < 0) +		goto st_accel_set_fullscale_error; + +	err = st_sensors_write_data_with_mask(indio_dev, +				sdata->sensor->fs.addr, +				sdata->sensor->fs.mask, +				sdata->sensor->fs.fs_avl[i].value); +	if (err < 0) +		goto st_accel_set_fullscale_error; + +	sdata->current_fullscale = (struct st_sensor_fullscale_avl *) +						&sdata->sensor->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->pw.value_on; +		if ((sdata->sensor->odr.addr == sdata->sensor->pw.addr) && +			(sdata->sensor->odr.mask == sdata->sensor->pw.mask)) { +			err = st_sensors_match_odr(sdata->sensor, +							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->pw.addr, +				sdata->sensor->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->pw.addr, +				sdata->sensor->pw.mask, +				sdata->sensor->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->enable_axis.addr, +				sdata->sensor->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->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->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; +} + +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); +	int err = 0; + +	mutex_init(&sdata->tb.buf_lock); + +	if (pdata) +		err = st_sensors_set_drdy_int_pin(indio_dev, pdata); + +	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->bdu.addr, sdata->sensor->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->drdy_irq.addr) +		return 0; + +	/* Enable/Disable the interrupt generator 1. */ +	if (sdata->sensor->drdy_irq.ig1.en_addr > 0) { +		err = st_sensors_write_data_with_mask(indio_dev, +			sdata->sensor->drdy_irq.ig1.en_addr, +			sdata->sensor->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->drdy_irq.mask_int1; +	else +		drdy_mask = sdata->sensor->drdy_irq.mask_int2; + +	/* Enable/Disable the interrupt generator for data ready. */ +	err = st_sensors_write_data_with_mask(indio_dev, +			sdata->sensor->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->fs.fs_avl[i].gain == scale) && +				(sdata->sensor->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->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->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_sensors *sensors) +{ +	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 (sensors[i].wai == wai) +			break; +	} +	if (i == num_sensors_list) +		goto device_not_supported; + +	for (n = 0; n < ARRAY_SIZE(sensors[i].sensors_supported); n++) { +		if (strcmp(indio_dev->name, +				&sensors[i].sensors_supported[n][0]) == 0) +			break; +	} +	if (n == ARRAY_SIZE(sensors[i].sensors_supported)) { +		dev_err(&indio_dev->dev, "device name and WhoAmI mismatch.\n"); +		goto sensor_name_mismatch; +	} + +	sdata->sensor = (struct st_sensors *)&sensors[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_get_sampling_frequency(struct device *dev, +				struct device_attribute *attr, char *buf) +{ +	struct st_sensor_data *adata = iio_priv(dev_get_drvdata(dev)); + +	return sprintf(buf, "%d\n", adata->odr); +} +EXPORT_SYMBOL(st_sensors_sysfs_get_sampling_frequency); + +ssize_t st_sensors_sysfs_set_sampling_frequency(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t size) +{ +	int err; +	unsigned int odr; +	struct iio_dev *indio_dev = dev_get_drvdata(dev); + +	err = kstrtoint(buf, 10, &odr); +	if (err < 0) +		goto conversion_error; + +	mutex_lock(&indio_dev->mlock); +	err = st_sensors_set_odr(indio_dev, odr); +	mutex_unlock(&indio_dev->mlock); + +conversion_error: +	return err < 0 ? err : size; +} +EXPORT_SYMBOL(st_sensors_sysfs_set_sampling_frequency); + +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->odr.odr_avl[i].hz == 0) +			break; + +		len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", +					sdata->sensor->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->fs.fs_avl[i].num == 0) +			break; + +		len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ", +					sdata->sensor->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 00000000000..38af9440c10 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_i2c.c @@ -0,0 +1,81 @@ +/* + * 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/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->tf = &st_sensors_tf_i2c; +	sdata->get_irq_data_ready = st_sensors_i2c_get_irq; +} +EXPORT_SYMBOL(st_sensors_i2c_configure); + +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 00000000000..251baf6abc2 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_spi.c @@ -0,0 +1,121 @@ +/* + * 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*sizeof(u8)); +	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->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 00000000000..8fc3a97eb26 --- /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 = 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 00000000000..f378ca8033d --- /dev/null +++ b/drivers/iio/dac/Kconfig @@ -0,0 +1,166 @@ +# +# 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 DAC driver" +	depends on I2C +	help +	  If you say yes here you get support for the Maxim chips MAX517, +	  MAX518 and MAX519 (I2C 8-Bit DACs with rail-to-rail outputs). + +	  This driver can also be built as a module.  If so, the module +	  will be called max517. + +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. + +endmenu diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile new file mode 100644 index 00000000000..bb84ad64463 --- /dev/null +++ b/drivers/iio/dac/Makefile @@ -0,0 +1,20 @@ +# +# 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_MCP4725) += mcp4725.o diff --git a/drivers/iio/dac/ad5064.c b/drivers/iio/dac/ad5064.c new file mode 100644 index 00000000000..f03b92fd380 --- /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 00000000000..64634d7f578 --- /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 00000000000..9de4c4d3828 --- /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 00000000000..787ef1d859c --- /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 00000000000..46bb62a5c1d --- /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 00000000000..64d7256cbb6 --- /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 00000000000..1e6449346b5 --- /dev/null +++ b/drivers/iio/dac/ad5504.c @@ -0,0 +1,381 @@ +/* + * 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/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/events.h> +#include <linux/iio/dac/ad5504.h> + +#define AD5505_BITS			12 +#define AD5504_RES_MASK			((1 << (AD5505_BITS)) - 1) + +#define AD5504_CMD_READ			(1 << 15) +#define AD5504_CMD_WRITE		(0 << 15) +#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 + * @us:			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); +	int ret; + +	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: +		ret = -EINVAL; +	} + +	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 00000000000..5dca3028cdf --- /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 00000000000..e8199cce2ae --- /dev/null +++ b/drivers/iio/dac/ad5624r_spi.c @@ -0,0 +1,322 @@ +/* + * 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 len) +{ +	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 << (16 - len)); +	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); +	int ret; + +	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: +		ret = -EINVAL; +	} + +	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 00000000000..17aca4d9bd0 --- /dev/null +++ b/drivers/iio/dac/ad5686.c @@ -0,0 +1,409 @@ +/* + * 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, regdone = 0, 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(&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; + +	regdone = 1; +	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 00000000000..a7c851f62d7 --- /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 00000000000..d0d38165339 --- /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 00000000000..ae49afe2b38 --- /dev/null +++ b/drivers/iio/dac/ad5791.c @@ -0,0 +1,475 @@ +/* + * 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/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/dac/ad5791.h> + +#define AD5791_RES_MASK(x)		((1 << (x)) - 1) +#define AD5791_DAC_MASK			AD5791_RES_MASK(20) +#define AD5791_DAC_MSB			(1 << 19) + +#define AD5791_CMD_READ			(1 << 23) +#define AD5791_CMD_WRITE		(0 << 23) +#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		(1 << 1) +#define AD5791_CTRL_OPGND		(1 << 2) +#define AD5791_CTRL_DACTRI		(1 << 3) +#define AD5791_CTRL_BIN2SC		(1 << 4) +#define AD5791_CTRL_SDODIS		(1 << 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		(1 << 0) +#define AD5791_SWCTRL_CLR		(1 << 1) +#define AD5791_SWCTRL_RESET		(1 << 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 + * @us:			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 &= AD5791_RES_MASK(chan->scan_type.realbits); +		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 00000000000..fa281003296 --- /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 00000000000..9a82a7255eb --- /dev/null +++ b/drivers/iio/dac/max517.c @@ -0,0 +1,222 @@ +/* + *  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, +}; + +struct max517_data { +	struct i2c_client	*client; +	unsigned short		vref_mv[2]; +}; + +/* + * 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) +}; + +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; + +	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; + +	/* reduced channel set for MAX517 */ +	if (id->driver_data == ID_MAX517) +		indio_dev->num_channels = 1; +	else +		indio_dev->num_channels = 2; +	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 +	 */ +	if (id->driver_data == ID_MAX518 || !platform_data) { +		data->vref_mv[0] = data->vref_mv[1] = 5000; /* mV */ +	} else { +		data->vref_mv[0] = platform_data->vref_mv[0]; +		data->vref_mv[1] = platform_data->vref_mv[1]; +	} + +	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 }, +	{ } +}; +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/MAX518/MAX519 8-bit DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp4725.c b/drivers/iio/dac/mcp4725.c new file mode 100644 index 00000000000..43d14588448 --- /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/frequency/Kconfig b/drivers/iio/frequency/Kconfig new file mode 100644 index 00000000000..dc5e0b72882 --- /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 00000000000..2bca03f3e2e --- /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 00000000000..7c5245d9f99 --- /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(16); +		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 00000000000..63a25d9e120 --- /dev/null +++ b/drivers/iio/frequency/adf4350.c @@ -0,0 +1,642 @@ +/* + * 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) { +		dev_err(dev, "could not allocate memory for platform data\n"); +		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 00000000000..ac2d69e34c8 --- /dev/null +++ b/drivers/iio/gyro/Kconfig @@ -0,0 +1,101 @@ +# +# 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 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 00000000000..2f2752a4ea8 --- /dev/null +++ b/drivers/iio/gyro/Makefile @@ -0,0 +1,23 @@ +# +# 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_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_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 00000000000..add50983726 --- /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 00000000000..8d08c7ed1ea --- /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 00000000000..591bd555e1f --- /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 00000000000..22b6fb80fa1 --- /dev/null +++ b/drivers/iio/gyro/adis16260.c @@ -0,0 +1,421 @@ +/* + * 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 + +static ssize_t adis16260_read_frequency(struct device *dev, +		struct device_attribute *attr, +		char *buf) +{ +	struct iio_dev *indio_dev = dev_to_iio_dev(dev); +	struct adis *adis = iio_priv(indio_dev); +	int ret, len = 0; +	u16 t; +	int sps; +	ret = adis_read_reg_16(adis, ADIS16260_SMPL_PRD, &t); +	if (ret) +		return ret; + +	if (spi_get_device_id(adis->spi)->driver_data) /* If an adis16251 */ +		sps = (t & ADIS16260_SMPL_PRD_TIME_BASE) ? 8 : 256; +	else +		sps = (t & ADIS16260_SMPL_PRD_TIME_BASE) ? 66 : 2048; +	sps /= (t & ADIS16260_SMPL_PRD_DIV_MASK) + 1; +	len = sprintf(buf, "%d\n", sps); +	return len; +} + +static ssize_t adis16260_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 adis *adis = iio_priv(indio_dev); +	unsigned int val; +	int ret; +	u8 t; + +	ret = kstrtouint(buf, 10, &val); +	if (ret) +		return ret; + +	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 ? ret : len; +} + +/* 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 IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, +		adis16260_read_frequency, +		adis16260_write_frequency); + +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), 14), +	ADIS_INCLI_CHAN(X, ADIS16260_ANGL_OUT, ADIS16260_SCAN_ANGL, 0, 14), +	ADIS_TEMP_CHAN(ADIS16260_TEMP_OUT, ADIS16260_SCAN_TEMP, 12), +	ADIS_SUPPLY_CHAN(ADIS16260_SUPPLY_OUT, ADIS16260_SCAN_SUPPLY, 12), +	ADIS_AUX_ADC_CHAN(ADIS16260_AUX_ADC, ADIS16260_SCAN_AUX_ADC, 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; +	} +	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); +	u8 addr; + +	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); +	} +	return -EINVAL; +} + +static struct attribute *adis16260_attributes[] = { +	&iio_dev_attr_sampling_frequency.dev_attr.attr, +	NULL +}; + +static const struct attribute_group adis16260_attribute_group = { +	.attrs = adis16260_attributes, +}; + +static const struct iio_info adis16260_info = { +	.attrs = &adis16260_attribute_group, +	.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 00000000000..eb0e08ec9e2 --- /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/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c new file mode 100644 index 00000000000..fa034a3dad7 --- /dev/null +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c @@ -0,0 +1,428 @@ +/* + * 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; +	s32 poll_value; + +	*val = 0; +	*val2 = 0; +	switch (mask) { +	case 0: +		poll_value = hid_sensor_read_poll_value( +					&gyro_state->common_attributes); +		if (poll_value < 0) +			return -EINVAL; + +		hid_sensor_power_state(&gyro_state->common_attributes, true); +		msleep_interruptible(poll_value * 2); +		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); +		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, +		.owner	= THIS_MODULE, +	}, +	.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 00000000000..e3b3c508407 --- /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 = 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 00000000000..8295e318399 --- /dev/null +++ b/drivers/iio/gyro/itg3200_core.c @@ -0,0 +1,389 @@ +/* + * 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; + +	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; +	default: +		return -EINVAL; +	} +} + +static ssize_t itg3200_read_frequency(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct iio_dev *indio_dev = dev_to_iio_dev(dev); +	int ret, sps; +	u8 val; + +	ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, &val); +	if (ret) +		return ret; + +	sps = (val & ITG3200_DLPF_CFG_MASK) ? 1000 : 8000; + +	ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_SAMPLE_RATE_DIV, &val); +	if (ret) +		return ret; + +	sps /= val + 1; + +	return sprintf(buf, "%d\n", sps); +} + +static ssize_t itg3200_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); +	unsigned val; +	int ret; +	u8 t; + +	ret = kstrtouint(buf, 10, &val); +	if (ret) +		return ret; + +	mutex_lock(&indio_dev->mlock); + +	ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_DLPF, &t); +	if (ret) +		goto err_ret; + +	if (val == 0) { +		ret = -EINVAL; +		goto err_ret; +	} +	t = ((t & ITG3200_DLPF_CFG_MASK) ? 1000u : 8000u) / val - 1; + +	ret = itg3200_write_reg_8(indio_dev, ITG3200_REG_SAMPLE_RATE_DIV, t); + +err_ret: +	mutex_unlock(&indio_dev->mlock); + +	return ret ? ret : len; +} + +/* + * 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_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_reset(indio_dev); +	if (ret) +		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), \ +	.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), +		.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), +}; + +/* IIO device attributes */ +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, itg3200_read_frequency, +		itg3200_write_frequency); + +static struct attribute *itg3200_attributes[] = { +	&iio_dev_attr_sampling_frequency.dev_attr.attr, +	NULL +}; + +static const struct attribute_group itg3200_attribute_group = { +	.attrs = itg3200_attributes, +}; + +static const struct iio_info itg3200_info = { +	.attrs = &itg3200_attribute_group, +	.read_raw = &itg3200_read_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 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", +	}, +	.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/st_gyro.h b/drivers/iio/gyro/st_gyro.h new file mode 100644 index 00000000000..c197360c450 --- /dev/null +++ b/drivers/iio/gyro/st_gyro.h @@ -0,0 +1,53 @@ +/* + * 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, +					struct st_sensors_platform_data *pdata); +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 00000000000..d67b17b6a7a --- /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 00000000000..ed74a906998 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_core.c @@ -0,0 +1,380 @@ +/* + * 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 + +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_sensors st_gyro_sensors[] = { +	{ +		.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, +	}, +}; + +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; +	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; +	default: +		err = -EINVAL; +	} + +	return err; +} + +static ST_SENSOR_DEV_ATTR_SAMP_FREQ(); +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, +	&iio_dev_attr_sampling_frequency.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_sensors_platform_data *pdata) +{ +	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; + +	st_sensors_power_enable(indio_dev); + +	err = st_sensors_check_device_support(indio_dev, +				ARRAY_SIZE(st_gyro_sensors), st_gyro_sensors); +	if (err < 0) +		return err; + +	gdata->num_data_channels = ST_GYRO_NUMBER_DATA_CHANNELS; +	gdata->multiread_bit = gdata->sensor->multi_read_bit; +	indio_dev->channels = gdata->sensor->ch; +	indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + +	gdata->current_fullscale = (struct st_sensor_fullscale_avl *) +						&gdata->sensor->fs.fs_avl[0]; +	gdata->odr = gdata->sensor->odr.odr_avl[0].hz; + +	err = st_sensors_init_sensor(indio_dev, 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 00000000000..23c12f361b0 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_i2c.c @@ -0,0 +1,77 @@ +/* + * 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" + +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); +	gdata->dev = &client->dev; + +	st_sensors_i2c_configure(indio_dev, client, gdata); + +	err = st_gyro_common_probe(indio_dev, +				(struct st_sensors_platform_data *)&gyro_pdata); +	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", +	}, +	.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 00000000000..b4ad3be2668 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_spi.c @@ -0,0 +1,76 @@ +/* + * 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); +	gdata->dev = &spi->dev; + +	st_sensors_spi_configure(indio_dev, spi, gdata); + +	err = st_gyro_common_probe(indio_dev, +				(struct st_sensors_platform_data *)&gyro_pdata); +	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 00000000000..e116bd8dd0e --- /dev/null +++ b/drivers/iio/humidity/Kconfig @@ -0,0 +1,25 @@ +# +# 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. + +endmenu diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile new file mode 100644 index 00000000000..e3f3d942e64 --- /dev/null +++ b/drivers/iio/humidity/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for IIO humidity sensor drivers +# + +obj-$(CONFIG_DHT11) += dht11.o +obj-$(CONFIG_SI7005) += si7005.o diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c new file mode 100644 index 00000000000..d8771f546bf --- /dev/null +++ b/drivers/iio/humidity/dht11.c @@ -0,0 +1,294 @@ +/* + * 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/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 4 +#define DHT11_BITS_PER_READ 40 +#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; + +	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 = 0; 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; +} + +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; + +	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 = wait_for_completion_killable_timeout(&dht11->completion, +								 HZ); +		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; +	return ret; +} + +static const struct iio_info dht11_iio_info = { +	.driver_module		= THIS_MODULE, +	.read_raw		= dht11_read_raw, +}; + +/* + * 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 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; +	} +	ret = devm_request_irq(dev, dht11->irq, dht11_handle_irq, +				IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, +				pdev->name, iio); +	if (ret) +		return ret; + +	dht11->timestamp = iio_get_time_ns() - DHT11_DATA_VALID_TIME - 1; +	dht11->num_edges = -1; + +	platform_set_drvdata(pdev, iio); + +	init_completion(&dht11->completion); +	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, +		.owner	= THIS_MODULE, +		.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 00000000000..bdd586e6d95 --- /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/iio_core.h b/drivers/iio/iio_core.h new file mode 100644 index 00000000000..5f0ea77fe71 --- /dev/null +++ b/drivers/iio/iio_core.h @@ -0,0 +1,73 @@ +/* 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); + + +#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 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 00000000000..1fdb1e4ea4a --- /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 00000000000..2b0e45133e9 --- /dev/null +++ b/drivers/iio/imu/Kconfig @@ -0,0 +1,42 @@ +# +# 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. + +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 00000000000..114d2c17cbe --- /dev/null +++ b/drivers/iio/imu/Makefile @@ -0,0 +1,16 @@ +# +# 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/ diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c new file mode 100644 index 00000000000..911255d41c1 --- /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 00000000000..0916bf6b6c3 --- /dev/null +++ b/drivers/iio/imu/adis16400.h @@ -0,0 +1,213 @@ +/* + * 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) + +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; +}; + +/* 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 00000000000..f2cf829e5df --- /dev/null +++ b/drivers/iio/imu/adis16400_buffer.c @@ -0,0 +1,91 @@ +#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; +	uint16_t *tx, *rx; + +	if (st->variant->flags & ADIS16400_NO_BURST) +		return adis_update_scan_mode(indio_dev, scan_mask); + +	kfree(adis->xfer); +	kfree(adis->buffer); + +	adis->xfer = kcalloc(2, sizeof(*adis->xfer), GFP_KERNEL); +	if (!adis->xfer) +		return -ENOMEM; + +	adis->buffer = kzalloc(indio_dev->scan_bytes + sizeof(u16), +		GFP_KERNEL); +	if (!adis->buffer) +		return -ENOMEM; + +	rx = adis->buffer; +	tx = adis->buffer + indio_dev->scan_bytes; + +	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].tx_buf = tx; +	adis->xfer[1].bits_per_word = 8; +	adis->xfer[1].len = indio_dev->scan_bytes; + +	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; +	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); +	} + +	iio_push_to_buffers_with_timestamp(indio_dev, adis->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 00000000000..433583b6f80 --- /dev/null +++ b/drivers/iio/imu/adis16400_core.c @@ -0,0 +1,967 @@ +/* + * 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/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 ssize_t adis16400_read_frequency(struct device *dev, +		struct device_attribute *attr, +		char *buf) +{ +	struct iio_dev *indio_dev = dev_to_iio_dev(dev); +	struct adis16400_state *st = iio_priv(indio_dev); +	int ret; + +	ret = st->variant->get_freq(st); +	if (ret < 0) +		return ret; + +	return sprintf(buf, "%d.%.3d\n", ret / 1000, ret % 1000); +} + +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; +} + +static ssize_t adis16400_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 adis16400_state *st = iio_priv(indio_dev); +	int i, f, val; +	int ret; + +	ret = iio_str_to_fixpoint(buf, 100, &i, &f); +	if (ret) +		return ret; + +	val = i * 1000 + f; + +	if (val <= 0) +		return -EINVAL; + +	mutex_lock(&indio_dev->mlock); +	st->variant->set_freq(st, val); +	mutex_unlock(&indio_dev->mlock); + +	return len; +} + +/* 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 IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, +			      adis16400_read_frequency, +			      adis16400_write_frequency); + +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; +	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; +		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 = ((val16 & 0xFFF) << 4) >> 4; +		*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; +	default: +		return -EINVAL; +	} +} + +#define ADIS16400_VOLTAGE_CHAN(addr, bits, name, si) { \ +	.type = IIO_VOLTAGE, \ +	.indexed = 1, \ +	.channel = 0, \ +	.extend_name = name, \ +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ +		BIT(IIO_CHAN_INFO_SCALE), \ +	.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) + +#define ADIS16400_AUX_ADC_CHAN(addr, bits) \ +	ADIS16400_VOLTAGE_CHAN(addr, bits, NULL, ADIS16400_SCAN_ADC) + +#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), \ +	.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), \ +	.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), \ +	.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), \ +	.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), \ +	.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), \ +	.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), +		.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 attribute *adis16400_attributes[] = { +	&iio_dev_attr_sampling_frequency.dev_attr.attr, +	NULL +}; + +static const struct attribute_group adis16400_attribute_group = { +	.attrs = adis16400_attributes, +}; + +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, +		.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, +	.attrs = &adis16400_attribute_group, +	.update_scan_mode = adis16400_update_scan_mode, +	.debugfs_reg_access = adis_debugfs_reg_access, +}; + +static const unsigned long adis16400_burst_scan_mask[] = { +	~0UL, +	0, +}; + +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 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)) +		indio_dev->available_scan_masks = adis16400_burst_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 00000000000..dd4206cac62 --- /dev/null +++ b/drivers/iio/imu/adis16480.c @@ -0,0 +1,920 @@ +/* + * 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 adis16480 *st, unsigned int freq) +{ +	unsigned int t; + +	t = 2460000 / freq; +	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 adis16480 *st, unsigned int *freq) +{ +	uint16_t t; +	int ret; + +	ret = adis_read_reg_16(&st->adis, ADIS16480_REG_DEC_RATE, &t); +	if (ret < 0) +		return ret; + +	*freq = 2460000 / (t + 1); + +	return 0; +} + +static ssize_t adis16480_read_frequency(struct device *dev, +		struct device_attribute *attr, +		char *buf) +{ +	struct iio_dev *indio_dev = dev_to_iio_dev(dev); +	struct adis16480 *st = iio_priv(indio_dev); +	unsigned int freq; +	int ret; + +	ret = adis16480_get_freq(st, &freq); +	if (ret < 0) +		return ret; + +	return sprintf(buf, "%d.%.3d\n", freq / 1000, freq % 1000); +} + +static ssize_t adis16480_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 adis16480 *st = iio_priv(indio_dev); +	int freq_int, freq_fract; +	long val; +	int ret; + +	ret = iio_str_to_fixpoint(buf, 100, &freq_int, &freq_fract); +	if (ret) +		return ret; + +	val = freq_int * 1000 + freq_fract; + +	if (val <= 0) +		return -EINVAL; + +	ret = adis16480_set_freq(st, val); + +	return ret ? ret : len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, +			      adis16480_read_frequency, +			      adis16480_write_frequency); + +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); +	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); +	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), \ +		.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), \ +		.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), \ +		.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 struct attribute *adis16480_attributes[] = { +	&iio_dev_attr_sampling_frequency.dev_attr.attr, +	NULL +}; + +static const struct attribute_group adis16480_attribute_group = { +	.attrs = adis16480_attributes, +}; + +static const struct iio_info adis16480_info = { +	.attrs = &adis16480_attribute_group, +	.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 00000000000..cb32b593f1c --- /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 00000000000..e0017c22bb9 --- /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 = 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 00000000000..2d0608ba88d --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/Kconfig @@ -0,0 +1,16 @@ +# +# 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 +	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 00000000000..3a677c778af --- /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 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 00000000000..0c6517c94a9 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -0,0 +1,791 @@ +/* +* 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 "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, +}; + +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); +} + +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; + +	if (power_on) +		result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, 0); +	else +		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_fsr(struct inv_mpu6050_state *st, int fsr) +{ +	int result; +	u8 d; + +	if (fsr < 0 || fsr > INV_MPU6050_MAX_GYRO_FS_PARAM) +		return -EINVAL; +	if (fsr == st->chip_config.fsr) +		return 0; + +	d = (fsr << 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 = fsr; + +	return 0; +} + +static int inv_mpu6050_write_accel_fs(struct inv_mpu6050_state *st, int fs) +{ +	int result; +	u8 d; + +	if (fs < 0 || fs > INV_MPU6050_MAX_ACCL_FS_PARAM) +		return -EINVAL; +	if (fs == st->chip_config.accl_fs) +		return 0; + +	d = (fs << 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 = fs; + +	return 0; +} + +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_fsr(st, val); +			break; +		case IIO_ACCEL: +			result = inv_mpu6050_write_accel_fs(st, val); +			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, +	.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; +	pdata = (struct inv_mpu6050_platform_data +			*)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; +	indio_dev->name = id->name; +	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; +	} + +	return 0; + +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); + +	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 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, +	}, +}; + +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 00000000000..e7799315d4d --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -0,0 +1,247 @@ +/* +* 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; +}; + +/*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 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 + +/* 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); 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 00000000000..0cd306a72a6 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -0,0 +1,192 @@ +/* +* 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" + +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; +	/* 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; +} + +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); +} + +/** + * 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); +	inv_clear_kfifo(st); +	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 00000000000..03b9372c121 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -0,0 +1,155 @@ +/* +* 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 = iio_trigger_alloc("%s-dev%d", +					indio_dev->name, +					indio_dev->id); +	if (st->trig == NULL) { +		ret = -ENOMEM; +		goto error_ret; +	} +	ret = request_irq(st->client->irq, &iio_trigger_generic_data_rdy_poll, +				IRQF_TRIGGER_RISING, +				"inv_mpu", +				st->trig); +	if (ret) +		goto error_free_trig; +	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) +		goto error_free_irq; +	indio_dev->trig = st->trig; + +	return 0; + +error_free_irq: +	free_irq(st->client->irq, st->trig); +error_free_trig: +	iio_trigger_free(st->trig); +error_ret: +	return ret; +} + +void inv_mpu6050_remove_trigger(struct inv_mpu6050_state *st) +{ +	iio_trigger_unregister(st->trig); +	free_irq(st->client->irq, st->trig); +	iio_trigger_free(st->trig); +} diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c new file mode 100644 index 00000000000..9f1a1400990 --- /dev/null +++ b/drivers/iio/industrialio-buffer.c @@ -0,0 +1,1109 @@ +/* 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 bool iio_buffer_data_available(struct iio_buffer *buf) +{ +	if (buf->access->data_available) +		return buf->access->data_available(buf); + +	return buf->stufftoread; +} + +/** + * 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; +	int ret; + +	if (!indio_dev->info) +		return -ENODEV; + +	if (!rb || !rb->access->read_first_n) +		return -EINVAL; + +	do { +		if (!iio_buffer_data_available(rb)) { +			if (filp->f_flags & O_NONBLOCK) +				return -EAGAIN; + +			ret = wait_event_interruptible(rb->pollq, +					iio_buffer_data_available(rb) || +					indio_dev->info == NULL); +			if (ret) +				return ret; +			if (indio_dev->info == NULL) +				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_data_available(rb)) +		return POLLIN | POLLRDNORM; +	/* need a way of knowing if there may be enough data... */ +	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); +} +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); +} + +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 const char * const iio_scan_elements_group_name = "scan_elements"; + +int iio_buffer_register(struct iio_dev *indio_dev, +			const struct iio_chan_spec *channels, +			int num_channels) +{ +	struct iio_dev_attr *p; +	struct attribute **attr; +	struct iio_buffer *buffer = indio_dev->buffer; +	int ret, i, attrn, attrcount, attrcount_orig = 0; + +	if (buffer->attrs) +		indio_dev->groups[indio_dev->groupcounter++] = buffer->attrs; + +	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); +	if (channels) { +		/* new magic */ +		for (i = 0; i < 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); + +	return ret; +} +EXPORT_SYMBOL(iio_buffer_register); + +void iio_buffer_unregister(struct iio_dev *indio_dev) +{ +	kfree(indio_dev->buffer->scan_mask); +	kfree(indio_dev->buffer->scan_el_group.attrs); +	iio_free_chan_devattr_list(&indio_dev->buffer->scan_el_dev_attr_list); +} +EXPORT_SYMBOL(iio_buffer_unregister); + +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; + +	if (buffer->access->get_length) +		return sprintf(buf, "%d\n", +			       buffer->access->get_length(buffer)); + +	return 0; +} +EXPORT_SYMBOL(iio_buffer_read_length); + +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 (buffer->access->get_length) +		if (val == buffer->access->get_length(buffer)) +			return len; + +	mutex_lock(&indio_dev->mlock); +	if (iio_buffer_is_active(indio_dev->buffer)) { +		ret = -EBUSY; +	} else { +		if (buffer->access->set_length) +			buffer->access->set_length(buffer, val); +		ret = 0; +	} +	mutex_unlock(&indio_dev->mlock); + +	return ret ? ret : len; +} +EXPORT_SYMBOL(iio_buffer_write_length); + +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)); +} +EXPORT_SYMBOL(iio_buffer_show_enable); + +/* 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 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); +	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) { +		if (!indio_dev->trig) { +			printk(KERN_INFO "Buffer not started: no trigger\n"); +			ret = -EINVAL; +			/* Can only occur on first buffer */ +			goto error_run_postdisable; +		} +		indio_dev->currentmode = INDIO_BUFFER_TRIGGERED; +	} else if (indio_dev->modes & INDIO_BUFFER_HARDWARE) { +		indio_dev->currentmode = INDIO_BUFFER_HARDWARE; +	} else { /* Should never be reached */ +		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); + +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; +} +EXPORT_SYMBOL(iio_buffer_store_enable); + +/** + * 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); + +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. + */ +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; +} +EXPORT_SYMBOL_GPL(iio_scan_mask_set); + +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); + +	return buffer->access->store_to(buffer, dataout); +} + +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_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; + +	/* 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 += length; +			if (in_loc % length) +				in_loc += length - in_loc % length; +		} +		p = kmalloc(sizeof(*p), GFP_KERNEL); +		if (p == NULL) { +			ret = -ENOMEM; +			goto error_clear_mux_table; +		} +		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; +		if (out_loc % length) +			out_loc += length - out_loc % length; +		if (in_loc % length) +			in_loc += length - in_loc % length; +		p->from = in_loc; +		p->to = out_loc; +		p->length = length; +		list_add_tail(&p->l, &buffer->demux_list); +		out_loc += length; +		in_loc += length; +	} +	/* Relies on scan_timestamp being last */ +	if (buffer->scan_timestamp) { +		p = kmalloc(sizeof(*p), GFP_KERNEL); +		if (p == NULL) { +			ret = -ENOMEM; +			goto error_clear_mux_table; +		} +		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; +		if (out_loc % length) +			out_loc += length - out_loc % length; +		if (in_loc % length) +			in_loc += length - in_loc % length; +		p->from = in_loc; +		p->to = out_loc; +		p->length = length; +		list_add_tail(&p->l, &buffer->demux_list); +		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 00000000000..4b1f375c565 --- /dev/null +++ b/drivers/iio/industrialio-core.c @@ -0,0 +1,1280 @@ +/* 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", +}; + +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", +}; + +/* 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_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. Note: if you want to reuse the list after calling + * this function you have to reinitialize it using INIT_LIST_HEAD(). + */ +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); +		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); +} + +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; + +	/* use raw alloc_dr for kmalloc caller tracing */ +	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 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; + +	/* 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_device_register_sysfs(indio_dev); +	if (ret) { +		dev_err(indio_dev->dev.parent, +			"Failed to register sysfs interfaces\n"); +		goto error_unreg_debugfs; +	} +	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_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); +} +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 00000000000..bfbf4d419f4 --- /dev/null +++ b/drivers/iio/industrialio-event.c @@ -0,0 +1,519 @@ +/* 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", +}; + +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", +}; + +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; +		postfix = kasprintf(GFP_KERNEL, "%s_%s_%s", +				iio_ev_type_text[type], iio_ev_dir_text[dir], +				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; + +	/* Dynically 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); +	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 00000000000..3383b025f62 --- /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, s64 time) +{ +	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, iio_get_time_ns()); +	return IRQ_HANDLED; +} +EXPORT_SYMBOL(iio_trigger_generic_data_rdy_poll); + +void iio_trigger_poll_chained(struct iio_trigger *trig, s64 time) +{ +	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, 0); +} +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 00000000000..d6f54930b34 --- /dev/null +++ b/drivers/iio/industrialio-triggered-buffer.c @@ -0,0 +1,112 @@ + /* + * 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, as well as register the buffer with the IIO core. + * + * 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(indio_dev); +	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; + +	ret = iio_buffer_register(indio_dev, +				  indio_dev->channels, +				  indio_dev->num_channels); +	if (ret) +		goto error_dealloc_pollfunc; + +	return 0; + +error_dealloc_pollfunc: +	iio_dealloc_pollfunc(indio_dev->pollfunc); +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_buffer_unregister(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 00000000000..c7497009d60 --- /dev/null +++ b/drivers/iio/inkern.c @@ -0,0 +1,610 @@ +/* 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; +} + +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; +	index = iiospec.args_count ? iiospec.args[0] : 0; +	if (index >= indio_dev->num_channels) { +		err = -EINVAL; +		goto err_put; +	} +	channel->channel = &indio_dev->channels[index]; + +	return 0; + +err_put: +	iio_device_put(indio_dev); +	return err; +} + +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)) +			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 (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); diff --git a/drivers/iio/kfifo_buf.c b/drivers/iio/kfifo_buf.c new file mode 100644 index 00000000000..7134e8ada09 --- /dev/null +++ b/drivers/iio/kfifo_buf.c @@ -0,0 +1,192 @@ +#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); +		buf->update_needed = false; +	} else { +		kfifo_reset_out(&buf->kf); +	} +	mutex_unlock(&buf->user_lock); + +	return ret; +} + +static int iio_get_length_kfifo(struct iio_buffer *r) +{ +	return r->length; +} + +static IIO_BUFFER_ENABLE_ATTR; +static IIO_BUFFER_LENGTH_ATTR; + +static struct attribute *iio_kfifo_attributes[] = { +	&dev_attr_length.attr, +	&dev_attr_enable.attr, +	NULL, +}; + +static struct attribute_group iio_kfifo_attribute_group = { +	.attrs = iio_kfifo_attributes, +	.name = "buffer", +}; + +static int iio_get_bytes_per_datum_kfifo(struct iio_buffer *r) +{ +	return r->bytes_per_datum; +} + +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; + +	wake_up_interruptible_poll(&r->pollq, POLLIN | POLLRDNORM); + +	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 bool iio_kfifo_buf_data_available(struct iio_buffer *r) +{ +	struct iio_kfifo *kf = iio_to_kfifo(r); +	bool empty; + +	mutex_lock(&kf->user_lock); +	empty = kfifo_is_empty(&kf->kf); +	mutex_unlock(&kf->user_lock); + +	return !empty; +} + +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, +	.get_bytes_per_datum = &iio_get_bytes_per_datum_kfifo, +	.set_bytes_per_datum = &iio_set_bytes_per_datum_kfifo, +	.get_length = &iio_get_length_kfifo, +	.set_length = &iio_set_length_kfifo, +	.release = &iio_kfifo_buffer_release, +}; + +struct iio_buffer *iio_kfifo_allocate(struct iio_dev *indio_dev) +{ +	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.attrs = &iio_kfifo_attribute_group; +	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); + +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig new file mode 100644 index 00000000000..c89740d4748 --- /dev/null +++ b/drivers/iio/light/Kconfig @@ -0,0 +1,161 @@ +# +# 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 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 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 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 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. + +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 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 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 00000000000..3eb36e5151f --- /dev/null +++ b/drivers/iio/light/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for IIO Light sensors +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ADJD_S311)		+= adjd_s311.o +obj-$(CONFIG_APDS9300)		+= apds9300.o +obj-$(CONFIG_CM32181)		+= cm32181.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_SENSORS_LM3533)	+= lm3533-als.o +obj-$(CONFIG_LTR501)		+= ltr501.o +obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.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 00000000000..09ad5f1ce53 --- /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/apds9300.c b/drivers/iio/light/apds9300.c new file mode 100644 index 00000000000..9ddde0ca9c3 --- /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 00000000000..d976e6ce60d --- /dev/null +++ b/drivers/iio/light/cm32181.c @@ -0,0 +1,380 @@ +/* + * 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 claibscale 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 = iio_device_register(indio_dev); +	if (ret) { +		dev_err(&client->dev, +			"%s: regist device failed\n", +			__func__); +		return ret; +	} + +	return 0; +} + +static int cm32181_remove(struct i2c_client *client) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(client); + +	iio_device_unregister(indio_dev); +	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, +	.remove		= cm32181_remove, +}; + +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/cm36651.c b/drivers/iio/light/cm36651.c new file mode 100644 index 00000000000..39fc67e8213 --- /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 00000000000..04bdb85d2d9 --- /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 <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, 0); +} + +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; +	__le32 light_lux; +	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); +			light_lux = cpu_to_le32(out_val); +			memcpy(&priv->buffer[d_size], (u8 *)&light_lux, 4); +			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 00000000000..96e71e103ea --- /dev/null +++ b/drivers/iio/light/hid-sensor-als.c @@ -0,0 +1,393 @@ +/* + * 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; +	s32 poll_value; + +	*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) { +			poll_value = hid_sensor_read_poll_value( +						&als_state->common_attributes); +			if (poll_value < 0) +				return -EINVAL; + +			hid_sensor_power_state(&als_state->common_attributes, +						true); +			msleep_interruptible(poll_value * 2); + +			*val = sensor_hub_input_attr_get_raw_value( +					als_state->common_attributes.hsdev, +					HID_USAGE_SENSOR_ALS, address, +					report_id); +			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, +		.owner	= THIS_MODULE, +	}, +	.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 00000000000..412bae86d6a --- /dev/null +++ b/drivers/iio/light/hid-sensor-prox.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_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, +		.modified = 1, +		.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) | +		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; +	s32 poll_value; + +	*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) { +			poll_value = hid_sensor_read_poll_value( +					&prox_state->common_attributes); +			if (poll_value < 0) +				return -EINVAL; + +			hid_sensor_power_state(&prox_state->common_attributes, +						true); + +			msleep_interruptible(poll_value * 2); + +			*val = sensor_hub_input_attr_get_raw_value( +				prox_state->common_attributes.hsdev, +				HID_USAGE_SENSOR_PROX, address, +				report_id); +			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; +	struct iio_chan_spec *channels; + +	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; +	} + +	channels = kmemdup(prox_channels, sizeof(prox_channels), GFP_KERNEL); +	if (!channels) { +		dev_err(&pdev->dev, "failed to duplicate channels\n"); +		return -ENOMEM; +	} + +	ret = prox_parse_report(pdev, hsdev, 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->channels = channels; +	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, +		.owner	= THIS_MODULE, +	}, +	.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/lm3533-als.c b/drivers/iio/light/lm3533-als.c new file mode 100644 index 00000000000..c1aadc6b865 --- /dev/null +++ b/drivers/iio/light/lm3533-als.c @@ -0,0 +1,928 @@ +/* + * 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", +		.owner	= THIS_MODULE, +	}, +	.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 00000000000..62b7072af4d --- /dev/null +++ b/drivers/iio/light/ltr501.c @@ -0,0 +1,445 @@ +/* + * 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_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) +		return ret; + +	ret = iio_device_register(indio_dev); +	if (ret) +		goto error_unreg_buffer; + +	return 0; + +error_unreg_buffer: +	iio_triggered_buffer_cleanup(indio_dev); +	return ret; +} + +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_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/tcs3472.c b/drivers/iio/light/tcs3472.c new file mode 100644 index 00000000000..752569985d1 --- /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 00000000000..94daa9fc124 --- /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 00000000000..a15006efa13 --- /dev/null +++ b/drivers/iio/light/tsl4531.c @@ -0,0 +1,258 @@ +/* + * 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); +} +#endif + +static SIMPLE_DEV_PM_OPS(tsl4531_pm_ops, tsl4531_suspend, tsl4531_resume); + +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 00000000000..d948c4778ba --- /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 00000000000..05a364c543f --- /dev/null +++ b/drivers/iio/magnetometer/Kconfig @@ -0,0 +1,72 @@ +# +# Magnetometer sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Magnetometer sensors" + +config AK8975 +	tristate "Asahi Kasei AK8975 3-Axis Magnetometer" +	depends on I2C +	depends on GPIOLIB +	help +	  Say yes here to build support for Asahi Kasei AK8975 3-Axis +	  Magnetometer. This driver can also support AK8963, if i2c +	  device name is identified as ak8963. + +	  To compile this driver as a module, choose M here: the module +	  will be called ak8975. + +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 00000000000..0f5d3c98579 --- /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 00000000000..ea08313af0d --- /dev/null +++ b/drivers/iio/magnetometer/ak8975.c @@ -0,0 +1,631 @@ +/* + * 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	0 +#define AK8975_REG_CNTL_MODE_ONCE	1 +#define AK8975_REG_CNTL_MODE_SELF_TEST	8 +#define AK8975_REG_CNTL_MODE_FUSE_ROM	0xF + +#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 + +/* + * Miscellaneous values. + */ +#define AK8975_MAX_CONVERSION_TIMEOUT	500 +#define AK8975_CONVERSION_DONE_POLL_TIME 10 +#define AK8975_DATA_READY_TIMEOUT	((100*HZ)/1000) +#define RAW_TO_GAUSS_8975(asa) ((((asa) + 128) * 3000) / 256) +#define RAW_TO_GAUSS_8963(asa) ((((asa) + 128) * 6000) / 256) + +/* Compatible Asahi Kasei Compass parts */ +enum asahi_compass_chipset { +	AK8975, +	AK8963, +}; + +/* + * Per-instance context data for the device. + */ +struct ak8975_data { +	struct i2c_client	*client; +	struct attribute_group	attrs; +	struct mutex		lock; +	u8			asa[3]; +	long			raw_to_gauss[3]; +	u8			reg_cache[AK8975_MAX_REGS]; +	int			eoc_gpio; +	int			eoc_irq; +	wait_queue_head_t	data_ready_queue; +	unsigned long		flags; +	enum asahi_compass_chipset chipset; +}; + +static const int ak8975_index_to_reg[] = { +	AK8975_REG_HXL, AK8975_REG_HYL, AK8975_REG_HZL, +}; + +/* + * Helper function to write to the I2C device's registers. + */ +static int ak8975_write_data(struct i2c_client *client, +			     u8 reg, u8 val, u8 mask, u8 shift) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(client); +	struct ak8975_data *data = iio_priv(indio_dev); +	u8 regval; +	int ret; + +	regval = (data->reg_cache[reg] & ~mask) | (val << shift); +	ret = i2c_smbus_write_byte_data(client, reg, regval); +	if (ret < 0) { +		dev_err(&client->dev, "Write to device fails status %x\n", ret); +		return ret; +	} +	data->reg_cache[reg] = regval; + +	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 = request_irq(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); +	u8 device_id; +	int ret; + +	/* Confirm that the device we're talking to is really an AK8975. */ +	ret = i2c_smbus_read_byte_data(client, AK8975_REG_WIA); +	if (ret < 0) { +		dev_err(&client->dev, "Error reading WIA\n"); +		return ret; +	} +	device_id = ret; +	if (device_id != AK8975_DEVICE_ID) { +		dev_err(&client->dev, "Device ak8975 not found\n"); +		return -ENODEV; +	} + +	/* Write the fused rom access mode. */ +	ret = ak8975_write_data(client, +				AK8975_REG_CNTL, +				AK8975_REG_CNTL_MODE_FUSE_ROM, +				AK8975_REG_CNTL_MODE_MASK, +				AK8975_REG_CNTL_MODE_SHIFT); +	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, AK8975_REG_ASAX, +					    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_write_data(client, +				AK8975_REG_CNTL, +				AK8975_REG_CNTL_MODE_POWER_DOWN, +				AK8975_REG_CNTL_MODE_MASK, +				AK8975_REG_CNTL_MODE_SHIFT); + +	if (data->eoc_gpio > 0 || client->irq) { +		ret = ak8975_setup_irq(data); +		if (ret < 0) { +			dev_err(&client->dev, +				"Error setting data ready interrupt\n"); +			return ret; +		} +	} + +	if (ret < 0) { +		dev_err(&client->dev, "Error in setting power-down mode\n"); +		return ret; +	} + +/* + * 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(). + */ +	if (data->chipset == AK8963) { +		/* +		 * H range is +-8190 and magnetometer range is +-4912. +		 * So HuT using the above explanation for 8975, +		 * 4912/8190 = ~ 6/10. +		 * So the Hadj should use 6/10 instead of 3/10. +		 */ +		data->raw_to_gauss[0] = RAW_TO_GAUSS_8963(data->asa[0]); +		data->raw_to_gauss[1] = RAW_TO_GAUSS_8963(data->asa[1]); +		data->raw_to_gauss[2] = RAW_TO_GAUSS_8963(data->asa[2]); +	} else { +		data->raw_to_gauss[0] = RAW_TO_GAUSS_8975(data->asa[0]); +		data->raw_to_gauss[1] = RAW_TO_GAUSS_8975(data->asa[1]); +		data->raw_to_gauss[2] = RAW_TO_GAUSS_8975(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, AK8975_REG_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, AK8975_REG_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_write_data(client, +				AK8975_REG_CNTL, +				AK8975_REG_CNTL_MODE_ONCE, +				AK8975_REG_CNTL_MODE_MASK, +				AK8975_REG_CNTL_MODE_SHIFT); +	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 & AK8975_REG_ST1_DRDY_MASK) { +		ret = i2c_smbus_read_byte_data(client, AK8975_REG_ST2); +		if (ret < 0) { +			dev_err(&client->dev, "Error in reading ST2\n"); +			goto exit; +		} +		if (ret & (AK8975_REG_ST2_DERR_MASK | +			   AK8975_REG_ST2_HOFL_MASK)) { +			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, ak8975_index_to_reg[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, -4096, 4095); +	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}, +	{ }, +}; +MODULE_DEVICE_TABLE(acpi, ak_acpi_match); + +static 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 (char *)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; +	char *name = NULL; + +	/* 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 = gpio_request_one(eoc_gpio, GPIOF_IN, "ak_8975"); +		if (err < 0) { +			dev_err(&client->dev, +				"failed to request GPIO %d, error %d\n", +							eoc_gpio, err); +			goto exit; +		} +	} + +	/* Register with IIO */ +	indio_dev = iio_device_alloc(sizeof(*data)); +	if (indio_dev == NULL) { +		err = -ENOMEM; +		goto exit_gpio; +	} +	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) { +		data->chipset = +			(enum asahi_compass_chipset)(id->driver_data); +		name = (char *) id->name; +	} else if (ACPI_HANDLE(&client->dev)) +		name = ak8975_match_acpi_device(&client->dev, &data->chipset); +	else { +		err = -ENOSYS; +		goto exit_free_iio; +	} +	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, "AK8975 initialization fails\n"); +		goto exit_free_iio; +	} + +	data->client = client; +	mutex_init(&data->lock); +	data->eoc_gpio = eoc_gpio; +	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; +	err = iio_device_register(indio_dev); +	if (err < 0) +		goto exit_free_iio; + +	return 0; + +exit_free_iio: +	iio_device_free(indio_dev); +	if (data->eoc_irq) +		free_irq(data->eoc_irq, data); +exit_gpio: +	if (gpio_is_valid(eoc_gpio)) +		gpio_free(eoc_gpio); +exit: +	return err; +} + +static int ak8975_remove(struct i2c_client *client) +{ +	struct iio_dev *indio_dev = i2c_get_clientdata(client); +	struct ak8975_data *data = iio_priv(indio_dev); + +	iio_device_unregister(indio_dev); + +	if (data->eoc_irq) +		free_irq(data->eoc_irq, data); + +	if (gpio_is_valid(data->eoc_gpio)) +		gpio_free(data->eoc_gpio); + +	iio_device_free(indio_dev); + +	return 0; +} + +static const struct i2c_device_id ak8975_id[] = { +	{"ak8975", AK8975}, +	{"ak8963", AK8963}, +	{} +}; + +MODULE_DEVICE_TABLE(i2c, ak8975_id); + +static const struct of_device_id ak8975_of_match[] = { +	{ .compatible = "asahi-kasei,ak8975", }, +	{ .compatible = "ak8975", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, ak8975_of_match); + +static struct i2c_driver ak8975_driver = { +	.driver = { +		.name	= "ak8975", +		.of_match_table = ak8975_of_match, +		.acpi_match_table = ACPI_PTR(ak_acpi_match), +	}, +	.probe		= ak8975_probe, +	.remove		= ak8975_remove, +	.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 00000000000..b2b0937d513 --- /dev/null +++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c @@ -0,0 +1,433 @@ +/* + * 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, +	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]; +	u32 magn_val[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 +}; + +/* 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), +		.scan_index = CHANNEL_SCAN_INDEX_X, +	}, { +		.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), +		.scan_index = CHANNEL_SCAN_INDEX_Y, +	}, { +		.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), +		.scan_index = CHANNEL_SCAN_INDEX_Z, +	} +}; + +/* 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; +	s32 poll_value; + +	*val = 0; +	*val2 = 0; +	switch (mask) { +	case 0: +		poll_value = hid_sensor_read_poll_value( +					&magn_state->common_attributes); +		if (poll_value < 0) +				return -EINVAL; + +		hid_sensor_power_state(&magn_state->common_attributes, true); +		msleep_interruptible(poll_value * 2); + +		report_id = +			magn_state->magn[chan->scan_index].report_id; +		address = magn_3d_addresses[chan->scan_index]; +		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); +		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, +	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 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->magn_val, +				sizeof(magn_state->magn_val)); + +	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 = -EINVAL; + +	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; +		magn_state->magn_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 magn_3d_parse_report(struct platform_device *pdev, +				struct hid_sensor_hub_device *hsdev, +				struct iio_chan_spec *channels, +				unsigned usage_id, +				struct magn_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_ORIENT_MAGN_FLUX_X_AXIS + i, +				&st->magn[CHANNEL_SCAN_INDEX_X + i]); +		if (ret < 0) +			break; +		magn_3d_adjust_channel_bit_mask(channels, +				CHANNEL_SCAN_INDEX_X + i, +				st->magn[CHANNEL_SCAN_INDEX_X + i].size); +	} +	dev_dbg(&pdev->dev, "magn_3d %x:%x, %x:%x, %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); + +	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 ret; +} + +/* 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; + +	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; +	} + +	channels = kmemdup(magn_3d_channels, sizeof(magn_3d_channels), +			   GFP_KERNEL); +	if (!channels) { +		dev_err(&pdev->dev, "failed to duplicate channels\n"); +		return -ENOMEM; +	} + +	ret = magn_3d_parse_report(pdev, hsdev, channels, +				HID_USAGE_SENSOR_COMPASS_3D, magn_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(magn_3d_channels); +	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"); +		goto error_free_dev_mem; +	} +	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); +error_free_dev_mem: +	kfree(indio_dev->channels); +	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); +	kfree(indio_dev->channels); + +	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, +		.owner	= THIS_MODULE, +	}, +	.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 00000000000..e3106b43ef4 --- /dev/null +++ b/drivers/iio/magnetometer/mag3110.c @@ -0,0 +1,438 @@ +/* + * 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_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) +		return ret; + +	ret = iio_triggered_buffer_setup(indio_dev, NULL, +		mag3110_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 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_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 00000000000..694e33e0fb7 --- /dev/null +++ b/drivers/iio/magnetometer/st_magn.h @@ -0,0 +1,46 @@ +/* + * 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, +					struct st_sensors_platform_data *pdata); +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 00000000000..bf427dc0d22 --- /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 00000000000..240a21dd0c6 --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_core.c @@ -0,0 +1,423 @@ +/* + * 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_10000MG			10000 + +/* 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		1100 +#define ST_MAGN_1_FS_AVL_1900_GAIN_XY		855 +#define ST_MAGN_1_FS_AVL_2500_GAIN_XY		670 +#define ST_MAGN_1_FS_AVL_4000_GAIN_XY		450 +#define ST_MAGN_1_FS_AVL_4700_GAIN_XY		400 +#define ST_MAGN_1_FS_AVL_5600_GAIN_XY		330 +#define ST_MAGN_1_FS_AVL_8100_GAIN_XY		230 +#define ST_MAGN_1_FS_AVL_1300_GAIN_Z		980 +#define ST_MAGN_1_FS_AVL_1900_GAIN_Z		760 +#define ST_MAGN_1_FS_AVL_2500_GAIN_Z		600 +#define ST_MAGN_1_FS_AVL_4000_GAIN_Z		400 +#define ST_MAGN_1_FS_AVL_4700_GAIN_Z		355 +#define ST_MAGN_1_FS_AVL_5600_GAIN_Z		295 +#define ST_MAGN_1_FS_AVL_8100_GAIN_Z		205 +#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_10000_VAL		0x02 +#define ST_MAGN_2_FS_AVL_4000_GAIN		430 +#define ST_MAGN_2_FS_AVL_8000_GAIN		230 +#define ST_MAGN_2_FS_AVL_10000_GAIN		230 +#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_sensors st_magn_sensors[] = { +	{ +		.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_10000MG, +					.value = ST_MAGN_2_FS_AVL_10000_VAL, +					.gain = ST_MAGN_2_FS_AVL_10000_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; +	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; +	default: +		err = -EINVAL; +	} + +	return err; +} + +static ST_SENSOR_DEV_ATTR_SAMP_FREQ(); +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, +	&iio_dev_attr_sampling_frequency.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_sensors_platform_data *pdata) +{ +	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; + +	st_sensors_power_enable(indio_dev); + +	err = st_sensors_check_device_support(indio_dev, +				ARRAY_SIZE(st_magn_sensors), st_magn_sensors); +	if (err < 0) +		return err; + +	mdata->num_data_channels = ST_MAGN_NUMBER_DATA_CHANNELS; +	mdata->multiread_bit = mdata->sensor->multi_read_bit; +	indio_dev->channels = mdata->sensor->ch; +	indio_dev->num_channels = ST_SENSORS_NUMBER_ALL_CHANNELS; + +	mdata->current_fullscale = (struct st_sensor_fullscale_avl *) +						&mdata->sensor->fs.fs_avl[0]; +	mdata->odr = mdata->sensor->odr.odr_avl[0].hz; + +	err = st_sensors_init_sensor(indio_dev, pdata); +	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 00000000000..892e0feeb5c --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_i2c.c @@ -0,0 +1,73 @@ +/* + * 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" + +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); +	mdata->dev = &client->dev; + +	st_sensors_i2c_configure(indio_dev, client, mdata); + +	err = st_magn_common_probe(indio_dev, NULL); +	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", +	}, +	.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 00000000000..a6143ea51df --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_spi.c @@ -0,0 +1,72 @@ +/* + * 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); +	mdata->dev = &spi->dev; + +	st_sensors_spi_configure(indio_dev, spi, mdata); + +	err = st_magn_common_probe(indio_dev, NULL); +	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 00000000000..e3aa1e58d92 --- /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 00000000000..4734dabbde1 --- /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 00000000000..2478f6c2ef2 --- /dev/null +++ b/drivers/iio/orientation/hid-sensor-incl-3d.c @@ -0,0 +1,449 @@ +/* + * 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; +	s32 poll_value; + +	*val = 0; +	*val2 = 0; +	switch (mask) { +	case IIO_CHAN_INFO_RAW: +		poll_value = hid_sensor_read_poll_value( +					&incl_state->common_attributes); +		if (poll_value < 0) +			return -EINVAL; + +		hid_sensor_power_state(&incl_state->common_attributes, true); +		msleep_interruptible(poll_value * 2); + +		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); +		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, +		.owner	= THIS_MODULE, +	}, +	.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 00000000000..dccf848e8b0 --- /dev/null +++ b/drivers/iio/orientation/hid-sensor-rotation.c @@ -0,0 +1,346 @@ +/* + * 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, +		.owner	= THIS_MODULE, +	}, +	.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 00000000000..ffac8ac1efc --- /dev/null +++ b/drivers/iio/pressure/Kconfig @@ -0,0 +1,73 @@ +# +# Pressure drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Pressure sensors" + +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 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 + +endmenu diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile new file mode 100644 index 00000000000..c53d2500737 --- /dev/null +++ b/drivers/iio/pressure/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for industrial I/O pressure drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_HID_SENSOR_PRESS)   += hid-sensor-press.o +obj-$(CONFIG_MPL115) += mpl115.o +obj-$(CONFIG_MPL3115) += mpl3115.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_IIO_ST_PRESS_I2C) += st_pressure_i2c.o +obj-$(CONFIG_IIO_ST_PRESS_SPI) += st_pressure_spi.o diff --git a/drivers/iio/pressure/hid-sensor-press.c b/drivers/iio/pressure/hid-sensor-press.c new file mode 100644 index 00000000000..2c0d2a4fed8 --- /dev/null +++ b/drivers/iio/pressure/hid-sensor-press.c @@ -0,0 +1,394 @@ +/* + * 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, +		.modified = 1, +		.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) | +		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; +	s32 poll_value; + +	*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) { +			poll_value = hid_sensor_read_poll_value( +					&press_state->common_attributes); +			if (poll_value < 0) +				return -EINVAL; +			hid_sensor_power_state(&press_state->common_attributes, +						true); + +			msleep_interruptible(poll_value * 2); + +			*val = sensor_hub_input_attr_get_raw_value( +				press_state->common_attributes.hsdev, +				HID_USAGE_SENSOR_PRESSURE, address, +				report_id); +			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, +		.owner	= THIS_MODULE, +	}, +	.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 00000000000..f5ecd6e19f5 --- /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 00000000000..01b2e0b1887 --- /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/st_pressure.h b/drivers/iio/pressure/st_pressure.h new file mode 100644 index 00000000000..242943c0c4e --- /dev/null +++ b/drivers/iio/pressure/st_pressure.h @@ -0,0 +1,50 @@ +/* + * 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, +					struct st_sensors_platform_data *pdata); +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 00000000000..b37b1c9ac93 --- /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 *pdata = iio_priv(indio_dev); + +	pdata->buffer_data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); +	if (pdata->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(pdata->buffer_data); +allocate_memory_error: +	return err; +} + +static int st_press_buffer_predisable(struct iio_dev *indio_dev) +{ +	int err; +	struct st_sensor_data *pdata = 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(pdata->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 00000000000..cd7e01f3a93 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_core.c @@ -0,0 +1,473 @@ +/* + * 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_sensors st_press_sensors[] = { +	{ +		.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_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 *pdata = 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 = pdata->current_fullscale->gain; +			break; +		case IIO_TEMP: +			*val2 = pdata->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; +	default: +		return -EINVAL; +	} + +read_error: +	return err; +} + +static ST_SENSOR_DEV_ATTR_SAMP_FREQ(); +static ST_SENSORS_DEV_ATTR_SAMP_FREQ_AVAIL(); + +static struct attribute *st_press_attributes[] = { +	&iio_dev_attr_sampling_frequency_available.dev_attr.attr, +	&iio_dev_attr_sampling_frequency.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, +}; + +#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_sensors_platform_data *plat_data) +{ +	struct st_sensor_data *pdata = iio_priv(indio_dev); +	int irq = pdata->get_irq_data_ready(indio_dev); +	int err; + +	indio_dev->modes = INDIO_DIRECT_MODE; +	indio_dev->info = &press_info; + +	st_sensors_power_enable(indio_dev); + +	err = st_sensors_check_device_support(indio_dev, +					      ARRAY_SIZE(st_press_sensors), +					      st_press_sensors); +	if (err < 0) +		return err; + +	pdata->num_data_channels = ST_PRESS_NUMBER_DATA_CHANNELS; +	pdata->multiread_bit     = pdata->sensor->multi_read_bit; +	indio_dev->channels      = pdata->sensor->ch; +	indio_dev->num_channels  = pdata->sensor->num_ch; + +	if (pdata->sensor->fs.addr != 0) +		pdata->current_fullscale = (struct st_sensor_fullscale_avl *) +			&pdata->sensor->fs.fs_avl[0]; + +	pdata->odr = pdata->sensor->odr.odr_avl[0].hz; + +	/* Some devices don't support a data ready pin. */ +	if (!plat_data && pdata->sensor->drdy_irq.addr) +		plat_data = +			(struct st_sensors_platform_data *)&default_press_pdata; + +	err = st_sensors_init_sensor(indio_dev, plat_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 *pdata = iio_priv(indio_dev); + +	st_sensors_power_disable(indio_dev); + +	iio_device_unregister(indio_dev); +	if (pdata->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 00000000000..3cd73e39b84 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_i2c.c @@ -0,0 +1,72 @@ +/* + * 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" + +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 *pdata; +	int err; + +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*pdata)); +	if (!indio_dev) +		return -ENOMEM; + +	pdata = iio_priv(indio_dev); +	pdata->dev = &client->dev; + +	st_sensors_i2c_configure(indio_dev, client, pdata); + +	err = st_press_common_probe(indio_dev, client->dev.platform_data); +	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", +	}, +	.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 00000000000..f45d430ec52 --- /dev/null +++ b/drivers/iio/pressure/st_pressure_spi.c @@ -0,0 +1,71 @@ +/* + * 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 *pdata; +	int err; + +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*pdata)); +	if (indio_dev == NULL) +		return -ENOMEM; + +	pdata = iio_priv(indio_dev); +	pdata->dev = &spi->dev; + +	st_sensors_spi_configure(indio_dev, spi, pdata); + +	err = st_press_common_probe(indio_dev, spi->dev.platform_data); +	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/proximity/Kconfig b/drivers/iio/proximity/Kconfig new file mode 100644 index 00000000000..0c8cdf58f6a --- /dev/null +++ b/drivers/iio/proximity/Kconfig @@ -0,0 +1,19 @@ +# +# 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 diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile new file mode 100644 index 00000000000..743adee1c8b --- /dev/null +++ b/drivers/iio/proximity/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for IIO proximity sensors +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_AS3935)		+= as3935.o diff --git a/drivers/iio/proximity/as3935.c b/drivers/iio/proximity/as3935.c new file mode 100644 index 00000000000..bf677bfe8eb --- /dev/null +++ b/drivers/iio/proximity/as3935.c @@ -0,0 +1,456 @@ +/* + * 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, iio_get_time_ns()); +		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 spi_device *spi, pm_message_t msg) +{ +	struct iio_dev *indio_dev = spi_get_drvdata(spi); +	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 spi_device *spi) +{ +	struct iio_dev *indio_dev = spi_get_drvdata(spi); +	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; +} +#else +#define as3935_suspend	NULL +#define as3935_resume	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, +	}, +	.probe		= as3935_probe, +	.remove		= as3935_remove, +	.id_table	= as3935_id, +	.suspend	= as3935_suspend, +	.resume		= as3935_resume, +}; +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/temperature/Kconfig b/drivers/iio/temperature/Kconfig new file mode 100644 index 00000000000..21feaa4661b --- /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 00000000000..40710a81158 --- /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 00000000000..c8b6ac8b2d6 --- /dev/null +++ b/drivers/iio/temperature/mlx90614.c @@ -0,0 +1,150 @@ +/* + * mlx90614.c - Support for Melexis MLX90614 contactless IR 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. + * + * 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 + +/* RAM offsets with 16-bit data, MSB first */ +#define MLX90614_TA 0x06 /* ambient temperature */ +#define MLX90614_TOBJ1 0x07 /* object temperature */ + +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); +	s32 ret; + +	switch (mask) { +	case IIO_CHAN_INFO_RAW: /* 0.02K / LSB */ +		switch (channel->channel2) { +		case IIO_MOD_TEMP_AMBIENT: +			ret = i2c_smbus_read_word_data(data->client, +			    MLX90614_OP_RAM | MLX90614_TA); +			if (ret < 0) +				return ret; +			break; +		case IIO_MOD_TEMP_OBJECT: +			ret = i2c_smbus_read_word_data(data->client, +			    MLX90614_OP_RAM | MLX90614_TOBJ1); +			if (ret < 0) +				return ret; +			break; +		default: +			return -EINVAL; +		} +		*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), +	}, +}; + +static const struct iio_info mlx90614_info = { +	.read_raw = mlx90614_read_raw, +	.driver_module = THIS_MODULE, +}; + +static int mlx90614_probe(struct i2c_client *client, +			 const struct i2c_device_id *id) +{ +	struct iio_dev *indio_dev; +	struct mlx90614_data *data; + +	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; + +	indio_dev->channels = mlx90614_channels; +	indio_dev->num_channels = ARRAY_SIZE(mlx90614_channels); + +	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_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 00000000000..84a0789c3d9 --- /dev/null +++ b/drivers/iio/temperature/tmp006.c @@ -0,0 +1,290 @@ +/* + * 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; + +	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 00000000000..79996123a71 --- /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 00000000000..0694daecaf2 --- /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 00000000000..02577ec54c6 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-interrupt.c @@ -0,0 +1,121 @@ +/* + * 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) +{ +	/* Timestamp not currently provided */ +	iio_trigger_poll(private, 0); +	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", +		.owner = THIS_MODULE, +	}, +}; + +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 00000000000..15e3b850f51 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-sysfs.c @@ -0,0 +1,227 @@ +/* + * 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, 0); +} + +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");  | 
