diff options
| author | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
| commit | 8e4bff4cab0ddac6060645b0715210484d02ff40 (patch) | |
| tree | 3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/iio | |
Diffstat (limited to 'drivers/iio')
135 files changed, 43708 insertions, 0 deletions
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig new file mode 100644 index 00000000..d6c68b5c --- /dev/null +++ b/drivers/iio/Kconfig @@ -0,0 +1,75 @@ +# +# Industrial I/O subsystem configuration +# + +menuconfig IIO + tristate "Industrial I/O support" + depends on GENERIC_HARDIRQS + 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-inkernel 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. Use this if you want a fifo + rather than a ring buffer. 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 + ring 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/imu/inv_mpu/Kconfig" +source "drivers/iio/inv_test/Kconfig" +source "drivers/iio/light/Kconfig" +source "drivers/iio/magnetometer/inv_compass/Kconfig" + +endif # IIO diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile new file mode 100644 index 00000000..e5ad4e3e --- /dev/null +++ b/drivers/iio/Makefile @@ -0,0 +1,24 @@ +# +# 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 += imu/ +obj-y += light/ +obj-y += magnetometer/ +obj-y += inv_test/ diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig new file mode 100644 index 00000000..bb594963 --- /dev/null +++ b/drivers/iio/accel/Kconfig @@ -0,0 +1,55 @@ +# +# Accelerometer drivers +# +menu "Accelerometers" + +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 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 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) + select IIO_ST_ACCEL_BUFFER if (IIO_TRIGGERED_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, will be created + these modules: + - 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 + +endmenu diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile new file mode 100644 index 00000000..87d8fa26 --- /dev/null +++ b/drivers/iio/accel/Makefile @@ -0,0 +1,14 @@ +# +# Makefile for industrial I/O accelerometer drivers +# + +obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.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 + +obj-$(CONFIG_KXSD9) += kxsd9.o 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 00000000..bbcbd710 --- /dev/null +++ b/drivers/iio/accel/hid-sensor-accel-3d.c @@ -0,0 +1,404 @@ +/* + * 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> +#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" + +/*Format: HID-SENSOR-usage_id_in_hex*/ +/*Usage ID from spec for Accelerometer-3D: 0x200073*/ +#define DRIVER_NAME "HID-SENSOR-200073" + +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]; +}; + +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_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_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_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; + int ret_type; + + *val = 0; + *val2 = 0; + switch (mask) { + case 0: + 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; + return -EINVAL; + } + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = accel_state->accel[CHANNEL_SCAN_INDEX_X].units; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_OFFSET: + *val = hid_sensor_convert_exponent( + accel_state->accel[CHANNEL_SCAN_INDEX_X].unit_expo); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_read_samp_freq_value( + &accel_state->common_attributes, val, val2); + ret_type = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_read_raw_hyst_value( + &accel_state->common_attributes, val, val2); + ret_type = IIO_VAL_INT_PLUS_MICRO; + 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 int accel_3d_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_MICRO; +} + +static const struct iio_info accel_3d_info = { + .driver_module = THIS_MODULE, + .read_raw = &accel_3d_read_raw, + .write_raw = &accel_3d_write_raw, + .write_raw_get_fmt = &accel_3d_write_raw_get_fmt, +}; + +/* 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 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 [%d]\n", + accel_state->common_attributes.data_ready); + if (accel_state->common_attributes.data_ready) + hid_sensor_push_data(indio_dev, + (u8 *)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); + + 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 = iio_device_alloc(sizeof(struct accel_3d_state)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + 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"); + goto error_free_dev; + } + + channels = kmemdup(accel_3d_channels, sizeof(accel_3d_channels), + GFP_KERNEL); + if (!channels) { + ret = -ENOMEM; + dev_err(&pdev->dev, "failed to duplicate channels\n"); + goto error_free_dev; + } + + 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; + } + accel_state->common_attributes.data_ready = false; + 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(indio_dev); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: + kfree(indio_dev->channels); +error_free_dev: + iio_device_free(indio_dev); +error_ret: + 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); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ACCEL_3D); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + kfree(indio_dev->channels); + iio_device_free(indio_dev); + + return 0; +} + +static struct platform_driver hid_accel_3d_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .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 00000000..7c9a1d97 --- /dev/null +++ b/drivers/iio/accel/kxsd9.c @@ -0,0 +1,288 @@ +/* + * 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; + int ret; + + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + 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); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + return 0; + +error_free_dev: + iio_device_free(indio_dev); +error_ret: + return ret; +} + +static int kxsd9_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_device_free(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/st_accel.h b/drivers/iio/accel/st_accel.h new file mode 100644 index 00000000..37949b94 --- /dev/null +++ b/drivers/iio/accel/st_accel.h @@ -0,0 +1,47 @@ +/* + * 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" + +int st_accel_common_probe(struct iio_dev *indio_dev); +void st_accel_common_remove(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +int st_accel_allocate_ring(struct iio_dev *indio_dev); +void st_accel_deallocate_ring(struct iio_dev *indio_dev); +int st_accel_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_ACCEL_TRIGGER_SET_STATE (&st_accel_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_accel_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +static inline void st_accel_deallocate_ring(struct iio_dev *indio_dev) +{ +} +#define ST_ACCEL_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_ACCEL_H */ diff --git a/drivers/iio/accel/st_accel_buffer.c b/drivers/iio/accel/st_accel_buffer.c new file mode 100644 index 00000000..d9b35075 --- /dev/null +++ b/drivers/iio/accel/st_accel_buffer.c @@ -0,0 +1,114 @@ +/* + * 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) +{ + int err; + + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto st_accel_set_enable_error; + + err = iio_sw_buffer_preenable(indio_dev); + +st_accel_set_enable_error: + return err; +} + +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 00000000..e0f5a3ce --- /dev/null +++ b/drivers/iio/accel/st_accel_core.c @@ -0,0 +1,500 @@ +/* + * 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" + +/* 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_MASK 0x10 +#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_MASK 0x02 +#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_MASK 0x80 +#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, ST_SENSORS_SCAN_X, IIO_MOD_X, IIO_LE, + ST_SENSORS_DEFAULT_12_REALBITS, ST_ACCEL_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, ST_SENSORS_SCAN_Y, IIO_MOD_Y, IIO_LE, + ST_SENSORS_DEFAULT_12_REALBITS, ST_ACCEL_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, ST_SENSORS_SCAN_Z, IIO_MOD_Z, IIO_LE, + ST_SENSORS_DEFAULT_12_REALBITS, 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, ST_SENSORS_SCAN_X, IIO_MOD_X, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_ACCEL_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, ST_SENSORS_SCAN_Y, IIO_MOD_Y, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_ACCEL_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, ST_SENSORS_SCAN_Z, IIO_MOD_Z, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, 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 = ST_ACCEL_1_DRDY_IRQ_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 = ST_ACCEL_2_DRDY_IRQ_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 = ST_ACCEL_3_DRDY_IRQ_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) +{ + int err; + struct st_sensor_data *adata = iio_priv(indio_dev); + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &accel_info; + + err = st_sensors_check_device_support(indio_dev, + ARRAY_SIZE(st_accel_sensors), st_accel_sensors); + if (err < 0) + goto st_accel_common_probe_error; + + 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; + + err = st_sensors_init_sensor(indio_dev); + if (err < 0) + goto st_accel_common_probe_error; + + if (adata->get_irq_data_ready(indio_dev) > 0) { + err = st_accel_allocate_ring(indio_dev); + if (err < 0) + goto st_accel_common_probe_error; + + 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; + + return err; + +st_accel_device_register_error: + if (adata->get_irq_data_ready(indio_dev) > 0) + st_sensors_deallocate_trigger(indio_dev); +st_accel_probe_trigger_error: + if (adata->get_irq_data_ready(indio_dev) > 0) + st_accel_deallocate_ring(indio_dev); +st_accel_common_probe_error: + 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); + + 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); + } + iio_device_free(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 00000000..ffc9d097 --- /dev/null +++ b/drivers/iio/accel/st_accel_i2c.c @@ -0,0 +1,86 @@ +/* + * 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 = iio_device_alloc(sizeof(*adata)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto iio_device_alloc_error; + } + + adata = iio_priv(indio_dev); + adata->dev = &client->dev; + + st_sensors_i2c_configure(indio_dev, client, adata); + + err = st_accel_common_probe(indio_dev); + if (err < 0) + goto st_accel_common_probe_error; + + return 0; + +st_accel_common_probe_error: + iio_device_free(indio_dev); +iio_device_alloc_error: + return err; +} + +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 00000000..22b35bfe --- /dev/null +++ b/drivers/iio/accel/st_accel_spi.c @@ -0,0 +1,85 @@ +/* + * 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 = iio_device_alloc(sizeof(*adata)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto iio_device_alloc_error; + } + + adata = iio_priv(indio_dev); + adata->dev = &spi->dev; + + st_sensors_spi_configure(indio_dev, spi, adata); + + err = st_accel_common_probe(indio_dev); + if (err < 0) + goto st_accel_common_probe_error; + + return 0; + +st_accel_common_probe_error: + iio_device_free(indio_dev); +iio_device_alloc_error: + return err; +} + +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 00000000..ab0767e6 --- /dev/null +++ b/drivers/iio/adc/Kconfig @@ -0,0 +1,160 @@ +# +# ADC drivers +# +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 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 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 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 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 AT91_ADC + tristate "Atmel AT91 ADC" + depends on ARCH_AT91 + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select SYSFS + help + Say yes here to build support for Atmel AT91 ADC. + +config EXYNOS_ADC + bool "Exynos ADC driver support" + depends on OF + 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 + bool "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 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 ADC driver" + depends on MFD_TI_AM335X_TSCADC + help + Say yes here to build support for Texas Instruments ADC + driver which is also a MFD client. + +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. + +endmenu diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile new file mode 100644 index 00000000..0a825bed --- /dev/null +++ b/drivers/iio/adc/Makefile @@ -0,0 +1,19 @@ +# +# Makefile for IIO ADC drivers +# + +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_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_TI_ADC081C) += ti-adc081c.o +obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o +obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o diff --git a/drivers/iio/adc/ad7266.c b/drivers/iio/adc/ad7266.c new file mode 100644 index 00000000..c2744a75 --- /dev/null +++ b/drivers/iio/adc/ad7266.c @@ -0,0 +1,539 @@ +/* + * 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_uv; + + 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). + */ + uint8_t data[ALIGN(4, sizeof(s64)) + sizeof(s64)] ____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, 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, 1); +} + +static int ad7266_preenable(struct iio_dev *indio_dev) +{ + struct ad7266_state *st = iio_priv(indio_dev); + int ret; + + ret = ad7266_wakeup(st); + if (ret) + return ret; + + ret = iio_sw_buffer_preenable(indio_dev); + if (ret) + ad7266_powerdown(st); + + return ret; +} + +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, 4); + if (ret == 0) { + if (indio_dev->scan_timestamp) + ((s64 *)st->data)[1] = pf->timestamp; + iio_push_to_buffers(indio_dev, (u8 *)st->data); + } + + 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[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_uv; + 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_uv = (st->vref_uv * 100); + if (st->mode == AD7266_MODE_DIFF) + scale_uv *= 2; + if (st->range == AD7266_RANGE_2VREF) + scale_uv *= 2; + + scale_uv >>= chan->scan_type.realbits; + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + 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 unsigned long ad7266_available_scan_masks[] = { + 0x003, + 0x00c, + 0x030, + 0x0c0, + 0x300, + 0xc00, + 0x000, +}; + +static unsigned long ad7266_available_scan_masks_diff[] = { + 0x003, + 0x00c, + 0x030, + 0x000, +}; + +static unsigned long ad7266_available_scan_masks_fixed[] = { + 0x003, + 0x000, +}; + +struct ad7266_chan_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; + 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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "vref"); + if (!IS_ERR_OR_NULL(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + ret = regulator_get_voltage(st->reg); + if (ret < 0) + goto error_disable_reg; + + st->vref_uv = ret; + } else { + /* Use internal reference */ + st->vref_uv = 2500000; + } + + 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; + st->single_xfer[0].len = 2; + st->single_xfer[0].cs_change = 1; + /* conversion */ + st->single_xfer[1].rx_buf = &st->data; + st->single_xfer[1].len = 4; + st->single_xfer[1].cs_change = 1; + /* powerdown */ + st->single_xfer[2].tx_buf = &st->data; + 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); +error_put_reg: + if (!IS_ERR_OR_NULL(st->reg)) + regulator_put(st->reg); + + iio_device_free(indio_dev); + + 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); + regulator_put(st->reg); + } + iio_device_free(indio_dev); + + 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 00000000..03b77189 --- /dev/null +++ b/drivers/iio/adc/ad7298.c @@ -0,0 +1,408 @@ +/* + * 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); + s64 time_ns = 0; + int b_sent; + + b_sent = spi_sync(st->spi, &st->ring_msg); + if (b_sent) + goto done; + + if (indio_dev->scan_timestamp) { + time_ns = iio_get_time_ns(); + memcpy((u8 *)st->rx_buf + indio_dev->scan_bytes - sizeof(s64), + &time_ns, sizeof(time_ns)); + } + + iio_push_to_buffers(indio_dev, (u8 *)st->rx_buf); + +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 = iio_device_alloc(sizeof(*st)); + int ret; + + 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 = regulator_get(&spi->dev, "vref"); + if (IS_ERR(st->reg)) { + ret = PTR_ERR(st->reg); + goto error_free; + } + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + } + + 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); +error_put_reg: + if (st->ext_ref) + regulator_put(st->reg); +error_free: + iio_device_free(indio_dev); + + 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); + regulator_put(st->reg); + } + iio_device_free(indio_dev); + + 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 00000000..2e98bef4 --- /dev/null +++ b/drivers/iio/adc/ad7476.c @@ -0,0 +1,332 @@ +/* + * 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); + s64 time_ns; + int b_sent; + + b_sent = spi_sync(st->spi, &st->msg); + if (b_sent < 0) + goto done; + + time_ns = iio_get_time_ns(); + + if (indio_dev->scan_timestamp) + ((s64 *)st->data)[1] = time_ns; + + iio_push_to_buffers(indio_dev, st->data); +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; + } + scale_uv >>= chan->scan_type.realbits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + 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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + st->chip_info = + &ad7476_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + st->reg = regulator_get(&spi->dev, "vcc"); + if (IS_ERR(st->reg)) { + ret = PTR_ERR(st->reg); + goto error_free_dev; + } + + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + 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); +error_put_reg: + regulator_put(st->reg); +error_free_dev: + iio_device_free(indio_dev); + +error_ret: + 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); + regulator_put(st->reg); + iio_device_free(indio_dev); + + 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 00000000..5e8d1da6 --- /dev/null +++ b/drivers/iio/adc/ad7791.c @@ -0,0 +1,460 @@ +/* + * 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); + unsigned long long scale_pv; + + 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) { + scale_pv = 5850000000000ULL; + } else { + int voltage_uv; + + voltage_uv = regulator_get_voltage(st->reg); + if (voltage_uv < 0) + return voltage_uv; + scale_pv = (unsigned long long)voltage_uv * 1000000; + } + if (unipolar) + scale_pv >>= chan->scan_type.realbits; + else + scale_pv >>= chan->scan_type.realbits - 1; + *val2 = do_div(scale_pv, 1000000000); + *val = scale_pv; + + return IIO_VAL_INT_PLUS_NANO; + } + + 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 = iio_device_alloc(sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "refin"); + if (IS_ERR(st->reg)) { + ret = PTR_ERR(st->reg); + goto err_iio_free; + } + + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + 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); +error_put_reg: + regulator_put(st->reg); +err_iio_free: + iio_device_free(indio_dev); + + 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); + regulator_put(st->reg); + + iio_device_free(indio_dev); + + 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 00000000..334e31ff --- /dev/null +++ b/drivers/iio/adc/ad7793.c @@ -0,0 +1,876 @@ +/* + * 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 = iio_device_alloc(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 = regulator_get(&spi->dev, "refin"); + if (IS_ERR(st->reg)) { + ret = PTR_ERR(st->reg); + goto error_device_free; + } + + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + 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); +error_put_reg: + if (pdata->refsel != AD7793_REFSEL_INTERNAL) + regulator_put(st->reg); +error_device_free: + iio_device_free(indio_dev); + + 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); + regulator_put(st->reg); + } + + iio_device_free(indio_dev); + + 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 00000000..3394ebd4 --- /dev/null +++ b/drivers/iio/adc/ad7887.c @@ -0,0 +1,390 @@ +/* + * 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); + int ret; + + ret = iio_sw_buffer_preenable(indio_dev); + if (ret < 0) + return ret; + + /* 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); + s64 time_ns; + int b_sent; + + b_sent = spi_sync(st->spi, st->ring_msg); + if (b_sent) + goto done; + + time_ns = iio_get_time_ns(); + + if (indio_dev->scan_timestamp) + memcpy(st->data + indio_dev->scan_bytes - sizeof(s64), + &time_ns, sizeof(time_ns)); + + iio_push_to_buffers(indio_dev, st->data); +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 = iio_device_alloc(sizeof(*st)); + uint8_t mode; + int ret; + + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + if (!pdata || !pdata->use_onchip_ref) { + st->reg = regulator_get(&spi->dev, "vref"); + if (IS_ERR(st->reg)) { + ret = PTR_ERR(st->reg); + goto error_free; + } + + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + } + + 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); +error_put_reg: + if (st->reg) + regulator_put(st->reg); +error_free: + iio_device_free(indio_dev); + + 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); + regulator_put(st->reg); + } + iio_device_free(indio_dev); + + 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 00000000..97fa0d3d --- /dev/null +++ b/drivers/iio/adc/ad7923.c @@ -0,0 +1,383 @@ +/* + * 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); + s64 time_ns = 0; + int b_sent; + + b_sent = spi_sync(st->spi, &st->ring_msg); + if (b_sent) + goto done; + + if (indio_dev->scan_timestamp) { + time_ns = iio_get_time_ns(); + memcpy((u8 *)st->rx_buf + indio_dev->scan_bytes - sizeof(s64), + &time_ns, sizeof(time_ns)); + } + + iio_push_to_buffers(indio_dev, (u8 *)st->rx_buf); + +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 = iio_device_alloc(sizeof(*st)); + const struct ad7923_chip_info *info; + int ret; + + 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 = regulator_get(&spi->dev, "refin"); + if (IS_ERR(st->reg)) { + ret = PTR_ERR(st->reg); + goto error_free; + } + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + 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); +error_put_reg: + regulator_put(st->reg); +error_free: + iio_device_free(indio_dev); + + 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); + regulator_put(st->reg); + iio_device_free(indio_dev); + + 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/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c new file mode 100644 index 00000000..05d2733e --- /dev/null +++ b/drivers/iio/adc/ad_sigma_delta.c @@ -0,0 +1,558 @@ +/* + * 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; + INIT_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; + INIT_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); + + INIT_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); + + /* Guaranteed to be aligned with 8 byte boundary */ + if (indio_dev->scan_timestamp) + ((s64 *)data)[1] = pf->timestamp; + + 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(indio_dev, (uint8_t *)data); + + 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 = { + .preenable = &iio_sw_buffer_preenable, + .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 = iio_trigger_get(sigma_delta->trig); + + return 0; + +error_free_irq: + free_irq(sigma_delta->spi->irq, sigma_delta); +error_free_trig: + iio_trigger_free(sigma_delta->trig); +error_ret: + return ret; +} + +static void ad_sd_remove_trigger(struct iio_dev *indio_dev) +{ + struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev); + + iio_trigger_unregister(sigma_delta->trig); + free_irq(sigma_delta->spi->irq, sigma_delta); + iio_trigger_free(sigma_delta->trig); +} + +/** + * ad_sd_setup_buffer_and_trigger() - + * @indio_dev: The IIO device + */ +int ad_sd_setup_buffer_and_trigger(struct iio_dev *indio_dev) +{ + int ret; + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &ad_sd_trigger_handler, &ad_sd_buffer_setup_ops); + if (ret) + return ret; + + ret = ad_sd_probe_trigger(indio_dev); + if (ret) { + iio_triggered_buffer_cleanup(indio_dev); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(ad_sd_setup_buffer_and_trigger); + +/** + * ad_sd_cleanup_buffer_and_trigger() - + * @indio_dev: The IIO device + */ +void ad_sd_cleanup_buffer_and_trigger(struct iio_dev *indio_dev) +{ + ad_sd_remove_trigger(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); +} +EXPORT_SYMBOL_GPL(ad_sd_cleanup_buffer_and_trigger); + +/** + * ad_sd_init() - Initializes a ad_sigma_delta struct + * @sigma_delta: The ad_sigma_delta device + * @indio_dev: The IIO device which the Sigma Delta device is used for + * @spi: The SPI device for the ad_sigma_delta device + * @info: Device specific callbacks and options + * + * This function needs to be called before any other operations are performed on + * the ad_sigma_delta struct. + */ +int ad_sd_init(struct ad_sigma_delta *sigma_delta, struct iio_dev *indio_dev, + struct spi_device *spi, const struct ad_sigma_delta_info *info) +{ + sigma_delta->spi = spi; + sigma_delta->info = info; + iio_device_set_drvdata(indio_dev, sigma_delta); + + return 0; +} +EXPORT_SYMBOL_GPL(ad_sd_init); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("Analog Devices Sigma-Delta ADCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c new file mode 100644 index 00000000..14fdaf0f --- /dev/null +++ b/drivers/iio/adc/at91_adc.c @@ -0,0 +1,796 @@ +/* + * 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/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> + +#include <mach/at91_adc.h> + +#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)) + +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; +}; + +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++; + } + + if (idev->scan_timestamp) { + s64 *timestamp = (s64 *)((u8 *)st->buffer + + ALIGN(j, sizeof(s64))); + *timestamp = pf->timestamp; + } + + iio_push_to_buffers(idev, (u8 *)st->buffer); + + 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; +} + +static irqreturn_t at91_adc_eoc_trigger(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); + + if (!(status & st->registers->drdy_mask)) + return IRQ_HANDLED; + + 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); + } + + 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; + + 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 * 1000) >> chan->scan_type.realbits; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + 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 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->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; + + if (of_property_read_u32(node, "atmel,adc-num-channels", &prop)) { + dev_err(&idev->dev, "Missing adc-num-channels property in the DT.\n"); + ret = -EINVAL; + goto error_ret; + } + st->num_channels = 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 = devm_kzalloc(&idev->dev, + sizeof(struct at91_adc_reg_desc), + GFP_KERNEL); + if (!st->registers) { + dev_err(&idev->dev, "Could not allocate register memory.\n"); + ret = -ENOMEM; + goto error_ret; + } + + if (of_property_read_u32(node, "atmel,adc-channel-base", &prop)) { + dev_err(&idev->dev, "Missing adc-channel-base property in the DT.\n"); + ret = -EINVAL; + goto error_ret; + } + st->registers->channel_base = prop; + + if (of_property_read_u32(node, "atmel,adc-drdy-mask", &prop)) { + dev_err(&idev->dev, "Missing adc-drdy-mask property in the DT.\n"); + ret = -EINVAL; + goto error_ret; + } + st->registers->drdy_mask = prop; + + if (of_property_read_u32(node, "atmel,adc-status-register", &prop)) { + dev_err(&idev->dev, "Missing adc-status-register property in the DT.\n"); + ret = -EINVAL; + goto error_ret; + } + st->registers->status_register = prop; + + if (of_property_read_u32(node, "atmel,adc-trigger-register", &prop)) { + dev_err(&idev->dev, "Missing adc-trigger-register property in the DT.\n"); + ret = -EINVAL; + goto error_ret; + } + st->registers->trigger_register = prop; + + 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++; + } + + 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->use_external = pdata->use_external_triggers; + st->vref_mv = pdata->vref; + st->channels_mask = pdata->channels_used; + st->num_channels = pdata->num_channels; + st->startup_time = pdata->startup_time; + st->trigger_number = pdata->trigger_number; + st->trigger_list = pdata->trigger_list; + st->registers = pdata->registers; + + return 0; +} + +static const struct iio_info at91_adc_info = { + .driver_module = THIS_MODULE, + .read_raw = &at91_adc_read_raw, +}; + +static int at91_adc_probe(struct platform_device *pdev) +{ + unsigned int prsc, mstrclk, ticks, adc_clk, shtim; + int ret; + struct iio_dev *idev; + struct at91_adc_state *st; + struct resource *res; + u32 reg; + + idev = iio_device_alloc(sizeof(struct at91_adc_state)); + if (idev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + 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"); + ret = -EINVAL; + goto error_free_device; + } + + 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"); + ret = -ENODEV; + goto error_free_device; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + st->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(st->reg_base)) { + ret = PTR_ERR(st->reg_base); + goto error_free_device; + } + + /* + * 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); + ret = request_irq(st->irq, + at91_adc_eoc_trigger, + 0, + pdev->dev.driver->name, + idev); + if (ret) { + dev_err(&pdev->dev, "Failed to allocate IRQ.\n"); + goto error_free_device; + } + + 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); + 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; + } + + /* + * 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 + */ + ticks = round_up((st->startup_time * adc_clk / + 1000000) - 1, 8) / 8; + /* + * 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 + */ + shtim = round_up((st->sample_hold_time * adc_clk / + 1000000) - 1, 1); + + reg = AT91_ADC_PRESCAL_(prsc) & AT91_ADC_PRESCAL; + reg |= AT91_ADC_STARTUP_(ticks) & AT91_ADC_STARTUP; + 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); + + 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"); + goto error_unregister_buffer; + } + + ret = iio_device_register(idev); + if (ret < 0) { + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto error_remove_triggers; + } + + return 0; + +error_remove_triggers: + at91_adc_trigger_remove(idev); +error_unregister_buffer: + at91_adc_buffer_remove(idev); +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); +error_free_device: + iio_device_free(idev); +error_ret: + 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); + at91_adc_trigger_remove(idev); + at91_adc_buffer_remove(idev); + clk_disable_unprepare(st->adc_clk); + clk_disable_unprepare(st->clk); + free_irq(st->irq, idev); + iio_device_free(idev); + + return 0; +} + +static const struct of_device_id at91_adc_dt_ids[] = { + { .compatible = "atmel,at91sam9260-adc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, at91_adc_dt_ids); + +static struct platform_driver at91_adc_driver = { + .probe = at91_adc_probe, + .remove = at91_adc_remove, + .driver = { + .name = "at91_adc", + .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 00000000..b3d03d33 --- /dev/null +++ b/drivers/iio/adc/exynos_adc.c @@ -0,0 +1,452 @@ +/* + * 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/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(1000)) + +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 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; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + /* 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_interruptible_timeout + (&info->completion, EXYNOS_ADC_TIMEOUT); + *val = info->value; + + mutex_unlock(&indio_dev->mlock); + + if (timeout == 0) + return -ETIMEDOUT; + + return IIO_VAL_INT; +} + +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 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_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 = iio_device_alloc(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_request_and_ioremap(&pdev->dev, mem); + if (!info->regs) { + ret = -ENOMEM; + goto err_iio; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + info->enable_reg = devm_request_and_ioremap(&pdev->dev, mem); + if (!info->enable_reg) { + ret = -ENOMEM; + goto err_iio; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + ret = irq; + goto err_iio; + } + + info->irq = irq; + + init_completion(&info->completion); + + 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_iio; + } + + writel(1, info->enable_reg); + + 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); + goto err_irq; + } + + 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)); + ret = PTR_ERR(info->vdd); + goto err_irq; + } + + 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 = iio_device_register(indio_dev); + if (ret) + goto err_irq; + + ret = regulator_enable(info->vdd); + if (ret) + goto err_iio_dev; + + clk_prepare_enable(info->clk); + + exynos_adc_hw_init(info); + + ret = of_platform_populate(np, exynos_adc_match, NULL, &pdev->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(&pdev->dev, NULL, + exynos_adc_remove_devices); + regulator_disable(info->vdd); + clk_disable_unprepare(info->clk); +err_iio_dev: + iio_device_unregister(indio_dev); +err_irq: + free_irq(info->irq, info); +err_iio: + iio_device_free(indio_dev); + 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(&pdev->dev, NULL, + exynos_adc_remove_devices); + regulator_disable(info->vdd); + clk_disable_unprepare(info->clk); + writel(0, info->enable_reg); + iio_device_unregister(indio_dev); + free_irq(info->irq, info); + iio_device_free(indio_dev); + + 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)); + } + + clk_disable_unprepare(info->clk); + writel(0, info->enable_reg); + 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; + + writel(1, info->enable_reg); + clk_prepare_enable(info->clk); + + 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 00000000..62bc39e9 --- /dev/null +++ b/drivers/iio/adc/lp8788_adc.c @@ -0,0 +1,258 @@ +/* + * 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 = iio_device_alloc(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) + goto err_iio_map; + + 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); +err_iio_map: + iio_device_free(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); + iio_device_free(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 00000000..b2b5dcbf --- /dev/null +++ b/drivers/iio/adc/max1363.c @@ -0,0 +1,1667 @@ + /* + * 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 + * + * 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. + * + * max1363.c + * + * Partial support for max1363 and similar chips. + * + * Not currently implemented. + * + * - Control of internal reference. + */ + +#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 + */ +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; +}; + +#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_write_basic_config(struct i2c_client *client, + unsigned char d1, + unsigned char d2) +{ + u8 tx_buf[2] = {d1, d2}; + + return i2c_master_send(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->client, + st->setupbyte, + st->configbyte); +} + +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 = i2c_master_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 = i2c_master_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; + unsigned long scale_uv; + + 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: + scale_uv = st->vref_uv >> st->chip_info->bits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + 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, +}; + +#define MAX1363_EV_M \ + (IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) \ + | IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING)) + +#define MAX1363_CHAN_U(num, addr, si, bits, evmask) \ + { \ + .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_mask = evmask, \ + } + +/* bipolar channel */ +#define MAX1363_CHAN_B(num, num2, addr, si, bits, evmask) \ + { \ + .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_mask = evmask, \ + } + +#define MAX1363_4X_CHANS(bits, em) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, em), \ + MAX1363_CHAN_U(1, _s1, 1, bits, em), \ + MAX1363_CHAN_U(2, _s2, 2, bits, em), \ + MAX1363_CHAN_U(3, _s3, 3, bits, em), \ + MAX1363_CHAN_B(0, 1, d0m1, 4, bits, em), \ + MAX1363_CHAN_B(2, 3, d2m3, 5, bits, em), \ + MAX1363_CHAN_B(1, 0, d1m0, 6, bits, em), \ + MAX1363_CHAN_B(3, 2, d3m2, 7, bits, em), \ + IIO_CHAN_SOFT_TIMESTAMP(8) \ + } + +static const struct iio_chan_spec max1036_channels[] = MAX1363_4X_CHANS(8, 0); +static const struct iio_chan_spec max1136_channels[] = MAX1363_4X_CHANS(10, 0); +static const struct iio_chan_spec max1236_channels[] = MAX1363_4X_CHANS(12, 0); +static const struct iio_chan_spec max1361_channels[] = + MAX1363_4X_CHANS(10, MAX1363_EV_M); +static const struct iio_chan_spec max1363_channels[] = + MAX1363_4X_CHANS(12, MAX1363_EV_M); + +/* 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, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, 0), \ + MAX1363_CHAN_U(2, _s2, 2, bits, 0), \ + MAX1363_CHAN_U(3, _s3, 3, bits, 0), \ + MAX1363_CHAN_U(4, _s4, 4, bits, 0), \ + MAX1363_CHAN_U(5, _s5, 5, bits, 0), \ + MAX1363_CHAN_U(6, _s6, 6, bits, 0), \ + MAX1363_CHAN_U(7, _s7, 7, bits, 0), \ + MAX1363_CHAN_U(8, _s8, 8, bits, 0), \ + MAX1363_CHAN_U(9, _s9, 9, bits, 0), \ + MAX1363_CHAN_U(10, _s10, 10, bits, 0), \ + MAX1363_CHAN_U(11, _s11, 11, bits, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 12, bits, 0), \ + MAX1363_CHAN_B(2, 3, d2m3, 13, bits, 0), \ + MAX1363_CHAN_B(4, 5, d4m5, 14, bits, 0), \ + MAX1363_CHAN_B(6, 7, d6m7, 15, bits, 0), \ + MAX1363_CHAN_B(8, 9, d8m9, 16, bits, 0), \ + MAX1363_CHAN_B(10, 11, d10m11, 17, bits, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 18, bits, 0), \ + MAX1363_CHAN_B(3, 2, d3m2, 19, bits, 0), \ + MAX1363_CHAN_B(5, 4, d5m4, 20, bits, 0), \ + MAX1363_CHAN_B(7, 6, d7m6, 21, bits, 0), \ + MAX1363_CHAN_B(9, 8, d9m8, 22, bits, 0), \ + MAX1363_CHAN_B(11, 10, d11m10, 23, bits, 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, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, 0), \ + MAX1363_CHAN_U(2, _s2, 2, bits, 0), \ + MAX1363_CHAN_U(3, _s3, 3, bits, 0), \ + MAX1363_CHAN_U(4, _s4, 4, bits, 0), \ + MAX1363_CHAN_U(5, _s5, 5, bits, 0), \ + MAX1363_CHAN_U(6, _s6, 6, bits, 0), \ + MAX1363_CHAN_U(7, _s7, 7, bits, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 8, bits, 0), \ + MAX1363_CHAN_B(2, 3, d2m3, 9, bits, 0), \ + MAX1363_CHAN_B(4, 5, d4m5, 10, bits, 0), \ + MAX1363_CHAN_B(6, 7, d6m7, 11, bits, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 12, bits, 0), \ + MAX1363_CHAN_B(3, 2, d3m2, 13, bits, 0), \ + MAX1363_CHAN_B(5, 4, d5m4, 14, bits, 0), \ + MAX1363_CHAN_B(7, 6, d7m6, 15, bits, 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, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 2, bits, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 3, bits, 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 = strict_strtoul(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, + u64 event_code, + int *val) +{ + struct max1363_state *st = iio_priv(indio_dev); + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_FALLING) + *val = st->thresh_low[IIO_EVENT_CODE_EXTRACT_CHAN(event_code)]; + else + *val = st->thresh_high[IIO_EVENT_CODE_EXTRACT_CHAN(event_code)]; + return 0; +} + +static int max1363_write_thresh(struct iio_dev *indio_dev, + u64 event_code, + int val) +{ + 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 (IIO_EVENT_CODE_EXTRACT_DIR(event_code)) { + case IIO_EV_DIR_FALLING: + st->thresh_low[IIO_EVENT_CODE_EXTRACT_CHAN(event_code)] = val; + break; + case IIO_EV_DIR_RISING: + st->thresh_high[IIO_EVENT_CODE_EXTRACT_CHAN(event_code)] = val; + break; + } + + 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 }; + + i2c_master_recv(st->client, &rx, 1); + mask = rx; + for_each_set_bit(loc, &mask, 8) + iio_push_event(indio_dev, max1363_event_codes[loc], timestamp); + i2c_master_send(st->client, tx, 2); + + return IRQ_HANDLED; +} + +static int max1363_read_event_config(struct iio_dev *indio_dev, + u64 event_code) +{ + struct max1363_state *st = iio_priv(indio_dev); + int val; + int number = IIO_EVENT_CODE_EXTRACT_CHAN(event_code); + + mutex_lock(&indio_dev->mlock); + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == 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->client, + st->setupbyte, + st->configbyte); + } + + /* 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 = i2c_master_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 = i2c_master_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, + u64 event_code, + int state) +{ + int ret = 0; + struct max1363_state *st = iio_priv(indio_dev); + u16 unifiedmask; + int number = IIO_EVENT_CODE_EXTRACT_CHAN(event_code); + + mutex_lock(&indio_dev->mlock); + unifiedmask = st->mask_low | st->mask_high; + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == 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 = 4098, + .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 = 4098, + .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 = 4098, + .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); + s64 time_ns; + __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 = i2c_master_recv(st->client, rxbuf, numvals*2); + else + b_sent = i2c_master_recv(st->client, rxbuf, numvals); + if (b_sent < 0) + goto done_free; + + time_ns = iio_get_time_ns(); + + if (indio_dev->scan_timestamp) + memcpy(rxbuf + d_size - sizeof(s64), &time_ns, sizeof(time_ns)); + iio_push_to_buffers(indio_dev, rxbuf); + +done_free: + kfree(rxbuf); +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops max1363_buffered_setup_ops = { + .postenable = &iio_triggered_buffer_postenable, + .preenable = &iio_sw_buffer_preenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +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 = iio_device_alloc(sizeof(struct max1363_state)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_out; + } + + indio_dev->dev.of_node = client->dev.of_node; + ret = iio_map_array_register(indio_dev, client->dev.platform_data); + if (ret < 0) + goto error_free_device; + + 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(&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; + } + + 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, &max1363_buffered_setup_ops); + 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); +error_free_device: + iio_device_free(indio_dev); +error_out: + 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); + iio_device_free(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/ti-adc081c.c b/drivers/iio/adc/ti-adc081c.c new file mode 100644 index 00000000..2826faae --- /dev/null +++ b/drivers/iio/adc/ti-adc081c.c @@ -0,0 +1,161 @@ +/* + * 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/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 = iio_device_alloc(sizeof(*adc)); + if (!iio) + return -ENOMEM; + + adc = iio_priv(iio); + adc->i2c = client; + + adc->ref = regulator_get(&client->dev, "vref"); + if (IS_ERR(adc->ref)) { + err = PTR_ERR(adc->ref); + goto iio_free; + } + + err = regulator_enable(adc->ref); + if (err < 0) + goto regulator_put; + + 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); +regulator_put: + regulator_put(adc->ref); +iio_free: + iio_device_free(iio); + + 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); + regulator_put(adc->ref); + iio_device_free(iio); + + 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 00000000..5f9a7e7d --- /dev/null +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -0,0 +1,260 @@ +/* + * 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/init.h> +#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/mfd/ti_am335x_tscadc.h> +#include <linux/platform_data/ti_am335x_adc.h> + +struct tiadc_device { + struct ti_tscadc_dev *mfd_tscadc; + int channels; +}; + +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 void tiadc_step_config(struct tiadc_device *adc_dev) +{ + unsigned int stepconfig; + int i, channels = 0, 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; + channels = TOTAL_CHANNELS - adc_dev->channels; + + stepconfig = STEPCONFIG_AVG_16 | STEPCONFIG_FIFO1; + + for (i = (steps + 1); i <= TOTAL_STEPS; i++) { + tiadc_writel(adc_dev, REG_STEPCONFIG(i), + stepconfig | STEPCONFIG_INP(channels)); + tiadc_writel(adc_dev, REG_STEPDELAY(i), + STEPCONFIG_OPENDLY); + channels++; + } + tiadc_writel(adc_dev, REG_SE, STPENB_STEPENB); +} + +static int tiadc_channel_init(struct iio_dev *indio_dev, int channels) +{ + struct iio_chan_spec *chan_array; + int i; + + indio_dev->num_channels = channels; + chan_array = kcalloc(indio_dev->num_channels, + sizeof(struct iio_chan_spec), GFP_KERNEL); + + if (chan_array == NULL) + return -ENOMEM; + + for (i = 0; i < (indio_dev->num_channels); i++) { + struct iio_chan_spec *chan = chan_array + i; + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->channel = i; + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + } + + indio_dev->channels = chan_array; + + return indio_dev->num_channels; +} + +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; + unsigned int fifo1count, readx1; + + /* + * When the sub-system is first enabled, + * the sequencer will always start with the + * lowest step (1) and continue until step (16). + * For ex: If we have enabled 4 ADC channels and + * currently use only 1 out of them, the + * sequencer still configures all the 4 steps, + * leading to 3 unwanted data. + * Hence we need to flush out this data. + */ + + fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT); + for (i = 0; i < fifo1count; i++) { + readx1 = tiadc_readl(adc_dev, REG_FIFO1); + if (i == chan->channel) + *val = readx1 & 0xfff; + } + tiadc_writel(adc_dev, REG_SE, STPENB_STEPENB); + + return IIO_VAL_INT; +} + +static const struct iio_info tiadc_info = { + .read_raw = &tiadc_read_raw, +}; + +static int tiadc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct tiadc_device *adc_dev; + struct ti_tscadc_dev *tscadc_dev = pdev->dev.platform_data; + struct mfd_tscadc_board *pdata; + int err; + + pdata = tscadc_dev->dev->platform_data; + if (!pdata || !pdata->adc_init) { + dev_err(&pdev->dev, "Could not find platform data\n"); + return -EINVAL; + } + + indio_dev = iio_device_alloc(sizeof(struct tiadc_device)); + if (indio_dev == NULL) { + dev_err(&pdev->dev, "failed to allocate iio device\n"); + err = -ENOMEM; + goto err_ret; + } + adc_dev = iio_priv(indio_dev); + + adc_dev->mfd_tscadc = tscadc_dev; + adc_dev->channels = pdata->adc_init->adc_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(adc_dev); + + err = tiadc_channel_init(indio_dev, adc_dev->channels); + if (err < 0) + goto err_free_device; + + err = iio_device_register(indio_dev); + if (err) + goto err_free_channels; + + platform_set_drvdata(pdev, indio_dev); + + return 0; + +err_free_channels: + tiadc_channels_remove(indio_dev); +err_free_device: + iio_device_free(indio_dev); +err_ret: + return err; +} + +static int tiadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + tiadc_channels_remove(indio_dev); + + iio_device_free(indio_dev); + + 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 = dev->platform_data; + unsigned int idle; + + 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(adc_dev); + + 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 struct platform_driver tiadc_driver = { + .driver = { + .name = "tiadc", + .owner = THIS_MODULE, + .pm = TIADC_PM_OPS, + }, + .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/viperboard_adc.c b/drivers/iio/adc/viperboard_adc.c new file mode 100644 index 00000000..56ac481c --- /dev/null +++ b/drivers/iio/adc/viperboard_adc.c @@ -0,0 +1,181 @@ +/* + * 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), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 8, \ + .storagebits = 8, \ + }, \ +} + +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->scan_index; + 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 = iio_device_alloc(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 = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "could not register iio (adc)"); + goto error; + } + + platform_set_drvdata(pdev, indio_dev); + + return 0; + +error: + iio_device_free(indio_dev); + return ret; +} + +static int vprbrd_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + iio_device_free(indio_dev); + + return 0; +} + +static struct platform_driver vprbrd_adc_driver = { + .driver = { + .name = "viperboard-adc", + .owner = THIS_MODULE, + }, + .probe = vprbrd_adc_probe, + .remove = vprbrd_adc_remove, +}; + +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/amplifiers/Kconfig b/drivers/iio/amplifiers/Kconfig new file mode 100644 index 00000000..05d707ed --- /dev/null +++ b/drivers/iio/amplifiers/Kconfig @@ -0,0 +1,17 @@ +# +# Gain Amplifiers, etc. +# +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 00000000..a6ca3669 --- /dev/null +++ b/drivers/iio/amplifiers/Makefile @@ -0,0 +1,5 @@ +# +# Makefile iio/amplifiers +# + +obj-$(CONFIG_AD8366) += ad8366.o diff --git a/drivers/iio/amplifiers/ad8366.c b/drivers/iio/amplifiers/ad8366.c new file mode 100644 index 00000000..d354554b --- /dev/null +++ b/drivers/iio/amplifiers/ad8366.c @@ -0,0 +1,222 @@ +/* + * 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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + } + + 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); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + + iio_device_free(indio_dev); + + 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); + regulator_put(reg); + } + + iio_device_free(indio_dev); + + 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 00000000..9d19ba74 --- /dev/null +++ b/drivers/iio/buffer_cb.c @@ -0,0 +1,116 @@ +#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)(u8 *data, void *private); + void *private; + struct iio_channel *channels; +}; + +static int iio_buffer_cb_store_to(struct iio_buffer *buffer, u8 *data) +{ + struct iio_cb_buffer *cb_buff = container_of(buffer, + struct iio_cb_buffer, + buffer); + + return cb_buff->cb(data, cb_buff->private); +} + +static struct iio_buffer_access_funcs iio_cb_access = { + .store_to = &iio_buffer_cb_store_to, +}; + +struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, + int (*cb)(u8 *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) { + ret = -ENOMEM; + goto error_ret; + } + + 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); +error_ret: + 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) +{ + kfree(cb_buff->buffer.scan_mask); + iio_channel_release_all(cb_buff->channels); + kfree(cb_buff); +} +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 00000000..0b6e97d1 --- /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 00000000..c2352beb --- /dev/null +++ b/drivers/iio/common/Makefile @@ -0,0 +1,10 @@ +# +# 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. +# + +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 00000000..1178121b --- /dev/null +++ b/drivers/iio/common/hid-sensors/Kconfig @@ -0,0 +1,37 @@ +# +# 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. + +config HID_SENSOR_ENUM_BASE_QUIRKS + bool "ENUM base quirks for HID Sensor IIO drivers" + depends on HID_SENSOR_IIO_COMMON + help + Say yes here to build support for sensor hub FW using + enumeration, which is using 1 as base instead of 0. + Since logical minimum is still set 0 instead of 1, + there is no easy way to differentiate. + +endmenu diff --git a/drivers/iio/common/hid-sensors/Makefile b/drivers/iio/common/hid-sensors/Makefile new file mode 100644 index 00000000..22e7c5a8 --- /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 00000000..75b54730 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-attributes.c @@ -0,0 +1,249 @@ +/* + * HID Sensors Driver + * Copyright (c) 2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/hid-sensor-hub.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +static 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; +} + +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); + +int hid_sensor_parse_common_attributes(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); + + 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 00000000..87419c41 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -0,0 +1,100 @@ +/* + * 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" + +static int hid_sensor_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct hid_sensor_common *st = iio_trigger_get_drvdata(trig); + int state_val; + + state_val = state ? 1 : 0; + if (IS_ENABLED(CONFIG_HID_SENSOR_ENUM_BASE_QUIRKS)) + ++state_val; + st->data_ready = state; + sensor_hub_set_feature(st->hsdev, st->power_state.report_id, + st->power_state.index, + (s32)state_val); + + sensor_hub_set_feature(st->hsdev, st->report_state.report_id, + st->report_state.index, + (s32)state_val); + + return 0; +} + +void hid_sensor_remove_trigger(struct iio_dev *indio_dev) +{ + iio_trigger_unregister(indio_dev->trig); + iio_trigger_free(indio_dev->trig); + indio_dev->trig = NULL; +} +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 = 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 00000000..9a873147 --- /dev/null +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.h @@ -0,0 +1,26 @@ +/* + * 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 iio_dev *indio_dev); + +#endif diff --git a/drivers/iio/common/st_sensors/Kconfig b/drivers/iio/common/st_sensors/Kconfig new file mode 100644 index 00000000..865f1ca3 --- /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 00000000..9f3e24f3 --- /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 00000000..09b236d6 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_buffer.c @@ -0,0 +1,116 @@ +/* + * 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) +{ + int i, n = 0, len; + u8 addr[ST_SENSORS_NUMBER_DATA_CHANNELS]; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + for (i = 0; i < ST_SENSORS_NUMBER_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], ST_SENSORS_BYTE_FOR_CHANNEL, buf, + sdata->multiread_bit); + break; + case 2: + if ((addr[1] - addr[0]) == ST_SENSORS_BYTE_FOR_CHANNEL) { + len = sdata->tf->read_multiple_byte(&sdata->tb, + sdata->dev, addr[0], + ST_SENSORS_BYTE_FOR_CHANNEL*n, + buf, sdata->multiread_bit); + } else { + u8 rx_array[ST_SENSORS_BYTE_FOR_CHANNEL* + ST_SENSORS_NUMBER_DATA_CHANNELS]; + len = sdata->tf->read_multiple_byte(&sdata->tb, + sdata->dev, addr[0], + ST_SENSORS_BYTE_FOR_CHANNEL* + ST_SENSORS_NUMBER_DATA_CHANNELS, + rx_array, sdata->multiread_bit); + if (len < 0) + goto read_data_channels_error; + + for (i = 0; i < n * ST_SENSORS_NUMBER_DATA_CHANNELS; + i++) { + if (i < n) + buf[i] = rx_array[i]; + else + buf[i] = rx_array[n + i]; + } + len = ST_SENSORS_BYTE_FOR_CHANNEL*n; + } + break; + case 3: + len = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev, + addr[0], ST_SENSORS_BYTE_FOR_CHANNEL* + ST_SENSORS_NUMBER_DATA_CHANNELS, + buf, sdata->multiread_bit); + break; + default: + len = -EINVAL; + goto read_data_channels_error; + } + if (len != ST_SENSORS_BYTE_FOR_CHANNEL*n) { + len = -EIO; + goto read_data_channels_error; + } + +read_data_channels_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; + + if (indio_dev->scan_timestamp) + *(s64 *)((u8 *)sdata->buffer_data + + ALIGN(len, sizeof(s64))) = pf->timestamp; + + iio_push_to_buffers(indio_dev, sdata->buffer_data); + +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 00000000..ed9bc8ae --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -0,0 +1,447 @@ +/* + * 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 <asm/unaligned.h> + +#include <linux/iio/common/st_sensors.h> + + +#define ST_SENSORS_WAI_ADDRESS 0x0f + +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); + +int st_sensors_init_sensor(struct iio_dev *indio_dev) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + mutex_init(&sdata->tb.buf_lock); + + err = st_sensors_set_enable(indio_dev, false); + if (err < 0) + goto init_error; + + err = st_sensors_set_fullscale(indio_dev, + sdata->current_fullscale->num); + if (err < 0) + goto init_error; + + err = st_sensors_set_odr(indio_dev, sdata->odr); + if (err < 0) + goto init_error; + + /* set BDU */ + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->bdu.addr, sdata->sensor->bdu.mask, true); + if (err < 0) + goto init_error; + + err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); + +init_error: + return err; +} +EXPORT_SYMBOL(st_sensors_init_sensor); + +int st_sensors_set_dataready_irq(struct iio_dev *indio_dev, bool enable) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + /* 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; + } + + /* Enable/Disable the interrupt generator for data ready. */ + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor->drdy_irq.addr, + sdata->sensor->drdy_irq.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, + u8 ch_addr, int *data) +{ + int err; + u8 outdata[ST_SENSORS_BYTE_FOR_CHANNEL]; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + err = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev, + ch_addr, ST_SENSORS_BYTE_FOR_CHANNEL, + outdata, sdata->multiread_bit); + if (err < 0) + goto read_error; + + *data = (s16)get_unaligned_le16(outdata); + +read_error: + 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 read_error; + } else { + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto read_error; + + msleep((sdata->sensor->bootime * 1000) / sdata->odr); + err = st_sensors_read_axis_data(indio_dev, ch->address, val); + if (err < 0) + goto read_error; + + *val = *val >> ch->scan_type.shift; + + err = st_sensors_set_enable(indio_dev, false); + } + mutex_unlock(&indio_dev->mlock); + + return err; + +read_error: + 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 00000000..38af9440 --- /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 00000000..251baf6a --- /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 00000000..8d8ca6f1 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c @@ -0,0 +1,77 @@ +/* + * STMicroelectronics sensors trigger library driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/interrupt.h> + +#include <linux/iio/common/st_sensors.h> + + +int st_sensors_allocate_trigger(struct iio_dev *indio_dev, + const struct iio_trigger_ops *trigger_ops) +{ + int err; + struct st_sensor_data *sdata = iio_priv(indio_dev); + + sdata->trig = iio_trigger_alloc("%s-trigger", indio_dev->name); + if (sdata->trig == NULL) { + err = -ENOMEM; + dev_err(&indio_dev->dev, "failed to allocate iio trigger.\n"); + goto iio_trigger_alloc_error; + } + + err = request_threaded_irq(sdata->get_irq_data_ready(indio_dev), + iio_trigger_generic_data_rdy_poll, + NULL, + IRQF_TRIGGER_RISING, + sdata->trig->name, + sdata->trig); + if (err) + goto request_irq_error; + + iio_trigger_set_drvdata(sdata->trig, indio_dev); + sdata->trig->ops = trigger_ops; + sdata->trig->dev.parent = sdata->dev; + + err = iio_trigger_register(sdata->trig); + if (err < 0) { + dev_err(&indio_dev->dev, "failed to register iio trigger.\n"); + goto iio_trigger_register_error; + } + indio_dev->trig = iio_trigger_get(sdata->trig); + + return 0; + +iio_trigger_register_error: + free_irq(sdata->get_irq_data_ready(indio_dev), sdata->trig); +request_irq_error: + iio_trigger_free(sdata->trig); +iio_trigger_alloc_error: + return err; +} +EXPORT_SYMBOL(st_sensors_allocate_trigger); + +void st_sensors_deallocate_trigger(struct iio_dev *indio_dev) +{ + struct st_sensor_data *sdata = iio_priv(indio_dev); + + iio_trigger_unregister(sdata->trig); + free_irq(sdata->get_irq_data_ready(indio_dev), sdata->trig); + iio_trigger_free(sdata->trig); +} +EXPORT_SYMBOL(st_sensors_deallocate_trigger); + +MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics ST-sensors trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig new file mode 100644 index 00000000..b61160bd --- /dev/null +++ b/drivers/iio/dac/Kconfig @@ -0,0 +1,154 @@ +# +# DAC drivers +# +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 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 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 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, AD5660, AD5662 DACs. + + To compile this driver as a module, choose M here: the + module will be called ad5446. + +config AD5449 + tristate "Analog Device 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 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 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 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 00000000..5b528ebb --- /dev/null +++ b/drivers/iio/dac/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for industrial I/O DAC drivers +# + +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_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 00000000..aa26d50a --- /dev/null +++ b/drivers/iio/dac/ad5064.c @@ -0,0 +1,688 @@ +/* + * 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; + + scale_uv = (scale_uv * 100) >> chan->scan_type.realbits; + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + 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, + }, + IIO_ENUM("powerdown_mode", false, &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 = IIO_ST('u', (bits), 16, 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 = iio_device_alloc(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 = regulator_bulk_get(dev, ad5064_num_vref(st), + st->vref_reg); + if (ret) { + if (!st->chip_info->internal_vref) + goto error_free; + 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); + goto error_free; + } + } else { + ret = regulator_bulk_enable(ad5064_num_vref(st), st->vref_reg); + if (ret) + goto error_free_reg; + } + + 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); +error_free_reg: + if (!st->use_internal_vref) + regulator_bulk_free(ad5064_num_vref(st), st->vref_reg); +error_free: + iio_device_free(indio_dev); + + 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); + regulator_bulk_free(ad5064_num_vref(st), st->vref_reg); + } + + iio_device_free(indio_dev); + + 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 00000000..80923af4 --- /dev/null +++ b/drivers/iio/dac/ad5360.c @@ -0,0 +1,565 @@ +/* + * 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 = IIO_ST('u', (bits), 16, 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: + /* vout = 4 * vref * dac_code */ + scale_uv = ad5360_get_channel_vref(st, chan->channel) * 4 * 100; + if (scale_uv < 0) + return scale_uv; + + scale_uv >>= (chan->scan_type.realbits); + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + 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 = iio_device_alloc(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); + goto error_free; + } + + for (i = 0; i < st->chip_info->num_vrefs; ++i) + st->vref_reg[i].supply = ad5360_vref_name[i]; + + ret = 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_reg; + } + + 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_reg: + regulator_bulk_free(st->chip_info->num_vrefs, st->vref_reg); +error_free_channels: + kfree(indio_dev->channels); +error_free: + iio_device_free(indio_dev); + + 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); + regulator_bulk_free(st->chip_info->num_vrefs, st->vref_reg); + + iio_device_free(indio_dev); + + 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 00000000..bf2db022 --- /dev/null +++ b/drivers/iio/dac/ad5380.c @@ -0,0 +1,659 @@ +/* + * 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); + unsigned long scale_uv; + 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: + scale_uv = ((2 * st->vref) >> chan->scan_type.realbits) * 100; + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + 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, + }, + IIO_ENUM("powerdown_mode", true, &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 = IIO_ST('u', (_bits), 16, 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 = 1250000, + }, + [ID_AD5380_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 40, + .int_vref = 2500000, + }, + [ID_AD5381_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 1250000, + }, + [ID_AD5381_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 2500000, + }, + [ID_AD5382_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 32, + .int_vref = 1250000, + }, + [ID_AD5382_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 32, + .int_vref = 2500000, + }, + [ID_AD5383_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 32, + .int_vref = 1250000, + }, + [ID_AD5383_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 32, + .int_vref = 2500000, + }, + [ID_AD5390_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 16, + .int_vref = 1250000, + }, + [ID_AD5390_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 16, + .int_vref = 2500000, + }, + [ID_AD5391_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 1250000, + }, + [ID_AD5391_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 2500000, + }, + [ID_AD5392_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 8, + .int_vref = 1250000, + }, + [ID_AD5392_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 8, + .int_vref = 2500000, + }, +}; + +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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + dev_err(dev, "Failed to allocate iio device\n"); + ret = -ENOMEM; + goto error_out; + } + + 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); + goto error_free; + } + + if (st->chip_info->int_vref == 2500000) + ctrl |= AD5380_CTRL_INT_VREF_2V5; + + st->vref_reg = 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; + } 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: + if (!IS_ERR(st->vref_reg)) + regulator_put(st->vref_reg); + + kfree(indio_dev->channels); +error_free: + iio_device_free(indio_dev); +error_out: + + 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); + regulator_put(st->vref_reg); + } + + iio_device_free(indio_dev); + + 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 00000000..98f24407 --- /dev/null +++ b/drivers/iio/dac/ad5421.c @@ -0,0 +1,539 @@ +/* + * 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 { + u32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +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 = IIO_ST('u', 16, 16, 0), + .event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + }, + { + .type = IIO_TEMP, + .channel = -1, + .event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + }, +}; + +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 inline unsigned int ad5421_get_scale(struct ad5421_state *st) +{ + unsigned int min, max; + + ad5421_get_current_min_max(st, &min, &max); + return ((max - min) * 1000) / (1 << 16); +} + +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); + 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: + *val = 0; + *val2 = ad5421_get_scale(st); + return IIO_VAL_INT_PLUS_MICRO; + 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, + u64 event_code, int state) +{ + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int mask; + + switch (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code)) { + case IIO_CURRENT: + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + 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, + u64 event_code) +{ + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int mask; + + switch (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code)) { + case IIO_CURRENT: + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + 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, u64 event_code, + int *val) +{ + int ret; + + switch (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code)) { + 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 0; +} + +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 = iio_device_alloc(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 = request_threaded_irq(spi->irq, + NULL, + ad5421_fault_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ad5421 fault", + indio_dev); + if (ret) + goto error_free; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device: %d\n", ret); + goto error_free_irq; + } + + return 0; + +error_free_irq: + if (spi->irq) + free_irq(spi->irq, indio_dev); +error_free: + iio_device_free(indio_dev); + + return ret; +} + +static int ad5421_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + if (spi->irq) + free_irq(spi->irq, indio_dev); + iio_device_free(indio_dev); + + return 0; +} + +static struct spi_driver ad5421_driver = { + .driver = { + .name = "ad5421", + .owner = THIS_MODULE, + }, + .probe = ad5421_probe, + .remove = ad5421_remove, +}; +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 00000000..cae8f605 --- /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, + }, + IIO_ENUM("powerdown_mode", false, &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 = IIO_ST('u', (bits), (storage), (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); + unsigned long scale_uv; + + switch (m) { + case IIO_CHAN_INFO_RAW: + *val = st->cached_val; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + + } + 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 = regulator_get(dev, "vcc"); + if (!IS_ERR(reg)) { + ret = regulator_enable(reg); + if (ret) + goto error_put_reg; + + ret = regulator_get_voltage(reg); + if (ret < 0) + goto error_disable_reg; + + voltage_uv = ret; + } + + indio_dev = iio_device_alloc(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_free_device; + + return 0; + +error_free_device: + iio_device_free(indio_dev); +error_disable_reg: + if (!IS_ERR(reg)) + regulator_disable(reg); +error_put_reg: + if (!IS_ERR(reg)) + regulator_put(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); + regulator_put(st->reg); + } + iio_device_free(indio_dev); + + 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_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_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}, + {"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 00000000..ba1c914b --- /dev/null +++ b/drivers/iio/dac/ad5449.c @@ -0,0 +1,376 @@ +/* + * 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_message msg; + 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, + }, + }; + + spi_message_init(&msg); + spi_message_add_tail(&t[0], &msg); + spi_message_add_tail(&t[1], &msg); + + 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(st->spi, &msg); + 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 = IIO_ST('u', (bits), 16, 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 = iio_device_alloc(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 = regulator_bulk_get(&spi->dev, st->chip_info->num_channels, + st->vref_reg); + if (ret) + goto error_free; + + ret = regulator_bulk_enable(st->chip_info->num_channels, st->vref_reg); + if (ret) + goto error_free_reg; + + 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); +error_free_reg: + regulator_bulk_free(st->chip_info->num_channels, st->vref_reg); +error_free: + iio_device_free(indio_dev); + + 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); + regulator_bulk_free(st->chip_info->num_channels, st->vref_reg); + + iio_device_free(indio_dev); + + 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 00000000..139206e8 --- /dev/null +++ b/drivers/iio/dac/ad5504.c @@ -0,0 +1,393 @@ +/* + * 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 + */ + +struct ad5504_state { + struct spi_device *spi; + struct regulator *reg; + unsigned short vref_mv; + unsigned pwr_down_mask; + unsigned pwr_down_mode; +}; + +/** + * ad5504_supported_device_ids: + */ + +enum ad5504_supported_device_ids { + ID_AD5504, + ID_AD5501, +}; + +static int ad5504_spi_write(struct spi_device *spi, u8 addr, u16 val) +{ + u16 tmp = cpu_to_be16(AD5504_CMD_WRITE | + AD5504_ADDR(addr) | + (val & AD5504_RES_MASK)); + + return spi_write(spi, (u8 *)&tmp, 2); +} + +static int ad5504_spi_read(struct spi_device *spi, u8 addr) +{ + u16 tmp = cpu_to_be16(AD5504_CMD_READ | AD5504_ADDR(addr)); + u16 val; + int ret; + struct spi_transfer t = { + .tx_buf = &tmp, + .rx_buf = &val, + .len = 2, + }; + ret = spi_sync_transfer(spi, &t, 1); + + if (ret < 0) + return ret; + + return be16_to_cpu(val) & 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); + unsigned long scale_uv; + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ad5504_spi_read(st->spi, chan->address); + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + + } + 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->spi, 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->spi, 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->spi, 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, + }, + IIO_ENUM("powerdown_mode", true, &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 = IIO_ST('u', 12, 16, 0), \ + .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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(reg)) { + ret = regulator_enable(reg); + if (ret) + goto error_put_reg; + + 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 = request_threaded_irq(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_free_irq; + + return 0; + +error_free_irq: + if (spi->irq) + free_irq(spi->irq, indio_dev); +error_disable_reg: + if (!IS_ERR(reg)) + regulator_disable(reg); +error_put_reg: + if (!IS_ERR(reg)) + regulator_put(reg); + + iio_device_free(indio_dev); +error_ret: + 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 (spi->irq) + free_irq(spi->irq, indio_dev); + + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_device_free(indio_dev); + + 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 00000000..5dca3028 --- /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 00000000..bb298aaf --- /dev/null +++ b/drivers/iio/dac/ad5624r_spi.c @@ -0,0 +1,328 @@ +/* + * 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); + unsigned long scale_uv; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + + } + 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, + }, + IIO_ENUM("powerdown_mode", true, &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 = IIO_ST('u', (_bits), 16, 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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + 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); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + iio_device_free(indio_dev); +error_ret: + + 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); + regulator_put(st->reg); + } + iio_device_free(indio_dev); + + 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 00000000..06439b1a --- /dev/null +++ b/drivers/iio/dac/ad5686.c @@ -0,0 +1,417 @@ +/* + * 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 { + u32 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); + unsigned long scale_uv; + 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; + break; + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->vref_mv * 100000) + >> (chan->scan_type.realbits); + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + + } + 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, + }, + IIO_ENUM("powerdown_mode", false, &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 = IIO_ST('u', bits, 16, 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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + 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); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + + iio_device_free(indio_dev); + + 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); + regulator_put(st->reg); + } + iio_device_free(indio_dev); + + 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 00000000..12bb315e --- /dev/null +++ b/drivers/iio/dac/ad5755.c @@ -0,0 +1,645 @@ +/* + * 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 { + u32 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 inline int ad5755_get_scale(struct ad5755_state *st, + struct iio_chan_spec const *chan) +{ + int min, max; + + ad5755_get_min_max(st, chan, &min, &max); + return ((max - min) * 1000000000ULL) >> chan->scan_type.realbits; +} + +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 ret; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = ad5755_get_scale(st, chan); + return IIO_VAL_INT_PLUS_NANO; + 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, + }, + { }, +}; + +#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 = IIO_ST('u', (_bits), 16, 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 = iio_device_alloc(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) + goto error_free; + + ret = ad5755_setup_pdata(indio_dev, pdata); + if (ret) + goto error_free; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device: %d\n", ret); + goto error_free; + } + + return 0; + +error_free: + iio_device_free(indio_dev); + + return ret; +} + +static int ad5755_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + iio_device_free(indio_dev); + + return 0; +} + +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, + .remove = ad5755_remove, + .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 00000000..7a53f7d7 --- /dev/null +++ b/drivers/iio/dac/ad5764.c @@ -0,0 +1,377 @@ +/* + * 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 = IIO_ST('u', (_bits), 16, 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 long scale_uv; + 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 / 65535) - 0.5) */ + vref = ad5764_get_channel_vref(st, chan->channel); + if (vref < 0) + return vref; + + scale_uv = (vref * 4 * 100) >> chan->scan_type.realbits; + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + 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 = iio_device_alloc(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 = 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); + goto error_free; + } + + 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); + goto error_free_reg; + } + } + + 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); +error_free_reg: + if (st->chip_info->int_vref == 0) + regulator_bulk_free(ARRAY_SIZE(st->vref_reg), st->vref_reg); +error_free: + iio_device_free(indio_dev); + + 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); + regulator_bulk_free(ARRAY_SIZE(st->vref_reg), st->vref_reg); + } + + iio_device_free(indio_dev); + + 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 00000000..97c1e5d7 --- /dev/null +++ b/drivers/iio/dac/ad5791.c @@ -0,0 +1,490 @@ +/* + * 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; +}; + +/** + * ad5791_supported_device_ids: + */ + +enum ad5791_supported_device_ids { + ID_AD5760, + ID_AD5780, + ID_AD5781, + ID_AD5791, +}; + +static int ad5791_spi_write(struct spi_device *spi, u8 addr, u32 val) +{ + union { + u32 d32; + u8 d8[4]; + } data; + + data.d32 = cpu_to_be32(AD5791_CMD_WRITE | + AD5791_ADDR(addr) | + (val & AD5791_DAC_MASK)); + + return spi_write(spi, &data.d8[1], 3); +} + +static int ad5791_spi_read(struct spi_device *spi, u8 addr, u32 *val) +{ + union { + u32 d32; + u8 d8[4]; + } data[3]; + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &data[0].d8[1], + .bits_per_word = 8, + .len = 3, + .cs_change = 1, + }, { + .tx_buf = &data[1].d8[1], + .rx_buf = &data[2].d8[1], + .bits_per_word = 8, + .len = 3, + }, + }; + + data[0].d32 = cpu_to_be32(AD5791_CMD_READ | + AD5791_ADDR(addr)); + data[1].d32 = cpu_to_be32(AD5791_ADDR(AD5791_ADDR_NOOP)); + + ret = spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); + + *val = be32_to_cpu(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->spi, 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->spi, 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 = 0; + *val2 = (((u64)st->vref_mv) * 1000000ULL) >> chan->scan_type.realbits; + return IIO_VAL_INT_PLUS_MICRO; + 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 = true, + .read = ad5791_read_dac_powerdown, + .write = ad5791_write_dac_powerdown, + }, + IIO_ENUM("powerdown_mode", true, &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 = IIO_ST('u', bits, 24, 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->spi, 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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + st->reg_vdd = regulator_get(&spi->dev, "vdd"); + if (!IS_ERR(st->reg_vdd)) { + ret = regulator_enable(st->reg_vdd); + if (ret) + goto error_put_reg_pos; + + ret = regulator_get_voltage(st->reg_vdd); + if (ret < 0) + goto error_disable_reg_pos; + + pos_voltage_uv = ret; + } + + st->reg_vss = regulator_get(&spi->dev, "vss"); + if (!IS_ERR(st->reg_vss)) { + ret = regulator_enable(st->reg_vss); + if (ret) + goto error_put_reg_neg; + + 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(spi, 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(spi, 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_put_reg_neg: + if (!IS_ERR(st->reg_vss)) + regulator_put(st->reg_vss); + +error_disable_reg_pos: + if (!IS_ERR(st->reg_vdd)) + regulator_disable(st->reg_vdd); +error_put_reg_pos: + if (!IS_ERR(st->reg_vdd)) + regulator_put(st->reg_vdd); + iio_device_free(indio_dev); +error_ret: + + 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); + regulator_put(st->reg_vdd); + } + + if (!IS_ERR(st->reg_vss)) { + regulator_disable(st->reg_vss); + regulator_put(st->reg_vss); + } + iio_device_free(indio_dev); + + 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/max517.c b/drivers/iio/dac/max517.c new file mode 100644 index 00000000..ebfaa415 --- /dev/null +++ b/drivers/iio/dac/max517.c @@ -0,0 +1,242 @@ +/* + * 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/init.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); + unsigned int scale_uv; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + /* Corresponds to Vref / 2^(bits) */ + scale_uv = (data->vref_mv[chan->channel] * 1000) >> 8; + *val = scale_uv / 1000000; + *val2 = scale_uv % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + 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), \ + .scan_type = IIO_ST('u', 8, 8, 0), \ +} + +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; + int err; + + indio_dev = iio_device_alloc(sizeof(*data)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto exit; + } + 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]; + } + + err = iio_device_register(indio_dev); + if (err) + goto exit_free_device; + + dev_info(&client->dev, "DAC registered\n"); + + return 0; + +exit_free_device: + iio_device_free(indio_dev); +exit: + return err; +} + +static int max517_remove(struct i2c_client *client) +{ + iio_device_unregister(i2c_get_clientdata(client)); + iio_device_free(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 00000000..a612ec76 --- /dev/null +++ b/drivers/iio/dac/mcp4725.c @@ -0,0 +1,227 @@ +/* + * 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) + * + * writing the DAC value to EEPROM is not supported + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/err.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; +}; + +#ifdef CONFIG_PM_SLEEP +static int mcp4725_suspend(struct device *dev) +{ + u8 outbuf[2]; + + outbuf[0] = 0x3 << 4; /* power-down bits, 500 kOhm resistor */ + outbuf[1] = 0; + + return i2c_master_send(to_i2c_client(dev), outbuf, 2); +} + +static int mcp4725_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct mcp4725_data *data = iio_priv(indio_dev); + u8 outbuf[2]; + + /* restore previous DAC value */ + outbuf[0] = (data->dac_value >> 8) & 0xf; + outbuf[1] = data->dac_value & 0xff; + + return i2c_master_send(to_i2c_client(dev), outbuf, 2); +} + +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 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), + .scan_type = IIO_ST('u', 12, 16, 0), +}; + +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); + unsigned long scale_uv; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = data->dac_value; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = (data->vref_mv * 1000) >> 12; + *val = scale_uv / 1000000; + *val2 = scale_uv % 1000000; + return IIO_VAL_INT_PLUS_MICRO; + } + 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, + .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]; + int err; + + if (!platform_data || !platform_data->vref_mv) { + dev_err(&client->dev, "invalid platform data"); + err = -EINVAL; + goto exit; + } + + indio_dev = iio_device_alloc(sizeof(*data)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto exit; + } + 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"); + goto exit_free_device; + } + data->dac_value = (inbuf[1] << 4) | (inbuf[2] >> 4); + + err = iio_device_register(indio_dev); + if (err) + goto exit_free_device; + + dev_info(&client->dev, "MCP4725 DAC registered\n"); + + return 0; + +exit_free_device: + iio_device_free(indio_dev); +exit: + return err; +} + +static int mcp4725_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_device_free(indio_dev); + + 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 00000000..6aaa33ef --- /dev/null +++ b/drivers/iio/frequency/Kconfig @@ -0,0 +1,41 @@ +# +# Frequency +# Direct Digital Synthesis drivers (DDS) +# Clock Distribution device drivers +# Phase-Locked Loop (PLL) frequency synthesizers +# + +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 00000000..00d26e5d --- /dev/null +++ b/drivers/iio/frequency/Makefile @@ -0,0 +1,6 @@ +# +# Makefile iio/frequency +# + +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 00000000..92276dee --- /dev/null +++ b/drivers/iio/frequency/ad9523.c @@ -0,0 +1,1049 @@ +/* + * 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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + } + + 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); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + + iio_device_free(indio_dev); + + 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); + regulator_put(st->reg); + } + + iio_device_free(indio_dev); + + 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 00000000..e76d4ace --- /dev/null +++ b/drivers/iio/frequency/adf4350.c @@ -0,0 +1,486 @@ +/* + * ADF4350/ADF4351 SPI Wideband Synthesizer 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/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/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; + 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]; + + /* + * 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 = { + .clkin = 122880000, + .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; + + 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; + 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; + else + st->clkin = readin; + 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: + 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; + } + 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, \ +} + +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, +}; + +static int adf4350_probe(struct spi_device *spi) +{ + struct adf4350_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev; + struct adf4350_state *st; + int ret; + + if (!pdata) { + dev_warn(&spi->dev, "no platform data? using default\n"); + + pdata = &default_pdata; + } + + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + } + + 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; + 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 = gpio_request(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_free_gpio; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_gpio; + + return 0; + +error_free_gpio: + if (gpio_is_valid(pdata->gpio_lock_detect)) + gpio_free(pdata->gpio_lock_detect); + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + + iio_device_free(indio_dev); + + 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 (!IS_ERR(reg)) { + regulator_disable(reg); + regulator_put(reg); + } + + if (gpio_is_valid(st->pdata->gpio_lock_detect)) + gpio_free(st->pdata->gpio_lock_detect); + + iio_device_free(indio_dev); + + 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 <hennerich@blackfin.uclinux.org>"); +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 00000000..107cafcb --- /dev/null +++ b/drivers/iio/gyro/Kconfig @@ -0,0 +1,81 @@ +# +# IIO Digital Gyroscope Sensor drivers configuration +# +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 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 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) + select IIO_ST_GYRO_BUFFER if (IIO_TRIGGERED_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, will be created + these modules: + - 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 00000000..225d2890 --- /dev/null +++ b/drivers/iio/gyro/Makefile @@ -0,0 +1,20 @@ +# +# Makefile for industrial I/O gyroscope sensor drivers +# + +obj-$(CONFIG_ADIS16080) += adis16080.o +obj-$(CONFIG_ADIS16136) += adis16136.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 00000000..e1bb5f99 --- /dev/null +++ b/drivers/iio/gyro/adis16080.c @@ -0,0 +1,259 @@ +/* + * 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); + struct spi_message m; + 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); + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + ret = spi_sync(st->us, &m); + 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); + int ret; + struct adis16080_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + 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; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + return 0; + +error_free_dev: + iio_device_free(indio_dev); +error_ret: + return ret; +} + +static int adis16080_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_device_free(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/adis16136.c b/drivers/iio/gyro/adis16136.c new file mode 100644 index 00000000..058e6d5c --- /dev/null +++ b/drivers/iio/gyro/adis16136.c @@ -0,0 +1,581 @@ +/* + * 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 = iio_device_alloc(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) + goto error_free_dev; + + ret = adis_setup_buffer_and_trigger(&adis16136->adis, indio_dev, NULL); + if (ret) + goto error_free_dev; + + 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); +error_free_dev: + iio_device_free(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); + + iio_device_free(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/adxrs450.c b/drivers/iio/gyro/adxrs450.c new file mode 100644 index 00000000..8bd72b49 --- /dev/null +++ b/drivers/iio/gyro/adxrs450.c @@ -0,0 +1,494 @@ +/* + * 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 spi_message msg; + 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); + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + 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 spi_message msg; + 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); + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + 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; + } + break; + 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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + 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 = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + /* Get the device into a sane initial state */ + ret = adxrs450_initial_setup(indio_dev); + if (ret) + goto error_initial; + return 0; +error_initial: + iio_device_unregister(indio_dev); +error_free_dev: + iio_device_free(indio_dev); + +error_ret: + return ret; +} + +static int adxrs450_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_device_free(spi_get_drvdata(spi)); + + 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, + .remove = adxrs450_remove, + .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 00000000..bc943dd4 --- /dev/null +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c @@ -0,0 +1,404 @@ +/* + * 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> +#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" + +/*Format: HID-SENSOR-usage_id_in_hex*/ +/*Usage ID from spec for Gyro-3D: 0x200076*/ +#define DRIVER_NAME "HID-SENSOR-200076" + +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]; +}; + +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_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_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_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; + int ret_type; + + *val = 0; + *val2 = 0; + switch (mask) { + case 0: + 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; + return -EINVAL; + } + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = gyro_state->gyro[CHANNEL_SCAN_INDEX_X].units; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_OFFSET: + *val = hid_sensor_convert_exponent( + gyro_state->gyro[CHANNEL_SCAN_INDEX_X].unit_expo); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_read_samp_freq_value( + &gyro_state->common_attributes, val, val2); + ret_type = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_read_raw_hyst_value( + &gyro_state->common_attributes, val, val2); + ret_type = IIO_VAL_INT_PLUS_MICRO; + 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 int gyro_3d_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_MICRO; +} + +static const struct iio_info gyro_3d_info = { + .driver_module = THIS_MODULE, + .read_raw = &gyro_3d_read_raw, + .write_raw = &gyro_3d_write_raw, + .write_raw_get_fmt = &gyro_3d_write_raw_get_fmt, +}; + +/* 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 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 [%d]\n", + gyro_state->common_attributes.data_ready); + if (gyro_state->common_attributes.data_ready) + hid_sensor_push_data(indio_dev, + (u8 *)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); + + 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 = iio_device_alloc(sizeof(struct gyro_3d_state)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + 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"); + goto error_free_dev; + } + + channels = kmemdup(gyro_3d_channels, sizeof(gyro_3d_channels), + GFP_KERNEL); + if (!channels) { + ret = -ENOMEM; + dev_err(&pdev->dev, "failed to duplicate channels\n"); + goto error_free_dev; + } + + 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; + } + gyro_state->common_attributes.data_ready = false; + 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(indio_dev); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: + kfree(indio_dev->channels); +error_free_dev: + iio_device_free(indio_dev); +error_ret: + 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); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + kfree(indio_dev->channels); + iio_device_free(indio_dev); + + return 0; +} + +static struct platform_driver hid_gyro_3d_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .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 00000000..14917fae --- /dev/null +++ b/drivers/iio/gyro/itg3200_buffer.c @@ -0,0 +1,156 @@ +/* + * 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; + + if (indio_dev->scan_timestamp) + memcpy(buf + indio_dev->scan_bytes - sizeof(s64), + &pf->timestamp, sizeof(pf->timestamp)); + + iio_push_to_buffers(indio_dev, (u8 *)buf); + iio_trigger_notify_done(indio_dev->trig); + +error_ret: + return IRQ_HANDLED; +} + +int itg3200_buffer_configure(struct iio_dev *indio_dev) +{ + return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + itg3200_trigger_handler, NULL); +} + +void itg3200_buffer_unconfigure(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} + + +static int itg3200_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + int ret; + u8 msc; + + ret = itg3200_read_reg_8(indio_dev, ITG3200_REG_IRQ_CONFIG, &msc); + if (ret) + goto error_ret; + + if (state) + msc |= ITG3200_IRQ_DATA_RDY_ENABLE; + else + msc &= ~ITG3200_IRQ_DATA_RDY_ENABLE; + + ret = itg3200_write_reg_8(indio_dev, ITG3200_REG_IRQ_CONFIG, msc); + if (ret) + goto error_ret; + +error_ret: + return ret; + +} + +static const struct iio_trigger_ops itg3200_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &itg3200_data_rdy_trigger_set_state, +}; + +int itg3200_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct itg3200 *st = iio_priv(indio_dev); + + st->trig = iio_trigger_alloc("%s-dev%d", indio_dev->name, + indio_dev->id); + if (!st->trig) + return -ENOMEM; + + ret = request_irq(st->i2c->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "itg3200_data_rdy", + st->trig); + if (ret) + goto error_free_trig; + + + st->trig->dev.parent = &st->i2c->dev; + st->trig->ops = &itg3200_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = iio_trigger_register(st->trig); + if (ret) + goto error_free_irq; + + /* select default trigger */ + indio_dev->trig = iio_trigger_get(st->trig); + + return 0; + +error_free_irq: + free_irq(st->i2c->irq, st->trig); +error_free_trig: + iio_trigger_free(st->trig); + return ret; +} + +void itg3200_remove_trigger(struct iio_dev *indio_dev) +{ + struct itg3200 *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->i2c->irq, st->trig); + iio_trigger_free(st->trig); +} diff --git a/drivers/iio/gyro/itg3200_core.c b/drivers/iio/gyro/itg3200_core.c new file mode 100644 index 00000000..d66605d2 --- /dev/null +++ b/drivers/iio/gyro/itg3200_core.c @@ -0,0 +1,398 @@ +/* + * 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; + } + + return ret; +} + +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 = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + 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) + goto error_free_dev; + + 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); +error_free_dev: + iio_device_free(indio_dev); +error_ret: + 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); + + iio_device_free(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 00000000..25ee236b --- /dev/null +++ b/drivers/iio/gyro/st_gyro.h @@ -0,0 +1,44 @@ +/* + * 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" + +int st_gyro_common_probe(struct iio_dev *indio_dev); +void st_gyro_common_remove(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +int st_gyro_allocate_ring(struct iio_dev *indio_dev); +void st_gyro_deallocate_ring(struct iio_dev *indio_dev); +int st_gyro_trig_set_state(struct iio_trigger *trig, bool state); +#define ST_GYRO_TRIGGER_SET_STATE (&st_gyro_trig_set_state) +#else /* CONFIG_IIO_BUFFER */ +static inline int st_gyro_allocate_ring(struct iio_dev *indio_dev) +{ + return 0; +} +static inline void st_gyro_deallocate_ring(struct iio_dev *indio_dev) +{ +} +#define ST_GYRO_TRIGGER_SET_STATE NULL +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* ST_GYRO_H */ diff --git a/drivers/iio/gyro/st_gyro_buffer.c b/drivers/iio/gyro/st_gyro_buffer.c new file mode 100644 index 00000000..69017c7e --- /dev/null +++ b/drivers/iio/gyro/st_gyro_buffer.c @@ -0,0 +1,114 @@ +/* + * STMicroelectronics gyroscopes driver + * + * Copyright 2012-2013 STMicroelectronics Inc. + * + * Denis Ciocca <denis.ciocca@st.com> + * + * Licensed under the GPL-2. + */ + +#include <linux/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) +{ + int err; + + err = st_sensors_set_enable(indio_dev, true); + if (err < 0) + goto st_gyro_set_enable_error; + + err = iio_sw_buffer_preenable(indio_dev); + +st_gyro_set_enable_error: + return err; +} + +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 00000000..1fabc3ff --- /dev/null +++ b/drivers/iio/gyro/st_gyro_core.c @@ -0,0 +1,367 @@ +/* + * 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" + +/* 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_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_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, ST_SENSORS_SCAN_X, + IIO_MOD_X, IIO_LE, ST_SENSORS_DEFAULT_16_REALBITS, + ST_GYRO_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ANGL_VEL, ST_SENSORS_SCAN_Y, + IIO_MOD_Y, IIO_LE, ST_SENSORS_DEFAULT_16_REALBITS, + ST_GYRO_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_ANGL_VEL, ST_SENSORS_SCAN_Z, + IIO_MOD_Z, IIO_LE, ST_SENSORS_DEFAULT_16_REALBITS, + 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 = ST_GYRO_1_DRDY_IRQ_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 = ST_GYRO_2_DRDY_IRQ_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) +{ + int err; + struct st_sensor_data *gdata = iio_priv(indio_dev); + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &gyro_info; + + err = st_sensors_check_device_support(indio_dev, + ARRAY_SIZE(st_gyro_sensors), st_gyro_sensors); + if (err < 0) + goto st_gyro_common_probe_error; + + 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); + if (err < 0) + goto st_gyro_common_probe_error; + + if (gdata->get_irq_data_ready(indio_dev) > 0) { + err = st_gyro_allocate_ring(indio_dev); + if (err < 0) + goto st_gyro_common_probe_error; + + 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; + + return err; + +st_gyro_device_register_error: + if (gdata->get_irq_data_ready(indio_dev) > 0) + st_sensors_deallocate_trigger(indio_dev); +st_gyro_probe_trigger_error: + if (gdata->get_irq_data_ready(indio_dev) > 0) + st_gyro_deallocate_ring(indio_dev); +st_gyro_common_probe_error: + 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); + + 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); + } + iio_device_free(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 00000000..c1755ce2 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_i2c.c @@ -0,0 +1,83 @@ +/* + * 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 = iio_device_alloc(sizeof(*gdata)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto iio_device_alloc_error; + } + + gdata = iio_priv(indio_dev); + gdata->dev = &client->dev; + + st_sensors_i2c_configure(indio_dev, client, gdata); + + err = st_gyro_common_probe(indio_dev); + if (err < 0) + goto st_gyro_common_probe_error; + + return 0; + +st_gyro_common_probe_error: + iio_device_free(indio_dev); +iio_device_alloc_error: + return err; +} + +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 00000000..b37fc9e0 --- /dev/null +++ b/drivers/iio/gyro/st_gyro_spi.c @@ -0,0 +1,82 @@ +/* + * 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 = iio_device_alloc(sizeof(*gdata)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto iio_device_alloc_error; + } + + gdata = iio_priv(indio_dev); + gdata->dev = &spi->dev; + + st_sensors_spi_configure(indio_dev, spi, gdata); + + err = st_gyro_common_probe(indio_dev); + if (err < 0) + goto st_gyro_common_probe_error; + + return 0; + +st_gyro_common_probe_error: + iio_device_free(indio_dev); +iio_device_alloc_error: + return err; +} + +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/iio_core.h b/drivers/iio/iio_core.h new file mode 100644 index 00000000..05c1b745 --- /dev/null +++ b/drivers/iio/iio_core.h @@ -0,0 +1,63 @@ +/* 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, + bool generic, + struct device *dev, + struct list_head *attr_list); + +/* 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) + +#else + +#define iio_buffer_poll_addr NULL +#define iio_buffer_read_first_n_outer_addr NULL + +#endif + +int iio_device_register_eventset(struct iio_dev *indio_dev); +void iio_device_unregister_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 00000000..6f7c56fc --- /dev/null +++ b/drivers/iio/iio_core_trigger.h @@ -0,0 +1,46 @@ + +/* 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 00000000..6d12fd10 --- /dev/null +++ b/drivers/iio/imu/Kconfig @@ -0,0 +1,5 @@ + +source "drivers/staging/iio/imu/inv_mpu/Kconfig" + + + diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile new file mode 100644 index 00000000..4814d514 --- /dev/null +++ b/drivers/iio/imu/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the industrial I/O core. +# + +obj-y += inv_mpu/ + diff --git a/drivers/iio/imu/adis16400.h b/drivers/iio/imu/adis16400.h new file mode 100644 index 00000000..0916bf6b --- /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_core.c b/drivers/iio/imu/adis16400_core.c new file mode 100644 index 00000000..d6ece2d1 --- /dev/null +++ b/drivers/iio/imu/adis16400_core.c @@ -0,0 +1,971 @@ +/* + * 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 ret ? ret : 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 = iio_device_alloc(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) + goto error_free_dev; + + ret = adis_setup_buffer_and_trigger(&st->adis, indio_dev, + adis16400_trigger_handler); + if (ret) + goto error_free_dev; + + /* 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); +error_free_dev: + iio_device_free(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); + + iio_device_free(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/inv_mpu/Kconfig b/drivers/iio/imu/inv_mpu/Kconfig new file mode 100644 index 00000000..21cc237c --- /dev/null +++ b/drivers/iio/imu/inv_mpu/Kconfig @@ -0,0 +1,24 @@ +# +# inv-mpu-iio driver for Invensense MPU devices and combos +# + +config INV_MPU_IIO + tristate "Invensense MPU devices" + depends on I2C && SYSFS && IIO && IIO_KFIFO_BUF && IIO_TRIGGER && !INV_MPU + default n + help + This driver supports the Invensense MPU devices. + This includes MPU6050/MPU3050/MPU9150/ITG3500/MPU6500/MPU9250. + This driver can be built as a module. The module will be called + inv-mpu-iio. + +config INV_IIO_MPU3050_ACCEL_SLAVE_BMA250 + bool "Invensense MPU3050 slave accelerometer device for bma250" + depends on INV_MPU_IIO + default n + help + This is slave device enable MPU3050 accelerometer slave device. + Right now, it is only bma250. For other acceleromter device, + it can be added to this menu if the proper interface is filled. + There are some interface function to be defined. + diff --git a/drivers/iio/imu/inv_mpu/Makefile b/drivers/iio/imu/inv_mpu/Makefile new file mode 100644 index 00000000..3bbb2699 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/Makefile @@ -0,0 +1,27 @@ +# +# Makefile for Invensense inv-mpu-iio device. +# + +obj-$(CONFIG_INV_MPU_IIO) += inv-mpu-iio.o + +inv-mpu-iio-objs := inv_mpu_core.o +inv-mpu-iio-objs += inv_mpu_ring.o +inv-mpu-iio-objs += inv_mpu_trigger.o +inv-mpu-iio-objs += inv_mpu_misc.o +inv-mpu-iio-objs += inv_mpu3050_iio.o +inv-mpu-iio-objs += dmpDefaultMPU6050.o + +CFLAGS_inv_mpu_core.o += -Idrivers/staging/iio +CFLAGS_inv_mpu_ring.o += -Idrivers/staging/iio +CFLAGS_inv_mpu_trigger.o += -Idrivers/staging/iio +CFLAGS_inv_mpu_misc.o += -Idrivers/staging/iio +CFLAGS_inv_mpu3050_iio.o += -Idrivers/staging/iio +CFLAGS_dmpDefaultMPU6050.o += -Idrivers/staging/iio + +# the Bosch BMA250 driver is added to the inv-mpu device driver because it +# must be connected to an MPU3050 device on the secondary slave bus. +ifeq ($(CONFIG_INV_IIO_MPU3050_ACCEL_SLAVE_BMA250), y) +inv-mpu-iio-objs += inv_slave_bma250.o +CFLAGS_inv_slave_bma250.o += -Idrivers/staging/iio +endif + diff --git a/drivers/iio/imu/inv_mpu/README b/drivers/iio/imu/inv_mpu/README new file mode 100644 index 00000000..d59e2d1d --- /dev/null +++ b/drivers/iio/imu/inv_mpu/README @@ -0,0 +1,569 @@ +Kernel driver inv-mpu-iio +Author: Invensense <http://invensense.com> + +Table of Contents: +================== +- Description +- Integrating the Driver in the Linux Kernel +- Board and Platform Data + > Interrupt Pin + > Platform Data +- Board File Modifications for Secondary I2C Configuration + > MPU-6050 + AKM8963 on the secondary I2C interface + > MPU-6500 + AKM8963 on the secondary I2C interface + > MPU-9150 + > MPU-9250 + > MPU-3050 + BMA250 on the secondary I2C interface +- Board File Modifications for Invensense Devices + > MPU-3050 + > ITG-3500 + > MPU-6050 + > MPU-6500 + > MPU-9150 + > MPU-9250 +- IIO Subsystem + > Communicating with the Driver in Userspace + > ITG-3500 + > MPU-6050 and MPU-6500 + > MPU-9150 + > MPU-9250 + > MPU-3050 + BMA250 on the secondary I2C interface +- Suspend and Resume +- DMP Event +- Motion Event +- Streaming Data to an Userspace Application +- Recommended Sysfs Entry Setup Sequence + > With DMP Firmware + > Without DMP Firmware +- Test Applications + > Running Test Applications with MPU-9150/MPU-6050/MPU-6500/MPU-9250 + > Running Test Applications with MPU-3050/ITG-3500 + + + +Description +=========== +This document describes how to install the Invensense device driver into a +Linux kernel. The Invensense driver currently supports the following sensors: +- ITG-3500 +- MPU-6050 +- MPU-9150 +- MPU-6500 +- MPU-9250 +- MPU-3050 + +The slave address of each device is either 0x68 or 0x69, depending on the AD0 +pin value of the device. Please refer to the appropriate product specification +document for further information regarding the AD0 pin. The driver supports both +addresses. + +The following files are included in this package: +- Kconfig +- Makefile +- inv_mpu_core.c +- inv_mpu_misc.c +- inv_mpu_trigger.c +- inv_mpu3050_iio.c +- inv_mpu_iio.h +- inv_mpu_ring.c +- inv_slave_bma250.c +- dmpDefaultMPU6050.c +- dmpkey.h +- dmpmap.h +- mpu.h + + +Integrating the Driver in the Linux Kernel +========================================== +Please add the files as follows: +- Add mpu.h to "kernel/include/linux". +- Add all other files to drivers/staging/iio/imu/inv_mpu +(another directory is acceptable, but this is the recommended destination) + +In order to see the driver in menuconfig when building the kernel, please +make modifications as shown below: + + modify "drivers/staging/iio/imu/Kconfig" with: + >> source "drivers/staging/iio/imu/inv_mpu/Kconfig" + + modify "drivers/staging/iio/imu/Makefile" with: + >> obj-y += inv_mpu/ + + +Board and Platform Data +======================= +In order to recognize the Invensense device on the I2C bus, the board file must +be modified. +The i2c_board_info instance must be defined as shown below. + +Interrupt Pin +------------- +The hardcoded value of 140 corresponds to the GPIO input pin connected to the +Invensense device's interrupt pin. +This pin will most likely be different for your platform, and the value should +be changed accordingly. + +Platform Data +------------- +The platform data (orientation matrix and secondary bus configurations) must be +modified as show below, according to your particular platform configuration. + +Please note that the MPU-9150 it is treated as a MPU-6050 with AKM8975 on the +device's secondary I2C interface. Thus the secondary I2C address must be +provided. + +Please note that the MPU-9250 it is treated as a MPU-6500 with AKM8963 on the +device's secondary I2C interface. Thus the secondary I2C address must be +provided. + +Board File Modifications for Secondary I2C Configuration +======================================================== +For the Panda Board, the board file can be found at +arch/arm/mach-omap2/board-omap4panda.c. +Please modify the pertinent baord file in your system according to the examples +shown below: + +MPU-6050 + AKM8963 on the secondary I2C interface +------------------------------------------------- +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x10, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8963, + .secondary_i2c_addr = 0x0E +}; + +MPU-6500 + AKM8963 on the secondary I2C interface +------------------------------------------------- +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x10, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8963, + .secondary_i2c_addr = 0x0E +}; + +MPU-9150 +-------- +For MPU-9150, please provide the following secondary I2C bus information. + +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x10, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8975, + .secondary_i2c_addr = 0x0E +}; + +MPU-9250 +-------- +For MPU-9250, please provide the following secondary I2C bus information. + +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x10, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8963, + .secondary_i2c_addr = 0x0C +}; + +MPU-3050 + BMA250 on the secondary I2C interface +------------------------------------------------ +For BMA250 on the secondary I2C bus, please provide the following information. + +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x10, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_ACCEL, + .sec_slave_id = ACCEL_ID_BMA250, + .secondary_i2c_addr = 0x18, +}; + + +Board File Modifications for Invensense Devices +=============================================== +For Invensense devices, please provide the i2c init data as shown in the +examples below. + +In the _i2c_init function, the device is registered in the following manner: + + // arch/arm/mach-omap2/board-omap4panda.c + // in static int __init omap4_panda_i2c_init(void) + omap_register_i2c_bus(4, 400, + single_chip_board_info, + ARRAY_SIZE(single_chip_board_info)); + +MPU-3050 +-------- +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu3050", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +ITG-3050 +-------- +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("itg3500", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +MPU6050 +------- +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu6050", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +MPU6500 +------- +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu6500", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +MPU9150 +------- +arch/arm/mach-omap2/board-omap4panda.c +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu9150", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +MPU9250 +------- +arch/arm/mach-omap2/board-omap4panda.c +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu9250", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +IIO subsystem +============= +A successful installation will create the following two new directories under +/sys/bus/iio/devices: + - iio:device0 + - trigger0 + +Also, a new file, "iio:device0", will be created in the /dev/ diretory. +(if you have more than one IIO device, the file will be named "iio:deviceX", +where X is a number) + + +Communicating with the Driver in Userspace +------------------------------------------ +The driver generates several files in sysfs upon installation. +These files are used to communicate with the driver. The files can be found +at /sys/bus/iio/devices/iio:device0 (or ../iio:deviceX as shown above). + +A brief description of the pertinent files for each Invensense device is shown +below: + +ITG-3500 +-------- +temperature (Read-only) +--Read temperature data directly from the temperature register. + +sampling_frequency (Read/write) +--Configure the ADC sampling rate and FIFO output rate. + +sampling_frequency_available(read-only) +--show commonly used frequency + +clock_source (Read-only) +--Check which clock-source is used by the chip. + +power_state (Read/write) +--turn on/off the power supply + +self_test (read-only) +--read this entry trigger self test. The return value is D. +D is the success/fail. +For different chip, the result is different for success/fail. +1 means success 0 means fail. The LSB of D is for gyro; the bit +next to LSB of D is for accel. The bit 2 of D is for compass result. + +key (read-only) +--show the key value of this driver. Used by MPL. + +gyro_matrix (read-only) +--show the orientation matrix obtained from the board file. + +MPU-6050 and MPU-6500 +--------------------- +MPU-6050 and MPU-6500 have all sysfs files belonging to ITG-3500 (shown above). +In addition, it has the files below: + +gyro_enable (read/write) +--enable/disable gyro functionality. Affects raw_gyro. Turning this off this + will shut down gyro and save power. + +accl_enable (read/write) +--enable/disable accelerometer functionality. Affects raw_accl. +Turning this off this will shut down accel and save power. + +firmware_loaded (read/write) +--Flag indicating the whether firmware is loaded or not in the DMP engine. +0 means no firmware loaded. 1 means firmware is already loaded . This +flag can only be written as 0. It internally updates to 1. + +dmp_on(read/write) +--This entry controls whether to run DMP or not. +Write 1 to enable DMP and write 0 to disable dmp. +Please note that firmware_loaded must be 1 in order to enable DMP. + +dmp_int_on(read/write) +--This entry controls whether dmp interrupt is on/off. +Please note that firmware_loaded must be 1. +Also, we'd like to remind you that it is sometimes advantageous to +turn interrupts off while the DMP is running. + +dmp_output_rate +--control dmp output rate when dmp is on. + +dmp_event_int_on(read/write) +--This entry controls whether dmp event interrupt is on/off. +Please note that turning this on will turn off the data interrupt. +Interrupts will be generated only when events occur. +This is useful for saving power when the system is waiting for a special event +to wake up. + +dmp_firmware (write only binary file) +--DMP firmware code is loaded into this entry. +If loading is successful, the firmware_loaded flag will be updated to 1. +In order to load new firmware, the firmware_loaded flag must be first set to 0. + +accel_matrix +--orientation matrix for accelerometer. + +quaternion_on +--Turn on/off quaterniion data output. DMP is required for this feature. + +pedometer_time +pedometer_steps, +--Pedometer related entries + +event_tap +event_display_orientation +event_accel_motion +--Event related entries. +Please poll these entries to read their values. Direct reads will yield +meaningless results. +Further details are provided in the DMP Events section of this README. + +tap_on +--Controls tap function of DMP + +tap_time +tap_min_count +tap_threshold +--Tap related entries. Controls various parameters of tap function. + +display_orientation_on +--Turn on/off display orientation function of DMP. + +quaternion_on +--Turn on/off quaterniion data output. DMP is required for this feature. + +Low power accel motion interrupt related settings. +if motion_lpa_on is set, this will disable all engines except accel. Accel will +enter low power mode and the whole chip will be turned on/off at specific frequency. +----------------------------------------------------------------------------- +motion_lpa_duration +--set motion duration. in ms. This means filtered out all the motino interrupts + during this period. + +motion_lpa_threshold +--set motion threshold. in mg. The maximum is 1020mg and resolution is 32mg. + +motion_lpa_on +--turn on/off motion function. + +motion_lpa_freq +--motion lpa frequency. which determines power on/off frequency. +------------------------------------------------------------------------------ +MPU-9150 +-------- +MPU-9150 has all of MPU-6050's entries. It also has two additional entries, +described below. + +compass_enable (read/write) +--Enables compass function. + +compass_matrix (read-only) +--Compass orientation matrix + +MPU-3050 with BMA250 on secondary I2C interface +----------------------------------------------- +MPU-3050 with BMA250 on the secondary I2C interface has ever ITG-3500 entry. +It also has two additional entries, shown below: + +accl_matrix + +accl_enable + +Suspend and Resume +=================================================== +The suspend and resume functions are call backs registered to the system +and executed when the system goes in suspend and resumes. +It is enabled when CONFIG_PM is defined. +The current behavior is simple: +- suspend will turn off the chip +- resume will turn on the chip + +However, it is possible for the driver to do more complex things; +for example, leaving pedometers running when system is off. This can save whole +system power while letting pedometer working. Other behaviors are possible +too. + +DMP Event +========= +A DMP Event is an event that is output by the DMP unit within the Invensense +device (MPU). +Only the MPU-6050, MPU-6500, MPU-9250, MPU-9150, MPU-9250 feature the DMP. + +There are three sysfs entries for DMP events: +- event_tap +- event_orientation +- event_display_orientation + +These four events must be polled before reading. + +The proper method to poll sysfs is as follows: +1. open file. +2. dummy read. +3. poll. +4. once the poll passed, use fopen and fread to read the sysfs entry. +5. interpret the data. + +Motion Event +============ +Motion event are accelerometer enabled event. They don't need dmp nor data +output, only the accelerometer engine needs to be on. +MPU6050/MPU6500 supports motion and no motion event: these are the eight sysfs entries +------------------------------------------------------------------------------ +motion_threshold: +threshold for motion detection. + +motion_dur: +duration for motion to trigger. + +motion_on: +turn on/off of motion function. + +event_accel_motion: +pollable event sysfs entry for motion. +------------------------------------------------------------ + +Streaming Data to an Userspace Application +========================================== +When streaming data to an userspace application, we recommend that you access +gyro/accel/compass data via /dev/iio:device0. + +Please follow the steps below to read data at a constant rate from the driver: + +1. Write a 1 to power_state to turn on the chip. This is the default setting + after installing the driver. +2. Write the desired output rate to fifo_rate. +3. Write 1 to enable to turn on the event. +4. Read /dev/iio:device0 to get a string of gyro/accel/compass data. +5. Parse this string to obtain each gyro/accel/compass element. +6. If dmp firmware code is loaded, use "dmp_on" to enable/disable dmp. +7. If compass is enabled, the output will contain compass data. + + +Recommended Sysfs Entry Setup Senquence +======================================= + +Without DMP Firmware +-------------------- +1. Set "power_state" to 1, +2. Set the scale and fifo rate values according to your needs. +3. Set gyro_enable, accel_enable, and compass_enable according to your needs. + For example: + - If you only want gyro data, set accel_enable to 0 (and compass_enable to + 0, if applicable). + - If you only want accel data, set gyro_enable to 0 (and compass_enable to + 0, if applicable). + - If you only want compass data, set gyro_enable to 0 and accel_enable to 0. +4. Set "enable" to 1. +5. You will now get the output that you want. + +With DMP Firmware +----------------- +1. Set "power_state" to 1. +2. Write "0" to firmware_loaded if it is not zero already. +3. Load firmware into "dmp_firmware" as a whole. Don't split the DMP firmware + image. +4. Make sure firmware_loaded is 1 after loading the DMP image. +5. Make appropriate configurations as shown above in the "without DMP firmware" + case. +6. Set dmp_on to 1. +7. Set "enable" to 1. + +Please note that the enable function uses the enable entry under +"/sys/bus/iio/devices/iio:device0/buffer" + +Test Applications +================= +A test application is located under software/simple_apps/mpu_iio. +This application is stand-alone in that it cannot be run concurrently with other +entities trying to access the device node(s) or sysfs entries; in particular, +the + +Running Test Applications with MPU-9150/MPU-6050/MPU-6500/MPU-9250 +--------------------------------------------------------- +To run test applications with MPU-9150, MPU-9250, MPU-6050, or MPU-6500 devices, +please use the following commands: + +1. For tap/display orientation events: + mpu_iio -c 10 -l 3 -p + +2. In addition, to test the motion interrupt (and no_motion on MPU6050) use: + mpu_iio -c 10 -l 3 -p -m + +3. For printing data normally: + mpu_iio -c 10 -l 3 -r + +Running Test Applications with MPU-3050/ITG-3500 +------------------------------------------------ +To run test applications with MPU-3050 or ITG-3500 devices, +please use the following command: + +1. For printing data normally: + mpu_iio -c 10 -l 3 -r + +Please use mpu_iio.c and iio_utils.h as example code for your development +purposes. diff --git a/drivers/iio/imu/inv_mpu/dmpDefaultMPU6050.c b/drivers/iio/imu/inv_mpu/dmpDefaultMPU6050.c new file mode 100644 index 00000000..b49fc0a5 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/dmpDefaultMPU6050.c @@ -0,0 +1,323 @@ +/* + * 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. + * + */ +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file dmpDefaultMPU6050.c + * @brief dmp Default data + * @details This file is part of invensense mpu driver code + * + */ + +#include "dmpKey.h" +#include "dmpmap.h" + +#define CFG_LP_QUAT (2710) +#define END_ORIENT_TEMP (1874) +#define CFG_27 (2738) +#define CFG_20 (2232) +#define CFG_23 (2741) +#define CFG_FIFO_ON_EVENT (2687) +#define END_PREDICTION_UPDATE (1769) +#define CGNOTICE_INTR (2629) +#define X_GRT_Y_TMP (1366) +#define CFG_DR_INT (1029) +#define CFG_AUTH (1035) +#define UPDATE_PROP_ROT (1843) +#define FCFG_1 (1062) +#define SKIP_X_GRT_Y_TMP (1367) +#define SKIP_END_COMPARE (1443) +#define FCFG_3 (1110) +#define FCFG_2 (1066) +#define END_COMPARE_Y_X_TMP2 (1463) +#define END_COMPARE_Y_X_TMP3 (1442) +#define FCFG_7 (1076) +#define FCFG_6 (1128) +#define FLAT_STATE_END (1721) +#define SWING_END_4 (1624) +#define SWING_END_2 (1573) +#define SWING_END_3 (1595) +#define SWING_END_1 (1558) +#define CFG_8 (2716) +#define CFG_15 (2724) +#define CFG_16 (2742) +#define FLAT_STATE_END_TEMP (1691) +#define END_COMPARE_Y_X_TMP (1415) +#define DO_NOT_UPDATE_PROP_ROT (1847) +#define CFG_7 (1221) +#define END_COMPARE_Y_X (1492) +#define SKIP_SWING_END_1 (1559) +#define SKIP_SWING_END_3 (1596) +#define SKIP_SWING_END_2 (1574) +#define TILTG75_START (1680) +#define CFG_6 (2749) +#define TILTL75_END (1677) +#define END_ORIENT (1892) +#define CFG_FLICK_IN (2582) +#define TILTL75_START (1651) +#define CFG_MOTION_BIAS (1224) +#define X_GRT_Y (1416) +#define TEMPLABEL (2332) +#define CFG_DISPLAY_ORIENT_INT (1861) +#define X_GRT_Y_TMP2 (1387) + +#define D_0_22 (22+512) +#define D_0_24 (24+512) + +#define D_0_36 (36) +#define D_0_52 (52) +#define D_0_96 (96) +#define D_0_104 (104) +#define D_0_108 (108) +#define D_0_163 (163) +#define D_0_188 (188) +#define D_0_192 (192) +#define D_0_224 (224) +#define D_0_228 (228) +#define D_0_232 (232) +#define D_0_236 (236) + +#define D_1_2 (256 + 2) +#define D_1_4 (256 + 4) +#define D_1_8 (256 + 8) +#define D_1_10 (256 + 10) +#define D_1_24 (256 + 24) +#define D_1_28 (256 + 28) +#define D_1_36 (256 + 36) +#define D_1_40 (256 + 40) +#define D_1_44 (256 + 44) +#define D_1_72 (256 + 72) +#define D_1_74 (256 + 74) +#define D_1_79 (256 + 79) +#define D_1_88 (256 + 88) +#define D_1_90 (256 + 90) +#define D_1_92 (256 + 92) +#define D_1_96 (256 + 96) +#define D_1_98 (256 + 98) +#define D_1_106 (256 + 106) +#define D_1_108 (256 + 108) +#define D_1_112 (256 + 112) +#define D_1_128 (256 + 144) +#define D_1_152 (256 + 12) +#define D_1_160 (256 + 160) +#define D_1_176 (256 + 176) +#define D_1_178 (256 + 178) +#define D_1_218 (256 + 218) +#define D_1_232 (256 + 232) +#define D_1_236 (256 + 236) +#define D_1_240 (256 + 240) +#define D_1_244 (256 + 244) +#define D_1_250 (256 + 250) +#define D_1_252 (256 + 252) +#define D_2_12 (512 + 12) +#define D_2_96 (512 + 96) +#define D_2_108 (512 + 108) +#define D_2_208 (512 + 208) +#define D_2_224 (512 + 224) +#define D_2_236 (512 + 236) +#define D_2_244 (512 + 244) +#define D_2_248 (512 + 248) +#define D_2_252 (512 + 252) + +#define CPASS_BIAS_X (35 * 16 + 4) +#define CPASS_BIAS_Y (35 * 16 + 8) +#define CPASS_BIAS_Z (35 * 16 + 12) +#define CPASS_MTX_00 (36 * 16) +#define CPASS_MTX_01 (36 * 16 + 4) +#define CPASS_MTX_02 (36 * 16 + 8) +#define CPASS_MTX_10 (36 * 16 + 12) +#define CPASS_MTX_11 (37 * 16) +#define CPASS_MTX_12 (37 * 16 + 4) +#define CPASS_MTX_20 (37 * 16 + 8) +#define CPASS_MTX_21 (37 * 16 + 12) +#define CPASS_MTX_22 (43 * 16 + 12) +#define D_ACT0 (40 * 16) +#define D_ACSX (40 * 16 + 4) +#define D_ACSY (40 * 16 + 8) +#define D_ACSZ (40 * 16 + 12) + +#define FLICK_MSG (45 * 16 + 4) +#define FLICK_COUNTER (45 * 16 + 8) +#define FLICK_LOWER (45 * 16 + 12) +#define FLICK_UPPER (46 * 16 + 12) + +#define D_AUTH_OUT (992) +#define D_AUTH_IN (996) +#define D_AUTH_A (1000) +#define D_AUTH_B (1004) + +#define D_PEDSTD_BP_B (768 + 0x1C) +#define D_PEDSTD_HP_A (768 + 0x78) +#define D_PEDSTD_HP_B (768 + 0x7C) +#define D_PEDSTD_BP_A4 (768 + 0x40) +#define D_PEDSTD_BP_A3 (768 + 0x44) +#define D_PEDSTD_BP_A2 (768 + 0x48) +#define D_PEDSTD_BP_A1 (768 + 0x4C) +#define D_PEDSTD_INT_THRSH (768 + 0x68) +#define D_PEDSTD_CLIP (768 + 0x6C) +#define D_PEDSTD_SB (768 + 0x28) +#define D_PEDSTD_SB_TIME (768 + 0x2C) +#define D_PEDSTD_PEAKTHRSH (768 + 0x98) +#define D_PEDSTD_TIML (768 + 0x2A) +#define D_PEDSTD_TIMH (768 + 0x2E) +#define D_PEDSTD_PEAK (768 + 0X94) +#define D_PEDSTD_STEPCTR (768 + 0x60) +#define D_PEDSTD_TIMECTR (964) +#define D_PEDSTD_DECI (768 + 0xA0) + +#define D_HOST_NO_MOT (976) +#define D_ACCEL_BIAS (660) + +#define D_ORIENT_GAP (76) + +#define D_TILT0_H (48) +#define D_TILT0_L (50) +#define D_TILT1_H (52) +#define D_TILT1_L (54) +#define D_TILT2_H (56) +#define D_TILT2_L (58) +#define D_TILT3_H (60) +#define D_TILT3_L (62) + +static const struct tKeyLabel dmpTConfig[] = { + {KEY_CFG_27, CFG_27}, + {KEY_CFG_20, CFG_20}, + {KEY_CFG_23, CFG_23}, + {KEY_CFG_FIFO_ON_EVENT, CFG_FIFO_ON_EVENT}, + {KEY_CGNOTICE_INTR, CGNOTICE_INTR}, + {KEY_X_GRT_Y_TMP, X_GRT_Y_TMP}, + {KEY_CFG_DR_INT, CFG_DR_INT}, + {KEY_CFG_AUTH, CFG_AUTH}, + {KEY_FCFG_1, FCFG_1}, + {KEY_SKIP_X_GRT_Y_TMP, SKIP_X_GRT_Y_TMP}, + {KEY_SKIP_END_COMPARE, SKIP_END_COMPARE}, + {KEY_FCFG_3, FCFG_3}, + {KEY_FCFG_2, FCFG_2}, + {KEY_END_COMPARE_Y_X_TMP2, END_COMPARE_Y_X_TMP2}, + {KEY_CFG_DISPLAY_ORIENT_INT, CFG_DISPLAY_ORIENT_INT}, + {KEY_FCFG_7, FCFG_7}, + {KEY_FCFG_6, FCFG_6}, + {KEY_CFG_8, CFG_8}, + {KEY_CFG_15, CFG_15}, + {KEY_CFG_16, CFG_16}, + {KEY_END_COMPARE_Y_X_TMP, END_COMPARE_Y_X_TMP}, + {KEY_CFG_6, CFG_6}, + {KEY_END_COMPARE_Y_X, END_COMPARE_Y_X}, + {KEY_CFG_LP_QUAT, CFG_LP_QUAT}, + {KEY_END_ORIENT, END_ORIENT}, + {KEY_CFG_FLICK_IN, CFG_FLICK_IN}, + {KEY_CFG_7, CFG_7}, + {KEY_CFG_MOTION_BIAS, CFG_MOTION_BIAS}, + {KEY_X_GRT_Y, X_GRT_Y}, + {KEY_TEMPLABEL, TEMPLABEL}, + {KEY_END_COMPARE_Y_X_TMP3, END_COMPARE_Y_X_TMP3}, + {KEY_X_GRT_Y_TMP2, X_GRT_Y_TMP2}, + {KEY_D_0_22, D_0_22}, + {KEY_D_0_96, D_0_96}, + {KEY_D_0_104, D_0_104}, + {KEY_D_0_108, D_0_108}, + {KEY_D_1_36, D_1_36}, + {KEY_D_1_40, D_1_40}, + {KEY_D_1_44, D_1_44}, + {KEY_D_1_72, D_1_72}, + {KEY_D_1_74, D_1_74}, + {KEY_D_1_79, D_1_79}, + {KEY_D_1_88, D_1_88}, + {KEY_D_1_90, D_1_90}, + {KEY_D_1_92, D_1_92}, + {KEY_D_1_160, D_1_160}, + {KEY_D_1_176, D_1_176}, + {KEY_D_1_178, D_1_178}, + {KEY_D_1_218, D_1_218}, + {KEY_D_1_232, D_1_232}, + {KEY_D_1_250, D_1_250}, + {KEY_DMP_TAPW_MIN, DMP_TAPW_MIN}, + {KEY_DMP_TAP_THR_X, DMP_TAP_THX}, + {KEY_DMP_TAP_THR_Y, DMP_TAP_THY}, + {KEY_DMP_TAP_THR_Z, DMP_TAP_THZ}, + {KEY_DMP_SH_TH_Y, DMP_SH_TH_Y}, + {KEY_DMP_SH_TH_X, DMP_SH_TH_X}, + {KEY_DMP_SH_TH_Z, DMP_SH_TH_Z}, + {KEY_DMP_ORIENT, DMP_ORIENT}, + {KEY_D_AUTH_OUT, D_AUTH_OUT}, + {KEY_D_AUTH_IN, D_AUTH_IN}, + {KEY_D_AUTH_A, D_AUTH_A}, + {KEY_D_AUTH_B, D_AUTH_B}, + {KEY_CPASS_BIAS_X, CPASS_BIAS_X}, + {KEY_CPASS_BIAS_Y, CPASS_BIAS_Y}, + {KEY_CPASS_BIAS_Z, CPASS_BIAS_Z}, + {KEY_CPASS_MTX_00, CPASS_MTX_00}, + {KEY_CPASS_MTX_01, CPASS_MTX_01}, + {KEY_CPASS_MTX_02, CPASS_MTX_02}, + {KEY_CPASS_MTX_10, CPASS_MTX_10}, + {KEY_CPASS_MTX_11, CPASS_MTX_11}, + {KEY_CPASS_MTX_12, CPASS_MTX_12}, + {KEY_CPASS_MTX_20, CPASS_MTX_20}, + {KEY_CPASS_MTX_21, CPASS_MTX_21}, + {KEY_CPASS_MTX_22, CPASS_MTX_22}, + {KEY_D_ACT0, D_ACT0}, + {KEY_D_ACSX, D_ACSX}, + {KEY_D_ACSY, D_ACSY}, + {KEY_D_ACSZ, D_ACSZ}, + {KEY_FLICK_MSG, FLICK_MSG}, + {KEY_FLICK_COUNTER, FLICK_COUNTER}, + {KEY_FLICK_LOWER, FLICK_LOWER}, + {KEY_FLICK_UPPER, FLICK_UPPER}, + {KEY_D_PEDSTD_BP_B, D_PEDSTD_BP_B}, + {KEY_D_PEDSTD_HP_A, D_PEDSTD_HP_A}, + {KEY_D_PEDSTD_HP_B, D_PEDSTD_HP_B}, + {KEY_D_PEDSTD_BP_A4, D_PEDSTD_BP_A4}, + {KEY_D_PEDSTD_BP_A3, D_PEDSTD_BP_A3}, + {KEY_D_PEDSTD_BP_A2, D_PEDSTD_BP_A2}, + {KEY_D_PEDSTD_BP_A1, D_PEDSTD_BP_A1}, + {KEY_D_PEDSTD_INT_THRSH, D_PEDSTD_INT_THRSH}, + {KEY_D_PEDSTD_CLIP, D_PEDSTD_CLIP}, + {KEY_D_PEDSTD_SB, D_PEDSTD_SB}, + {KEY_D_PEDSTD_SB_TIME, D_PEDSTD_SB_TIME}, + {KEY_D_PEDSTD_PEAKTHRSH, D_PEDSTD_PEAKTHRSH}, + {KEY_D_PEDSTD_TIML, D_PEDSTD_TIML}, + {KEY_D_PEDSTD_TIMH, D_PEDSTD_TIMH}, + {KEY_D_PEDSTD_PEAK, D_PEDSTD_PEAK}, + {KEY_D_PEDSTD_STEPCTR, D_PEDSTD_STEPCTR}, + {KEY_D_PEDSTD_TIMECTR, D_PEDSTD_TIMECTR}, + {KEY_D_PEDSTD_DECI, D_PEDSTD_DECI}, + {KEY_D_HOST_NO_MOT, D_HOST_NO_MOT}, + {KEY_D_ACCEL_BIAS, D_ACCEL_BIAS}, +}; +#define NUM_LOCAL_KEYS (sizeof(dmpTConfig)/sizeof(dmpTConfig[0])) + +static struct tKeyLabel keys[NUM_KEYS]; + +unsigned short inv_dmp_get_address(unsigned short key) +{ + static int isSorted; + if (!isSorted) { + int kk; + for (kk = 0; kk < NUM_KEYS; ++kk) { + keys[kk].addr = 0xffff; + keys[kk].key = kk; + } + for (kk = 0; kk < NUM_LOCAL_KEYS; ++kk) + keys[dmpTConfig[kk].key].addr = dmpTConfig[kk].addr; + isSorted = 1; + } + if (key >= NUM_KEYS) + return 0xffff; + return keys[key].addr; +} +/** + * @} + */ diff --git a/drivers/iio/imu/inv_mpu/dmpKey.h b/drivers/iio/imu/inv_mpu/dmpKey.h new file mode 100644 index 00000000..150f429d --- /dev/null +++ b/drivers/iio/imu/inv_mpu/dmpKey.h @@ -0,0 +1,462 @@ +/* + * 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. + * + */ +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file dmpKey.h + * @brief dmp Key definition + * @details This file is part of invensense mpu driver code + * + */ + +#ifndef DMPKEY_H__ +#define DMPKEY_H__ + +#define KEY_CFG_25 (0) +#define KEY_CFG_24 (KEY_CFG_25 + 1) +#define KEY_CFG_26 (KEY_CFG_24 + 1) +#define KEY_CFG_27 (KEY_CFG_26 + 1) +#define KEY_CFG_21 (KEY_CFG_27 + 1) +#define KEY_CFG_20 (KEY_CFG_21 + 1) +#define KEY_CFG_TAP4 (KEY_CFG_20 + 1) +#define KEY_CFG_TAP5 (KEY_CFG_TAP4 + 1) +#define KEY_CFG_TAP6 (KEY_CFG_TAP5 + 1) +#define KEY_CFG_TAP7 (KEY_CFG_TAP6 + 1) +#define KEY_CFG_TAP0 (KEY_CFG_TAP7 + 1) +#define KEY_CFG_TAP1 (KEY_CFG_TAP0 + 1) +#define KEY_CFG_TAP2 (KEY_CFG_TAP1 + 1) +#define KEY_CFG_TAP3 (KEY_CFG_TAP2 + 1) +#define KEY_CFG_TAP_QUANTIZE (KEY_CFG_TAP3 + 1) +#define KEY_CFG_TAP_JERK (KEY_CFG_TAP_QUANTIZE + 1) +#define KEY_CFG_DR_INT (KEY_CFG_TAP_JERK + 1) +#define KEY_CFG_AUTH (KEY_CFG_DR_INT + 1) +#define KEY_CFG_TAP_SAVE_ACCB (KEY_CFG_AUTH + 1) +#define KEY_CFG_TAP_CLEAR_STICKY (KEY_CFG_TAP_SAVE_ACCB + 1) +#define KEY_CFG_FIFO_ON_EVENT (KEY_CFG_TAP_CLEAR_STICKY + 1) +#define KEY_FCFG_ACCEL_INPUT (KEY_CFG_FIFO_ON_EVENT + 1) +#define KEY_FCFG_ACCEL_INIT (KEY_FCFG_ACCEL_INPUT + 1) +#define KEY_CFG_23 (KEY_FCFG_ACCEL_INIT + 1) +#define KEY_FCFG_1 (KEY_CFG_23 + 1) +#define KEY_FCFG_3 (KEY_FCFG_1 + 1) +#define KEY_FCFG_2 (KEY_FCFG_3 + 1) +#define KEY_CFG_3D (KEY_FCFG_2 + 1) +#define KEY_CFG_3B (KEY_CFG_3D + 1) +#define KEY_CFG_3C (KEY_CFG_3B + 1) +#define KEY_FCFG_5 (KEY_CFG_3C + 1) +#define KEY_FCFG_4 (KEY_FCFG_5 + 1) +#define KEY_FCFG_7 (KEY_FCFG_4 + 1) +#define KEY_FCFG_FSCALE (KEY_FCFG_7 + 1) +#define KEY_FCFG_AZ (KEY_FCFG_FSCALE + 1) +#define KEY_FCFG_6 (KEY_FCFG_AZ + 1) +#define KEY_FCFG_LSB4 (KEY_FCFG_6 + 1) +#define KEY_CFG_12 (KEY_FCFG_LSB4 + 1) +#define KEY_CFG_14 (KEY_CFG_12 + 1) +#define KEY_CFG_15 (KEY_CFG_14 + 1) +#define KEY_CFG_16 (KEY_CFG_15 + 1) +#define KEY_CFG_18 (KEY_CFG_16 + 1) +#define KEY_CFG_6 (KEY_CFG_18 + 1) +#define KEY_CFG_7 (KEY_CFG_6 + 1) +#define KEY_CFG_4 (KEY_CFG_7 + 1) +#define KEY_CFG_5 (KEY_CFG_4 + 1) +#define KEY_CFG_2 (KEY_CFG_5 + 1) +#define KEY_CFG_3 (KEY_CFG_2 + 1) +#define KEY_CFG_1 (KEY_CFG_3 + 1) +#define KEY_CFG_EXTERNAL (KEY_CFG_1 + 1) +#define KEY_CFG_8 (KEY_CFG_EXTERNAL + 1) +#define KEY_CFG_9 (KEY_CFG_8 + 1) +#define KEY_CFG_ORIENT_3 (KEY_CFG_9 + 1) +#define KEY_CFG_ORIENT_2 (KEY_CFG_ORIENT_3 + 1) +#define KEY_CFG_ORIENT_1 (KEY_CFG_ORIENT_2 + 1) +#define KEY_CFG_GYRO_SOURCE (KEY_CFG_ORIENT_1 + 1) +#define KEY_CFG_ORIENT_IRQ_1 (KEY_CFG_GYRO_SOURCE + 1) +#define KEY_CFG_ORIENT_IRQ_2 (KEY_CFG_ORIENT_IRQ_1 + 1) +#define KEY_CFG_ORIENT_IRQ_3 (KEY_CFG_ORIENT_IRQ_2 + 1) +#define KEY_FCFG_MAG_VAL (KEY_CFG_ORIENT_IRQ_3 + 1) +#define KEY_FCFG_MAG_MOV (KEY_FCFG_MAG_VAL + 1) +#define KEY_CFG_LP_QUAT (KEY_FCFG_MAG_MOV + 1) + +/* MPU6050 keys */ +#define KEY_CFG_ACCEL_FILTER (KEY_CFG_LP_QUAT + 1) +#define KEY_CFG_MOTION_BIAS (KEY_CFG_ACCEL_FILTER + 1) +#define KEY_TEMPLABEL (KEY_CFG_MOTION_BIAS + 1) + +#define KEY_D_0_22 (KEY_TEMPLABEL + 1) +#define KEY_D_0_24 (KEY_D_0_22 + 1) +#define KEY_D_0_36 (KEY_D_0_24 + 1) +#define KEY_D_0_52 (KEY_D_0_36 + 1) +#define KEY_D_0_96 (KEY_D_0_52 + 1) +#define KEY_D_0_104 (KEY_D_0_96 + 1) +#define KEY_D_0_108 (KEY_D_0_104 + 1) +#define KEY_D_0_163 (KEY_D_0_108 + 1) +#define KEY_D_0_188 (KEY_D_0_163 + 1) +#define KEY_D_0_192 (KEY_D_0_188 + 1) +#define KEY_D_0_224 (KEY_D_0_192 + 1) +#define KEY_D_0_228 (KEY_D_0_224 + 1) +#define KEY_D_0_232 (KEY_D_0_228 + 1) +#define KEY_D_0_236 (KEY_D_0_232 + 1) + +#define KEY_DMP_PREVPTAT (KEY_D_0_236 + 1) +#define KEY_D_1_2 (KEY_DMP_PREVPTAT + 1) +#define KEY_D_1_4 (KEY_D_1_2 + 1) +#define KEY_D_1_8 (KEY_D_1_4 + 1) +#define KEY_D_1_10 (KEY_D_1_8 + 1) +#define KEY_D_1_24 (KEY_D_1_10 + 1) +#define KEY_D_1_28 (KEY_D_1_24 + 1) +#define KEY_D_1_36 (KEY_D_1_28 + 1) +#define KEY_D_1_40 (KEY_D_1_36 + 1) +#define KEY_D_1_44 (KEY_D_1_40 + 1) +#define KEY_D_1_72 (KEY_D_1_44 + 1) +#define KEY_D_1_74 (KEY_D_1_72 + 1) +#define KEY_D_1_79 (KEY_D_1_74 + 1) +#define KEY_D_1_88 (KEY_D_1_79 + 1) +#define KEY_D_1_90 (KEY_D_1_88 + 1) +#define KEY_D_1_92 (KEY_D_1_90 + 1) +#define KEY_D_1_96 (KEY_D_1_92 + 1) +#define KEY_D_1_98 (KEY_D_1_96 + 1) +#define KEY_D_1_100 (KEY_D_1_98 + 1) +#define KEY_D_1_106 (KEY_D_1_100 + 1) +#define KEY_D_1_108 (KEY_D_1_106 + 1) +#define KEY_D_1_112 (KEY_D_1_108 + 1) +#define KEY_D_1_128 (KEY_D_1_112 + 1) +#define KEY_D_1_152 (KEY_D_1_128 + 1) +#define KEY_D_1_160 (KEY_D_1_152 + 1) +#define KEY_D_1_168 (KEY_D_1_160 + 1) +#define KEY_D_1_175 (KEY_D_1_168 + 1) +#define KEY_D_1_176 (KEY_D_1_175 + 1) +#define KEY_D_1_178 (KEY_D_1_176 + 1) +#define KEY_D_1_179 (KEY_D_1_178 + 1) +#define KEY_D_1_218 (KEY_D_1_179 + 1) +#define KEY_D_1_232 (KEY_D_1_218 + 1) +#define KEY_D_1_236 (KEY_D_1_232 + 1) +#define KEY_D_1_240 (KEY_D_1_236 + 1) +#define KEY_D_1_244 (KEY_D_1_240 + 1) +#define KEY_D_1_250 (KEY_D_1_244 + 1) +#define KEY_D_1_252 (KEY_D_1_250 + 1) +#define KEY_D_2_12 (KEY_D_1_252 + 1) +#define KEY_D_2_96 (KEY_D_2_12 + 1) +#define KEY_D_2_108 (KEY_D_2_96 + 1) +#define KEY_D_2_208 (KEY_D_2_108 + 1) +#define KEY_FLICK_MSG (KEY_D_2_208 + 1) +#define KEY_FLICK_COUNTER (KEY_FLICK_MSG + 1) +#define KEY_FLICK_LOWER (KEY_FLICK_COUNTER + 1) +#define KEY_CFG_FLICK_IN (KEY_FLICK_LOWER + 1) +#define KEY_FLICK_UPPER (KEY_CFG_FLICK_IN + 1) +#define KEY_CGNOTICE_INTR (KEY_FLICK_UPPER + 1) +#define KEY_D_2_224 (KEY_CGNOTICE_INTR + 1) +#define KEY_D_2_244 (KEY_D_2_224 + 1) +#define KEY_D_2_248 (KEY_D_2_244 + 1) +#define KEY_D_2_252 (KEY_D_2_248 + 1) + +#define KEY_D_GYRO_BIAS_X (KEY_D_2_252 + 1) +#define KEY_D_GYRO_BIAS_Y (KEY_D_GYRO_BIAS_X + 1) +#define KEY_D_GYRO_BIAS_Z (KEY_D_GYRO_BIAS_Y + 1) +#define KEY_D_GYRO_ENABLE (KEY_D_GYRO_BIAS_Z + 1) +#define KEY_D_ACCEL_ENABLE (KEY_D_GYRO_ENABLE + 1) +#define KEY_D_QUAT_ENABLE (KEY_D_ACCEL_ENABLE + 1) +#define KEY_D_CR_TIME_G (KEY_D_QUAT_ENABLE + 1) +#define KEY_D_CR_TIME_A (KEY_D_CR_TIME_G + 1) +#define KEY_D_CR_TIME_Q (KEY_D_CR_TIME_A + 1) +#define KEY_D_CS_TAX (KEY_D_CR_TIME_Q + 1) +#define KEY_D_CS_TAY (KEY_D_CS_TAX + 1) +#define KEY_D_CS_TAZ (KEY_D_CS_TAY + 1) + +#define KEY_D_CS_TGX (KEY_D_CS_TAZ + 1) +#define KEY_D_CS_TGY (KEY_D_CS_TGX + 1) +#define KEY_D_CS_TGZ (KEY_D_CS_TGY + 1) +#define KEY_D_CS_TQ0 (KEY_D_CS_TGZ + 1) +#define KEY_D_CS_TQ1 (KEY_D_CS_TQ0 + 1) +#define KEY_D_CS_TQ2 (KEY_D_CS_TQ1 + 1) +#define KEY_D_CS_TQ3 (KEY_D_CS_TQ2 + 1) + +/* Compass keys */ +#define KEY_CPASS_BIAS_X (KEY_D_CS_TQ3 + 1) +#define KEY_CPASS_BIAS_Y (KEY_CPASS_BIAS_X + 1) +#define KEY_CPASS_BIAS_Z (KEY_CPASS_BIAS_Y + 1) +#define KEY_CPASS_MTX_00 (KEY_CPASS_BIAS_Z + 1) +#define KEY_CPASS_MTX_01 (KEY_CPASS_MTX_00 + 1) +#define KEY_CPASS_MTX_02 (KEY_CPASS_MTX_01 + 1) +#define KEY_CPASS_MTX_10 (KEY_CPASS_MTX_02 + 1) +#define KEY_CPASS_MTX_11 (KEY_CPASS_MTX_10 + 1) +#define KEY_CPASS_MTX_12 (KEY_CPASS_MTX_11 + 1) +#define KEY_CPASS_MTX_20 (KEY_CPASS_MTX_12 + 1) +#define KEY_CPASS_MTX_21 (KEY_CPASS_MTX_20 + 1) +#define KEY_CPASS_MTX_22 (KEY_CPASS_MTX_21 + 1) + +/* Gesture Keys */ +#define KEY_DMP_TAPW_MIN (KEY_CPASS_MTX_22 + 1) +#define KEY_DMP_TAP_THR_X (KEY_DMP_TAPW_MIN + 1) +#define KEY_DMP_TAP_THR_Y (KEY_DMP_TAP_THR_X + 1) +#define KEY_DMP_TAP_THR_Z (KEY_DMP_TAP_THR_Y + 1) +#define KEY_DMP_SH_TH_Y (KEY_DMP_TAP_THR_Z + 1) +#define KEY_DMP_SH_TH_X (KEY_DMP_SH_TH_Y + 1) +#define KEY_DMP_SH_TH_Z (KEY_DMP_SH_TH_X + 1) +#define KEY_DMP_ORIENT (KEY_DMP_SH_TH_Z + 1) +#define KEY_D_ACT0 (KEY_DMP_ORIENT + 1) +#define KEY_D_ACSX (KEY_D_ACT0 + 1) +#define KEY_D_ACSY (KEY_D_ACSX + 1) +#define KEY_D_ACSZ (KEY_D_ACSY + 1) + +#define KEY_X_GRT_Y_TMP (KEY_D_ACSZ + 1) +#define KEY_SKIP_X_GRT_Y_TMP (KEY_X_GRT_Y_TMP + 1) +#define KEY_SKIP_END_COMPARE (KEY_SKIP_X_GRT_Y_TMP + 1) +#define KEY_END_COMPARE_Y_X_TMP2 (KEY_SKIP_END_COMPARE + 1) +#define KEY_CFG_DISPLAY_ORIENT_INT (KEY_END_COMPARE_Y_X_TMP2 + 1) +#define KEY_NO_ORIENT_INTERRUPT (KEY_CFG_DISPLAY_ORIENT_INT + 1) +#define KEY_END_COMPARE_Y_X_TMP (KEY_NO_ORIENT_INTERRUPT + 1) +#define KEY_END_ORIENT_1 (KEY_END_COMPARE_Y_X_TMP + 1) +#define KEY_END_COMPARE_Y_X (KEY_END_ORIENT_1 + 1) +#define KEY_END_ORIENT (KEY_END_COMPARE_Y_X + 1) +#define KEY_X_GRT_Y (KEY_END_ORIENT + 1) +#define KEY_NOT_TIME_MINUS_1 (KEY_X_GRT_Y + 1) +#define KEY_END_COMPARE_Y_X_TMP3 (KEY_NOT_TIME_MINUS_1 + 1) +#define KEY_X_GRT_Y_TMP2 (KEY_END_COMPARE_Y_X_TMP3 + 1) + +/* Authenticate Keys */ +#define KEY_D_AUTH_OUT (KEY_X_GRT_Y_TMP2 + 1) +#define KEY_D_AUTH_IN (KEY_D_AUTH_OUT + 1) +#define KEY_D_AUTH_A (KEY_D_AUTH_IN + 1) +#define KEY_D_AUTH_B (KEY_D_AUTH_A + 1) + +/* Pedometer standalone only keys */ +#define KEY_D_PEDSTD_BP_B (KEY_D_AUTH_B + 1) +#define KEY_D_PEDSTD_HP_A (KEY_D_PEDSTD_BP_B + 1) +#define KEY_D_PEDSTD_HP_B (KEY_D_PEDSTD_HP_A + 1) +#define KEY_D_PEDSTD_BP_A4 (KEY_D_PEDSTD_HP_B + 1) +#define KEY_D_PEDSTD_BP_A3 (KEY_D_PEDSTD_BP_A4 + 1) +#define KEY_D_PEDSTD_BP_A2 (KEY_D_PEDSTD_BP_A3 + 1) +#define KEY_D_PEDSTD_BP_A1 (KEY_D_PEDSTD_BP_A2 + 1) +#define KEY_D_PEDSTD_INT_THRSH (KEY_D_PEDSTD_BP_A1 + 1) +#define KEY_D_PEDSTD_CLIP (KEY_D_PEDSTD_INT_THRSH + 1) +#define KEY_D_PEDSTD_SB (KEY_D_PEDSTD_CLIP + 1) +#define KEY_D_PEDSTD_SB_TIME (KEY_D_PEDSTD_SB + 1) +#define KEY_D_PEDSTD_PEAKTHRSH (KEY_D_PEDSTD_SB_TIME + 1) +#define KEY_D_PEDSTD_TIML (KEY_D_PEDSTD_PEAKTHRSH + 1) +#define KEY_D_PEDSTD_TIMH (KEY_D_PEDSTD_TIML + 1) +#define KEY_D_PEDSTD_PEAK (KEY_D_PEDSTD_TIMH + 1) +#define KEY_D_PEDSTD_TIMECTR (KEY_D_PEDSTD_PEAK + 1) +#define KEY_D_PEDSTD_STEPCTR (KEY_D_PEDSTD_TIMECTR + 1) +#define KEY_D_PEDSTD_WALKTIME (KEY_D_PEDSTD_STEPCTR + 1) +#define KEY_D_PEDSTD_DECI (KEY_D_PEDSTD_WALKTIME + 1) + +/*Host Based No Motion*/ +#define KEY_D_HOST_NO_MOT (KEY_D_PEDSTD_DECI + 1) +#define KEY_D_ACCEL_BIAS (KEY_D_HOST_NO_MOT + 1) + +#define NUM_KEYS (KEY_D_ACCEL_BIAS + 1) + +struct tKeyLabel { + unsigned short key; + unsigned short addr; +}; + +#define DINA0A 0x0a +#define DINA22 0x22 +#define DINA42 0x42 +#define DINA5A 0x5a + +#define DINA06 0x06 +#define DINA0E 0x0e +#define DINA16 0x16 +#define DINA1E 0x1e +#define DINA26 0x26 +#define DINA2E 0x2e +#define DINA36 0x36 +#define DINA3E 0x3e +#define DINA46 0x46 +#define DINA4E 0x4e +#define DINA56 0x56 +#define DINA5E 0x5e +#define DINA66 0x66 +#define DINA6E 0x6e +#define DINA76 0x76 +#define DINA7E 0x7e + +#define DINA00 0x00 +#define DINA08 0x08 +#define DINA10 0x10 +#define DINA18 0x18 +#define DINA20 0x20 +#define DINA28 0x28 +#define DINA30 0x30 +#define DINA38 0x38 +#define DINA40 0x40 +#define DINA48 0x48 +#define DINA50 0x50 +#define DINA58 0x58 +#define DINA60 0x60 +#define DINA68 0x68 +#define DINA70 0x70 +#define DINA78 0x78 + +#define DINA04 0x04 +#define DINA0C 0x0c +#define DINA14 0x14 +#define DINA1C 0x1C +#define DINA24 0x24 +#define DINA2C 0x2c +#define DINA34 0x34 +#define DINA3C 0x3c +#define DINA44 0x44 +#define DINA4C 0x4c +#define DINA54 0x54 +#define DINA5C 0x5c +#define DINA64 0x64 +#define DINA6C 0x6c +#define DINA74 0x74 +#define DINA7C 0x7c + +#define DINA01 0x01 +#define DINA09 0x09 +#define DINA11 0x11 +#define DINA19 0x19 +#define DINA21 0x21 +#define DINA29 0x29 +#define DINA31 0x31 +#define DINA39 0x39 +#define DINA41 0x41 +#define DINA49 0x49 +#define DINA51 0x51 +#define DINA59 0x59 +#define DINA61 0x61 +#define DINA69 0x69 +#define DINA71 0x71 +#define DINA79 0x79 + +#define DINA25 0x25 +#define DINA2D 0x2d +#define DINA35 0x35 +#define DINA3D 0x3d +#define DINA4D 0x4d +#define DINA55 0x55 +#define DINA5D 0x5D +#define DINA6D 0x6d +#define DINA75 0x75 +#define DINA7D 0x7d + +#define DINADC 0xdc +#define DINAF2 0xf2 +#define DINAAB 0xab +#define DINAAA 0xaa +#define DINAF1 0xf1 +#define DINADF 0xdf +#define DINADA 0xda +#define DINAB1 0xb1 +#define DINAB9 0xb9 +#define DINAF3 0xf3 +#define DINA8B 0x8b +#define DINAA3 0xa3 +#define DINA91 0x91 +#define DINAB6 0xb6 +#define DINAB4 0xb4 + + +#define DINC00 0x00 +#define DINC01 0x01 +#define DINC02 0x02 +#define DINC03 0x03 +#define DINC08 0x08 +#define DINC09 0x09 +#define DINC0A 0x0a +#define DINC0B 0x0b +#define DINC10 0x10 +#define DINC11 0x11 +#define DINC12 0x12 +#define DINC13 0x13 +#define DINC18 0x18 +#define DINC19 0x19 +#define DINC1A 0x1a +#define DINC1B 0x1b + +#define DINC20 0x20 +#define DINC21 0x21 +#define DINC22 0x22 +#define DINC23 0x23 +#define DINC28 0x28 +#define DINC29 0x29 +#define DINC2A 0x2a +#define DINC2B 0x2b +#define DINC30 0x30 +#define DINC31 0x31 +#define DINC32 0x32 +#define DINC33 0x33 +#define DINC38 0x38 +#define DINC39 0x39 +#define DINC3A 0x3a +#define DINC3B 0x3b + +#define DINC40 0x40 +#define DINC41 0x41 +#define DINC42 0x42 +#define DINC43 0x43 +#define DINC48 0x48 +#define DINC49 0x49 +#define DINC4A 0x4a +#define DINC4B 0x4b +#define DINC50 0x50 +#define DINC51 0x51 +#define DINC52 0x52 +#define DINC53 0x53 +#define DINC58 0x58 +#define DINC59 0x59 +#define DINC5A 0x5a +#define DINC5B 0x5b + +#define DINC60 0x60 +#define DINC61 0x61 +#define DINC62 0x62 +#define DINC63 0x63 +#define DINC68 0x68 +#define DINC69 0x69 +#define DINC6A 0x6a +#define DINC6B 0x6b +#define DINC70 0x70 +#define DINC71 0x71 +#define DINC72 0x72 +#define DINC73 0x73 +#define DINC78 0x78 +#define DINC79 0x79 +#define DINC7A 0x7a +#define DINC7B 0x7b +#define DIND40 0x40 +#define DINA80 0x80 +#define DINA90 0x90 +#define DINAA0 0xa0 +#define DINAC9 0xc9 +#define DINACB 0xcb +#define DINACD 0xcd +#define DINACF 0xcf +#define DINAC8 0xc8 +#define DINACA 0xca +#define DINACC 0xcc +#define DINACE 0xce +#define DINAD8 0xd8 +#define DINADD 0xdd +#define DINAF8 0xf0 +#define DINAFE 0xfe + +#define DINBF8 0xf8 +#define DINAC0 0xb0 +#define DINAC1 0xb1 +#define DINAC2 0xb4 +#define DINAC3 0xb5 +#define DINAC4 0xb8 +#define DINAC5 0xb9 +#define DINBC0 0xc0 +#define DINBC2 0xc2 +#define DINBC4 0xc4 +#define DINBC6 0xc6 + +#endif diff --git a/drivers/iio/imu/inv_mpu/dmpmap.h b/drivers/iio/imu/inv_mpu/dmpmap.h new file mode 100644 index 00000000..28f59af0 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/dmpmap.h @@ -0,0 +1,283 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file dmpmap.h + * @brief dmp map definition + * @details This file is part of invensense mpu driver code + * + */ +#ifndef DMPMAP_H +#define DMPMAP_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define DMP_PTAT 0 +#define DMP_XGYR 2 +#define DMP_YGYR 4 +#define DMP_ZGYR 6 +#define DMP_XACC 8 +#define DMP_YACC 10 +#define DMP_ZACC 12 +#define DMP_ADC1 14 +#define DMP_ADC2 16 +#define DMP_ADC3 18 +#define DMP_BIASUNC 20 +#define DMP_FIFORT 22 +#define DMP_INVGSFH 24 +#define DMP_INVGSFL 26 +#define DMP_1H 28 +#define DMP_1L 30 +#define DMP_BLPFSTCH 32 +#define DMP_BLPFSTCL 34 +#define DMP_BLPFSXH 36 +#define DMP_BLPFSXL 38 +#define DMP_BLPFSYH 40 +#define DMP_BLPFSYL 42 +#define DMP_BLPFSZH 44 +#define DMP_BLPFSZL 46 +#define DMP_BLPFMTC 48 +#define DMP_SMC 50 +#define DMP_BLPFMXH 52 +#define DMP_BLPFMXL 54 +#define DMP_BLPFMYH 56 +#define DMP_BLPFMYL 58 +#define DMP_BLPFMZH 60 +#define DMP_BLPFMZL 62 +#define DMP_BLPFC 64 +#define DMP_SMCTH 66 +#define DMP_0H2 68 +#define DMP_0L2 70 +#define DMP_BERR2H 72 +#define DMP_BERR2L 74 +#define DMP_BERR2NH 76 +#define DMP_SMCINC 78 +#define DMP_ANGVBXH 80 +#define DMP_ANGVBXL 82 +#define DMP_ANGVBYH 84 +#define DMP_ANGVBYL 86 +#define DMP_ANGVBZH 88 +#define DMP_ANGVBZL 90 +#define DMP_BERR1H 92 +#define DMP_BERR1L 94 +#define DMP_ATCH 96 +#define DMP_BIASUNCSF 98 +#define DMP_ACT2H 100 +#define DMP_ACT2L 102 +#define DMP_GSFH 104 +#define DMP_GSFL 106 +#define DMP_GH 108 +#define DMP_GL 110 +#define DMP_0_5H 112 +#define DMP_0_5L 114 +#define DMP_0_0H 116 +#define DMP_0_0L 118 +#define DMP_1_0H 120 +#define DMP_1_0L 122 +#define DMP_1_5H 124 +#define DMP_1_5L 126 +#define DMP_TMP1AH 128 +#define DMP_TMP1AL 130 +#define DMP_TMP2AH 132 +#define DMP_TMP2AL 134 +#define DMP_TMP3AH 136 +#define DMP_TMP3AL 138 +#define DMP_TMP4AH 140 +#define DMP_TMP4AL 142 +#define DMP_XACCW 144 +#define DMP_TMP5 146 +#define DMP_XACCB 148 +#define DMP_TMP8 150 +#define DMP_YACCB 152 +#define DMP_TMP9 154 +#define DMP_ZACCB 156 +#define DMP_TMP10 158 +#define DMP_DZH 160 +#define DMP_DZL 162 +#define DMP_XGCH 164 +#define DMP_XGCL 166 +#define DMP_YGCH 168 +#define DMP_YGCL 170 +#define DMP_ZGCH 172 +#define DMP_ZGCL 174 +#define DMP_YACCW 176 +#define DMP_TMP7 178 +#define DMP_AFB1H 180 +#define DMP_AFB1L 182 +#define DMP_AFB2H 184 +#define DMP_AFB2L 186 +#define DMP_MAGFBH 188 +#define DMP_MAGFBL 190 +#define DMP_QT1H 192 +#define DMP_QT1L 194 +#define DMP_QT2H 196 +#define DMP_QT2L 198 +#define DMP_QT3H 200 +#define DMP_QT3L 202 +#define DMP_QT4H 204 +#define DMP_QT4L 206 +#define DMP_CTRL1H 208 +#define DMP_CTRL1L 210 +#define DMP_CTRL2H 212 +#define DMP_CTRL2L 214 +#define DMP_CTRL3H 216 +#define DMP_CTRL3L 218 +#define DMP_CTRL4H 220 +#define DMP_CTRL4L 222 +#define DMP_CTRLS1 224 +#define DMP_CTRLSF1 226 +#define DMP_CTRLS2 228 +#define DMP_CTRLSF2 230 +#define DMP_CTRLS3 232 +#define DMP_CTRLSFNLL 234 +#define DMP_CTRLS4 236 +#define DMP_CTRLSFNL2 238 +#define DMP_CTRLSFNL 240 +#define DMP_TMP30 242 +#define DMP_CTRLSFJT 244 +#define DMP_TMP31 246 +#define DMP_TMP11 248 +#define DMP_CTRLSF2_2 250 +#define DMP_TMP12 252 +#define DMP_CTRLSF1_2 254 +#define DMP_PREVPTAT 256 +#define DMP_ACCZB 258 +#define DMP_ACCXB 264 +#define DMP_ACCYB 266 +#define DMP_1HB 272 +#define DMP_1LB 274 +#define DMP_0H 276 +#define DMP_0L 278 +#define DMP_ASR22H 280 +#define DMP_ASR22L 282 +#define DMP_ASR6H 284 +#define DMP_ASR6L 286 +#define DMP_TMP13 288 +#define DMP_TMP14 290 +#define DMP_FINTXH 292 +#define DMP_FINTXL 294 +#define DMP_FINTYH 296 +#define DMP_FINTYL 298 +#define DMP_FINTZH 300 +#define DMP_FINTZL 302 +#define DMP_TMP1BH 304 +#define DMP_TMP1BL 306 +#define DMP_TMP2BH 308 +#define DMP_TMP2BL 310 +#define DMP_TMP3BH 312 +#define DMP_TMP3BL 314 +#define DMP_TMP4BH 316 +#define DMP_TMP4BL 318 +#define DMP_STXG 320 +#define DMP_ZCTXG 322 +#define DMP_STYG 324 +#define DMP_ZCTYG 326 +#define DMP_STZG 328 +#define DMP_ZCTZG 330 +#define DMP_CTRLSFJT2 332 +#define DMP_CTRLSFJTCNT 334 +#define DMP_PVXG 336 +#define DMP_TMP15 338 +#define DMP_PVYG 340 +#define DMP_TMP16 342 +#define DMP_PVZG 344 +#define DMP_TMP17 346 +#define DMP_MNMFLAGH 352 +#define DMP_MNMFLAGL 354 +#define DMP_MNMTMH 356 +#define DMP_MNMTML 358 +#define DMP_MNMTMTHRH 360 +#define DMP_MNMTMTHRL 362 +#define DMP_MNMTHRH 364 +#define DMP_MNMTHRL 366 +#define DMP_ACCQD4H 368 +#define DMP_ACCQD4L 370 +#define DMP_ACCQD5H 372 +#define DMP_ACCQD5L 374 +#define DMP_ACCQD6H 376 +#define DMP_ACCQD6L 378 +#define DMP_ACCQD7H 380 +#define DMP_ACCQD7L 382 +#define DMP_ACCQD0H 384 +#define DMP_ACCQD0L 386 +#define DMP_ACCQD1H 388 +#define DMP_ACCQD1L 390 +#define DMP_ACCQD2H 392 +#define DMP_ACCQD2L 394 +#define DMP_ACCQD3H 396 +#define DMP_ACCQD3L 398 +#define DMP_XN2H 400 +#define DMP_XN2L 402 +#define DMP_XN1H 404 +#define DMP_XN1L 406 +#define DMP_YN2H 408 +#define DMP_YN2L 410 +#define DMP_YN1H 412 +#define DMP_YN1L 414 +#define DMP_YH 416 +#define DMP_YL 418 +#define DMP_B0H 420 +#define DMP_B0L 422 +#define DMP_A1H 424 +#define DMP_A1L 426 +#define DMP_A2H 428 +#define DMP_A2L 430 +#define DMP_SEM1 432 +#define DMP_FIFOCNT 434 +#define DMP_SH_TH_X 436 +#define DMP_PACKET 438 +#define DMP_SH_TH_Y 440 +#define DMP_FOOTER 442 +#define DMP_SH_TH_Z 444 +#define DMP_TEMP29 448 +#define DMP_TEMP30 450 +#define DMP_XACCB_PRE 452 +#define DMP_XACCB_PREL 454 +#define DMP_YACCB_PRE 456 +#define DMP_YACCB_PREL 458 +#define DMP_ZACCB_PRE 460 +#define DMP_ZACCB_PREL 462 +#define DMP_TMP22 464 +#define DMP_TAP_TIMER 466 +#define DMP_TAP_THX 468 +#define DMP_TAP_THY 472 +#define DMP_TAP_THZ 476 +#define DMP_TAPW_MIN 478 +#define DMP_TMP25 480 +#define DMP_TMP26 482 +#define DMP_TMP27 484 +#define DMP_TMP28 486 +#define DMP_ORIENT 488 +#define DMP_THRSH 490 +#define DMP_ENDIANH 492 +#define DMP_ENDIANL 494 +#define DMP_BLPFNMTCH 496 +#define DMP_BLPFNMTCL 498 +#define DMP_BLPFNMXH 500 +#define DMP_BLPFNMXL 502 +#define DMP_BLPFNMYH 504 +#define DMP_BLPFNMYL 506 +#define DMP_BLPFNMZH 508 +#define DMP_BLPFNMZL 510 +#ifdef __cplusplus +} +#endif +#endif diff --git a/drivers/iio/imu/inv_mpu/inv_counters.h b/drivers/iio/imu/inv_mpu/inv_counters.h new file mode 100644 index 00000000..35a802b0 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_counters.h @@ -0,0 +1,72 @@ +/* + * @file inv_counters.h + * @brief Debug file to keep track of various counters for the InvenSense + * sensor drivers. + * + * @version 0.1 + */ + +#ifndef _INV_COUNTERS_H_ +#define _INV_COUNTERS_H_ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/sysfs.h> +#include <linux/string.h> +#include <linux/jiffies.h> +#include <linux/spinlock.h> + +enum irqtype { + MPU, + ACCEL, + COMPASS +}; + +#ifdef CONFIG_INV_TESTING + +#define INV_I2C_INC_MPUREAD(x) inv_iio_counters_mpuread(x) +#define INV_I2C_INC_MPUWRITE(x) inv_iio_counters_mpuwrite(x) +#define INV_I2C_INC_ACCELREAD(x) inv_iio_counters_accelread(x) +#define INV_I2C_INC_ACCELWRITE(x) inv_iio_counters_accelwrite(x) +#define INV_I2C_INC_COMPASSREAD(x) inv_iio_counters_compassread(x) +#define INV_I2C_INC_COMPASSWRITE(x) inv_iio_counters_compasswrite(x) + +#define INV_I2C_INC_TEMPREAD(x) inv_iio_counters_tempread(x) + +#define INV_I2C_SETIRQ(type, irq) inv_iio_counters_set_i2cirq(type, irq) +#define INV_I2C_INC_COMPASSIRQ() inv_iio_counters_compassirq() +#define INV_I2C_INC_ACCELIRQ() inv_iio_counters_accelirq() + +void inv_iio_counters_mpuread(int count); +void inv_iio_counters_mpuwrite(int count); +void inv_iio_counters_accelread(int count); +void inv_iio_counters_accelwrite(int count); +void inv_iio_counters_compassread(int count); +void inv_iio_counters_compasswrite(int count); + +void inv_iio_counters_tempread(int count); + +void inv_iio_counters_set_i2cirq(enum irqtype type, int irq); +void inv_iio_counters_compassirq(void); +void inv_iio_counters_accelirq(void); + +#else + +#define INV_I2C_INC_MPUREAD(x) +#define INV_I2C_INC_MPUWRITE(x) +#define INV_I2C_INC_ACCELREAD(x) +#define INV_I2C_INC_ACCELWRITE(x) +#define INV_I2C_INC_COMPASSREAD(x) +#define INV_I2C_INC_COMPASSWRITE(x) + +#define INV_I2C_INC_TEMPREAD(x) + +#define INV_I2C_SETIRQ(type, irq) +#define INV_I2C_INC_COMPASSIRQ() +#define INV_I2C_INC_ACCELIRQ() + +#endif /* CONFIG_INV_TESTING */ + +#endif /* _INV_COUNTERS_H_ */ + diff --git a/drivers/iio/imu/inv_mpu/inv_mpu3050_iio.c b/drivers/iio/imu/inv_mpu/inv_mpu3050_iio.c new file mode 100644 index 00000000..920c9dde --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu3050_iio.c @@ -0,0 +1,292 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_mpu3050_iio.c + * @brief A sysfs device driver for Invensense devices + * @details This file is part of invensense mpu driver code + */ + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> +#include <linux/spinlock.h> + +#include "inv_mpu_iio.h" +#define MPU3050_NACK_MIN_TIME (2 * 1000) +#define MPU3050_NACK_MAX_TIME (3 * 1000) + +#define MPU3050_ONE_MPU_TIME 20 +#define MPU3050_BOGUS_ADDR 0x7F +int __attribute__((weak)) inv_register_mpu3050_slave(struct inv_mpu_iio_s *st) +{ + return 0; +} + +int set_3050_bypass(struct inv_mpu_iio_s *st, bool enable) +{ + struct inv_reg_map_s *reg; + int result; + u8 b; + + reg = &st->reg; + result = inv_i2c_read(st, reg->user_ctrl, 1, &b); + if (result) + return result; + if (((b & BIT_3050_AUX_IF_EN) == 0) && enable) + return 0; + if ((b & BIT_3050_AUX_IF_EN) && (enable == 0)) + return 0; + b &= ~BIT_3050_AUX_IF_EN; + if (!enable) { + b |= BIT_3050_AUX_IF_EN; + result = inv_i2c_single_write(st, reg->user_ctrl, b); + return result; + } else { + /* Coming out of I2C is tricky due to several erratta. Do not + * modify this algorithm + */ + /* + * 1) wait for the right time and send the command to change + * the aux i2c slave address to an invalid address that will + * get nack'ed + * + * 0x00 is broadcast. 0x7F is unlikely to be used by any aux. + */ + result = inv_i2c_single_write(st, REG_3050_SLAVE_ADDR, + MPU3050_BOGUS_ADDR); + if (result) + return result; + /* + * 2) wait enough time for a nack to occur, then go into + * bypass mode: + */ + usleep_range(MPU3050_NACK_MIN_TIME, MPU3050_NACK_MAX_TIME); + result = inv_i2c_single_write(st, reg->user_ctrl, b); + if (result) + return result; + /* + * 3) wait for up to one MPU cycle then restore the slave + * address + */ + msleep(MPU3050_ONE_MPU_TIME); + + result = inv_i2c_single_write(st, REG_3050_SLAVE_ADDR, + st->plat_data.secondary_i2c_addr); + if (result) + return result; + result = inv_i2c_single_write(st, reg->user_ctrl, b); + if (result) + return result; + usleep_range(MPU3050_NACK_MIN_TIME, MPU3050_NACK_MAX_TIME); + } + return 0; +} + +void inv_setup_reg_mpu3050(struct inv_reg_map_s *reg) +{ + reg->fifo_en = REG_3050_FIFO_EN; + reg->sample_rate_div = REG_3050_SAMPLE_RATE_DIV; + reg->lpf = REG_3050_LPF; + reg->fifo_count_h = REG_3050_FIFO_COUNT_H; + reg->fifo_r_w = REG_3050_FIFO_R_W; + reg->user_ctrl = REG_3050_USER_CTRL; + reg->pwr_mgmt_1 = REG_3050_PWR_MGMT_1; + reg->raw_gyro = REG_3050_RAW_GYRO; + reg->raw_accl = REG_3050_AUX_XOUT_H; + reg->temperature = REG_3050_TEMPERATURE; + reg->int_enable = REG_3050_INT_ENABLE; + reg->int_status = REG_3050_INT_STATUS; +} + +int inv_switch_3050_gyro_engine(struct inv_mpu_iio_s *st, bool en) +{ + struct inv_reg_map_s *reg; + u8 data, p; + int result; + reg = &st->reg; + if (en) { + data = INV_CLK_PLL; + p = (BITS_3050_POWER1 | data); + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p); + if (result) + return result; + p = (BITS_3050_POWER2 | data); + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p); + if (result) + return result; + p = data; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p); + msleep(SENSOR_UP_TIME); + } else { + p = BITS_3050_GYRO_STANDBY; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p); + } + + return result; +} + +int inv_switch_3050_accl_engine(struct inv_mpu_iio_s *st, bool en) +{ + int result; + if (NULL == st->mpu_slave) + return -EPERM; + if (en) + result = st->mpu_slave->resume(st); + else + result = st->mpu_slave->suspend(st); + + return result; +} + +/** + * inv_init_config_mpu3050() - Initialize hardware, disable FIFO. + * @st: Device driver instance. + * Initial configuration: + * FSR: +/- 2000DPS + * DLPF: 42Hz + * FIFO rate: 50Hz + * Clock source: Gyro PLL + */ +int inv_init_config_mpu3050(struct iio_dev *indio_dev) +{ + struct inv_reg_map_s *reg; + int result; + u8 data; + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + + if (st->chip_config.is_asleep) + return -EPERM; + /*reading AUX VDDIO register */ + result = inv_i2c_read(st, REG_3050_AUX_VDDIO, 1, &data); + if (result) + return result; + data &= ~BIT_3050_VDDIO; + if (st->plat_data.level_shifter) + data |= BIT_3050_VDDIO; + result = inv_i2c_single_write(st, REG_3050_AUX_VDDIO, data); + if (result) + return result; + + reg = &st->reg; + result = set_inv_enable(indio_dev, false); + if (result) + return result; + /*2000dps full scale range*/ + result = inv_i2c_single_write(st, reg->lpf, + (INV_FSR_2000DPS << GYRO_CONFIG_FSR_SHIFT) + | INV_FILTER_42HZ); + if (result) + return result; + st->chip_config.fsr = INV_FSR_2000DPS; + st->chip_config.lpf = INV_FILTER_42HZ; + result = inv_i2c_single_write(st, reg->sample_rate_div, + ONE_K_HZ/INIT_FIFO_RATE - 1); + if (result) + return result; + st->chip_config.fifo_rate = INIT_FIFO_RATE; + st->irq_dur_ns = INIT_DUR_TIME; + st->chip_config.prog_start_addr = DMP_START_ADDR; + st->chip_config.gyro_enable = 1; + st->chip_config.gyro_fifo_enable = 1; + if ((SECONDARY_SLAVE_TYPE_ACCEL == st->plat_data.sec_slave_type) && + st->mpu_slave) { + result = st->mpu_slave->setup(st); + if (result) + return result; + result = st->mpu_slave->set_fs(st, INV_FS_02G); + if (result) + return result; + result = st->mpu_slave->set_lpf(st, INIT_FIFO_RATE); + if (result) + return result; + st->chip_config.accl_enable = 1; + st->chip_config.accl_fifo_enable = 1; + } + + return 0; +} + +/** + * set_power_mpu3050() - set power of mpu3050. + * @st: Device driver instance. + * @power_on: on/off + */ +int set_power_mpu3050(struct inv_mpu_iio_s *st, bool power_on) +{ + struct inv_reg_map_s *reg; + u8 data, p; + int result; + reg = &st->reg; + if (power_on) { + data = 0; + } else { + if (st->mpu_slave) { + result = st->mpu_slave->suspend(st); + if (result) + return result; + } + data = BIT_SLEEP; + } + if (st->chip_config.gyro_enable) { + p = (BITS_3050_POWER1 | INV_CLK_PLL); + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p); + if (result) + return result; + + p = (BITS_3050_POWER2 | INV_CLK_PLL); + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p); + if (result) + return result; + + p = INV_CLK_PLL; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p); + if (result) + return result; + } else { + data |= (BITS_3050_GYRO_STANDBY | INV_CLK_INTERNAL); + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data); + if (result) + return result; + } + if (power_on) { + msleep(POWER_UP_TIME); + if (st->mpu_slave) { + result = st->mpu_slave->resume(st); + if (result) + return result; + } + } + st->chip_config.is_asleep = !power_on; + + return 0; +} +/** + * @} + */ + diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_core.c b/drivers/iio/imu/inv_mpu/inv_mpu_core.c new file mode 100644 index 00000000..e547ab77 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_core.c @@ -0,0 +1,2078 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_mpu_core.c + * @brief A sysfs device driver for Invensense devices + * @details This driver currently works for the + * MPU3050/MPU6050/MPU9150/MPU6500/MPU9250 devices. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> +#include <linux/spinlock.h> +#include <linux/iio/sysfs.h> + +#include "inv_mpu_iio.h" +#include "inv_counters.h" + +#include <linux/regulator/consumer.h> +#include <mach/regulator.h> +#include <linux/gpio.h> +#include <linux/input.h> +s64 get_time_ns(void) +{ + struct timespec ts; + ktime_get_ts(&ts); + return timespec_to_ns(&ts); +} + +static const short AKM8975_ST_Lower[3] = {-100, -100, -1000}; +static const short AKM8975_ST_Upper[3] = {100, 100, -300}; + +static const short AKM8972_ST_Lower[3] = {-50, -50, -500}; +static const short AKM8972_ST_Upper[3] = {50, 50, -100}; + +static const short AKM8963_ST_Lower[3] = {-200, -200, -3200}; +static const short AKM8963_ST_Upper[3] = {200, 200, -800}; + +static const struct inv_hw_s hw_info[INV_NUM_PARTS] = { + {119, "ITG3500"}, + { 63, "MPU3050"}, + {117, "MPU6050"}, + {118, "MPU9150"}, + {119, "MPU6500"}, + {118, "MPU9250"}, +}; + +static void inv_setup_reg(struct inv_reg_map_s *reg) +{ + reg->sample_rate_div = REG_SAMPLE_RATE_DIV; + reg->lpf = REG_CONFIG; + reg->bank_sel = REG_BANK_SEL; + reg->user_ctrl = REG_USER_CTRL; + reg->fifo_en = REG_FIFO_EN; + reg->gyro_config = REG_GYRO_CONFIG; + reg->accl_config = REG_ACCEL_CONFIG; + reg->fifo_count_h = REG_FIFO_COUNT_H; + reg->fifo_r_w = REG_FIFO_R_W; + reg->raw_gyro = REG_RAW_GYRO; + reg->raw_accl = REG_RAW_ACCEL; + reg->temperature = REG_TEMPERATURE; + reg->int_enable = REG_INT_ENABLE; + reg->int_status = REG_INT_STATUS; + reg->pwr_mgmt_1 = REG_PWR_MGMT_1; + reg->pwr_mgmt_2 = REG_PWR_MGMT_2; + reg->mem_start_addr = REG_MEM_START_ADDR; + reg->mem_r_w = REG_MEM_RW; + reg->prgm_strt_addrh = REG_PRGM_STRT_ADDRH; +}; + +/** + * inv_i2c_read() - Read one or more bytes from the device registers. + * @st: Device driver instance. + * @reg: First device register to be read from. + * @length: Number of bytes to read. + * @data: Data read from device. + * NOTE:This is not re-implementation of i2c_smbus_read because i2c + * address could be specified in this case. We could have two different + * i2c address due to secondary i2c interface. + */ +int inv_i2c_read_base(struct inv_mpu_iio_s *st, u16 i2c_addr, + u8 reg, u16 length, u8 *data) +{ + struct i2c_msg msgs[2]; + int res; + + if (!data) + return -EINVAL; + + msgs[0].addr = i2c_addr; + msgs[0].flags = 0; /* write */ + msgs[0].buf = ® + msgs[0].len = 1; + + msgs[1].addr = i2c_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = data; + msgs[1].len = length; + + res = i2c_transfer(st->sl_handle, msgs, 2); + + if (res < 2) { + if (res >= 0) + res = -EIO; + } else + res = 0; + + INV_I2C_INC_MPUWRITE(3); + INV_I2C_INC_MPUREAD(length); +#if CONFIG_DYNAMIC_DEBUG + { + char *read = 0; + pr_debug("%s RD%02X%02X%02X -> %s%s\n", st->hw->name, + i2c_addr, reg, length, + wr_pr_debug_begin(data, length, read), + wr_pr_debug_end(read)); + } +#endif + return res; +} + +/** + * inv_i2c_single_write() - Write a byte to a device register. + * @st: Device driver instance. + * @reg: Device register to be written to. + * @data: Byte to write to device. + * NOTE:This is not re-implementation of i2c_smbus_write because i2c + * address could be specified in this case. We could have two different + * i2c address due to secondary i2c interface. + */ +int inv_i2c_single_write_base(struct inv_mpu_iio_s *st, + u16 i2c_addr, u8 reg, u8 data) +{ + u8 tmp[2]; + struct i2c_msg msg; + int res; + + tmp[0] = reg; + tmp[1] = data; + + msg.addr = i2c_addr; + msg.flags = 0; /* write */ + msg.buf = tmp; + msg.len = 2; + + pr_debug("%s WR%02X%02X%02X\n", st->hw->name, i2c_addr, reg, data); + INV_I2C_INC_MPUWRITE(3); + + res = i2c_transfer(st->sl_handle, &msg, 1); + if (res < 1) { + if (res == 0) + res = -EIO; + return res; + } else + return 0; +} + +static int inv_switch_engine(struct inv_mpu_iio_s *st, bool en, u32 mask) +{ + struct inv_reg_map_s *reg; + u8 data, mgmt_1; + int result; + reg = &st->reg; + /* 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 (BIT_PWR_GYRO_STBY == mask) { + result = inv_i2c_read(st, reg->pwr_mgmt_1, 1, &mgmt_1); + if (result) + return result; + + mgmt_1 &= ~BIT_CLK_MASK; + } + + if ((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_i2c_single_write(st, reg->pwr_mgmt_1, + mgmt_1); + if (result) + return result; + } + + result = inv_i2c_read(st, reg->pwr_mgmt_2, 1, &data); + if (result) + return result; + if (en) + data &= (~mask); + else + data |= mask; + result = inv_i2c_single_write(st, reg->pwr_mgmt_2, data); + if (result) + return result; + + if ((BIT_PWR_GYRO_STBY == mask) && en) { + /* only gyro on needs sensor up time */ + msleep(SENSOR_UP_TIME); + /* after gyro is on & stable, switch internal clock to PLL */ + mgmt_1 |= INV_CLK_PLL; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, + mgmt_1); + if (result) + return result; + } + + return 0; +} + +/** + * inv_lpa_freq() - store current low power frequency setting. + */ +static int inv_lpa_freq(struct inv_mpu_iio_s *st, int lpa_freq) +{ + unsigned long result; + u8 d; + struct inv_reg_map_s *reg; + /* this mapping makes 6500 and 6050 setting close */ + /* 2, 4, 6, 7 corresponds to 0.98, 3.91, 15.63, 31.25 */ + const u8 mpu6500_lpa_mapping[] = {2, 4, 6, 7}; + + if (lpa_freq > MAX_LPA_FREQ_PARAM) + return -EINVAL; + + if (INV_MPU6500 == st->chip_type) { + d = mpu6500_lpa_mapping[lpa_freq]; + result = inv_i2c_single_write(st, REG_6500_LP_ACCEL_ODR, d); + if (result) + return result; + } else { + reg = &st->reg; + result = inv_i2c_read(st, reg->pwr_mgmt_2, 1, &d); + if (result) + return result; + d &= ~BIT_LPA_FREQ; + d |= (u8)(lpa_freq << LPA_FREQ_SHIFT); + result = inv_i2c_single_write(st, reg->pwr_mgmt_2, d); + if (result) + return result; + } + st->chip_config.lpa_freq = lpa_freq; + + return 0; +} + +static int set_power_itg(struct inv_mpu_iio_s *st, bool power_on) +{ + struct inv_reg_map_s *reg; + u8 data; + int result; + + reg = &st->reg; + if (power_on) + data = 0; + else + data = BIT_SLEEP; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data); + if (result) + return result; + + if (power_on) { + msleep(POWER_UP_TIME); + result = inv_switch_engine(st, st->chip_config.gyro_enable, + BIT_PWR_GYRO_STBY); + if (result) + return result; + result = inv_switch_engine(st, st->chip_config.accl_enable, + BIT_PWR_ACCL_STBY); + if (result) + return result; + result = inv_lpa_freq(st, st->chip_config.lpa_freq); + if (result) + return result; + } + st->chip_config.is_asleep = !power_on; + + return 0; +} + +/** + * inv_init_config() - Initialize hardware, disable FIFO. + * @indio_dev: Device driver instance. + * Initial configuration: + * FSR: +/- 2000DPS + * DLPF: 42Hz + * FIFO rate: 50Hz + */ +static int inv_init_config(struct iio_dev *indio_dev) +{ + struct inv_reg_map_s *reg; + int result; + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + + if (st->chip_config.is_asleep) + return -EPERM; + + reg = &st->reg; + result = set_inv_enable(indio_dev, false); + if (result) + return result; + + result = inv_i2c_single_write(st, reg->gyro_config, + INV_FSR_2000DPS << GYRO_CONFIG_FSR_SHIFT); + if (result) + return result; + + st->chip_config.fsr = INV_FSR_2000DPS; + + result = inv_i2c_single_write(st, reg->lpf, INV_FILTER_42HZ); + if (result) + return result; + st->chip_config.lpf = INV_FILTER_42HZ; + + result = inv_i2c_single_write(st, reg->sample_rate_div, + ONE_K_HZ / INIT_FIFO_RATE - 1); + if (result) + return result; + st->chip_config.fifo_rate = INIT_FIFO_RATE; + st->irq_dur_ns = INIT_DUR_TIME; + st->chip_config.prog_start_addr = DMP_START_ADDR; + st->chip_config.gyro_enable = 1; + st->chip_config.gyro_fifo_enable = 1; + st->chip_config.dmp_output_rate = INIT_DMP_OUTPUT_RATE; + if (INV_ITG3500 != st->chip_type) { + st->chip_config.accl_enable = 1; + st->chip_config.accl_fifo_enable = 1; + st->chip_config.accl_fs = INV_FS_02G; + result = inv_i2c_single_write(st, reg->accl_config, + (INV_FS_02G << ACCL_CONFIG_FSR_SHIFT)); + if (result) + return result; + st->tap.time = INIT_TAP_TIME; + st->tap.thresh = INIT_TAP_THRESHOLD; + st->tap.min_count = INIT_TAP_MIN_COUNT; + + result = inv_i2c_single_write(st, REG_ACCEL_MOT_DUR, + INIT_MOT_DUR); + if (result) + return result; + st->mot_int.mot_dur = INIT_MOT_DUR; + + result = inv_i2c_single_write(st, REG_ACCEL_MOT_THR, + INIT_MOT_THR); + if (result) + return result; + st->mot_int.mot_thr = INIT_MOT_THR; + } + + return 0; +} + +/** + * inv_compass_scale_show() - show compass scale. + */ +static int inv_compass_scale_show(struct inv_mpu_iio_s *st, int *scale) +{ + if (COMPASS_ID_AK8975 == st->plat_data.sec_slave_id) + *scale = DATA_AKM8975_SCALE; + else if (COMPASS_ID_AK8972 == st->plat_data.sec_slave_id) + *scale = DATA_AKM8972_SCALE; + else if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id) + if (st->compass_scale) + *scale = DATA_AKM8963_SCALE1; + else + *scale = DATA_AKM8963_SCALE0; + else + return -EINVAL; + + return IIO_VAL_INT; +} + +/** + * mpu_read_raw() - read raw method. + */ +static int mpu_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + int result; + if (st->chip_config.is_asleep) + return -EINVAL; + switch (mask) { + case 0: + if (!st->chip_config.enable) + return -EPERM; + switch (chan->type) { + case IIO_ANGL_VEL: + if (!st->chip_config.gyro_enable) + return -EPERM; + *val = st->raw_gyro[chan->channel2 - IIO_MOD_X]; + return IIO_VAL_INT; + case IIO_ACCEL: + if (!st->chip_config.accl_enable) + return -EPERM; + *val = st->raw_accel[chan->channel2 - IIO_MOD_X]; + return IIO_VAL_INT; + case IIO_MAGN: + if (!st->chip_config.compass_enable) + return -EPERM; + *val = st->raw_compass[chan->channel2 - IIO_MOD_X]; + return IIO_VAL_INT; + case IIO_QUATERNION: + if (!(st->chip_config.dmp_on + && st->chip_config.quaternion_on)) + return -EPERM; + if (IIO_MOD_R == chan->channel2) + *val = st->raw_quaternion[0]; + else + *val = st->raw_quaternion[chan->channel2 - + IIO_MOD_X + 1]; + return IIO_VAL_INT; + default: + return -EINVAL; + } + return -EINVAL; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + { + const s16 gyro_scale[] = {250, 500, 1000, 2000}; + + *val = gyro_scale[st->chip_config.fsr]; + + return IIO_VAL_INT; + } + case IIO_ACCEL: + { + const s16 accel_scale[] = {2, 4, 8, 16}; + *val = accel_scale[st->chip_config.accl_fs]; + return IIO_VAL_INT; + } + case IIO_MAGN: + return inv_compass_scale_show(st, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + /* return bias=0 for both accel and gyro for MPU6500; + self test not supported yet */ + if (INV_MPU6500 == st->chip_type) { + *val = 0; + return IIO_VAL_INT; + } + if (st->chip_config.self_test_run_once == 0) { + result = inv_do_test(st, 0, st->gyro_bias, + st->accel_bias); + /* Reset Accel and Gyro full scale range + back to default value */ + inv_recover_setting(st); + if (result) + return result; + st->chip_config.self_test_run_once = 1; + } + + switch (chan->type) { + case IIO_ANGL_VEL: + *val = st->gyro_bias[chan->channel2 - IIO_MOD_X]; + return IIO_VAL_INT; + case IIO_ACCEL: + *val = st->accel_bias[chan->channel2 - IIO_MOD_X] * + st->chip_info.multi; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_ACCEL: + *val = st->input_accel_bias[chan->channel2 - IIO_MOD_X]; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +/** + * inv_write_fsr() - Configure the gyro's scale range. + */ +static int inv_write_fsr(struct inv_mpu_iio_s *st, int fsr) +{ + struct inv_reg_map_s *reg; + int result; + reg = &st->reg; + if ((fsr < 0) || (fsr > MAX_GYRO_FS_PARAM)) + return -EINVAL; + if (fsr == st->chip_config.fsr) + return 0; + + if (INV_MPU3050 == st->chip_type) + result = inv_i2c_single_write(st, reg->lpf, + (fsr << GYRO_CONFIG_FSR_SHIFT) | st->chip_config.lpf); + else + result = inv_i2c_single_write(st, reg->gyro_config, + fsr << GYRO_CONFIG_FSR_SHIFT); + + if (result) + return result; + st->chip_config.fsr = fsr; + + return 0; +} + +/** + * inv_write_accel_fs() - Configure the accelerometer's scale range. + */ +static int inv_write_accel_fs(struct inv_mpu_iio_s *st, int fs) +{ + int result; + struct inv_reg_map_s *reg; + reg = &st->reg; + + if (fs < 0 || fs > MAX_ACCL_FS_PARAM) + return -EINVAL; + if (fs == st->chip_config.accl_fs) + return 0; + if (INV_MPU3050 == st->chip_type) + result = st->mpu_slave->set_fs(st, fs); + else + result = inv_i2c_single_write(st, reg->accl_config, + (fs << ACCL_CONFIG_FSR_SHIFT)); + if (result) + return result; + + st->chip_config.accl_fs = fs; + + return 0; +} + +/** + * inv_write_compass_scale() - Configure the compass's scale range. + */ +static int inv_write_compass_scale(struct inv_mpu_iio_s *st, int data) +{ + char d, en; + int result; + if (COMPASS_ID_AK8963 != st->plat_data.sec_slave_id) + return 0; + en = !!data; + if (st->compass_scale == en) + return 0; + d = (DATA_AKM_MODE_SM | (st->compass_scale << AKM8963_SCALE_SHIFT)); + result = inv_i2c_single_write(st, REG_I2C_SLV1_DO, d); + if (result) + return result; + st->compass_scale = en; + + return 0; +} + +static inline int check_enable(struct inv_mpu_iio_s *st) +{ + return st->chip_config.is_asleep | st->chip_config.enable; +} + +static inline int check_dmp_on(struct inv_mpu_iio_s *st) +{ + return (!st->chip_config.is_asleep) && + st->chip_config.enable && st->chip_config.dmp_on; +} + +/** + * mpu_write_raw() - write raw method. + */ +static int mpu_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) { + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + int result; + if (check_enable(st)) + return -EPERM; + switch (mask) { + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + return inv_write_fsr(st, val); + case IIO_ACCEL: + return inv_write_accel_fs(st, val); + case IIO_MAGN: + return inv_write_compass_scale(st, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_ACCEL: + if (!st->chip_config.firmware_loaded) + return -EPERM; + result = inv_set_accel_bias_dmp(st); + if (result) + return result; + st->input_accel_bias[chan->channel2 - IIO_MOD_X] = val; + return 0; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + return 0; +} + +/** + * inv_set_lpf() - set low pass filer based on fifo rate. + */ +static int inv_set_lpf(struct inv_mpu_iio_s *st, int rate) +{ + const short hz[] = {188, 98, 42, 20, 10, 5}; + const int d[] = {INV_FILTER_188HZ, INV_FILTER_98HZ, + INV_FILTER_42HZ, INV_FILTER_20HZ, + INV_FILTER_10HZ, INV_FILTER_5HZ}; + int i, h, data, result; + struct inv_reg_map_s *reg; + reg = &st->reg; + h = (rate >> 1); + i = 0; + while ((h < hz[i]) && (i < ARRAY_SIZE(d) - 1)) + i++; + data = d[i]; + if (INV_MPU3050 == st->chip_type) { + if (st->mpu_slave != NULL) { + result = st->mpu_slave->set_lpf(st, rate); + if (result) + return result; + } + result = inv_i2c_single_write(st, reg->lpf, data | + (st->chip_config.fsr << GYRO_CONFIG_FSR_SHIFT)); + } else { + result = inv_i2c_single_write(st, reg->lpf, data); + } + if (result) + return result; + st->chip_config.lpf = data; + + return 0; +} + +/** + * inv_fifo_rate_store() - Set fifo rate. + */ +static ssize_t inv_fifo_rate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u32 fifo_rate; + u8 data; + int result; + struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev)); + struct inv_reg_map_s *reg; + reg = &st->reg; + + if (check_enable(st)) + return -EPERM; + if (kstrtouint(buf, 10, &fifo_rate)) + return -EINVAL; + if ((fifo_rate < MIN_FIFO_RATE) || (fifo_rate > MAX_FIFO_RATE)) + return -EINVAL; + if (fifo_rate == st->chip_config.fifo_rate) + return count; + if (st->chip_config.has_compass) { + st->compass_divider = COMPASS_RATE_SCALE * fifo_rate / + ONE_K_HZ; + if (st->compass_divider > 0) + st->compass_divider -= 1; + st->compass_counter = 0; + } + data = ONE_K_HZ / fifo_rate - 1; + result = inv_i2c_single_write(st, reg->sample_rate_div, data); + if (result) + return result; + st->chip_config.fifo_rate = fifo_rate; + result = inv_set_lpf(st, fifo_rate); + if (result) + return result; + st->irq_dur_ns = (data + 1) * NSEC_PER_MSEC; + + 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_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev)); + + return sprintf(buf, "%d\n", st->chip_config.fifo_rate); +} + +/** + * inv_power_state_store() - Turn device on/off. + */ +static ssize_t inv_power_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int result; + u32 power_state; + struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev)); + if (kstrtouint(buf, 10, &power_state)) + return -EINVAL; + if ((!power_state) == st->chip_config.is_asleep) + return count; + result = st->set_power_state(st, power_state); + + return count; +} + +/** + * inv_reg_dump_show() - Register dump for testing. + */ +static ssize_t inv_reg_dump_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ii; + char data; + ssize_t bytes_printed = 0; + struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev)); + + for (ii = 0; ii < st->hw->num_reg; ii++) { + /* don't read fifo r/w register */ + if (ii == st->reg.fifo_r_w) + data = 0; + else + inv_i2c_read(st, ii, 1, &data); + bytes_printed += sprintf(buf + bytes_printed, "%#2x: %#2x\n", + ii, data); + } + + return bytes_printed; +} + +int write_be32_key_to_mem(struct inv_mpu_iio_s *st, + u32 data, int key) +{ + cpu_to_be32s(&data); + return mem_w_key(key, sizeof(data), (u8 *)&data); +} + +/** + * inv_dmp_attr_store() - calling this function will store current + * dmp parameter settings + */ +static ssize_t inv_dmp_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int result, data; + mutex_lock(&indio_dev->mlock); + if (st->chip_config.is_asleep | (!st->chip_config.firmware_loaded) | + st->chip_config.enable) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + result = kstrtoint(buf, 10, &data); + if (result) + goto dmp_attr_store_fail; + switch (this_attr->address) { + case ATTR_DMP_PEDOMETER_STEPS: + result = write_be32_key_to_mem(st, data, KEY_D_PEDSTD_STEPCTR); + if (result) + goto dmp_attr_store_fail; + break; + case ATTR_DMP_PEDOMETER_TIME: + result = write_be32_key_to_mem(st, data, KEY_D_PEDSTD_TIMECTR); + if (result) + goto dmp_attr_store_fail; + break; + case ATTR_DMP_TAP_THRESHOLD: { + const char ax[] = {INV_TAP_AXIS_X, INV_TAP_AXIS_Y, + INV_TAP_AXIS_Z}; + int i; + if (data < 0 || data > USHRT_MAX) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + for (i = 0; i < ARRAY_SIZE(ax); i++) { + result = inv_set_tap_threshold_dmp(st, ax[i], data); + if (result) + goto dmp_attr_store_fail; + } + st->tap.thresh = data; + break; + } + case ATTR_DMP_TAP_MIN_COUNT: + if (data < 0 || data > USHRT_MAX) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + result = inv_set_min_taps_dmp(st, data); + if (result) + goto dmp_attr_store_fail; + st->tap.min_count = data; + break; + case ATTR_DMP_TAP_ON: + result = inv_enable_tap_dmp(st, !!data); + if (result) + goto dmp_attr_store_fail; + st->chip_config.tap_on = !!data; + break; + case ATTR_DMP_TAP_TIME: + if (data < 0 || data > USHRT_MAX) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + result = inv_set_tap_time_dmp(st, data); + if (result) + goto dmp_attr_store_fail; + st->tap.time = data; + break; + case ATTR_DMP_ON: + st->chip_config.dmp_on = !!data; + break; + case ATTR_DMP_INT_ON: + st->chip_config.dmp_int_on = !!data; + break; + case ATTR_DMP_EVENT_INT_ON: + result = inv_set_interrupt_on_gesture_event(st, !!data); + if (result) + goto dmp_attr_store_fail; + st->chip_config.dmp_event_int_on = !!data; + break; + case ATTR_DMP_OUTPUT_RATE: + if (data <= 0 || data > USHRT_MAX) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + result = inv_set_fifo_rate(st, data); + if (result) + goto dmp_attr_store_fail; + if (st->chip_config.has_compass) { + st->compass_dmp_divider = COMPASS_RATE_SCALE * data / + ONE_K_HZ; + if (st->compass_dmp_divider > 0) + st->compass_dmp_divider -= 1; + st->compass_counter = 0; + } + st->chip_config.dmp_output_rate = data; + break; + case ATTR_DMP_DISPLAY_ORIENTATION_ON: + result = inv_set_display_orient_interrupt_dmp(st, !!data); + if (result) + goto dmp_attr_store_fail; + st->chip_config.display_orient_on = !!data; + break; + default: + result = -EINVAL; + goto dmp_attr_store_fail; + } + result = count; + +dmp_attr_store_fail: + mutex_unlock(&indio_dev->mlock); + + return result; +} + +/** + * inv_attr_show() - calling this function will show current + * dmp parameters. + */ +static ssize_t inv_attr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev)); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + s8 d[4]; + int result, data; + s8 *m; + + switch (this_attr->address) { + case ATTR_DMP_PEDOMETER_STEPS: + if (!check_dmp_on(st)) + return -EPERM; + result = mpu_memory_read(st, st->i2c_addr, + inv_dmp_get_address(KEY_D_PEDSTD_STEPCTR), 4, d); + if (result) + return result; + data = be32_to_cpup((int *)d); + return sprintf(buf, "%d\n", data); + case ATTR_DMP_PEDOMETER_TIME: + if (!check_dmp_on(st)) + return -EPERM; + result = mpu_memory_read(st, st->i2c_addr, + inv_dmp_get_address(KEY_D_PEDSTD_TIMECTR), 4, d); + if (result) + return result; + data = be32_to_cpup((int *)d); + return sprintf(buf, "%d\n", data * MS_PER_DMP_TICK); + case ATTR_DMP_TAP_THRESHOLD: + return sprintf(buf, "%d\n", st->tap.thresh); + case ATTR_DMP_TAP_MIN_COUNT: + return sprintf(buf, "%d\n", st->tap.min_count); + case ATTR_DMP_TAP_ON: + return sprintf(buf, "%d\n", st->chip_config.tap_on); + case ATTR_DMP_TAP_TIME: + return sprintf(buf, "%d\n", st->tap.time); + case ATTR_DMP_ON: + return sprintf(buf, "%d\n", st->chip_config.dmp_on); + case ATTR_DMP_INT_ON: + return sprintf(buf, "%d\n", st->chip_config.dmp_int_on); + case ATTR_DMP_EVENT_INT_ON: + return sprintf(buf, "%d\n", st->chip_config.dmp_event_int_on); + case ATTR_DMP_OUTPUT_RATE: + return sprintf(buf, "%d\n", st->chip_config.dmp_output_rate); + case ATTR_DMP_QUATERNION_ON: + return sprintf(buf, "%d\n", st->chip_config.quaternion_on); + case ATTR_DMP_DISPLAY_ORIENTATION_ON: + return sprintf(buf, "%d\n", + st->chip_config.display_orient_on); + case ATTR_LPA_FREQ:{ + const char *f[] = {"1.25", "5", "20", "40"}; + return sprintf(buf, "%s\n", f[st->chip_config.lpa_freq]); + } + case ATTR_SELF_TEST: + if (INV_MPU3050 == st->chip_type) + result = 1; + else if (INV_MPU6500 == st->chip_type) + result = inv_hw_self_test_6500(st); + else + result = inv_hw_self_test(st); + return sprintf(buf, "%d\n", result); + case ATTR_GYRO_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]); + case ATTR_ACCL_MATRIX: + if (st->plat_data.sec_slave_type == SECONDARY_SLAVE_TYPE_ACCEL) + m = st->plat_data.secondary_orientation; + else + 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]); + case ATTR_COMPASS_MATRIX: + if (st->plat_data.sec_slave_type == + SECONDARY_SLAVE_TYPE_COMPASS) + m = st->plat_data.secondary_orientation; + else + return -ENODEV; + 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]); + case ATTR_GYRO_ENABLE: + return sprintf(buf, "%d\n", st->chip_config.gyro_enable); + case ATTR_ACCL_ENABLE: + return sprintf(buf, "%d\n", st->chip_config.accl_enable); + case ATTR_COMPASS_ENABLE: + return sprintf(buf, "%d\n", st->chip_config.compass_enable); + case ATTR_POWER_STATE: + return sprintf(buf, "%d\n", !st->chip_config.is_asleep); + case ATTR_FIRMWARE_LOADED: + return sprintf(buf, "%d\n", st->chip_config.firmware_loaded); + case ATTR_MOTION_ON: + return sprintf(buf, "%d\n", st->mot_int.mot_on); + case ATTR_MOTION_DURATION: + return sprintf(buf, "%d\n", st->mot_int.mot_dur); + case ATTR_MOTION_THRESHOLD: + return sprintf(buf, "%d\n", st->mot_int.mot_thr); +#ifdef CONFIG_INV_TESTING + case ATTR_REG_WRITE: + return sprintf(buf, "1\n"); +#endif + default: + return -EPERM; + } +} + +/** + * inv_dmp_display_orient_show() - calling this function will + * show orientation This event must use poll. + */ +static ssize_t inv_dmp_display_orient_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev)); + return sprintf(buf, "%d\n", st->display_orient_data); +} + +/** + * inv_accel_motion_show() - calling this function showes motion interrupt. + * This event must use poll. + */ +static ssize_t inv_accel_motion_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "1\n"); +} + +/** + * inv_dmp_tap_show() - calling this function will show tap + * This event must use poll. + */ +static ssize_t inv_dmp_tap_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev)); + return sprintf(buf, "%d\n", st->tap_data); +} + +/** + * inv_temperature_show() - Read temperature data directly from registers. + */ +static ssize_t inv_temperature_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu_iio_s *st = iio_priv(dev_get_drvdata(dev)); + struct inv_reg_map_s *reg; + int result; + short temp; + long scale_t; + u8 data[2]; + reg = &st->reg; + + if (st->chip_config.is_asleep) + return -EPERM; + result = inv_i2c_read(st, reg->temperature, 2, data); + if (result) { + pr_err("Could not read temperature register.\n"); + return result; + } + temp = (signed short)(be16_to_cpup((short *)&data[0])); + + if (INV_MPU3050 == st->chip_type) + scale_t = MPU3050_TEMP_OFFSET + + inv_q30_mult((int)temp << MPU_TEMP_SHIFT, + MPU3050_TEMP_SCALE); + else + scale_t = MPU6050_TEMP_OFFSET + + inv_q30_mult((int)temp << MPU_TEMP_SHIFT, + MPU6050_TEMP_SCALE); + + INV_I2C_INC_TEMPREAD(1); + + return sprintf(buf, "%ld %lld\n", scale_t, get_time_ns()); +} + +/** + * inv_firmware_loaded() - calling this function will change + * firmware load + */ +static int inv_firmware_loaded(struct inv_mpu_iio_s *st, int data) +{ + if (data) + return -EINVAL; + st->chip_config.firmware_loaded = 0; + st->chip_config.dmp_on = 0; + st->chip_config.quaternion_on = 0; + + return 0; +} + +/** + * inv_quaternion_on() - calling this function will store + * current quaternion on + */ +static int inv_quaternion_on(struct inv_mpu_iio_s *st, + struct iio_buffer *ring, bool en) +{ + st->chip_config.quaternion_on = en; + if (!en) { + clear_bit(INV_MPU_SCAN_QUAT_R, ring->scan_mask); + clear_bit(INV_MPU_SCAN_QUAT_X, ring->scan_mask); + clear_bit(INV_MPU_SCAN_QUAT_Y, ring->scan_mask); + clear_bit(INV_MPU_SCAN_QUAT_Z, ring->scan_mask); + } + + return 0; +} + +static int inv_switch_gyro_engine(struct inv_mpu_iio_s *st, bool en) +{ + return inv_switch_engine(st, en, BIT_PWR_GYRO_STBY); +} + +static int inv_switch_accl_engine(struct inv_mpu_iio_s *st, bool en) +{ + return inv_switch_engine(st, en, BIT_PWR_ACCL_STBY); +} + +/** + * inv_gyro_enable() - Enable/disable gyro. + */ +static int inv_gyro_enable(struct inv_mpu_iio_s *st, + struct iio_buffer *ring, bool en) +{ + int result; + if (en == st->chip_config.gyro_enable) + return 0; + result = st->switch_gyro_engine(st, en); + if (result) + return result; + + if (!en) { + st->chip_config.gyro_fifo_enable = 0; + clear_bit(INV_MPU_SCAN_GYRO_X, ring->scan_mask); + clear_bit(INV_MPU_SCAN_GYRO_Y, ring->scan_mask); + clear_bit(INV_MPU_SCAN_GYRO_Z, ring->scan_mask); + } + st->chip_config.gyro_enable = en; + + return 0; +} + +/** + * inv_accl_enable() - Enable/disable accl. + */ +static ssize_t inv_accl_enable(struct inv_mpu_iio_s *st, + struct iio_buffer *ring, bool en) +{ + int result; + if (en == st->chip_config.accl_enable) + return 0; + result = st->switch_accl_engine(st, en); + if (result) + return result; + st->chip_config.accl_enable = en; + if (!en) { + st->chip_config.accl_fifo_enable = 0; + clear_bit(INV_MPU_SCAN_ACCL_X, ring->scan_mask); + clear_bit(INV_MPU_SCAN_ACCL_Y, ring->scan_mask); + clear_bit(INV_MPU_SCAN_ACCL_Z, ring->scan_mask); + } + + return 0; +} + +/** + * inv_compass_enable() - calling this function will store compass + * enable + */ +static ssize_t inv_compass_enable(struct inv_mpu_iio_s *st, + struct iio_buffer *ring, bool en) +{ + if (en == st->chip_config.compass_enable) + return 0; + st->chip_config.compass_enable = en; + if (!en) { + st->chip_config.compass_fifo_enable = 0; + clear_bit(INV_MPU_SCAN_MAGN_X, ring->scan_mask); + clear_bit(INV_MPU_SCAN_MAGN_Y, ring->scan_mask); + clear_bit(INV_MPU_SCAN_MAGN_Z, ring->scan_mask); + } + + return 0; +} + +/** + * inv_attr_store() - calling this function will store current + * non-dmp parameter settings + */ +static ssize_t inv_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int data; + u8 d; + int result; + + mutex_lock(&indio_dev->mlock); + if (check_enable(st)) { + result = -EINVAL; + goto attr_store_fail; + } + + result = kstrtoint(buf, 10, &data); + if (result) + goto attr_store_fail; + switch (this_attr->address) { + case ATTR_GYRO_ENABLE: + result = inv_gyro_enable(st, ring, !!data); + break; + case ATTR_ACCL_ENABLE: + result = inv_accl_enable(st, ring, !!data); + break; + case ATTR_COMPASS_ENABLE: + result = inv_compass_enable(st, ring, !!data); + break; + case ATTR_DMP_QUATERNION_ON: + result = inv_quaternion_on(st, ring, !!data); + break; + case ATTR_LPA_FREQ: + result = inv_lpa_freq(st, data); + break; + case ATTR_FIRMWARE_LOADED: + result = inv_firmware_loaded(st, data); + break; + case ATTR_MOTION_ON: + if (INV_MPU6500 == st->chip_type) { + if (data) + /* enable and put in MPU6500 mode */ + d = BIT_ACCEL_INTEL_ENABLE + | BIT_ACCEL_INTEL_MODE; + else + d = 0; + result = inv_i2c_single_write(st, + REG_6500_ACCEL_INTEL_CTRL, d); + if (result) + goto attr_store_fail; + } + st->mot_int.mot_on = !!data; + st->chip_config.lpa_mode = !!data; + break; + case ATTR_MOTION_DURATION: + if (INV_MPU6500 != st->chip_type) { + result = inv_i2c_single_write(st, REG_ACCEL_MOT_DUR, + MPU6050_MOTION_DUR_DEFAULT); + if (result) + goto attr_store_fail; + } + st->mot_int.mot_dur = data; + break; + case ATTR_MOTION_THRESHOLD: + if ((data > MPU6XXX_MAX_MOTION_THRESH) || (data < 0)) { + result = -EINVAL; + goto attr_store_fail; + } + d = (u8)(data >> MPU6XXX_MOTION_THRESH_SHIFT); + data = (d << MPU6XXX_MOTION_THRESH_SHIFT); + result = inv_i2c_single_write(st, REG_ACCEL_MOT_THR, d); + if (result) + goto attr_store_fail; + st->mot_int.mot_thr = data; + break; + default: + result = -EINVAL; + goto attr_store_fail; + }; + if (result) + goto attr_store_fail; + result = count; + +attr_store_fail: + mutex_unlock(&indio_dev->mlock); + + return result; +} + +#ifdef CONFIG_INV_TESTING +/** + * inv_reg_write_store() - register write command for testing. + * Format: WSRRDD, where RR is the register in hex, + * and DD is the data in hex. + */ +static ssize_t inv_reg_write_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + u32 result; + u8 wreg, wval; + int temp; + char local_buf[10]; + + if ((buf[0] != 'W' && buf[0] != 'w') || + (buf[1] != 'S' && buf[1] != 's')) + return -EINVAL; + if (strlen(buf) < 6) + return -EINVAL; + + strncpy(local_buf, buf, 7); + local_buf[6] = 0; + result = sscanf(&local_buf[4], "%x", &temp); + if (result == 0) + return -EINVAL; + wval = temp; + local_buf[4] = 0; + sscanf(&local_buf[2], "%x", &temp); + if (result == 0) + return -EINVAL; + wreg = temp; + + result = inv_i2c_single_write(st, wreg, wval); + if (result) + return result; + + return count; +} +#endif /* CONFIG_INV_TESTING */ + +#define INV_MPU_CHAN(_type, _channel2, _index) \ + { \ + .type = _type, \ + .modified = 1, \ + .channel2 = _channel2, \ + .info_mask = (IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT), \ + .scan_index = _index, \ + .scan_type = IIO_ST('s', 16, 16, 0) \ + } + +#define INV_ACCL_CHAN(_type, _channel2, _index) \ + { \ + .type = _type, \ + .modified = 1, \ + .channel2 = _channel2, \ + .info_mask = (IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT), \ + .scan_index = _index, \ + .scan_type = IIO_ST('s', 16, 16, 0) \ + } + +#define INV_MPU_QUATERNION_CHAN(_channel2, _index) \ + { \ + .type = IIO_QUATERNION, \ + .modified = 1, \ + .channel2 = _channel2, \ + .scan_index = _index, \ + .scan_type = IIO_ST('s', 32, 32, 0) \ + } + +#define INV_MPU_MAGN_CHAN(_channel2, _index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = _channel2, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .scan_index = _index, \ + .scan_type = IIO_ST('s', 16, 16, 0) \ + } + +static const struct iio_chan_spec inv_mpu_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU_SCAN_TIMESTAMP), + + INV_MPU_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU_SCAN_GYRO_X), + INV_MPU_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU_SCAN_GYRO_Y), + INV_MPU_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU_SCAN_GYRO_Z), + + INV_ACCL_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU_SCAN_ACCL_X), + INV_ACCL_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU_SCAN_ACCL_Y), + INV_ACCL_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU_SCAN_ACCL_Z), + + INV_MPU_QUATERNION_CHAN(IIO_MOD_R, INV_MPU_SCAN_QUAT_R), + INV_MPU_QUATERNION_CHAN(IIO_MOD_X, INV_MPU_SCAN_QUAT_X), + INV_MPU_QUATERNION_CHAN(IIO_MOD_Y, INV_MPU_SCAN_QUAT_Y), + INV_MPU_QUATERNION_CHAN(IIO_MOD_Z, INV_MPU_SCAN_QUAT_Z), + + INV_MPU_MAGN_CHAN(IIO_MOD_X, INV_MPU_SCAN_MAGN_X), + INV_MPU_MAGN_CHAN(IIO_MOD_Y, INV_MPU_SCAN_MAGN_Y), + INV_MPU_MAGN_CHAN(IIO_MOD_Z, INV_MPU_SCAN_MAGN_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_fifo_rate_store); +static DEVICE_ATTR(temperature, S_IRUGO, inv_temperature_show, NULL); +static IIO_DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, inv_attr_show, + inv_power_state_store, ATTR_POWER_STATE); +static IIO_DEVICE_ATTR(firmware_loaded, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_FIRMWARE_LOADED); +static IIO_DEVICE_ATTR(motion_lpa_freq, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_LPA_FREQ); +static IIO_DEVICE_ATTR(motion_lpa_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_MOTION_ON); +static IIO_DEVICE_ATTR(motion_lpa_duration, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_MOTION_DURATION); +static IIO_DEVICE_ATTR(motion_lpa_threshold, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_MOTION_THRESHOLD); +static DEVICE_ATTR(reg_dump, S_IRUGO, inv_reg_dump_show, NULL); +static IIO_DEVICE_ATTR(self_test, S_IRUGO, inv_attr_show, NULL, + ATTR_SELF_TEST); +static IIO_DEVICE_ATTR(gyro_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_GYRO_MATRIX); +static IIO_DEVICE_ATTR(accl_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_ACCL_MATRIX); +static IIO_DEVICE_ATTR(compass_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_COMPASS_MATRIX); +static IIO_DEVICE_ATTR(dmp_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_ON); +static IIO_DEVICE_ATTR(dmp_int_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_INT_ON); +static IIO_DEVICE_ATTR(dmp_event_int_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_EVENT_INT_ON); +static IIO_DEVICE_ATTR(dmp_output_rate, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_OUTPUT_RATE); +static IIO_DEVICE_ATTR(quaternion_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_DMP_QUATERNION_ON); +static IIO_DEVICE_ATTR(display_orientation_on, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_DISPLAY_ORIENTATION_ON); +static IIO_DEVICE_ATTR(tap_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_TAP_ON); +static IIO_DEVICE_ATTR(tap_time, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_TAP_TIME); +static IIO_DEVICE_ATTR(tap_min_count, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_TAP_MIN_COUNT); +static IIO_DEVICE_ATTR(tap_threshold, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_TAP_THRESHOLD); +static IIO_DEVICE_ATTR(pedometer_time, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_PEDOMETER_TIME); +static IIO_DEVICE_ATTR(pedometer_steps, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_PEDOMETER_STEPS); +static DEVICE_ATTR(event_tap, S_IRUGO, inv_dmp_tap_show, NULL); +static DEVICE_ATTR(event_display_orientation, S_IRUGO, + inv_dmp_display_orient_show, NULL); +static DEVICE_ATTR(event_accel_motion, S_IRUGO, inv_accel_motion_show, NULL); +static IIO_DEVICE_ATTR(gyro_enable, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_GYRO_ENABLE); +static IIO_DEVICE_ATTR(accl_enable, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_ACCL_ENABLE); +static IIO_DEVICE_ATTR(compass_enable, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_COMPASS_ENABLE); +#ifdef CONFIG_INV_TESTING +static IIO_DEVICE_ATTR(reg_write, S_IRUGO | S_IWUSR, inv_attr_show, + inv_reg_write_store, ATTR_REG_WRITE); +#endif + +static const struct attribute *inv_gyro_attributes[] = { + &iio_dev_attr_gyro_enable.dev_attr.attr, + &dev_attr_temperature.attr, + &iio_dev_attr_power_state.dev_attr.attr, + &dev_attr_reg_dump.attr, + &iio_dev_attr_self_test.dev_attr.attr, + &iio_dev_attr_gyro_matrix.dev_attr.attr, +#ifdef CONFIG_INV_TESTING + &iio_dev_attr_reg_write.dev_attr.attr, +#endif + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, +}; + +static const struct attribute *inv_mpu6050_attributes[] = { + &iio_dev_attr_accl_enable.dev_attr.attr, + &iio_dev_attr_accl_matrix.dev_attr.attr, + &iio_dev_attr_firmware_loaded.dev_attr.attr, + &iio_dev_attr_motion_lpa_freq.dev_attr.attr, + &iio_dev_attr_motion_lpa_on.dev_attr.attr, + &iio_dev_attr_motion_lpa_duration.dev_attr.attr, + &iio_dev_attr_motion_lpa_threshold.dev_attr.attr, + &iio_dev_attr_dmp_on.dev_attr.attr, + &iio_dev_attr_dmp_int_on.dev_attr.attr, + &iio_dev_attr_dmp_event_int_on.dev_attr.attr, + &iio_dev_attr_dmp_output_rate.dev_attr.attr, + &iio_dev_attr_quaternion_on.dev_attr.attr, + &iio_dev_attr_display_orientation_on.dev_attr.attr, + &iio_dev_attr_tap_on.dev_attr.attr, + &iio_dev_attr_tap_time.dev_attr.attr, + &iio_dev_attr_tap_min_count.dev_attr.attr, + &iio_dev_attr_tap_threshold.dev_attr.attr, + &iio_dev_attr_pedometer_time.dev_attr.attr, + &iio_dev_attr_pedometer_steps.dev_attr.attr, + &dev_attr_event_display_orientation.attr, + &dev_attr_event_tap.attr, + &dev_attr_event_accel_motion.attr, +}; + +static const struct attribute *inv_compass_attributes[] = { + &iio_dev_attr_compass_matrix.dev_attr.attr, + &iio_dev_attr_compass_enable.dev_attr.attr, +}; + +static const struct attribute *inv_mpu3050_attributes[] = { + &iio_dev_attr_accl_matrix.dev_attr.attr, + &iio_dev_attr_accl_enable.dev_attr.attr, +}; + +static struct attribute *inv_attributes[ARRAY_SIZE(inv_gyro_attributes) + + ARRAY_SIZE(inv_mpu6050_attributes) + + ARRAY_SIZE(inv_compass_attributes) + 1]; + +static const struct attribute_group inv_attribute_group = { + .name = "mpu", + .attrs = inv_attributes +}; + +static const struct iio_info mpu_info = { + .driver_module = THIS_MODULE, + .read_raw = &mpu_read_raw, + .write_raw = &mpu_write_raw, + .attrs = &inv_attribute_group, +}; + +/** + * inv_setup_compass() - Configure compass. + */ +static int inv_setup_compass(struct inv_mpu_iio_s *st) +{ + int result; + u8 data[4]; + + result = inv_i2c_read(st, REG_YGOFFS_TC, 1, data); + if (result) + return result; + data[0] &= ~BIT_I2C_MST_VDDIO; + if (st->plat_data.level_shifter) + data[0] |= BIT_I2C_MST_VDDIO; + /*set up VDDIO register */ + result = inv_i2c_single_write(st, REG_YGOFFS_TC, data[0]); + if (result) + return result; + /* set to bypass mode */ + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config | BIT_BYPASS_EN); + if (result) + return result; + /*read secondary i2c ID register */ + result = inv_secondary_read(REG_AKM_ID, 1, data); + if (result) + return result; + if (data[0] != DATA_AKM_ID) + return -ENXIO; + /*set AKM to Fuse ROM access mode */ + result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_FR); + if (result) + return result; + result = inv_secondary_read(REG_AKM_SENSITIVITY, THREE_AXIS, + st->chip_info.compass_sens); + if (result) + return result; + /*revert to power down mode */ + result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_PD); + if (result) + return result; + pr_debug("%s senx=%d, seny=%d, senz=%d\n", + st->hw->name, + st->chip_info.compass_sens[0], + st->chip_info.compass_sens[1], + st->chip_info.compass_sens[2]); + /*restore to non-bypass mode */ + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config); + if (result) + return result; + + /*setup master mode and master clock and ES bit*/ + result = inv_i2c_single_write(st, REG_I2C_MST_CTRL, BIT_WAIT_FOR_ES); + if (result) + return result; + /* slave 0 is used to read data from compass */ + /*read mode */ + result = inv_i2c_single_write(st, REG_I2C_SLV0_ADDR, BIT_I2C_READ| + st->plat_data.secondary_i2c_addr); + if (result) + return result; + /* AKM status register address is 2 */ + result = inv_i2c_single_write(st, REG_I2C_SLV0_REG, REG_AKM_STATUS); + if (result) + return result; + /* slave 0 is enabled at the beginning, read 8 bytes from here */ + result = inv_i2c_single_write(st, REG_I2C_SLV0_CTRL, BIT_SLV_EN | + NUM_BYTES_COMPASS_SLAVE); + if (result) + return result; + /*slave 1 is used for AKM mode change only*/ + result = inv_i2c_single_write(st, REG_I2C_SLV1_ADDR, + st->plat_data.secondary_i2c_addr); + if (result) + return result; + /* AKM mode register address is 0x0A */ + result = inv_i2c_single_write(st, REG_I2C_SLV1_REG, REG_AKM_MODE); + if (result) + return result; + /* slave 1 is enabled, byte length is 1 */ + result = inv_i2c_single_write(st, REG_I2C_SLV1_CTRL, BIT_SLV_EN | 1); + if (result) + return result; + /* output data for slave 1 is fixed, single measure mode*/ + st->compass_scale = 1; + if (COMPASS_ID_AK8975 == st->plat_data.sec_slave_id) { + st->compass_st_upper = AKM8975_ST_Upper; + st->compass_st_lower = AKM8975_ST_Lower; + data[0] = DATA_AKM_MODE_SM; + } else if (COMPASS_ID_AK8972 == st->plat_data.sec_slave_id) { + st->compass_st_upper = AKM8972_ST_Upper; + st->compass_st_lower = AKM8972_ST_Lower; + data[0] = DATA_AKM_MODE_SM; + } else if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id) { + st->compass_st_upper = AKM8963_ST_Upper; + st->compass_st_lower = AKM8963_ST_Lower; + data[0] = DATA_AKM_MODE_SM | + (st->compass_scale << AKM8963_SCALE_SHIFT); + } + result = inv_i2c_single_write(st, REG_I2C_SLV1_DO, data[0]); + if (result) + return result; + /* slave 0 and 1 timer action is enabled every sample*/ + result = inv_i2c_single_write(st, REG_I2C_MST_DELAY_CTRL, + BIT_SLV0_DLY_EN | BIT_SLV1_DLY_EN); + return result; +} + +static void inv_setup_func_ptr(struct inv_mpu_iio_s *st) +{ + if (st->chip_type == INV_MPU3050) { + st->set_power_state = set_power_mpu3050; + st->switch_gyro_engine = inv_switch_3050_gyro_engine; + st->switch_accl_engine = inv_switch_3050_accl_engine; + st->init_config = inv_init_config_mpu3050; + st->setup_reg = inv_setup_reg_mpu3050; + } else { + st->set_power_state = set_power_itg; + st->switch_gyro_engine = inv_switch_gyro_engine; + st->switch_accl_engine = inv_switch_accl_engine; + st->init_config = inv_init_config; + st->setup_reg = inv_setup_reg; + /*MPU6XXX special functions */ + st->compass_en = inv_compass_enable; + st->quaternion_en = inv_quaternion_on; + st->gyro_en = inv_gyro_enable; + st->accl_en = inv_accl_enable; + } +} + +static int inv_detect_6xxx(struct inv_mpu_iio_s *st) +{ + int result; + u8 d; + + result = inv_i2c_read(st, REG_WHOAMI, 1, &d); + if (result) + return result; + if (d == MPU6500_ID) { + st->chip_type = INV_MPU6500; + strcpy(st->name, "mpu6500"); + } else { + strcpy(st->name, "mpu6050"); + } + + return 0; +} + +/** + * inv_check_chip_type() - check and setup chip type. + */ +static int inv_check_chip_type(struct inv_mpu_iio_s *st, + const struct i2c_device_id *id) +{ + struct inv_reg_map_s *reg; + int result; + int t_ind; + + if (!strcmp(id->name, "itg3500")) + st->chip_type = INV_ITG3500; + else if (!strcmp(id->name, "mpu3050")) + st->chip_type = INV_MPU3050; + else if (!strcmp(id->name, "mpu6050")) + st->chip_type = INV_MPU6050; + else if (!strcmp(id->name, "mpu9150")) + st->chip_type = INV_MPU6050; + else if (!strcmp(id->name, "mpu6500")) + st->chip_type = INV_MPU6500; + else if (!strcmp(id->name, "mpu9250")) + st->chip_type = INV_MPU6500; + else if (!strcmp(id->name, "mpu6xxx")) + st->chip_type = INV_MPU6050; + else + return -EPERM; + inv_setup_func_ptr(st); + st->hw = &hw_info[st->chip_type]; + st->mpu_slave = NULL; + reg = &st->reg; + st->setup_reg(reg); + st->chip_config.gyro_enable = 1; + /* reset to make sure previous state are not there */ + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, BIT_H_RESET); + if (result) + return result; + msleep(POWER_UP_TIME); + + /* turn off and turn on power to ensure gyro engine is on */ + result = st->set_power_state(st, false); + if (result) + return result; + result = st->set_power_state(st, true); + if (result) + return result; + + if (!strcmp(id->name, "mpu6xxx")) { + result = inv_detect_6xxx(st); + if (result) + return result; + } + + switch (st->chip_type) { + case INV_ITG3500: + st->num_channels = INV_CHANNEL_NUM_GYRO; + break; + case INV_MPU6050: + case INV_MPU6500: + if (SECONDARY_SLAVE_TYPE_COMPASS == + st->plat_data.sec_slave_type) { + st->chip_config.has_compass = 1; + st->num_channels = + INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION_MAGN; + } else { + st->chip_config.has_compass = 0; + st->num_channels = + INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION; + } + break; + case INV_MPU3050: + if (SECONDARY_SLAVE_TYPE_ACCEL == + st->plat_data.sec_slave_type) { + if (ACCEL_ID_BMA250 == st->plat_data.sec_slave_id) + inv_register_mpu3050_slave(st); + st->num_channels = INV_CHANNEL_NUM_GYRO_ACCL; + } else { + st->num_channels = INV_CHANNEL_NUM_GYRO; + } + break; + default: + result = st->set_power_state(st, false); + return -ENODEV; + } + + switch (st->chip_type) { + case INV_MPU6050: + result = inv_get_silicon_rev_mpu6050(st); + break; + case INV_MPU6500: + result = inv_get_silicon_rev_mpu6500(st); + break; + default: + result = 0; + break; + } + if (result) { + pr_err("read silicon rev error\n"); + st->set_power_state(st, false); + return result; + } + +//wlg bypass i2c slave + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config | BIT_BYPASS_EN); + if (result) + return result; + + if (st->chip_config.has_compass) { + result = inv_setup_compass(st); + if (result) { + pr_err("compass setup failed\n"); + st->set_power_state(st, false); + return result; + } + } + + t_ind = 0; + memcpy(&inv_attributes[t_ind], inv_gyro_attributes, + sizeof(inv_gyro_attributes)); + t_ind += ARRAY_SIZE(inv_gyro_attributes); + + if (INV_MPU3050 == st->chip_type && st->mpu_slave != NULL) { + memcpy(&inv_attributes[t_ind], inv_mpu3050_attributes, + sizeof(inv_mpu3050_attributes)); + t_ind += ARRAY_SIZE(inv_mpu3050_attributes); + inv_attributes[t_ind] = NULL; + return 0; + } + + if ((INV_MPU6050 == st->chip_type) || (INV_MPU6500 == st->chip_type)) { + memcpy(&inv_attributes[t_ind], inv_mpu6050_attributes, + sizeof(inv_mpu6050_attributes)); + t_ind += ARRAY_SIZE(inv_mpu6050_attributes); + } + + if (st->chip_config.has_compass) { + memcpy(&inv_attributes[t_ind], inv_compass_attributes, + sizeof(inv_compass_attributes)); + t_ind += ARRAY_SIZE(inv_compass_attributes); + } + inv_attributes[t_ind] = NULL; + + return 0; +} + +/** + * inv_create_dmp_sysfs() - create binary sysfs dmp entry. + */ +static const struct bin_attribute dmp_firmware = { + .attr = { + .name = "dmp_firmware", + .mode = S_IRUGO | S_IWUSR + }, + .size = 4096, + .read = inv_dmp_firmware_read, + .write = inv_dmp_firmware_write, +}; + +static int inv_create_dmp_sysfs(struct iio_dev *ind) +{ + int result; + result = sysfs_create_bin_file(&ind->dev.kobj, &dmp_firmware); + + return result; +} +static void inv_mpu_hw_init(struct i2c_client *client) +{ + struct regulator *reg_vdd; + struct mpu_platform_data *pdata = client->dev.platform_data; + reg_vdd = regulator_get(&client->dev, pdata->vdd_name); + if (!WARN(IS_ERR(reg_vdd), "inv_mpu regulator: failed to get %s.\n", pdata->vdd_name)) { + regulator_set_voltage(reg_vdd, 1800000, 1800000); + regulator_enable(reg_vdd); + } + msleep(100); +} + +/** + * inv_mpu_probe() - probe function. + */ +static int inv_mpu_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct inv_mpu_iio_s *st; + struct iio_dev *indio_dev; + int result; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENOSYS; + pr_err("I2c function error\n"); + goto out_no_free; + } + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + pr_err("memory allocation failed\n"); + result = -ENOMEM; + goto out_no_free; + } + st = iio_priv(indio_dev); + st->client = client; + st->sl_handle = client->adapter; + st->i2c_addr = client->addr; + st->plat_data = + *(struct mpu_platform_data *)dev_get_platdata(&client->dev); + inv_mpu_hw_init(client); + + /* power is turned on inside check chip type*/ + result = inv_check_chip_type(st, id); + if (result) + goto out_free; + + result = st->init_config(indio_dev); + if (result) { + dev_err(&client->adapter->dev, + "Could not initialize device.\n"); + goto out_free; + } + result = st->set_power_state(st, false); + if (result) { + dev_err(&client->adapter->dev, + "%s could not be turned off.\n", st->hw->name); + goto out_free; + } + /* Make state variables available to all _show and _store functions. */ + i2c_set_clientdata(client, indio_dev); + indio_dev->dev.parent = &client->dev; + if (!strcmp(id->name, "mpu6xxx")) + indio_dev->name = st->name; + else + indio_dev->name = id->name; + indio_dev->channels = inv_mpu_channels; + indio_dev->num_channels = st->num_channels; + + indio_dev->info = &mpu_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->currentmode = INDIO_DIRECT_MODE; + + result = inv_mpu_configure_ring(indio_dev); + if (result) { + pr_err("configure ring buffer fail\n"); + goto out_free; + } + result = iio_buffer_register(indio_dev, indio_dev->channels, + indio_dev->num_channels); + if (result) { + pr_err("ring buffer register fail\n"); + goto out_unreg_ring; + } + st->irq = gpio_to_irq(client->irq); + result = inv_mpu_probe_trigger(indio_dev); + if (result) { + pr_err("trigger probe fail\n"); + goto out_remove_ring; + } + + /* Tell the i2c counter, we have an IRQ */ + INV_I2C_SETIRQ(MPU, gpio_to_irq(client->irq)); + + result = iio_device_register(indio_dev); + if (result) { + pr_err("IIO device register fail\n"); + goto out_remove_trigger; + } + + if (INV_MPU6050 == st->chip_type || + INV_MPU6500 == st->chip_type) { + result = inv_create_dmp_sysfs(indio_dev); + if (result) { + pr_err("create dmp sysfs failed\n"); + goto out_unreg_iio; + } + } + + INIT_KFIFO(st->timestamps); + spin_lock_init(&st->time_stamp_lock); + dev_info(&client->adapter->dev, "%s is ready to go!\n", + indio_dev->name); + + return 0; +out_unreg_iio: + iio_device_unregister(indio_dev); +out_remove_trigger: + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + inv_mpu_remove_trigger(indio_dev); +out_remove_ring: + iio_buffer_unregister(indio_dev); +out_unreg_ring: + inv_mpu_unconfigure_ring(indio_dev); +out_free: + iio_device_free(indio_dev); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + + return -EIO; +} + +static void inv_mpu_shutdown(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + struct inv_reg_map_s *reg; + int result; + + reg = &st->reg; + dev_dbg(&client->adapter->dev, "Shutting down %s...\n", st->hw->name); + + /* reset to make sure previous state are not there */ + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, BIT_H_RESET); + if (result) + dev_err(&client->adapter->dev, "Failed to reset %s\n", + st->hw->name); + msleep(POWER_UP_TIME); + /* turn off power to ensure gyro engine is off */ + result = st->set_power_state(st, false); + if (result) + dev_err(&client->adapter->dev, "Failed to turn off %s\n", + st->hw->name); +} + +/** + * inv_mpu_remove() - remove function. + */ +static int inv_mpu_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + kfifo_free(&st->timestamps); + iio_device_unregister(indio_dev); + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + inv_mpu_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + inv_mpu_unconfigure_ring(indio_dev); + iio_device_free(indio_dev); + + dev_info(&client->adapter->dev, "inv-mpu-iio module removed.\n"); + + return 0; +} + +#ifdef CONFIG_PM +static int inv_mpu_resume(struct device *dev) +{ + struct inv_mpu_iio_s *st = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + pr_debug("%s inv_mpu_resume\n", st->hw->name); + return st->set_power_state(st, true); +} + +static int inv_mpu_suspend(struct device *dev) +{ + struct inv_mpu_iio_s *st = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + pr_debug("%s inv_mpu_suspend\n", st->hw->name); + return st->set_power_state(st, false); +} +static const struct dev_pm_ops inv_mpu_pmops = { + SET_SYSTEM_SLEEP_PM_OPS(inv_mpu_suspend, inv_mpu_resume) +}; +#define INV_MPU_PMOPS (&inv_mpu_pmops) +#else +#define INV_MPU_PMOPS NULL +#endif /* CONFIG_PM */ + +static const u16 normal_i2c[] = { I2C_CLIENT_END }; +/* device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_mpu_id[] = { + {"itg3500", INV_ITG3500}, + {"mpu3050", INV_MPU3050}, + {"mpu6050", INV_MPU6050}, + {"mpu9150", INV_MPU9150}, + {"mpu6500", INV_MPU6500}, + {"mpu9250", INV_MPU9250}, + {"mpu6xxx", INV_MPU6XXX}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, inv_mpu_id); + +static struct i2c_driver inv_mpu_driver = { + .class = I2C_CLASS_HWMON, + .probe = inv_mpu_probe, + .remove = inv_mpu_remove, + .shutdown = inv_mpu_shutdown, + .id_table = inv_mpu_id, + .driver = { + .owner = THIS_MODULE, + .name = "inv-mpu-iio", + .pm = INV_MPU_PMOPS, + }, + .address_list = normal_i2c, +}; + +static int __init inv_mpu_init(void) +{ + int result = i2c_add_driver(&inv_mpu_driver); + if (result) { + pr_err("failed\n"); + return result; + } + return 0; +} + +static void __exit inv_mpu_exit(void) +{ + i2c_del_driver(&inv_mpu_driver); +} + +module_init(inv_mpu_init); +module_exit(inv_mpu_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("inv-mpu-iio"); + +/** + * @} + */ diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu/inv_mpu_iio.h new file mode 100644 index 00000000..28c541ea --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_iio.h @@ -0,0 +1,861 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_mpu_iio.h + * @brief Struct definitions for the Invensense mpu driver. + */ + +#ifndef _INV_MPU_IIO_H_ +#define _INV_MPU_IIO_H_ + +#include <linux/i2c.h> +#include <linux/kfifo.h> +#include <linux/miscdevice.h> +#include <linux/spinlock.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/mpu.h> +#include <linux/iio/types.h> +#include <linux/input.h> + +#include "dmpKey.h" + +/** + * struct inv_reg_map_s - Notable slave registers. + * @sample_rate_div: Divider applied to gyro output rate. + * @lpf: Configures internal LPF. + * @bank_sel: Selects between memory banks. + * @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. + * @int_status: Interrupt flags. + * @pwr_mgmt_1: Controls chip's power state and clock source. + * @pwr_mgmt_2: Controls power state of individual sensors. + * @mem_start_addr: Address of first memory read. + * @mem_r_w: Access to memory. + * @prgm_strt_addrh firmware program start address register + */ +struct inv_reg_map_s { + u8 sample_rate_div; + u8 lpf; + u8 bank_sel; + 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 int_status; + u8 pwr_mgmt_1; + u8 pwr_mgmt_2; + u8 mem_start_addr; + u8 mem_r_w; + u8 prgm_strt_addrh; +}; +/*device enum */ +enum inv_devices { + INV_ITG3500, + INV_MPU3050, + INV_MPU6050, + INV_MPU9150, + INV_MPU6500, + INV_MPU9250, + INV_MPU6XXX, + INV_NUM_PARTS +}; + +/** + * struct test_setup_t - set up parameters for self test. + * @gyro_sens: sensitity for gyro. + * @sample_rate: sample rate, i.e, fifo rate. + * @lpf: low pass filter. + * @fsr: full scale range. + * @accl_fs: accel full scale range. + * @accl_sens: accel sensitivity + */ +struct test_setup_t { + int gyro_sens; + int sample_rate; + int lpf; + int fsr; + int accl_fs; + u32 accl_sens[3]; +}; + +/** + * struct inv_hw_s - Other important hardware information. + * @num_reg: Number of registers on device. + * @name: name of the chip + */ +struct inv_hw_s { + u8 num_reg; + u8 *name; +}; + +/** + * struct inv_chip_config_s - Cached chip configuration data. + * @fsr: Full scale range. + * @lpf: Digital low pass filter frequency. + * @accl_fs: accel full scale range. + * @self_test_run_once flag for self test run ever. + * @has_footer: MPU3050 specific work around. + * @has_compass: has compass or not. + * @enable: master enable to enable output + * @accl_enable: enable accel functionality + * @accl_fifo_enable: enable accel data output + * @gyro_enable: enable gyro functionality + * @gyro_fifo_enable: enable gyro data output + * @compass_enable: enable compass + * @compass_fifo_enable: enable compass data output + * @is_asleep: 1 if chip is powered down. + * @dmp_on: dmp is on/off. + * @dmp_int_on: dmp interrupt on/off. + * @dmp_event_int_on: dmp event interrupt on/off. + * @firmware_loaded: flag indicate firmware loaded or not. + * @lpa_mod: low power mode. + * @tap_on: tap on/off. + * @flick_int_on: flick interrupt on/off. + * @quaternion_on: send quaternion data on/off. + * @display_orient_on: display orientation on/off. + * @normal_compass_measure: discard first compass data after reset. + * @lpa_freq: low power frequency + * @prog_start_addr: firmware program start address. + * @dmp_output_rate: dmp output rate. + * @fifo_rate: FIFO update rate. + */ +struct inv_chip_config_s { + u32 fsr:2; + u32 lpf:3; + u32 accl_fs:2; + u32 self_test_run_once:1; + u32 has_footer:1; + u32 has_compass:1; + u32 enable:1; + u32 accl_enable:1; + u32 accl_fifo_enable:1; + u32 gyro_enable:1; + u32 gyro_fifo_enable:1; + u32 compass_enable:1; + u32 compass_fifo_enable:1; + u32 is_asleep:1; + u32 dmp_on:1; + u32 dmp_int_on:1; + u32 dmp_event_int_on:1; + u32 firmware_loaded:1; + u32 lpa_mode:1; + u32 tap_on:1; + u32 flick_int_on:1; + u32 quaternion_on:1; + u32 display_orient_on:1; + u32 normal_compass_measure:1; + u16 lpa_freq; + u16 prog_start_addr; + u16 fifo_rate; + u16 dmp_output_rate; +}; + +/** + * struct inv_chip_info_s - Chip related information. + * @product_id: Product id. + * @product_revision: Product revision. + * @silicon_revision: Silicon revision. + * @software_revision: software revision. + * @multi: accel specific multiplier. + * @compass_sens: compass sensitivity. + * @gyro_sens_trim: Gyro sensitivity trim factor. + * @accl_sens_trim: accel sensitivity trim factor. + */ +struct inv_chip_info_s { + u8 product_id; + u8 product_revision; + u8 silicon_revision; + u8 software_revision; + u8 multi; + u8 compass_sens[3]; + u32 gyro_sens_trim; + u32 accl_sens_trim; +}; + +/** + * struct inv_flick_s structure to store flick data. + * @lower: lower bound of flick. + * @upper: upper bound of flick. + * @counter: counter of flick. + * @msg_on; message to carry flick + * @axis: axis of flick + */ +struct inv_flick_s { + int lower; + int upper; + int counter; + int msg_on; + int axis; +}; + +enum inv_channel_num { + INV_CHANNEL_NUM_GYRO = 4, + INV_CHANNEL_NUM_GYRO_ACCL = 7, + INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION = 11, + INV_CHANNEL_NUM_GYRO_ACCL_QUANTERNION_MAGN = 14, +}; + +/** + * struct inv_tap_s structure to store tap data. + * @min_count: minimum taps counted. + * @thresh: tap threshold. + * @time: tap time. + */ +struct inv_tap_s { + u16 min_count; + u16 thresh; + u16 time; +}; + +/** + * struct accel_mot_int_s structure to store motion interrupt data + * @mot_thr: motion threshold. + * @mot_dur: motion duration. + * @mot_on: flag to indicate motion detection on; + */ +struct accel_mot_int_s { + u16 mot_thr; + u32 mot_dur; + u8 mot_on:1; +}; + +struct inv_mpu_slave; +/** + * struct inv_mpu_iio_s - Driver state variables. + * @chip_config: Cached attribute information. + * @chip_info: Chip information from read-only registers. + * @trig; iio trigger. + * @flick: flick data structure + * @tap: tap data structure + * @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. + * @mpu_slave: mpu slave handle. + * (*set_power_state)(struct inv_mpu_iio_s *, int on): function ptr + * (*switch_gyro_engine)(struct inv_mpu_iio_s *, int on): function ptr + * (*switch_accl_engine)(struct inv_mpu_iio_s *, int on): function ptr + * (*compass_en)(struct inv_mpu_iio_s *, struct iio_buffer *, bool); + * (*quaternion_en)(struct inv_mpu_iio_s *, struct iio_buffer *, bool) + * (*gyro_en)(struct inv_mpu_iio_s *, struct iio_buffer *, bool): func ptr. + * (*accl_en)(struct inv_mpu_iio_s *, struct iio_buffer *, bool): func ptr. + * (*init_config)(struct iio_dev *indio_dev): function ptr + * void (*setup_reg)(struct inv_reg_map_s *reg): function ptr + * @timestamps: kfifo queue to store time stamp. + * @compass_st_upper: compass self test upper limit. + * @compass_st_lower: compass self test lower limit. + * @irq: irq number store. + * @accel_bias: accel bias store. + * @gyro_bias: gyro bias store. + * @raw_gyro: raw gyro data. + * @raw_accel: raw accel data. + * @raw_compass: raw compass. + * @raw_quaternion raw quaternion data. + * @compass_scale: compass scale. + * @i2c_addr: i2c address. + * @compass_divider: slow down compass rate. + * @compass_dmp_divider: slow down compass rate for dmp. + * @compass_counter: slow down compass rate. + * @sample_divider: sample divider for dmp. + * @fifo_divider: fifo divider for dmp. + * @display_orient_data:display orient data. + * @tap_data: tap data. + * @num_channels: number of channels for current chip. + * @sl_handle: Handle to I2C port. + * @irq_dur_ns: duration between each irq. + * @last_isr_time: last isr time. + * @mpu6500_last_motion_time: MPU6500 last real motion interrupt time. + * @name: name for distiguish MPU6050 and MPU6500 in MPU6XXX. + */ +struct inv_mpu_iio_s { +#define TIMESTAMP_FIFO_SIZE 16 + struct inv_chip_config_s chip_config; + struct inv_chip_info_s chip_info; + struct iio_trigger *trig; + struct inv_flick_s flick; + struct inv_tap_s tap; + struct inv_reg_map_s reg; + const struct inv_hw_s *hw; + enum inv_devices chip_type; + spinlock_t time_stamp_lock; + struct i2c_client *client; + struct mpu_platform_data plat_data; + struct inv_mpu_slave *mpu_slave; + struct accel_mot_int_s mot_int; + int (*set_power_state)(struct inv_mpu_iio_s *, bool on); + int (*switch_gyro_engine)(struct inv_mpu_iio_s *, bool on); + int (*switch_accl_engine)(struct inv_mpu_iio_s *, bool on); + int (*compass_en)(struct inv_mpu_iio_s *, + struct iio_buffer *ring, bool on); + int (*quaternion_en)(struct inv_mpu_iio_s *, + struct iio_buffer *ring, bool on); + int (*gyro_en)(struct inv_mpu_iio_s *, + struct iio_buffer *ring, bool on); + int (*accl_en)(struct inv_mpu_iio_s *, + struct iio_buffer *ring, bool on); + int (*init_config)(struct iio_dev *indio_dev); + void (*setup_reg)(struct inv_reg_map_s *reg); + DECLARE_KFIFO(timestamps, u64, TIMESTAMP_FIFO_SIZE); + const short *compass_st_upper; + const short *compass_st_lower; + short irq; + int accel_bias[3]; + int gyro_bias[3]; + short raw_gyro[3]; + short raw_accel[3]; + short raw_compass[3]; + int raw_quaternion[4]; + int input_accel_bias[3]; + u8 compass_scale; + u8 i2c_addr; + u8 compass_divider; + u8 compass_counter; + u8 compass_dmp_divider; + u8 sample_divider; + u8 fifo_divider; + u8 display_orient_data; + u8 tap_data; + enum inv_channel_num num_channels; + void *sl_handle; + u32 irq_dur_ns; + u64 last_isr_time; + u64 mpu6500_last_motion_time; + u8 name[20]; +}; + +/* produces an unique identifier for each device based on the + combination of product version and product revision */ +struct prod_rev_map_t { + u16 mpl_product_key; + u8 silicon_rev; + u16 gyro_trim; + u16 accel_trim; +}; + +/** + * struct inv_mpu_slave - MPU slave structure. + * @suspend: suspend operation. + * @resume: resume operation. + * @setup: setup chip. initialization. + * @combine_data: combine raw data into meaningful data. + * @get_mode: get current chip mode. + * @set_lpf set low pass filter. + * @set_fs set full scale + */ +struct inv_mpu_slave { + int (*suspend)(struct inv_mpu_iio_s *); + int (*resume)(struct inv_mpu_iio_s *); + int (*setup)(struct inv_mpu_iio_s *); + int (*combine_data)(u8 *in, short *out); + int (*get_mode)(void); + int (*set_lpf)(struct inv_mpu_iio_s *, int rate); + int (*set_fs)(struct inv_mpu_iio_s *, int fs); +}; + +/* AKM definitions */ +#define REG_AKM_ID 0x00 +#define REG_AKM_STATUS 0x02 +#define REG_AKM_MEASURE_DATA 0x03 +#define REG_AKM_MODE 0x0A +#define REG_AKM_ST_CTRL 0x0C +#define REG_AKM_SENSITIVITY 0x10 +#define REG_AKM8963_CNTL1 0x0A + +#define DATA_AKM_ID 0x48 +#define DATA_AKM_MODE_PD 0x00 +#define DATA_AKM_MODE_SM 0x01 +#define DATA_AKM_MODE_ST 0x08 +#define DATA_AKM_MODE_FR 0x0F +#define DATA_AKM_SELF_TEST 0x40 +#define DATA_AKM_DRDY 0x01 +#define DATA_AKM8963_BIT 0x10 +#define DATA_AKM_STAT_MASK 0x0C + +#define DATA_AKM8975_SCALE (9830 * (1L << 15)) +#define DATA_AKM8972_SCALE (19661 * (1L << 15)) +#define DATA_AKM8963_SCALE0 (19661 * (1L << 15)) +#define DATA_AKM8963_SCALE1 (4915 * (1L << 15)) +#define AKM8963_SCALE_SHIFT 4 +#define NUM_BYTES_COMPASS_SLAVE 8 + +/*register and associated bit definition*/ +#define REG_3050_FIFO_EN 0x12 +#define BITS_3050_ACCL_OUT 0x0E + +#define REG_3050_AUX_VDDIO 0x13 +#define BIT_3050_VDDIO 0x04 + +#define REG_3050_SLAVE_ADDR 0x14 +#define REG_3050_SAMPLE_RATE_DIV 0x15 +#define REG_3050_LPF 0x16 +#define REG_3050_INT_ENABLE 0x17 +#define REG_3050_AUX_BST_ADDR 0x18 +#define REG_3050_INT_STATUS 0x1A +#define REG_3050_TEMPERATURE 0x1B +#define REG_3050_RAW_GYRO 0x1D +#define REG_3050_AUX_XOUT_H 0x23 +#define REG_3050_FIFO_COUNT_H 0x3A +#define REG_3050_FIFO_R_W 0x3C + +#define REG_3050_USER_CTRL 0x3D +#define BIT_3050_AUX_IF_EN 0x20 +#define BIT_3050_FIFO_RST 0x02 + +#define REG_3050_PWR_MGMT_1 0x3E +#define BITS_3050_POWER1 0x30 +#define BITS_3050_POWER2 0x10 +#define BITS_3050_GYRO_STANDBY 0x38 + +#define REG_3500_OTP 0x0 + +#define REG_YGOFFS_TC 0x1 +#define BIT_I2C_MST_VDDIO 0x80 + +#define REG_XA_OFFS_L_TC 0x7 +#define REG_PRODUCT_ID 0xC +#define REG_ST_GCT_X 0xD +#define REG_SAMPLE_RATE_DIV 0x19 +#define REG_CONFIG 0x1A + +#define REG_GYRO_CONFIG 0x1B +#define BITS_SELF_TEST_EN 0xE0 + +#define REG_ACCEL_CONFIG 0x1C +#define REG_ACCEL_MOT_THR 0x1F +#define REG_ACCEL_MOT_DUR 0x20 + +#define REG_FIFO_EN 0x23 +#define BIT_ACCEL_OUT 0x08 +#define BITS_GYRO_OUT 0x70 + + +#define REG_I2C_MST_CTRL 0x24 +#define BIT_WAIT_FOR_ES 0x40 + +#define REG_I2C_SLV0_ADDR 0x25 +#define BIT_I2C_READ 0x80 + +#define REG_I2C_SLV0_REG 0x26 + +#define REG_I2C_SLV0_CTRL 0x27 +#define BIT_SLV_EN 0x80 + +#define REG_I2C_SLV1_ADDR 0x28 +#define REG_I2C_SLV1_REG 0x29 +#define REG_I2C_SLV1_CTRL 0x2A +#define REG_I2C_SLV4_CTRL 0x34 + +#define REG_INT_PIN_CFG 0x37 +#define BIT_BYPASS_EN 0x2 + +#define REG_INT_ENABLE 0x38 +#define BIT_DATA_RDY_EN 0x01 +#define BIT_DMP_INT_EN 0x02 +#define BIT_ZMOT_EN 0x20 +#define BIT_MOT_EN 0x40 +#define BIT_6500_WOM_EN 0x40 + +#define REG_DMP_INT_STATUS 0x39 + +#define REG_INT_STATUS 0x3A +#define BIT_MOT_INT 0x40 +#define BIT_ZMOT_INT 0x20 + +#define REG_RAW_ACCEL 0x3B +#define REG_TEMPERATURE 0x41 +#define REG_RAW_GYRO 0x43 +#define REG_EXT_SENS_DATA_00 0x49 + +#define REG_ACCEL_INTEL_STATUS 0x61 + +#define REG_I2C_SLV1_DO 0x64 + +#define REG_I2C_MST_DELAY_CTRL 0x67 +#define BIT_SLV0_DLY_EN 0x01 +#define BIT_SLV1_DLY_EN 0x02 + +#define REG_USER_CTRL 0x6A +#define BIT_FIFO_RST 0x04 +#define BIT_DMP_RST 0x08 +#define BIT_I2C_MST_EN 0x20 +#define BIT_FIFO_EN 0x40 +#define BIT_DMP_EN 0x80 + +#define REG_PWR_MGMT_1 0x6B +#define BIT_H_RESET 0x80 +#define BIT_SLEEP 0x40 +#define BIT_CYCLE 0x20 +#define BIT_CLK_MASK 0x7 + +#define REG_PWR_MGMT_2 0x6C +#define BIT_PWR_ACCL_STBY 0x38 +#define BIT_PWR_GYRO_STBY 0x07 +#define BIT_LPA_FREQ 0xC0 + +#define REG_BANK_SEL 0x6D +#define REG_MEM_START_ADDR 0x6E +#define REG_MEM_RW 0x6F +#define REG_PRGM_STRT_ADDRH 0x70 +#define REG_FIFO_COUNT_H 0x72 +#define REG_FIFO_R_W 0x74 +#define REG_WHOAMI 0x75 + +#define REG_6500_ACCEL_CONFIG2 0x1D +#define BIT_ACCEL_FCHOCIE_B 0x08 + +#define REG_6500_LP_ACCEL_ODR 0x1E +#define REG_6500_ACCEL_WOM_THR 0x1F + +#define REG_6500_ACCEL_INTEL_CTRL 0x69 +#define BIT_ACCEL_INTEL_ENABLE 0x80 +#define BIT_ACCEL_INTEL_MODE 0x40 + +/* data definitions */ +#define DMP_START_ADDR 0x400 +#define DMP_MASK_TAP 0x3f +#define DMP_MASK_DIS_ORIEN 0xC0 +#define DMP_DIS_ORIEN_SHIFT 6 + +#define BYTES_FOR_DMP 16 +#define BYTES_FOR_EVENTS 4 +#define QUATERNION_BYTES 16 +#define BYTES_PER_SENSOR 6 +#define MPU3050_FOOTER_SIZE 2 +#define FIFO_COUNT_BYTE 2 +#define FIFO_THRESHOLD 500 +#define POWER_UP_TIME 100 +#define SENSOR_UP_TIME 30 +#define MPU_MEM_BANK_SIZE 256 +#define MPU3050_TEMP_OFFSET 5383314L +#define MPU3050_TEMP_SCALE 3834792L +#define MPU6050_TEMP_OFFSET 2462307L +#define MPU6050_TEMP_SCALE 2977653L +#define MPU6XXX_MAX_MOTION_THRESH (255*4) +#define MPU6XXX_MOTION_THRESH_SHIFT 5 +#define MPU6050_MOTION_DUR_DEFAULT 1 +#define MPU6050_ID 0x68 +#define MPU6050_MAX_MOTION_DUR 255 +#define MPU_TEMP_SHIFT 16 +#define LPA_FREQ_SHIFT 6 +#define COMPASS_RATE_SCALE 10 +#define MAX_GYRO_FS_PARAM 3 +#define MAX_ACCL_FS_PARAM 3 +#define MAX_LPA_FREQ_PARAM 3 + +#define INIT_MOT_DUR 128 +#define INIT_MOT_THR 128 +#define INIT_ZMOT_DUR 128 +#define INIT_ZMOT_THR 128 + +/*---- MPU6500 ----*/ +#define MPU6500_ID 0x70 /* unique WHOAMI */ +#define MPU6500_PRODUCT_REVISION 1 +#define MPU6500_MEM_REV_ADDR 0x17 +#define MPU6500_REV 2 + +/*---- MPU9250 ----*/ +#define MPU9250_ID 0x71 /* unique WHOAMI */ + +#define THREE_AXIS 3 +#define GYRO_CONFIG_FSR_SHIFT 3 +#define ACCL_CONFIG_FSR_SHIFT 3 +#define GYRO_DPS_SCALE 250 +#define MEM_ADDR_PROD_REV 0x6 +#define SOFT_PROD_VER_BYTES 5 +#define CRC_FIRMWARE_SEED 0 +#define SELF_TEST_SUCCESS 1 +#define MS_PER_DMP_TICK 20 + +/* init parameters */ +#define INIT_FIFO_RATE 50 +#define INIT_DMP_OUTPUT_RATE 25 +#define INIT_DUR_TIME ((1000 / INIT_FIFO_RATE) * 1000 * 1000) +#define INIT_TAP_THRESHOLD 100 +#define INIT_TAP_TIME 100 +#define INIT_TAP_MIN_COUNT 2 +#define MPL_PROD_KEY(ver, rev) (ver * 100 + rev) +#define NUM_OF_PROD_REVS (ARRAY_SIZE(prod_rev_map)) +/*---- MPU6050 Silicon Revisions ----*/ +#define MPU_SILICON_REV_A2 1 /* MPU6050A2 Device */ +#define MPU_SILICON_REV_B1 2 /* MPU6050B1 Device */ + +#define BIT_PRFTCH_EN 0x40 +#define BIT_CFG_USER_BANK 0x20 +#define BITS_MEM_SEL 0x1f + +#define TIME_STAMP_TOR 5 +#define MAX_CATCH_UP 5 +#define DEFAULT_ACCL_TRIM 16384 +#define DEFAULT_GYRO_TRIM 131 +#define MAX_FIFO_RATE 1000 +#define MIN_FIFO_RATE 4 +#define ONE_K_HZ 1000 +#define NS_PER_MS_SHIFT 20 + +/* flick related defines */ +#define DATA_INT 2097 +#define DATA_MSG_ON 262144 +#define FLICK_INT_STATUS 8 + +/*tap related defines */ +#define INV_TAP 0x08 +#define INV_NUM_TAP_AXES 3 + +#define INV_TAP_AXIS_X_POS 0x20 +#define INV_TAP_AXIS_X_NEG 0x10 +#define INV_TAP_AXIS_Y_POS 0x08 +#define INV_TAP_AXIS_Y_NEG 0x04 +#define INV_TAP_AXIS_Z_POS 0x02 +#define INV_TAP_AXIS_Z_NEG 0x01 +#define INV_TAP_ALL_DIRECTIONS 0x3f + +#define INV_TAP_AXIS_X 0x1 +#define INV_TAP_AXIS_Y 0x2 +#define INV_TAP_AXIS_Z 0x4 + +#define INV_TAP_AXIS_ALL \ + (INV_TAP_AXIS_X | \ + INV_TAP_AXIS_Y | \ + INV_TAP_AXIS_Z) + +#define INT_SRC_TAP 0x01 +#define INT_SRC_DISPLAY_ORIENT 0x08 +#define INT_SRC_SHAKE 0x10 + +#define INV_X_AXIS_INDEX 0x00 +#define INV_Y_AXIS_INDEX 0x01 +#define INV_Z_AXIS_INDEX 0x02 + +#define INV_ELEMENT_1 0x0001 +#define INV_ELEMENT_2 0x0002 +#define INV_ELEMENT_3 0x0004 +#define INV_ELEMENT_4 0x0008 +#define INV_ELEMENT_5 0x0010 +#define INV_ELEMENT_6 0x0020 +#define INV_ELEMENT_7 0x0040 +#define INV_ELEMENT_8 0x0080 +#define INV_ALL 0xFFFF +#define INV_ELEMENT_MASK 0x00FF +#define INV_GYRO_ACC_MASK 0x007E +#define INV_ACCL_MASK 0x70 +#define INV_GYRO_MASK 0xE +/* scan element definition */ +enum inv_mpu_scan { + INV_MPU_SCAN_QUAT_R = 0, + INV_MPU_SCAN_QUAT_X, + INV_MPU_SCAN_QUAT_Y, + INV_MPU_SCAN_QUAT_Z, + INV_MPU_SCAN_GYRO_X, + INV_MPU_SCAN_GYRO_Y, + INV_MPU_SCAN_GYRO_Z, + INV_MPU_SCAN_ACCL_X, + INV_MPU_SCAN_ACCL_Y, + INV_MPU_SCAN_ACCL_Z, + INV_MPU_SCAN_MAGN_X, + INV_MPU_SCAN_MAGN_Y, + INV_MPU_SCAN_MAGN_Z, + INV_MPU_SCAN_TIMESTAMP, +}; + +enum inv_filter_e { + INV_FILTER_256HZ_NOLPF2 = 0, + INV_FILTER_188HZ, + INV_FILTER_98HZ, + INV_FILTER_42HZ, + INV_FILTER_20HZ, + INV_FILTER_10HZ, + INV_FILTER_5HZ, + INV_FILTER_2100HZ_NOLPF, + NUM_FILTER +}; + +enum inv_slave_mode { + INV_MODE_SUSPEND, + INV_MODE_NORMAL, +}; + +/*==== MPU6050B1 MEMORY ====*/ +enum MPU_MEMORY_BANKS { + MEM_RAM_BANK_0 = 0, + MEM_RAM_BANK_1, + MEM_RAM_BANK_2, + MEM_RAM_BANK_3, + MEM_RAM_BANK_4, + MEM_RAM_BANK_5, + MEM_RAM_BANK_6, + MEM_RAM_BANK_7, + MEM_RAM_BANK_8, + MEM_RAM_BANK_9, + MEM_RAM_BANK_10, + MEM_RAM_BANK_11, + MPU_MEM_NUM_RAM_BANKS, + MPU_MEM_OTP_BANK_0 = 16 +}; + +/* IIO attribute address */ +enum MPU_IIO_ATTR_ADDR { + ATTR_DMP_FLICK_LOWER, + ATTR_DMP_FLICK_UPPER, + ATTR_DMP_FLICK_COUNTER, + ATTR_DMP_FLICK_INT_ON, + ATTR_DMP_FLICK_AXIS, + ATTR_DMP_FLICK_MSG_ON, + ATTR_DMP_PEDOMETER_STEPS, + ATTR_DMP_PEDOMETER_TIME, + ATTR_DMP_TAP_THRESHOLD, + ATTR_DMP_TAP_MIN_COUNT, + ATTR_DMP_TAP_ON, + ATTR_DMP_TAP_TIME, + ATTR_DMP_ON, + ATTR_DMP_INT_ON, + ATTR_DMP_EVENT_INT_ON, + ATTR_DMP_OUTPUT_RATE, + ATTR_DMP_QUATERNION_ON, + ATTR_DMP_DISPLAY_ORIENTATION_ON, + ATTR_LPA_FREQ, + ATTR_SELF_TEST, + ATTR_KEY, + ATTR_GYRO_MATRIX, + ATTR_ACCL_MATRIX, + ATTR_COMPASS_MATRIX, + ATTR_GYRO_ENABLE, + ATTR_ACCL_ENABLE, + ATTR_COMPASS_ENABLE, + ATTR_POWER_STATE, + ATTR_FIRMWARE_LOADED, + ATTR_MOTION_ON, + ATTR_MOTION_DURATION, + ATTR_MOTION_THRESHOLD, +#ifdef CONFIG_INV_TESTING + ATTR_I2C_COUNTERS, + ATTR_REG_WRITE, +#endif +}; + +enum inv_accl_fs_e { + INV_FS_02G = 0, + INV_FS_04G, + INV_FS_08G, + INV_FS_16G, + NUM_ACCL_FSR +}; + +enum inv_fsr_e { + INV_FSR_250DPS = 0, + INV_FSR_500DPS, + INV_FSR_1000DPS, + INV_FSR_2000DPS, + NUM_FSR +}; + +enum inv_clock_sel_e { + INV_CLK_INTERNAL = 0, + INV_CLK_PLL, + NUM_CLK +}; + +ssize_t inv_dmp_firmware_write(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t pos, size_t size); +ssize_t inv_dmp_firmware_read(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count); + +int inv_mpu_configure_ring(struct iio_dev *indio_dev); +int inv_mpu_probe_trigger(struct iio_dev *indio_dev); +void inv_mpu_unconfigure_ring(struct iio_dev *indio_dev); +void inv_mpu_remove_trigger(struct iio_dev *indio_dev); +int inv_init_config_mpu3050(struct iio_dev *indio_dev); +int inv_get_silicon_rev_mpu6050(struct inv_mpu_iio_s *st); +int inv_get_silicon_rev_mpu6500(struct inv_mpu_iio_s *st); +int set_3050_bypass(struct inv_mpu_iio_s *st, bool enable); +int inv_register_mpu3050_slave(struct inv_mpu_iio_s *st); +void inv_setup_reg_mpu3050(struct inv_reg_map_s *reg); +int inv_switch_3050_gyro_engine(struct inv_mpu_iio_s *st, bool en); +int inv_switch_3050_accl_engine(struct inv_mpu_iio_s *st, bool en); +int set_power_mpu3050(struct inv_mpu_iio_s *st, bool power_on); +int set_inv_enable(struct iio_dev *indio_dev, bool enable); +int inv_set_interrupt_on_gesture_event(struct inv_mpu_iio_s *st, bool on); +int inv_send_quaternion(struct inv_mpu_iio_s *st, bool on); +int inv_set_display_orient_interrupt_dmp(struct inv_mpu_iio_s *st, bool on); +int inv_set_fifo_rate(struct inv_mpu_iio_s *st, u16 fifo_rate); +u16 inv_dmp_get_address(u16 key); +int inv_q30_mult(int a, int b); +int inv_set_tap_threshold_dmp(struct inv_mpu_iio_s *st, + u32 axis, u16 threshold); +int inv_set_min_taps_dmp(struct inv_mpu_iio_s *st, u16 min_taps); +int inv_set_tap_time_dmp(struct inv_mpu_iio_s *st, u16 time); +int inv_enable_tap_dmp(struct inv_mpu_iio_s *st, bool on); +int inv_i2c_read_base(struct inv_mpu_iio_s *st, u16 i2c_addr, + u8 reg, u16 length, u8 *data); +int inv_i2c_single_write_base(struct inv_mpu_iio_s *st, + u16 i2c_addr, u8 reg, u8 data); +int inv_do_test(struct inv_mpu_iio_s *st, int self_test_flag, + int *gyro_result, int *accl_result); +int inv_hw_self_test(struct inv_mpu_iio_s *st); +int inv_hw_self_test_6500(struct inv_mpu_iio_s *st); +void inv_recover_setting(struct inv_mpu_iio_s *st); +s64 get_time_ns(void); +int write_be32_key_to_mem(struct inv_mpu_iio_s *st, + u32 data, int key); +int inv_set_accel_bias_dmp(struct inv_mpu_iio_s *st); +int inv_send_sensor_data(struct inv_mpu_iio_s *st, u16 elements); +int inv_send_interrupt_word(struct inv_mpu_iio_s *st, bool on); +int mpu_memory_write(struct inv_mpu_iio_s *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 const *data); +int mpu_memory_read(struct inv_mpu_iio_s *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 *data); +int mpu_memory_write_unaligned(struct inv_mpu_iio_s *st, u16 key, int len, + u8 const *d); +/* used to print i2c data using pr_debug */ +char *wr_pr_debug_begin(u8 const *data, u32 len, char *string); +char *wr_pr_debug_end(char *string); + +#define mem_w(a, b, c) \ + mpu_memory_write(st, st->i2c_addr, a, b, c) +#define mem_w_key(key, b, c) mpu_memory_write_unaligned(st, key, b, c) +#define inv_i2c_read(st, reg, len, data) \ + inv_i2c_read_base(st, st->i2c_addr, reg, len, data) +#define inv_i2c_single_write(st, reg, data) \ + inv_i2c_single_write_base(st, st->i2c_addr, reg, data) +#define inv_secondary_read(reg, len, data) \ + inv_i2c_read_base(st, st->plat_data.secondary_i2c_addr, reg, len, data) +#define inv_secondary_write(reg, data) \ + inv_i2c_single_write_base(st, st->plat_data.secondary_i2c_addr, \ + reg, data) + +#endif /* #ifndef _INV_MPU_IIO_H_ */ + diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_misc.c b/drivers/iio/imu/inv_mpu/inv_mpu_misc.c new file mode 100644 index 00000000..22351018 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_misc.c @@ -0,0 +1,1874 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_mpu_misc.c + * @brief A sysfs device driver for Invensense mpu. + * @details This file is part of invensense mpu driver code + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> +#include <linux/crc32.h> + +#include "inv_mpu_iio.h" +#include "inv_counters.h" + +/* DMP defines */ +#define DMP_ORIENTATION_TIME 500 +#define DMP_ORIENTATION_ANGLE 60 +#define DMP_DEFAULT_FIFO_RATE 200 +#define DMP_TAP_SCALE (767603923 / 5) +#define DMP_MULTI_SHIFT 30 +#define DMP_MULTI_TAP_TIME 500 +#define DMP_SHAKE_REJECT_THRESH 100 +#define DMP_SHAKE_REJECT_TIME 10 +#define DMP_SHAKE_REJECT_TIMEOUT 10 +#define DMP_ANGLE_SCALE 15 +#define DMP_PRECISION 1000 +#define DMP_MAX_DIVIDER 4 +#define DMP_MAX_MIN_TAPS 4 +#define DMP_IMAGE_CRC_VALUE 0xa87fd63c +#define DMP_IMAGE_SIZE 3058 + +/*--- Test parameters defaults --- */ +#define DEF_OLDEST_SUPP_PROD_REV 8 +#define DEF_OLDEST_SUPP_SW_REV 2 + +/* sample rate */ +#define DEF_SELFTEST_SAMPLE_RATE 0 +/* LPF parameter */ +#define DEF_SELFTEST_LPF_PARA 1 +/* full scale setting dps */ +#define DEF_SELFTEST_GYRO_FULL_SCALE (0 << 3) +#define DEF_SELFTEST_ACCL_FULL_SCALE (2 << 3) +#define DEF_SELFTEST_GYRO_SENS (32768 / 250) +/* wait time before collecting data */ +#define DEF_GYRO_WAIT_TIME 50 +#define DEF_ST_STABLE_TIME 200 +#define DEF_GYRO_PACKET_THRESH DEF_GYRO_WAIT_TIME +#define DEF_GYRO_THRESH 10 +#define DEF_GYRO_SCALE 131 +#define DEF_ST_PRECISION 1000 +#define DEF_ST_ACCL_FULL_SCALE 8000UL +#define DEF_ST_SCALE (1L << 15) +#define DEF_ST_TRY_TIMES 2 +#define DEF_ST_COMPASS_RESULT_SHIFT 2 +#define DEF_ST_ACCEL_RESULT_SHIFT 1 +#define DEF_ST_OTP0_THRESH 60 +#define DEF_ST_ABS_THRESH 20 +#define DEF_ST_TOR 2 + +#define DEF_ST_COMPASS_WAIT_MIN (10 * 1000) +#define DEF_ST_COMPASS_WAIT_MAX (15 * 1000) +#define DEF_ST_COMPASS_TRY_TIMES 10 +#define DEF_ST_COMPASS_8963_SHIFT 2 + +#define X 0 +#define Y 1 +#define Z 2 +/*---- MPU6050 notable product revisions ----*/ +#define MPU_PRODUCT_KEY_B1_E1_5 105 +#define MPU_PRODUCT_KEY_B2_F1 431 +/* accelerometer Hw self test min and max bias shift (mg) */ +#define DEF_ACCEL_ST_SHIFT_MIN 300 +#define DEF_ACCEL_ST_SHIFT_MAX 950 + +#define DEF_ACCEL_ST_SHIFT_DELTA 140 +#define DEF_GYRO_CT_SHIFT_DELTA 140 +/* gyroscope Coriolis self test min and max bias shift (dps) */ +#define DEF_GYRO_CT_SHIFT_MIN 10 +#define DEF_GYRO_CT_SHIFT_MAX 105 + +static struct test_setup_t test_setup = { + .gyro_sens = DEF_SELFTEST_GYRO_SENS, + .sample_rate = DEF_SELFTEST_SAMPLE_RATE, + .lpf = DEF_SELFTEST_LPF_PARA, + .fsr = DEF_SELFTEST_GYRO_FULL_SCALE, + .accl_fs = DEF_SELFTEST_ACCL_FULL_SCALE +}; + +/* NOTE: product entries are in chronological order */ +static const struct prod_rev_map_t prod_rev_map[] = { + /* prod_ver = 0 */ + {MPL_PROD_KEY(0, 1), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 2), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 3), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 4), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 5), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 6), MPU_SILICON_REV_A2, 131, 16384}, + /* prod_ver = 1 */ + {MPL_PROD_KEY(0, 7), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 8), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 9), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 10), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 11), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 12), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 13), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 14), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 15), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 27), MPU_SILICON_REV_A2, 131, 16384}, + /* prod_ver = 1 */ + {MPL_PROD_KEY(1, 16), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 17), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 18), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 19), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 20), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 28), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 1), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 2), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 3), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 4), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 5), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 6), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 2 */ + {MPL_PROD_KEY(2, 7), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 8), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 9), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 10), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 11), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 12), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 29), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 3 */ + {MPL_PROD_KEY(3, 30), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 4 */ + {MPL_PROD_KEY(4, 31), MPU_SILICON_REV_B1, 131, 8192}, + {MPL_PROD_KEY(4, 1), MPU_SILICON_REV_B1, 131, 8192}, + {MPL_PROD_KEY(4, 3), MPU_SILICON_REV_B1, 131, 8192}, + /* prod_ver = 5 */ + {MPL_PROD_KEY(5, 3), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 6 */ + {MPL_PROD_KEY(6, 19), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 7 */ + {MPL_PROD_KEY(7, 19), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 8 */ + {MPL_PROD_KEY(8, 19), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 9 */ + {MPL_PROD_KEY(9, 19), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 10 */ + {MPL_PROD_KEY(10, 19), MPU_SILICON_REV_B1, 131, 16384} +}; + +/* +* List of product software revisions +* +* NOTE : +* software revision 0 falls back to the old detection method +* based off the product version and product revision per the +* table above +*/ +static const struct prod_rev_map_t sw_rev_map[] = { + {0, 0, 0, 0}, + {1, MPU_SILICON_REV_B1, 131, 8192}, /* rev C */ + {2, MPU_SILICON_REV_B1, 131, 16384} /* rev D */ +}; + +static const int accl_st_tb[31] = { + 340, 351, 363, 375, 388, 401, 414, 428, + 443, 458, 473, 489, 506, 523, 541, 559, + 578, 597, 617, 638, 660, 682, 705, 729, + 753, 779, 805, 832, 860, 889, 919}; +static const int gyro_6050_st_tb[31] = { + 3275, 3425, 3583, 3748, 3920, 4100, 4289, 4486, + 4693, 4909, 5134, 5371, 5618, 5876, 6146, 6429, + 6725, 7034, 7358, 7696, 8050, 8421, 8808, 9213, + 9637, 10080, 10544, 11029, 11537, 12067, 12622}; +static const int gyro_3500_st_tb[255] = { + 2620, 2646, 2672, 2699, 2726, 2753, 2781, 2808, + 2837, 2865, 2894, 2923, 2952, 2981, 3011, 3041, + 3072, 3102, 3133, 3165, 3196, 3228, 3261, 3293, + 3326, 3359, 3393, 3427, 3461, 3496, 3531, 3566, + 3602, 3638, 3674, 3711, 3748, 3786, 3823, 3862, + 3900, 3939, 3979, 4019, 4059, 4099, 4140, 4182, + 4224, 4266, 4308, 4352, 4395, 4439, 4483, 4528, + 4574, 4619, 4665, 4712, 4759, 4807, 4855, 4903, + 4953, 5002, 5052, 5103, 5154, 5205, 5257, 5310, + 5363, 5417, 5471, 5525, 5581, 5636, 5693, 5750, + 5807, 5865, 5924, 5983, 6043, 6104, 6165, 6226, + 6289, 6351, 6415, 6479, 6544, 6609, 6675, 6742, + 6810, 6878, 6946, 7016, 7086, 7157, 7229, 7301, + 7374, 7448, 7522, 7597, 7673, 7750, 7828, 7906, + 7985, 8065, 8145, 8227, 8309, 8392, 8476, 8561, + 8647, 8733, 8820, 8909, 8998, 9088, 9178, 9270, + 9363, 9457, 9551, 9647, 9743, 9841, 9939, 10038, + 10139, 10240, 10343, 10446, 10550, 10656, 10763, 10870, + 10979, 11089, 11200, 11312, 11425, 11539, 11654, 11771, + 11889, 12008, 12128, 12249, 12371, 12495, 12620, 12746, + 12874, 13002, 13132, 13264, 13396, 13530, 13666, 13802, + 13940, 14080, 14221, 14363, 14506, 14652, 14798, 14946, + 15096, 15247, 15399, 15553, 15709, 15866, 16024, 16184, + 16346, 16510, 16675, 16842, 17010, 17180, 17352, 17526, + 17701, 17878, 18057, 18237, 18420, 18604, 18790, 18978, + 19167, 19359, 19553, 19748, 19946, 20145, 20347, 20550, + 20756, 20963, 21173, 21385, 21598, 21814, 22033, 22253, + 22475, 22700, 22927, 23156, 23388, 23622, 23858, 24097, + 24338, 24581, 24827, 25075, 25326, 25579, 25835, 26093, + 26354, 26618, 26884, 27153, 27424, 27699, 27976, 28255, + 28538, 28823, 29112, 29403, 29697, 29994, 30294, 30597, + 30903, 31212, 31524, 31839, 32157, 32479, 32804}; + +char *wr_pr_debug_begin(u8 const *data, u32 len, char *string) +{ + int ii; + string = kmalloc(len * 2 + 1, GFP_KERNEL); + for (ii = 0; ii < len; ii++) + sprintf(&string[ii * 2], "%02X", data[ii]); + string[len * 2] = 0; + return string; +} + +char *wr_pr_debug_end(char *string) +{ + kfree(string); + return ""; +} + +int mpu_memory_write(struct inv_mpu_iio_s *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 const *data) +{ + u8 bank[2]; + u8 addr[2]; + u8 buf[513]; + + struct i2c_msg msgs[3]; + int res; + + if (!data || !st) + return -EINVAL; + + if (len >= (sizeof(buf) - 1)) + return -ENOMEM; + + bank[0] = REG_BANK_SEL; + bank[1] = mem_addr >> 8; + + addr[0] = REG_MEM_START_ADDR; + addr[1] = mem_addr & 0xFF; + + buf[0] = REG_MEM_RW; + memcpy(buf + 1, data, len); + + /* write message */ + msgs[0].addr = mpu_addr; + msgs[0].flags = 0; + msgs[0].buf = bank; + msgs[0].len = sizeof(bank); + + msgs[1].addr = mpu_addr; + msgs[1].flags = 0; + msgs[1].buf = addr; + msgs[1].len = sizeof(addr); + + msgs[2].addr = mpu_addr; + msgs[2].flags = 0; + msgs[2].buf = (u8 *)buf; + msgs[2].len = len + 1; + + INV_I2C_INC_MPUWRITE(3 + 3 + (2 + len)); +#if CONFIG_DYNAMIC_DEBUG + { + char *write = 0; + pr_debug("%s WM%02X%02X%02X%s%s - %d\n", st->hw->name, + mpu_addr, bank[1], addr[1], + wr_pr_debug_begin(data, len, write), + wr_pr_debug_end(write), + len); + } +#endif + + res = i2c_transfer(st->sl_handle, msgs, 3); + if (res != 3) { + if (res >= 0) + res = -EIO; + return res; + } else { + return 0; + } +} + +int mpu_memory_read(struct inv_mpu_iio_s *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 *data) +{ + u8 bank[2]; + u8 addr[2]; + u8 buf; + + struct i2c_msg msgs[4]; + int res; + + if (!data || !st) + return -EINVAL; + + bank[0] = REG_BANK_SEL; + bank[1] = mem_addr >> 8; + + addr[0] = REG_MEM_START_ADDR; + addr[1] = mem_addr & 0xFF; + + buf = REG_MEM_RW; + + /* write message */ + msgs[0].addr = mpu_addr; + msgs[0].flags = 0; + msgs[0].buf = bank; + msgs[0].len = sizeof(bank); + + msgs[1].addr = mpu_addr; + msgs[1].flags = 0; + msgs[1].buf = addr; + msgs[1].len = sizeof(addr); + + msgs[2].addr = mpu_addr; + msgs[2].flags = 0; + msgs[2].buf = &buf; + msgs[2].len = 1; + + msgs[3].addr = mpu_addr; + msgs[3].flags = I2C_M_RD; + msgs[3].buf = data; + msgs[3].len = len; + + res = i2c_transfer(st->sl_handle, msgs, 4); + if (res != 4) { + if (res >= 0) + res = -EIO; + } else + res = 0; + + INV_I2C_INC_MPUWRITE(3 + 3 + 3); + INV_I2C_INC_MPUREAD(len); +#if CONFIG_DYNAMIC_DEBUG + { + char *read = 0; + pr_debug("%s RM%02X%02X%02X%02X - %s%s\n", st->hw->name, + mpu_addr, bank[1], addr[1], len, + wr_pr_debug_begin(data, len, read), + wr_pr_debug_end(read)); + } +#endif + + return res; +} + +int mpu_memory_write_unaligned(struct inv_mpu_iio_s *st, u16 key, int len, + u8 const *d) +{ + int addr; + int start, end; + int len1, len2; + int result = 0; + if (len > MPU_MEM_BANK_SIZE) + return -EINVAL; + addr = inv_dmp_get_address(key); + start = (addr >> 8); + end = ((addr + len - 1) >> 8); + if (start == end) { + result = mpu_memory_write(st, st->i2c_addr, addr, len, d); + } else { + end <<= 8; + len1 = end - addr; + len2 = len - len1; + result = mpu_memory_write(st, st->i2c_addr, addr, len1, d); + result |= mpu_memory_write(st, st->i2c_addr, end, len2, + d + len1); + } + + return result; +} + +/** + * index_of_key()- Inverse lookup of the index of an MPL product key . + * @key: the MPL product indentifier also referred to as 'key'. + */ +static short index_of_key(u16 key) +{ + int i; + for (i = 0; i < NUM_OF_PROD_REVS; i++) + if (prod_rev_map[i].mpl_product_key == key) + return (short)i; + return -EINVAL; +} + +int inv_get_silicon_rev_mpu6500(struct inv_mpu_iio_s *st) +{ + struct inv_chip_info_s *chip_info = &st->chip_info; + int result; + u8 whoami, sw_rev; + + result = inv_i2c_read(st, REG_WHOAMI, 1, &whoami); + if (result) + return result; + if (whoami != MPU6500_ID && whoami != MPU9250_ID) + return -EINVAL; + + /*memory read need more time after power up */ + msleep(POWER_UP_TIME); + result = mpu_memory_read(st, st->i2c_addr, + MPU6500_MEM_REV_ADDR, 1, &sw_rev); + if (sw_rev == 0) { + pr_warning("Rev 0 of MPU6500\n"); + pr_warning("can't sit with other devices in same I2C bus\n"); + } + if (result) + return result; + if (sw_rev > MPU6500_REV) + return -EINVAL; + + /* these values are place holders and not real values */ + chip_info->product_id = MPU6500_PRODUCT_REVISION; + chip_info->product_revision = MPU6500_PRODUCT_REVISION; + chip_info->silicon_revision = MPU6500_PRODUCT_REVISION; + chip_info->software_revision = sw_rev; + chip_info->gyro_sens_trim = DEFAULT_GYRO_TRIM; + chip_info->accl_sens_trim = DEFAULT_ACCL_TRIM; + chip_info->multi = 1; + + return 0; +} + +int inv_get_silicon_rev_mpu6050(struct inv_mpu_iio_s *st) +{ + int result; + struct inv_reg_map_s *reg; + u8 prod_ver = 0x00, prod_rev = 0x00; + struct prod_rev_map_t *p_rev; + u8 bank = + (BIT_PRFTCH_EN | BIT_CFG_USER_BANK | MPU_MEM_OTP_BANK_0); + u16 mem_addr = ((bank << 8) | MEM_ADDR_PROD_REV); + u16 key; + u8 regs[5]; + u16 sw_rev; + short index; + struct inv_chip_info_s *chip_info = &st->chip_info; + reg = &st->reg; + + result = inv_i2c_read(st, REG_PRODUCT_ID, 1, &prod_ver); + if (result) + return result; + prod_ver &= 0xf; + /*memory read need more time after power up */ + msleep(POWER_UP_TIME); + result = mpu_memory_read(st, st->i2c_addr, mem_addr, + 1, &prod_rev); + if (result) + return result; + prod_rev >>= 2; + /* clean the prefetch and cfg user bank bits */ + result = inv_i2c_single_write(st, reg->bank_sel, 0); + if (result) + return result; + /* get the software-product version, read from XA_OFFS_L */ + result = inv_i2c_read(st, REG_XA_OFFS_L_TC, + SOFT_PROD_VER_BYTES, regs); + if (result) + return result; + + sw_rev = (regs[4] & 0x01) << 2 | /* 0x0b, bit 0 */ + (regs[2] & 0x01) << 1 | /* 0x09, bit 0 */ + (regs[0] & 0x01); /* 0x07, bit 0 */ + /* if 0, use the product key to determine the type of part */ + if (sw_rev == 0) { + key = MPL_PROD_KEY(prod_ver, prod_rev); + if (key == 0) + return -EINVAL; + index = index_of_key(key); + if (index < 0 || index >= NUM_OF_PROD_REVS) + return -EINVAL; + /* check MPL is compiled for this device */ + if (prod_rev_map[index].silicon_rev != MPU_SILICON_REV_B1) + return -EINVAL; + p_rev = (struct prod_rev_map_t *)&prod_rev_map[index]; + /* if valid, use the software product key */ + } else if (sw_rev < ARRAY_SIZE(sw_rev_map)) { + p_rev = (struct prod_rev_map_t *)&sw_rev_map[sw_rev]; + } else { + return -EINVAL; + } + chip_info->product_id = prod_ver; + chip_info->product_revision = prod_rev; + chip_info->silicon_revision = p_rev->silicon_rev; + chip_info->software_revision = sw_rev; + chip_info->gyro_sens_trim = p_rev->gyro_trim; + chip_info->accl_sens_trim = p_rev->accel_trim; + if (chip_info->accl_sens_trim == 0) + chip_info->accl_sens_trim = DEFAULT_ACCL_TRIM; + chip_info->multi = DEFAULT_ACCL_TRIM / chip_info->accl_sens_trim; + if (chip_info->multi != 1) + pr_info("multi is %d\n", chip_info->multi); + return result; +} + +/** + * read_accel_hw_self_test_prod_shift()- read the accelerometer hardware + * self-test bias shift calculated + * during final production test and + * stored in chip non-volatile memory. + * @st: main data structure. + * @st_prod: A pointer to an array of 3 elements to hold the values + * for production hardware self-test bias shifts returned to the + * user. + */ +static int read_accel_hw_self_test_prod_shift(struct inv_mpu_iio_s *st, + int *st_prod) +{ + u8 regs[4]; + u8 shift_code[3]; + int result, i; + st_prod[0] = 0; + st_prod[1] = 0; + st_prod[2] = 0; + result = inv_i2c_read(st, REG_ST_GCT_X, ARRAY_SIZE(regs), regs); + if (result) + return result; + if ((0 == regs[0]) && (0 == regs[1]) && + (0 == regs[2]) && (0 == regs[3])) + return -EINVAL; + shift_code[X] = ((regs[0] & 0xE0) >> 3) | ((regs[3] & 0x30) >> 4); + shift_code[Y] = ((regs[1] & 0xE0) >> 3) | ((regs[3] & 0x0C) >> 2); + shift_code[Z] = ((regs[2] & 0xE0) >> 3) | (regs[3] & 0x03); + for (i = 0; i < 3; i++) { + if (shift_code[i] != 0) + st_prod[i] = test_setup.accl_sens[i]* + accl_st_tb[shift_code[i] - 1]; + } + + return 0; +} +/** +* inv_check_accl_self_test()- check accel self test. this function returns +* zero as success. A non-zero return value +* indicates failure in self test. +* @*st: main data structure. +* @*reg_avg: average value of normal test. +* @*st_avg: average value of self test +*/ +static int inv_check_accl_self_test(struct inv_mpu_iio_s *st, + int *reg_avg, int *st_avg){ + int gravity, reg_z_avg, g_z_sign, fs, j, ret_val; + int tmp1; + int st_shift_prod[THREE_AXIS], st_shift_cust[THREE_AXIS]; + int st_shift_ratio[THREE_AXIS]; + if (st->chip_info.software_revision < DEF_OLDEST_SUPP_SW_REV && + st->chip_info.product_revision < DEF_OLDEST_SUPP_PROD_REV) + return 0; + fs = DEF_ST_ACCL_FULL_SCALE; /* assume +/- 2 mg as typical */ + g_z_sign = 1; + ret_val = 0; + test_setup.accl_sens[X] = (u32)(DEF_ST_SCALE * + DEF_ST_PRECISION / fs); + test_setup.accl_sens[Y] = (u32)(DEF_ST_SCALE * + DEF_ST_PRECISION / fs); + test_setup.accl_sens[Z] = (u32)(DEF_ST_SCALE * + DEF_ST_PRECISION / fs); + + if (MPL_PROD_KEY(st->chip_info.product_id, + st->chip_info.product_revision) == + MPU_PRODUCT_KEY_B1_E1_5) { + /* half sensitivity Z accelerometer parts */ + test_setup.accl_sens[Z] /= 2; + } else { + /* half sensitivity X, Y, Z accelerometer parts */ + test_setup.accl_sens[X] /= st->chip_info.multi; + test_setup.accl_sens[Y] /= st->chip_info.multi; + test_setup.accl_sens[Z] /= st->chip_info.multi; + } + gravity = test_setup.accl_sens[Z]; + reg_z_avg = reg_avg[Z] - g_z_sign * gravity*DEF_ST_PRECISION; + read_accel_hw_self_test_prod_shift(st, st_shift_prod); + for (j = 0; j < 3; j++) { + st_shift_cust[j] = abs(reg_avg[j] - st_avg[j]); + if (st_shift_prod[j]) { + tmp1 = st_shift_prod[j]/DEF_ST_PRECISION; + st_shift_ratio[j] = st_shift_cust[j]/tmp1 + - DEF_ST_PRECISION; + if (st_shift_ratio[j] > DEF_ACCEL_ST_SHIFT_DELTA) + ret_val |= 1 << j; + if (st_shift_ratio[j] < -DEF_ACCEL_ST_SHIFT_DELTA) + ret_val |= 1 << j; + } else { + if (st_shift_cust[j] < + DEF_ACCEL_ST_SHIFT_MIN*gravity) + ret_val |= 1 << j; + if (st_shift_cust[j] > + DEF_ACCEL_ST_SHIFT_MAX*gravity) + ret_val |= 1 << j; + } + } + + return ret_val; +} +/** +* inv_check_3500_gyro_self_test() check gyro self test. this function returns +* zero as success. A non-zero return value +* indicates failure in self test. +* @*st: main data structure. +* @*reg_avg: average value of normal test. +* @*st_avg: average value of self test +*/ + +static int inv_check_3500_gyro_self_test(struct inv_mpu_iio_s *st, + int *reg_avg, int *st_avg){ + int result; + int gst[3], ret_val; + int gst_otp[3], i; + u8 st_code[THREE_AXIS]; + ret_val = 0; + + for (i = 0; i < 3; i++) + gst[i] = st_avg[i] - reg_avg[i]; + result = inv_i2c_read(st, REG_3500_OTP, THREE_AXIS, st_code); + if (result) + return result; + gst_otp[0] = 0; + gst_otp[1] = 0; + gst_otp[2] = 0; + for (i = 0; i < 3; i++) { + if (st_code[i] != 0) + gst_otp[i] = gyro_3500_st_tb[st_code[i] - 1]; + } + /* check self test value passing criterion. Using the DEF_ST_TOR + * for certain degree of tolerance */ + for (i = 0; i < 3; i++) { + if (gst_otp[i] == 0) { + if (abs(gst[i]) * DEF_ST_TOR < DEF_ST_OTP0_THRESH * + DEF_ST_PRECISION * + DEF_GYRO_SCALE) + ret_val |= (1 << i); + } else { + if (abs(gst[i]/gst_otp[i] - DEF_ST_PRECISION) > + DEF_GYRO_CT_SHIFT_DELTA) + ret_val |= (1 << i); + } + } + /* check for absolute value passing criterion. Using DEF_ST_TOR + * for certain degree of tolerance */ + for (i = 0; i < 3; i++) { + if (abs(reg_avg[i]) > DEF_ST_TOR * DEF_ST_ABS_THRESH * + DEF_ST_PRECISION * DEF_GYRO_SCALE) + ret_val |= (1 << i); + } + + return ret_val; +} + +/** +* inv_check_6050_gyro_self_test() - check 6050 gyro self test. this function +* returns zero as success. A non-zero return +* value indicates failure in self test. +* @*st: main data structure. +* @*reg_avg: average value of normal test. +* @*st_avg: average value of self test +*/ +static int inv_check_6050_gyro_self_test(struct inv_mpu_iio_s *st, + int *reg_avg, int *st_avg){ + int result; + int ret_val; + int ct_shift_prod[3], st_shift_cust[3], st_shift_ratio[3], i; + u8 regs[3]; + if (st->chip_info.software_revision < DEF_OLDEST_SUPP_SW_REV && + st->chip_info.product_revision < DEF_OLDEST_SUPP_PROD_REV) + return 0; + + ret_val = 0; + result = inv_i2c_read(st, REG_ST_GCT_X, 3, regs); + if (result) + return result; + regs[X] &= 0x1f; + regs[Y] &= 0x1f; + regs[Z] &= 0x1f; + + for (i = 0; i < 3; i++) { + if (regs[i] != 0) + ct_shift_prod[i] = gyro_6050_st_tb[regs[i] - 1]; + else + ct_shift_prod[i] = 0; + } + for (i = 0; i < 3; i++) { + st_shift_cust[i] = abs(reg_avg[i] - st_avg[i]); + if (ct_shift_prod[i]) { + st_shift_ratio[i] = st_shift_cust[i] / + ct_shift_prod[i] - DEF_ST_PRECISION; + if (st_shift_ratio[i] > DEF_GYRO_CT_SHIFT_DELTA) + ret_val |= 1 << i; + if (st_shift_ratio[i] < -DEF_GYRO_CT_SHIFT_DELTA) + ret_val |= 1 << i; + } else { + if (st_shift_cust[i] < DEF_ST_PRECISION * + DEF_GYRO_CT_SHIFT_MIN * test_setup.gyro_sens) + ret_val |= 1 << i; + if (st_shift_cust[i] > DEF_ST_PRECISION * + DEF_GYRO_CT_SHIFT_MAX * test_setup.gyro_sens) + ret_val |= 1 << i; + } + } + /* check for absolute value passing criterion. Using DEF_ST_TOR + * for certain degree of tolerance */ + for (i = 0; i < 3; i++) { + if (abs(reg_avg[i]) > DEF_ST_TOR * DEF_ST_ABS_THRESH * + DEF_ST_PRECISION * DEF_GYRO_SCALE) + ret_val |= (1 << i); + } + + return ret_val; +} + +/** + * inv_do_test() - do the actual test of self testing + */ +int inv_do_test(struct inv_mpu_iio_s *st, int self_test_flag, + int *gyro_result, int *accl_result) +{ + struct inv_reg_map_s *reg; + int result, i, j, packet_size; + u8 data[BYTES_PER_SENSOR * 2], has_accl; + int fifo_count, packet_count, ind; + + reg = &st->reg; + has_accl = (st->chip_type != INV_ITG3500); + packet_size = BYTES_PER_SENSOR*(1 + has_accl); + + result = inv_i2c_single_write(st, reg->int_enable, 0); + if (result) + return result; + /* disable the sensor output to FIFO */ + result = inv_i2c_single_write(st, reg->fifo_en, 0); + if (result) + return result; + /* disable fifo reading */ + result = inv_i2c_single_write(st, reg->user_ctrl, 0); + if (result) + return result; + /* clear FIFO */ + result = inv_i2c_single_write(st, reg->user_ctrl, BIT_FIFO_RST); + if (result) + return result; + /* setup parameters */ + result = inv_i2c_single_write(st, reg->lpf, test_setup.lpf); + if (result) + return result; + result = inv_i2c_single_write(st, reg->sample_rate_div, + test_setup.sample_rate); + if (result) + return result; + result = inv_i2c_single_write(st, reg->gyro_config, + self_test_flag | test_setup.fsr); + if (result) + return result; + if (has_accl) { + result = inv_i2c_single_write(st, reg->accl_config, + self_test_flag | test_setup.accl_fs); + if (result) + return result; + } + /* wait for the output to get stable */ + if (self_test_flag) + msleep(DEF_ST_STABLE_TIME); + + /* enable FIFO reading */ + result = inv_i2c_single_write(st, reg->user_ctrl, BIT_FIFO_EN); + if (result) + return result; + /* enable sensor output to FIFO */ + result = inv_i2c_single_write(st, reg->fifo_en, BITS_GYRO_OUT + | (has_accl << 3)); + if (result) + return result; + mdelay(DEF_GYRO_WAIT_TIME); + /* stop sending data to FIFO */ + result = inv_i2c_single_write(st, reg->fifo_en, 0); + if (result) + return result; + result = inv_i2c_read(st, reg->fifo_count_h, FIFO_COUNT_BYTE, data); + if (result) + return result; + fifo_count = be16_to_cpup((__be16 *)(&data[0])); + packet_count = fifo_count / packet_size; + for (i = 0; i < 3; i++) { + gyro_result[i] = 0; + accl_result[i] = 0; + } + if (abs(packet_count - DEF_GYRO_PACKET_THRESH) > DEF_GYRO_THRESH) + return -EAGAIN; + + for (i = 0; i < packet_count; i++) { + /* getting FIFO data */ + result = inv_i2c_read(st, reg->fifo_r_w, + packet_size, data); + if (result) + return result; + ind = 0; + if (has_accl) { + for (j = 0; j < THREE_AXIS; j++) + accl_result[j] += + (short)be16_to_cpup( + (__be16 *)(&data[ind + 2 * j])); + ind += BYTES_PER_SENSOR; + } + for (j = 0; j < THREE_AXIS; j++) + gyro_result[j] += + (short)be16_to_cpup( + (__be16 *)(&data[ind + 2 * j])); + } + + gyro_result[0] = gyro_result[0] * DEF_ST_PRECISION / packet_count; + gyro_result[1] = gyro_result[1] * DEF_ST_PRECISION / packet_count; + gyro_result[2] = gyro_result[2] * DEF_ST_PRECISION / packet_count; + if (has_accl) { + accl_result[0] = + accl_result[0] * DEF_ST_PRECISION / packet_count; + accl_result[1] = + accl_result[1] * DEF_ST_PRECISION / packet_count; + accl_result[2] = + accl_result[2] * DEF_ST_PRECISION / packet_count; + } + + return 0; +} + +/** + * inv_recover_setting() recover the old settings after everything is done + */ + +void inv_recover_setting(struct inv_mpu_iio_s *st) +{ + struct inv_reg_map_s *reg; + int data; + struct iio_dev *indio = iio_priv_to_dev(st); + + reg = &st->reg; + set_inv_enable(indio, st->chip_config.enable); + inv_i2c_single_write(st, reg->gyro_config, + st->chip_config.fsr << GYRO_CONFIG_FSR_SHIFT); + inv_i2c_single_write(st, reg->lpf, st->chip_config.lpf); + data = ONE_K_HZ/st->chip_config.fifo_rate - 1; + inv_i2c_single_write(st, reg->sample_rate_div, data); + if (INV_ITG3500 != st->chip_type) { + inv_i2c_single_write(st, reg->accl_config, + (st->chip_config.accl_fs << + ACCL_CONFIG_FSR_SHIFT)); + } + st->set_power_state(st, !st->chip_config.is_asleep); +} + +static int inv_check_compass_self_test(struct inv_mpu_iio_s *st) +{ + int result; + u8 data[6]; + u8 counter, cntl; + short x, y, z; + u8 *sens; + sens = st->chip_info.compass_sens; + + /* set to bypass mode */ + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config | BIT_BYPASS_EN); + if (result) { + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config); + return result; + } + /* set to power down mode */ + result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_PD); + if (result) + goto AKM_fail; + + /* write 1 to ASTC register */ + result = inv_secondary_write(REG_AKM_ST_CTRL, DATA_AKM_SELF_TEST); + if (result) + goto AKM_fail; + /* set self test mode */ + result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_ST); + if (result) + goto AKM_fail; + counter = DEF_ST_COMPASS_TRY_TIMES; + while (counter > 0) { + usleep_range(DEF_ST_COMPASS_WAIT_MIN, DEF_ST_COMPASS_WAIT_MAX); + result = inv_secondary_read(REG_AKM_STATUS, 1, data); + if (result) + goto AKM_fail; + if ((data[0] & DATA_AKM_DRDY) == 0) + counter--; + else + counter = 0; + } + if ((data[0] & DATA_AKM_DRDY) == 0) { + result = -EINVAL; + goto AKM_fail; + } + result = inv_secondary_read(REG_AKM_MEASURE_DATA, + BYTES_PER_SENSOR, data); + if (result) + goto AKM_fail; + + x = le16_to_cpup((__le16 *)(&data[0])); + y = le16_to_cpup((__le16 *)(&data[2])); + z = le16_to_cpup((__le16 *)(&data[4])); + x = ((x * (sens[0] + 128)) >> 8); + y = ((y * (sens[1] + 128)) >> 8); + z = ((z * (sens[2] + 128)) >> 8); + if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id) { + result = inv_secondary_read(REG_AKM8963_CNTL1, 1, &cntl); + if (result) + goto AKM_fail; + if (0 == (cntl & DATA_AKM8963_BIT)) { + x <<= DEF_ST_COMPASS_8963_SHIFT; + y <<= DEF_ST_COMPASS_8963_SHIFT; + z <<= DEF_ST_COMPASS_8963_SHIFT; + } + } + result = -EINVAL; + if (x > st->compass_st_upper[X] || x < st->compass_st_lower[X]) + goto AKM_fail; + if (y > st->compass_st_upper[Y] || y < st->compass_st_lower[Y]) + goto AKM_fail; + if (z > st->compass_st_upper[Z] || z < st->compass_st_lower[Z]) + goto AKM_fail; + result = 0; +AKM_fail: + /*write 0 to ASTC register */ + result |= inv_secondary_write(REG_AKM_ST_CTRL, 0); + /*set to power down mode */ + result |= inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_PD); + /*restore to non-bypass mode */ + result |= inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config); + return result; +} + +static int inv_power_up_self_test(struct inv_mpu_iio_s *st) +{ + int result; + result = inv_i2c_single_write(st, st->reg.pwr_mgmt_1, INV_CLK_PLL); + if (result) + return result; + msleep(POWER_UP_TIME); + result = inv_i2c_single_write(st, st->reg.pwr_mgmt_2, 0); + if (result) + return result; + msleep(SENSOR_UP_TIME); + + return 0; +} + +/** + * inv_hw_self_test() - main function to do hardware self test + */ +int inv_hw_self_test(struct inv_mpu_iio_s *st) +{ + int result; + int gyro_bias_st[THREE_AXIS], gyro_bias_regular[THREE_AXIS]; + int accl_bias_st[THREE_AXIS], accl_bias_regular[THREE_AXIS]; + int test_times; + char compass_result, accel_result, gyro_result; + if (st->chip_config.is_asleep || + st->chip_config.lpa_mode || + (!st->chip_config.gyro_enable) || + (!st->chip_config.accl_enable)) { + result = inv_power_up_self_test(st); + if (result) + return result; + } + compass_result = 0; + accel_result = 0; + gyro_result = 0; + test_times = DEF_ST_TRY_TIMES; + while (test_times > 0) { + result = inv_do_test(st, 0, gyro_bias_regular, + accl_bias_regular); + if (result == -EAGAIN) + test_times--; + else + test_times = 0; + } + if (result) + goto test_fail; + + test_times = DEF_ST_TRY_TIMES; + while (test_times > 0) { + result = inv_do_test(st, BITS_SELF_TEST_EN, gyro_bias_st, + accl_bias_st); + if (result == -EAGAIN) + test_times--; + else + break; + } + if (result) + goto test_fail; + if (st->chip_type == INV_ITG3500) { + gyro_result = !inv_check_3500_gyro_self_test(st, + gyro_bias_regular, gyro_bias_st); + } else { + if (st->chip_config.has_compass) + compass_result = !inv_check_compass_self_test(st); + accel_result = !inv_check_accl_self_test(st, + accl_bias_regular, accl_bias_st); + gyro_result = !inv_check_6050_gyro_self_test(st, + gyro_bias_regular, gyro_bias_st); + } +test_fail: + inv_recover_setting(st); + + return (compass_result << DEF_ST_COMPASS_RESULT_SHIFT) | + (accel_result << DEF_ST_ACCEL_RESULT_SHIFT) | gyro_result; +} + +/** + * inv_hw_self_test_6500() - main function to do hardware self test for 6500 + */ +int inv_hw_self_test_6500(struct inv_mpu_iio_s *st) +{ + int compass_result; + compass_result = !inv_check_compass_self_test(st); + return compass_result << DEF_ST_COMPASS_RESULT_SHIFT; +} + +static int inv_load_firmware(struct inv_mpu_iio_s *st, + u8 *data, int size) +{ + int bank, write_size; + int result; + u16 memaddr; + + /* Write and verify memory */ + for (bank = 0; size > 0; bank++, + size -= write_size, + data += write_size) { + if (size > MPU_MEM_BANK_SIZE) + write_size = MPU_MEM_BANK_SIZE; + else + write_size = size; + + memaddr = ((bank << 8) | 0x00); + + result = mem_w(memaddr, write_size, data); + if (result) + return result; + } + return 0; +} + +static int inv_verify_firmware(struct inv_mpu_iio_s *st, + u8 *data, int size) +{ + int bank, write_size; + int result; + u16 memaddr; + u8 firmware[MPU_MEM_BANK_SIZE]; + + /* Write and verify memory */ + for (bank = 0; size > 0; bank++, + size -= write_size, + data += write_size) { + if (size > MPU_MEM_BANK_SIZE) + write_size = MPU_MEM_BANK_SIZE; + else + write_size = size; + + memaddr = ((bank << 8) | 0x00); + result = mpu_memory_read(st, + st->i2c_addr, memaddr, write_size, firmware); + if (result) + return result; + if (0 != memcmp(firmware, data, write_size)) + return -EINVAL; + } + return 0; +} + +static int inv_set_fifo_div(struct inv_mpu_iio_s *st, + u16 fifoRate) +{ + u8 regs[2]; + int result = 0; + /*For some reason DINAC4 is defined as 0xb8, but DINBC4 is not*/ + const u8 regs_end[12] = {DINAFE, DINAF2, DINAAB, 0xc4, + DINAAA, DINAF1, DINADF, DINADF, + 0xbb, 0xaf, DINADF, DINADF}; + + regs[0] = (u8)((fifoRate >> 8) & 0xff); + regs[1] = (u8)(fifoRate & 0xff); + result = mem_w_key(KEY_D_0_22, ARRAY_SIZE(regs), regs); + if (result) + return result; + + /*Modify the FIFO handler to reset the tap/orient interrupt flags*/ + /* each time the FIFO handler runs*/ + result = mem_w_key(KEY_CFG_6, ARRAY_SIZE(regs_end), regs_end); + + return result; +} + +int inv_send_quaternion(struct inv_mpu_iio_s *st, bool on) +{ + const u8 regs_on[] = {DINBC0, DINBC2, + DINBC4, DINBC6}; + const u8 regs_off[] = {DINA80, DINA80, + DINA80, DINA80}; + const u8 *regs; + u8 result; + if (on) + regs = regs_on; + else + regs = regs_off; + result = mem_w_key(KEY_CFG_LP_QUAT, ARRAY_SIZE(regs_on), regs); + + return result; +} + +int inv_set_display_orient_interrupt_dmp(struct inv_mpu_iio_s *st, + bool on) +{ + /*Turn on the display orientation interrupt in the DMP*/ + int result; + u8 regs[] = {0xd8}; + + if (on) + regs[0] = 0xd9; + result = mem_w_key(KEY_CFG_DISPLAY_ORIENT_INT, 1, regs); + return result; +} + +int inv_set_fifo_rate(struct inv_mpu_iio_s *st, u16 fifo_rate) +{ + u8 divider; + int result; + + divider = (u8)(ONE_K_HZ / fifo_rate) - 1; + if (divider > DMP_MAX_DIVIDER) { + st->sample_divider = DMP_MAX_DIVIDER; + st->fifo_divider = + (u8)(DMP_DEFAULT_FIFO_RATE / fifo_rate) - 1; + } else { + st->sample_divider = divider; + st->fifo_divider = 0; + } + + result = inv_set_fifo_div(st, st->fifo_divider); + return result; +} + +static int inv_set_tap_interrupt_dmp(struct inv_mpu_iio_s *st, + u8 on) +{ + int result; + u8 regs[] = {0}; + + if (on) + regs[0] = 0xf8; + else + regs[0] = DINAD8; + result = mem_w_key(KEY_CFG_20, ARRAY_SIZE(regs), regs); + if (result) + return result; + return result; +} + +int inv_set_tap_threshold_dmp(struct inv_mpu_iio_s *st, + u32 axis, u16 threshold) +{ + /* Sets the tap threshold in the dmp + Simultaneously sets secondary tap threshold to help correct the tap + direction for soft taps */ + int result; + /* DMP Algorithm */ + u8 data[2]; + int sampleDivider; + int scaledThreshold; + u32 dmpThreshold; + u8 sample_div; + const u32 accel_sens = (0x20000000 / 0x00010000); + + if ((axis & ~(INV_TAP_AXIS_ALL)) || (threshold > (1 << 15))) + return -EINVAL; + sample_div = st->sample_divider; + + sampleDivider = (1 + sample_div); + /* Scale factor corresponds linearly using + * 0 : 0 + * 25 : 0.0250 g/ms + * 50 : 0.0500 g/ms + * 100: 1.0000 g/ms + * 200: 2.0000 g/ms + * 400: 4.0000 g/ms + * 800: 8.0000 g/ms + */ + /*multiply by 1000 to avoid floating point 1000/1000*/ + scaledThreshold = threshold; + /* Convert to per sample */ + scaledThreshold *= sampleDivider; + + /* Scale to DMP 16 bit value */ + if (accel_sens != 0) + dmpThreshold = (u32)(scaledThreshold * accel_sens); + else + return -EINVAL; + dmpThreshold = dmpThreshold / DMP_PRECISION; + + data[0] = dmpThreshold >> 8; + data[1] = dmpThreshold & 0xFF; + + /* MPL algorithm */ + if (axis & INV_TAP_AXIS_X) { + result = mem_w_key(KEY_DMP_TAP_THR_X, ARRAY_SIZE(data), data); + if (result) + return result; + + /*Also set additional threshold for correcting the direction + of taps that were very near the threshold. */ + data[0] = (dmpThreshold * 3 / 4) >> 8; + data[1] = (dmpThreshold * 3 / 4) & 0xFF; + result = mem_w_key(KEY_D_1_36, ARRAY_SIZE(data), data); + if (result) + return result; + } + if (axis & INV_TAP_AXIS_Y) { + result = mem_w_key(KEY_DMP_TAP_THR_Y, 2, data); + if (result) + return result; + data[0] = (dmpThreshold * 3 / 4) >> 8; + data[1] = (dmpThreshold * 3 / 4) & 0xFF; + + result = mem_w_key(KEY_D_1_40, ARRAY_SIZE(data), data); + if (result) + return result; + } + if (axis & INV_TAP_AXIS_Z) { + result = mem_w_key(KEY_DMP_TAP_THR_Z, ARRAY_SIZE(data), data); + if (result) + return result; + data[0] = (dmpThreshold * 3 / 4) >> 8; + data[1] = (dmpThreshold * 3 / 4) & 0xFF; + + result = mem_w_key(KEY_D_1_44, ARRAY_SIZE(data), data); + if (result) + return result; + } + return 0; +} + +static int inv_set_tap_axes_dmp(struct inv_mpu_iio_s *st, + u32 axes) +{ + /* Sets a mask in the DMP that indicates what tap events + should result in an interrupt */ + u8 regs[4]; + u8 result; + + /* check if any spurious bit other the ones expected are set */ + if (axes & (~(INV_TAP_ALL_DIRECTIONS))) + return -EINVAL; + + regs[0] = (u8)axes; + result = mem_w_key(KEY_D_1_72, 1, regs); + + return result; +} + +int inv_set_min_taps_dmp(struct inv_mpu_iio_s *st, + u16 min_taps) { + /*Indicates the minimum number of consecutive taps required + before the DMP will generate an interrupt */ + u8 regs[1]; + u8 result; + /* check if any spurious bit other the ones expected are set */ + if ((min_taps > DMP_MAX_MIN_TAPS) || (min_taps < 1)) + return -EINVAL; + regs[0] = (u8)(min_taps-1); + result = mem_w_key(KEY_D_1_79, ARRAY_SIZE(regs), regs); + + return result; +} + +int inv_set_tap_time_dmp(struct inv_mpu_iio_s *st, u16 time) +{ + /* Determines how long after a tap the DMP requires before + another tap can be registered*/ + int result; + /* DMP Algorithm */ + u16 dmpTime; + u8 data[2]; + u8 sampleDivider; + + sampleDivider = st->sample_divider; + sampleDivider++; + + /* 60 ms minimum time added */ + dmpTime = ((time) / sampleDivider); + data[0] = dmpTime >> 8; + data[1] = dmpTime & 0xFF; + + result = mem_w_key(KEY_DMP_TAPW_MIN, ARRAY_SIZE(data), data); + + return result; +} + +static int inv_set_multiple_tap_time_dmp(struct inv_mpu_iio_s *st, + u32 time) +{ + /*Determines how close together consecutive taps must occur + to be considered double/triple taps*/ + int result; + /* DMP Algorithm */ + u16 dmpTime; + u8 data[2]; + u8 sampleDivider; + + sampleDivider = st->sample_divider; + sampleDivider++; + + /* 60 ms minimum time added */ + dmpTime = ((time) / sampleDivider); + data[0] = dmpTime >> 8; + data[1] = dmpTime & 0xFF; + result = mem_w_key(KEY_D_1_218, ARRAY_SIZE(data), data); + + return result; +} + +int inv_q30_mult(int a, int b) +{ + u64 temp; + int result; + temp = (u64)a * b; + result = (int)(temp >> DMP_MULTI_SHIFT); + + return result; +} + +static u16 inv_row_2_scale(const s8 *row) +{ + u16 b; + + if (row[0] > 0) + b = 0; + else if (row[0] < 0) + b = 4; + else if (row[1] > 0) + b = 1; + else if (row[1] < 0) + b = 5; + else if (row[2] > 0) + b = 2; + else if (row[2] < 0) + b = 6; + else + b = 7; + + return b; +} + +/** Converts an orientation matrix made up of 0,+1,and -1 to a scalar +* representation. +* @param[in] mtx Orientation matrix to convert to a scalar. +* @return Description of orientation matrix. The lowest 2 bits (0 and 1) +* represent the column the one is on for the +* first row, with the bit number 2 being the sign. The next 2 bits +* (3 and 4) represent +* the column the one is on for the second row with bit number 5 being +* the sign. +* The next 2 bits (6 and 7) represent the column the one is on for the +* third row with +* bit number 8 being the sign. In binary the identity matrix would therefor +* be: 010_001_000 or 0x88 in hex. +*/ +static u16 inv_orientation_matrix_to_scaler(const signed char *mtx) +{ + + u16 scalar; + scalar = inv_row_2_scale(mtx); + scalar |= inv_row_2_scale(mtx + 3) << 3; + scalar |= inv_row_2_scale(mtx + 6) << 6; + + return scalar; +} + +static int inv_gyro_dmp_cal(struct inv_mpu_iio_s *st) +{ + int inv_gyro_orient; + u8 regs[3]; + int result; + + u8 tmpD = DINA4C; + u8 tmpE = DINACD; + u8 tmpF = DINA6C; + + inv_gyro_orient = + inv_orientation_matrix_to_scaler(st->plat_data.orientation); + + if ((inv_gyro_orient & 3) == 0) + regs[0] = tmpD; + else if ((inv_gyro_orient & 3) == 1) + regs[0] = tmpE; + else if ((inv_gyro_orient & 3) == 2) + regs[0] = tmpF; + if ((inv_gyro_orient & 0x18) == 0) + regs[1] = tmpD; + else if ((inv_gyro_orient & 0x18) == 0x8) + regs[1] = tmpE; + else if ((inv_gyro_orient & 0x18) == 0x10) + regs[1] = tmpF; + if ((inv_gyro_orient & 0xc0) == 0) + regs[2] = tmpD; + else if ((inv_gyro_orient & 0xc0) == 0x40) + regs[2] = tmpE; + else if ((inv_gyro_orient & 0xc0) == 0x80) + regs[2] = tmpF; + + result = mem_w_key(KEY_FCFG_1, 3, regs); + if (result) + return result; + + if (inv_gyro_orient & 4) + regs[0] = DINA36 | 1; + else + regs[0] = DINA36; + if (inv_gyro_orient & 0x20) + regs[1] = DINA56 | 1; + else + regs[1] = DINA56; + if (inv_gyro_orient & 0x100) + regs[2] = DINA76 | 1; + else + regs[2] = DINA76; + + result = mem_w_key(KEY_FCFG_3, ARRAY_SIZE(regs), regs); + + return result; +} + +static int inv_accel_dmp_cal(struct inv_mpu_iio_s *st) +{ + int inv_accel_orient; + int result; + u8 regs[3]; + const u8 tmp[3] = { DINA0C, DINAC9, DINA2C }; + inv_accel_orient = + inv_orientation_matrix_to_scaler(st->plat_data.orientation); + + regs[0] = tmp[inv_accel_orient & 3]; + regs[1] = tmp[(inv_accel_orient >> 3) & 3]; + regs[2] = tmp[(inv_accel_orient >> 6) & 3]; + result = mem_w_key(KEY_FCFG_2, 3, regs); + if (result) + return result; + + regs[0] = DINA26; + regs[1] = DINA46; + regs[2] = DINA66; + if (inv_accel_orient & 4) + regs[0] |= 1; + if (inv_accel_orient & 0x20) + regs[1] |= 1; + if (inv_accel_orient & 0x100) + regs[2] |= 1; + result = mem_w_key(KEY_FCFG_7, ARRAY_SIZE(regs), regs); + + return result; +} + +static u16 inv_orientation_matrix_to_scalar(const s8 *mtx) +{ + + u16 scalar; + + /* + XYZ 010_001_000 Identity Matrix + XZY 001_010_000 + YXZ 010_000_001 + YZX 000_010_001 + ZXY 001_000_010 + ZYX 000_001_010 + */ + + scalar = inv_row_2_scale(mtx); + scalar |= inv_row_2_scale(mtx + 3) << 3; + scalar |= inv_row_2_scale(mtx + 6) << 6; + + return scalar; +} + +int inv_set_accel_bias_dmp(struct inv_mpu_iio_s *st) +{ + int inv_accel_orient, result, i, accel_bias_body[3], out[3]; + int tmp[] = {1, 1, 1}; + int mask[] = {4, 0x20, 0x100}; + int accel_sf = 0x20000000;/* 536870912 */ + u8 *regs; + + inv_accel_orient = + inv_orientation_matrix_to_scalar(st->plat_data.orientation); + + for (i = 0; i < 3; i++) + if (inv_accel_orient & mask[i]) + tmp[i] = -1; + + for (i = 0; i < 3; i++) + accel_bias_body[i] = st->input_accel_bias[(inv_accel_orient >> + (i * 3)) & 3] * tmp[i]; + for (i = 0; i < 3; i++) + accel_bias_body[i] = inv_q30_mult(accel_sf, + accel_bias_body[i]); + for (i = 0; i < 3; i++) + out[i] = cpu_to_be32p(&accel_bias_body[i]); + regs = (u8 *)out; + result = mem_w_key(KEY_D_ACCEL_BIAS, sizeof(out), regs); + + return result; +} + +static int inv_set_gyro_sf_dmp(struct inv_mpu_iio_s *st) +{ + /*The gyro threshold, in dps, above which taps will be rejected*/ + int result; + /* DMP Algorithm */ + u8 sampleDivider; + u32 gyro_sf; + const u32 gyro_sens = 0x03e80000; + + sampleDivider = st->sample_divider; + gyro_sf = inv_q30_mult(gyro_sens, + (int)(DMP_TAP_SCALE * (sampleDivider + 1))); + result = write_be32_key_to_mem(st, gyro_sf, KEY_D_0_104); + + return result; +} + +static int inv_set_shake_reject_thresh_dmp(struct inv_mpu_iio_s *st, + int thresh) +{ /*THIS FUNCTION FAILS MEM_W*/ + /*The gyro threshold, in dps, above which taps will be rejected */ + int result; + /* DMP Algorithm */ + u8 sampleDivider; + int thresh_scaled; + u32 gyro_sf; + const u32 gyro_sens = 0x03e80000; + sampleDivider = st->sample_divider; + gyro_sf = inv_q30_mult(gyro_sens, (int)(DMP_TAP_SCALE * + (sampleDivider + 1))); + /* We're in units of DPS, convert it back to chip units*/ + /*split the operation to aviod overflow of integer*/ + thresh_scaled = gyro_sens / (1L << 16); + thresh_scaled = thresh_scaled / thresh; + thresh_scaled = gyro_sf / thresh_scaled; + result = write_be32_key_to_mem(st, thresh_scaled, KEY_D_1_92); + + return result; +} + +static int inv_set_shake_reject_time_dmp(struct inv_mpu_iio_s *st, + u32 time) +{ + /* How long a gyro axis must remain above its threshold + before taps are rejected */ + int result; + /* DMP Algorithm */ + u16 dmpTime; + u8 data[2]; + u8 sampleDivider; + + sampleDivider = st->sample_divider; + sampleDivider++; + + /* 60 ms minimum time added */ + dmpTime = ((time) / sampleDivider); + data[0] = dmpTime >> 8; + data[1] = dmpTime & 0xFF; + + result = mem_w_key(KEY_D_1_88, ARRAY_SIZE(data), data); + return result; +} + +static int inv_set_shake_reject_timeout_dmp(struct inv_mpu_iio_s *st, + u32 time) +{ + /*How long the gyros must remain below their threshold, + after taps have been rejected, before taps can be detected again*/ + int result; + /* DMP Algorithm */ + u16 dmpTime; + u8 data[2]; + u8 sampleDivider; + + sampleDivider = st->sample_divider; + sampleDivider++; + + /* 60 ms minimum time added */ + dmpTime = ((time) / sampleDivider); + data[0] = dmpTime >> 8; + data[1] = dmpTime & 0xFF; + + result = mem_w_key(KEY_D_1_90, ARRAY_SIZE(data), data); + return result; +} + +int inv_set_interrupt_on_gesture_event(struct inv_mpu_iio_s *st, bool on) +{ + u8 result; + const u8 regs_on[] = {DINADA, DINADA, DINAB1, DINAB9, + DINAF3, DINA8B, DINAA3, DINA91, + DINAB6, DINADA, DINAB4, DINADA}; + const u8 regs_off[] = {0xd8, 0xd8, 0xb1, 0xb9, 0xf3, 0x8b, + 0xa3, 0x91, 0xb6, 0x09, 0xb4, 0xd9}; + /*For some reason DINAC4 is defined as 0xb8, + but DINBC4 is not defined.*/ + const u8 regs_end[] = {DINAFE, DINAF2, DINAAB, 0xc4, + DINAAA, DINAF1, DINADF, DINADF, + 0xbb, 0xaf, DINADF, DINADF}; + const u8 regs[] = {0, 0}; + /* reset fifo count to zero */ + result = mem_w_key(KEY_D_1_178, ARRAY_SIZE(regs), regs); + if (result) + return result; + + if (on) + /*Sets the DMP to send an interrupt and put a FIFO packet + in the FIFO if and only if a tap/orientation event + just occurred*/ + result = mem_w_key(KEY_CFG_FIFO_ON_EVENT, ARRAY_SIZE(regs_on), + regs_on); + else + /*Sets the DMP to send an interrupt and put a FIFO packet + in the FIFO at the rate specified by the FIFO div. + see inv_set_fifo_div in hw_setup.c to set the FIFO div.*/ + result = mem_w_key(KEY_CFG_FIFO_ON_EVENT, ARRAY_SIZE(regs_off), + regs_off); + if (result) + return result; + + result = mem_w_key(KEY_CFG_6, ARRAY_SIZE(regs_end), regs_end); + + return result; +} + +/** + * inv_enable_tap_dmp() - calling this function will enable/disable tap function. + */ +int inv_enable_tap_dmp(struct inv_mpu_iio_s *st, bool on) +{ + int result; + result = inv_set_tap_interrupt_dmp(st, on); + if (result) + return result; + if (on) { + result = inv_set_tap_threshold_dmp(st, INV_TAP_AXIS_X, + st->tap.thresh); + if (result) + return result; + result = inv_set_tap_threshold_dmp(st, INV_TAP_AXIS_Y, + st->tap.thresh); + if (result) + return result; + result = inv_set_tap_threshold_dmp(st, INV_TAP_AXIS_Z, + st->tap.thresh); + if (result) + return result; + } + + result = inv_set_tap_axes_dmp(st, INV_TAP_ALL_DIRECTIONS); + if (result) + return result; + result = inv_set_min_taps_dmp(st, st->tap.min_count); + if (result) + return result; + + result = inv_set_tap_time_dmp(st, st->tap.time); + if (result) + return result; + + result = inv_set_multiple_tap_time_dmp(st, DMP_MULTI_TAP_TIME); + if (result) + return result; + + result = inv_set_gyro_sf_dmp(st); + if (result) + return result; + + result = inv_set_shake_reject_thresh_dmp(st, DMP_SHAKE_REJECT_THRESH); + if (result) + return result; + + result = inv_set_shake_reject_time_dmp(st, DMP_SHAKE_REJECT_TIME); + if (result) + return result; + + result = inv_set_shake_reject_timeout_dmp(st, + DMP_SHAKE_REJECT_TIMEOUT); + return result; +} + +int inv_send_sensor_data(struct inv_mpu_iio_s *st, u16 elements) +{ + int result; + u8 regs[] = {DINAA0 + 3, DINAA0 + 3, DINAA0 + 3, + DINAA0 + 3, DINAA0 + 3, DINAA0 + 3, + DINAA0 + 3, DINAA0 + 3, DINAA0 + 3, + DINAA0 + 3}; + + if (elements & INV_ELEMENT_1) + regs[0] = DINACA; + if (elements & INV_ELEMENT_2) + regs[4] = DINBC4; + if (elements & INV_ELEMENT_3) + regs[5] = DINACC; + if (elements & INV_ELEMENT_4) + regs[6] = DINBC6; + if ((elements & INV_ELEMENT_5) || (elements & INV_ELEMENT_6) || + (elements & INV_ELEMENT_7)) { + regs[1] = DINBC0; + regs[2] = DINAC8; + regs[3] = DINBC2; + } + result = mem_w_key(KEY_CFG_15, ARRAY_SIZE(regs), regs); + return result; +} + +int inv_send_interrupt_word(struct inv_mpu_iio_s *st, bool on) +{ + const u8 regs_on[] = { DINA20 }; + const u8 regs_off[] = { DINAA3 }; + u8 result; + + if (on) + result = mem_w_key(KEY_CFG_27, ARRAY_SIZE(regs_on), regs_on); + else + result = mem_w_key(KEY_CFG_27, ARRAY_SIZE(regs_off), regs_off); + + return result; +} + +/** + * inv_dmp_firmware_write() - calling this function will load the firmware. + * This is the write function of file "dmp_firmware". + */ +ssize_t inv_dmp_firmware_write(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t pos, size_t size) +{ + u8 *firmware; + int result; + struct inv_reg_map_s *reg; + struct iio_dev *indio_dev; + struct inv_mpu_iio_s *st; + + indio_dev = dev_get_drvdata(container_of(kobj, struct device, kobj)); + st = iio_priv(indio_dev); + + if (st->chip_config.is_asleep || st->chip_config.firmware_loaded) + return -EINVAL; + + reg = &st->reg; + if (DMP_IMAGE_SIZE != size) { + pr_err("wrong DMP image size\n"); + return -EINVAL; + } + + firmware = kmalloc(size, GFP_KERNEL); + if (!firmware) + return -ENOMEM; + + memcpy(firmware, buf, size); + result = crc32(CRC_FIRMWARE_SEED, firmware, size); + if (DMP_IMAGE_CRC_VALUE != result) { + result = -EINVAL; + pr_err("firmware CRC error - 0x%08x vs 0x%08x\n", + result, DMP_IMAGE_CRC_VALUE); + goto firmware_write_fail; + } + + result = inv_load_firmware(st, firmware, size); + if (result) + goto firmware_write_fail; + + result = inv_verify_firmware(st, firmware, size); + if (result) + goto firmware_write_fail; + + result = inv_i2c_single_write(st, reg->prgm_strt_addrh, + st->chip_config.prog_start_addr >> 8); + if (result) + goto firmware_write_fail; + result = inv_i2c_single_write(st, reg->prgm_strt_addrh + 1, + st->chip_config.prog_start_addr & 0xff); + if (result) + goto firmware_write_fail; + + result = inv_set_fifo_rate(st, DMP_DEFAULT_FIFO_RATE); + if (result) + goto firmware_write_fail; + result = inv_gyro_dmp_cal(st); + if (result) + goto firmware_write_fail; + result = inv_accel_dmp_cal(st); + if (result) + goto firmware_write_fail; + st->chip_config.firmware_loaded = 1; + result = size; +firmware_write_fail: + kfree(firmware); + + return result; +} + +ssize_t inv_dmp_firmware_read(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + int bank, write_size, size, data, result; + u16 memaddr; + struct iio_dev *indio_dev; + struct inv_mpu_iio_s *st; + + size = count; + indio_dev = dev_get_drvdata(container_of(kobj, struct device, kobj)); + st = iio_priv(indio_dev); + + data = 0; + for (bank = 0; size > 0; bank++, size -= write_size, + data += write_size) { + if (size > MPU_MEM_BANK_SIZE) + write_size = MPU_MEM_BANK_SIZE; + else + write_size = size; + + memaddr = (bank << 8); + result = mpu_memory_read(st, + st->i2c_addr, memaddr, write_size, &buf[data]); + if (result) + return result; + } + + return count; +} +/** + * @} + */ diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu/inv_mpu_ring.c new file mode 100644 index 00000000..b578b1c5 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_ring.c @@ -0,0 +1,1048 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_mpu_ring.c + * @brief A sysfs device driver for Invensense gyroscopes. + * @details This file is part of inv mpu iio driver code + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> +#include <linux/iio/iio.h> +#include <linux/gpio.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/sysfs.h> + +#include "inv_mpu_iio.h" + +/** + * reset_fifo_mpu3050() - Reset FIFO related registers + * @st: Device driver instance. + */ +static int reset_fifo_mpu3050(struct iio_dev *indio_dev) +{ + struct inv_reg_map_s *reg; + int result; + u8 val, user_ctrl; + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + reg = &st->reg; + + /* disable interrupt */ + result = inv_i2c_single_write(st, reg->int_enable, + st->plat_data.int_config); + if (result) + return result; + /* disable the sensor output to FIFO */ + result = inv_i2c_single_write(st, reg->fifo_en, 0); + if (result) + goto reset_fifo_fail; + result = inv_i2c_read(st, reg->user_ctrl, 1, &user_ctrl); + if (result) + goto reset_fifo_fail; + /* disable fifo reading */ + user_ctrl &= ~BIT_FIFO_EN; + st->chip_config.has_footer = 0; + /* reset fifo */ + val = (BIT_3050_FIFO_RST | user_ctrl); + result = inv_i2c_single_write(st, reg->user_ctrl, val); + if (result) + goto reset_fifo_fail; + st->last_isr_time = get_time_ns(); + if (st->chip_config.dmp_on) { + /* enable interrupt when DMP is done */ + result = inv_i2c_single_write(st, reg->int_enable, + st->plat_data.int_config | BIT_DMP_INT_EN); + if (result) + return result; + + result = inv_i2c_single_write(st, reg->user_ctrl, + BIT_FIFO_EN|user_ctrl); + if (result) + return result; + } else { + /* enable interrupt */ + if (st->chip_config.accl_fifo_enable || + st->chip_config.gyro_fifo_enable) { + result = inv_i2c_single_write(st, reg->int_enable, + st->plat_data.int_config | BIT_DATA_RDY_EN); + if (result) + return result; + } + /* enable FIFO reading and I2C master interface*/ + result = inv_i2c_single_write(st, reg->user_ctrl, + BIT_FIFO_EN | user_ctrl); + if (result) + return result; + /* enable sensor output to FIFO and FIFO footer*/ + val = 1; + if (st->chip_config.accl_fifo_enable) + val |= BITS_3050_ACCL_OUT; + if (st->chip_config.gyro_fifo_enable) + val |= BITS_GYRO_OUT; + result = inv_i2c_single_write(st, reg->fifo_en, val); + if (result) + return result; + } + + return 0; +reset_fifo_fail: + if (st->chip_config.dmp_on) + val = BIT_DMP_INT_EN; + else + val = BIT_DATA_RDY_EN; + inv_i2c_single_write(st, reg->int_enable, + st->plat_data.int_config | val); + pr_err("reset fifo failed\n"); + + return result; +} + +/** + * inv_lpa_mode() - store current low power mode settings + */ +static int inv_lpa_mode(struct inv_mpu_iio_s *st, int lpa_mode) +{ + unsigned long result; + u8 d; + struct inv_reg_map_s *reg; + + reg = &st->reg; + result = inv_i2c_read(st, reg->pwr_mgmt_1, 1, &d); + if (result) + return result; + if (lpa_mode) + d |= BIT_CYCLE; + else + d &= ~BIT_CYCLE; + + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, d); + if (result) + return result; + if (INV_MPU6500 == st->chip_type) { + if (lpa_mode) + result = inv_i2c_single_write(st, + REG_6500_ACCEL_CONFIG2, + BIT_ACCEL_FCHOCIE_B); + else + result = inv_i2c_single_write(st, 0, + BIT_ACCEL_FCHOCIE_B); + + if (result) + return result; + } + + return 0; +} + +/** + * reset_fifo_itg() - Reset FIFO related registers. + * @st: Device driver instance. + */ +static int reset_fifo_itg(struct iio_dev *indio_dev) +{ + struct inv_reg_map_s *reg; + int result, data; + u8 val, int_word; + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + reg = &st->reg; + + if (st->chip_config.lpa_mode) { + result = inv_lpa_mode(st, 0); + if (result) { + pr_err("reset lpa mode failed\n"); + return result; + } + } + /* disable interrupt */ + result = inv_i2c_single_write(st, reg->int_enable, 0); + if (result) { + pr_err("int_enable write failed\n"); + return result; + } + /* disable the sensor output to FIFO */ + result = inv_i2c_single_write(st, reg->fifo_en, 0); + if (result) + goto reset_fifo_fail; + /* disable fifo reading */ + result = inv_i2c_single_write(st, reg->user_ctrl, 0); + if (result) + goto reset_fifo_fail; + int_word = 0; + + /* MPU6500's BIT_6500_WOM_EN is the same as BIT_MOT_EN */ + if (st->mot_int.mot_on) + int_word |= BIT_MOT_EN; + + if (st->chip_config.dmp_on) { + val = (BIT_FIFO_RST | BIT_DMP_RST); + result = inv_i2c_single_write(st, reg->user_ctrl, val); + if (result) + goto reset_fifo_fail; + st->last_isr_time = get_time_ns(); + if (st->chip_config.dmp_int_on) { + int_word |= BIT_DMP_INT_EN; + result = inv_i2c_single_write(st, reg->int_enable, + int_word); + if (result) + return result; + } + val = (BIT_DMP_EN | BIT_FIFO_EN); + if (st->chip_config.compass_enable & + (!st->chip_config.dmp_event_int_on)) + val |= BIT_I2C_MST_EN; + result = inv_i2c_single_write(st, reg->user_ctrl, val); + if (result) + goto reset_fifo_fail; + + if (st->chip_config.compass_enable) { + /* I2C_MST_DLY is set according to sample rate, + slow down the power*/ + data = max(COMPASS_RATE_SCALE * + st->chip_config.fifo_rate / ONE_K_HZ, + st->chip_config.fifo_rate / + st->chip_config.dmp_output_rate); + if (data > 0) + data -= 1; + result = inv_i2c_single_write(st, REG_I2C_SLV4_CTRL, + data); + if (result) + return result; + } + val = 0; + if (st->chip_config.accl_fifo_enable) + val |= INV_ACCL_MASK; + if (st->chip_config.gyro_fifo_enable) + val |= INV_GYRO_MASK; + result = inv_send_sensor_data(st, val); + if (result) + return result; + if (st->chip_config.display_orient_on || st->chip_config.tap_on) + result = inv_send_interrupt_word(st, true); + else + result = inv_send_interrupt_word(st, false); + } else { + /* reset FIFO and possibly reset I2C*/ + val = BIT_FIFO_RST; + result = inv_i2c_single_write(st, reg->user_ctrl, val); + if (result) + goto reset_fifo_fail; + st->last_isr_time = get_time_ns(); + /* enable interrupt */ + if (st->chip_config.accl_fifo_enable || + st->chip_config.gyro_fifo_enable || + st->chip_config.compass_enable) { + int_word |= BIT_DATA_RDY_EN; + } + result = inv_i2c_single_write(st, reg->int_enable, int_word); + if (result) + return result; + /* enable FIFO reading and I2C master interface*/ + val = BIT_FIFO_EN; + if (st->chip_config.compass_enable) + val |= BIT_I2C_MST_EN; + result = inv_i2c_single_write(st, reg->user_ctrl, val); + if (result) + goto reset_fifo_fail; + if (st->chip_config.compass_enable) { + /* I2C_MST_DLY is set according to sample rate, + slow down the power*/ + data = COMPASS_RATE_SCALE * + st->chip_config.fifo_rate / ONE_K_HZ; + if (data > 0) + data -= 1; + result = inv_i2c_single_write(st, REG_I2C_SLV4_CTRL, + data); + if (result) + return result; + } + /* enable sensor output to FIFO */ + val = 0; + if (st->chip_config.gyro_fifo_enable) + val |= BITS_GYRO_OUT; + if (st->chip_config.accl_fifo_enable) + val |= BIT_ACCEL_OUT; + result = inv_i2c_single_write(st, reg->fifo_en, val); + if (result) + goto reset_fifo_fail; + } + st->chip_config.normal_compass_measure = 0; + result = inv_lpa_mode(st, st->chip_config.lpa_mode); + if (result) + goto reset_fifo_fail; + + return 0; + +reset_fifo_fail: + if (st->chip_config.dmp_on) + val = BIT_DMP_INT_EN; + else + val = BIT_DATA_RDY_EN; + inv_i2c_single_write(st, reg->int_enable, val); + pr_err("reset fifo failed\n"); + + return result; +} + +/** + * inv_clear_kfifo() - clear time stamp fifo + * @st: Device driver instance. + */ +static void inv_clear_kfifo(struct inv_mpu_iio_s *st) +{ + unsigned long flags; + + spin_lock_irqsave(&st->time_stamp_lock, flags); + kfifo_reset(&st->timestamps); + spin_unlock_irqrestore(&st->time_stamp_lock, flags); +} + +/** + * inv_reset_fifo() - Reset FIFO related registers. + * @st: Device driver instance. + */ +static int inv_reset_fifo(struct iio_dev *indio_dev) +{ + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + + inv_clear_kfifo(st); + if (INV_MPU3050 == st->chip_type) + return reset_fifo_mpu3050(indio_dev); + else + return reset_fifo_itg(indio_dev); +} + +/** + * set_inv_enable() - Reset FIFO related registers. + * This also powers on the chip if needed. + * @st: Device driver instance. + * @fifo_enable: enable/disable + */ +int set_inv_enable(struct iio_dev *indio_dev, + bool enable) { + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + struct inv_reg_map_s *reg; + int result; + + if (st->chip_config.is_asleep) + return -EINVAL; + reg = &st->reg; + if (enable) { + result = inv_reset_fifo(indio_dev); + if (result) + return result; + } else { + if ((INV_MPU3050 != st->chip_type) + && st->chip_config.lpa_mode) { + result = inv_lpa_mode(st, 0); + if (result) + return result; + } + result = inv_i2c_single_write(st, reg->fifo_en, 0); + if (result) + return result; + /* disable fifo reading */ + if (INV_MPU3050 != st->chip_type) { + result = inv_i2c_single_write(st, reg->int_enable, 0); + if (result) + return result; + result = inv_i2c_single_write(st, reg->user_ctrl, 0); + } else { + result = inv_i2c_single_write(st, reg->int_enable, + st->plat_data.int_config); + } + if (result) + return result; + } + st->chip_config.enable = !!enable; + + return 0; +} + +/** + * inv_irq_handler() - Cache a timestamp at each data ready interrupt. + */ +static irqreturn_t inv_irq_handler(int irq, void *dev_id) +{ + struct inv_mpu_iio_s *st; + u64 timestamp; + int catch_up; + u64 time_since_last_irq; + + st = (struct inv_mpu_iio_s *)dev_id; + timestamp = get_time_ns(); + time_since_last_irq = timestamp - st->last_isr_time; + spin_lock(&st->time_stamp_lock); + catch_up = 0; + while ((time_since_last_irq > st->irq_dur_ns * 2) && + (catch_up < MAX_CATCH_UP) && + (!st->chip_config.lpa_mode) && + (!st->chip_config.dmp_on)) { + st->last_isr_time += st->irq_dur_ns; + kfifo_in(&st->timestamps, + &st->last_isr_time, 1); + time_since_last_irq = timestamp - st->last_isr_time; + catch_up++; + } + kfifo_in(&st->timestamps, ×tamp, 1); + st->last_isr_time = timestamp; + spin_unlock(&st->time_stamp_lock); + + return IRQ_WAKE_THREAD; +} + +static int put_scan_to_buf(struct iio_dev *indio_dev, u8 *d, + short *s, int scan_index, int d_ind) { + struct iio_buffer *ring = indio_dev->buffer; + int st; + int i; + for (i = 0; i < 3; i++) { + st = iio_scan_mask_query(indio_dev, ring, scan_index + i); + if (st) { + memcpy(&d[d_ind], &s[i], sizeof(s[i])); + d_ind += sizeof(s[i]); + } + } + return d_ind; +} + +static int put_scan_to_buf_q(struct iio_dev *indio_dev, u8 *d, + int *s, int scan_index, int d_ind) { + struct iio_buffer *ring = indio_dev->buffer; + int st; + int i; + for (i = 0; i < 4; i++) { + st = iio_scan_mask_query(indio_dev, ring, scan_index + i); + if (st) { + memcpy(&d[d_ind], &s[i], sizeof(s[i])); + d_ind += sizeof(s[i]); + } + } + return d_ind; +} + +static void inv_report_data_3050(struct iio_dev *indio_dev, s64 t, + int has_footer, u8 *data) +{ + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + int ind, i, d_ind; + struct inv_chip_config_s *conf; + short g[THREE_AXIS], a[THREE_AXIS]; + s64 buf[8]; + u8 *tmp; + int bytes_per_datum, scan_count; + conf = &st->chip_config; + + scan_count = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + bytes_per_datum = scan_count * 2; + + ind = 0; + if (has_footer) + ind += 2; + tmp = (u8 *)buf; + d_ind = 0; + + if (conf->gyro_fifo_enable) { + for (i = 0; i < ARRAY_SIZE(g); i++) { + g[i] = be16_to_cpup((__be16 *)(&data[ind + i * 2])); + st->raw_gyro[i] = g[i]; + } + ind += BYTES_PER_SENSOR; + d_ind = put_scan_to_buf(indio_dev, tmp, g, + INV_MPU_SCAN_GYRO_X, d_ind); + } + if (conf->accl_fifo_enable) { + st->mpu_slave->combine_data(&data[ind], a); + for (i = 0; i < ARRAY_SIZE(a); i++) + st->raw_accel[i] = a[i]; + + ind += BYTES_PER_SENSOR; + d_ind = put_scan_to_buf(indio_dev, tmp, a, + INV_MPU_SCAN_ACCL_X, d_ind); + } + + i = (bytes_per_datum + 7) / 8; + if (ring->scan_timestamp) + buf[i] = t; + ring->access->store_to(indio_dev->buffer, (u8 *)buf); +} + +/** + * inv_read_fifo_mpu3050() - Transfer data from FIFO to ring buffer for + * mpu3050. + */ +irqreturn_t inv_read_fifo_mpu3050(int irq, void *dev_id) +{ + + struct inv_mpu_iio_s *st = (struct inv_mpu_iio_s *)dev_id; + struct iio_dev *indio_dev = iio_priv_to_dev(st); + int bytes_per_datum; + u8 data[64]; + int result; + short fifo_count, byte_read; + u32 copied; + s64 timestamp; + struct inv_reg_map_s *reg; + reg = &st->reg; + /* It is impossible that chip is asleep or enable is + zero when interrupt is on + * because interrupt is now connected with enable */ + if (st->chip_config.dmp_on) + bytes_per_datum = BYTES_FOR_DMP; + else + bytes_per_datum = (st->chip_config.accl_fifo_enable + + st->chip_config.gyro_fifo_enable)*BYTES_PER_SENSOR; + if (st->chip_config.has_footer) + byte_read = bytes_per_datum + MPU3050_FOOTER_SIZE; + else + byte_read = bytes_per_datum; + + fifo_count = 0; + if (byte_read != 0) { + result = inv_i2c_read(st, reg->fifo_count_h, + FIFO_COUNT_BYTE, data); + if (result) + goto end_session; + fifo_count = be16_to_cpup((__be16 *)(&data[0])); + if (fifo_count < byte_read) + goto end_session; + if (fifo_count & 1) + goto flush_fifo; + if (fifo_count > FIFO_THRESHOLD) + goto flush_fifo; + /* Timestamp mismatch. */ + if (kfifo_len(&st->timestamps) < + fifo_count / byte_read) + goto flush_fifo; + if (kfifo_len(&st->timestamps) > + fifo_count / byte_read + TIME_STAMP_TOR) { + if (st->chip_config.dmp_on) { + result = kfifo_to_user(&st->timestamps, + ×tamp, sizeof(timestamp), &copied); + if (result) + goto flush_fifo; + } else { + goto flush_fifo; + } + } + } + while ((bytes_per_datum != 0) && (fifo_count >= byte_read)) { + result = inv_i2c_read(st, reg->fifo_r_w, byte_read, data); + if (result) + goto flush_fifo; + + result = kfifo_to_user(&st->timestamps, + ×tamp, sizeof(timestamp), &copied); + if (result) + goto flush_fifo; + inv_report_data_3050(indio_dev, timestamp, + st->chip_config.has_footer, data); + fifo_count -= byte_read; + if (st->chip_config.has_footer == 0) { + st->chip_config.has_footer = 1; + byte_read = bytes_per_datum + MPU3050_FOOTER_SIZE; + } + } + +end_session: + return IRQ_HANDLED; + +flush_fifo: + /* Flush HW and SW FIFOs. */ + inv_reset_fifo(indio_dev); + inv_clear_kfifo(st); + return IRQ_HANDLED; +} + +static int inv_report_gyro_accl_compass(struct iio_dev *indio_dev, + u8 *data, s64 t) +{ + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + short g[THREE_AXIS], a[THREE_AXIS], c[THREE_AXIS]; + int q[4]; + int result, ind, d_ind; + s64 buf[8]; + u32 word; + u8 d[8], compass_divider; + u8 *tmp; + int source, i; + struct inv_chip_config_s *conf; + + conf = &st->chip_config; + ind = 0; + if (conf->quaternion_on & conf->dmp_on) { + for (i = 0; i < ARRAY_SIZE(q); i++) { + q[i] = be32_to_cpup((__be32 *)(&data[ind + i * 4])); + st->raw_quaternion[i] = q[i]; + } + ind += QUATERNION_BYTES; + } + if (conf->accl_fifo_enable) { + for (i = 0; i < ARRAY_SIZE(a); i++) { + a[i] = be16_to_cpup((__be16 *)(&data[ind + i * 2])); + a[i] *= st->chip_info.multi; + st->raw_accel[i] = a[i]; + } + ind += BYTES_PER_SENSOR; + } + if (conf->gyro_fifo_enable) { + for (i = 0; i < ARRAY_SIZE(g); i++) { + g[i] = be16_to_cpup((__be16 *)(&data[ind + i * 2])); + st->raw_gyro[i] = g[i]; + } + ind += BYTES_PER_SENSOR; + } + if (conf->dmp_on && (conf->tap_on || conf->display_orient_on)) { + word = (u32)(be32_to_cpup((u32 *)&data[ind])); + source = ((word >> 16) & 0xff); + if (source) { + st->tap_data = (DMP_MASK_TAP & (word & 0xff)); + st->display_orient_data = + ((DMP_MASK_DIS_ORIEN & (word & 0xff)) >> + DMP_DIS_ORIEN_SHIFT); + } + + /* report tap information */ + if (source & INT_SRC_TAP) + sysfs_notify(&indio_dev->dev.kobj, NULL, "event_tap"); + /* report orientation information */ + if (source & INT_SRC_DISPLAY_ORIENT) + sysfs_notify(&indio_dev->dev.kobj, NULL, + "event_display_orientation"); + } + /*divider and counter is used to decrease the speed of read in + high frequency sample rate*/ + if (conf->compass_fifo_enable) { + c[0] = 0; + c[1] = 0; + c[2] = 0; + if (conf->dmp_on) + compass_divider = st->compass_dmp_divider; + else + compass_divider = st->compass_divider; + if (compass_divider == st->compass_counter) { + /*read from external sensor data register */ + result = inv_i2c_read(st, REG_EXT_SENS_DATA_00, + NUM_BYTES_COMPASS_SLAVE, d); + /* d[7] is status 2 register */ + /*for AKM8975, bit 2 and 3 should be all be zero*/ + /* for AMK8963, bit 3 should be zero*/ + if ((DATA_AKM_DRDY == d[0]) && + (0 == (d[7] & DATA_AKM_STAT_MASK)) && + (!result)) { + u8 *sens; + sens = st->chip_info.compass_sens; + c[0] = (short)((d[2] << 8) | d[1]); + c[1] = (short)((d[4] << 8) | d[3]); + c[2] = (short)((d[6] << 8) | d[5]); + c[0] = (short)(((int)c[0] * + (sens[0] + 128)) >> 8); + c[1] = (short)(((int)c[1] * + (sens[1] + 128)) >> 8); + c[2] = (short)(((int)c[2] * + (sens[2] + 128)) >> 8); + st->raw_compass[0] = c[0]; + st->raw_compass[1] = c[1]; + st->raw_compass[2] = c[2]; + } + st->compass_counter = 0; + } else if (compass_divider != 0) { + st->compass_counter++; + } + if (!conf->normal_compass_measure) { + c[0] = 0; + c[1] = 0; + c[2] = 0; + conf->normal_compass_measure = 1; + } + } + + tmp = (u8 *)buf; + d_ind = 0; + if (conf->quaternion_on & conf->dmp_on) + d_ind = put_scan_to_buf_q(indio_dev, tmp, q, + INV_MPU_SCAN_QUAT_R, d_ind); + if (conf->gyro_fifo_enable) + d_ind = put_scan_to_buf(indio_dev, tmp, g, + INV_MPU_SCAN_GYRO_X, d_ind); + if (conf->accl_fifo_enable) + d_ind = put_scan_to_buf(indio_dev, tmp, a, + INV_MPU_SCAN_ACCL_X, d_ind); + if (conf->compass_fifo_enable) + d_ind = put_scan_to_buf(indio_dev, tmp, c, + INV_MPU_SCAN_MAGN_X, d_ind); + if (ring->scan_timestamp) + buf[(d_ind + 7) / 8] = t; + /* if nothing is enabled, send nothing */ + if (d_ind) + ring->access->store_to(indio_dev->buffer, (u8 *)buf); + + return 0; +} + +static void inv_process_motion(struct inv_mpu_iio_s *st) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + s32 diff, true_motion; + s64 timestamp; + int result; + u8 data[1]; + + /* motion interrupt */ + result = inv_i2c_read(st, REG_INT_STATUS, 1, data); + if (result) + return; + + if (data[0] & BIT_MOT_INT) { + timestamp = get_time_ns(); + diff = (int)(((timestamp - st->mpu6500_last_motion_time) >> + NS_PER_MS_SHIFT)); + if (diff > st->mot_int.mot_dur) { + st->mpu6500_last_motion_time = timestamp; + true_motion = 1; + } else { + true_motion = 0; + } + if (true_motion) + sysfs_notify(&indio_dev->dev.kobj, NULL, + "event_accel_motion"); + } +} + +static int get_bytes_per_datum(struct inv_mpu_iio_s *st) +{ + int bytes_per_datum; + + bytes_per_datum = 0; + if (st->chip_config.dmp_on) { + if (st->chip_config.quaternion_on) + bytes_per_datum += QUATERNION_BYTES; + if (st->chip_config.tap_on || + st->chip_config.display_orient_on) + bytes_per_datum += BYTES_FOR_EVENTS; + } + if (st->chip_config.accl_fifo_enable) + bytes_per_datum += BYTES_PER_SENSOR; + if (st->chip_config.gyro_fifo_enable) + bytes_per_datum += BYTES_PER_SENSOR; + + return bytes_per_datum; +} + +/** + * inv_read_fifo() - Transfer data from FIFO to ring buffer. + */ +irqreturn_t inv_read_fifo(int irq, void *dev_id) +{ + + struct inv_mpu_iio_s *st = (struct inv_mpu_iio_s *)dev_id; + struct iio_dev *indio_dev = iio_priv_to_dev(st); + size_t bytes_per_datum; + int result; + u8 data[BYTES_FOR_DMP + QUATERNION_BYTES]; + u16 fifo_count; + u32 copied; + s64 timestamp; + struct inv_reg_map_s *reg; + s64 buf[8]; + s8 *tmp; + + mutex_lock(&indio_dev->mlock); + if (!(iio_buffer_enabled(indio_dev))) + goto end_session; + + reg = &st->reg; + if (!(st->chip_config.accl_fifo_enable | + st->chip_config.gyro_fifo_enable | + st->chip_config.dmp_on | + st->chip_config.compass_fifo_enable | + st->mot_int.mot_on)) + goto end_session; + if (st->mot_int.mot_on) + inv_process_motion(st); + if (st->chip_config.lpa_mode) { + result = inv_i2c_read(st, reg->raw_accl, + BYTES_PER_SENSOR, data); + if (result) + goto end_session; + inv_report_gyro_accl_compass(indio_dev, data, + get_time_ns()); + goto end_session; + } + bytes_per_datum = get_bytes_per_datum(st); + fifo_count = 0; + if (bytes_per_datum != 0) { + result = inv_i2c_read(st, reg->fifo_count_h, + FIFO_COUNT_BYTE, data); + if (result) + goto end_session; + fifo_count = be16_to_cpup((__be16 *)(&data[0])); + if (fifo_count < bytes_per_datum) + goto end_session; + /* fifo count can't be odd number */ + if (fifo_count & 1) + goto flush_fifo; + if (fifo_count > FIFO_THRESHOLD) + goto flush_fifo; + /* timestamp mismatch. */ + if (kfifo_len(&st->timestamps) < + fifo_count / bytes_per_datum) + goto flush_fifo; + if (kfifo_len(&st->timestamps) > + fifo_count / bytes_per_datum + TIME_STAMP_TOR) { + if (st->chip_config.dmp_on) { + result = kfifo_to_user(&st->timestamps, + ×tamp, sizeof(timestamp), &copied); + if (result) + goto flush_fifo; + } else { + goto flush_fifo; + } + } + } else { + result = kfifo_to_user(&st->timestamps, + ×tamp, sizeof(timestamp), &copied); + if (result) + goto flush_fifo; + } + tmp = (s8 *)buf; + while ((bytes_per_datum != 0) && (fifo_count >= bytes_per_datum)) { + result = inv_i2c_read(st, reg->fifo_r_w, bytes_per_datum, + data); + if (result) + goto flush_fifo; + + result = kfifo_to_user(&st->timestamps, + ×tamp, sizeof(timestamp), &copied); + if (result) + goto flush_fifo; + inv_report_gyro_accl_compass(indio_dev, data, timestamp); + fifo_count -= bytes_per_datum; + } + if (bytes_per_datum == 0 && st->chip_config.compass_fifo_enable) + inv_report_gyro_accl_compass(indio_dev, data, timestamp); + +end_session: + mutex_unlock(&indio_dev->mlock); + + return IRQ_HANDLED; + +flush_fifo: + /* Flush HW and SW FIFOs. */ + inv_reset_fifo(indio_dev); + inv_clear_kfifo(st); + mutex_unlock(&indio_dev->mlock); + + return IRQ_HANDLED; +} + +void inv_mpu_unconfigure_ring(struct iio_dev *indio_dev) +{ + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + free_irq(gpio_to_irq(st->client->irq), st); + iio_kfifo_free(indio_dev->buffer); +}; + +static int inv_postenable(struct iio_dev *indio_dev) +{ + return set_inv_enable(indio_dev, true); +} + +static int inv_predisable(struct iio_dev *indio_dev) +{ + return set_inv_enable(indio_dev, false); +} + +static void inv_scan_query(struct iio_dev *indio_dev) +{ + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + int result; + + if (iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_GYRO_X) || + iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_GYRO_Y) || + iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_GYRO_Z)) + st->chip_config.gyro_fifo_enable = 1; + else + st->chip_config.gyro_fifo_enable = 0; + + if (iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_ACCL_X) || + iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_ACCL_Y) || + iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_ACCL_Z)) + st->chip_config.accl_fifo_enable = 1; + else + st->chip_config.accl_fifo_enable = 0; + + if (iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_MAGN_X) || + iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_MAGN_Y) || + iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_MAGN_Z)) + st->chip_config.compass_fifo_enable = 1; + else + st->chip_config.compass_fifo_enable = 0; + + /* check to make sure engine is turned on if fifo is turned on */ + if (st->chip_config.gyro_fifo_enable && + (!st->chip_config.gyro_enable)) { + result = st->switch_gyro_engine(st, true); + if (result) + return; + st->chip_config.gyro_enable = true; + } + if (st->chip_config.accl_fifo_enable && + (!st->chip_config.accl_enable)) { + result = st->switch_accl_engine(st, true); + if (result) + return; + st->chip_config.accl_enable = true; + } +} + +static int inv_check_quaternion(struct iio_dev *indio_dev) +{ + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + int result; + + if (st->chip_config.dmp_on) { + if ( + iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_QUAT_R) || + iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_QUAT_X) || + iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_QUAT_Y) || + iio_scan_mask_query(indio_dev, ring, INV_MPU_SCAN_QUAT_Z)) + st->chip_config.quaternion_on = 1; + else + st->chip_config.quaternion_on = 0; + + result = inv_send_quaternion(st, + st->chip_config.quaternion_on); + if (result) + return result; + } else { + st->chip_config.quaternion_on = 0; + clear_bit(INV_MPU_SCAN_QUAT_R, ring->scan_mask); + clear_bit(INV_MPU_SCAN_QUAT_X, ring->scan_mask); + clear_bit(INV_MPU_SCAN_QUAT_Y, ring->scan_mask); + clear_bit(INV_MPU_SCAN_QUAT_Z, ring->scan_mask); + } + + return 0; +} + +static int inv_check_conflict_sysfs(struct iio_dev *indio_dev) +{ + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + int result; + + if (st->chip_config.lpa_mode) { + /* dmp cannot run with low power mode on */ + st->chip_config.dmp_on = 0; + result = st->gyro_en(st, ring, false); + if (result) + return result; + result = st->compass_en(st, ring, false); + if (result) + return result; + result = st->quaternion_en(st, ring, false); + if (result) + return result; + + result = st->accl_en(st, ring, true); + if (result) + return result; + } + result = inv_check_quaternion(indio_dev); + if (result) + return result; + + return result; +} + +static int inv_preenable(struct iio_dev *indio_dev) +{ + int result; + + result = inv_check_conflict_sysfs(indio_dev); + if (result) + return result; + inv_scan_query(indio_dev); + result = iio_sw_buffer_preenable(indio_dev); + + return result; +} + +static const struct iio_buffer_setup_ops inv_mpu_ring_setup_ops = { + .preenable = &inv_preenable, + .postenable = &inv_postenable, + .predisable = &inv_predisable, +}; + +int inv_mpu_configure_ring(struct iio_dev *indio_dev) +{ + int ret; + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + struct iio_buffer *ring; + + ring = iio_kfifo_allocate(indio_dev); + if (!ring) + return -ENOMEM; + indio_dev->buffer = ring; + /* setup ring buffer */ + ring->scan_timestamp = true; + indio_dev->setup_ops = &inv_mpu_ring_setup_ops; + /*scan count double count timestamp. should subtract 1. but + number of channels still includes timestamp*/ + if (INV_MPU3050 == st->chip_type) + ret = request_threaded_irq(gpio_to_irq(st->client->irq), inv_irq_handler, + inv_read_fifo_mpu3050, + IRQF_TRIGGER_RISING | IRQF_SHARED, "inv_irq", st); + else + ret = request_threaded_irq(gpio_to_irq(st->client->irq), inv_irq_handler, + inv_read_fifo, + IRQF_TRIGGER_RISING | IRQF_SHARED, "inv_irq", st); + if (ret) + goto error_iio_sw_rb_free; + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; +error_iio_sw_rb_free: + iio_kfifo_free(indio_dev->buffer); + return ret; +} + +/** + * @} + */ + diff --git a/drivers/iio/imu/inv_mpu/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu/inv_mpu_trigger.c new file mode 100644 index 00000000..4e01ad15 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_mpu_trigger.c @@ -0,0 +1,92 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_mpu_trigger.c + * @brief A sysfs device driver for Invensense devices + * @details This file is part of inv mpu iio driver code + */ + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> +#include <linux/spinlock.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> + +#include "inv_mpu_iio.h" + +/** + * inv_mpu_data_rdy_trigger_set_state() set data ready interrupt state + **/ +static int inv_mpu_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + return 0; +} + +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_mpu_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + + st->trig = iio_trigger_alloc("%s-dev%d", + indio_dev->name, + indio_dev->id); + if (st->trig == NULL) + return -ENOMEM; + st->trig->dev.parent = &st->client->dev; + st->trig->ops = &inv_mpu_trigger_ops; + ret = iio_trigger_register(st->trig); + + if (ret) { + iio_trigger_free(st->trig); + return -EPERM; + } + indio_dev->trig = st->trig; + + return 0; +} + +void inv_mpu_remove_trigger(struct iio_dev *indio_dev) +{ + struct inv_mpu_iio_s *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + iio_trigger_free(st->trig); +} +/** + * @} + */ + diff --git a/drivers/iio/imu/inv_mpu/inv_slave_bma250.c b/drivers/iio/imu/inv_mpu/inv_slave_bma250.c new file mode 100644 index 00000000..bd84f637 --- /dev/null +++ b/drivers/iio/imu/inv_mpu/inv_slave_bma250.c @@ -0,0 +1,330 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_slave_bma250.c + * @brief A sysfs device driver for Invensense devices + * @details This file is part of invensense mpu driver code + * + */ + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> +#include <linux/spinlock.h> + +#include "inv_mpu_iio.h" +#define BMA250_CHIP_ID 3 +#define BMA250_RANGE_SET 0 +#define BMA250_BW_SET 4 + +/* range and bandwidth */ +#define BMA250_RANGE_2G 3 +#define BMA250_RANGE_4G 5 +#define BMA250_RANGE_8G 8 +#define BMA250_RANGE_16G 12 +#define BMA250_RANGE_MAX 4 +#define BMA250_RANGE_MASK 0xF0 + +#define BMA250_BW_7_81HZ 0x08 +#define BMA250_BW_15_63HZ 0x09 +#define BMA250_BW_31_25HZ 0x0A +#define BMA250_BW_62_50HZ 0x0B +#define BMA250_BW_125HZ 0x0C +#define BMA250_BW_250HZ 0x0D +#define BMA250_BW_500HZ 0x0E +#define BMA250_BW_1000HZ 0x0F +#define BMA250_MAX_BW_SIZE 8 +#define BMA250_BW_REG_MASK 0xE0 + +/* register definitions */ +#define BMA250_X_AXIS_LSB_REG 0x02 +#define BMA250_RANGE_SEL_REG 0x0F +#define BMA250_BW_SEL_REG 0x10 +#define BMA250_MODE_CTRL_REG 0x11 + +/* mode settings */ +#define BMA250_MODE_NORMAL 0 +#define BMA250_MODE_LOWPOWER 1 +#define BMA250_MODE_SUSPEND 2 +#define BMA250_MODE_MAX 3 +#define BMA250_MODE_MASK 0x3F +#define BMA250_BIT_SUSPEND 0x80 +#define BMA250_BIT_LP 0x40 + +struct bma_property { + int range; + int bandwidth; + int mode; +}; + +static struct bma_property bma_static_property = { + .range = BMA250_RANGE_SET, + .bandwidth = BMA250_BW_SET, + .mode = BMA250_MODE_SUSPEND +}; + +static int bma250_set_bandwidth(struct inv_mpu_iio_s *st, u8 bw) +{ + int res; + u8 data; + int bandwidth; + switch (bw) { + case 0: + bandwidth = BMA250_BW_7_81HZ; + break; + case 1: + bandwidth = BMA250_BW_15_63HZ; + break; + case 2: + bandwidth = BMA250_BW_31_25HZ; + break; + case 3: + bandwidth = BMA250_BW_62_50HZ; + break; + case 4: + bandwidth = BMA250_BW_125HZ; + break; + case 5: + bandwidth = BMA250_BW_250HZ; + break; + case 6: + bandwidth = BMA250_BW_500HZ; + break; + case 7: + bandwidth = BMA250_BW_1000HZ; + break; + default: + return -EINVAL; + } + res = inv_secondary_read(BMA250_BW_SEL_REG, 1, &data); + if (res) + return res; + data &= BMA250_BW_REG_MASK; + data |= bandwidth; + res = inv_secondary_write(BMA250_BW_SEL_REG, data); + return res; +} + +static int bma250_set_range(struct inv_mpu_iio_s *st, u8 range) +{ + int res; + u8 orig, data; + switch (range) { + case 0: + data = BMA250_RANGE_2G; + break; + case 1: + data = BMA250_RANGE_4G; + break; + case 2: + data = BMA250_RANGE_8G; + break; + case 3: + data = BMA250_RANGE_16G; + break; + default: + return -EINVAL; + } + res = inv_secondary_read(BMA250_RANGE_SEL_REG, 1, &orig); + if (res) + return res; + orig &= BMA250_RANGE_MASK; + data |= orig; + res = inv_secondary_write(BMA250_RANGE_SEL_REG, data); + if (res) + return res; + bma_static_property.range = range; + + return 0; +} + +static int setup_slave_bma250(struct inv_mpu_iio_s *st) +{ + int result; + u8 data[2]; + result = set_3050_bypass(st, true); + if (result) + return result; + /*read secondary i2c ID register */ + result = inv_secondary_read(0, 1, data); + if (result) + return result; + if (BMA250_CHIP_ID != data[0]) + return -EINVAL; + result = set_3050_bypass(st, false); + if (result) + return result; + /*AUX(accel), slave address is set inside set_3050_bypass*/ + /* bma250 x axis LSB register address is 2 */ + result = inv_i2c_single_write(st, REG_3050_AUX_BST_ADDR, + BMA250_X_AXIS_LSB_REG); + + return result; +} + +static int bma250_set_mode(struct inv_mpu_iio_s *st, u8 mode) +{ + int res; + u8 data; + + res = inv_secondary_read(BMA250_MODE_CTRL_REG, 1, &data); + if (res) + return res; + data &= BMA250_MODE_MASK; + switch (mode) { + case BMA250_MODE_NORMAL: + break; + case BMA250_MODE_LOWPOWER: + data |= BMA250_BIT_LP; + break; + case BMA250_MODE_SUSPEND: + data |= BMA250_BIT_SUSPEND; + break; + default: + return -EINVAL; + } + res = inv_secondary_write(BMA250_MODE_CTRL_REG, data); + if (res) + return res; + bma_static_property.mode = mode; + + return 0; +} + +static int suspend_slave_bma250(struct inv_mpu_iio_s *st) +{ + int result; + if (bma_static_property.mode == BMA250_MODE_SUSPEND) + return 0; + /*set to bypass mode */ + result = set_3050_bypass(st, true); + if (result) + return result; + bma250_set_mode(st, BMA250_MODE_SUSPEND); + /* no need to recover to non-bypass mode because we need it now */ + + return 0; +} + +static int resume_slave_bma250(struct inv_mpu_iio_s *st) +{ + int result; + if (bma_static_property.mode == BMA250_MODE_NORMAL) + return 0; + /*set to bypass mode */ + result = set_3050_bypass(st, true); + if (result) + return result; + result = bma250_set_mode(st, BMA250_MODE_NORMAL); + /* recover bypass mode */ + result |= set_3050_bypass(st, false); + + return result ? (-EINVAL) : 0; +} + +static int combine_data_slave_bma250(u8 *in, short *out) +{ + out[0] = le16_to_cpup((__le16 *)(&in[0])); + out[1] = le16_to_cpup((__le16 *)(&in[2])); + out[2] = le16_to_cpup((__le16 *)(&in[4])); + + return 0; +} + +static int get_mode_slave_bma250(void) +{ + switch (bma_static_property.mode) { + case BMA250_MODE_SUSPEND: + return INV_MODE_SUSPEND; + case BMA250_MODE_NORMAL: + return INV_MODE_NORMAL; + default: + return -EINVAL; + } +} + +/** + * set_lpf_bma250() - set lpf value + */ + +static int set_lpf_bma250(struct inv_mpu_iio_s *st, int rate) +{ + const short hz[] = {1000, 500, 250, 125, 62, 31, 15, 7}; + const int d[] = {7, 6, 5, 4, 3, 2, 1, 0}; + int i, h, data, result; + h = (rate >> 1); + i = 0; + while ((h < hz[i]) && (i < ARRAY_SIZE(hz) - 1)) + i++; + data = d[i]; + + result = set_3050_bypass(st, true); + if (result) + return result; + result = bma250_set_bandwidth(st, (u8) data); + result |= set_3050_bypass(st, false); + + return result ? (-EINVAL) : 0; +} +/** + * set_fs_bma250() - set range value + */ + +static int set_fs_bma250(struct inv_mpu_iio_s *st, int fs) +{ + int result; + result = set_3050_bypass(st, true); + if (result) + return result; + result = bma250_set_range(st, (u8) fs); + result |= set_3050_bypass(st, false); + + return result ? (-EINVAL) : 0; +} + +static struct inv_mpu_slave slave_bma250 = { + .suspend = suspend_slave_bma250, + .resume = resume_slave_bma250, + .setup = setup_slave_bma250, + .combine_data = combine_data_slave_bma250, + .get_mode = get_mode_slave_bma250, + .set_lpf = set_lpf_bma250, + .set_fs = set_fs_bma250 +}; + +int inv_register_mpu3050_slave(struct inv_mpu_iio_s *st) +{ + st->mpu_slave = &slave_bma250; + + return 0; +} +/** + * @} + */ + 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 00000000..111ac381 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -0,0 +1,798 @@ +/* +* 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/init.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 "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 stablize */ + 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_READ_I2C_BLOCK | + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) { + result = -ENOSYS; + goto out_no_free; + } + indio_dev = iio_device_alloc(sizeof(*st)); + if (indio_dev == NULL) { + result = -ENOMEM; + goto out_no_free; + } + 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) + goto out_free; + + result = inv_mpu6050_init_config(indio_dev); + if (result) { + dev_err(&client->dev, + "Could not initialize device.\n"); + goto out_free; + } + + 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); + goto out_free; + } + 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); +out_free: + iio_device_free(indio_dev); +out_no_free: + + 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); + iio_device_free(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}, + {} +}; + +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_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c new file mode 100644 index 00000000..926fccea --- /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 = iio_trigger_get(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 00000000..e13c5f4b --- /dev/null +++ b/drivers/iio/industrialio-buffer.c @@ -0,0 +1,938 @@ +/* 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/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_dev *indio_dev, + struct iio_buffer *buf) +{ + struct list_head *p; + + list_for_each(p, &indio_dev->buffer_list) + if (p == &buf->buffer_list) + return true; + + return false; +} + +/** + * iio_buffer_read_first_n_outer() - chrdev read for buffer access + * + * This function relies on all buffer implementations having an + * iio_buffer as their first element. + **/ +ssize_t iio_buffer_read_first_n_outer(struct file *filp, char __user *buf, + size_t n, loff_t *f_ps) +{ + struct iio_dev *indio_dev = filp->private_data; + struct iio_buffer *rb = indio_dev->buffer; + + if (!rb || !rb->access->read_first_n) + return -EINVAL; + return rb->access->read_first_n(rb, n, buf); +} + +/** + * 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; + + poll_wait(filp, &rb->pollq, wait); + if (rb->stufftoread) + return POLLIN | POLLRDNORM; + /* need a way of knowing if there may be enough data... */ + return 0; +} + +void iio_buffer_init(struct iio_buffer *buffer) +{ + INIT_LIST_HEAD(&buffer->demux_list); + init_waitqueue_head(&buffer->pollq); +} +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 + } + 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, 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, 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, + 0, + &indio_dev->dev, + &buffer->scan_el_dev_attr_list); + if (ret) + goto error_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) + goto error_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); + attrcount++; + ret = attrcount; +error_ret: + return ret; +} + +static void iio_buffer_remove_and_free_scan_dev_attr(struct iio_dev *indio_dev, + struct iio_dev_attr *p) +{ + kfree(p->dev_attr.attr.name); + kfree(p); +} + +static void __iio_buffer_attr_cleanup(struct iio_dev *indio_dev) +{ + struct iio_dev_attr *p, *n; + struct iio_buffer *buffer = indio_dev->buffer; + + list_for_each_entry_safe(p, n, + &buffer->scan_el_dev_attr_list, l) + iio_buffer_remove_and_free_scan_dev_attr(indio_dev, p); +} + +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_buffer_attr_cleanup(indio_dev); + + 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_buffer_attr_cleanup(indio_dev); +} +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, 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, + 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 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); + 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); + length = ch->scan_type.storagebits / 8; + bytes = ALIGN(bytes, length); + bytes += length; + } + return bytes; +} + +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) + goto error_ret; + } + indio_dev->currentmode = INDIO_DIRECT_MODE; + if (indio_dev->setup_ops->postdisable) { + ret = indio_dev->setup_ops->postdisable(indio_dev); + if (ret) + goto error_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) + list_del(&remove_buffer->buffer_list); + if (insert_buffer) + list_add(&insert_buffer->buffer_list, &indio_dev->buffer_list); + + /* 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. + */ + list_del(&insert_buffer->buffer_list); + indio_dev->active_scan_mask = old_mask; + success = -EINVAL; + } + } 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\n"); + 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) + if (buffer->access->request_update) { + ret = buffer->access->request_update(buffer); + if (ret) { + printk(KERN_INFO + "Buffer not started:" + "buffer parameter update failed\n"); + 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 "update scan mode failed\n"); + 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\n"); + 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) + list_del(&insert_buffer->buffer_list); + indio_dev->active_scan_mask = old_mask; + kfree(compound_mask); +error_ret: + + 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); + struct iio_buffer *pbuf = indio_dev->buffer; + 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, pbuf); + /* 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); + +int iio_sw_buffer_preenable(struct iio_dev *indio_dev) +{ + struct iio_buffer *buffer; + unsigned bytes; + dev_dbg(&indio_dev->dev, "%s\n", __func__); + + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) + if (buffer->access->set_bytes_per_datum) { + bytes = iio_compute_scan_bytes(indio_dev, + buffer->scan_mask, + buffer->scan_timestamp); + + buffer->access->set_bytes_per_datum(buffer, bytes); + } + return 0; +} +EXPORT_SYMBOL(iio_sw_buffer_preenable); + +/** + * 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 + * @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 unsigned char *iio_demux(struct iio_buffer *buffer, + unsigned char *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, unsigned char *data) +{ + unsigned char *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, unsigned char *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); + 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); + 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); + 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); diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c new file mode 100644 index 00000000..dd576560 --- /dev/null +++ b/drivers/iio/industrialio-core.c @@ -0,0 +1,1046 @@ +/* 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. + */ + +#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> + +/* 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_QUATERNION] = "quaternion", +}; + +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_R] = "r", +}; + +/* 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", +}; + +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) { + printk(KERN_ERR + "%s could not register bus type\n", + __FILE__); + goto error_nothing; + } + + ret = alloc_chrdev_region(&iio_devt, 0, IIO_DEV_MAX, "iio"); + if (ret < 0) { + printk(KERN_ERR "%s: failed to allocate char dev region\n", + __FILE__); + 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 sprintf(buf, "%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); + +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); + unsigned long long tmp; + int val, val2; + bool scale_db = false; + int ret = indio_dev->info->read_raw(indio_dev, this_attr->c, + &val, &val2, this_attr->address); + + if (ret < 0) + return ret; + + switch (ret) { + case IIO_VAL_INT: + return sprintf(buf, "%d\n", val); + case IIO_VAL_INT_PLUS_MICRO_DB: + scale_db = true; + case IIO_VAL_INT_PLUS_MICRO: + if (val2 < 0) + return sprintf(buf, "-%d.%06u%s\n", val, -val2, + scale_db ? " dB" : ""); + else + return sprintf(buf, "%d.%06u%s\n", val, val2, + scale_db ? " dB" : ""); + case IIO_VAL_INT_PLUS_NANO: + if (val2 < 0) + return sprintf(buf, "-%d.%09u\n", val, -val2); + else + return sprintf(buf, "%d.%09u\n", val, val2); + case IIO_VAL_FRACTIONAL: + tmp = div_s64((s64)val * 1000000000LL, val2); + val2 = do_div(tmp, 1000000000LL); + val = tmp; + return sprintf(buf, "%d.%09u\n", val, val2); + case IIO_VAL_FRACTIONAL_LOG2: + tmp = (s64)val * 1000000000LL >> val2; + val2 = do_div(tmp, 1000000000LL); + val = tmp; + return sprintf(buf, "%d.%09u\n", val, val2); + default: + return 0; + } +} + +/** + * 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), + bool generic) +{ + int ret; + char *name_format, *full_postfix; + sysfs_attr_init(&dev_attr->attr); + + /* Build up postfix of <extend_name>_<modifier>_postfix */ + if (chan->modified && !generic) { + 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) + full_postfix = kstrdup(postfix, GFP_KERNEL); + else + full_postfix = kasprintf(GFP_KERNEL, + "%s_%s", + chan->extend_name, + postfix); + } + if (full_postfix == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + if (chan->differential) { /* Differential can not have modifier */ + if (generic) + name_format + = 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); + else if (chan->indexed) + name_format + = 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); + else { + WARN_ON("Differential channels must be indexed\n"); + ret = -EINVAL; + goto error_free_full_postfix; + } + } else { /* Single ended */ + if (generic) + name_format + = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + full_postfix); + else if (chan->indexed) + name_format + = kasprintf(GFP_KERNEL, "%s_%s%d_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + chan->channel, + full_postfix); + else + name_format + = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + full_postfix); + } + if (name_format == NULL) { + ret = -ENOMEM; + goto error_free_full_postfix; + } + dev_attr->attr.name = kasprintf(GFP_KERNEL, + name_format, + chan->channel, + chan->channel2); + if (dev_attr->attr.name == NULL) { + ret = -ENOMEM; + goto error_free_name_format; + } + + if (readfunc) { + dev_attr->attr.mode |= S_IRUGO; + dev_attr->show = readfunc; + } + + if (writefunc) { + dev_attr->attr.mode |= S_IWUSR; + dev_attr->store = writefunc; + } + kfree(name_format); + kfree(full_postfix); + + return 0; + +error_free_name_format: + kfree(name_format); +error_free_full_postfix: + kfree(full_postfix); +error_ret: + 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, + bool generic, + 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) { + ret = -ENOMEM; + goto error_ret; + } + ret = __iio_device_attr_init(&iio_attr->dev_attr, + postfix, chan, + readfunc, writefunc, generic); + 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 (!generic) + 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); +error_ret: + return ret; +} + +static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + int ret, attrcount = 0; + int i; + const struct iio_chan_spec_ext_info *ext_info; + + if (chan->channel < 0) + return 0; + for_each_set_bit(i, &chan->info_mask_separate, sizeof(long)*8) { + ret = __iio_add_chan_devattr(iio_chan_info_postfix[i], + chan, + &iio_read_channel_info, + &iio_write_channel_info, + i, + 0, + &indio_dev->dev, + &indio_dev->channel_attr_list); + if (ret < 0) + goto error_ret; + attrcount++; + } + for_each_set_bit(i, &chan->info_mask_shared_by_type, sizeof(long)*8) { + ret = __iio_add_chan_devattr(iio_chan_info_postfix[i], + chan, + &iio_read_channel_info, + &iio_write_channel_info, + i, + 1, + &indio_dev->dev, + &indio_dev->channel_attr_list); + if (ret == -EBUSY) { + ret = 0; + continue; + } else if (ret < 0) { + goto error_ret; + } + attrcount++; + } + + 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) + goto error_ret; + + attrcount++; + } + } + + ret = attrcount; +error_ret: + return ret; +} + +static void iio_device_remove_and_free_read_attr(struct iio_dev *indio_dev, + struct iio_dev_attr *p) +{ + 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 sprintf(buf, "%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, *n; + 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: + list_for_each_entry_safe(p, n, + &indio_dev->channel_attr_list, l) { + list_del(&p->l); + iio_device_remove_and_free_read_attr(indio_dev, p); + } + + return ret; +} + +static void iio_device_unregister_sysfs(struct iio_dev *indio_dev) +{ + + struct iio_dev_attr *p, *n; + + list_for_each_entry_safe(p, n, &indio_dev->channel_attr_list, l) { + list_del(&p->l); + iio_device_remove_and_free_read_attr(indio_dev, p); + } + 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->chrdev.dev) + cdev_del(&indio_dev->chrdev); + 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_device_unregister_debugfs(indio_dev); + + ida_simple_remove(&iio_ida, indio_dev->id); + kfree(indio_dev); +} + +struct device_type iio_device_type = { + .name = "iio_device", + .release = iio_dev_release, +}; + +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 */ + printk(KERN_ERR "Failed to get 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); + +void iio_device_free(struct iio_dev *dev) +{ + if (dev) + put_device(&dev->dev); +} +EXPORT_SYMBOL(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; + + 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); + 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 (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; + +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"); + goto error_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; + + ret = device_add(&indio_dev->dev); + if (ret < 0) + goto error_unreg_eventset; + cdev_init(&indio_dev->chrdev, &iio_buffer_fileops); + indio_dev->chrdev.owner = indio_dev->info->driver_module; + ret = cdev_add(&indio_dev->chrdev, indio_dev->dev.devt, 1); + if (ret < 0) + goto error_del_device; + return 0; + +error_del_device: + device_del(&indio_dev->dev); +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); +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_device_register); + +void iio_device_unregister(struct iio_dev *indio_dev) +{ + mutex_lock(&indio_dev->info_exist_lock); + indio_dev->info = NULL; + mutex_unlock(&indio_dev->info_exist_lock); + device_del(&indio_dev->dev); +} +EXPORT_SYMBOL(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 00000000..145c9861 --- /dev/null +++ b/drivers/iio/industrialio-event.c @@ -0,0 +1,456 @@ +/* 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; + struct mutex read_lock; + DECLARE_KFIFO(det_events, struct iio_event_data, 16); + + struct list_head dev_attr_list; + unsigned long flags; + struct attribute_group group; +}; + +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; + unsigned long flags; + int copied; + + /* Does anyone care? */ + spin_lock_irqsave(&ev_int->wait.lock, flags); + 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_locked_poll(&ev_int->wait, POLLIN); + } + spin_unlock_irqrestore(&ev_int->wait.lock, flags); + + 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_event_interface *ev_int = filep->private_data; + unsigned int events = 0; + + poll_wait(filep, &ev_int->wait, wait); + + spin_lock_irq(&ev_int->wait.lock); + if (!kfifo_is_empty(&ev_int->det_events)) + events = POLLIN | POLLRDNORM; + spin_unlock_irq(&ev_int->wait.lock); + + return events; +} + +static ssize_t iio_event_chrdev_read(struct file *filep, + char __user *buf, + size_t count, + loff_t *f_ps) +{ + struct iio_event_interface *ev_int = filep->private_data; + unsigned int copied; + int ret; + + if (count < sizeof(struct iio_event_data)) + return -EINVAL; + + if (mutex_lock_interruptible(&ev_int->read_lock)) + return -ERESTARTSYS; + + if (kfifo_is_empty(&ev_int->det_events)) { + if (filep->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto error_unlock; + } + /* Blocking on device; waiting for something to be there */ + ret = wait_event_interruptible(ev_int->wait, + !kfifo_is_empty(&ev_int->det_events)); + if (ret) + goto error_unlock; + /* Single access device so no one else can get the data */ + } + + ret = kfifo_to_user(&ev_int->det_events, buf, count, &copied); + +error_unlock: + mutex_unlock(&ev_int->read_lock); + + return ret ? ret : copied; +} + +static int iio_event_chrdev_release(struct inode *inode, struct file *filep) +{ + struct iio_event_interface *ev_int = filep->private_data; + + spin_lock_irq(&ev_int->wait.lock); + __clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags); + /* + * In order to maintain a clean state for reopening, + * clear out any awaiting events. The mask will prevent + * any new __iio_push_event calls running. + */ + kfifo_reset_out(&ev_int->det_events); + spin_unlock_irq(&ev_int->wait.lock); + + 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; + + spin_lock_irq(&ev_int->wait.lock); + if (__test_and_set_bit(IIO_BUSY_BIT_POS, &ev_int->flags)) { + spin_unlock_irq(&ev_int->wait.lock); + return -EBUSY; + } + spin_unlock_irq(&ev_int->wait.lock); + fd = anon_inode_getfd("iio:event", + &iio_event_chrdev_fileops, ev_int, O_RDONLY); + if (fd < 0) { + spin_lock_irq(&ev_int->wait.lock); + __clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags); + spin_unlock_irq(&ev_int->wait.lock); + } + 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 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->address, + 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 = indio_dev->info->read_event_config(indio_dev, + this_attr->address); + + 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, ret; + + ret = indio_dev->info->read_event_value(indio_dev, + this_attr->address, &val); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", val); +} + +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; + int ret; + + if (!indio_dev->info->write_event_value) + return -EINVAL; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + ret = indio_dev->info->write_event_value(indio_dev, this_attr->address, + val); + if (ret < 0) + return ret; + + return len; +} + +static int iio_device_add_event_sysfs(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + int ret = 0, i, attrcount = 0; + u64 mask = 0; + char *postfix; + if (!chan->event_mask) + return 0; + + for_each_set_bit(i, &chan->event_mask, sizeof(chan->event_mask)*8) { + postfix = kasprintf(GFP_KERNEL, "%s_%s_en", + iio_ev_type_text[i/IIO_EV_DIR_MAX], + iio_ev_dir_text[i%IIO_EV_DIR_MAX]); + if (postfix == NULL) { + ret = -ENOMEM; + goto error_ret; + } + if (chan->modified) + mask = IIO_MOD_EVENT_CODE(chan->type, 0, chan->channel, + i/IIO_EV_DIR_MAX, + i%IIO_EV_DIR_MAX); + else if (chan->differential) + mask = IIO_EVENT_CODE(chan->type, + 0, 0, + i%IIO_EV_DIR_MAX, + i/IIO_EV_DIR_MAX, + 0, + chan->channel, + chan->channel2); + else + mask = IIO_UNMOD_EVENT_CODE(chan->type, + chan->channel, + i/IIO_EV_DIR_MAX, + i%IIO_EV_DIR_MAX); + + ret = __iio_add_chan_devattr(postfix, + chan, + &iio_ev_state_show, + iio_ev_state_store, + mask, + 0, + &indio_dev->dev, + &indio_dev->event_interface-> + dev_attr_list); + kfree(postfix); + if (ret) + goto error_ret; + attrcount++; + postfix = kasprintf(GFP_KERNEL, "%s_%s_value", + iio_ev_type_text[i/IIO_EV_DIR_MAX], + iio_ev_dir_text[i%IIO_EV_DIR_MAX]); + if (postfix == NULL) { + ret = -ENOMEM; + goto error_ret; + } + ret = __iio_add_chan_devattr(postfix, chan, + iio_ev_value_show, + iio_ev_value_store, + mask, + 0, + &indio_dev->dev, + &indio_dev->event_interface-> + dev_attr_list); + kfree(postfix); + if (ret) + goto error_ret; + attrcount++; + } + ret = attrcount; +error_ret: + return ret; +} + +static inline void __iio_remove_event_config_attrs(struct iio_dev *indio_dev) +{ + struct iio_dev_attr *p, *n; + list_for_each_entry_safe(p, n, + &indio_dev->event_interface-> + dev_attr_list, l) { + kfree(p->dev_attr.attr.name); + kfree(p); + } +} + +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].event_mask != 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) { + ret = -ENOMEM; + goto error_ret; + } + + 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_remove_event_config_attrs(indio_dev); + mutex_destroy(&indio_dev->event_interface->read_lock); + kfree(indio_dev->event_interface); +error_ret: + + return ret; +} + +void iio_device_unregister_eventset(struct iio_dev *indio_dev) +{ + if (indio_dev->event_interface == NULL) + return; + __iio_remove_event_config_attrs(indio_dev); + kfree(indio_dev->event_interface->group.attrs); + mutex_destroy(&indio_dev->event_interface->read_lock); + kfree(indio_dev->event_interface); +} diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c new file mode 100644 index 00000000..4d6c7d84 --- /dev/null +++ b/drivers/iio/industrialio-trigger.c @@ -0,0 +1,496 @@ +/* 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, +}; + +static struct attribute_group iio_trig_attr_group = { + .attrs = iio_trig_dev_attrs, +}; + +static const struct attribute_group *iio_trig_attr_groups[] = { + &iio_trig_attr_group, + NULL +}; + +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) { + ret = trig_info->id; + goto error_ret; + } + /* 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); +error_ret: + 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_unregister(&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 (!trig->use_count) + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) + if (trig->subirqs[i].enabled) { + trig->use_count++; + generic_handle_irq(trig->subirq_base + i); + } +} +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 (!trig->use_count) + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) + if (trig->subirqs[i].enabled) { + trig->use_count++; + handle_nested_irq(trig->subirq_base + i); + } +} +EXPORT_SYMBOL(iio_trigger_poll_chained); + +void iio_trigger_notify_done(struct iio_trigger *trig) +{ + trig->use_count--; + if (trig->use_count == 0 && 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) + goto error_ret; + } + iio_trigger_put_irq(trig, pf->irq); + free_irq(pf->irq, pf); + module_put(pf->indio_dev->info->driver_module); + +error_ret: + 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 triggers + * 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 && indio_dev->trig != 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_attr_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; +} + +struct iio_trigger *iio_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; + } + va_start(vargs, fmt); + trig->name = kvasprintf(GFP_KERNEL, fmt, vargs); + va_end(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; +} +EXPORT_SYMBOL(iio_trigger_alloc); + +void iio_trigger_free(struct iio_trigger *trig) +{ + if (trig) + put_device(&trig->dev); +} +EXPORT_SYMBOL(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 00000000..46c619b0 --- /dev/null +++ b/drivers/iio/industrialio-triggered-buffer.c @@ -0,0 +1,110 @@ + /* + * 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 = { + .preenable = &iio_sw_buffer_preenable, + .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) +{ + int ret; + + indio_dev->buffer = iio_kfifo_allocate(indio_dev); + if (!indio_dev->buffer) { + ret = -ENOMEM; + goto error_ret; + } + + 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 00000000..4fc88e61 --- /dev/null +++ b/drivers/iio/inkern.c @@ -0,0 +1,580 @@ +/* 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) || PTR_ERR(chan) == -EPROBE_DEFER) + break; + else if (name && index >= 0) { + pr_err("ERROR: could not get IIO channel %s:%s(%i)\n", + np->full_name, name ? name : "", index); + return NULL; + } + + /* + * No matching IIO channel found on this node. + * If the parent node has a "io-channel-ranges" property, + * then we can try one of its channels. + */ + np = np->parent; + if (np && !of_get_property(np, "io-channel-ranges", NULL)) + return NULL; + } + + return chan; +} + +static struct iio_channel *of_iio_channel_get_all(struct device *dev) +{ + struct iio_channel *chans; + int i, mapind, nummaps = 0; + int ret; + + do { + ret = of_parse_phandle_with_args(dev->of_node, + "io-channels", + "#io-channel-cells", + nummaps, NULL); + if (ret < 0) + break; + } while (++nummaps); + + if (nummaps == 0) /* no error, return NULL to search map table */ + return NULL; + + /* NULL terminated array to save passing size */ + chans = kcalloc(nummaps + 1, sizeof(*chans), GFP_KERNEL); + if (chans == NULL) + return ERR_PTR(-ENOMEM); + + /* Search for OF matches */ + for (mapind = 0; mapind < nummaps; mapind++) { + ret = __of_iio_channel_get(&chans[mapind], dev->of_node, + mapind); + if (ret) + goto error_free_chans; + } + return chans; + +error_free_chans: + for (i = 0; i < mapind; i++) + iio_device_put(chans[i].indio_dev); + kfree(chans); + return ERR_PTR(ret); +} + +#else /* CONFIG_OF */ + +static inline struct iio_channel * +of_iio_channel_get_by_name(struct device_node *np, const char *name) +{ + return NULL; +} + +static inline struct iio_channel *of_iio_channel_get_all(struct device *dev) +{ + return NULL; +} + +#endif /* CONFIG_OF */ + +static struct iio_channel *iio_channel_get_sys(const char *name, + const char *channel_name) +{ + struct iio_map_internal *c_i = NULL, *c = NULL; + struct iio_channel *channel; + int err; + + if (name == NULL && channel_name == NULL) + return ERR_PTR(-ENODEV); + + /* first find matching entry the channel map */ + mutex_lock(&iio_map_list_lock); + list_for_each_entry(c_i, &iio_map_list, l) { + if ((name && strcmp(name, c_i->map->consumer_dev_name) != 0) || + (channel_name && + strcmp(channel_name, c_i->map->consumer_channel) != 0)) + continue; + c = c_i; + iio_device_get(c->indio_dev); + break; + } + mutex_unlock(&iio_map_list_lock); + if (c == NULL) + return ERR_PTR(-ENODEV); + + channel = kzalloc(sizeof(*channel), GFP_KERNEL); + if (channel == NULL) { + err = -ENOMEM; + goto error_no_mem; + } + + channel->indio_dev = c->indio_dev; + + if (c->map->adc_channel_label) { + channel->channel = + iio_chan_spec_from_name(channel->indio_dev, + c->map->adc_channel_label); + + if (channel->channel == NULL) { + err = -EINVAL; + goto error_no_chan; + } + } + + return channel; + +error_no_chan: + kfree(channel); +error_no_mem: + iio_device_put(c->indio_dev); + return ERR_PTR(err); +} + +struct iio_channel *iio_channel_get(struct device *dev, + const char *channel_name) +{ + const char *name = dev ? dev_name(dev) : NULL; + struct iio_channel *channel; + + if (dev) { + channel = of_iio_channel_get_by_name(dev->of_node, + channel_name); + if (channel != NULL) + return channel; + } + + return iio_channel_get_sys(name, channel_name); +} +EXPORT_SYMBOL_GPL(iio_channel_get); + +void iio_channel_release(struct iio_channel *channel) +{ + iio_device_put(channel->indio_dev); + kfree(channel); +} +EXPORT_SYMBOL_GPL(iio_channel_release); + +struct iio_channel *iio_channel_get_all(struct device *dev) +{ + const char *name; + struct iio_channel *chans; + struct iio_map_internal *c = NULL; + int nummaps = 0; + int mapind = 0; + int i, ret; + + if (dev == NULL) + return ERR_PTR(-EINVAL); + + chans = of_iio_channel_get_all(dev); + if (chans) + return chans; + + name = dev_name(dev); + + mutex_lock(&iio_map_list_lock); + /* first count the matching maps */ + list_for_each_entry(c, &iio_map_list, l) + if (name && strcmp(name, c->map->consumer_dev_name) != 0) + continue; + else + nummaps++; + + if (nummaps == 0) { + ret = -ENODEV; + goto error_ret; + } + + /* NULL terminated array to save passing size */ + chans = kzalloc(sizeof(*chans)*(nummaps + 1), GFP_KERNEL); + if (chans == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + /* for each map fill in the chans element */ + list_for_each_entry(c, &iio_map_list, l) { + if (name && strcmp(name, c->map->consumer_dev_name) != 0) + continue; + chans[mapind].indio_dev = c->indio_dev; + chans[mapind].data = c->map->consumer_data; + chans[mapind].channel = + iio_chan_spec_from_name(chans[mapind].indio_dev, + c->map->adc_channel_label); + if (chans[mapind].channel == NULL) { + ret = -EINVAL; + goto error_free_chans; + } + iio_device_get(chans[mapind].indio_dev); + mapind++; + } + if (mapind == 0) { + ret = -ENODEV; + goto error_free_chans; + } + mutex_unlock(&iio_map_list_lock); + + return chans; + +error_free_chans: + for (i = 0; i < nummaps; i++) + iio_device_put(chans[i].indio_dev); + kfree(chans); +error_ret: + mutex_unlock(&iio_map_list_lock); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(iio_channel_get_all); + +void iio_channel_release_all(struct iio_channel *channels) +{ + struct iio_channel *chan = &channels[0]; + + while (chan->indio_dev) { + iio_device_put(chan->indio_dev); + chan++; + } + kfree(channels); +} +EXPORT_SYMBOL_GPL(iio_channel_release_all); + +static int iio_channel_read(struct iio_channel *chan, int *val, int *val2, + enum iio_chan_info_enum info) +{ + int unused; + + if (val2 == NULL) + val2 = &unused; + + return chan->indio_dev->info->read_raw(chan->indio_dev, chan->channel, + val, val2, info); +} + +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); + +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/inv_test/Kconfig b/drivers/iio/inv_test/Kconfig new file mode 100644 index 00000000..f6b6e159 --- /dev/null +++ b/drivers/iio/inv_test/Kconfig @@ -0,0 +1,11 @@ +# +# Kconfig for Invensense IIO testing hooks +# + +config INV_TESTING + boolean "Invensense IIO testing hooks" + depends on INV_MPU_IIO || INV_AMI306_IIO || INV_YAS530 + default n + help + This flag enables display of additional testing information from the + Invensense IIO drivers diff --git a/drivers/iio/inv_test/Makefile b/drivers/iio/inv_test/Makefile new file mode 100644 index 00000000..4f0edd3d --- /dev/null +++ b/drivers/iio/inv_test/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for Invensense IIO testing hooks. +# + +obj-$(CONFIG_INV_TESTING) += inv_counters.o + diff --git a/drivers/iio/inv_test/inv_counters.c b/drivers/iio/inv_test/inv_counters.c new file mode 100644 index 00000000..929227aa --- /dev/null +++ b/drivers/iio/inv_test/inv_counters.c @@ -0,0 +1,153 @@ +/* + * @file inv_counters.c + * @brief Exports i2c read write counts through sysfs + * + * @version 0.1 + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/miscdevice.h> +#include <linux/err.h> +#include <linux/sysfs.h> +#include <linux/kdev_t.h> +#include <linux/string.h> +#include <linux/jiffies.h> +#include <linux/spinlock.h> +#include <linux/kernel_stat.h> + +#include "inv_counters.h" + +static int mpu_irq; +static int accel_irq; +static int compass_irq; + +struct inv_counters { + uint32_t i2c_tempreads; + uint32_t i2c_mpureads; + uint32_t i2c_mpuwrites; + uint32_t i2c_accelreads; + uint32_t i2c_accelwrites; + uint32_t i2c_compassreads; + uint32_t i2c_compasswrites; + uint32_t i2c_compassirq; + uint32_t i2c_accelirq; +}; + +static struct inv_counters Counters; + +void inv_iio_counters_set_i2cirq(enum irqtype type, int irq) +{ + switch (type) { + case MPU: + mpu_irq = irq; + break; + case ACCEL: + accel_irq = irq; + break; + case COMPASS: + compass_irq = irq; + break; + } +} + +static ssize_t i2c_counters_show(struct class *cls, + struct class_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, + "%ld.%03ld %u %u %u %u %u %u %u %u %u %u\n", + jiffies / HZ, ((jiffies % HZ) * (1024 / HZ)), + mpu_irq ? kstat_irqs(mpu_irq) : 0, + Counters.i2c_tempreads, + Counters.i2c_mpureads, Counters.i2c_mpuwrites, + accel_irq ? kstat_irqs(accel_irq) : Counters.i2c_accelirq, + Counters.i2c_accelreads, Counters.i2c_accelwrites, + compass_irq ? kstat_irqs(compass_irq) : Counters.i2c_compassirq, + Counters.i2c_compassreads, Counters.i2c_compasswrites); +} + +void inv_iio_counters_tempread(int count) +{ + Counters.i2c_tempreads += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_tempread); + +void inv_iio_counters_mpuread(int count) +{ + Counters.i2c_mpureads += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_mpuread); + +void inv_iio_counters_mpuwrite(int count) +{ + Counters.i2c_mpuwrites += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_mpuwrite); + +void inv_iio_counters_accelread(int count) +{ + Counters.i2c_accelreads += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_accelread); + +void inv_iio_counters_accelwrite(int count) +{ + Counters.i2c_accelwrites += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_accelwrite); + +void inv_iio_counters_compassread(int count) +{ + Counters.i2c_compassreads += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_compassread); + +void inv_iio_counters_compasswrite(int count) +{ + Counters.i2c_compasswrites += count; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_compasswrite); + +void inv_iio_counters_compassirq(void) +{ + Counters.i2c_compassirq++; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_compassirq); + +void inv_iio_counters_accelirq(void) +{ + Counters.i2c_accelirq++; +} +EXPORT_SYMBOL_GPL(inv_iio_counters_accelirq); + +static struct class_attribute inv_class_attr[] = { + __ATTR(i2c_counter, S_IRUGO, i2c_counters_show, NULL), + __ATTR_NULL +}; + +static struct class inv_counters_class = { + .name = "inv_counters", + .owner = THIS_MODULE, + .class_attrs = (struct class_attribute *) &inv_class_attr +}; + +static int __init inv_counters_init(void) +{ + memset(&Counters, 0, sizeof(Counters)); + + return class_register(&inv_counters_class); +} + +static void __exit inv_counters_exit(void) +{ + class_unregister(&inv_counters_class); +} + +module_init(inv_counters_init); +module_exit(inv_counters_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("GESL"); +MODULE_DESCRIPTION("inv_counters debug support"); + diff --git a/drivers/iio/inv_test/inv_counters.h b/drivers/iio/inv_test/inv_counters.h new file mode 100644 index 00000000..35a802b0 --- /dev/null +++ b/drivers/iio/inv_test/inv_counters.h @@ -0,0 +1,72 @@ +/* + * @file inv_counters.h + * @brief Debug file to keep track of various counters for the InvenSense + * sensor drivers. + * + * @version 0.1 + */ + +#ifndef _INV_COUNTERS_H_ +#define _INV_COUNTERS_H_ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/sysfs.h> +#include <linux/string.h> +#include <linux/jiffies.h> +#include <linux/spinlock.h> + +enum irqtype { + MPU, + ACCEL, + COMPASS +}; + +#ifdef CONFIG_INV_TESTING + +#define INV_I2C_INC_MPUREAD(x) inv_iio_counters_mpuread(x) +#define INV_I2C_INC_MPUWRITE(x) inv_iio_counters_mpuwrite(x) +#define INV_I2C_INC_ACCELREAD(x) inv_iio_counters_accelread(x) +#define INV_I2C_INC_ACCELWRITE(x) inv_iio_counters_accelwrite(x) +#define INV_I2C_INC_COMPASSREAD(x) inv_iio_counters_compassread(x) +#define INV_I2C_INC_COMPASSWRITE(x) inv_iio_counters_compasswrite(x) + +#define INV_I2C_INC_TEMPREAD(x) inv_iio_counters_tempread(x) + +#define INV_I2C_SETIRQ(type, irq) inv_iio_counters_set_i2cirq(type, irq) +#define INV_I2C_INC_COMPASSIRQ() inv_iio_counters_compassirq() +#define INV_I2C_INC_ACCELIRQ() inv_iio_counters_accelirq() + +void inv_iio_counters_mpuread(int count); +void inv_iio_counters_mpuwrite(int count); +void inv_iio_counters_accelread(int count); +void inv_iio_counters_accelwrite(int count); +void inv_iio_counters_compassread(int count); +void inv_iio_counters_compasswrite(int count); + +void inv_iio_counters_tempread(int count); + +void inv_iio_counters_set_i2cirq(enum irqtype type, int irq); +void inv_iio_counters_compassirq(void); +void inv_iio_counters_accelirq(void); + +#else + +#define INV_I2C_INC_MPUREAD(x) +#define INV_I2C_INC_MPUWRITE(x) +#define INV_I2C_INC_ACCELREAD(x) +#define INV_I2C_INC_ACCELWRITE(x) +#define INV_I2C_INC_COMPASSREAD(x) +#define INV_I2C_INC_COMPASSWRITE(x) + +#define INV_I2C_INC_TEMPREAD(x) + +#define INV_I2C_SETIRQ(type, irq) +#define INV_I2C_INC_COMPASSIRQ() +#define INV_I2C_INC_ACCELIRQ() + +#endif /* CONFIG_INV_TESTING */ + +#endif /* _INV_COUNTERS_H_ */ + diff --git a/drivers/iio/kfifo_buf.c b/drivers/iio/kfifo_buf.c new file mode 100644 index 00000000..a923c78d --- /dev/null +++ b/drivers/iio/kfifo_buf.c @@ -0,0 +1,164 @@ +#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> + +struct iio_kfifo { + struct iio_buffer buffer; + struct kfifo kf; + 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); + + if (!buf->update_needed) + goto error_ret; + kfifo_free(&buf->kf); + ret = __iio_allocate_kfifo(buf, buf->buffer.bytes_per_datum, + buf->buffer.length); + r->stufftoread = false; +error_ret: + 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, + u8 *data) +{ + int ret; + struct iio_kfifo *kf = iio_to_kfifo(r); + ret = kfifo_in(&kf->kf, data, 1); + if (ret != 1) + return -EBUSY; + r->stufftoread = true; + wake_up_interruptible(&r->pollq); + + 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 (n < r->bytes_per_datum || r->bytes_per_datum == 0) + return -EINVAL; + + ret = kfifo_to_user(&kf->kf, buf, n, &copied); + if (ret < 0) + return ret; + + if (kfifo_is_empty(&kf->kf)) + r->stufftoread = false; + /* verify it is still empty to avoid race */ + if (!kfifo_is_empty(&kf->kf)) + r->stufftoread = true; + + return copied; +} + +static const struct iio_buffer_access_funcs kfifo_access_funcs = { + .store_to = &iio_store_to_kfifo, + .read_first_n = &iio_read_first_n_kfifo, + .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, +}; + +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; + return &kf->buffer; +} +EXPORT_SYMBOL(iio_kfifo_allocate); + +void iio_kfifo_free(struct iio_buffer *r) +{ + kfree(iio_to_kfifo(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 00000000..5ef1a396 --- /dev/null +++ b/drivers/iio/light/Kconfig @@ -0,0 +1,66 @@ +# +# Light sensors +# +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 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 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 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. + +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. + +endmenu diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile new file mode 100644 index 00000000..040d9c75 --- /dev/null +++ b/drivers/iio/light/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for IIO Light sensors +# + +obj-$(CONFIG_ADJD_S311) += adjd_s311.o +obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o +obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o +obj-$(CONFIG_VCNL4000) += vcnl4000.o +obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o diff --git a/drivers/iio/light/adjd_s311.c b/drivers/iio/light/adjd_s311.c new file mode 100644 index 00000000..5f4749e6 --- /dev/null +++ b/drivers/iio/light/adjd_s311.c @@ -0,0 +1,364 @@ +/* + * 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/init.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_LO 0x0a +#define ADJD_S311_INT_RED_HI 0x0b +#define ADJD_S311_INT_GREEN_LO 0x0c +#define ADJD_S311_INT_GREEN_HI 0x0d +#define ADJD_S311_INT_BLUE_LO 0x0e +#define ADJD_S311_INT_BLUE_HI 0x0f +#define ADJD_S311_INT_CLEAR_LO 0x10 +#define ADJD_S311_INT_CLEAR_HI 0x11 +#define ADJD_S311_DATA_RED_LO 0x40 +#define ADJD_S311_DATA_RED_HI 0x41 +#define ADJD_S311_DATA_GREEN_LO 0x42 +#define ADJD_S311_DATA_GREEN_HI 0x43 +#define ADJD_S311_DATA_BLUE_LO 0x44 +#define ADJD_S311_DATA_BLUE_HI 0x45 +#define ADJD_S311_DATA_CLEAR_LO 0x46 +#define ADJD_S311_DATA_CLEAR_HI 0x47 +#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_LO + (chan) * 2) +#define ADJD_S311_INT_REG(chan) (ADJD_S311_INT_RED_LO + (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 ssize_t adjd_s311_read_int_time(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + s32 ret; + + ret = i2c_smbus_read_word_data(data->client, + ADJD_S311_INT_REG(chan->address)); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret & ADJD_S311_INT_MASK); +} + +static ssize_t adjd_s311_write_int_time(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + struct adjd_s311_data *data = iio_priv(indio_dev); + unsigned long int_time; + int ret; + + ret = kstrtoul(buf, 10, &int_time); + if (ret) + return ret; + + if (int_time > ADJD_S311_INT_MASK) + return -EINVAL; + + ret = i2c_smbus_write_word_data(data->client, + ADJD_S311_INT_REG(chan->address), int_time); + if (ret < 0) + return ret; + + return len; +} + +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 len = 0; + 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; + len += 2; + } + + if (indio_dev->scan_timestamp) + *(s64 *)((u8 *)data->buffer + ALIGN(len, sizeof(s64))) + = time_ns; + iio_push_to_buffers(indio_dev, (u8 *)data->buffer); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_chan_spec_ext_info adjd_s311_ext_info[] = { + { + .name = "integration_time", + .read = adjd_s311_read_int_time, + .write = adjd_s311_write_int_time, + }, + { } +}; + +#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), \ + .channel2 = (IIO_MOD_LIGHT_##_color), \ + .scan_index = (_scan_idx), \ + .scan_type = IIO_ST('u', 10, 16, 0), \ + .ext_info = adjd_s311_ext_info, \ +} + +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, 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; + } + 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); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + if (val < 0 || val > ADJD_S311_CAP_MASK) + return -EINVAL; + + ret = i2c_smbus_write_byte_data(data->client, + ADJD_S311_CAP_REG(chan->address), val); + return ret; + } + 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 = iio_device_alloc(sizeof(*data)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto exit; + } + 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) + goto exit_free_device; + + 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); +exit_free_device: + iio_device_free(indio_dev); +exit: + 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); + iio_device_free(indio_dev); + + 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/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c new file mode 100644 index 00000000..80d68ff0 --- /dev/null +++ b/drivers/iio/light/hid-sensor-als.c @@ -0,0 +1,370 @@ +/* + * 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> +#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" + +/*Format: HID-SENSOR-usage_id_in_hex*/ +/*Usage ID from spec for Accelerometer-3D: 0x200041*/ +#define DRIVER_NAME "HID-SENSOR-200041" + +#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; +}; + +/* Channel definitions */ +static const struct iio_chan_spec als_channels[] = { + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .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; + int ret_type; + + *val = 0; + *val2 = 0; + switch (mask) { + case 0: + switch (chan->scan_index) { + case CHANNEL_SCAN_INDEX_ILLUM: + report_id = als_state->als_illum.report_id; + address = + HID_USAGE_SENSOR_LIGHT_ILLUM; + break; + default: + report_id = -1; + break; + } + if (report_id >= 0) + *val = sensor_hub_input_attr_get_raw_value( + als_state->common_attributes.hsdev, + HID_USAGE_SENSOR_ALS, address, + report_id); + else { + *val = 0; + return -EINVAL; + } + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = als_state->als_illum.units; + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_OFFSET: + *val = hid_sensor_convert_exponent( + als_state->als_illum.unit_expo); + ret_type = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = hid_sensor_read_samp_freq_value( + &als_state->common_attributes, val, val2); + ret_type = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_HYSTERESIS: + ret = hid_sensor_read_raw_hyst_value( + &als_state->common_attributes, val, val2); + ret_type = IIO_VAL_INT_PLUS_MICRO; + 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 int als_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_MICRO; +} + +static const struct iio_info als_info = { + .driver_module = THIS_MODULE, + .read_raw = &als_read_raw, + .write_raw = &als_write_raw, + .write_raw_get_fmt = &als_write_raw_get_fmt, +}; + +/* 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 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 [%d]\n", + als_state->common_attributes.data_ready); + if (als_state->common_attributes.data_ready) + hid_sensor_push_data(indio_dev, + (u8 *)&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); + + 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 = iio_device_alloc(sizeof(struct als_state)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + 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"); + goto error_free_dev; + } + + channels = kmemdup(als_channels, sizeof(als_channels), GFP_KERNEL); + if (!channels) { + ret = -ENOMEM; + dev_err(&pdev->dev, "failed to duplicate channels\n"); + goto error_free_dev; + } + + 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; + } + als_state->common_attributes.data_ready = false; + 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(indio_dev); +error_unreg_buffer_funcs: + iio_triggered_buffer_cleanup(indio_dev); +error_free_dev_mem: + kfree(indio_dev->channels); +error_free_dev: + iio_device_free(indio_dev); +error_ret: + 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); + + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ALS); + iio_device_unregister(indio_dev); + hid_sensor_remove_trigger(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + kfree(indio_dev->channels); + iio_device_free(indio_dev); + + return 0; +} + +static struct platform_driver hid_als_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .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/lm3533-als.c b/drivers/iio/light/lm3533-als.c new file mode 100644 index 00000000..5fa31a4e --- /dev/null +++ b/drivers/iio/light/lm3533-als.c @@ -0,0 +1,931 @@ +/* + * 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 = iio_device_alloc(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) + goto err_free_dev; + } + + 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); +err_free_dev: + iio_device_free(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); + iio_device_free(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/tsl2563.c b/drivers/iio/light/tsl2563.c new file mode 100644 index 00000000..1f529f36 --- /dev/null +++ b/drivers/iio/light/tsl2563.c @@ -0,0 +1,887 @@ +/* + * 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 (chan->channel == IIO_MOD_LIGHT_BOTH) + chip->calib0 = calib_from_sysfs(val); + else + chip->calib1 = calib_from_sysfs(val); + + return 0; +} + +static int tsl2563_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret = -EINVAL; + u32 calib0, calib1; + struct tsl2563_chip *chip = iio_priv(indio_dev); + + mutex_lock(&chip->lock); + switch (m) { + 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->channel == 0) + *val = chip->data0; + else + *val = chip->data1; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->channel == 0) + *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_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_mask = (IIO_EV_BIT(IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING)), + }, { + .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, + u64 event_code, + int *val) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + + switch (IIO_EVENT_CODE_EXTRACT_DIR(event_code)) { + case IIO_EV_DIR_RISING: + *val = chip->high_thres; + break; + case IIO_EV_DIR_FALLING: + *val = chip->low_thres; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tsl2563_write_thresh(struct iio_dev *indio_dev, + u64 event_code, + int val) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + u8 address; + + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == 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 (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == 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, + u64 event_code, + 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, + u64 event_code) +{ + 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; + int err = 0; + u8 id = 0; + + indio_dev = iio_device_alloc(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); + goto fail1; + } + + err = tsl2563_read_id(chip, &id); + if (err) { + dev_err(&client->dev, "read id error %d\n", -err); + goto fail1; + } + + 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 + 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 = request_threaded_irq(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); + goto fail1; + } + } + + err = tsl2563_configure(chip); + if (err) { + dev_err(&client->dev, "configure error %d\n", -err); + goto fail2; + } + + 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 fail3; + } + + return 0; + +fail3: + cancel_delayed_work(&chip->poweroff_work); + flush_scheduled_work(); +fail2: + if (client->irq) + free_irq(client->irq, indio_dev); +fail1: + iio_device_free(indio_dev); + 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); + if (client->irq) + free_irq(client->irq, indio_dev); + + iio_device_free(indio_dev); + + 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/vcnl4000.c b/drivers/iio/light/vcnl4000.c new file mode 100644 index 00000000..1014943d --- /dev/null +++ b/drivers/iio/light/vcnl4000.c @@ -0,0 +1,217 @@ +/* + * 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; + u16 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 = iio_device_alloc(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) + goto error_free_dev; + + 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; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_free_dev; + + return 0; + +error_free_dev: + iio_device_free(indio_dev); + return ret; +} + +static int vcnl4000_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_device_free(indio_dev); + + return 0; +} + +static struct i2c_driver vcnl4000_driver = { + .driver = { + .name = VCNL4000_DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = vcnl4000_probe, + .remove = vcnl4000_remove, + .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 00000000..4025222c --- /dev/null +++ b/drivers/iio/magnetometer/Kconfig @@ -0,0 +1,5 @@ + +source "drivers/staging/iio/magnetometer/inv_compass/Kconfig" + + + diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile new file mode 100644 index 00000000..a274a4ea --- /dev/null +++ b/drivers/iio/magnetometer/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the industrial I/O core. +# + +obj-y += inv_compass/ + diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c new file mode 100644 index 00000000..53f82900 --- /dev/null +++ b/drivers/iio/magnetometer/ak8975.c @@ -0,0 +1,478 @@ +/* + * 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/err.h> +#include <linux/mutex.h> +#include <linux/delay.h> + +#include <linux/gpio.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 + +/* + * 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; +}; + +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; +} + +/* + * 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 (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 = 100 gauss, our final scale factor becomes: + * + * Hadj = H * ((ASA + 128) / 256) * 3/10 * 100 + * Hadj = H * ((ASA + 128) * 30 / 256 + * + * Since ASA doesn't change, we cache the resultant scale factor into the + * device context in ak8975_setup(). + */ + data->raw_to_gauss[0] = ((data->asa[0] + 128) * 30) >> 8; + data->raw_to_gauss[1] = ((data->asa[1] + 128) * 30) >> 8; + data->raw_to_gauss[2] = ((data->asa[2] + 128) * 30) >> 8; + + 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; +} + +/* + * 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 (gpio_is_valid(data->eoc_gpio)) + ret = wait_conversion_complete_gpio(data); + else + ret = wait_conversion_complete_polled(data); + if (ret < 0) + goto exit; + + 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 = data->raw_to_gauss[chan->address]; + return IIO_VAL_INT; + } + 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 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; + + /* Grab and set up the supplied GPIO. */ + if (client->dev.platform_data == NULL) + eoc_gpio = -1; + else + eoc_gpio = *(int *)(client->dev.platform_data); + + /* 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); + /* 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; + + err = iio_device_register(indio_dev); + if (err < 0) + goto exit_free_iio; + + return 0; + +exit_free_iio: + iio_device_free(indio_dev); +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 (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", 0}, + {} +}; + +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, + }, + .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/inv_compass/Kconfig b/drivers/iio/magnetometer/inv_compass/Kconfig new file mode 100644 index 00000000..ea1ef709 --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/Kconfig @@ -0,0 +1,25 @@ +# +# Kconfig for Invensense IIO compass drivers of 3rd party compass devices. +# + +# Yamaha YAS530/YAS532/YAS533 +config INV_YAS53X_IIO + tristate "Invensense IIO driver for Yamaha YAS530/YAS532/YAS533 compass" + depends on I2C && SYSFS && IIO && IIO_KFIFO_BUF + default n + help + This driver supports the Yamaha YAS530/YAS532/YAS533. It is the Invensense + implementation of YAS53x series compass devices. + This driver can be built as a module. The module will be called + inv_yas53x_iio. + +# Aichi AMI306 +config INV_AMI306_IIO + tristate "Invensense IIO driver for Aichi AMI306 compass" + depends on I2C && SYSFS && IIO && IIO_KFIFO_BUF + default n + help + This driver supports the Aichi AMI306 compass. It is the Invensense + IIO implementation for the AMI306 compass device. + This driver can be built as a module. The module will be called + inv-ami306-iio. diff --git a/drivers/iio/magnetometer/inv_compass/Makefile b/drivers/iio/magnetometer/inv_compass/Makefile new file mode 100644 index 00000000..adc7dd93 --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/Makefile @@ -0,0 +1,25 @@ +# +# Makefile for Invensense IIO compass drivers of 3rd party compass devices. +# + +# Yamaha YAS530/YAS532/YAS533 +obj-$(CONFIG_INV_YAS53X_IIO) += inv_yas53x.o + +inv_yas53x-objs := inv_yas53x_core.o +inv_yas53x-objs += inv_yas53x_ring.o +inv_yas53x-objs += inv_yas53x_trigger.o + +CFLAGS_inv_yas53x_core.o += -Idrivers/staging/iio +CFLAGS_inv_yas53x_ring.o += -Idrivers/staging/iio +CFLAGS_inv_yas53x_trigger.o += -Idrivers/staging/iio + +# Aichi AMI306 +obj-$(CONFIG_INV_AMI306_IIO) += inv-ami306-iio.o + +inv-ami306-iio-objs := inv_ami306_core.o +inv-ami306-iio-objs += inv_ami306_ring.o +inv-ami306-iio-objs += inv_ami306_trigger.o + +CFLAGS_inv_ami306_core.o += -Idrivers/staging/iio +CFLAGS_inv_ami306_ring.o += -Idrivers/staging/iio +CFLAGS_inv_ami306_trigger.o += -Idrivers/staging/iio diff --git a/drivers/iio/magnetometer/inv_compass/README b/drivers/iio/magnetometer/inv_compass/README new file mode 100644 index 00000000..c5445e5b --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/README @@ -0,0 +1,176 @@ +Kernel driver +Author: Invensense <http://invensense.com> + +Table of Contents: +================== +- Description +- Integrating the Driver in the Linux Kernel +- Board and Platform Data + > Platform Data +- Board File Modifications for compass + > AMI306 + > YAS530/532/533 +- IIO Subsystem + > Communicating with the Driver in Userspace +- Streaming Data to an Userspace Application +- Test Applications + > Running Test Applications with AMI306 or YAS53x + +Description +=========== +This document describes how to install the Invensense device driver for AMI306 +and YAS53x series compass chip into a Linux kernel. The Invensense driver +currently supports the following sensors: +- AMI306 +- YAS530 +- YAS532 +- YAS533 + +Please refer to the appropriate product specification +document for further information regarding the slave address. + +The following files are included in this package: +- Kconfig +- Makefile +- inv_ami306_core.c +- inv_ami306_ring.c +- inv_ami306_trigger.c +- inv_ami306_iio.h +- inv_yas53x_core.c +- inv_yas53x_ring.c +- inv_yas53x_trigger.c +- inv_yas53x_iio.h + +Integrating the Driver in the Linux Kernel +========================================== +Please add the files as follows: +- Add all above files to drivers/staging/iio/magnetometer/inv_compass +(another directory is acceptable, but this is the recommended destination) + +In order to see the driver in menuconfig when building the kernel, please +make modifications as shown below: + + modify "drivers/staging/iio/magnetometer/Kconfig" with: + >> source "drivers/staging/iio/magnetometer/inv_compass/Kconfig" + + modify "drivers/staging/iio/magnetometer/Makefile" with: + >> obj-y += inv_compass/ + + +Board and Platform Data +======================= +In order to recognize the Invensense device on the I2C bus, the board file must +be modified. +The i2c_board_info instance must be defined as shown below. + +Platform Data +------------- +The platform data (orientation matrix and secondary bus configurations) must be +modified as show below, according to your particular platform configuration. + +Board File Modifications for Secondary I2C Configuration +======================================================== +For the Panda Board, the board file can be found at +arch/arm/mach-omap2/board-omap4panda.c. +Please modify the pertinent baord file in your system according to the examples +shown below: + +AMI306 +------------------------------------------------- +static struct mpu_platform_data compass_data = { + .orientation = { 0, 0, 1, + 0, 1, 0, + 1, 0, 0 }, +}; + +static struct i2c_board_info __initdata chip_board_info[] = { + { + I2C_BOARD_INFO("ami306", 0x0E), + .platform_data = &compass_data, + }, +}; + +YAS53x(Use YAS532 as an example) +------------------------------------------------- +static struct mpu_platform_data compass_data = { + .orientation = { 0, -1, 0, + 1, 0, 0, + 0, 0, 1 }, +}; + +static struct i2c_board_info __initdata compass_board_info[] = { + { + I2C_BOARD_INFO("yas532", 0x2E), + .platform_data = &compass_data, + }, +}; + +IIO subsystem +============= +A successful installation will create the following two new directories under +/sys/bus/iio/devices: + - iio:device0 + - trigger0 + +Also, a new file, "iio:device0", will be created in the /dev/ diretory. +(if you have more than one IIO device, the file will be named "iio:deviceX", +where X is a number) + + +Communicating with the Driver in Userspace +------------------------------------------ +The driver generates several files in sysfs upon installation. +These files are used to communicate with the driver. The files can be found +at /sys/bus/iio/devices/iio:device0 (or ../iio:deviceX as shown above). + +A brief description of the pertinent files for each Invensense device is shown +below: + +AMI306 +-------- +compass_matrix (read-only) +--show the orientation matrix obtained from the board file. + +sampling_frequency(read and write) +--show and change the sampling rate of the sensor. + +YAS53x +--------------------- +YAS53x has all the attributes AMI306 has. It has one more additional attribute: + +overunderflow(read-only) +--value 1 shows an overflow or underflow happens. Need to write into it to make + it zero. + +Streaming Data to an Userspace Application +========================================== +When streaming data to an userspace application, we recommend that you access +compass data via /dev/iio:device0. + +Please follow the steps below to read data at a constant rate from the driver: + +1. Write the desired output rate to sampling_frequency. +2. Write 1 to enable to turn on the event. +3. Read /dev/iio:device0 to get a string of gyro/accel/compass data. +4. Parse this string to obtain each compass element. + +Test Applications +================= +A test application is located under software/simple_apps/mpu_iio. +This application is stand-alone in that it cannot be run concurrently with other +entities trying to access the device node(s) or sysfs entries; in particular, +the + +Running Test Applications +--------------------------------------------------------- +To run test applications with AMI306 or YAS53x devices, +please use the following commands: + +1. for ami306: + mpu_iio -n ami306 -c 10 -l 3 + +2. for yas532: + mpu_iio -n yas532 -c 10 -l 3 + +Please use mpu_iio.c and iio_utils.h as example code for your development +purposes. diff --git a/drivers/iio/magnetometer/inv_compass/inv_ami306_core.c b/drivers/iio/magnetometer/inv_compass/inv_ami306_core.c new file mode 100644 index 00000000..612ba72b --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/inv_ami306_core.c @@ -0,0 +1,570 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_ami306_core.c + * @brief Invensense implementation for AMI306 + * @details This driver currently works for the AMI306 + */ + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> +#include <linux/spinlock.h> + +#include "inv_ami306_iio.h" +#include "sysfs.h" +#include "inv_test/inv_counters.h" + +static unsigned char late_initialize = true; + +s32 i2c_write(const struct i2c_client *client, + u8 command, u8 length, const u8 *values) +{ + INV_I2C_INC_COMPASSWRITE(3); + return i2c_smbus_write_i2c_block_data(client, command, length, values); +} + +s32 i2c_read(const struct i2c_client *client, + u8 command, u8 length, u8 *values) +{ + INV_I2C_INC_COMPASSWRITE(3); + INV_I2C_INC_COMPASSREAD(length); + return i2c_smbus_read_i2c_block_data(client, command, length, values); +} + +static int ami306_read_param(struct inv_ami306_state_s *st) +{ + int result = 0; + unsigned char regs[AMI_PARAM_LEN]; + struct ami_sensor_parametor *param = &st->param; + + result = i2c_read(st->i2c, REG_AMI_SENX, + AMI_PARAM_LEN, regs); + if (result < 0) + return result; + + /* Little endian 16 bit registers */ + param->m_gain.x = le16_to_cpup((__le16 *)(®s[0])); + param->m_gain.y = le16_to_cpup((__le16 *)(®s[2])); + param->m_gain.z = le16_to_cpup((__le16 *)(®s[4])); + + param->m_interference.xy = regs[7]; + param->m_interference.xz = regs[6]; + param->m_interference.yx = regs[9]; + param->m_interference.yz = regs[8]; + param->m_interference.zx = regs[11]; + param->m_interference.zy = regs[10]; + + param->m_offset.x = AMI_STANDARD_OFFSET; + param->m_offset.y = AMI_STANDARD_OFFSET; + param->m_offset.z = AMI_STANDARD_OFFSET; + + param->m_gain_cor.x = AMI_GAIN_COR_DEFAULT; + param->m_gain_cor.y = AMI_GAIN_COR_DEFAULT; + param->m_gain_cor.z = AMI_GAIN_COR_DEFAULT; + + return 0; +} + +static int ami306_write_offset(const struct i2c_client *client, + unsigned char *fine) +{ + int result = 0; + unsigned char dat[3]; + dat[0] = (0x7f & fine[0]); + dat[1] = 0; + result = i2c_write(client, REG_AMI_OFFX, 2, dat); + dat[0] = (0x7f & fine[1]); + dat[1] = 0; + result = i2c_write(client, REG_AMI_OFFY, 2, dat); + dat[0] = (0x7f & fine[2]); + dat[1] = 0; + result = i2c_write(client, REG_AMI_OFFZ, 2, dat); + + return result; +} + +static int ami306_wait_data_ready(struct inv_ami306_state_s *st, + unsigned long usecs, unsigned long times) +{ + int result = 0; + unsigned char buf; + + for (; 0 < times; --times) { + udelay(usecs); + result = i2c_read(st->i2c, REG_AMI_STA1, 1, &buf); + if (result < 0) + return INV_ERROR_COMPASS_DATA_NOT_READY; + if (buf & AMI_STA1_DRDY_BIT) + return 0; + else if (buf & AMI_STA1_DOR_BIT) + return INV_ERROR_COMPASS_DATA_OVERFLOW; + } + + return INV_ERROR_COMPASS_DATA_NOT_READY; +} +int ami306_read_raw_data(struct inv_ami306_state_s *st, + short dat[3]) +{ + int result; + unsigned char buf[6]; + result = i2c_read(st->i2c, REG_AMI_DATAX, sizeof(buf), buf); + if (result < 0) + return result; + dat[0] = le16_to_cpup((__le16 *)(&buf[0])); + dat[1] = le16_to_cpup((__le16 *)(&buf[2])); + dat[2] = le16_to_cpup((__le16 *)(&buf[4])); + + return 0; +} + +#define AMI_WAIT_DATAREADY_RETRY 3 /* retry times */ +#define AMI_DRDYWAIT 800 /* u(micro) sec */ +static int ami306_force_measurement(struct inv_ami306_state_s *st, + short ver[3]) +{ + int result; + int status; + char buf; + buf = AMI_CTRL3_FORCE_BIT; + result = i2c_write(st->i2c, REG_AMI_CTRL3, 1, &buf); + if (result < 0) + return result; + + result = ami306_wait_data_ready(st, + AMI_DRDYWAIT, AMI_WAIT_DATAREADY_RETRY); + if (result && result != INV_ERROR_COMPASS_DATA_OVERFLOW) + return result; + /* READ DATA X,Y,Z */ + status = ami306_read_raw_data(st, ver); + if (status) + return status; + + return result; +} + +static int ami306_initial_b0_adjust(struct inv_ami306_state_s *st) +{ + int result; + unsigned char fine[3] = { 0 }; + short data[3]; + int diff[3] = { 0x7fff, 0x7fff, 0x7fff }; + int fn = 0; + int ax = 0; + unsigned char buf[3]; + + buf[0] = AMI_CTRL2_DREN; + result = i2c_write(st->i2c, REG_AMI_CTRL2, 1, buf); + if (result) + return result; + + buf[0] = AMI_CTRL4_HS & 0xFF; + buf[1] = (AMI_CTRL4_HS >> 8) & 0xFF; + result = i2c_write(st->i2c, REG_AMI_CTRL4, 2, buf); + if (result < 0) + return result; + + for (fn = 0; fn < AMI_FINE_MAX; ++fn) { /* fine 0 -> 95 */ + fine[0] = fine[1] = fine[2] = fn; + result = ami306_write_offset(st->i2c, fine); + if (result) + return result; + + result = ami306_force_measurement(st, data); + if (result) + return result; + + for (ax = 0; ax < 3; ax++) { + /* search point most close to zero. */ + if (diff[ax] > abs(data[ax])) { + st->fine[ax] = fn; + diff[ax] = abs(data[ax]); + } + } + } + result = ami306_write_offset(st->i2c, st->fine); + if (result) + return result; + + /* Software Reset */ + buf[0] = AMI_CTRL3_SRST_BIT; + result = i2c_write(st->i2c, REG_AMI_CTRL3, 1, buf); + if (result < 0) + return result; + else + return 0; +} + +static int ami306_start_sensor(struct inv_ami306_state_s *st) +{ + int result = 0; + unsigned char buf[2]; + + /* Step 1 */ + buf[0] = (AMI_CTRL1_PC1 | AMI_CTRL1_FS1_FORCE); + result = i2c_write(st->i2c, REG_AMI_CTRL1, 1, buf); + if (result < 0) + return result; + /* Step 2 */ + buf[0] = AMI_CTRL2_DREN; + result = i2c_write(st->i2c, REG_AMI_CTRL2, 1, buf); + if (result < 0) + return result; + /* Step 3 */ + buf[0] = (AMI_CTRL4_HS & 0xFF); + buf[1] = (AMI_CTRL4_HS >> 8) & 0xFF; + + result = i2c_write(st->i2c, REG_AMI_CTRL4, 2, buf); + if (result < 0) + return result; + + /* Step 4 */ + result = ami306_write_offset(st->i2c, st->fine); + + return result; +} + +int set_ami306_enable(struct iio_dev *indio_dev, int state) +{ + struct inv_ami306_state_s *st = iio_priv(indio_dev); + int result; + char buf; + + buf = (AMI_CTRL1_PC1 | AMI_CTRL1_FS1_FORCE); + result = i2c_write(st->i2c, REG_AMI_CTRL1, 1, &buf); + if (result < 0) + return result; + + result = ami306_read_param(st); + if (result) + return result; + if (late_initialize) { + result = ami306_initial_b0_adjust(st); + if (result) + return result; + late_initialize = false; + } + result = ami306_start_sensor(st); + if (result) + return result; + buf = AMI_CTRL3_FORCE_BIT; + st->timestamp = iio_get_time_ns(); + result = i2c_write(st->i2c, REG_AMI_CTRL3, 1, &buf); + if (result) + return result; + + return 0; +} + +/** + * ami306_read_raw() - read raw method. + */ +static int ami306_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) { + struct inv_ami306_state_s *st = iio_priv(indio_dev); + + switch (mask) { + case 0: + if (!(iio_buffer_enabled(indio_dev))) + return -EINVAL; + if (chan->type == IIO_MAGN) { + *val = st->compass_data[chan->channel2 - IIO_MOD_X]; + return IIO_VAL_INT; + } + + return -EINVAL; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_MAGN) { + *val = AMI_SCALE; + return IIO_VAL_INT; + } + return -EINVAL; + default: + return -EINVAL; + } +} + +/** + * inv_compass_matrix_show() - show orientation matrix + */ +static ssize_t inv_compass_matrix_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + signed char *m; + struct inv_ami306_state_s *st = iio_priv(indio_dev); + 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]); +} + +static ssize_t ami306_rate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_ami306_state_s *st = iio_priv(indio_dev); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (0 == data) + return -EINVAL; + /* transform rate to delay in ms */ + data = 1000 / data; + if (data > AMI_MAX_DELAY) + data = AMI_MAX_DELAY; + if (data < AMI_MIN_DELAY) + data = AMI_MIN_DELAY; + st->delay = data; + return count; +} + +static ssize_t ami306_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_ami306_state_s *st = iio_priv(indio_dev); + /* transform delay in ms to rate */ + return sprintf(buf, "%d\n", 1000 / st->delay); +} + + +static void ami306_work_func(struct work_struct *work) +{ + struct inv_ami306_state_s *st = + container_of((struct delayed_work *)work, + struct inv_ami306_state_s, work); + struct iio_dev *indio_dev = iio_priv_to_dev(st); + unsigned long delay = msecs_to_jiffies(st->delay); + + mutex_lock(&indio_dev->mlock); + if (!(iio_buffer_enabled(indio_dev))) + goto error_ret; + + st->timestamp = iio_get_time_ns(); + schedule_delayed_work(&st->work, delay); + inv_read_ami306_fifo(indio_dev); + INV_I2C_INC_COMPASSIRQ(); + +error_ret: + mutex_unlock(&indio_dev->mlock); +} + +static const struct iio_chan_spec compass_channels[] = { + { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = INV_AMI306_SCAN_MAGN_X, + .scan_type = IIO_ST('s', 16, 16, 0) + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = INV_AMI306_SCAN_MAGN_Y, + .scan_type = IIO_ST('s', 16, 16, 0) + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = INV_AMI306_SCAN_MAGN_Z, + .scan_type = IIO_ST('s', 16, 16, 0) + }, + IIO_CHAN_SOFT_TIMESTAMP(INV_AMI306_SCAN_TIMESTAMP) +}; + +static DEVICE_ATTR(compass_matrix, S_IRUGO, inv_compass_matrix_show, NULL); +static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, ami306_rate_show, + ami306_rate_store); + +static struct attribute *inv_ami306_attributes[] = { + &dev_attr_compass_matrix.attr, + &dev_attr_sampling_frequency.attr, + NULL, +}; +static const struct attribute_group inv_attribute_group = { + .name = "ami306", + .attrs = inv_ami306_attributes +}; + +static const struct iio_info ami306_info = { + .driver_module = THIS_MODULE, + .read_raw = &ami306_read_raw, + .attrs = &inv_attribute_group, +}; + +/*constant IIO attribute */ +/** + * inv_ami306_probe() - probe function. + */ +static int inv_ami306_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct inv_ami306_state_s *st; + struct iio_dev *indio_dev; + int result; + char data; + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + result = -ENOMEM; + goto out_no_free; + } + st = iio_priv(indio_dev); + st->i2c = client; + st->plat_data = + *(struct mpu_platform_data *)dev_get_platdata(&client->dev); + st->delay = 10; + + /* Make state variables available to all _show and _store functions. */ + i2c_set_clientdata(client, indio_dev); + result = i2c_read(st->i2c, REG_AMI_WIA, 1, &data); + if (result < 0) + goto out_free; + if (data != DATA_WIA) + goto out_free; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->channels = compass_channels; + indio_dev->num_channels = ARRAY_SIZE(compass_channels); + indio_dev->info = &ami306_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->currentmode = INDIO_DIRECT_MODE; + + result = inv_ami306_configure_ring(indio_dev); + if (result) + goto out_free; + result = iio_buffer_register(indio_dev, indio_dev->channels, + indio_dev->num_channels); + if (result) + goto out_unreg_ring; + result = inv_ami306_probe_trigger(indio_dev); + if (result) + goto out_remove_ring; + + result = iio_device_register(indio_dev); + if (result) + goto out_remove_trigger; + INIT_DELAYED_WORK(&st->work, ami306_work_func); + pr_info("%s: Probe name %s\n", __func__, id->name); + return 0; +out_remove_trigger: + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + inv_ami306_remove_trigger(indio_dev); +out_remove_ring: + iio_buffer_unregister(indio_dev); +out_unreg_ring: + inv_ami306_unconfigure_ring(indio_dev); +out_free: + iio_free_device(indio_dev); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return -EIO; +} + +/** + * inv_ami306_remove() - remove function. + */ +static int inv_ami306_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_ami306_state_s *st = iio_priv(indio_dev); + cancel_delayed_work_sync(&st->work); + iio_device_unregister(indio_dev); + inv_ami306_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + inv_ami306_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + dev_info(&client->adapter->dev, "inv-ami306-iio module removed.\n"); + return 0; +} +static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; +/* device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_ami306_id[] = { + {"ami306", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, inv_ami306_id); + +static struct i2c_driver inv_ami306_driver = { + .class = I2C_CLASS_HWMON, + .probe = inv_ami306_probe, + .remove = inv_ami306_remove, + .id_table = inv_ami306_id, + .driver = { + .owner = THIS_MODULE, + .name = "inv-ami306-iio", + }, + .address_list = normal_i2c, +}; + +static int __init inv_ami306_init(void) +{ + int result = i2c_add_driver(&inv_ami306_driver); + if (result) { + pr_err("%s failed\n", __func__); + return result; + } + return 0; +} + +static void __exit inv_ami306_exit(void) +{ + i2c_del_driver(&inv_ami306_driver); +} + +module_init(inv_ami306_init); +module_exit(inv_ami306_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("inv-ami306-iio"); +/** + * @} + */ + diff --git a/drivers/iio/magnetometer/inv_compass/inv_ami306_iio.h b/drivers/iio/magnetometer/inv_compass/inv_ami306_iio.h new file mode 100644 index 00000000..fa4f4ee1 --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/inv_ami306_iio.h @@ -0,0 +1,159 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_ami306_iio.h + * @brief Struct definitions for the Invensense implementation + * of ami306 driver. + */ + +#ifndef _INV_GYRO_H_ +#define _INV_GYRO_H_ + +#include <linux/i2c.h> +#include <linux/kfifo.h> +#include <linux/miscdevice.h> +#include <linux/input.h> +#include <linux/spinlock.h> +#include <linux/mpu.h> + +#include "iio.h" +#include "buffer.h" +#include "trigger.h" + +/** axis sensitivity(gain) calibration parameter information */ +struct ami_vector3d { + signed short x; /**< X-axis */ + signed short y; /**< Y-axis */ + signed short z; /**< Z-axis */ +}; + +/** axis interference information */ +struct ami_interference { + /**< Y-axis magnetic field for X-axis correction value */ + signed short xy; + /**< Z-axis magnetic field for X-axis correction value */ + signed short xz; + /**< X-axis magnetic field for Y-axis correction value */ + signed short yx; + /**< Z-axis magnetic field for Y-axis correction value */ + signed short yz; + /**< X-axis magnetic field for Z-axis correction value */ + signed short zx; + /**< Y-axis magnetic field for Z-axis correction value */ + signed short zy; +}; + +/** sensor calibration Parameter information */ +struct ami_sensor_parametor { + /**< geomagnetic field sensor gain */ + struct ami_vector3d m_gain; + /**< geomagnetic field sensor gain correction parameter */ + struct ami_vector3d m_gain_cor; + /**< geomagnetic field sensor offset */ + struct ami_vector3d m_offset; + /**< geomagnetic field sensor axis interference parameter */ + struct ami_interference m_interference; +}; + +/** + * struct inv_ami306_state_s - Driver state variables. + * @plat_data: board file platform data. + * @i2c: i2c client handle. + * @trig: not used. for compatibility. + * @param: ami specific sensor data. + * @work: work data structure. + * @delay: delay between each scheduled work. + * @fine: fine tunign parameters. + * @compass_data: compass data store. + * @timestamp: time stamp. + */ +struct inv_ami306_state_s { + struct mpu_platform_data plat_data; + struct i2c_client *i2c; + struct iio_trigger *trig; + struct ami_sensor_parametor param; + struct delayed_work work; + int delay; + s8 fine[3]; + short compass_data[3]; + s64 timestamp; +}; +/* scan element definition */ +enum inv_mpu_scan { + INV_AMI306_SCAN_MAGN_X, + INV_AMI306_SCAN_MAGN_Y, + INV_AMI306_SCAN_MAGN_Z, + INV_AMI306_SCAN_TIMESTAMP, +}; + +#define REG_AMI_WIA 0x0f +#define REG_AMI_DATAX 0x10 +#define REG_AMI_STA1 0x18 +#define REG_AMI_CTRL1 0x1b +#define REG_AMI_CTRL2 0x1c +#define REG_AMI_CTRL3 0x1d +#define REG_AMI_B0X 0x20 +#define REG_AMI_B0Y 0x22 +#define REG_AMI_B0Z 0x24 +#define REG_AMI_CTRL5 0x40 +#define REG_AMI_CTRL4 0x5c +#define REG_AMI_TEMP 0x60 +#define REG_AMI_SENX 0x96 +#define REG_AMI_OFFX 0x6c +#define REG_AMI_OFFY 0x72 +#define REG_AMI_OFFZ 0x78 + + +#define DATA_WIA 0x46 +#define AMI_CTRL1_PC1 0x80 +#define AMI_CTRL1_FS1_FORCE 0x02 +#define AMI_CTRL1_ODR1 0x10 +#define AMI_CTRL2_DREN 0x08 +#define AMI_CTRL2_DRP 0x04 +#define AMI_CTRL3_FORCE_BIT 0x40 +#define AMI_CTRL3_B0_LO_BIT 0x10 +#define AMI_CTRL3_SRST_BIT 0x80 +#define AMI_CTRL4_HS 0xa07e +#define AMI_CTRL4_AB 0x0001 +#define AMI_STA1_DRDY_BIT 0x40 +#define AMI_STA1_DOR_BIT 0x20 + +#define AMI_PARAM_LEN 12 +#define AMI_STANDARD_OFFSET 0x800 +#define AMI_GAIN_COR_DEFAULT 1000 +#define AMI_FINE_MAX 96 +#define AMI_MAX_DELAY 1000 +#define AMI_MIN_DELAY 10 +#define AMI_SCALE (5461 * (1<<15)) + +#define INV_ERROR_COMPASS_DATA_OVERFLOW (-1) +#define INV_ERROR_COMPASS_DATA_NOT_READY (-2) + + +int inv_ami306_configure_ring(struct iio_dev *indio_dev); +void inv_ami306_unconfigure_ring(struct iio_dev *indio_dev); +int inv_ami306_probe_trigger(struct iio_dev *indio_dev); +void inv_ami306_remove_trigger(struct iio_dev *indio_dev); +int set_ami306_enable(struct iio_dev *indio_dev, int state); +int ami306_read_raw_data(struct inv_ami306_state_s *st, + short dat[3]); +int inv_read_ami306_fifo(struct iio_dev *indio_dev); + +#endif /* #ifndef _INV_GYRO_H_ */ + diff --git a/drivers/iio/magnetometer/inv_compass/inv_ami306_ring.c b/drivers/iio/magnetometer/inv_compass/inv_ami306_ring.c new file mode 100644 index 00000000..ed91cf49 --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/inv_ami306_ring.c @@ -0,0 +1,163 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_ami306_ring.c + * @brief Invensense implementation for AMI306 + * @details This driver currently works for the AMI306 + */ + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> + +#include "iio.h" +#include "kfifo_buf.h" +#include "trigger_consumer.h" +#include "sysfs.h" + +#include "inv_ami306_iio.h" + +static int put_scan_to_buf(struct iio_dev *indio_dev, unsigned char *d, + short *s, int scan_index) { + struct iio_buffer *ring = indio_dev->buffer; + int st; + int i, d_ind; + d_ind = 0; + for (i = 0; i < 3; i++) { + st = iio_scan_mask_query(indio_dev, ring, scan_index + i); + if (st) { + memcpy(&d[d_ind], &s[i], sizeof(s[i])); + d_ind += sizeof(s[i]); + } + } + return d_ind; +} + +/** + * inv_read_fifo() - Transfer data from FIFO to ring buffer. + */ +int inv_read_ami306_fifo(struct iio_dev *indio_dev) +{ + struct inv_ami306_state_s *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + int result, status, d_ind; + char b; + char *tmp; + s64 tmp_buf[2]; + + result = i2c_smbus_read_i2c_block_data(st->i2c, REG_AMI_STA1, 1, &b); + if (result < 0) + goto end_session; + if (b & AMI_STA1_DRDY_BIT) { + status = ami306_read_raw_data(st, st->compass_data); + if (status) { + pr_err("error reading raw\n"); + goto end_session; + } + tmp = (unsigned char *)tmp_buf; + d_ind = put_scan_to_buf(indio_dev, tmp, st->compass_data, + INV_AMI306_SCAN_MAGN_X); + if (ring->scan_timestamp) + tmp_buf[(d_ind + 7)/8] = st->timestamp; + ring->access->store_to(indio_dev->buffer, tmp, st->timestamp); + } else if (b & AMI_STA1_DOR_BIT) + pr_err("not ready\n"); +end_session: + b = AMI_CTRL3_FORCE_BIT; + result = i2c_smbus_write_i2c_block_data(st->i2c, REG_AMI_CTRL3, 1, &b); + + return IRQ_HANDLED; +} + +void inv_ami306_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_kfifo_free(indio_dev->buffer); +}; +static int inv_ami306_postenable(struct iio_dev *indio_dev) +{ + struct inv_ami306_state_s *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + int result; + + /* when all the outputs are disabled, even though buffer/enable is on, + do nothing */ + if (!(iio_scan_mask_query(indio_dev, ring, INV_AMI306_SCAN_MAGN_X) || + iio_scan_mask_query(indio_dev, ring, INV_AMI306_SCAN_MAGN_Y) || + iio_scan_mask_query(indio_dev, ring, INV_AMI306_SCAN_MAGN_Z))) + return 0; + + result = set_ami306_enable(indio_dev, true); + if (result) + return result; + schedule_delayed_work(&st->work, msecs_to_jiffies(st->delay)); + + return 0; +} + +static int inv_ami306_predisable(struct iio_dev *indio_dev) +{ + struct iio_buffer *ring = indio_dev->buffer; + struct inv_ami306_state_s *st = iio_priv(indio_dev); + + cancel_delayed_work_sync(&st->work); + clear_bit(INV_AMI306_SCAN_MAGN_X, ring->scan_mask); + clear_bit(INV_AMI306_SCAN_MAGN_Y, ring->scan_mask); + clear_bit(INV_AMI306_SCAN_MAGN_Z, ring->scan_mask); + + return 0; +} + +static const struct iio_buffer_setup_ops inv_ami306_ring_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &inv_ami306_postenable, + .predisable = &inv_ami306_predisable, +}; + +int inv_ami306_configure_ring(struct iio_dev *indio_dev) +{ + int ret = 0; + struct iio_buffer *ring; + + ring = iio_kfifo_allocate(indio_dev); + if (!ring) { + ret = -ENOMEM; + return ret; + } + indio_dev->buffer = ring; + /* setup ring buffer */ + ring->scan_timestamp = true; + indio_dev->setup_ops = &inv_ami306_ring_setup_ops; + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; +} +/** + * @} + */ + diff --git a/drivers/iio/magnetometer/inv_compass/inv_ami306_trigger.c b/drivers/iio/magnetometer/inv_compass/inv_ami306_trigger.c new file mode 100644 index 00000000..f7fe59ef --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/inv_ami306_trigger.c @@ -0,0 +1,90 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_ami306_trigger.c + * @brief Invensense implementation for AMI306 + * @details This driver currently works for the AMI306 + */ + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> +#include <linux/spinlock.h> + +#include "iio.h" +#include "sysfs.h" +#include "trigger.h" +#include "inv_ami306_iio.h" + +static const struct iio_trigger_ops inv_ami306_trigger_ops = { + .owner = THIS_MODULE, +}; + +int inv_ami306_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct inv_ami306_state_s *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("%s-dev%d", + indio_dev->name, + indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + /* select default trigger */ + st->trig->dev.parent = &st->i2c->dev; + st->trig->private_data = indio_dev; + st->trig->ops = &inv_ami306_trigger_ops; + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_trig; + + return 0; + +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void inv_ami306_remove_trigger(struct iio_dev *indio_dev) +{ + struct inv_ami306_state_s *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + iio_free_trigger(st->trig); +} +/** + * @} + */ + diff --git a/drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c b/drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c new file mode 100644 index 00000000..6af420bb --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/inv_yas53x_core.c @@ -0,0 +1,969 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_yas53x_core.c + * @brief Invensense implementation for yas530/yas532/yas533. + * @details This driver currently works for yas530/yas532/yas533. + */ + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> +#include <linux/spinlock.h> + +#include "inv_yas53x_iio.h" +#include "sysfs.h" +#include "inv_test/inv_counters.h" + +/* -------------------------------------------------------------------------- */ +static int Cx, Cy1, Cy2; +static int /*a1, */ a2, a3, a4, a5, a6, a7, a8, a9; +static int k; + +static u8 dx, dy1, dy2; +static u8 d2, d3, d4, d5, d6, d7, d8, d9, d0; +static u8 dck, ver; + +/** + * inv_serial_read() - Read one or more bytes from the device registers. + * @st: Device driver instance. + * @reg: First device register to be read from. + * @length: Number of bytes to read. + * @data: Data read from device. + * NOTE: The slave register will not increment when reading from the FIFO. + */ +int inv_serial_read(struct inv_compass_state *st, u8 reg, u16 length, u8 *data) +{ + int result; + INV_I2C_INC_COMPASSWRITE(3); + INV_I2C_INC_COMPASSREAD(length); + result = i2c_smbus_read_i2c_block_data(st->client, reg, length, data); + if (result != length) { + if (result < 0) + return result; + else + return -EINVAL; + } else { + return 0; + } +} + +/** + * inv_serial_single_write() - Write a byte to a device register. + * @st: Device driver instance. + * @reg: Device register to be written to. + * @data: Byte to write to device. + */ +int inv_serial_single_write(struct inv_compass_state *st, u8 reg, u8 data) +{ + u8 d[1]; + d[0] = data; + INV_I2C_INC_COMPASSWRITE(3); + + return i2c_smbus_write_i2c_block_data(st->client, reg, 1, d); +} + +static int set_hardware_offset(struct inv_compass_state *st, + char offset_x, char offset_y1, char offset_y2) +{ + char data; + int result = 0; + + data = offset_x & 0x3f; + result = inv_serial_single_write(st, YAS530_REGADDR_OFFSET_X, data); + if (result) + return result; + + data = offset_y1 & 0x3f; + result = inv_serial_single_write(st, YAS530_REGADDR_OFFSET_Y1, data); + if (result) + return result; + + data = offset_y2 & 0x3f; + result = inv_serial_single_write(st, YAS530_REGADDR_OFFSET_Y2, data); + return result; +} + +static int set_measure_command(struct inv_compass_state *st) +{ + int result = 0; + result = inv_serial_single_write(st, + YAS530_REGADDR_MEASURE_COMMAND, 0x01); + return result; +} + +static int measure_normal(struct inv_compass_state *st, + int *busy, unsigned short *t, + unsigned short *x, unsigned short *y1, + unsigned short *y2) +{ + int result; + ktime_t sleeptime; + result = set_measure_command(st); + sleeptime = ktime_set(0, 2 * NSEC_PER_MSEC); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_hrtimeout(&sleeptime, HRTIMER_MODE_REL); + + result = st->read_data(st, busy, t, x, y1, y2); + + return result; +} + +static int measure_int(struct inv_compass_state *st, + int *busy, unsigned short *t, + unsigned short *x, unsigned short *y1, + unsigned short *y2) +{ + int result; + if (st->first_read_after_reset) { + st->first_read_after_reset = 0; + result = 1; + } else { + result = st->read_data(st, busy, t, x, y1, y2); + } + result |= set_measure_command(st); + + return result; +} + +static int yas530_read_data(struct inv_compass_state *st, + int *busy, u16 *t, u16 *x, u16 *y1, u16 *y2) +{ + u8 data[8]; + u16 b, to, xo, y1o, y2o; + int result; + + result = inv_serial_read(st, + YAS530_REGADDR_MEASURE_DATA, 8, data); + if (result) + return result; + + b = (data[0] >> 7) & 0x01; + to = (s16)(((data[0] << 2) & 0x1fc) | ((data[1] >> 6) & 0x03)); + xo = (s16)(((data[2] << 5) & 0xfe0) | ((data[3] >> 3) & 0x1f)); + y1o = (s16)(((data[4] << 5) & 0xfe0) | ((data[5] >> 3) & 0x1f)); + y2o = (s16)(((data[6] << 5) & 0xfe0) | ((data[7] >> 3) & 0x1f)); + + *busy = b; + *t = to; + *x = xo; + *y1 = y1o; + *y2 = y2o; + + return 0; +} + +static int yas532_533_read_data(struct inv_compass_state *st, + int *busy, u16 *t, u16 *x, u16 *y1, u16 *y2) +{ + u8 data[8]; + u16 b, to, xo, y1o, y2o; + int result; + + result = inv_serial_read(st, + YAS530_REGADDR_MEASURE_DATA, 8, data); + if (result) + return result; + + b = (data[0] >> 7) & 0x01; + to = (s16)((((s32)data[0] << 3) & 0x3f8) | ((data[1] >> 5) & 0x07)); + xo = (s16)((((s32)data[2] << 6) & 0x1fc0) | ((data[3] >> 2) & 0x3f)); + y1o = (s16)((((s32)data[4] << 6) & 0x1fc0) | ((data[5] >> 2) & 0x3f)); + y2o = (s16)((((s32)data[6] << 6) & 0x1fc0) | ((data[7] >> 2) & 0x3f)); + + *busy = b; + *t = to; + *x = xo; + *y1 = y1o; + *y2 = y2o; + + return 0; +} + +static int check_offset(struct inv_compass_state *st, + char offset_x, char offset_y1, char offset_y2, + int *flag_x, int *flag_y1, int *flag_y2) +{ + int result; + int busy; + short t, x, y1, y2; + + result = set_hardware_offset(st, offset_x, offset_y1, offset_y2); + if (result) + return result; + result = measure_normal(st, &busy, &t, &x, &y1, &y2); + if (result) + return result; + *flag_x = 0; + *flag_y1 = 0; + *flag_y2 = 0; + + if (x > st->center) + *flag_x = 1; + if (y1 > st->center) + *flag_y1 = 1; + if (y2 > st->center) + *flag_y2 = 1; + if (x < st->center) + *flag_x = -1; + if (y1 < st->center) + *flag_y1 = -1; + if (y2 < st->center) + *flag_y2 = -1; + + return result; +} + +static int measure_and_set_offset(struct inv_compass_state *st, + char *offset) +{ + int i; + int result = 0; + char offset_x = 0, offset_y1 = 0, offset_y2 = 0; + int flag_x = 0, flag_y1 = 0, flag_y2 = 0; + static const int correct[5] = {16, 8, 4, 2, 1}; + + for (i = 0; i < 5; i++) { + result = check_offset(st, + offset_x, offset_y1, offset_y2, + &flag_x, &flag_y1, &flag_y2); + if (result) + return result; + if (flag_x) + offset_x += flag_x * correct[i]; + if (flag_y1) + offset_y1 += flag_y1 * correct[i]; + if (flag_y2) + offset_y2 += flag_y2 * correct[i]; + } + + result = set_hardware_offset(st, offset_x, offset_y1, offset_y2); + if (result) + return result; + offset[0] = offset_x; + offset[1] = offset_y1; + offset[2] = offset_y2; + + return result; +} + +static void coordinate_conversion(short x, short y1, short y2, short t, + int *xo, int *yo, int *zo) +{ + int sx, sy1, sy2, sy, sz; + int hx, hy, hz; + + sx = x - (Cx * t) / 100; + sy1 = y1 - (Cy1 * t) / 100; + sy2 = y2 - (Cy2 * t) / 100; + + sy = sy1 - sy2; + sz = -sy1 - sy2; + + hx = k * ((100 * sx + a2 * sy + a3 * sz) / 10); + hy = k * ((a4 * sx + a5 * sy + a6 * sz) / 10); + hz = k * ((a7 * sx + a8 * sy + a9 * sz) / 10); + + *xo = hx; + *yo = hy; + *zo = hz; +} + +static int get_cal_data_yas532_533(struct inv_compass_state *st) +{ + u8 data[YAS_YAS532_533_CAL_DATA_SIZE]; + int result; + + result = inv_serial_read(st, YAS530_REGADDR_CAL, + YAS_YAS532_533_CAL_DATA_SIZE, data); + if (result) + return result; + /* CAL data Second Read */ + result = inv_serial_read(st, YAS530_REGADDR_CAL, + YAS_YAS532_533_CAL_DATA_SIZE, data); + if (result) + return result; + + dx = data[0]; + dy1 = data[1]; + dy2 = data[2]; + d2 = (data[3] >> 2) & 0x03f; + d3 = (u8)(((data[3] << 2) & 0x0c) | ((data[4] >> 6) & 0x03)); + d4 = (u8)(data[4] & 0x3f); + d5 = (data[5] >> 2) & 0x3f; + d6 = (u8)(((data[5] << 4) & 0x30) | ((data[6] >> 4) & 0x0f)); + d7 = (u8)(((data[6] << 3) & 0x78) | ((data[7] >> 5) & 0x07)); + d8 = (u8)(((data[7] << 1) & 0x3e) | ((data[8] >> 7) & 0x01)); + d9 = (u8)(((data[8] << 1) & 0xfe) | ((data[9] >> 7) & 0x01)); + d0 = (u8)((data[9] >> 2) & 0x1f); + dck = (u8)(((data[9] << 1) & 0x06) | ((data[10] >> 7) & 0x01)); + ver = (u8)((data[13]) & 0x01); + + Cx = dx * 10 - 1280; + Cy1 = dy1 * 10 - 1280; + Cy2 = dy2 * 10 - 1280; + a2 = d2 - 32; + a3 = d3 - 8; + a4 = d4 - 32; + a5 = d5 + 38; + a6 = d6 - 32; + a7 = d7 - 64; + a8 = d8 - 32; + a9 = d9; + k = d0; + + return 0; +} + +static int get_cal_data_yas530(struct inv_compass_state *st) +{ + u8 data[YAS_YAS530_CAL_DATA_SIZE]; + int result; + /* CAL data read */ + result = inv_serial_read(st, YAS530_REGADDR_CAL, + YAS_YAS530_CAL_DATA_SIZE, data); + if (result) + return result; + /* CAL data Second Read */ + result = inv_serial_read(st, YAS530_REGADDR_CAL, + YAS_YAS530_CAL_DATA_SIZE, data); + if (result) + return result; + /*Cal data */ + dx = data[0]; + dy1 = data[1]; + dy2 = data[2]; + d2 = (data[3] >> 2) & 0x03f; + d3 = ((data[3] << 2) & 0x0c) | ((data[4] >> 6) & 0x03); + d4 = data[4] & 0x3f; + d5 = (data[5] >> 2) & 0x3f; + d6 = ((data[5] << 4) & 0x30) | ((data[6] >> 4) & 0x0f); + d7 = ((data[6] << 3) & 0x78) | ((data[7] >> 5) & 0x07); + d8 = ((data[7] << 1) & 0x3e) | ((data[8] >> 7) & 0x01); + d9 = ((data[8] << 1) & 0xfe) | ((data[9] >> 7) & 0x01); + d0 = (data[9] >> 2) & 0x1f; + dck = ((data[9] << 1) & 0x06) | ((data[10] >> 7) & 0x01); + ver = (u8)((data[15]) & 0x03); + + /*Correction Data */ + Cx = (int)dx * 6 - 768; + Cy1 = (int)dy1 * 6 - 768; + Cy2 = (int)dy2 * 6 - 768; + a2 = (int)d2 - 32; + a3 = (int)d3 - 8; + a4 = (int)d4 - 32; + a5 = (int)d5 + 38; + a6 = (int)d6 - 32; + a7 = (int)d7 - 64; + a8 = (int)d8 - 32; + a9 = (int)d9; + k = (int)d0 + 10; + + return 0; +} + + +static void thresh_filter_init(struct yas_thresh_filter *thresh_filter, + int threshold) +{ + thresh_filter->threshold = threshold; + thresh_filter->last = 0; +} + +static void +adaptive_filter_init(struct yas_adaptive_filter *adap_filter, int len, + int noise) +{ + int i; + + adap_filter->num = 0; + adap_filter->index = 0; + adap_filter->filter_noise = noise; + adap_filter->filter_len = len; + + for (i = 0; i < adap_filter->filter_len; ++i) + adap_filter->sequence[i] = 0; +} + +static void yas_init_adap_filter(struct inv_compass_state *st) +{ + struct yas_filter *f; + int i; + int noise[] = {YAS_MAG_DEFAULT_FILTER_NOISE_X, + YAS_MAG_DEFAULT_FILTER_NOISE_Y, + YAS_MAG_DEFAULT_FILTER_NOISE_Z}; + + f = &st->filter; + f->filter_len = YAS_MAG_DEFAULT_FILTER_LEN; + for (i = 0; i < 3; i++) + f->filter_noise[i] = noise[i]; + + for (i = 0; i < 3; i++) { + adaptive_filter_init(&f->adap_filter[i], f->filter_len, + f->filter_noise[i]); + thresh_filter_init(&f->thresh_filter[i], f->filter_thresh); + } +} + +int yas53x_resume(struct inv_compass_state *st) +{ + int result = 0; + + unsigned char dummyData = 0x00; + unsigned char read_reg[1]; + + /* =============================================== */ + + /* Step 1 - Test register initialization */ + dummyData = 0x00; + result = inv_serial_single_write(st, + YAS530_REGADDR_TEST1, dummyData); + if (result) + return result; + result = + inv_serial_single_write(st, + YAS530_REGADDR_TEST2, dummyData); + if (result) + return result; + /* Device ID read */ + result = inv_serial_read(st, + YAS530_REGADDR_DEVICE_ID, 1, read_reg); + + /*Step 2 Read the CAL register */ + st->get_cal_data(st); + + /*Obtain the [49:47] bits */ + dck &= 0x07; + + /*Step 3 : Storing the CONFIG with the CLK value */ + dummyData = 0x00 | (dck << 2); + result = inv_serial_single_write(st, + YAS530_REGADDR_CONFIG, dummyData); + if (result) + return result; + /*Step 4 : Set Acquisition Interval Register */ + dummyData = 0x00; + result = inv_serial_single_write(st, + YAS530_REGADDR_MEASURE_INTERVAL, + dummyData); + if (result) + return result; + + /*Step 5 : Reset Coil */ + dummyData = 0x00; + result = inv_serial_single_write(st, + YAS530_REGADDR_ACTUATE_INIT_COIL, + dummyData); + if (result) + return result; + /* Offset Measurement and Set */ + result = measure_and_set_offset(st, st->offset); + if (result) + return result; + st->first_measure_after_reset = 1; + st->first_read_after_reset = 1; + st->reset_timer = 0; + + yas_init_adap_filter(st); + + return result; +} + +static int inv_check_range(struct inv_compass_state *st, s16 x, s16 y1, s16 y2) +{ + int result = 0; + + if (x == 0) + result |= 0x01; + if (x == st->overflow_bound) + result |= 0x02; + if (y1 == 0) + result |= 0x04; + if (y1 == st->overflow_bound) + result |= 0x08; + if (y2 == 0) + result |= 0x10; + if (y2 == st->overflow_bound) + result |= 0x20; + + return result; +} +static int square(int data) +{ + return data * data; +} + +static int +adaptive_filter_filter(struct yas_adaptive_filter *adap_filter, int in) +{ + int avg, sum; + int i; + + if (adap_filter->filter_len == 0) + return in; + if (adap_filter->num < adap_filter->filter_len) { + adap_filter->sequence[adap_filter->index++] = in / 100; + adap_filter->num++; + return in; + } + if (adap_filter->filter_len <= adap_filter->index) + adap_filter->index = 0; + adap_filter->sequence[adap_filter->index++] = in / 100; + + avg = 0; + for (i = 0; i < adap_filter->filter_len; i++) + avg += adap_filter->sequence[i]; + avg /= adap_filter->filter_len; + + sum = 0; + for (i = 0; i < adap_filter->filter_len; i++) + sum += square(avg - adap_filter->sequence[i]); + sum /= adap_filter->filter_len; + + if (sum <= adap_filter->filter_noise) + return avg * 100; + + return ((in/100 - avg) * (sum - adap_filter->filter_noise) / sum + avg) + * 100; +} + +static int +thresh_filter_filter(struct yas_thresh_filter *thresh_filter, int in) +{ + if (in < thresh_filter->last - thresh_filter->threshold + || thresh_filter->last + + thresh_filter->threshold < in) { + thresh_filter->last = in; + return in; + } else { + return thresh_filter->last; + } +} + +static void +filter_filter(struct yas_filter *d, int *orig, int *filtered) +{ + int i; + + for (i = 0; i < 3; i++) { + filtered[i] = adaptive_filter_filter(&d->adap_filter[i], + orig[i]); + filtered[i] = thresh_filter_filter(&d->thresh_filter[i], + filtered[i]); + } +} + +int yas53x_read(struct inv_compass_state *st, short rawfixed[3], + int *overunderflow) +{ + int result = 0; + + int busy, i, ov; + short t, x, y1, y2; + s32 xyz[3], disturb[3]; + + result = measure_int(st, &busy, &t, &x, &y1, &y2); + if (result) + return result; + if (busy) + return -1; + coordinate_conversion(x, y1, y2, t, &xyz[0], &xyz[1], &xyz[2]); + filter_filter(&st->filter, xyz, xyz); + for (i = 0; i < 3; i++) + rawfixed[i] = (short)(xyz[i] / 100); + + if (st->first_measure_after_reset) { + for (i = 0; i < 3; i++) + st->base_compass_data[i] = rawfixed[i]; + st->first_measure_after_reset = 0; + } + ov = 0; + for (i = 0; i < 3; i++) { + disturb[i] = abs(st->base_compass_data[i] - rawfixed[i]); + if (disturb[i] > YAS_MAG_DISTURBURNCE_THRESHOLD) + ov = 1; + } + if (ov) + st->reset_timer += st->delay; + else + st->reset_timer = 0; + + if (st->reset_timer > YAS_RESET_COIL_TIME_THRESHOLD) + *overunderflow = (1<<8); + else + *overunderflow = 0; + *overunderflow |= inv_check_range(st, x, y1, y2); + + return 0; +} + +/** + * yas53x_read_raw() - read raw method. + */ +static int yas53x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) { + struct inv_compass_state *st = iio_priv(indio_dev); + + switch (mask) { + case 0: + if (!(iio_buffer_enabled(indio_dev))) + return -EINVAL; + if (chan->type == IIO_MAGN) { + *val = st->compass_data[chan->channel2 - IIO_MOD_X]; + return IIO_VAL_INT; + } + + return -EINVAL; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_MAGN) { + *val = YAS530_SCALE; + return IIO_VAL_INT; + } + return -EINVAL; + default: + return -EINVAL; + } +} + +/** + * inv_compass_matrix_show() - show orientation matrix + */ +static ssize_t inv_compass_matrix_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + signed char *m; + struct inv_compass_state *st = iio_priv(indio_dev); + 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]); +} + +static ssize_t yas53x_rate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 data; + int error; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_compass_state *st = iio_priv(indio_dev); + + error = kstrtoint(buf, 10, &data); + if (error) + return error; + if (0 == data) + return -EINVAL; + /* transform rate to delay in ms */ + data = MSEC_PER_SEC / data; + + if (data > YAS530_MAX_DELAY) + data = YAS530_MAX_DELAY; + if (data < YAS530_MIN_DELAY) + data = YAS530_MIN_DELAY; + st->delay = data; + + return count; +} + +static ssize_t yas53x_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_compass_state *st = iio_priv(indio_dev); + /* transform delay in ms to rate */ + return sprintf(buf, "%d\n", (int)MSEC_PER_SEC / st->delay); +} + +static ssize_t yas53x_overunderflow_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 data; + int error; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_compass_state *st = iio_priv(indio_dev); + + error = kstrtoint(buf, 10, &data); + if (error) + return error; + if (data) + return -EINVAL; + st->overunderflow = data; + + return count; +} + +static ssize_t yas53x_overunderflow_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_compass_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->overunderflow); +} + +void set_yas53x_enable(struct iio_dev *indio_dev, bool enable) +{ + struct inv_compass_state *st = iio_priv(indio_dev); + + yas_init_adap_filter(st); + st->first_measure_after_reset = 1; + st->first_read_after_reset = 1; + schedule_delayed_work(&st->work, msecs_to_jiffies(st->delay)); +} + +static void yas53x_work_func(struct work_struct *work) +{ + struct inv_compass_state *st = + container_of((struct delayed_work *)work, + struct inv_compass_state, work); + struct iio_dev *indio_dev = iio_priv_to_dev(st); + u32 delay = msecs_to_jiffies(st->delay); + + mutex_lock(&indio_dev->mlock); + if (!(iio_buffer_enabled(indio_dev))) + goto error_ret; + + schedule_delayed_work(&st->work, delay); + inv_read_yas53x_fifo(indio_dev); + INV_I2C_INC_COMPASSIRQ(); + +error_ret: + mutex_unlock(&indio_dev->mlock); +} + +static const struct iio_chan_spec compass_channels[] = { + { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = INV_YAS53X_SCAN_MAGN_X, + .scan_type = IIO_ST('s', 16, 16, 0) + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = INV_YAS53X_SCAN_MAGN_Y, + .scan_type = IIO_ST('s', 16, 16, 0) + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = INV_YAS53X_SCAN_MAGN_Z, + .scan_type = IIO_ST('s', 16, 16, 0) + }, + IIO_CHAN_SOFT_TIMESTAMP(INV_YAS53X_SCAN_TIMESTAMP) +}; + +static DEVICE_ATTR(compass_matrix, S_IRUGO, inv_compass_matrix_show, NULL); +static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, yas53x_rate_show, + yas53x_rate_store); +static DEVICE_ATTR(overunderflow, S_IRUGO | S_IWUSR, + yas53x_overunderflow_show, yas53x_overunderflow_store); + +static struct attribute *inv_yas53x_attributes[] = { + &dev_attr_compass_matrix.attr, + &dev_attr_sampling_frequency.attr, + &dev_attr_overunderflow.attr, + NULL, +}; +static const struct attribute_group inv_attribute_group = { + .name = "yas53x", + .attrs = inv_yas53x_attributes +}; + +static const struct iio_info yas53x_info = { + .driver_module = THIS_MODULE, + .read_raw = &yas53x_read_raw, + .attrs = &inv_attribute_group, +}; + +/*constant IIO attribute */ +/** + * inv_yas53x_probe() - probe function. + */ +static int inv_yas53x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct inv_compass_state *st; + struct iio_dev *indio_dev; + int result; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENODEV; + goto out_no_free; + } + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + result = -ENOMEM; + goto out_no_free; + } + st = iio_priv(indio_dev); + st->client = client; + st->plat_data = + *(struct mpu_platform_data *)dev_get_platdata(&client->dev); + st->delay = 10; + + i2c_set_clientdata(client, indio_dev); + + if (!strcmp(id->name, "yas530")) { + st->read_data = yas530_read_data; + st->get_cal_data = get_cal_data_yas530; + st->overflow_bound = YAS_YAS530_DATA_OVERFLOW; + st->center = YAS_YAS530_DATA_CENTER; + st->filter.filter_thresh = YAS530_MAG_DEFAULT_FILTER_THRESH; + } else { + st->read_data = yas532_533_read_data; + st->get_cal_data = get_cal_data_yas532_533; + st->overflow_bound = YAS_YAS532_533_DATA_OVERFLOW; + st->center = YAS_YAS532_533_DATA_CENTER; + st->filter.filter_thresh = YAS532_MAG_DEFAULT_FILTER_THRESH; + } + st->upper_bound = st->center + (st->center >> 1); + st->lower_bound = (st->center >> 1); + + result = yas53x_resume(st); + if (result) + goto out_free; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->channels = compass_channels; + indio_dev->num_channels = ARRAY_SIZE(compass_channels); + indio_dev->info = &yas53x_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->currentmode = INDIO_DIRECT_MODE; + + result = inv_yas53x_configure_ring(indio_dev); + if (result) + goto out_free; + result = iio_buffer_register(indio_dev, indio_dev->channels, + indio_dev->num_channels); + if (result) + goto out_unreg_ring; + result = inv_yas53x_probe_trigger(indio_dev); + if (result) + goto out_remove_ring; + + result = iio_device_register(indio_dev); + if (result) + goto out_remove_trigger; + INIT_DELAYED_WORK(&st->work, yas53x_work_func); + pr_info("%s: Probe name %s\n", __func__, id->name); + + return 0; +out_remove_trigger: + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + inv_yas53x_remove_trigger(indio_dev); +out_remove_ring: + iio_buffer_unregister(indio_dev); +out_unreg_ring: + inv_yas53x_unconfigure_ring(indio_dev); +out_free: + iio_free_device(indio_dev); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + return -EIO; +} + +/** + * inv_yas53x_remove() - remove function. + */ +static int inv_yas53x_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_compass_state *st = iio_priv(indio_dev); + cancel_delayed_work_sync(&st->work); + iio_device_unregister(indio_dev); + inv_yas53x_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + inv_yas53x_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + dev_info(&client->adapter->dev, "inv_yas53x_iio module removed.\n"); + return 0; +} +static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; +/* device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_yas53x_id[] = { + {"yas530", 0}, + {"yas532", 0}, + {"yas533", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, inv_yas53x_id); + +static struct i2c_driver inv_yas53x_driver = { + .class = I2C_CLASS_HWMON, + .probe = inv_yas53x_probe, + .remove = inv_yas53x_remove, + .id_table = inv_yas53x_id, + .driver = { + .owner = THIS_MODULE, + .name = "inv_yas53x_iio", + }, + .address_list = normal_i2c, +}; + +static int __init inv_yas53x_init(void) +{ + int result = i2c_add_driver(&inv_yas53x_driver); + if (result) { + pr_err("%s failed\n", __func__); + return result; + } + return 0; +} + +static void __exit inv_yas53x_exit(void) +{ + i2c_del_driver(&inv_yas53x_driver); +} + +module_init(inv_yas53x_init); +module_exit(inv_yas53x_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("inv_yas53x_iio"); +/** + * @} + */ + diff --git a/drivers/iio/magnetometer/inv_compass/inv_yas53x_iio.h b/drivers/iio/magnetometer/inv_compass/inv_yas53x_iio.h new file mode 100644 index 00000000..92bf0af7 --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/inv_yas53x_iio.h @@ -0,0 +1,172 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_yas53x_iio.h + * @brief Struct definitions for the Invensense implementation + * of yas53x driver. + */ + +#ifndef _INV_GYRO_H_ +#define _INV_GYRO_H_ + +#include <linux/i2c.h> +#include <linux/kfifo.h> +#include <linux/miscdevice.h> +#include <linux/input.h> +#include <linux/spinlock.h> +#include <linux/mpu.h> + +#include "iio.h" +#include "buffer.h" +#include "trigger.h" + +#define YAS_MAG_MAX_FILTER_LEN 30 +struct yas_adaptive_filter { + int num; + int index; + int filter_len; + int filter_noise; + int sequence[YAS_MAG_MAX_FILTER_LEN]; +}; + +struct yas_thresh_filter { + int threshold; + int last; +}; + +struct yas_filter { + int filter_len; + int filter_thresh; + int filter_noise[3]; + struct yas_adaptive_filter adap_filter[3]; + struct yas_thresh_filter thresh_filter[3]; +}; + +/** + * struct inv_compass_state - Driver state variables. + * @plat_data: mpu platform data from board file. + * @client: i2c client handle. + * @chan_info: channel information. + * @trig: IIO trigger. + * @work: work structure. + * @delay: delay to schedule the next work. + * @overflow_bound: bound to determine overflow. + * @center: center of the measurement. + * @compass_data[3]: compass data store. + * @offset[3]: yas530 specific data. + * @base_compass_data[3]: first measure data after reset. + * @first_measure_after_reset:1: flag for first measurement after reset. + * @first_read_after_reset:1: flag for first read after reset. + * @reset_timer: timer to accumulate overflow conditions. + * @overunderflow:1: overflow and underflow flag. + * @filter: filter data structure. + * @read_data: function pointer of reading data from device. + * @get_cal_data: function pointer of reading cal data. + */ +struct inv_compass_state { + struct mpu_platform_data plat_data; + struct i2c_client *client; + struct iio_trigger *trig; + struct delayed_work work; + s16 delay; + s16 overflow_bound; + s16 upper_bound; + s16 lower_bound; + s16 center; + s16 compass_data[3]; + s8 offset[3]; + s16 base_compass_data[3]; + u8 first_measure_after_reset:1; + u8 first_read_after_reset:1; + u8 overunderflow:1; + s32 reset_timer; + struct yas_filter filter; + int (*read_data)(struct inv_compass_state *st, + int *, u16 *, u16 *, u16 *, u16 *); + int (*get_cal_data)(struct inv_compass_state *); +}; + +/* scan element definition */ +enum inv_mpu_scan { + INV_YAS53X_SCAN_MAGN_X, + INV_YAS53X_SCAN_MAGN_Y, + INV_YAS53X_SCAN_MAGN_Z, + INV_YAS53X_SCAN_TIMESTAMP, +}; + +#define YAS530_REGADDR_DEVICE_ID 0x80 +#define YAS530_REGADDR_ACTUATE_INIT_COIL 0x81 +#define YAS530_REGADDR_MEASURE_COMMAND 0x82 +#define YAS530_REGADDR_CONFIG 0x83 +#define YAS530_REGADDR_MEASURE_INTERVAL 0x84 +#define YAS530_REGADDR_OFFSET_X 0x85 +#define YAS530_REGADDR_OFFSET_Y1 0x86 +#define YAS530_REGADDR_OFFSET_Y2 0x87 +#define YAS530_REGADDR_TEST1 0x88 +#define YAS530_REGADDR_TEST2 0x89 +#define YAS530_REGADDR_CAL 0x90 +#define YAS530_REGADDR_MEASURE_DATA 0xb0 + +#define YAS530_MAX_DELAY 200 +#define YAS530_MIN_DELAY 5 +#define YAS530_SCALE 107374182L + +#define YAS_YAS530_VERSION_A 0 /* YAS530 (MS-3E Aver) */ +#define YAS_YAS530_VERSION_B 1 /* YAS530B (MS-3E Bver) */ +#define YAS_YAS530_VERSION_A_COEF 380 +#define YAS_YAS530_VERSION_B_COEF 550 +#define YAS_YAS530_DATA_CENTER 2048 +#define YAS_YAS530_DATA_OVERFLOW 4095 +#define YAS_YAS530_CAL_DATA_SIZE 16 + +/*filter related defines */ +#define YAS_MAG_DEFAULT_FILTER_NOISE_X 144 /* sd: 1200 nT */ +#define YAS_MAG_DEFAULT_FILTER_NOISE_Y 144 /* sd: 1200 nT */ +#define YAS_MAG_DEFAULT_FILTER_NOISE_Z 144 /* sd: 1200 nT */ +#define YAS_MAG_DEFAULT_FILTER_LEN 20 + +#define YAS530_MAG_DEFAULT_FILTER_THRESH 100 +#define YAS532_MAG_DEFAULT_FILTER_THRESH 300 + +#define YAS_YAS532_533_VERSION_AB 0 /* YAS532_533AB (MS-3R/3F ABver) */ +#define YAS_YAS532_533_VERSION_AC 1 /* YAS532_533AC (MS-3R/3F ACver) */ +#define YAS_YAS532_533_VERSION_AB_COEF 1800 +#define YAS_YAS532_533_VERSION_AC_COEF 900 +#define YAS_YAS532_533_DATA_CENTER 4096 +#define YAS_YAS532_533_DATA_OVERFLOW 8190 +#define YAS_YAS532_533_CAL_DATA_SIZE 14 + +#define YAS_MAG_DISTURBURNCE_THRESHOLD 1600 +#define YAS_RESET_COIL_TIME_THRESHOLD 3000 + +#define INV_ERROR_COMPASS_DATA_OVERFLOW (-1) +#define INV_ERROR_COMPASS_DATA_NOT_READY (-2) + +int inv_yas53x_configure_ring(struct iio_dev *indio_dev); +void inv_yas53x_unconfigure_ring(struct iio_dev *indio_dev); +int inv_yas53x_probe_trigger(struct iio_dev *indio_dev); +void inv_yas53x_remove_trigger(struct iio_dev *indio_dev); +void set_yas53x_enable(struct iio_dev *indio_dev, bool enable); +void inv_read_yas53x_fifo(struct iio_dev *indio_dev); +int yas53x_read(struct inv_compass_state *st, short rawfixed[3], + s32 *overunderflow); +int yas53x_resume(struct inv_compass_state *st); + +#endif /* #ifndef _INV_GYRO_H_ */ + diff --git a/drivers/iio/magnetometer/inv_compass/inv_yas53x_ring.c b/drivers/iio/magnetometer/inv_compass/inv_yas53x_ring.c new file mode 100644 index 00000000..efcf49c6 --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/inv_yas53x_ring.c @@ -0,0 +1,165 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_yas53x_ring.c + * @brief Invensense implementation for yas530/yas532/yas533. + * @details This driver currently works for the yas530/yas532/yas533. + */ + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> + +#include "iio.h" +#include "kfifo_buf.h" +#include "trigger_consumer.h" +#include "sysfs.h" + +#include "inv_yas53x_iio.h" + +static s64 get_time_ns(void) +{ + struct timespec ts; + ktime_get_ts(&ts); + + return timespec_to_ns(&ts); +} + +static int put_scan_to_buf(struct iio_dev *indio_dev, unsigned char *d, + short *s, int scan_index) +{ + struct iio_buffer *ring = indio_dev->buffer; + int st; + int i, d_ind; + + d_ind = 0; + for (i = 0; i < 3; i++) { + st = iio_scan_mask_query(indio_dev, ring, scan_index + i); + if (st) { + memcpy(&d[d_ind], &s[i], sizeof(s[i])); + d_ind += sizeof(s[i]); + } + } + + return d_ind; +} + +/** + * inv_read_yas53x_fifo() - Transfer data from FIFO to ring buffer. + */ +void inv_read_yas53x_fifo(struct iio_dev *indio_dev) +{ + struct inv_compass_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + int d_ind; + s32 overunderflow; + s8 *tmp; + s64 tmp_buf[2]; + + if (!yas53x_read(st, st->compass_data, &overunderflow)) { + tmp = (u8 *)tmp_buf; + d_ind = put_scan_to_buf(indio_dev, tmp, st->compass_data, + INV_YAS53X_SCAN_MAGN_X); + if (ring->scan_timestamp) + tmp_buf[(d_ind + 7) / 8] = get_time_ns(); + ring->access->store_to(indio_dev->buffer, tmp, 0); + + if (overunderflow) { + yas53x_resume(st); + if (!st->overunderflow) + st->overunderflow = 1; + } + } +} + +void inv_yas53x_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_kfifo_free(indio_dev->buffer); +}; + +static int inv_yas53x_postenable(struct iio_dev *indio_dev) +{ + struct inv_compass_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + + /* when all the outputs are disabled, even though buffer/enable is on, + do nothing */ + if (!(iio_scan_mask_query(indio_dev, ring, INV_YAS53X_SCAN_MAGN_X) || + iio_scan_mask_query(indio_dev, ring, INV_YAS53X_SCAN_MAGN_Y) || + iio_scan_mask_query(indio_dev, ring, INV_YAS53X_SCAN_MAGN_Z))) + return 0; + + set_yas53x_enable(indio_dev, true); + schedule_delayed_work(&st->work, + msecs_to_jiffies(st->delay)); + + return 0; +} + +static int inv_yas53x_predisable(struct iio_dev *indio_dev) +{ + struct inv_compass_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + + cancel_delayed_work_sync(&st->work); + clear_bit(INV_YAS53X_SCAN_MAGN_X, ring->scan_mask); + clear_bit(INV_YAS53X_SCAN_MAGN_Y, ring->scan_mask); + clear_bit(INV_YAS53X_SCAN_MAGN_Z, ring->scan_mask); + + return 0; +} + +static const struct iio_buffer_setup_ops inv_yas53x_ring_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &inv_yas53x_postenable, + .predisable = &inv_yas53x_predisable, +}; + +int inv_yas53x_configure_ring(struct iio_dev *indio_dev) +{ + int ret = 0; + struct iio_buffer *ring; + + ring = iio_kfifo_allocate(indio_dev); + if (!ring) { + ret = -ENOMEM; + return ret; + } + indio_dev->buffer = ring; + /* setup ring buffer */ + ring->scan_timestamp = true; + indio_dev->setup_ops = &inv_yas53x_ring_setup_ops; + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; +} +/** + * @} + */ + diff --git a/drivers/iio/magnetometer/inv_compass/inv_yas53x_trigger.c b/drivers/iio/magnetometer/inv_compass/inv_yas53x_trigger.c new file mode 100644 index 00000000..a20ce2ba --- /dev/null +++ b/drivers/iio/magnetometer/inv_compass/inv_yas53x_trigger.c @@ -0,0 +1,91 @@ +/* +* 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. +* +*/ + +/** + * @addtogroup DRIVERS + * @brief Hardware drivers. + * + * @{ + * @file inv_yas53x_trigger.c + * @brief Invensense implementation for yas530/yas532/yas533 + * @details This driver currently works for the yas530/yas532/yas533 + */ + +#include <linux/module.h> +#include <linux/init.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 <linux/miscdevice.h> +#include <linux/spinlock.h> + +#include "iio.h" +#include "sysfs.h" +#include "trigger.h" + +#include "inv_yas53x_iio.h" + +static const struct iio_trigger_ops inv_yas53x_trigger_ops = { + .owner = THIS_MODULE, +}; + +int inv_yas53x_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct inv_compass_state *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("%s-dev%d", + indio_dev->name, + indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + /* select default trigger */ + st->trig->dev.parent = &st->client->dev; + st->trig->private_data = indio_dev; + st->trig->ops = &inv_yas53x_trigger_ops; + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_trig; + + return 0; + +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void inv_yas53x_remove_trigger(struct iio_dev *indio_dev) +{ + struct inv_compass_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + iio_free_trigger(st->trig); +} +/** + * @} + */ + diff --git a/drivers/iio/magnetometer/st_magn_core.c b/drivers/iio/magnetometer/st_magn_core.c new file mode 100644 index 00000000..3ce3769c --- /dev/null +++ b/drivers/iio/magnetometer/st_magn_core.c @@ -0,0 +1,408 @@ +/* + * 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" + +/* DEFAULT VALUE FOR SENSORS */ +#define ST_MAGN_DEFAULT_OUT_X_L_ADDR 0X04 +#define ST_MAGN_DEFAULT_OUT_Y_L_ADDR 0X08 +#define ST_MAGN_DEFAULT_OUT_Z_L_ADDR 0X06 + +/* FULLSCALE */ +#define ST_MAGN_FS_AVL_1300MG 1300 +#define ST_MAGN_FS_AVL_1900MG 1900 +#define ST_MAGN_FS_AVL_2500MG 2500 +#define ST_MAGN_FS_AVL_4000MG 4000 +#define ST_MAGN_FS_AVL_4700MG 4700 +#define ST_MAGN_FS_AVL_5600MG 5600 +#define ST_MAGN_FS_AVL_8000MG 8000 +#define ST_MAGN_FS_AVL_8100MG 8100 +#define ST_MAGN_FS_AVL_12000MG 12000 +#define ST_MAGN_FS_AVL_16000MG 16000 + +/* CUSTOM VALUES FOR SENSOR 1 */ +#define ST_MAGN_1_WAI_EXP 0x3c +#define ST_MAGN_1_ODR_ADDR 0x00 +#define ST_MAGN_1_ODR_MASK 0x1c +#define ST_MAGN_1_ODR_AVL_1HZ_VAL 0x00 +#define ST_MAGN_1_ODR_AVL_2HZ_VAL 0x01 +#define ST_MAGN_1_ODR_AVL_3HZ_VAL 0x02 +#define ST_MAGN_1_ODR_AVL_8HZ_VAL 0x03 +#define ST_MAGN_1_ODR_AVL_15HZ_VAL 0x04 +#define ST_MAGN_1_ODR_AVL_30HZ_VAL 0x05 +#define ST_MAGN_1_ODR_AVL_75HZ_VAL 0x06 +#define ST_MAGN_1_ODR_AVL_220HZ_VAL 0x07 +#define ST_MAGN_1_PW_ADDR 0x02 +#define ST_MAGN_1_PW_MASK 0x03 +#define ST_MAGN_1_PW_ON 0x00 +#define ST_MAGN_1_PW_OFF 0x03 +#define ST_MAGN_1_FS_ADDR 0x01 +#define ST_MAGN_1_FS_MASK 0xe0 +#define ST_MAGN_1_FS_AVL_1300_VAL 0x01 +#define ST_MAGN_1_FS_AVL_1900_VAL 0x02 +#define ST_MAGN_1_FS_AVL_2500_VAL 0x03 +#define ST_MAGN_1_FS_AVL_4000_VAL 0x04 +#define ST_MAGN_1_FS_AVL_4700_VAL 0x05 +#define ST_MAGN_1_FS_AVL_5600_VAL 0x06 +#define ST_MAGN_1_FS_AVL_8100_VAL 0x07 +#define ST_MAGN_1_FS_AVL_1300_GAIN_XY 909 +#define ST_MAGN_1_FS_AVL_1900_GAIN_XY 1169 +#define ST_MAGN_1_FS_AVL_2500_GAIN_XY 1492 +#define ST_MAGN_1_FS_AVL_4000_GAIN_XY 2222 +#define ST_MAGN_1_FS_AVL_4700_GAIN_XY 2500 +#define ST_MAGN_1_FS_AVL_5600_GAIN_XY 3030 +#define ST_MAGN_1_FS_AVL_8100_GAIN_XY 4347 +#define ST_MAGN_1_FS_AVL_1300_GAIN_Z 1020 +#define ST_MAGN_1_FS_AVL_1900_GAIN_Z 1315 +#define ST_MAGN_1_FS_AVL_2500_GAIN_Z 1666 +#define ST_MAGN_1_FS_AVL_4000_GAIN_Z 2500 +#define ST_MAGN_1_FS_AVL_4700_GAIN_Z 2816 +#define ST_MAGN_1_FS_AVL_5600_GAIN_Z 3389 +#define ST_MAGN_1_FS_AVL_8100_GAIN_Z 4878 +#define ST_MAGN_1_MULTIREAD_BIT false + +/* CUSTOM VALUES FOR SENSOR 2 */ +#define ST_MAGN_2_WAI_EXP 0x3d +#define ST_MAGN_2_ODR_ADDR 0x20 +#define ST_MAGN_2_ODR_MASK 0x1c +#define ST_MAGN_2_ODR_AVL_1HZ_VAL 0x00 +#define ST_MAGN_2_ODR_AVL_2HZ_VAL 0x01 +#define ST_MAGN_2_ODR_AVL_3HZ_VAL 0x02 +#define ST_MAGN_2_ODR_AVL_5HZ_VAL 0x03 +#define ST_MAGN_2_ODR_AVL_10HZ_VAL 0x04 +#define ST_MAGN_2_ODR_AVL_20HZ_VAL 0x05 +#define ST_MAGN_2_ODR_AVL_40HZ_VAL 0x06 +#define ST_MAGN_2_ODR_AVL_80HZ_VAL 0x07 +#define ST_MAGN_2_PW_ADDR 0x22 +#define ST_MAGN_2_PW_MASK 0x03 +#define ST_MAGN_2_PW_ON 0x00 +#define ST_MAGN_2_PW_OFF 0x03 +#define ST_MAGN_2_FS_ADDR 0x21 +#define ST_MAGN_2_FS_MASK 0x60 +#define ST_MAGN_2_FS_AVL_4000_VAL 0x00 +#define ST_MAGN_2_FS_AVL_8000_VAL 0x01 +#define ST_MAGN_2_FS_AVL_12000_VAL 0x02 +#define ST_MAGN_2_FS_AVL_16000_VAL 0x03 +#define ST_MAGN_2_FS_AVL_4000_GAIN 146 +#define ST_MAGN_2_FS_AVL_8000_GAIN 292 +#define ST_MAGN_2_FS_AVL_12000_GAIN 438 +#define ST_MAGN_2_FS_AVL_16000_GAIN 584 +#define ST_MAGN_2_MULTIREAD_BIT false +#define ST_MAGN_2_OUT_X_L_ADDR 0x28 +#define ST_MAGN_2_OUT_Y_L_ADDR 0x2a +#define ST_MAGN_2_OUT_Z_L_ADDR 0x2c + +static const struct iio_chan_spec st_magn_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_X, IIO_MOD_X, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_DEFAULT_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_Y, IIO_MOD_Y, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_DEFAULT_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_Z, IIO_MOD_Z, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_DEFAULT_OUT_Z_L_ADDR), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static const struct iio_chan_spec st_magn_2_16bit_channels[] = { + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_X, IIO_MOD_X, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_2_OUT_X_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_Y, IIO_MOD_Y, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, ST_MAGN_2_OUT_Y_L_ADDR), + ST_SENSORS_LSM_CHANNELS(IIO_MAGN, ST_SENSORS_SCAN_Z, IIO_MOD_Z, IIO_LE, + ST_SENSORS_DEFAULT_16_REALBITS, 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_12000MG, + .value = ST_MAGN_2_FS_AVL_12000_VAL, + .gain = ST_MAGN_2_FS_AVL_12000_GAIN, + }, + [3] = { + .num = ST_MAGN_FS_AVL_16000MG, + .value = ST_MAGN_2_FS_AVL_16000_VAL, + .gain = ST_MAGN_2_FS_AVL_16000_GAIN, + }, + }, + }, + .multi_read_bit = ST_MAGN_2_MULTIREAD_BIT, + .bootime = 2, + }, +}; + +static int st_magn_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *ch, int *val, + int *val2, long mask) +{ + int err; + struct st_sensor_data *mdata = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + err = st_sensors_read_info_raw(indio_dev, ch, val); + if (err < 0) + goto read_error; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if ((ch->scan_index == ST_SENSORS_SCAN_Z) && + (mdata->current_fullscale->gain2 != 0)) + *val2 = mdata->current_fullscale->gain2; + else + *val2 = mdata->current_fullscale->gain; + return IIO_VAL_INT_PLUS_MICRO; + 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) +{ + int err; + struct st_sensor_data *mdata = iio_priv(indio_dev); + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &magn_info; + + err = st_sensors_check_device_support(indio_dev, + ARRAY_SIZE(st_magn_sensors), st_magn_sensors); + if (err < 0) + goto st_magn_common_probe_error; + + 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); + if (err < 0) + goto st_magn_common_probe_error; + + if (mdata->get_irq_data_ready(indio_dev) > 0) { + err = st_magn_allocate_ring(indio_dev); + if (err < 0) + goto st_magn_common_probe_error; + 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; + + return err; + +st_magn_device_register_error: + if (mdata->get_irq_data_ready(indio_dev) > 0) + st_sensors_deallocate_trigger(indio_dev); +st_magn_probe_trigger_error: + if (mdata->get_irq_data_ready(indio_dev) > 0) + st_magn_deallocate_ring(indio_dev); +st_magn_common_probe_error: + 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); + + 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); + } + iio_device_free(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"); |
