From 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 Mon Sep 17 00:00:00 2001 From: Yunhong Jiang Date: Tue, 4 Aug 2015 12:17:53 -0700 Subject: Add the rt linux 4.1.3-rt3 as base Import the rt linux 4.1.3-rt3 as OPNFV kvm base. It's from git://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-rt-devel.git linux-4.1.y-rt and the base is: commit 0917f823c59692d751951bf5ea699a2d1e2f26a2 Author: Sebastian Andrzej Siewior Date: Sat Jul 25 12:13:34 2015 +0200 Prepare v4.1.3-rt3 Signed-off-by: Sebastian Andrzej Siewior We lose all the git history this way and it's not good. We should apply another opnfv project repo in future. Change-Id: I87543d81c9df70d99c5001fbdf646b202c19f423 Signed-off-by: Yunhong Jiang --- .../drivers/staging/iio/Documentation/dac/max517 | 41 + .../drivers/staging/iio/Documentation/device.txt | 79 + .../drivers/staging/iio/Documentation/inkernel.txt | 58 + .../light/sysfs-bus-iio-light-tsl2583 | 6 + .../light/sysfs-bus-iio-light-tsl2x7x | 13 + .../drivers/staging/iio/Documentation/overview.txt | 57 + kernel/drivers/staging/iio/Documentation/ring.txt | 47 + .../staging/iio/Documentation/sysfs-bus-iio-ad7192 | 20 + .../iio/Documentation/sysfs-bus-iio-adc-ad7280a | 21 + .../staging/iio/Documentation/sysfs-bus-iio-dds | 96 + .../sysfs-bus-iio-impedance-analyzer-ad5933 | 30 + .../staging/iio/Documentation/sysfs-bus-iio-light | 107 + .../iio/Documentation/sysfs-bus-iio-light-tsl2583 | 20 + .../drivers/staging/iio/Documentation/trigger.txt | 35 + kernel/drivers/staging/iio/Kconfig | 48 + kernel/drivers/staging/iio/Makefile | 23 + kernel/drivers/staging/iio/TODO | 84 + kernel/drivers/staging/iio/accel/Kconfig | 101 + kernel/drivers/staging/iio/accel/Makefile | 28 + kernel/drivers/staging/iio/accel/adis16201.h | 66 + kernel/drivers/staging/iio/accel/adis16201_core.c | 248 +++ kernel/drivers/staging/iio/accel/adis16203.h | 59 + kernel/drivers/staging/iio/accel/adis16203_core.c | 216 ++ kernel/drivers/staging/iio/accel/adis16204.h | 68 + kernel/drivers/staging/iio/accel/adis16204_core.c | 254 +++ kernel/drivers/staging/iio/accel/adis16209.h | 105 + kernel/drivers/staging/iio/accel/adis16209_core.c | 248 +++ kernel/drivers/staging/iio/accel/adis16220.h | 140 ++ kernel/drivers/staging/iio/accel/adis16220_core.c | 495 +++++ kernel/drivers/staging/iio/accel/adis16240.h | 129 ++ kernel/drivers/staging/iio/accel/adis16240_core.c | 301 +++ kernel/drivers/staging/iio/accel/lis3l02dq.h | 212 ++ kernel/drivers/staging/iio/accel/lis3l02dq_core.c | 813 ++++++++ kernel/drivers/staging/iio/accel/lis3l02dq_ring.c | 426 ++++ kernel/drivers/staging/iio/accel/sca3000.h | 278 +++ kernel/drivers/staging/iio/accel/sca3000_core.c | 1209 +++++++++++ kernel/drivers/staging/iio/accel/sca3000_ring.c | 348 ++++ kernel/drivers/staging/iio/adc/Kconfig | 118 ++ kernel/drivers/staging/iio/adc/Makefile | 17 + kernel/drivers/staging/iio/adc/ad7192.c | 720 +++++++ kernel/drivers/staging/iio/adc/ad7192.h | 47 + kernel/drivers/staging/iio/adc/ad7280a.c | 985 +++++++++ kernel/drivers/staging/iio/adc/ad7280a.h | 38 + kernel/drivers/staging/iio/adc/ad7606.h | 104 + kernel/drivers/staging/iio/adc/ad7606_core.c | 603 ++++++ kernel/drivers/staging/iio/adc/ad7606_par.c | 152 ++ kernel/drivers/staging/iio/adc/ad7606_ring.c | 101 + kernel/drivers/staging/iio/adc/ad7606_spi.c | 116 ++ kernel/drivers/staging/iio/adc/ad7780.c | 276 +++ kernel/drivers/staging/iio/adc/ad7780.h | 30 + kernel/drivers/staging/iio/adc/ad7816.c | 446 ++++ kernel/drivers/staging/iio/adc/lpc32xx_adc.c | 215 ++ kernel/drivers/staging/iio/adc/mxs-lradc.c | 1712 +++++++++++++++ kernel/drivers/staging/iio/adc/spear_adc.c | 400 ++++ kernel/drivers/staging/iio/addac/Kconfig | 37 + kernel/drivers/staging/iio/addac/Makefile | 7 + kernel/drivers/staging/iio/addac/adt7316-i2c.c | 136 ++ kernel/drivers/staging/iio/addac/adt7316-spi.c | 144 ++ kernel/drivers/staging/iio/addac/adt7316.c | 2188 ++++++++++++++++++++ kernel/drivers/staging/iio/addac/adt7316.h | 36 + kernel/drivers/staging/iio/cdc/Kconfig | 36 + kernel/drivers/staging/iio/cdc/Makefile | 7 + kernel/drivers/staging/iio/cdc/ad7150.c | 679 ++++++ kernel/drivers/staging/iio/cdc/ad7152.c | 543 +++++ kernel/drivers/staging/iio/cdc/ad7746.c | 791 +++++++ kernel/drivers/staging/iio/cdc/ad7746.h | 29 + kernel/drivers/staging/iio/frequency/Kconfig | 26 + kernel/drivers/staging/iio/frequency/Makefile | 6 + kernel/drivers/staging/iio/frequency/ad9832.c | 352 ++++ kernel/drivers/staging/iio/frequency/ad9832.h | 126 ++ kernel/drivers/staging/iio/frequency/ad9834.c | 459 ++++ kernel/drivers/staging/iio/frequency/ad9834.h | 111 + kernel/drivers/staging/iio/frequency/dds.h | 114 + kernel/drivers/staging/iio/gyro/Kconfig | 16 + kernel/drivers/staging/iio/gyro/Makefile | 6 + kernel/drivers/staging/iio/gyro/adis16060_core.c | 246 +++ kernel/drivers/staging/iio/iio_dummy_evgen.c | 239 +++ kernel/drivers/staging/iio/iio_dummy_evgen.h | 13 + kernel/drivers/staging/iio/iio_simple_dummy.c | 749 +++++++ kernel/drivers/staging/iio/iio_simple_dummy.h | 129 ++ .../drivers/staging/iio/iio_simple_dummy_buffer.c | 192 ++ .../drivers/staging/iio/iio_simple_dummy_events.c | 267 +++ .../drivers/staging/iio/impedance-analyzer/Kconfig | 18 + .../staging/iio/impedance-analyzer/Makefile | 5 + .../staging/iio/impedance-analyzer/ad5933.c | 804 +++++++ .../staging/iio/impedance-analyzer/ad5933.h | 28 + kernel/drivers/staging/iio/light/Kconfig | 43 + kernel/drivers/staging/iio/light/Makefile | 8 + kernel/drivers/staging/iio/light/isl29018.c | 815 ++++++++ kernel/drivers/staging/iio/light/isl29028.c | 562 +++++ kernel/drivers/staging/iio/light/tsl2583.c | 949 +++++++++ kernel/drivers/staging/iio/light/tsl2x7x.h | 100 + kernel/drivers/staging/iio/light/tsl2x7x_core.c | 2030 ++++++++++++++++++ kernel/drivers/staging/iio/magnetometer/Kconfig | 40 + kernel/drivers/staging/iio/magnetometer/Makefile | 7 + kernel/drivers/staging/iio/magnetometer/hmc5843.h | 66 + .../staging/iio/magnetometer/hmc5843_core.c | 646 ++++++ .../drivers/staging/iio/magnetometer/hmc5843_i2c.c | 104 + .../drivers/staging/iio/magnetometer/hmc5843_spi.c | 100 + kernel/drivers/staging/iio/meter/Kconfig | 78 + kernel/drivers/staging/iio/meter/Makefile | 15 + kernel/drivers/staging/iio/meter/ade7753.c | 547 +++++ kernel/drivers/staging/iio/meter/ade7753.h | 72 + kernel/drivers/staging/iio/meter/ade7754.c | 588 ++++++ kernel/drivers/staging/iio/meter/ade7754.h | 90 + kernel/drivers/staging/iio/meter/ade7758.h | 181 ++ kernel/drivers/staging/iio/meter/ade7758_core.c | 917 ++++++++ kernel/drivers/staging/iio/meter/ade7758_ring.c | 180 ++ kernel/drivers/staging/iio/meter/ade7758_trigger.c | 109 + kernel/drivers/staging/iio/meter/ade7759.c | 503 +++++ kernel/drivers/staging/iio/meter/ade7759.h | 53 + kernel/drivers/staging/iio/meter/ade7854-i2c.c | 256 +++ kernel/drivers/staging/iio/meter/ade7854-spi.c | 327 +++ kernel/drivers/staging/iio/meter/ade7854.c | 578 ++++++ kernel/drivers/staging/iio/meter/ade7854.h | 174 ++ kernel/drivers/staging/iio/meter/meter.h | 400 ++++ kernel/drivers/staging/iio/resolver/Kconfig | 39 + kernel/drivers/staging/iio/resolver/Makefile | 7 + kernel/drivers/staging/iio/resolver/ad2s1200.c | 167 ++ kernel/drivers/staging/iio/resolver/ad2s1210.c | 748 +++++++ kernel/drivers/staging/iio/resolver/ad2s1210.h | 20 + kernel/drivers/staging/iio/resolver/ad2s90.c | 120 ++ kernel/drivers/staging/iio/ring_hw.h | 27 + kernel/drivers/staging/iio/trigger/Kconfig | 29 + kernel/drivers/staging/iio/trigger/Makefile | 6 + .../staging/iio/trigger/iio-trig-bfin-timer.c | 292 +++ .../staging/iio/trigger/iio-trig-bfin-timer.h | 24 + .../staging/iio/trigger/iio-trig-periodic-rtc.c | 215 ++ 128 files changed, 33380 insertions(+) create mode 100644 kernel/drivers/staging/iio/Documentation/dac/max517 create mode 100644 kernel/drivers/staging/iio/Documentation/device.txt create mode 100644 kernel/drivers/staging/iio/Documentation/inkernel.txt create mode 100644 kernel/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583 create mode 100644 kernel/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2x7x create mode 100644 kernel/drivers/staging/iio/Documentation/overview.txt create mode 100644 kernel/drivers/staging/iio/Documentation/ring.txt create mode 100644 kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-ad7192 create mode 100644 kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a create mode 100644 kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-dds create mode 100644 kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-impedance-analyzer-ad5933 create mode 100644 kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-light create mode 100644 kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-light-tsl2583 create mode 100644 kernel/drivers/staging/iio/Documentation/trigger.txt create mode 100644 kernel/drivers/staging/iio/Kconfig create mode 100644 kernel/drivers/staging/iio/Makefile create mode 100644 kernel/drivers/staging/iio/TODO create mode 100644 kernel/drivers/staging/iio/accel/Kconfig create mode 100644 kernel/drivers/staging/iio/accel/Makefile create mode 100644 kernel/drivers/staging/iio/accel/adis16201.h create mode 100644 kernel/drivers/staging/iio/accel/adis16201_core.c create mode 100644 kernel/drivers/staging/iio/accel/adis16203.h create mode 100644 kernel/drivers/staging/iio/accel/adis16203_core.c create mode 100644 kernel/drivers/staging/iio/accel/adis16204.h create mode 100644 kernel/drivers/staging/iio/accel/adis16204_core.c create mode 100644 kernel/drivers/staging/iio/accel/adis16209.h create mode 100644 kernel/drivers/staging/iio/accel/adis16209_core.c create mode 100644 kernel/drivers/staging/iio/accel/adis16220.h create mode 100644 kernel/drivers/staging/iio/accel/adis16220_core.c create mode 100644 kernel/drivers/staging/iio/accel/adis16240.h create mode 100644 kernel/drivers/staging/iio/accel/adis16240_core.c create mode 100644 kernel/drivers/staging/iio/accel/lis3l02dq.h create mode 100644 kernel/drivers/staging/iio/accel/lis3l02dq_core.c create mode 100644 kernel/drivers/staging/iio/accel/lis3l02dq_ring.c create mode 100644 kernel/drivers/staging/iio/accel/sca3000.h create mode 100644 kernel/drivers/staging/iio/accel/sca3000_core.c create mode 100644 kernel/drivers/staging/iio/accel/sca3000_ring.c create mode 100644 kernel/drivers/staging/iio/adc/Kconfig create mode 100644 kernel/drivers/staging/iio/adc/Makefile create mode 100644 kernel/drivers/staging/iio/adc/ad7192.c create mode 100644 kernel/drivers/staging/iio/adc/ad7192.h create mode 100644 kernel/drivers/staging/iio/adc/ad7280a.c create mode 100644 kernel/drivers/staging/iio/adc/ad7280a.h create mode 100644 kernel/drivers/staging/iio/adc/ad7606.h create mode 100644 kernel/drivers/staging/iio/adc/ad7606_core.c create mode 100644 kernel/drivers/staging/iio/adc/ad7606_par.c create mode 100644 kernel/drivers/staging/iio/adc/ad7606_ring.c create mode 100644 kernel/drivers/staging/iio/adc/ad7606_spi.c create mode 100644 kernel/drivers/staging/iio/adc/ad7780.c create mode 100644 kernel/drivers/staging/iio/adc/ad7780.h create mode 100644 kernel/drivers/staging/iio/adc/ad7816.c create mode 100644 kernel/drivers/staging/iio/adc/lpc32xx_adc.c create mode 100644 kernel/drivers/staging/iio/adc/mxs-lradc.c create mode 100644 kernel/drivers/staging/iio/adc/spear_adc.c create mode 100644 kernel/drivers/staging/iio/addac/Kconfig create mode 100644 kernel/drivers/staging/iio/addac/Makefile create mode 100644 kernel/drivers/staging/iio/addac/adt7316-i2c.c create mode 100644 kernel/drivers/staging/iio/addac/adt7316-spi.c create mode 100644 kernel/drivers/staging/iio/addac/adt7316.c create mode 100644 kernel/drivers/staging/iio/addac/adt7316.h create mode 100644 kernel/drivers/staging/iio/cdc/Kconfig create mode 100644 kernel/drivers/staging/iio/cdc/Makefile create mode 100644 kernel/drivers/staging/iio/cdc/ad7150.c create mode 100644 kernel/drivers/staging/iio/cdc/ad7152.c create mode 100644 kernel/drivers/staging/iio/cdc/ad7746.c create mode 100644 kernel/drivers/staging/iio/cdc/ad7746.h create mode 100644 kernel/drivers/staging/iio/frequency/Kconfig create mode 100644 kernel/drivers/staging/iio/frequency/Makefile create mode 100644 kernel/drivers/staging/iio/frequency/ad9832.c create mode 100644 kernel/drivers/staging/iio/frequency/ad9832.h create mode 100644 kernel/drivers/staging/iio/frequency/ad9834.c create mode 100644 kernel/drivers/staging/iio/frequency/ad9834.h create mode 100644 kernel/drivers/staging/iio/frequency/dds.h create mode 100644 kernel/drivers/staging/iio/gyro/Kconfig create mode 100644 kernel/drivers/staging/iio/gyro/Makefile create mode 100644 kernel/drivers/staging/iio/gyro/adis16060_core.c create mode 100644 kernel/drivers/staging/iio/iio_dummy_evgen.c create mode 100644 kernel/drivers/staging/iio/iio_dummy_evgen.h create mode 100644 kernel/drivers/staging/iio/iio_simple_dummy.c create mode 100644 kernel/drivers/staging/iio/iio_simple_dummy.h create mode 100644 kernel/drivers/staging/iio/iio_simple_dummy_buffer.c create mode 100644 kernel/drivers/staging/iio/iio_simple_dummy_events.c create mode 100644 kernel/drivers/staging/iio/impedance-analyzer/Kconfig create mode 100644 kernel/drivers/staging/iio/impedance-analyzer/Makefile create mode 100644 kernel/drivers/staging/iio/impedance-analyzer/ad5933.c create mode 100644 kernel/drivers/staging/iio/impedance-analyzer/ad5933.h create mode 100644 kernel/drivers/staging/iio/light/Kconfig create mode 100644 kernel/drivers/staging/iio/light/Makefile create mode 100644 kernel/drivers/staging/iio/light/isl29018.c create mode 100644 kernel/drivers/staging/iio/light/isl29028.c create mode 100644 kernel/drivers/staging/iio/light/tsl2583.c create mode 100644 kernel/drivers/staging/iio/light/tsl2x7x.h create mode 100644 kernel/drivers/staging/iio/light/tsl2x7x_core.c create mode 100644 kernel/drivers/staging/iio/magnetometer/Kconfig create mode 100644 kernel/drivers/staging/iio/magnetometer/Makefile create mode 100644 kernel/drivers/staging/iio/magnetometer/hmc5843.h create mode 100644 kernel/drivers/staging/iio/magnetometer/hmc5843_core.c create mode 100644 kernel/drivers/staging/iio/magnetometer/hmc5843_i2c.c create mode 100644 kernel/drivers/staging/iio/magnetometer/hmc5843_spi.c create mode 100644 kernel/drivers/staging/iio/meter/Kconfig create mode 100644 kernel/drivers/staging/iio/meter/Makefile create mode 100644 kernel/drivers/staging/iio/meter/ade7753.c create mode 100644 kernel/drivers/staging/iio/meter/ade7753.h create mode 100644 kernel/drivers/staging/iio/meter/ade7754.c create mode 100644 kernel/drivers/staging/iio/meter/ade7754.h create mode 100644 kernel/drivers/staging/iio/meter/ade7758.h create mode 100644 kernel/drivers/staging/iio/meter/ade7758_core.c create mode 100644 kernel/drivers/staging/iio/meter/ade7758_ring.c create mode 100644 kernel/drivers/staging/iio/meter/ade7758_trigger.c create mode 100644 kernel/drivers/staging/iio/meter/ade7759.c create mode 100644 kernel/drivers/staging/iio/meter/ade7759.h create mode 100644 kernel/drivers/staging/iio/meter/ade7854-i2c.c create mode 100644 kernel/drivers/staging/iio/meter/ade7854-spi.c create mode 100644 kernel/drivers/staging/iio/meter/ade7854.c create mode 100644 kernel/drivers/staging/iio/meter/ade7854.h create mode 100644 kernel/drivers/staging/iio/meter/meter.h create mode 100644 kernel/drivers/staging/iio/resolver/Kconfig create mode 100644 kernel/drivers/staging/iio/resolver/Makefile create mode 100644 kernel/drivers/staging/iio/resolver/ad2s1200.c create mode 100644 kernel/drivers/staging/iio/resolver/ad2s1210.c create mode 100644 kernel/drivers/staging/iio/resolver/ad2s1210.h create mode 100644 kernel/drivers/staging/iio/resolver/ad2s90.c create mode 100644 kernel/drivers/staging/iio/ring_hw.h create mode 100644 kernel/drivers/staging/iio/trigger/Kconfig create mode 100644 kernel/drivers/staging/iio/trigger/Makefile create mode 100644 kernel/drivers/staging/iio/trigger/iio-trig-bfin-timer.c create mode 100644 kernel/drivers/staging/iio/trigger/iio-trig-bfin-timer.h create mode 100644 kernel/drivers/staging/iio/trigger/iio-trig-periodic-rtc.c (limited to 'kernel/drivers/staging/iio') diff --git a/kernel/drivers/staging/iio/Documentation/dac/max517 b/kernel/drivers/staging/iio/Documentation/dac/max517 new file mode 100644 index 000000000..e60ec2f91 --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/dac/max517 @@ -0,0 +1,41 @@ +Kernel driver max517 +==================== + +Supported chips: + * Maxim MAX517, MAX518, MAX519 + Prefix: 'max517' + Datasheet: Publicly available at the Maxim website + http://www.maxim-ic.com/ + +Author: + Roland Stigge + +Description +----------- + +The Maxim MAX517/518/519 is an 8-bit DAC on the I2C bus. The following table +shows the different feature sets of the variants MAX517, MAX518 and MAX519: + +Feature MAX517 MAX518 MAX519 +-------------------------------------------------------------------------- +One output channel X +Two output channels X X +Simultaneous output updates X X +Supply voltage as reference X +Separate reference input X +Reference input for each DAC X + +Via the iio sysfs interface, there are three attributes available: out1_raw, +out2_raw and out12_raw. With out1_raw and out2_raw, the current output values +(0..255) of the DACs can be written to the device. out12_raw can be used to set +both output channel values simultaneously. + +With MAX517, only out1_raw is available. + +Via out1_scale (and where appropriate, out2_scale), the current scaling factor +in mV can be read. + +When the operating system goes to a power down state, the Power Down function +of the chip is activated, reducing the supply current to 4uA. + +On power-up, the device is in 0V-output state. diff --git a/kernel/drivers/staging/iio/Documentation/device.txt b/kernel/drivers/staging/iio/Documentation/device.txt new file mode 100644 index 000000000..8be32e5a0 --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/device.txt @@ -0,0 +1,79 @@ +IIO Device drivers + +This is not intended to provide a comprehensive guide to writing an +IIO device driver. For further information see the drivers within the +subsystem. + +The crucial structure for device drivers in iio is iio_dev. + +First allocate one using: + +struct iio_dev *indio_dev = iio_device_alloc(sizeof(struct chip_state)); +where chip_state is a structure of local state data for this instance of +the chip. + +That data can be accessed using iio_priv(struct iio_dev *). + +Then fill in the following: + +- indio_dev->dev.parent + Struct device associated with the underlying hardware. +- indio_dev->name + Name of the device being driven - made available as the name + attribute in sysfs. + +- indio_dev->info + pointer to a structure with elements that tend to be fixed for + large sets of different parts supported by a given driver. + This contains: + * info->driver_module: + Set to THIS_MODULE. Used to ensure correct ownership + of various resources allocate by the core. + * info->event_attrs: + Attributes used to enable / disable hardware events. + * info->attrs: + General device attributes. Typically used for the weird + and the wonderful bits not covered by the channel specification. + * info->read_raw: + Raw data reading function. Used for both raw channel access + and for associate parameters such as offsets and scales. + * info->write_raw: + Raw value writing function. Used for writable device values such + as DAC values and calibbias. + * info->read_event_config: + Typically only set if there are some interrupt lines. This + is used to read if an on sensor event detector is enabled. + * info->write_event_config: + Enable / disable an on sensor event detector. + * info->read_event_value: + Read value associated with on sensor event detectors. Note that + the meaning of the returned value is dependent on the event + type. + * info->write_event_value: + Write the value associated with on sensor event detectors. E.g. + a threshold above which an interrupt occurs. Note that the + meaning of the value to be set is event type dependant. + +- indio_dev->modes: + Specify whether direct access and / or ring buffer access is supported. +- indio_dev->buffer: + An optional associated buffer. +- indio_dev->pollfunc: + Poll function related elements. This controls what occurs when a trigger + to which this device is attached sends an event. +- indio_dev->channels: + Specification of device channels. Most attributes etc. are built + from this spec. +- indio_dev->num_channels: + How many channels are there? + +Once these are set up, a call to iio_device_register(indio_dev) +will register the device with the iio core. + +Worth noting here is that, if a ring buffer is to be used, it can be +allocated prior to registering the device with the iio-core, but must +be registered afterwards (otherwise the whole parentage of devices +gets confused) + +On remove, iio_device_unregister(indio_dev) will remove the device from +the core, and iio_device_free(indio_dev) will clean up. diff --git a/kernel/drivers/staging/iio/Documentation/inkernel.txt b/kernel/drivers/staging/iio/Documentation/inkernel.txt new file mode 100644 index 000000000..ab528409b --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/inkernel.txt @@ -0,0 +1,58 @@ +Industrial I/O Subsystem in kernel consumers. + +The IIO subsystem can act as a layer under other elements of the kernel +providing a means of obtaining ADC type readings or of driving DAC type +signals. The functionality supported will grow as use cases arise. + +Describing the channel mapping (iio/machine.h) + +Channel associations are described using: + +struct iio_map { + const char *adc_channel_label; + const char *consumer_dev_name; + const char *consumer_channel; +}; + +adc_channel_label identifies the channel on the IIO device by being +matched against the datasheet_name field of the iio_chan_spec. + +consumer_dev_name allows identification of the consumer device. +This are then used to find the channel mapping from the consumer device (see +below). + +Finally consumer_channel is a string identifying the channel to the consumer. +(Perhaps 'battery_voltage' or similar). + +An array of these structures is then passed to the IIO driver. + +Supporting in kernel interfaces in the driver (driver.h) + +The driver must provide datasheet_name values for its channels and +must pass the iio_map structures and a pointer to its own iio_dev structure + on to the core via a call to iio_map_array_register. On removal, +iio_map_array_unregister reverses this process. + +The result of this is that the IIO core now has all the information needed +to associate a given channel with the consumer requesting it. + +Acting as an IIO consumer (consumer.h) + +The consumer first has to obtain an iio_channel structure from the core +by calling iio_channel_get(). The correct channel is identified by: + +* matching dev or dev_name against consumer_dev and consumer_dev_name +* matching consumer_channel against consumer_channel in the map + +There are then a number of functions that can be used to get information +about this channel such as it's current reading. + +e.g. +iio_read_channel_raw() - get a reading +iio_get_channel_type() - get the type of channel + +There is also provision for retrieving all of the channels associated +with a given consumer. This is useful for generic drivers such as +iio_hwmon where the number and naming of channels is not known by the +consumer driver. To do this, use iio_channel_get_all. + diff --git a/kernel/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583 b/kernel/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583 new file mode 100644 index 000000000..470f7ad9c --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2583 @@ -0,0 +1,6 @@ +What: /sys/bus/iio/devices/device[n]/in_illuminance0_calibrate +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property causes an internal calibration of the als gain trim + value which is later used in calculating illuminance in lux. diff --git a/kernel/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2x7x b/kernel/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2x7x new file mode 100644 index 000000000..b2798b258 --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/light/sysfs-bus-iio-light-tsl2x7x @@ -0,0 +1,13 @@ +What: /sys/bus/iio/devices/device[n]/in_illuminance0_calibrate +KernelVersion: 3.3-rc1 +Contact: linux-iio@vger.kernel.org +Description: + Causes an internal calibration of the als gain trim + value which is later used in calculating illuminance in lux. + +What: /sys/bus/iio/devices/device[n]/in_proximity0_calibrate +KernelVersion: 3.3-rc1 +Contact: linux-iio@vger.kernel.org +Description: + Causes a recalculation and adjustment to the + proximity_thresh_rising_value. diff --git a/kernel/drivers/staging/iio/Documentation/overview.txt b/kernel/drivers/staging/iio/Documentation/overview.txt new file mode 100644 index 000000000..43f92b06b --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/overview.txt @@ -0,0 +1,57 @@ +Overview of IIO + +The Industrial I/O subsystem is intended to provide support for devices +that in some sense are analog to digital converters (ADCs). As many +actual devices combine some ADCs with digital to analog converters +(DACs) that functionality is also supported. + +The aim is to fill the gap between the somewhat similar hwmon and +input subsystems. Hwmon is very much directed at low sample rate +sensors used in applications such as fan speed control and temperature +measurement. Input is, as its name suggests focused on input +devices. In some cases there is considerable overlap between these and +IIO. + +A typical device falling into this category would be connected via SPI +or I2C. + +Functionality of IIO + +* Basic device registration and handling. This is very similar to +hwmon with simple polled access to device channels via sysfs. + +* Event chrdevs. These are similar to input in that they provide a +route to user space for hardware triggered events. Such events include +threshold detectors, free-fall detectors and more complex action +detection. The events themselves are currently very simple with +merely an event code and a timestamp. Any data associated with the +event must be accessed via polling. + +Note: A given device may have one or more event channel. These events are +turned on or off (if possible) via sysfs interfaces. + +* Hardware buffer support. Some recent sensors have included +fifo / ring buffers on the sensor chip. These greatly reduce the load +on the host CPU by buffering relatively large numbers of data samples +based on an internal sampling clock. Examples include VTI SCA3000 +series and Analog Device ADXL345 accelerometers. Each buffer supports +polling to establish when data is available. + +* Trigger and software buffer support. In many data analysis +applications it it useful to be able to capture data based on some +external signal (trigger). These triggers might be a data ready +signal, a gpio line connected to some external system or an on +processor periodic interrupt. A single trigger may initialize data +capture or reading from a number of sensors. These triggers are +used in IIO to fill software buffers acting in a very similar +fashion to the hardware buffers described above. + +Other documentation: + +device.txt - elements of a typical device driver. + +trigger.txt - elements of a typical trigger driver. + +ring.txt - additional elements required for buffer support. + +sysfs-bus-iio - abi documentation file. diff --git a/kernel/drivers/staging/iio/Documentation/ring.txt b/kernel/drivers/staging/iio/Documentation/ring.txt new file mode 100644 index 000000000..18718fcaf --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/ring.txt @@ -0,0 +1,47 @@ +Buffer support within IIO + +This document is intended as a general overview of the functionality +a buffer may supply and how it is specified within IIO. For more +specific information on a given buffer implementation, see the +comments in the source code. Note that some drivers allow buffer +implementation to be selected at compile time via Kconfig options. + +A given buffer implementation typically embeds a struct +iio_ring_buffer and it is a pointer to this that is provided to the +IIO core. Access to the embedding structure is typically done via +container_of functions. + +struct iio_ring_buffer contains a struct iio_ring_setup_ops *setup_ops +which in turn contains the 4 function pointers +(preenable, postenable, predisable and postdisable). +These are used to perform device specific steps on either side +of the core changing its current mode to indicate that the buffer +is enabled or disabled (along with enabling triggering etc. as appropriate). + +Also in struct iio_ring_buffer is a struct iio_ring_access_funcs. +The function pointers within here are used to allow the core to handle +as much buffer functionality as possible. Note almost all of these +are optional. + +store_to + If possible, push data to the buffer. + +read_last + If possible, get the most recent scan from the buffer (without removal). + This provides polling like functionality whilst the ring buffering is in + use without a separate read from the device. + +rip_first_n + The primary buffer reading function. Note that it may well not return + as much data as requested. + +request_update + If parameters have changed that require reinitialization or configuration of + the buffer this will trigger it. + +set_bytes_per_datum + Set the number of bytes for a complete scan. (All samples + timestamp) + +set_length + Set the number of complete scans that may be held by the buffer. + diff --git a/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-ad7192 b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-ad7192 new file mode 100644 index 000000000..1c35c507c --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-ad7192 @@ -0,0 +1,20 @@ +What: /sys/.../iio:deviceX/ac_excitation_en +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + This attribute, if available, is used to enable the AC + excitation mode found on some converters. In ac excitation mode, + the polarity of the excitation voltage is reversed on + alternate cycles, to eliminate DC errors. + +What: /sys/.../iio:deviceX/bridge_switch_en +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + This attribute, if available, is used to close or open the + bridge power down switch found on some converters. + In bridge applications, such as strain gauges and load cells, + the bridge itself consumes the majority of the current in the + system. To minimize the current consumption of the system, + the bridge can be disconnected (when it is not being used + using the bridge_switch_en attribute. diff --git a/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a new file mode 100644 index 000000000..863d38567 --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a @@ -0,0 +1,21 @@ +What: /sys/bus/iio/devices/deviceX/inY-inZ_balance_switch_en +KernelVersion: 3.0.0 +Contact: linux-iio@vger.kernel.org +Description: + Writing 1 enables the cell balance output switch corresponding + to input Y. Writing 0 disables it. If the inY-inZ_balance_timer + is set to a none zero value, the corresponding switch will + enable for the programmed amount of time, before it + automatically disables. + +What: /sys/bus/iio/devices/deviceX/inY-inZ_balance_timer +KernelVersion: 3.0.0 +Contact: linux-iio@vger.kernel.org +Description: + The inY-inZ_balance_timer file allows the user to program + individual times for each cell balance output. The AD7280A + allows the user to set the timer to a value from 0 minutes to + 36.9 minutes. The resolution of the timer is 71.5 sec. + The value written is the on-time in milliseconds. When the + timer value is set 0, the timer is disabled. The cell balance + outputs are controlled only by inY-inZ_balance_switch_en. diff --git a/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-dds b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-dds new file mode 100644 index 000000000..ee8c509c6 --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-dds @@ -0,0 +1,96 @@ + +What: /sys/bus/iio/devices/.../out_altvoltageX_frequencyY +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Stores frequency into tuning word Y. + There will be more than one out_altvoltageX_frequencyY file, + which allows for pin controlled FSK Frequency Shift Keying + (out_altvoltageX_pincontrol_frequency_en is active) or the user + can control the desired active tuning word by writing Y to the + out_altvoltageX_frequencysymbol file. + +What: /sys/bus/iio/devices/.../out_altvoltageX_frequencyY_scale +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Scale to be applied to out_altvoltageX_frequencyY in order to + obtain the desired value in Hz. If shared across all frequency + registers Y is not present. It is also possible X is not present + if shared across all channels. + +What: /sys/bus/iio/devices/.../out_altvoltageX_frequencysymbol +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Specifies the active output frequency tuning word. The value + corresponds to the Y in out_altvoltageX_frequencyY. + To exit this mode the user can write + out_altvoltageX_pincontrol_frequency_en or + out_altvoltageX_out_enable file. + +What: /sys/bus/iio/devices/.../out_altvoltageX_phaseY +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Stores phase into Y. + There will be more than one out_altvoltageX_phaseY file, which + allows for pin controlled PSK Phase Shift Keying + (out_altvoltageX_pincontrol_phase_en is active) or the user can + control the desired phase Y which is added to the phase + accumulator output by writing Y to the phase_en file. + +What: /sys/bus/iio/devices/.../out_altvoltageX_phaseY_scale +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Scale to be applied to out_altvoltageX_phaseY in order to obtain + the desired value in rad. If shared across all phase registers + Y is not present. It is also possible X is not present if + shared across all channels. + +What: /sys/bus/iio/devices/.../out_altvoltageX_phasesymbol +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Specifies the active phase Y which is added to the phase + accumulator output. The value corresponds to the Y in + out_altvoltageX_phaseY. To exit this mode the user can write + out_altvoltageX_pincontrol_phase_en or disable file. + +What: /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_en +What: /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_frequency_en +What: /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_phase_en +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + out_altvoltageX_pincontrol_en: Both, the active frequency and + phase is controlled by the respective phase and frequency + control inputs. In case the device in features independent + controls, then there are dedicated files + (out_altvoltageX_pincontrol_frequency_en, + out_altvoltageX_pincontrol_phase_en). + +What: /sys/bus/iio/devices/.../out_altvoltageX_out_enable +What: /sys/bus/iio/devices/.../out_altvoltageX_outY_enable +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + out_altvoltageX_outY_enable controls signal generation on + output Y of channel X. Y may be suppressed if all channels are + controlled together. + +What: /sys/bus/iio/devices/.../out_altvoltageX_outY_wavetype +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Specifies the output waveform. + (sine, triangle, ramp, square, ...) + For a list of available output waveform options read + available_output_modes. + +What: /sys/bus/iio/devices/.../out_altvoltageX_outY_wavetype_available +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Lists all available output waveform options. diff --git a/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-impedance-analyzer-ad5933 b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-impedance-analyzer-ad5933 new file mode 100644 index 000000000..79c7e88c6 --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-impedance-analyzer-ad5933 @@ -0,0 +1,30 @@ +What: /sys/bus/iio/devices/iio:deviceX/outY_freq_start +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + Frequency sweep start frequency in Hz. + +What: /sys/bus/iio/devices/iio:deviceX/outY_freq_increment +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + Frequency increment in Hz (step size) between consecutive + frequency points along the sweep. + +What: /sys/bus/iio/devices/iio:deviceX/outY_freq_points +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + Number of frequency points (steps) in the frequency sweep. + This value, in conjunction with the outY_freq_start and the + outY_freq_increment, determines the frequency sweep range + for the sweep operation. + +What: /sys/bus/iio/devices/iio:deviceX/outY_settling_cycles +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + Number of output excitation cycles (settling time cycles) + that are allowed to pass through the unknown impedance, + after each frequency increment, and before the ADC is triggered + to perform a conversion sequence of the response signal. diff --git a/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-light b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-light new file mode 100644 index 000000000..17e5c9c51 --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-light @@ -0,0 +1,107 @@ + +What: /sys/bus/iio/devices/device[n]/range +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent ADC Full Scale Range used for some ambient + light sensors in calculating lux. + +What: /sys/bus/iio/devices/device[n]/range_available +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent supported vales for ADC Full Scale Range. + +What: /sys/bus/iio/devices/device[n]/adc_resolution +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent ADC resolution of the ambient light sensor + used in calculating the lux. + +What: /sys/bus/iio/devices/device[n]/adc_resolution_available +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent list of possible values supported for the + adc_resolution of the given sensor. + +What: /sys/bus/iio/devices/device[n]/in_illuminance0[_input|_raw] +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + This should return the calculated lux from the light sensor. If + it comes back in SI units, it should also include _input else it + should include _raw to signify it is not in SI units. + +What: /sys/.../device[n]/proximity_on_chip_ambient_infrared_suppression +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent mode for an ALS device to calculate the value + in proximity mode. When this is enabled, then the device should + use a infrared sensor reading to remove infrared noise from the + proximity reading. If this is not enabled, the driver can still + do this calculation manually by reading the infrared sensor + value and doing the negation in sw. + +What: /sys/bus/iio/devices/device[n]/in_proximity[_input|_raw] +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property is supported by proximity sensors and should be + used to return the value of a reading by the sensor. If this + value is returned in SI units, it should also include _input + but if it is not, then it should include _raw. + +What: /sys/bus/iio/devices/device[n]/intensity_infrared[_input|_raw] +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property is supported by sensors that have an infrared + sensing mode. This value should be the output from a reading + and if expressed in SI units, should include _input. If this + value is not in SI units, then it should include _raw. + +What: /sys/bus/iio/devices/device[n]/in_illuminance0_target +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property gets/sets the last known external + lux measurement used in/for calibration. + +What: /sys/bus/iio/devices/device[n]/in_illuminance0_integration_time +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property gets/sets the sensors ADC analog integration time. + +What: /sys/bus/iio/devices/device[n]/in_illuminance0_lux_table +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property gets/sets the table of coefficients + used in calculating illuminance in lux. + +What: /sys/bus/iio/devices/device[n]/in_intensity_clear[_input|_raw] +What: /sys/bus/iio/devices/device[n]/in_intensity_red[_input|_raw] +What: /sys/bus/iio/devices/device[n]/in_intensity_green[_input|_raw] +What: /sys/bus/iio/devices/device[n]/in_intensity_blue[_input|_raw] +KernelVersion: 3.6.0 +Contact: linux-iio@vger.kernel.org +Description: + This property is supported by sensors that have a RGBC + sensing mode. This value should be the output from a reading + and if expressed in SI units, should include _input. If this + value is not in SI units (irradiance, uW/mm^2), then it should + include _raw. + +What: /sys/bus/iio/devices/device[n]/in_cct0[_input|_raw] +KernelVersion: 3.6.0 +Contact: linux-iio@vger.kernel.org +Description: + This should return the correlated color temperature from the + light sensor. If it comes back in SI units, it should also + include _input else it should include _raw to signify it is not + in SI units. + diff --git a/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-light-tsl2583 b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-light-tsl2583 new file mode 100644 index 000000000..660781df4 --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/sysfs-bus-iio-light-tsl2583 @@ -0,0 +1,20 @@ +What: /sys/bus/iio/devices/device[n]/lux_table +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property gets/sets the table of coefficients + used in calculating illuminance in lux. + +What: /sys/bus/iio/devices/device[n]/illuminance0_calibrate +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property causes an internal calibration of the als gain trim + value which is later used in calculating illuminance in lux. + +What: /sys/bus/iio/devices/device[n]/illuminance0_input_target +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property is the known externally illuminance (in lux). + It is used in the process of calibrating the device accuracy. diff --git a/kernel/drivers/staging/iio/Documentation/trigger.txt b/kernel/drivers/staging/iio/Documentation/trigger.txt new file mode 100644 index 000000000..7c0e505e4 --- /dev/null +++ b/kernel/drivers/staging/iio/Documentation/trigger.txt @@ -0,0 +1,35 @@ +IIO trigger drivers. + +Many triggers are provided by hardware that will also be registered as +an IIO device. Whilst this can create device specific complexities +such triggers are registered with the core in the same way as +stand-alone triggers. + +struct iio_trig *trig = iio_trigger_alloc("", ...); + +allocates a trigger structure. The key elements to then fill in within +a driver are: + +trig->owner + Typically set to THIS_MODULE. Used to ensure correct + ownership of core allocated resources. + +trig->set_trigger_state: + Function that enables / disables the underlying source of the trigger. + +There is also a +trig->alloc_list which is useful for drivers that allocate multiple +triggers to keep track of what they have created. + +When these have been set call: + +iio_trigger_register(trig); + +to register the trigger with the core, making it available to trigger +consumers. + +Trigger Consumers + +Currently triggers are only used for the filling of software +buffers and as such any device supporting INDIO_BUFFER_TRIGGERED has the +consumer interface automatically created. diff --git a/kernel/drivers/staging/iio/Kconfig b/kernel/drivers/staging/iio/Kconfig new file mode 100644 index 000000000..6d5b38d69 --- /dev/null +++ b/kernel/drivers/staging/iio/Kconfig @@ -0,0 +1,48 @@ +# +# Industrial I/O subsystem configuration +# +menu "IIO staging drivers" + depends on IIO + +source "drivers/staging/iio/accel/Kconfig" +source "drivers/staging/iio/adc/Kconfig" +source "drivers/staging/iio/addac/Kconfig" +source "drivers/staging/iio/cdc/Kconfig" +source "drivers/staging/iio/frequency/Kconfig" +source "drivers/staging/iio/gyro/Kconfig" +source "drivers/staging/iio/impedance-analyzer/Kconfig" +source "drivers/staging/iio/light/Kconfig" +source "drivers/staging/iio/magnetometer/Kconfig" +source "drivers/staging/iio/meter/Kconfig" +source "drivers/staging/iio/resolver/Kconfig" +source "drivers/staging/iio/trigger/Kconfig" + +config IIO_DUMMY_EVGEN + tristate + +config IIO_SIMPLE_DUMMY + tristate "An example driver with no hardware requirements" + help + Driver intended mainly as documentation for how to write + a driver. May also be useful for testing userspace code + without hardware. + +if IIO_SIMPLE_DUMMY + +config IIO_SIMPLE_DUMMY_EVENTS + bool "Event generation support" + select IIO_DUMMY_EVGEN + help + Add some dummy events to the simple dummy driver. + +config IIO_SIMPLE_DUMMY_BUFFER + bool "Buffered capture support" + select IIO_BUFFER + select IIO_TRIGGER + select IIO_KFIFO_BUF + help + Add buffered data capture to the simple dummy driver. + +endif # IIO_SIMPLE_DUMMY + +endmenu diff --git a/kernel/drivers/staging/iio/Makefile b/kernel/drivers/staging/iio/Makefile new file mode 100644 index 000000000..d87106135 --- /dev/null +++ b/kernel/drivers/staging/iio/Makefile @@ -0,0 +1,23 @@ +# +# Makefile for the industrial I/O core. +# + +obj-$(CONFIG_IIO_SIMPLE_DUMMY) += iio_dummy.o +iio_dummy-y := iio_simple_dummy.o +iio_dummy-$(CONFIG_IIO_SIMPLE_DUMMY_EVENTS) += iio_simple_dummy_events.o +iio_dummy-$(CONFIG_IIO_SIMPLE_DUMMY_BUFFER) += iio_simple_dummy_buffer.o + +obj-$(CONFIG_IIO_DUMMY_EVGEN) += iio_dummy_evgen.o + +obj-y += accel/ +obj-y += adc/ +obj-y += addac/ +obj-y += cdc/ +obj-y += frequency/ +obj-y += gyro/ +obj-y += impedance-analyzer/ +obj-y += light/ +obj-y += magnetometer/ +obj-y += meter/ +obj-y += resolver/ +obj-y += trigger/ diff --git a/kernel/drivers/staging/iio/TODO b/kernel/drivers/staging/iio/TODO new file mode 100644 index 000000000..c22a0edd1 --- /dev/null +++ b/kernel/drivers/staging/iio/TODO @@ -0,0 +1,84 @@ +2009 8/18 + +Core: +1) Get reviews +2) Additional testing +3) Ensure all desirable features present by adding more devices. + Major changes not expected except in response to comments + +Max1363 core: +1) Possibly add sysfs exports of constant useful to userspace. +Would be nice +2) Support hardware generated interrupts +3) Expand device set. Lots of other maxim adc's have very + similar interfaces. + +MXS LRADC driver: +This is a classic MFD device as it combines the following subdevices + - touchscreen controller (input subsystem related device) + - general purpose ADC channels + - battery voltage monitor (power subsystem related device) + - die temperature monitor (thermal management) + +At least the battery voltage and die temperature feature is required in-kernel +by a driver of the SoC's battery charging unit to avoid any damage to the +silicon and the battery. + +TSL2561 +Would be nice +1) Open question of userspace vs kernel space balance when +converting to useful light measurements from device ones. +2) Add sysfs elements necessary to allow device agnostic +unit conversion. + +LIS3L02DQ core + +LIS3L02DQ ring + +KXSD9 +Currently minimal driver, would be nice to add: +1) Support for all chip generated interrupts (events), +basically get support up to level of lis3l02dq driver. + +Ring buffer core + +SCA3000 +Would be nice +1) Testing on devices other than sca3000-e05 + +Trigger core support +1) Discussion of approach. Is it general enough? + +Ring Buffer: +1) Discussion of approach. +There are probably better ways of doing this. The +intention is to allow for more than one software ring +buffer implementation as different users will have +different requirements. This one suits mid range +frequencies (100Hz - 4kHz). +2) Lots of testing + +Periodic Timer trigger +1) Move to a more general hardware periodic timer request +subsystem. Current approach is abusing purpose of RTC. +Initial discussions have taken place, but no actual code +is in place as yet. This topic will be reopened on lkml +shortly. I don't really envision this patch being merged +in anything like its current form. + +GPIO trigger +1) Add control over the type of interrupt etc. This will +necessitate a header that is also visible from arch board +files. (avoided at the moment to keep the driver set +contained in staging). + +ADI Drivers: +CC the device-drivers-devel@blackfin.uclinux.org mailing list when +e-mailing the normal IIO list (see below). + +Documentation +1) Lots of cleanup and expansion. +2) Some device require individual docs. + +Contact: Jonathan Cameron . +Mailing list: linux-iio@vger.kernel.org diff --git a/kernel/drivers/staging/iio/accel/Kconfig b/kernel/drivers/staging/iio/accel/Kconfig new file mode 100644 index 000000000..07b7ffa00 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/Kconfig @@ -0,0 +1,101 @@ +# +# Accelerometer drivers +# +menu "Accelerometers" + +config ADIS16201 + tristate "Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say Y here to build support for Analog Devices adis16201 dual-axis + digital inclinometer and accelerometer. + + To compile this driver as a module, say M here: the module will + be called adis16201. + +config ADIS16203 + tristate "Analog Devices ADIS16203 Programmable 360 Degrees Inclinometer" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say Y here to build support for Analog Devices adis16203 Programmable + 360 Degrees Inclinometer. + + To compile this driver as a module, say M here: the module will be + called adis16203. + +config ADIS16204 + tristate "Analog Devices ADIS16204 Programmable High-g Digital Impact Sensor and Recorder" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say Y here to build support for Analog Devices adis16204 Programmable + High-g Digital Impact Sensor and Recorder. + + To compile this driver as a module, say M here: the module will be + called adis16204. + +config ADIS16209 + tristate "Analog Devices ADIS16209 Dual-Axis Digital Inclinometer and Accelerometer" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say Y here to build support for Analog Devices adis16209 dual-axis digital inclinometer + and accelerometer. + + To compile this driver as a module, say M here: the module will be + called adis16209. + +config ADIS16220 + tristate "Analog Devices ADIS16220 Programmable Digital Vibration Sensor" + depends on SPI + select IIO_ADIS_LIB + help + Say Y here to build support for Analog Devices adis16220 programmable + digital vibration sensor. + + To compile this driver as a module, say M here: the module will be + called adis16220. + +config ADIS16240 + tristate "Analog Devices ADIS16240 Programmable Impact Sensor and Recorder" + depends on SPI + select IIO_ADIS_LIB + select IIO_ADIS_LIB_BUFFER if IIO_BUFFER + help + Say Y here to build support for Analog Devices adis16240 programmable + impact Sensor and recorder. + + To compile this driver as a module, say M here: the module will be + called adis16240. + +config LIS3L02DQ + tristate "ST Microelectronics LIS3L02DQ Accelerometer Driver" + depends on SPI + select IIO_TRIGGER if IIO_BUFFER + depends on !IIO_BUFFER || IIO_KFIFO_BUF + depends on GPIOLIB + help + Say Y here to build SPI support for the ST microelectronics + accelerometer. The driver supplies direct access via sysfs files + and an event interface via a character device. + + To compile this driver as a module, say M here: the module will be + called lis3l02dq. + +config SCA3000 + depends on IIO_BUFFER + depends on SPI + tristate "VTI SCA3000 series accelerometers" + help + Say Y here to build support for the VTI SCA3000 series of SPI + accelerometers. These devices use a hardware ring buffer. + + To compile this driver as a module, say M here: the module will be + called sca3000. +endmenu diff --git a/kernel/drivers/staging/iio/accel/Makefile b/kernel/drivers/staging/iio/accel/Makefile new file mode 100644 index 000000000..1ed137f1a --- /dev/null +++ b/kernel/drivers/staging/iio/accel/Makefile @@ -0,0 +1,28 @@ +# +# Makefile for industrial I/O accelerometer drivers +# + +adis16201-y := adis16201_core.o +obj-$(CONFIG_ADIS16201) += adis16201.o + +adis16203-y := adis16203_core.o +obj-$(CONFIG_ADIS16203) += adis16203.o + +adis16204-y := adis16204_core.o +obj-$(CONFIG_ADIS16204) += adis16204.o + +adis16209-y := adis16209_core.o +obj-$(CONFIG_ADIS16209) += adis16209.o + +adis16220-y := adis16220_core.o +obj-$(CONFIG_ADIS16220) += adis16220.o + +adis16240-y := adis16240_core.o +obj-$(CONFIG_ADIS16240) += adis16240.o + +lis3l02dq-y := lis3l02dq_core.o +lis3l02dq-$(CONFIG_IIO_BUFFER) += lis3l02dq_ring.o +obj-$(CONFIG_LIS3L02DQ) += lis3l02dq.o + +sca3000-y := sca3000_core.o sca3000_ring.o +obj-$(CONFIG_SCA3000) += sca3000.o diff --git a/kernel/drivers/staging/iio/accel/adis16201.h b/kernel/drivers/staging/iio/accel/adis16201.h new file mode 100644 index 000000000..e6b8c9af6 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16201.h @@ -0,0 +1,66 @@ +#ifndef SPI_ADIS16201_H_ +#define SPI_ADIS16201_H_ + +#define ADIS16201_STARTUP_DELAY 220 /* ms */ + +#define ADIS16201_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16201_SUPPLY_OUT 0x02 /* Output, power supply */ +#define ADIS16201_XACCL_OUT 0x04 /* Output, x-axis accelerometer */ +#define ADIS16201_YACCL_OUT 0x06 /* Output, y-axis accelerometer */ +#define ADIS16201_AUX_ADC 0x08 /* Output, auxiliary ADC input */ +#define ADIS16201_TEMP_OUT 0x0A /* Output, temperature */ +#define ADIS16201_XINCL_OUT 0x0C /* Output, x-axis inclination */ +#define ADIS16201_YINCL_OUT 0x0E /* Output, y-axis inclination */ +#define ADIS16201_XACCL_OFFS 0x10 /* Calibration, x-axis acceleration offset */ +#define ADIS16201_YACCL_OFFS 0x12 /* Calibration, y-axis acceleration offset */ +#define ADIS16201_XACCL_SCALE 0x14 /* x-axis acceleration scale factor */ +#define ADIS16201_YACCL_SCALE 0x16 /* y-axis acceleration scale factor */ +#define ADIS16201_XINCL_OFFS 0x18 /* Calibration, x-axis inclination offset */ +#define ADIS16201_YINCL_OFFS 0x1A /* Calibration, y-axis inclination offset */ +#define ADIS16201_XINCL_SCALE 0x1C /* x-axis inclination scale factor */ +#define ADIS16201_YINCL_SCALE 0x1E /* y-axis inclination scale factor */ +#define ADIS16201_ALM_MAG1 0x20 /* Alarm 1 amplitude threshold */ +#define ADIS16201_ALM_MAG2 0x22 /* Alarm 2 amplitude threshold */ +#define ADIS16201_ALM_SMPL1 0x24 /* Alarm 1, sample period */ +#define ADIS16201_ALM_SMPL2 0x26 /* Alarm 2, sample period */ +#define ADIS16201_ALM_CTRL 0x28 /* Alarm control */ +#define ADIS16201_AUX_DAC 0x30 /* Auxiliary DAC data */ +#define ADIS16201_GPIO_CTRL 0x32 /* General-purpose digital input/output control */ +#define ADIS16201_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16201_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16201_AVG_CNT 0x38 /* Operation, filter configuration */ +#define ADIS16201_SLP_CNT 0x3A /* Operation, sleep mode control */ +#define ADIS16201_DIAG_STAT 0x3C /* Diagnostics, system status register */ +#define ADIS16201_GLOB_CMD 0x3E /* Operation, system command register */ + +/* MSC_CTRL */ +#define ADIS16201_MSC_CTRL_SELF_TEST_EN BIT(8) /* Self-test enable */ +#define ADIS16201_MSC_CTRL_DATA_RDY_EN BIT(2) /* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16201_MSC_CTRL_ACTIVE_HIGH BIT(1) /* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16201_MSC_CTRL_DATA_RDY_DIO1 BIT(0) /* Data-ready line selection: 1 = DIO1, 0 = DIO0 */ + +/* DIAG_STAT */ +#define ADIS16201_DIAG_STAT_ALARM2 BIT(9) /* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16201_DIAG_STAT_ALARM1 BIT(8) /* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16201_DIAG_STAT_SPI_FAIL_BIT 3 /* SPI communications failure */ +#define ADIS16201_DIAG_STAT_FLASH_UPT_BIT 2 /* Flash update failure */ +#define ADIS16201_DIAG_STAT_POWER_HIGH_BIT 1 /* Power supply above 3.625 V */ +#define ADIS16201_DIAG_STAT_POWER_LOW_BIT 0 /* Power supply below 3.15 V */ + +/* GLOB_CMD */ +#define ADIS16201_GLOB_CMD_SW_RESET BIT(7) +#define ADIS16201_GLOB_CMD_FACTORY_CAL BIT(1) + +#define ADIS16201_ERROR_ACTIVE BIT(14) + +enum adis16201_scan { + ADIS16201_SCAN_ACC_X, + ADIS16201_SCAN_ACC_Y, + ADIS16201_SCAN_INCLI_X, + ADIS16201_SCAN_INCLI_Y, + ADIS16201_SCAN_SUPPLY, + ADIS16201_SCAN_AUX_ADC, + ADIS16201_SCAN_TEMP, +}; + +#endif /* SPI_ADIS16201_H_ */ diff --git a/kernel/drivers/staging/iio/accel/adis16201_core.c b/kernel/drivers/staging/iio/accel/adis16201_core.c new file mode 100644 index 000000000..10db68581 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16201_core.c @@ -0,0 +1,248 @@ +/* + * ADIS16201 Programmable Digital Vibration Sensor driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "adis16201.h" + +static const u8 adis16201_addresses[] = { + [ADIS16201_SCAN_ACC_X] = ADIS16201_XACCL_OFFS, + [ADIS16201_SCAN_ACC_Y] = ADIS16201_YACCL_OFFS, + [ADIS16201_SCAN_INCLI_X] = ADIS16201_XINCL_OFFS, + [ADIS16201_SCAN_INCLI_Y] = ADIS16201_YINCL_OFFS, +}; + +static int adis16201_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int ret; + int bits; + u8 addr; + s16 val16; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, + ADIS16201_ERROR_ACTIVE, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->channel == 0) { + *val = 1; + *val2 = 220000; /* 1.22 mV */ + } else { + *val = 0; + *val2 = 610000; /* 0.610 mV */ + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = -470; /* 0.47 C */ + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = IIO_G_TO_M_S_2(462400); /* 0.4624 mg */ + return IIO_VAL_INT_PLUS_NANO; + case IIO_INCLI: + *val = 0; + *val2 = 100000; /* 0.1 degree */ + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + *val = 25000 / -470 - 1278; /* 25 C = 1278 */ + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + bits = 12; + break; + case IIO_INCLI: + bits = 9; + break; + default: + return -EINVAL; + } + mutex_lock(&indio_dev->mlock); + addr = adis16201_addresses[chan->scan_index]; + ret = adis_read_reg_16(st, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int adis16201_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int bits; + s16 val16; + u8 addr; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + bits = 12; + break; + case IIO_INCLI: + bits = 9; + break; + default: + return -EINVAL; + } + val16 = val & ((1 << bits) - 1); + addr = adis16201_addresses[chan->scan_index]; + return adis_write_reg_16(st, addr, val16); + } + return -EINVAL; +} + +static const struct iio_chan_spec adis16201_channels[] = { + ADIS_SUPPLY_CHAN(ADIS16201_SUPPLY_OUT, ADIS16201_SCAN_SUPPLY, 0, 12), + ADIS_TEMP_CHAN(ADIS16201_TEMP_OUT, ADIS16201_SCAN_TEMP, 0, 12), + ADIS_ACCEL_CHAN(X, ADIS16201_XACCL_OUT, ADIS16201_SCAN_ACC_X, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_ACCEL_CHAN(Y, ADIS16201_YACCL_OUT, ADIS16201_SCAN_ACC_Y, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_AUX_ADC_CHAN(ADIS16201_AUX_ADC, ADIS16201_SCAN_AUX_ADC, 0, 12), + ADIS_INCLI_CHAN(X, ADIS16201_XINCL_OUT, ADIS16201_SCAN_INCLI_X, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_INCLI_CHAN(X, ADIS16201_YINCL_OUT, ADIS16201_SCAN_INCLI_Y, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + IIO_CHAN_SOFT_TIMESTAMP(7) +}; + +static const struct iio_info adis16201_info = { + .read_raw = &adis16201_read_raw, + .write_raw = &adis16201_write_raw, + .update_scan_mode = adis_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static const char * const adis16201_status_error_msgs[] = { + [ADIS16201_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", + [ADIS16201_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", + [ADIS16201_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", + [ADIS16201_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 3.15V", +}; + +static const struct adis_data adis16201_data = { + .read_delay = 20, + .msc_ctrl_reg = ADIS16201_MSC_CTRL, + .glob_cmd_reg = ADIS16201_GLOB_CMD, + .diag_stat_reg = ADIS16201_DIAG_STAT, + + .self_test_mask = ADIS16201_MSC_CTRL_SELF_TEST_EN, + .startup_delay = ADIS16201_STARTUP_DELAY, + + .status_error_msgs = adis16201_status_error_msgs, + .status_error_mask = BIT(ADIS16201_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16201_DIAG_STAT_FLASH_UPT_BIT) | + BIT(ADIS16201_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16201_DIAG_STAT_POWER_LOW_BIT), +}; + +static int adis16201_probe(struct spi_device *spi) +{ + int ret; + struct adis *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16201_info; + + indio_dev->channels = adis16201_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16201_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(st, indio_dev, spi, &adis16201_data); + if (ret) + return ret; + ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = adis_initial_startup(st); + if (ret) + goto error_cleanup_buffer_trigger; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_cleanup_buffer_trigger; + return 0; + +error_cleanup_buffer_trigger: + adis_cleanup_buffer_and_trigger(st, indio_dev); + return ret; +} + +static int adis16201_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis_cleanup_buffer_and_trigger(st, indio_dev); + + return 0; +} + +static struct spi_driver adis16201_driver = { + .driver = { + .name = "adis16201", + .owner = THIS_MODULE, + }, + .probe = adis16201_probe, + .remove = adis16201_remove, +}; +module_spi_driver(adis16201_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16201 Programmable Digital Vibration Sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16201"); diff --git a/kernel/drivers/staging/iio/accel/adis16203.h b/kernel/drivers/staging/iio/accel/adis16203.h new file mode 100644 index 000000000..6426e38bf --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16203.h @@ -0,0 +1,59 @@ +#ifndef SPI_ADIS16203_H_ +#define SPI_ADIS16203_H_ + +#define ADIS16203_STARTUP_DELAY 220 /* ms */ + +#define ADIS16203_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16203_SUPPLY_OUT 0x02 /* Output, power supply */ +#define ADIS16203_AUX_ADC 0x08 /* Output, auxiliary ADC input */ +#define ADIS16203_TEMP_OUT 0x0A /* Output, temperature */ +#define ADIS16203_XINCL_OUT 0x0C /* Output, x-axis inclination */ +#define ADIS16203_YINCL_OUT 0x0E /* Output, y-axis inclination */ +#define ADIS16203_INCL_NULL 0x18 /* Incline null calibration */ +#define ADIS16203_ALM_MAG1 0x20 /* Alarm 1 amplitude threshold */ +#define ADIS16203_ALM_MAG2 0x22 /* Alarm 2 amplitude threshold */ +#define ADIS16203_ALM_SMPL1 0x24 /* Alarm 1, sample period */ +#define ADIS16203_ALM_SMPL2 0x26 /* Alarm 2, sample period */ +#define ADIS16203_ALM_CTRL 0x28 /* Alarm control */ +#define ADIS16203_AUX_DAC 0x30 /* Auxiliary DAC data */ +#define ADIS16203_GPIO_CTRL 0x32 /* General-purpose digital input/output control */ +#define ADIS16203_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16203_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16203_AVG_CNT 0x38 /* Operation, filter configuration */ +#define ADIS16203_SLP_CNT 0x3A /* Operation, sleep mode control */ +#define ADIS16203_DIAG_STAT 0x3C /* Diagnostics, system status register */ +#define ADIS16203_GLOB_CMD 0x3E /* Operation, system command register */ + +/* MSC_CTRL */ +#define ADIS16203_MSC_CTRL_PWRUP_SELF_TEST BIT(10) /* Self-test at power-on: 1 = disabled, 0 = enabled */ +#define ADIS16203_MSC_CTRL_REVERSE_ROT_EN BIT(9) /* Reverses rotation of both inclination outputs */ +#define ADIS16203_MSC_CTRL_SELF_TEST_EN BIT(8) /* Self-test enable */ +#define ADIS16203_MSC_CTRL_DATA_RDY_EN BIT(2) /* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16203_MSC_CTRL_ACTIVE_HIGH BIT(1) /* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16203_MSC_CTRL_DATA_RDY_DIO1 BIT(0) /* Data-ready line selection: 1 = DIO1, 0 = DIO0 */ + +/* DIAG_STAT */ +#define ADIS16203_DIAG_STAT_ALARM2 BIT(9) /* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16203_DIAG_STAT_ALARM1 BIT(8) /* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT 5 /* Self-test diagnostic error flag */ +#define ADIS16203_DIAG_STAT_SPI_FAIL_BIT 3 /* SPI communications failure */ +#define ADIS16203_DIAG_STAT_FLASH_UPT_BIT 2 /* Flash update failure */ +#define ADIS16203_DIAG_STAT_POWER_HIGH_BIT 1 /* Power supply above 3.625 V */ +#define ADIS16203_DIAG_STAT_POWER_LOW_BIT 0 /* Power supply below 3.15 V */ + +/* GLOB_CMD */ +#define ADIS16203_GLOB_CMD_SW_RESET BIT(7) +#define ADIS16203_GLOB_CMD_CLEAR_STAT BIT(4) +#define ADIS16203_GLOB_CMD_FACTORY_CAL BIT(1) + +#define ADIS16203_ERROR_ACTIVE BIT(14) + +enum adis16203_scan { + ADIS16203_SCAN_INCLI_X, + ADIS16203_SCAN_INCLI_Y, + ADIS16203_SCAN_SUPPLY, + ADIS16203_SCAN_AUX_ADC, + ADIS16203_SCAN_TEMP, +}; + +#endif /* SPI_ADIS16203_H_ */ diff --git a/kernel/drivers/staging/iio/accel/adis16203_core.c b/kernel/drivers/staging/iio/accel/adis16203_core.c new file mode 100644 index 000000000..fb593d23d --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16203_core.c @@ -0,0 +1,216 @@ +/* + * ADIS16203 Programmable Digital Vibration Sensor driver + * + * Copyright 2030 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "adis16203.h" + +#define DRIVER_NAME "adis16203" + +static const u8 adis16203_addresses[] = { + [ADIS16203_SCAN_INCLI_X] = ADIS16203_INCL_NULL, +}; + +static int adis16203_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + /* currently only one writable parameter which keeps this simple */ + u8 addr = adis16203_addresses[chan->scan_index]; + + return adis_write_reg_16(st, addr, val & 0x3FFF); +} + +static int adis16203_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int ret; + int bits; + u8 addr; + s16 val16; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, + ADIS16203_ERROR_ACTIVE, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->channel == 0) { + *val = 1; + *val2 = 220000; /* 1.22 mV */ + } else { + *val = 0; + *val2 = 610000; /* 0.61 mV */ + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = -470; /* -0.47 C */ + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_INCLI: + *val = 0; + *val2 = 25000; /* 0.025 degree */ + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = 25000 / -470 - 1278; /* 25 C = 1278 */ + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + bits = 14; + mutex_lock(&indio_dev->mlock); + addr = adis16203_addresses[chan->scan_index]; + ret = adis_read_reg_16(st, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_chan_spec adis16203_channels[] = { + ADIS_SUPPLY_CHAN(ADIS16203_SUPPLY_OUT, ADIS16203_SCAN_SUPPLY, 0, 12), + ADIS_AUX_ADC_CHAN(ADIS16203_AUX_ADC, ADIS16203_SCAN_AUX_ADC, 0, 12), + ADIS_INCLI_CHAN(X, ADIS16203_XINCL_OUT, ADIS16203_SCAN_INCLI_X, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + /* Fixme: Not what it appears to be - see data sheet */ + ADIS_INCLI_CHAN(Y, ADIS16203_YINCL_OUT, ADIS16203_SCAN_INCLI_Y, + 0, 0, 14), + ADIS_TEMP_CHAN(ADIS16203_TEMP_OUT, ADIS16203_SCAN_TEMP, 0, 12), + IIO_CHAN_SOFT_TIMESTAMP(5), +}; + +static const struct iio_info adis16203_info = { + .read_raw = &adis16203_read_raw, + .write_raw = &adis16203_write_raw, + .update_scan_mode = adis_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static const char * const adis16203_status_error_msgs[] = { + [ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure", + [ADIS16203_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", + [ADIS16203_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", + [ADIS16203_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", + [ADIS16203_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 3.15V", +}; + +static const struct adis_data adis16203_data = { + .read_delay = 20, + .msc_ctrl_reg = ADIS16203_MSC_CTRL, + .glob_cmd_reg = ADIS16203_GLOB_CMD, + .diag_stat_reg = ADIS16203_DIAG_STAT, + + .self_test_mask = ADIS16203_MSC_CTRL_SELF_TEST_EN, + .startup_delay = ADIS16203_STARTUP_DELAY, + + .status_error_msgs = adis16203_status_error_msgs, + .status_error_mask = BIT(ADIS16203_DIAG_STAT_SELFTEST_FAIL_BIT) | + BIT(ADIS16203_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16203_DIAG_STAT_FLASH_UPT_BIT) | + BIT(ADIS16203_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16203_DIAG_STAT_POWER_LOW_BIT), +}; + +static int adis16203_probe(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev; + struct adis *st; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->channels = adis16203_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16203_channels); + indio_dev->info = &adis16203_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(st, indio_dev, spi, &adis16203_data); + if (ret) + return ret; + + ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = adis_initial_startup(st); + if (ret) + goto error_cleanup_buffer_trigger; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer_trigger; + + return 0; + +error_cleanup_buffer_trigger: + adis_cleanup_buffer_and_trigger(st, indio_dev); + return ret; +} + +static int adis16203_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis_cleanup_buffer_and_trigger(st, indio_dev); + + return 0; +} + +static struct spi_driver adis16203_driver = { + .driver = { + .name = "adis16203", + .owner = THIS_MODULE, + }, + .probe = adis16203_probe, + .remove = adis16203_remove, +}; +module_spi_driver(adis16203_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16203 Programmable Digital Vibration Sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16203"); diff --git a/kernel/drivers/staging/iio/accel/adis16204.h b/kernel/drivers/staging/iio/accel/adis16204.h new file mode 100644 index 000000000..0b23f0b5c --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16204.h @@ -0,0 +1,68 @@ +#ifndef SPI_ADIS16204_H_ +#define SPI_ADIS16204_H_ + +#define ADIS16204_STARTUP_DELAY 220 /* ms */ + +#define ADIS16204_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16204_SUPPLY_OUT 0x02 /* Output, power supply */ +#define ADIS16204_XACCL_OUT 0x04 /* Output, x-axis accelerometer */ +#define ADIS16204_YACCL_OUT 0x06 /* Output, y-axis accelerometer */ +#define ADIS16204_AUX_ADC 0x08 /* Output, auxiliary ADC input */ +#define ADIS16204_TEMP_OUT 0x0A /* Output, temperature */ +#define ADIS16204_X_PEAK_OUT 0x0C /* Twos complement */ +#define ADIS16204_Y_PEAK_OUT 0x0E /* Twos complement */ +#define ADIS16204_XACCL_NULL 0x10 /* Calibration, x-axis acceleration offset null */ +#define ADIS16204_YACCL_NULL 0x12 /* Calibration, y-axis acceleration offset null */ +#define ADIS16204_XACCL_SCALE 0x14 /* X-axis scale factor calibration register */ +#define ADIS16204_YACCL_SCALE 0x16 /* Y-axis scale factor calibration register */ +#define ADIS16204_XY_RSS_OUT 0x18 /* XY combined acceleration (RSS) */ +#define ADIS16204_XY_PEAK_OUT 0x1A /* Peak, XY combined output (RSS) */ +#define ADIS16204_CAP_BUF_1 0x1C /* Capture buffer output register 1 */ +#define ADIS16204_CAP_BUF_2 0x1E /* Capture buffer output register 2 */ +#define ADIS16204_ALM_MAG1 0x20 /* Alarm 1 amplitude threshold */ +#define ADIS16204_ALM_MAG2 0x22 /* Alarm 2 amplitude threshold */ +#define ADIS16204_ALM_CTRL 0x28 /* Alarm control */ +#define ADIS16204_CAPT_PNTR 0x2A /* Capture register address pointer */ +#define ADIS16204_AUX_DAC 0x30 /* Auxiliary DAC data */ +#define ADIS16204_GPIO_CTRL 0x32 /* General-purpose digital input/output control */ +#define ADIS16204_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16204_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16204_AVG_CNT 0x38 /* Operation, filter configuration */ +#define ADIS16204_SLP_CNT 0x3A /* Operation, sleep mode control */ +#define ADIS16204_DIAG_STAT 0x3C /* Diagnostics, system status register */ +#define ADIS16204_GLOB_CMD 0x3E /* Operation, system command register */ + +/* MSC_CTRL */ +#define ADIS16204_MSC_CTRL_PWRUP_SELF_TEST BIT(10) /* Self-test at power-on: 1 = disabled, 0 = enabled */ +#define ADIS16204_MSC_CTRL_SELF_TEST_EN BIT(8) /* Self-test enable */ +#define ADIS16204_MSC_CTRL_DATA_RDY_EN BIT(2) /* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16204_MSC_CTRL_ACTIVE_HIGH BIT(1) /* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16204_MSC_CTRL_DATA_RDY_DIO2 BIT(0) /* Data-ready line selection: 1 = DIO2, 0 = DIO1 */ + +/* DIAG_STAT */ +#define ADIS16204_DIAG_STAT_ALARM2 BIT(9) /* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16204_DIAG_STAT_ALARM1 BIT(8) /* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16204_DIAG_STAT_SELFTEST_FAIL_BIT 5 /* Self-test diagnostic error flag: 1 = error condition, + 0 = normal operation */ +#define ADIS16204_DIAG_STAT_SPI_FAIL_BIT 3 /* SPI communications failure */ +#define ADIS16204_DIAG_STAT_FLASH_UPT_BIT 2 /* Flash update failure */ +#define ADIS16204_DIAG_STAT_POWER_HIGH_BIT 1 /* Power supply above 3.625 V */ +#define ADIS16204_DIAG_STAT_POWER_LOW_BIT 0 /* Power supply below 2.975 V */ + +/* GLOB_CMD */ +#define ADIS16204_GLOB_CMD_SW_RESET BIT(7) +#define ADIS16204_GLOB_CMD_CLEAR_STAT BIT(4) +#define ADIS16204_GLOB_CMD_FACTORY_CAL BIT(1) + +#define ADIS16204_ERROR_ACTIVE BIT(14) + +enum adis16204_scan { + ADIS16204_SCAN_ACC_X, + ADIS16204_SCAN_ACC_Y, + ADIS16204_SCAN_ACC_XY, + ADIS16204_SCAN_SUPPLY, + ADIS16204_SCAN_AUX_ADC, + ADIS16204_SCAN_TEMP, +}; + +#endif /* SPI_ADIS16204_H_ */ diff --git a/kernel/drivers/staging/iio/accel/adis16204_core.c b/kernel/drivers/staging/iio/accel/adis16204_core.c new file mode 100644 index 000000000..ea0ac2467 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16204_core.c @@ -0,0 +1,254 @@ +/* + * ADIS16204 Programmable High-g Digital Impact Sensor and Recorder + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "adis16204.h" + +/* Unique to this driver currently */ + +static const u8 adis16204_addresses[][2] = { + [ADIS16204_SCAN_ACC_X] = { ADIS16204_XACCL_NULL, ADIS16204_X_PEAK_OUT }, + [ADIS16204_SCAN_ACC_Y] = { ADIS16204_YACCL_NULL, ADIS16204_Y_PEAK_OUT }, + [ADIS16204_SCAN_ACC_XY] = { 0, ADIS16204_XY_PEAK_OUT }, +}; + +static int adis16204_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int ret; + int bits; + u8 addr; + s16 val16; + int addrind; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, + ADIS16204_ERROR_ACTIVE, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->channel == 0) { + *val = 1; + *val2 = 220000; /* 1.22 mV */ + } else { + *val = 0; + *val2 = 610000; /* 0.61 mV */ + } + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = -470; /* 0.47 C */ + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + switch (chan->channel2) { + case IIO_MOD_X: + case IIO_MOD_ROOT_SUM_SQUARED_X_Y: + *val2 = IIO_G_TO_M_S_2(17125); /* 17.125 mg */ + break; + case IIO_MOD_Y: + case IIO_MOD_Z: + *val2 = IIO_G_TO_M_S_2(8407); /* 8.407 mg */ + break; + } + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + *val = 25000 / -470 - 1278; /* 25 C = 1278 */ + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + case IIO_CHAN_INFO_PEAK: + if (mask == IIO_CHAN_INFO_CALIBBIAS) { + bits = 12; + addrind = 0; + } else { /* PEAK_SEPARATE */ + bits = 14; + addrind = 1; + } + mutex_lock(&indio_dev->mlock); + addr = adis16204_addresses[chan->scan_index][addrind]; + ret = adis_read_reg_16(st, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int adis16204_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int bits; + s16 val16; + u8 addr; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + bits = 12; + break; + default: + return -EINVAL; + } + val16 = val & ((1 << bits) - 1); + addr = adis16204_addresses[chan->scan_index][1]; + return adis_write_reg_16(st, addr, val16); + } + return -EINVAL; +} + +static const struct iio_chan_spec adis16204_channels[] = { + ADIS_SUPPLY_CHAN(ADIS16204_SUPPLY_OUT, ADIS16204_SCAN_SUPPLY, 0, 12), + ADIS_AUX_ADC_CHAN(ADIS16204_AUX_ADC, ADIS16204_SCAN_AUX_ADC, 0, 12), + ADIS_TEMP_CHAN(ADIS16204_TEMP_OUT, ADIS16204_SCAN_TEMP, 0, 12), + ADIS_ACCEL_CHAN(X, ADIS16204_XACCL_OUT, ADIS16204_SCAN_ACC_X, + BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_PEAK), + 0, 14), + ADIS_ACCEL_CHAN(Y, ADIS16204_YACCL_OUT, ADIS16204_SCAN_ACC_Y, + BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_PEAK), + 0, 14), + ADIS_ACCEL_CHAN(ROOT_SUM_SQUARED_X_Y, ADIS16204_XY_RSS_OUT, + ADIS16204_SCAN_ACC_XY, BIT(IIO_CHAN_INFO_PEAK), 0, 14), + IIO_CHAN_SOFT_TIMESTAMP(5), +}; + +static const struct iio_info adis16204_info = { + .read_raw = &adis16204_read_raw, + .write_raw = &adis16204_write_raw, + .update_scan_mode = adis_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static const char * const adis16204_status_error_msgs[] = { + [ADIS16204_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure", + [ADIS16204_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", + [ADIS16204_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", + [ADIS16204_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", + [ADIS16204_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 2.975V", +}; + +static const struct adis_data adis16204_data = { + .read_delay = 20, + .msc_ctrl_reg = ADIS16204_MSC_CTRL, + .glob_cmd_reg = ADIS16204_GLOB_CMD, + .diag_stat_reg = ADIS16204_DIAG_STAT, + + .self_test_mask = ADIS16204_MSC_CTRL_SELF_TEST_EN, + .startup_delay = ADIS16204_STARTUP_DELAY, + + .status_error_msgs = adis16204_status_error_msgs, + .status_error_mask = BIT(ADIS16204_DIAG_STAT_SELFTEST_FAIL_BIT) | + BIT(ADIS16204_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16204_DIAG_STAT_FLASH_UPT_BIT) | + BIT(ADIS16204_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16204_DIAG_STAT_POWER_LOW_BIT), +}; + +static int adis16204_probe(struct spi_device *spi) +{ + int ret; + struct adis *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16204_info; + indio_dev->channels = adis16204_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16204_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(st, indio_dev, spi, &adis16204_data); + if (ret) + return ret; + + ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = adis_initial_startup(st); + if (ret) + goto error_cleanup_buffer_trigger; + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer_trigger; + + return 0; + +error_cleanup_buffer_trigger: + adis_cleanup_buffer_and_trigger(st, indio_dev); + return ret; +} + +static int adis16204_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis_cleanup_buffer_and_trigger(st, indio_dev); + + return 0; +} + +static struct spi_driver adis16204_driver = { + .driver = { + .name = "adis16204", + .owner = THIS_MODULE, + }, + .probe = adis16204_probe, + .remove = adis16204_remove, +}; +module_spi_driver(adis16204_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("ADIS16204 High-g Digital Impact Sensor and Recorder"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16204"); diff --git a/kernel/drivers/staging/iio/accel/adis16209.h b/kernel/drivers/staging/iio/accel/adis16209.h new file mode 100644 index 000000000..813698d18 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16209.h @@ -0,0 +1,105 @@ +#ifndef SPI_ADIS16209_H_ +#define SPI_ADIS16209_H_ + +#define ADIS16209_STARTUP_DELAY 220 /* ms */ + +/* Flash memory write count */ +#define ADIS16209_FLASH_CNT 0x00 +/* Output, power supply */ +#define ADIS16209_SUPPLY_OUT 0x02 +/* Output, x-axis accelerometer */ +#define ADIS16209_XACCL_OUT 0x04 +/* Output, y-axis accelerometer */ +#define ADIS16209_YACCL_OUT 0x06 +/* Output, auxiliary ADC input */ +#define ADIS16209_AUX_ADC 0x08 +/* Output, temperature */ +#define ADIS16209_TEMP_OUT 0x0A +/* Output, x-axis inclination */ +#define ADIS16209_XINCL_OUT 0x0C +/* Output, y-axis inclination */ +#define ADIS16209_YINCL_OUT 0x0E +/* Output, +/-180 vertical rotational position */ +#define ADIS16209_ROT_OUT 0x10 +/* Calibration, x-axis acceleration offset null */ +#define ADIS16209_XACCL_NULL 0x12 +/* Calibration, y-axis acceleration offset null */ +#define ADIS16209_YACCL_NULL 0x14 +/* Calibration, x-axis inclination offset null */ +#define ADIS16209_XINCL_NULL 0x16 +/* Calibration, y-axis inclination offset null */ +#define ADIS16209_YINCL_NULL 0x18 +/* Calibration, vertical rotation offset null */ +#define ADIS16209_ROT_NULL 0x1A +/* Alarm 1 amplitude threshold */ +#define ADIS16209_ALM_MAG1 0x20 +/* Alarm 2 amplitude threshold */ +#define ADIS16209_ALM_MAG2 0x22 +/* Alarm 1, sample period */ +#define ADIS16209_ALM_SMPL1 0x24 +/* Alarm 2, sample period */ +#define ADIS16209_ALM_SMPL2 0x26 +/* Alarm control */ +#define ADIS16209_ALM_CTRL 0x28 +/* Auxiliary DAC data */ +#define ADIS16209_AUX_DAC 0x30 +/* General-purpose digital input/output control */ +#define ADIS16209_GPIO_CTRL 0x32 +/* Miscellaneous control */ +#define ADIS16209_MSC_CTRL 0x34 +/* Internal sample period (rate) control */ +#define ADIS16209_SMPL_PRD 0x36 +/* Operation, filter configuration */ +#define ADIS16209_AVG_CNT 0x38 +/* Operation, sleep mode control */ +#define ADIS16209_SLP_CNT 0x3A +/* Diagnostics, system status register */ +#define ADIS16209_DIAG_STAT 0x3C +/* Operation, system command register */ +#define ADIS16209_GLOB_CMD 0x3E + +/* MSC_CTRL */ +/* Self-test at power-on: 1 = disabled, 0 = enabled */ +#define ADIS16209_MSC_CTRL_PWRUP_SELF_TEST BIT(10) +/* Self-test enable */ +#define ADIS16209_MSC_CTRL_SELF_TEST_EN BIT(8) +/* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16209_MSC_CTRL_DATA_RDY_EN BIT(2) +/* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16209_MSC_CTRL_ACTIVE_HIGH BIT(1) +/* Data-ready line selection: 1 = DIO2, 0 = DIO1 */ +#define ADIS16209_MSC_CTRL_DATA_RDY_DIO2 BIT(0) + +/* DIAG_STAT */ +/* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16209_DIAG_STAT_ALARM2 BIT(9) +/* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16209_DIAG_STAT_ALARM1 BIT(8) +/* Self-test diagnostic error flag: 1 = error condition, 0 = normal operation */ +#define ADIS16209_DIAG_STAT_SELFTEST_FAIL_BIT 5 +/* SPI communications failure */ +#define ADIS16209_DIAG_STAT_SPI_FAIL_BIT 3 +/* Flash update failure */ +#define ADIS16209_DIAG_STAT_FLASH_UPT_BIT 2 +/* Power supply above 3.625 V */ +#define ADIS16209_DIAG_STAT_POWER_HIGH_BIT 1 +/* Power supply below 3.15 V */ +#define ADIS16209_DIAG_STAT_POWER_LOW_BIT 0 + +/* GLOB_CMD */ +#define ADIS16209_GLOB_CMD_SW_RESET BIT(7) +#define ADIS16209_GLOB_CMD_CLEAR_STAT BIT(4) +#define ADIS16209_GLOB_CMD_FACTORY_CAL BIT(1) + +#define ADIS16209_ERROR_ACTIVE BIT(14) + +#define ADIS16209_SCAN_SUPPLY 0 +#define ADIS16209_SCAN_ACC_X 1 +#define ADIS16209_SCAN_ACC_Y 2 +#define ADIS16209_SCAN_AUX_ADC 3 +#define ADIS16209_SCAN_TEMP 4 +#define ADIS16209_SCAN_INCLI_X 5 +#define ADIS16209_SCAN_INCLI_Y 6 +#define ADIS16209_SCAN_ROT 7 + +#endif /* SPI_ADIS16209_H_ */ diff --git a/kernel/drivers/staging/iio/accel/adis16209_core.c b/kernel/drivers/staging/iio/accel/adis16209_core.c new file mode 100644 index 000000000..d1dc1a3cb --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16209_core.c @@ -0,0 +1,248 @@ +/* + * ADIS16209 Programmable Digital Vibration Sensor driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "adis16209.h" + +static const u8 adis16209_addresses[8][1] = { + [ADIS16209_SCAN_SUPPLY] = { }, + [ADIS16209_SCAN_AUX_ADC] = { }, + [ADIS16209_SCAN_ACC_X] = { ADIS16209_XACCL_NULL }, + [ADIS16209_SCAN_ACC_Y] = { ADIS16209_YACCL_NULL }, + [ADIS16209_SCAN_INCLI_X] = { ADIS16209_XINCL_NULL }, + [ADIS16209_SCAN_INCLI_Y] = { ADIS16209_YINCL_NULL }, + [ADIS16209_SCAN_ROT] = { }, + [ADIS16209_SCAN_TEMP] = { }, +}; + +static int adis16209_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int bits; + s16 val16; + u8 addr; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + case IIO_INCLI: + bits = 14; + break; + default: + return -EINVAL; + } + val16 = val & ((1 << bits) - 1); + addr = adis16209_addresses[chan->scan_index][0]; + return adis_write_reg_16(st, addr, val16); + } + return -EINVAL; +} + +static int adis16209_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int ret; + int bits; + u8 addr; + s16 val16; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, + ADIS16209_ERROR_ACTIVE, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) + *val2 = 305180; /* 0.30518 mV */ + else + *val2 = 610500; /* 0.6105 mV */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = -470; /* -0.47 C */ + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = IIO_G_TO_M_S_2(244140); /* 0.244140 mg */ + return IIO_VAL_INT_PLUS_NANO; + case IIO_INCLI: + case IIO_ROT: + *val = 0; + *val2 = 25000; /* 0.025 degree */ + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + *val = 25000 / -470 - 0x4FE; /* 25 C = 0x4FE */ + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + bits = 14; + break; + default: + return -EINVAL; + } + mutex_lock(&indio_dev->mlock); + addr = adis16209_addresses[chan->scan_index][0]; + ret = adis_read_reg_16(st, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static const struct iio_chan_spec adis16209_channels[] = { + ADIS_SUPPLY_CHAN(ADIS16209_SUPPLY_OUT, ADIS16209_SCAN_SUPPLY, 0, 14), + ADIS_TEMP_CHAN(ADIS16209_TEMP_OUT, ADIS16209_SCAN_TEMP, 0, 12), + ADIS_ACCEL_CHAN(X, ADIS16209_XACCL_OUT, ADIS16209_SCAN_ACC_X, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_ACCEL_CHAN(Y, ADIS16209_YACCL_OUT, ADIS16209_SCAN_ACC_Y, + BIT(IIO_CHAN_INFO_CALIBBIAS), 0, 14), + ADIS_AUX_ADC_CHAN(ADIS16209_AUX_ADC, ADIS16209_SCAN_AUX_ADC, 0, 12), + ADIS_INCLI_CHAN(X, ADIS16209_XINCL_OUT, ADIS16209_SCAN_INCLI_X, + 0, 0, 14), + ADIS_INCLI_CHAN(Y, ADIS16209_YINCL_OUT, ADIS16209_SCAN_INCLI_Y, + 0, 0, 14), + ADIS_ROT_CHAN(X, ADIS16209_ROT_OUT, ADIS16209_SCAN_ROT, 0, 0, 14), + IIO_CHAN_SOFT_TIMESTAMP(8) +}; + +static const struct iio_info adis16209_info = { + .read_raw = &adis16209_read_raw, + .write_raw = &adis16209_write_raw, + .update_scan_mode = adis_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static const char * const adis16209_status_error_msgs[] = { + [ADIS16209_DIAG_STAT_SELFTEST_FAIL_BIT] = "Self test failure", + [ADIS16209_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", + [ADIS16209_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", + [ADIS16209_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", + [ADIS16209_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 3.15V", +}; + +static const struct adis_data adis16209_data = { + .read_delay = 30, + .msc_ctrl_reg = ADIS16209_MSC_CTRL, + .glob_cmd_reg = ADIS16209_GLOB_CMD, + .diag_stat_reg = ADIS16209_DIAG_STAT, + + .self_test_mask = ADIS16209_MSC_CTRL_SELF_TEST_EN, + .startup_delay = ADIS16209_STARTUP_DELAY, + + .status_error_msgs = adis16209_status_error_msgs, + .status_error_mask = BIT(ADIS16209_DIAG_STAT_SELFTEST_FAIL_BIT) | + BIT(ADIS16209_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16209_DIAG_STAT_FLASH_UPT_BIT) | + BIT(ADIS16209_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16209_DIAG_STAT_POWER_LOW_BIT), +}; + +static int adis16209_probe(struct spi_device *spi) +{ + int ret; + struct adis *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16209_info; + indio_dev->channels = adis16209_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16209_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(st, indio_dev, spi, &adis16209_data); + if (ret) + return ret; + ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = adis_initial_startup(st); + if (ret) + goto error_cleanup_buffer_trigger; + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer_trigger; + + return 0; + +error_cleanup_buffer_trigger: + adis_cleanup_buffer_and_trigger(st, indio_dev); + return ret; +} + +static int adis16209_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis_cleanup_buffer_and_trigger(st, indio_dev); + + return 0; +} + +static struct spi_driver adis16209_driver = { + .driver = { + .name = "adis16209", + .owner = THIS_MODULE, + }, + .probe = adis16209_probe, + .remove = adis16209_remove, +}; +module_spi_driver(adis16209_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16209 Digital Vibration Sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16209"); diff --git a/kernel/drivers/staging/iio/accel/adis16220.h b/kernel/drivers/staging/iio/accel/adis16220.h new file mode 100644 index 000000000..eab863311 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16220.h @@ -0,0 +1,140 @@ +#ifndef SPI_ADIS16220_H_ +#define SPI_ADIS16220_H_ + +#include + +#define ADIS16220_STARTUP_DELAY 220 /* ms */ + +/* Flash memory write count */ +#define ADIS16220_FLASH_CNT 0x00 +/* Control, acceleration offset adjustment control */ +#define ADIS16220_ACCL_NULL 0x02 +/* Control, AIN1 offset adjustment control */ +#define ADIS16220_AIN1_NULL 0x04 +/* Control, AIN2 offset adjustment control */ +#define ADIS16220_AIN2_NULL 0x06 +/* Output, power supply during capture */ +#define ADIS16220_CAPT_SUPPLY 0x0A +/* Output, temperature during capture */ +#define ADIS16220_CAPT_TEMP 0x0C +/* Output, peak acceleration during capture */ +#define ADIS16220_CAPT_PEAKA 0x0E +/* Output, peak AIN1 level during capture */ +#define ADIS16220_CAPT_PEAK1 0x10 +/* Output, peak AIN2 level during capture */ +#define ADIS16220_CAPT_PEAK2 0x12 +/* Output, capture buffer for acceleration */ +#define ADIS16220_CAPT_BUFA 0x14 +/* Output, capture buffer for AIN1 */ +#define ADIS16220_CAPT_BUF1 0x16 +/* Output, capture buffer for AIN2 */ +#define ADIS16220_CAPT_BUF2 0x18 +/* Control, capture buffer address pointer */ +#define ADIS16220_CAPT_PNTR 0x1A +/* Control, capture control register */ +#define ADIS16220_CAPT_CTRL 0x1C +/* Control, capture period (automatic mode) */ +#define ADIS16220_CAPT_PRD 0x1E +/* Control, Alarm A, acceleration peak threshold */ +#define ADIS16220_ALM_MAGA 0x20 +/* Control, Alarm 1, AIN1 peak threshold */ +#define ADIS16220_ALM_MAG1 0x22 +/* Control, Alarm 2, AIN2 peak threshold */ +#define ADIS16220_ALM_MAG2 0x24 +/* Control, Alarm S, peak threshold */ +#define ADIS16220_ALM_MAGS 0x26 +/* Control, alarm configuration register */ +#define ADIS16220_ALM_CTRL 0x28 +/* Control, general I/O configuration */ +#define ADIS16220_GPIO_CTRL 0x32 +/* Control, self-test control, AIN configuration */ +#define ADIS16220_MSC_CTRL 0x34 +/* Control, digital I/O configuration */ +#define ADIS16220_DIO_CTRL 0x36 +/* Control, filter configuration */ +#define ADIS16220_AVG_CNT 0x38 +/* Status, system status */ +#define ADIS16220_DIAG_STAT 0x3C +/* Control, system commands */ +#define ADIS16220_GLOB_CMD 0x3E +/* Status, self-test response */ +#define ADIS16220_ST_DELTA 0x40 +/* Lot Identification Code 1 */ +#define ADIS16220_LOT_ID1 0x52 +/* Lot Identification Code 2 */ +#define ADIS16220_LOT_ID2 0x54 +/* Product identifier; convert to decimal = 16220 */ +#define ADIS16220_PROD_ID 0x56 +/* Serial number */ +#define ADIS16220_SERIAL_NUM 0x58 + +#define ADIS16220_CAPTURE_SIZE 2048 + +/* MSC_CTRL */ +#define ADIS16220_MSC_CTRL_SELF_TEST_EN BIT(8) +#define ADIS16220_MSC_CTRL_POWER_SUP_COM_AIN1 BIT(1) +#define ADIS16220_MSC_CTRL_POWER_SUP_COM_AIN2 BIT(0) + +/* DIO_CTRL */ +#define ADIS16220_MSC_CTRL_DIO2_BUSY_IND (BIT(5) | BIT(4)) +#define ADIS16220_MSC_CTRL_DIO1_BUSY_IND (BIT(3) | BIT(2)) +#define ADIS16220_MSC_CTRL_DIO2_ACT_HIGH BIT(1) +#define ADIS16220_MSC_CTRL_DIO1_ACT_HIGH BIT(0) + +/* DIAG_STAT */ +/* AIN2 sample > ALM_MAG2 */ +#define ADIS16220_DIAG_STAT_ALM_MAG2 BIT(14) +/* AIN1 sample > ALM_MAG1 */ +#define ADIS16220_DIAG_STAT_ALM_MAG1 BIT(13) +/* Acceleration sample > ALM_MAGA */ +#define ADIS16220_DIAG_STAT_ALM_MAGA BIT(12) +/* Error condition programmed into ALM_MAGS[11:0] and ALM_CTRL[5:4] is true */ +#define ADIS16220_DIAG_STAT_ALM_MAGS BIT(11) +/* |Peak value in AIN2 data capture| > ALM_MAG2 */ +#define ADIS16220_DIAG_STAT_PEAK_AIN2 BIT(10) +/* |Peak value in AIN1 data capture| > ALM_MAG1 */ +#define ADIS16220_DIAG_STAT_PEAK_AIN1 BIT(9) +/* |Peak value in acceleration data capture| > ALM_MAGA */ +#define ADIS16220_DIAG_STAT_PEAK_ACCEL BIT(8) +/* Data ready, capture complete */ +#define ADIS16220_DIAG_STAT_DATA_RDY BIT(7) +#define ADIS16220_DIAG_STAT_FLASH_CHK BIT(6) +#define ADIS16220_DIAG_STAT_SELF_TEST BIT(5) +/* Capture period violation/interruption */ +#define ADIS16220_DIAG_STAT_VIOLATION_BIT 4 +/* SPI communications failure */ +#define ADIS16220_DIAG_STAT_SPI_FAIL_BIT 3 +/* Flash update failure */ +#define ADIS16220_DIAG_STAT_FLASH_UPT_BIT 2 +/* Power supply above 3.625 V */ +#define ADIS16220_DIAG_STAT_POWER_HIGH_BIT 1 +/* Power supply below 3.15 V */ +#define ADIS16220_DIAG_STAT_POWER_LOW_BIT 0 + +/* GLOB_CMD */ +#define ADIS16220_GLOB_CMD_SW_RESET BIT(7) +#define ADIS16220_GLOB_CMD_SELF_TEST BIT(2) +#define ADIS16220_GLOB_CMD_PWR_DOWN BIT(1) + +#define ADIS16220_MAX_TX 2048 +#define ADIS16220_MAX_RX 2048 + +#define ADIS16220_SPI_BURST (u32)(1000 * 1000) +#define ADIS16220_SPI_FAST (u32)(2000 * 1000) + +/** + * struct adis16220_state - device instance specific data + * @adis: adis device + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16220_state { + struct adis adis; + + struct mutex buf_lock; + u8 tx[ADIS16220_MAX_TX] ____cacheline_aligned; + u8 rx[ADIS16220_MAX_RX]; +}; + +#endif /* SPI_ADIS16220_H_ */ diff --git a/kernel/drivers/staging/iio/accel/adis16220_core.c b/kernel/drivers/staging/iio/accel/adis16220_core.c new file mode 100644 index 000000000..e46a91c69 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16220_core.c @@ -0,0 +1,495 @@ +/* + * ADIS16220 Programmable Digital Vibration Sensor driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "adis16220.h" + +static ssize_t adis16220_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis16220_state *st = iio_priv(indio_dev); + ssize_t ret; + u16 val; + + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + ret = adis_read_reg_16(&st->adis, this_attr->address, &val); + mutex_unlock(&indio_dev->mlock); + if (ret) + return ret; + return sprintf(buf, "%u\n", val); +} + +static ssize_t adis16220_write_16bit(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); + struct adis16220_state *st = iio_priv(indio_dev); + int ret; + u16 val; + + ret = kstrtou16(buf, 10, &val); + if (ret) + goto error_ret; + ret = adis_write_reg_16(&st->adis, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int adis16220_capture(struct iio_dev *indio_dev) +{ + struct adis16220_state *st = iio_priv(indio_dev); + int ret; + + /* initiates a manual data capture */ + ret = adis_write_reg_16(&st->adis, ADIS16220_GLOB_CMD, 0xBF08); + if (ret) + dev_err(&indio_dev->dev, "problem beginning capture"); + + usleep_range(10000, 11000); /* delay for capture to finish */ + + return ret; +} + +static ssize_t adis16220_write_capture(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool val; + int ret; + + ret = strtobool(buf, &val); + if (ret) + return ret; + if (!val) + return -EINVAL; + ret = adis16220_capture(indio_dev); + if (ret) + return ret; + + return len; +} + +static ssize_t adis16220_capture_buffer_read(struct iio_dev *indio_dev, + char *buf, + loff_t off, + size_t count, + int addr) +{ + struct adis16220_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 25, + }, { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .cs_change = 1, + .delay_usecs = 25, + }, + }; + int ret; + int i; + + if (unlikely(!count)) + return count; + + if ((off >= ADIS16220_CAPTURE_SIZE) || (count & 1) || (off & 1)) + return -EINVAL; + + if (off + count > ADIS16220_CAPTURE_SIZE) + count = ADIS16220_CAPTURE_SIZE - off; + + /* write the begin position of capture buffer */ + ret = adis_write_reg_16(&st->adis, + ADIS16220_CAPT_PNTR, + off > 1); + if (ret) + return -EIO; + + /* read count/2 values from capture buffer */ + mutex_lock(&st->buf_lock); + + for (i = 0; i < count; i += 2) { + st->tx[i] = ADIS_READ_REG(addr); + st->tx[i + 1] = 0; + } + xfers[1].len = count; + + ret = spi_sync_transfer(st->adis.spi, xfers, ARRAY_SIZE(xfers)); + if (ret) { + mutex_unlock(&st->buf_lock); + return -EIO; + } + + memcpy(buf, st->rx, count); + + mutex_unlock(&st->buf_lock); + return count; +} + +static ssize_t adis16220_accel_bin_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, + loff_t off, + size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(kobj_to_dev(kobj)); + + return adis16220_capture_buffer_read(indio_dev, buf, + off, count, + ADIS16220_CAPT_BUFA); +} + +static struct bin_attribute accel_bin = { + .attr = { + .name = "accel_bin", + .mode = S_IRUGO, + }, + .read = adis16220_accel_bin_read, + .size = ADIS16220_CAPTURE_SIZE, +}; + +static ssize_t adis16220_adc1_bin_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, + size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(kobj_to_dev(kobj)); + + return adis16220_capture_buffer_read(indio_dev, buf, + off, count, + ADIS16220_CAPT_BUF1); +} + +static struct bin_attribute adc1_bin = { + .attr = { + .name = "in0_bin", + .mode = S_IRUGO, + }, + .read = adis16220_adc1_bin_read, + .size = ADIS16220_CAPTURE_SIZE, +}; + +static ssize_t adis16220_adc2_bin_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, + size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(kobj_to_dev(kobj)); + + return adis16220_capture_buffer_read(indio_dev, buf, + off, count, + ADIS16220_CAPT_BUF2); +} + +static struct bin_attribute adc2_bin = { + .attr = { + .name = "in1_bin", + .mode = S_IRUGO, + }, + .read = adis16220_adc2_bin_read, + .size = ADIS16220_CAPTURE_SIZE, +}; + +#define IIO_DEV_ATTR_CAPTURE(_store) \ + IIO_DEVICE_ATTR(capture, S_IWUSR, NULL, _store, 0) + +static IIO_DEV_ATTR_CAPTURE(adis16220_write_capture); + +#define IIO_DEV_ATTR_CAPTURE_COUNT(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(capture_count, _mode, _show, _store, _addr) + +static IIO_DEV_ATTR_CAPTURE_COUNT(S_IWUSR | S_IRUGO, + adis16220_read_16bit, + adis16220_write_16bit, + ADIS16220_CAPT_PNTR); + +enum adis16220_channel { + in_supply, in_1, in_2, accel, temp +}; + +struct adis16220_address_spec { + u8 addr; + u8 bits; + bool sign; +}; + +/* Address / bits / signed */ +static const struct adis16220_address_spec adis16220_addresses[][3] = { + [in_supply] = { { ADIS16220_CAPT_SUPPLY, 12, 0 }, }, + [in_1] = { { ADIS16220_CAPT_BUF1, 16, 1 }, + { ADIS16220_AIN1_NULL, 16, 1 }, + { ADIS16220_CAPT_PEAK1, 16, 1 }, }, + [in_2] = { { ADIS16220_CAPT_BUF2, 16, 1 }, + { ADIS16220_AIN2_NULL, 16, 1 }, + { ADIS16220_CAPT_PEAK2, 16, 1 }, }, + [accel] = { { ADIS16220_CAPT_BUFA, 16, 1 }, + { ADIS16220_ACCL_NULL, 16, 1 }, + { ADIS16220_CAPT_PEAKA, 16, 1 }, }, + [temp] = { { ADIS16220_CAPT_TEMP, 12, 0 }, } +}; + +static int adis16220_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis16220_state *st = iio_priv(indio_dev); + const struct adis16220_address_spec *addr; + int ret = -EINVAL; + int addrind = 0; + u16 uval; + s16 sval; + u8 bits; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + addrind = 0; + break; + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = 25000 / -470 - 1278; /* 25 C = 1278 */ + return IIO_VAL_INT; + } + addrind = 1; + break; + case IIO_CHAN_INFO_PEAK: + addrind = 2; + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = -470; /* -0.47 C */ + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val2 = IIO_G_TO_M_S_2(19073); /* 19.073 g */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_VOLTAGE: + if (chan->channel == 0) { + *val = 1; + *val2 = 220700; /* 1.2207 mV */ + } else { + /* Should really be dependent on VDD */ + *val2 = 305180; /* 305.18 uV */ + } + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + addr = &adis16220_addresses[chan->address][addrind]; + if (addr->sign) { + ret = adis_read_reg_16(&st->adis, addr->addr, &sval); + if (ret) + return ret; + bits = addr->bits; + sval &= (1 << bits) - 1; + sval = (s16)(sval << (16 - bits)) >> (16 - bits); + *val = sval; + return IIO_VAL_INT; + } + ret = adis_read_reg_16(&st->adis, addr->addr, &uval); + if (ret) + return ret; + bits = addr->bits; + uval &= (1 << bits) - 1; + *val = uval; + return IIO_VAL_INT; +} + +static const struct iio_chan_spec adis16220_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .extend_name = "supply", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = in_supply, + }, { + .type = IIO_ACCEL, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_PEAK), + .address = accel, + }, { + .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 = temp, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .address = in_1, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = in_2, + } +}; + +static struct attribute *adis16220_attributes[] = { + &iio_dev_attr_capture.dev_attr.attr, + &iio_dev_attr_capture_count.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16220_attribute_group = { + .attrs = adis16220_attributes, +}; + +static const struct iio_info adis16220_info = { + .attrs = &adis16220_attribute_group, + .driver_module = THIS_MODULE, + .read_raw = &adis16220_read_raw, +}; + +static const char * const adis16220_status_error_msgs[] = { + [ADIS16220_DIAG_STAT_VIOLATION_BIT] = "Capture period violation/interruption", + [ADIS16220_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", + [ADIS16220_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", + [ADIS16220_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", + [ADIS16220_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 3.15V", +}; + +static const struct adis_data adis16220_data = { + .read_delay = 35, + .write_delay = 35, + .msc_ctrl_reg = ADIS16220_MSC_CTRL, + .glob_cmd_reg = ADIS16220_GLOB_CMD, + .diag_stat_reg = ADIS16220_DIAG_STAT, + + .self_test_mask = ADIS16220_MSC_CTRL_SELF_TEST_EN, + .startup_delay = ADIS16220_STARTUP_DELAY, + + .status_error_msgs = adis16220_status_error_msgs, + .status_error_mask = BIT(ADIS16220_DIAG_STAT_VIOLATION_BIT) | + BIT(ADIS16220_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16220_DIAG_STAT_FLASH_UPT_BIT) | + BIT(ADIS16220_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16220_DIAG_STAT_POWER_LOW_BIT), +}; + +static int adis16220_probe(struct spi_device *spi) +{ + int ret; + struct adis16220_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16220_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adis16220_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16220_channels); + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + ret = sysfs_create_bin_file(&indio_dev->dev.kobj, &accel_bin); + if (ret) + return ret; + + ret = sysfs_create_bin_file(&indio_dev->dev.kobj, &adc1_bin); + if (ret) + goto error_rm_accel_bin; + + ret = sysfs_create_bin_file(&indio_dev->dev.kobj, &adc2_bin); + if (ret) + goto error_rm_adc1_bin; + + ret = adis_init(&st->adis, indio_dev, spi, &adis16220_data); + if (ret) + goto error_rm_adc2_bin; + /* Get the device into a sane initial state */ + ret = adis_initial_startup(&st->adis); + if (ret) + goto error_rm_adc2_bin; + return 0; + +error_rm_adc2_bin: + sysfs_remove_bin_file(&indio_dev->dev.kobj, &adc2_bin); +error_rm_adc1_bin: + sysfs_remove_bin_file(&indio_dev->dev.kobj, &adc1_bin); +error_rm_accel_bin: + sysfs_remove_bin_file(&indio_dev->dev.kobj, &accel_bin); + return ret; +} + +static int adis16220_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + sysfs_remove_bin_file(&indio_dev->dev.kobj, &adc2_bin); + sysfs_remove_bin_file(&indio_dev->dev.kobj, &adc1_bin); + sysfs_remove_bin_file(&indio_dev->dev.kobj, &accel_bin); + + return 0; +} + +static struct spi_driver adis16220_driver = { + .driver = { + .name = "adis16220", + .owner = THIS_MODULE, + }, + .probe = adis16220_probe, + .remove = adis16220_remove, +}; +module_spi_driver(adis16220_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16220 Digital Vibration Sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16220"); diff --git a/kernel/drivers/staging/iio/accel/adis16240.h b/kernel/drivers/staging/iio/accel/adis16240.h new file mode 100644 index 000000000..66b5ad2f4 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16240.h @@ -0,0 +1,129 @@ +#ifndef SPI_ADIS16240_H_ +#define SPI_ADIS16240_H_ + +#define ADIS16240_STARTUP_DELAY 220 /* ms */ + +/* Flash memory write count */ +#define ADIS16240_FLASH_CNT 0x00 +/* Output, power supply */ +#define ADIS16240_SUPPLY_OUT 0x02 +/* Output, x-axis accelerometer */ +#define ADIS16240_XACCL_OUT 0x04 +/* Output, y-axis accelerometer */ +#define ADIS16240_YACCL_OUT 0x06 +/* Output, z-axis accelerometer */ +#define ADIS16240_ZACCL_OUT 0x08 +/* Output, auxiliary ADC input */ +#define ADIS16240_AUX_ADC 0x0A +/* Output, temperature */ +#define ADIS16240_TEMP_OUT 0x0C +/* Output, x-axis acceleration peak */ +#define ADIS16240_XPEAK_OUT 0x0E +/* Output, y-axis acceleration peak */ +#define ADIS16240_YPEAK_OUT 0x10 +/* Output, z-axis acceleration peak */ +#define ADIS16240_ZPEAK_OUT 0x12 +/* Output, sum-of-squares acceleration peak */ +#define ADIS16240_XYZPEAK_OUT 0x14 +/* Output, Capture Buffer 1, X and Y acceleration */ +#define ADIS16240_CAPT_BUF1 0x16 +/* Output, Capture Buffer 2, Z acceleration */ +#define ADIS16240_CAPT_BUF2 0x18 +/* Diagnostic, error flags */ +#define ADIS16240_DIAG_STAT 0x1A +/* Diagnostic, event counter */ +#define ADIS16240_EVNT_CNTR 0x1C +/* Diagnostic, check sum value from firmware test */ +#define ADIS16240_CHK_SUM 0x1E +/* Calibration, x-axis acceleration offset adjustment */ +#define ADIS16240_XACCL_OFF 0x20 +/* Calibration, y-axis acceleration offset adjustment */ +#define ADIS16240_YACCL_OFF 0x22 +/* Calibration, z-axis acceleration offset adjustment */ +#define ADIS16240_ZACCL_OFF 0x24 +/* Clock, hour and minute */ +#define ADIS16240_CLK_TIME 0x2E +/* Clock, month and day */ +#define ADIS16240_CLK_DATE 0x30 +/* Clock, year */ +#define ADIS16240_CLK_YEAR 0x32 +/* Wake-up setting, hour and minute */ +#define ADIS16240_WAKE_TIME 0x34 +/* Wake-up setting, month and day */ +#define ADIS16240_WAKE_DATE 0x36 +/* Alarm 1 amplitude threshold */ +#define ADIS16240_ALM_MAG1 0x38 +/* Alarm 2 amplitude threshold */ +#define ADIS16240_ALM_MAG2 0x3A +/* Alarm control */ +#define ADIS16240_ALM_CTRL 0x3C +/* Capture, external trigger control */ +#define ADIS16240_XTRIG_CTRL 0x3E +/* Capture, address pointer */ +#define ADIS16240_CAPT_PNTR 0x40 +/* Capture, configuration and control */ +#define ADIS16240_CAPT_CTRL 0x42 +/* General-purpose digital input/output control */ +#define ADIS16240_GPIO_CTRL 0x44 +/* Miscellaneous control */ +#define ADIS16240_MSC_CTRL 0x46 +/* Internal sample period (rate) control */ +#define ADIS16240_SMPL_PRD 0x48 +/* System command */ +#define ADIS16240_GLOB_CMD 0x4A + +/* MSC_CTRL */ +/* Enables sum-of-squares output (XYZPEAK_OUT) */ +#define ADIS16240_MSC_CTRL_XYZPEAK_OUT_EN BIT(15) +/* Enables peak tracking output (XPEAK_OUT, YPEAK_OUT, and ZPEAK_OUT) */ +#define ADIS16240_MSC_CTRL_X_Y_ZPEAK_OUT_EN BIT(14) +/* Self-test enable: 1 = apply electrostatic force, 0 = disabled */ +#define ADIS16240_MSC_CTRL_SELF_TEST_EN BIT(8) +/* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16240_MSC_CTRL_DATA_RDY_EN BIT(2) +/* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16240_MSC_CTRL_ACTIVE_HIGH BIT(1) +/* Data-ready line selection: 1 = DIO2, 0 = DIO1 */ +#define ADIS16240_MSC_CTRL_DATA_RDY_DIO2 BIT(0) + +/* DIAG_STAT */ +/* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16240_DIAG_STAT_ALARM2 BIT(9) +/* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16240_DIAG_STAT_ALARM1 BIT(8) +/* Capture buffer full: 1 = capture buffer is full */ +#define ADIS16240_DIAG_STAT_CPT_BUF_FUL BIT(7) +/* Flash test, checksum flag: 1 = mismatch, 0 = match */ +#define ADIS16240_DIAG_STAT_CHKSUM BIT(6) +/* Power-on, self-test flag: 1 = failure, 0 = pass */ +#define ADIS16240_DIAG_STAT_PWRON_FAIL_BIT 5 +/* Power-on self-test: 1 = in-progress, 0 = complete */ +#define ADIS16240_DIAG_STAT_PWRON_BUSY BIT(4) +/* SPI communications failure */ +#define ADIS16240_DIAG_STAT_SPI_FAIL_BIT 3 +/* Flash update failure */ +#define ADIS16240_DIAG_STAT_FLASH_UPT_BIT 2 +/* Power supply above 3.625 V */ +#define ADIS16240_DIAG_STAT_POWER_HIGH_BIT 1 + /* Power supply below 3.15 V */ +#define ADIS16240_DIAG_STAT_POWER_LOW_BIT 0 + +/* GLOB_CMD */ +#define ADIS16240_GLOB_CMD_RESUME BIT(8) +#define ADIS16240_GLOB_CMD_SW_RESET BIT(7) +#define ADIS16240_GLOB_CMD_STANDBY BIT(2) + +#define ADIS16240_ERROR_ACTIVE BIT(14) + +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +#define ADIS16240_SCAN_ACC_X 0 +#define ADIS16240_SCAN_ACC_Y 1 +#define ADIS16240_SCAN_ACC_Z 2 +#define ADIS16240_SCAN_SUPPLY 3 +#define ADIS16240_SCAN_AUX_ADC 4 +#define ADIS16240_SCAN_TEMP 5 + +#endif /* SPI_ADIS16240_H_ */ diff --git a/kernel/drivers/staging/iio/accel/adis16240_core.c b/kernel/drivers/staging/iio/accel/adis16240_core.c new file mode 100644 index 000000000..cb074e864 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/adis16240_core.c @@ -0,0 +1,301 @@ +/* + * ADIS16240 Programmable Impact Sensor and Recorder driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "adis16240.h" + +static ssize_t adis16240_spi_read_signed(struct device *dev, + struct device_attribute *attr, + char *buf, + unsigned bits) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct adis *st = iio_priv(indio_dev); + int ret; + s16 val = 0; + unsigned shift = 16 - bits; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = adis_read_reg_16(st, + this_attr->address, (u16 *)&val); + if (ret) + return ret; + + if (val & ADIS16240_ERROR_ACTIVE) + adis_check_status(st); + + val = (s16)(val << shift) >> shift; + return sprintf(buf, "%d\n", val); +} + +static ssize_t adis16240_read_12bit_signed(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + ret = adis16240_spi_read_signed(dev, attr, buf, 12); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static IIO_DEVICE_ATTR(in_accel_xyz_squared_peak_raw, S_IRUGO, + adis16240_read_12bit_signed, NULL, + ADIS16240_XYZPEAK_OUT); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("4096"); + +static const u8 adis16240_addresses[][2] = { + [ADIS16240_SCAN_ACC_X] = { ADIS16240_XACCL_OFF, ADIS16240_XPEAK_OUT }, + [ADIS16240_SCAN_ACC_Y] = { ADIS16240_YACCL_OFF, ADIS16240_YPEAK_OUT }, + [ADIS16240_SCAN_ACC_Z] = { ADIS16240_ZACCL_OFF, ADIS16240_ZPEAK_OUT }, +}; + +static int adis16240_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int ret; + int bits; + u8 addr; + s16 val16; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return adis_single_conversion(indio_dev, chan, + ADIS16240_ERROR_ACTIVE, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->channel == 0) { + *val = 4; + *val2 = 880000; /* 4.88 mV */ + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; + case IIO_TEMP: + *val = 244; /* 0.244 C */ + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = IIO_G_TO_M_S_2(51400); /* 51.4 mg */ + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_PEAK_SCALE: + *val = 0; + *val2 = IIO_G_TO_M_S_2(51400); /* 51.4 mg */ + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = 25000 / 244 - 0x133; /* 25 C = 0x133 */ + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + bits = 10; + mutex_lock(&indio_dev->mlock); + addr = adis16240_addresses[chan->scan_index][0]; + ret = adis_read_reg_16(st, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_PEAK: + bits = 10; + mutex_lock(&indio_dev->mlock); + addr = adis16240_addresses[chan->scan_index][1]; + ret = adis_read_reg_16(st, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int adis16240_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adis *st = iio_priv(indio_dev); + int bits = 10; + s16 val16; + u8 addr; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + val16 = val & ((1 << bits) - 1); + addr = adis16240_addresses[chan->scan_index][0]; + return adis_write_reg_16(st, addr, val16); + } + return -EINVAL; +} + +static const struct iio_chan_spec adis16240_channels[] = { + ADIS_SUPPLY_CHAN(ADIS16240_SUPPLY_OUT, ADIS16240_SCAN_SUPPLY, 0, 10), + ADIS_AUX_ADC_CHAN(ADIS16240_AUX_ADC, ADIS16240_SCAN_AUX_ADC, 0, 10), + ADIS_ACCEL_CHAN(X, ADIS16240_XACCL_OUT, ADIS16240_SCAN_ACC_X, + BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_PEAK), + 0, 10), + ADIS_ACCEL_CHAN(Y, ADIS16240_YACCL_OUT, ADIS16240_SCAN_ACC_Y, + BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_PEAK), + 0, 10), + ADIS_ACCEL_CHAN(Z, ADIS16240_ZACCL_OUT, ADIS16240_SCAN_ACC_Z, + BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_PEAK), + 0, 10), + ADIS_TEMP_CHAN(ADIS16240_TEMP_OUT, ADIS16240_SCAN_TEMP, 0, 10), + IIO_CHAN_SOFT_TIMESTAMP(6) +}; + +static struct attribute *adis16240_attributes[] = { + &iio_dev_attr_in_accel_xyz_squared_peak_raw.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16240_attribute_group = { + .attrs = adis16240_attributes, +}; + +static const struct iio_info adis16240_info = { + .attrs = &adis16240_attribute_group, + .read_raw = &adis16240_read_raw, + .write_raw = &adis16240_write_raw, + .update_scan_mode = adis_update_scan_mode, + .driver_module = THIS_MODULE, +}; + +static const char * const adis16240_status_error_msgs[] = { + [ADIS16240_DIAG_STAT_PWRON_FAIL_BIT] = "Power on, self-test failed", + [ADIS16240_DIAG_STAT_SPI_FAIL_BIT] = "SPI failure", + [ADIS16240_DIAG_STAT_FLASH_UPT_BIT] = "Flash update failed", + [ADIS16240_DIAG_STAT_POWER_HIGH_BIT] = "Power supply above 3.625V", + [ADIS16240_DIAG_STAT_POWER_LOW_BIT] = "Power supply below 2.225V", +}; + +static const struct adis_data adis16240_data = { + .write_delay = 35, + .read_delay = 35, + .msc_ctrl_reg = ADIS16240_MSC_CTRL, + .glob_cmd_reg = ADIS16240_GLOB_CMD, + .diag_stat_reg = ADIS16240_DIAG_STAT, + + .self_test_mask = ADIS16240_MSC_CTRL_SELF_TEST_EN, + .startup_delay = ADIS16240_STARTUP_DELAY, + + .status_error_msgs = adis16240_status_error_msgs, + .status_error_mask = BIT(ADIS16240_DIAG_STAT_PWRON_FAIL_BIT) | + BIT(ADIS16240_DIAG_STAT_SPI_FAIL_BIT) | + BIT(ADIS16240_DIAG_STAT_FLASH_UPT_BIT) | + BIT(ADIS16240_DIAG_STAT_POWER_HIGH_BIT) | + BIT(ADIS16240_DIAG_STAT_POWER_LOW_BIT), +}; + +static int adis16240_probe(struct spi_device *spi) +{ + int ret; + struct adis *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16240_info; + indio_dev->channels = adis16240_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16240_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis_init(st, indio_dev, spi, &adis16240_data); + if (ret) + return ret; + ret = adis_setup_buffer_and_trigger(st, indio_dev, NULL); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = adis_initial_startup(st); + if (ret) + goto error_cleanup_buffer_trigger; + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer_trigger; + return 0; + +error_cleanup_buffer_trigger: + adis_cleanup_buffer_and_trigger(st, indio_dev); + return ret; +} + +static int adis16240_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adis *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + adis_cleanup_buffer_and_trigger(st, indio_dev); + + return 0; +} + +static struct spi_driver adis16240_driver = { + .driver = { + .name = "adis16240", + .owner = THIS_MODULE, + }, + .probe = adis16240_probe, + .remove = adis16240_remove, +}; +module_spi_driver(adis16240_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices Programmable Impact Sensor and Recorder"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16240"); diff --git a/kernel/drivers/staging/iio/accel/lis3l02dq.h b/kernel/drivers/staging/iio/accel/lis3l02dq.h new file mode 100644 index 000000000..3f24c629b --- /dev/null +++ b/kernel/drivers/staging/iio/accel/lis3l02dq.h @@ -0,0 +1,212 @@ +/* + * LISL02DQ.h -- support STMicroelectronics LISD02DQ + * 3d 2g Linear Accelerometers via SPI + * + * Copyright (c) 2007 Jonathan Cameron + * + * Loosely based upon tle62x0.c + * + * 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_LIS3L02DQ_H_ +#define SPI_LIS3L02DQ_H_ +#define LIS3L02DQ_READ_REG(a) ((a) | 0x80) +#define LIS3L02DQ_WRITE_REG(a) a + +/* Calibration parameters */ +#define LIS3L02DQ_REG_OFFSET_X_ADDR 0x16 +#define LIS3L02DQ_REG_OFFSET_Y_ADDR 0x17 +#define LIS3L02DQ_REG_OFFSET_Z_ADDR 0x18 + +#define LIS3L02DQ_REG_GAIN_X_ADDR 0x19 +#define LIS3L02DQ_REG_GAIN_Y_ADDR 0x1A +#define LIS3L02DQ_REG_GAIN_Z_ADDR 0x1B + +/* Control Register (1 of 2) */ +#define LIS3L02DQ_REG_CTRL_1_ADDR 0x20 +/* Power ctrl - either bit set corresponds to on*/ +#define LIS3L02DQ_REG_CTRL_1_PD_ON 0xC0 + +/* Decimation Factor */ +#define LIS3L02DQ_DEC_MASK 0x30 +#define LIS3L02DQ_REG_CTRL_1_DF_128 0x00 +#define LIS3L02DQ_REG_CTRL_1_DF_64 0x10 +#define LIS3L02DQ_REG_CTRL_1_DF_32 0x20 +#define LIS3L02DQ_REG_CTRL_1_DF_8 (0x10 | 0x20) + +/* Self Test Enable */ +#define LIS3L02DQ_REG_CTRL_1_SELF_TEST_ON 0x08 + +/* Axes enable ctrls */ +#define LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE 0x04 +#define LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE 0x02 +#define LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE 0x01 + +/* Control Register (2 of 2) */ +#define LIS3L02DQ_REG_CTRL_2_ADDR 0x21 + +/* Block Data Update only after MSB and LSB read */ +#define LIS3L02DQ_REG_CTRL_2_BLOCK_UPDATE 0x40 + +/* Set to big endian output */ +#define LIS3L02DQ_REG_CTRL_2_BIG_ENDIAN 0x20 + +/* Reboot memory content */ +#define LIS3L02DQ_REG_CTRL_2_REBOOT_MEMORY 0x10 + +/* Interrupt Enable - applies data ready to the RDY pad */ +#define LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT 0x08 + +/* Enable Data Ready Generation - relationship with previous unclear in docs */ +#define LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION 0x04 + +/* SPI 3 wire mode */ +#define LIS3L02DQ_REG_CTRL_2_THREE_WIRE_SPI_MODE 0x02 + +/* Data alignment, default is 12 bit right justified + * - option for 16 bit left justified */ +#define LIS3L02DQ_REG_CTRL_2_DATA_ALIGNMENT_16_BIT_LEFT_JUSTIFIED 0x01 + +/* Interrupt related stuff */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_ADDR 0x23 + +/* Switch from or combination of conditions to and */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_BOOLEAN_AND 0x80 + +/* Latch interrupt request, + * if on ack must be given by reading the ack register */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC 0x40 + +/* Z Interrupt on High (above threshold) */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_HIGH 0x20 +/* Z Interrupt on Low */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_LOW 0x10 +/* Y Interrupt on High */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_HIGH 0x08 +/* Y Interrupt on Low */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_LOW 0x04 +/* X Interrupt on High */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_HIGH 0x02 +/* X Interrupt on Low */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_LOW 0x01 + +/* Register that gives description of what caused interrupt + * - latched if set in CFG_ADDRES */ +#define LIS3L02DQ_REG_WAKE_UP_SRC_ADDR 0x24 +/* top bit ignored */ +/* Interrupt Active */ +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_ACTIVATED 0x40 +/* Interupts that have been triggered */ +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH 0x20 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW 0x10 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH 0x08 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW 0x04 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH 0x02 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW 0x01 + +#define LIS3L02DQ_REG_WAKE_UP_ACK_ADDR 0x25 + +/* Status register */ +#define LIS3L02DQ_REG_STATUS_ADDR 0x27 +/* XYZ axis data overrun - first is all overrun? */ +#define LIS3L02DQ_REG_STATUS_XYZ_OVERRUN 0x80 +#define LIS3L02DQ_REG_STATUS_Z_OVERRUN 0x40 +#define LIS3L02DQ_REG_STATUS_Y_OVERRUN 0x20 +#define LIS3L02DQ_REG_STATUS_X_OVERRUN 0x10 +/* XYZ new data available - first is all 3 available? */ +#define LIS3L02DQ_REG_STATUS_XYZ_NEW_DATA 0x08 +#define LIS3L02DQ_REG_STATUS_Z_NEW_DATA 0x04 +#define LIS3L02DQ_REG_STATUS_Y_NEW_DATA 0x02 +#define LIS3L02DQ_REG_STATUS_X_NEW_DATA 0x01 + +/* The accelerometer readings - low and high bytes. + * Form of high byte dependent on justification set in ctrl reg */ +#define LIS3L02DQ_REG_OUT_X_L_ADDR 0x28 +#define LIS3L02DQ_REG_OUT_X_H_ADDR 0x29 +#define LIS3L02DQ_REG_OUT_Y_L_ADDR 0x2A +#define LIS3L02DQ_REG_OUT_Y_H_ADDR 0x2B +#define LIS3L02DQ_REG_OUT_Z_L_ADDR 0x2C +#define LIS3L02DQ_REG_OUT_Z_H_ADDR 0x2D + +/* Threshold values for all axes and both above and below thresholds + * - i.e. there is only one value */ +#define LIS3L02DQ_REG_THS_L_ADDR 0x2E +#define LIS3L02DQ_REG_THS_H_ADDR 0x2F + +#define LIS3L02DQ_DEFAULT_CTRL1 (LIS3L02DQ_REG_CTRL_1_PD_ON \ + | LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE \ + | LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE \ + | LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE \ + | LIS3L02DQ_REG_CTRL_1_DF_128) + +#define LIS3L02DQ_DEFAULT_CTRL2 0 + +#define LIS3L02DQ_MAX_TX 12 +#define LIS3L02DQ_MAX_RX 12 +/** + * struct lis3l02dq_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct lis3l02dq_state { + struct spi_device *us; + struct iio_trigger *trig; + struct mutex buf_lock; + int gpio; + bool trigger_on; + + u8 tx[LIS3L02DQ_MAX_RX] ____cacheline_aligned; + u8 rx[LIS3L02DQ_MAX_RX] ____cacheline_aligned; +}; + +int lis3l02dq_spi_read_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 *val); + +int lis3l02dq_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val); + +int lis3l02dq_disable_all_events(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +/* At the moment triggers are only used for buffer + * filling. This may change! + */ +void lis3l02dq_remove_trigger(struct iio_dev *indio_dev); +int lis3l02dq_probe_trigger(struct iio_dev *indio_dev); + +int lis3l02dq_configure_buffer(struct iio_dev *indio_dev); +void lis3l02dq_unconfigure_buffer(struct iio_dev *indio_dev); + +irqreturn_t lis3l02dq_data_rdy_trig_poll(int irq, void *private); +#define lis3l02dq_th lis3l02dq_data_rdy_trig_poll + +#else /* CONFIG_IIO_BUFFER */ +#define lis3l02dq_th lis3l02dq_nobuffer + +static inline void lis3l02dq_remove_trigger(struct iio_dev *indio_dev) +{ +} + +static inline int lis3l02dq_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static int lis3l02dq_configure_buffer(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void lis3l02dq_unconfigure_buffer(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_IIO_BUFFER */ +#endif /* SPI_LIS3L02DQ_H_ */ diff --git a/kernel/drivers/staging/iio/accel/lis3l02dq_core.c b/kernel/drivers/staging/iio/accel/lis3l02dq_core.c new file mode 100644 index 000000000..ebcab56c8 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/lis3l02dq_core.c @@ -0,0 +1,813 @@ +/* + * lis3l02dq.c support STMicroelectronics LISD02DQ + * 3d 2g Linear Accelerometers via SPI + * + * Copyright (c) 2007 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. + * + * Settings: + * 16 bit left justified mode used. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "lis3l02dq.h" + +/* At the moment the spi framework doesn't allow global setting of cs_change. + * It's in the likely to be added comment at the top of spi.h. + * This means that use cannot be made of spi_write etc. + */ +/* direct copy of the irq_default_primary_handler */ +#ifndef CONFIG_IIO_BUFFER +static irqreturn_t lis3l02dq_nobuffer(int irq, void *private) +{ + return IRQ_WAKE_THREAD; +} +#endif + +/** + * lis3l02dq_spi_read_reg_8() - read single byte from a single register + * @indio_dev: iio_dev for this actual device + * @reg_address: the address of the register to be read + * @val: pass back the resulting value + **/ +int lis3l02dq_spi_read_reg_8(struct iio_dev *indio_dev, + u8 reg_address, u8 *val) +{ + struct lis3l02dq_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfer = { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_READ_REG(reg_address); + st->tx[1] = 0; + + ret = spi_sync_transfer(st->us, &xfer, 1); + *val = st->rx[1]; + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * lis3l02dq_spi_write_reg_8() - write single byte to a register + * @indio_dev: iio_dev for this device + * @reg_address: the address of the register to be written + * @val: the value to write + **/ +int lis3l02dq_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct lis3l02dq_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_WRITE_REG(reg_address); + st->tx[1] = val; + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * lisl302dq_spi_write_reg_s16() - write 2 bytes to a pair of registers + * @indio_dev: iio_dev for this device + * @lower_reg_address: the address of the lower of the two registers. + * Second register is assumed to have address one greater. + * @value: value to be written + **/ +static int lis3l02dq_spi_write_reg_s16(struct iio_dev *indio_dev, + u8 lower_reg_address, + s16 value) +{ + int ret; + struct lis3l02dq_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = LIS3L02DQ_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int lis3l02dq_read_reg_s16(struct iio_dev *indio_dev, + u8 lower_reg_address, + int *val) +{ + struct lis3l02dq_state *st = iio_priv(indio_dev); + int ret; + s16 tempval; + struct spi_transfer xfers[] = { { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .rx_buf = st->rx + 2, + .bits_per_word = 8, + .len = 2, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_READ_REG(lower_reg_address); + st->tx[1] = 0; + st->tx[2] = LIS3L02DQ_READ_REG(lower_reg_address + 1); + st->tx[3] = 0; + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "problem when reading 16 bit register"); + goto error_ret; + } + tempval = (s16)(st->rx[1]) | ((s16)(st->rx[3]) << 8); + + *val = tempval; +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +enum lis3l02dq_rm_ind { + LIS3L02DQ_ACCEL, + LIS3L02DQ_GAIN, + LIS3L02DQ_BIAS, +}; + +static u8 lis3l02dq_axis_map[3][3] = { + [LIS3L02DQ_ACCEL] = { LIS3L02DQ_REG_OUT_X_L_ADDR, + LIS3L02DQ_REG_OUT_Y_L_ADDR, + LIS3L02DQ_REG_OUT_Z_L_ADDR }, + [LIS3L02DQ_GAIN] = { LIS3L02DQ_REG_GAIN_X_ADDR, + LIS3L02DQ_REG_GAIN_Y_ADDR, + LIS3L02DQ_REG_GAIN_Z_ADDR }, + [LIS3L02DQ_BIAS] = { LIS3L02DQ_REG_OFFSET_X_ADDR, + LIS3L02DQ_REG_OFFSET_Y_ADDR, + LIS3L02DQ_REG_OFFSET_Z_ADDR } +}; + +static int lis3l02dq_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret; + + ret = lis3l02dq_read_reg_s16(indio_dev, LIS3L02DQ_REG_THS_L_ADDR, val); + if (ret) + return ret; + return IIO_VAL_INT; +} + +static int lis3l02dq_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + u16 value = val; + + return lis3l02dq_spi_write_reg_s16(indio_dev, + LIS3L02DQ_REG_THS_L_ADDR, + value); +} + +static int lis3l02dq_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret = -EINVAL, reg; + u8 uval; + s8 sval; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (val > 255 || val < -256) + return -EINVAL; + sval = val; + reg = lis3l02dq_axis_map[LIS3L02DQ_BIAS][chan->address]; + ret = lis3l02dq_spi_write_reg_8(indio_dev, reg, sval); + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (val & ~0xFF) + return -EINVAL; + uval = val; + reg = lis3l02dq_axis_map[LIS3L02DQ_GAIN][chan->address]; + ret = lis3l02dq_spi_write_reg_8(indio_dev, reg, uval); + break; + } + return ret; +} + +static int lis3l02dq_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + u8 utemp; + s8 stemp; + ssize_t ret = 0; + u8 reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + ret = -EBUSY; + } else { + reg = lis3l02dq_axis_map + [LIS3L02DQ_ACCEL][chan->address]; + ret = lis3l02dq_read_reg_s16(indio_dev, reg, val); + } + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + goto error_ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 9580; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBSCALE: + reg = lis3l02dq_axis_map[LIS3L02DQ_GAIN][chan->address]; + ret = lis3l02dq_spi_read_reg_8(indio_dev, reg, &utemp); + if (ret) + goto error_ret; + /* to match with what previous code does */ + *val = utemp; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_CALIBBIAS: + reg = lis3l02dq_axis_map[LIS3L02DQ_BIAS][chan->address]; + ret = lis3l02dq_spi_read_reg_8(indio_dev, reg, (u8 *)&stemp); + /* to match with what previous code does */ + *val = stemp; + return IIO_VAL_INT; + } +error_ret: + return ret; +} + +static ssize_t lis3l02dq_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + int ret, len = 0; + s8 t; + + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + (u8 *)&t); + if (ret) + return ret; + t &= LIS3L02DQ_DEC_MASK; + switch (t) { + case LIS3L02DQ_REG_CTRL_1_DF_128: + len = sprintf(buf, "280\n"); + break; + case LIS3L02DQ_REG_CTRL_1_DF_64: + len = sprintf(buf, "560\n"); + break; + case LIS3L02DQ_REG_CTRL_1_DF_32: + len = sprintf(buf, "1120\n"); + break; + case LIS3L02DQ_REG_CTRL_1_DF_8: + len = sprintf(buf, "4480\n"); + break; + } + return len; +} + +static ssize_t lis3l02dq_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 long val; + int ret; + u8 t; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &t); + if (ret) + goto error_ret_mutex; + /* Wipe the bits clean */ + t &= ~LIS3L02DQ_DEC_MASK; + switch (val) { + case 280: + t |= LIS3L02DQ_REG_CTRL_1_DF_128; + break; + case 560: + t |= LIS3L02DQ_REG_CTRL_1_DF_64; + break; + case 1120: + t |= LIS3L02DQ_REG_CTRL_1_DF_32; + break; + case 4480: + t |= LIS3L02DQ_REG_CTRL_1_DF_8; + break; + default: + ret = -EINVAL; + goto error_ret_mutex; + } + + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + t); + +error_ret_mutex: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static int lis3l02dq_initial_setup(struct iio_dev *indio_dev) +{ + struct lis3l02dq_state *st = iio_priv(indio_dev); + int ret; + u8 val, valtest; + + st->us->mode = SPI_MODE_3; + + spi_setup(st->us); + + val = LIS3L02DQ_DEFAULT_CTRL1; + /* Write suitable defaults to ctrl1 */ + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + val); + if (ret) { + dev_err(&st->us->dev, "problem with setup control register 1"); + goto err_ret; + } + /* Repeat as sometimes doesn't work first time? */ + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + val); + if (ret) { + dev_err(&st->us->dev, "problem with setup control register 1"); + goto err_ret; + } + + /* Read back to check this has worked acts as loose test of correct + * chip */ + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &valtest); + if (ret || (valtest != val)) { + dev_err(&indio_dev->dev, + "device not playing ball %d %d\n", valtest, val); + ret = -EINVAL; + goto err_ret; + } + + val = LIS3L02DQ_DEFAULT_CTRL2; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + val); + if (ret) { + dev_err(&st->us->dev, "problem with setup control register 2"); + goto err_ret; + } + + val = LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + val); + if (ret) + dev_err(&st->us->dev, "problem with interrupt cfg register"); +err_ret: + + return ret; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + lis3l02dq_read_frequency, + lis3l02dq_write_frequency); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("280 560 1120 4480"); + +static irqreturn_t lis3l02dq_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + u8 t; + + s64 timestamp = iio_get_time_ns(); + + lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_SRC_ADDR, + &t); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + /* Ack and allow for new interrupts */ + lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_ACK_ADDR, + &t); + + return IRQ_HANDLED; +} + +static const struct iio_event_spec lis3l02dq_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), + } +}; + +#define LIS3L02DQ_CHAN(index, mod) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = mod, \ + .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), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ + .event_spec = lis3l02dq_event, \ + .num_event_specs = ARRAY_SIZE(lis3l02dq_event), \ + } + +static const struct iio_chan_spec lis3l02dq_channels[] = { + LIS3L02DQ_CHAN(0, IIO_MOD_X), + LIS3L02DQ_CHAN(1, IIO_MOD_Y), + LIS3L02DQ_CHAN(2, IIO_MOD_Z), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + +static int lis3l02dq_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + u8 val; + int ret; + u8 mask = (1 << (chan->channel2*2 + (dir == IIO_EV_DIR_RISING))); + + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + &val); + if (ret < 0) + return ret; + + return !!(val & mask); +} + +int lis3l02dq_disable_all_events(struct iio_dev *indio_dev) +{ + int ret; + u8 control, val; + + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &control); + + control &= ~LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + control); + if (ret) + goto error_ret; + /* Also for consistency clear the mask */ + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + &val); + if (ret) + goto error_ret; + val &= ~0x3f; + + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + val); + if (ret) + goto error_ret; + + ret = control; +error_ret: + return ret; +} + +static int lis3l02dq_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + int ret = 0; + u8 val, control; + u8 currentlyset; + bool changed = false; + u8 mask = (1 << (chan->channel2*2 + (dir == IIO_EV_DIR_RISING))); + + mutex_lock(&indio_dev->mlock); + /* read current control */ + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &control); + if (ret) + goto error_ret; + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + &val); + if (ret < 0) + goto error_ret; + currentlyset = val & mask; + + if (!currentlyset && state) { + changed = true; + val |= mask; + } else if (currentlyset && !state) { + changed = true; + val &= ~mask; + } + + if (changed) { + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + val); + if (ret) + goto error_ret; + control = val & 0x3f ? + (control | LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT) : + (control & ~LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT); + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + control); + if (ret) + goto error_ret; + } + +error_ret: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static struct attribute *lis3l02dq_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group lis3l02dq_attribute_group = { + .attrs = lis3l02dq_attributes, +}; + +static const struct iio_info lis3l02dq_info = { + .read_raw = &lis3l02dq_read_raw, + .write_raw = &lis3l02dq_write_raw, + .read_event_value = &lis3l02dq_read_thresh, + .write_event_value = &lis3l02dq_write_thresh, + .write_event_config = &lis3l02dq_write_event_config, + .read_event_config = &lis3l02dq_read_event_config, + .driver_module = THIS_MODULE, + .attrs = &lis3l02dq_attribute_group, +}; + +static int lis3l02dq_probe(struct spi_device *spi) +{ + int ret; + struct lis3l02dq_state *st; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st->us = spi; + st->gpio = of_get_gpio(spi->dev.of_node, 0); + mutex_init(&st->buf_lock); + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &lis3l02dq_info; + indio_dev->channels = lis3l02dq_channels; + indio_dev->num_channels = ARRAY_SIZE(lis3l02dq_channels); + + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = lis3l02dq_configure_buffer(indio_dev); + if (ret) + return ret; + + if (spi->irq) { + ret = request_threaded_irq(st->us->irq, + &lis3l02dq_th, + &lis3l02dq_event_handler, + IRQF_TRIGGER_RISING, + "lis3l02dq", + indio_dev); + if (ret) + goto error_unreg_buffer_funcs; + + ret = lis3l02dq_probe_trigger(indio_dev); + if (ret) + goto error_free_interrupt; + } + + /* Get the device into a sane initial state */ + ret = lis3l02dq_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 (spi->irq) + lis3l02dq_remove_trigger(indio_dev); +error_free_interrupt: + if (spi->irq) + free_irq(st->us->irq, indio_dev); +error_unreg_buffer_funcs: + lis3l02dq_unconfigure_buffer(indio_dev); + return ret; +} + +/* Power down the device */ +static int lis3l02dq_stop_device(struct iio_dev *indio_dev) +{ + int ret; + struct lis3l02dq_state *st = iio_priv(indio_dev); + u8 val = 0; + + mutex_lock(&indio_dev->mlock); + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + val); + if (ret) { + dev_err(&st->us->dev, "problem with turning device off: ctrl1"); + goto err_ret; + } + + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + val); + if (ret) + dev_err(&st->us->dev, "problem with turning device off: ctrl2"); +err_ret: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +/* fixme, confirm ordering in this function */ +static int lis3l02dq_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct lis3l02dq_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + lis3l02dq_disable_all_events(indio_dev); + lis3l02dq_stop_device(indio_dev); + + if (spi->irq) + free_irq(st->us->irq, indio_dev); + + lis3l02dq_remove_trigger(indio_dev); + lis3l02dq_unconfigure_buffer(indio_dev); + + return 0; +} + +static struct spi_driver lis3l02dq_driver = { + .driver = { + .name = "lis3l02dq", + .owner = THIS_MODULE, + }, + .probe = lis3l02dq_probe, + .remove = lis3l02dq_remove, +}; +module_spi_driver(lis3l02dq_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("ST LIS3L02DQ Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:lis3l02dq"); diff --git a/kernel/drivers/staging/iio/accel/lis3l02dq_ring.c b/kernel/drivers/staging/iio/accel/lis3l02dq_ring.c new file mode 100644 index 000000000..b892f2cf5 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/lis3l02dq_ring.c @@ -0,0 +1,426 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "lis3l02dq.h" + +/** + * combine_8_to_16() utility function to munge two u8s into u16 + **/ +static inline u16 combine_8_to_16(u8 lower, u8 upper) +{ + u16 _lower = lower; + u16 _upper = upper; + + return _lower | (_upper << 8); +} + +/** + * lis3l02dq_data_rdy_trig_poll() the event handler for the data rdy trig + **/ +irqreturn_t lis3l02dq_data_rdy_trig_poll(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct lis3l02dq_state *st = iio_priv(indio_dev); + + if (st->trigger_on) { + iio_trigger_poll(st->trig); + return IRQ_HANDLED; + } + + return IRQ_WAKE_THREAD; +} + +static const u8 read_all_tx_array[] = { + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_X_L_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_X_H_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Y_L_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Y_H_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Z_L_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Z_H_ADDR), 0, +}; + +/** + * lis3l02dq_read_all() Reads all channels currently selected + * @indio_dev: IIO device state + * @rx_array: (dma capable) receive array, must be at least + * 4*number of channels + **/ +static int lis3l02dq_read_all(struct iio_dev *indio_dev, u8 *rx_array) +{ + struct lis3l02dq_state *st = iio_priv(indio_dev); + struct spi_transfer *xfers; + struct spi_message msg; + int ret, i, j = 0; + + xfers = kcalloc(bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * 2, + sizeof(*xfers), GFP_KERNEL); + if (!xfers) + return -ENOMEM; + + mutex_lock(&st->buf_lock); + + for (i = 0; i < ARRAY_SIZE(read_all_tx_array)/4; i++) + if (test_bit(i, indio_dev->active_scan_mask)) { + /* lower byte */ + xfers[j].tx_buf = st->tx + 2*j; + st->tx[2*j] = read_all_tx_array[i*4]; + st->tx[2*j + 1] = 0; + if (rx_array) + xfers[j].rx_buf = rx_array + j*2; + xfers[j].bits_per_word = 8; + xfers[j].len = 2; + xfers[j].cs_change = 1; + j++; + + /* upper byte */ + xfers[j].tx_buf = st->tx + 2*j; + st->tx[2*j] = read_all_tx_array[i*4 + 2]; + st->tx[2*j + 1] = 0; + if (rx_array) + xfers[j].rx_buf = rx_array + j*2; + xfers[j].bits_per_word = 8; + xfers[j].len = 2; + xfers[j].cs_change = 1; + j++; + } + + /* After these are transmitted, the rx_buff should have + * values in alternate bytes + */ + spi_message_init(&msg); + for (j = 0; j < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * 2; j++) + spi_message_add_tail(&xfers[j], &msg); + + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + kfree(xfers); + + return ret; +} + +static int lis3l02dq_get_buffer_element(struct iio_dev *indio_dev, + u8 *buf) +{ + int ret, i; + u8 *rx_array; + s16 *data = (s16 *)buf; + int scan_count = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + + rx_array = kcalloc(4, scan_count, GFP_KERNEL); + if (!rx_array) + return -ENOMEM; + ret = lis3l02dq_read_all(indio_dev, rx_array); + if (ret < 0) { + kfree(rx_array); + return ret; + } + for (i = 0; i < scan_count; i++) + data[i] = combine_8_to_16(rx_array[i*4+1], + rx_array[i*4+3]); + kfree(rx_array); + + return i*sizeof(data[0]); +} + +static irqreturn_t lis3l02dq_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + int len = 0; + char *data; + + data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!data) + goto done; + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + len = lis3l02dq_get_buffer_element(indio_dev, data); + + iio_push_to_buffers_with_timestamp(indio_dev, data, pf->timestamp); + + kfree(data); +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +/* Caller responsible for locking as necessary. */ +static int +__lis3l02dq_write_data_ready_config(struct iio_dev *indio_dev, bool state) +{ + int ret; + u8 valold; + bool currentlyset; + struct lis3l02dq_state *st = iio_priv(indio_dev); + + /* Get the current event mask register */ + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &valold); + if (ret) + goto error_ret; + /* Find out if data ready is already on */ + currentlyset + = valold & LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + + /* Disable requested */ + if (!state && currentlyset) { + /* Disable the data ready signal */ + valold &= ~LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + + /* The double write is to overcome a hardware bug? */ + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + valold); + if (ret) + goto error_ret; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + valold); + if (ret) + goto error_ret; + st->trigger_on = false; + /* Enable requested */ + } else if (state && !currentlyset) { + /* If not set, enable requested + * first disable all events */ + ret = lis3l02dq_disable_all_events(indio_dev); + if (ret < 0) + goto error_ret; + + valold = ret | + LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + + st->trigger_on = true; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + valold); + if (ret) + goto error_ret; + } + + return 0; +error_ret: + return ret; +} + +/** + * lis3l02dq_data_rdy_trigger_set_state() set datardy interrupt state + * + * If disabling the interrupt also does a final read to ensure it is clear. + * This is only important in some cases where the scan enable elements are + * switched before the buffer is reenabled. + **/ +static int lis3l02dq_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + int ret = 0; + u8 t; + + __lis3l02dq_write_data_ready_config(indio_dev, state); + if (!state) { + /* + * A possible quirk with the handler is currently worked around + * by ensuring outstanding read events are cleared. + */ + ret = lis3l02dq_read_all(indio_dev, NULL); + } + lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_SRC_ADDR, + &t); + return ret; +} + +/** + * lis3l02dq_trig_try_reen() try reenabling irq for data rdy trigger + * @trig: the datardy trigger + */ +static int lis3l02dq_trig_try_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct lis3l02dq_state *st = iio_priv(indio_dev); + int i; + + /* If gpio still high (or high again) + * In theory possible we will need to do this several times */ + for (i = 0; i < 5; i++) + if (gpio_get_value(st->gpio)) + lis3l02dq_read_all(indio_dev, NULL); + else + break; + if (i == 5) + pr_info("Failed to clear the interrupt for lis3l02dq\n"); + + /* irq reenabled so success! */ + return 0; +} + +static const struct iio_trigger_ops lis3l02dq_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &lis3l02dq_data_rdy_trigger_set_state, + .try_reenable = &lis3l02dq_trig_try_reen, +}; + +int lis3l02dq_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct lis3l02dq_state *st = iio_priv(indio_dev); + + st->trig = iio_trigger_alloc("lis3l02dq-dev%d", indio_dev->id); + if (!st->trig) { + ret = -ENOMEM; + goto error_ret; + } + + st->trig->dev.parent = &st->us->dev; + st->trig->ops = &lis3l02dq_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = iio_trigger_register(st->trig); + if (ret) + goto error_free_trig; + + return 0; + +error_free_trig: + iio_trigger_free(st->trig); +error_ret: + return ret; +} + +void lis3l02dq_remove_trigger(struct iio_dev *indio_dev) +{ + struct lis3l02dq_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + iio_trigger_free(st->trig); +} + +void lis3l02dq_unconfigure_buffer(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_kfifo_free(indio_dev->buffer); +} + +static int lis3l02dq_buffer_postenable(struct iio_dev *indio_dev) +{ + /* Disable unwanted channels otherwise the interrupt will not clear */ + u8 t; + int ret; + bool oneenabled = false; + + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &t); + if (ret) + goto error_ret; + + if (test_bit(0, indio_dev->active_scan_mask)) { + t |= LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE; + oneenabled = true; + } else { + t &= ~LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE; + } + if (test_bit(1, indio_dev->active_scan_mask)) { + t |= LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE; + oneenabled = true; + } else { + t &= ~LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE; + } + if (test_bit(2, indio_dev->active_scan_mask)) { + t |= LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE; + oneenabled = true; + } else { + t &= ~LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE; + } + if (!oneenabled) /* what happens in this case is unknown */ + return -EINVAL; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + t); + if (ret) + goto error_ret; + + return iio_triggered_buffer_postenable(indio_dev); +error_ret: + return ret; +} + +/* Turn all channels on again */ +static int lis3l02dq_buffer_predisable(struct iio_dev *indio_dev) +{ + u8 t; + int ret; + + ret = iio_triggered_buffer_predisable(indio_dev); + if (ret) + goto error_ret; + + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &t); + if (ret) + goto error_ret; + t |= LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE | + LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE | + LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE; + + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + t); + +error_ret: + return ret; +} + +static const struct iio_buffer_setup_ops lis3l02dq_buffer_setup_ops = { + .postenable = &lis3l02dq_buffer_postenable, + .predisable = &lis3l02dq_buffer_predisable, +}; + +int lis3l02dq_configure_buffer(struct iio_dev *indio_dev) +{ + int ret; + struct iio_buffer *buffer; + + buffer = iio_kfifo_allocate(); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + buffer->scan_timestamp = true; + indio_dev->setup_ops = &lis3l02dq_buffer_setup_ops; + + /* Functions are NULL as we set handler below */ + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &lis3l02dq_trigger_handler, + 0, + indio_dev, + "lis3l02dq_consumer%d", + indio_dev->id); + + if (!indio_dev->pollfunc) { + ret = -ENOMEM; + 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/kernel/drivers/staging/iio/accel/sca3000.h b/kernel/drivers/staging/iio/accel/sca3000.h new file mode 100644 index 000000000..9c8a9587d --- /dev/null +++ b/kernel/drivers/staging/iio/accel/sca3000.h @@ -0,0 +1,278 @@ +/* + * sca3000.c -- support VTI sca3000 series accelerometers + * via SPI + * + * Copyright (c) 2007 Jonathan Cameron + * + * Partly based upon tle62x0.c + * + * 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. + * + * Initial mode is direct measurement. + * + * Untested things + * + * Temperature reading (the e05 I'm testing with doesn't have a sensor) + * + * Free fall detection mode - supported but untested as I'm not droping my + * dubious wire rig far enough to test it. + * + * Unsupported as yet + * + * Time stamping of data from ring. Various ideas on how to do this but none + * are remotely simple. Suggestions welcome. + * + * Individual enabling disabling of channels going into ring buffer + * + * Overflow handling (this is signaled for all but 8 bit ring buffer mode.) + * + * Motion detector using AND combinations of signals. + * + * Note: Be very careful about not touching an register bytes marked + * as reserved on the data sheet. They really mean it as changing convents of + * some will cause the device to lock up. + * + * Known issues - on rare occasions the interrupts lock up. Not sure why as yet. + * Can probably alleviate this by reading the interrupt register on start, but + * that is really just brushing the problem under the carpet. + */ +#ifndef _SCA3000 +#define _SCA3000 + +#define SCA3000_WRITE_REG(a) (((a) << 2) | 0x02) +#define SCA3000_READ_REG(a) ((a) << 2) + +#define SCA3000_REG_ADDR_REVID 0x00 +#define SCA3000_REVID_MAJOR_MASK 0xf0 +#define SCA3000_REVID_MINOR_MASK 0x0f + +#define SCA3000_REG_ADDR_STATUS 0x02 +#define SCA3000_LOCKED 0x20 +#define SCA3000_EEPROM_CS_ERROR 0x02 +#define SCA3000_SPI_FRAME_ERROR 0x01 + +/* All reads done using register decrement so no need to directly access LSBs */ +#define SCA3000_REG_ADDR_X_MSB 0x05 +#define SCA3000_REG_ADDR_Y_MSB 0x07 +#define SCA3000_REG_ADDR_Z_MSB 0x09 + +#define SCA3000_REG_ADDR_RING_OUT 0x0f + +/* Temp read untested - the e05 doesn't have the sensor */ +#define SCA3000_REG_ADDR_TEMP_MSB 0x13 + +#define SCA3000_REG_ADDR_MODE 0x14 +#define SCA3000_MODE_PROT_MASK 0x28 + +#define SCA3000_RING_BUF_ENABLE 0x80 +#define SCA3000_RING_BUF_8BIT 0x40 +/* + * Free fall detection triggers an interrupt if the acceleration + * is below a threshold for equivalent of 25cm drop + */ +#define SCA3000_FREE_FALL_DETECT 0x10 +#define SCA3000_MEAS_MODE_NORMAL 0x00 +#define SCA3000_MEAS_MODE_OP_1 0x01 +#define SCA3000_MEAS_MODE_OP_2 0x02 + +/* + * In motion detection mode the accelerations are band pass filtered + * (approx 1 - 25Hz) and then a programmable threshold used to trigger + * and interrupt. + */ +#define SCA3000_MEAS_MODE_MOT_DET 0x03 + +#define SCA3000_REG_ADDR_BUF_COUNT 0x15 + +#define SCA3000_REG_ADDR_INT_STATUS 0x16 + +#define SCA3000_INT_STATUS_THREE_QUARTERS 0x80 +#define SCA3000_INT_STATUS_HALF 0x40 + +#define SCA3000_INT_STATUS_FREE_FALL 0x08 +#define SCA3000_INT_STATUS_Y_TRIGGER 0x04 +#define SCA3000_INT_STATUS_X_TRIGGER 0x02 +#define SCA3000_INT_STATUS_Z_TRIGGER 0x01 + +/* Used to allow access to multiplexed registers */ +#define SCA3000_REG_ADDR_CTRL_SEL 0x18 +/* Only available for SCA3000-D03 and SCA3000-D01 */ +#define SCA3000_REG_CTRL_SEL_I2C_DISABLE 0x01 +#define SCA3000_REG_CTRL_SEL_MD_CTRL 0x02 +#define SCA3000_REG_CTRL_SEL_MD_Y_TH 0x03 +#define SCA3000_REG_CTRL_SEL_MD_X_TH 0x04 +#define SCA3000_REG_CTRL_SEL_MD_Z_TH 0x05 +/* + * BE VERY CAREFUL WITH THIS, IF 3 BITS ARE NOT SET the device + * will not function + */ +#define SCA3000_REG_CTRL_SEL_OUT_CTRL 0x0B +#define SCA3000_OUT_CTRL_PROT_MASK 0xE0 +#define SCA3000_OUT_CTRL_BUF_X_EN 0x10 +#define SCA3000_OUT_CTRL_BUF_Y_EN 0x08 +#define SCA3000_OUT_CTRL_BUF_Z_EN 0x04 +#define SCA3000_OUT_CTRL_BUF_DIV_4 0x02 +#define SCA3000_OUT_CTRL_BUF_DIV_2 0x01 + +/* + * Control which motion detector interrupts are on. + * For now only OR combinations are supported. + */ +#define SCA3000_MD_CTRL_PROT_MASK 0xC0 +#define SCA3000_MD_CTRL_OR_Y 0x01 +#define SCA3000_MD_CTRL_OR_X 0x02 +#define SCA3000_MD_CTRL_OR_Z 0x04 +/* Currently unsupported */ +#define SCA3000_MD_CTRL_AND_Y 0x08 +#define SCA3000_MD_CTRL_AND_X 0x10 +#define SAC3000_MD_CTRL_AND_Z 0x20 + +/* + * Some control registers of complex access methods requiring this register to + * be used to remove a lock. + */ +#define SCA3000_REG_ADDR_UNLOCK 0x1e + +#define SCA3000_REG_ADDR_INT_MASK 0x21 +#define SCA3000_INT_MASK_PROT_MASK 0x1C + +#define SCA3000_INT_MASK_RING_THREE_QUARTER 0x80 +#define SCA3000_INT_MASK_RING_HALF 0x40 + +#define SCA3000_INT_MASK_ALL_INTS 0x02 +#define SCA3000_INT_MASK_ACTIVE_HIGH 0x01 +#define SCA3000_INT_MASK_ACTIVE_LOW 0x00 + +/* Values of multiplexed registers (write to ctrl_data after select) */ +#define SCA3000_REG_ADDR_CTRL_DATA 0x22 + +/* + * Measurement modes available on some sca3000 series chips. Code assumes others + * may become available in the future. + * + * Bypass - Bypass the low-pass filter in the signal channel so as to increase + * signal bandwidth. + * + * Narrow - Narrow low-pass filtering of the signal channel and half output + * data rate by decimation. + * + * Wide - Widen low-pass filtering of signal channel to increase bandwidth + */ +#define SCA3000_OP_MODE_BYPASS 0x01 +#define SCA3000_OP_MODE_NARROW 0x02 +#define SCA3000_OP_MODE_WIDE 0x04 +#define SCA3000_MAX_TX 6 +#define SCA3000_MAX_RX 2 + +/** + * struct sca3000_state - device instance state information + * @us: the associated spi device + * @info: chip variant information + * @interrupt_handler_ws: event interrupt handler for all events + * @last_timestamp: the timestamp of the last event + * @mo_det_use_count: reference counter for the motion detection unit + * @lock: lock used to protect elements of sca3000_state + * and the underlying device state. + * @bpse: number of bits per scan element + * @tx: dma-able transmit buffer + * @rx: dma-able receive buffer + **/ +struct sca3000_state { + struct spi_device *us; + const struct sca3000_chip_info *info; + struct work_struct interrupt_handler_ws; + s64 last_timestamp; + int mo_det_use_count; + struct mutex lock; + int bpse; + /* Can these share a cacheline ? */ + u8 rx[2] ____cacheline_aligned; + u8 tx[6] ____cacheline_aligned; +}; + +/** + * struct sca3000_chip_info - model dependent parameters + * @scale: scale * 10^-6 + * @temp_output: some devices have temperature sensors. + * @measurement_mode_freq: normal mode sampling frequency + * @option_mode_1: first optional mode. Not all models have one + * @option_mode_1_freq: option mode 1 sampling frequency + * @option_mode_2: second optional mode. Not all chips have one + * @option_mode_2_freq: option mode 2 sampling frequency + * + * This structure is used to hold information about the functionality of a given + * sca3000 variant. + **/ +struct sca3000_chip_info { + unsigned int scale; + bool temp_output; + int measurement_mode_freq; + int option_mode_1; + int option_mode_1_freq; + int option_mode_2; + int option_mode_2_freq; + int mot_det_mult_xz[6]; + int mot_det_mult_y[7]; +}; + +int sca3000_read_data_short(struct sca3000_state *st, + u8 reg_address_high, + int len); + +/** + * sca3000_write_reg() write a single register + * @address: address of register on chip + * @val: value to be written to register + * + * The main lock must be held. + **/ +int sca3000_write_reg(struct sca3000_state *st, u8 address, u8 val); + +#ifdef CONFIG_IIO_BUFFER +/** + * sca3000_register_ring_funcs() setup the ring state change functions + **/ +void sca3000_register_ring_funcs(struct iio_dev *indio_dev); + +/** + * sca3000_configure_ring() - allocate and configure ring buffer + * @indio_dev: iio-core device whose ring is to be configured + * + * The hardware ring buffer needs far fewer ring buffer functions than + * a software one as a lot of things are handled automatically. + * This function also tells the iio core that our device supports a + * hardware ring buffer mode. + **/ +int sca3000_configure_ring(struct iio_dev *indio_dev); + +/** + * sca3000_unconfigure_ring() - deallocate the ring buffer + * @indio_dev: iio-core device whose ring we are freeing + **/ +void sca3000_unconfigure_ring(struct iio_dev *indio_dev); + +/** + * sca3000_ring_int_process() handles ring related event pushing and escalation + * @val: the event code + **/ +void sca3000_ring_int_process(u8 val, struct iio_buffer *ring); + +#else +static inline void sca3000_register_ring_funcs(struct iio_dev *indio_dev) +{ +} + +static inline +int sca3000_register_ring_access_and_init(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void sca3000_ring_int_process(u8 val, void *ring) +{ +} + +#endif +#endif /* _SCA3000 */ diff --git a/kernel/drivers/staging/iio/accel/sca3000_core.c b/kernel/drivers/staging/iio/accel/sca3000_core.c new file mode 100644 index 000000000..b614f272b --- /dev/null +++ b/kernel/drivers/staging/iio/accel/sca3000_core.c @@ -0,0 +1,1209 @@ +/* + * sca3000_core.c -- support VTI sca3000 series accelerometers via SPI + * + * 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. + * + * Copyright (c) 2009 Jonathan Cameron + * + * See industrialio/accels/sca3000.h for comments. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sca3000.h" + +enum sca3000_variant { + d01, + e02, + e04, + e05, +}; + +/* + * Note where option modes are not defined, the chip simply does not + * support any. + * Other chips in the sca3000 series use i2c and are not included here. + * + * Some of these devices are only listed in the family data sheet and + * do not actually appear to be available. + */ +static const struct sca3000_chip_info sca3000_spi_chip_info_tbl[] = { + [d01] = { + .scale = 7357, + .temp_output = true, + .measurement_mode_freq = 250, + .option_mode_1 = SCA3000_OP_MODE_BYPASS, + .option_mode_1_freq = 250, + .mot_det_mult_xz = {50, 100, 200, 350, 650, 1300}, + .mot_det_mult_y = {50, 100, 150, 250, 450, 850, 1750}, + }, + [e02] = { + .scale = 9810, + .measurement_mode_freq = 125, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 63, + .mot_det_mult_xz = {100, 150, 300, 550, 1050, 2050}, + .mot_det_mult_y = {50, 100, 200, 350, 700, 1350, 2700}, + }, + [e04] = { + .scale = 19620, + .measurement_mode_freq = 100, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 50, + .option_mode_2 = SCA3000_OP_MODE_WIDE, + .option_mode_2_freq = 400, + .mot_det_mult_xz = {200, 300, 600, 1100, 2100, 4100}, + .mot_det_mult_y = {100, 200, 400, 7000, 1400, 2700, 54000}, + }, + [e05] = { + .scale = 61313, + .measurement_mode_freq = 200, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 50, + .option_mode_2 = SCA3000_OP_MODE_WIDE, + .option_mode_2_freq = 400, + .mot_det_mult_xz = {600, 900, 1700, 3200, 6100, 11900}, + .mot_det_mult_y = {300, 600, 1200, 2000, 4100, 7800, 15600}, + }, +}; + +int sca3000_write_reg(struct sca3000_state *st, u8 address, u8 val) +{ + st->tx[0] = SCA3000_WRITE_REG(address); + st->tx[1] = val; + return spi_write(st->us, st->tx, 2); +} + +int sca3000_read_data_short(struct sca3000_state *st, + uint8_t reg_address_high, + int len) +{ + struct spi_transfer xfer[2] = { + { + .len = 1, + .tx_buf = st->tx, + }, { + .len = len, + .rx_buf = st->rx, + } + }; + st->tx[0] = SCA3000_READ_REG(reg_address_high); + + return spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer)); +} + +/** + * sca3000_reg_lock_on() test if the ctrl register lock is on + * + * Lock must be held. + **/ +static int sca3000_reg_lock_on(struct sca3000_state *st) +{ + int ret; + + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_STATUS, 1); + if (ret < 0) + return ret; + + return !(st->rx[0] & SCA3000_LOCKED); +} + +/** + * __sca3000_unlock_reg_lock() unlock the control registers + * + * Note the device does not appear to support doing this in a single transfer. + * This should only ever be used as part of ctrl reg read. + * Lock must be held before calling this + **/ +static int __sca3000_unlock_reg_lock(struct sca3000_state *st) +{ + struct spi_transfer xfer[3] = { + { + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + }, { + .len = 2, + .cs_change = 1, + .tx_buf = st->tx + 2, + }, { + .len = 2, + .tx_buf = st->tx + 4, + }, + }; + st->tx[0] = SCA3000_WRITE_REG(SCA3000_REG_ADDR_UNLOCK); + st->tx[1] = 0x00; + st->tx[2] = SCA3000_WRITE_REG(SCA3000_REG_ADDR_UNLOCK); + st->tx[3] = 0x50; + st->tx[4] = SCA3000_WRITE_REG(SCA3000_REG_ADDR_UNLOCK); + st->tx[5] = 0xA0; + + return spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer)); +} + +/** + * sca3000_write_ctrl_reg() write to a lock protect ctrl register + * @sel: selects which registers we wish to write to + * @val: the value to be written + * + * Certain control registers are protected against overwriting by the lock + * register and use a shared write address. This function allows writing of + * these registers. + * Lock must be held. + **/ +static int sca3000_write_ctrl_reg(struct sca3000_state *st, + uint8_t sel, + uint8_t val) +{ + + int ret; + + ret = sca3000_reg_lock_on(st); + if (ret < 0) + goto error_ret; + if (ret) { + ret = __sca3000_unlock_reg_lock(st); + if (ret) + goto error_ret; + } + + /* Set the control select register */ + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_CTRL_SEL, sel); + if (ret) + goto error_ret; + + /* Write the actual value into the register */ + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_CTRL_DATA, val); + +error_ret: + return ret; +} + +/** + * sca3000_read_ctrl_reg() read from lock protected control register. + * + * Lock must be held. + **/ +static int sca3000_read_ctrl_reg(struct sca3000_state *st, + u8 ctrl_reg) +{ + int ret; + + ret = sca3000_reg_lock_on(st); + if (ret < 0) + goto error_ret; + if (ret) { + ret = __sca3000_unlock_reg_lock(st); + if (ret) + goto error_ret; + } + /* Set the control select register */ + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_CTRL_SEL, ctrl_reg); + if (ret) + goto error_ret; + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_CTRL_DATA, 1); + if (ret) + goto error_ret; + else + return st->rx[0]; +error_ret: + return ret; +} + +/** + * sca3000_show_rev() - sysfs interface to read the chip revision number + **/ +static ssize_t sca3000_show_rev(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len = 0, ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_REVID, 1); + if (ret < 0) + goto error_ret; + len += sprintf(buf + len, + "major=%d, minor=%d\n", + st->rx[0] & SCA3000_REVID_MAJOR_MASK, + st->rx[0] & SCA3000_REVID_MINOR_MASK); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_show_available_measurement_modes() display available modes + * + * This is all read from chip specific data in the driver. Not all + * of the sca3000 series support modes other than normal. + **/ +static ssize_t +sca3000_show_available_measurement_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int len = 0; + + len += sprintf(buf + len, "0 - normal mode"); + switch (st->info->option_mode_1) { + case SCA3000_OP_MODE_NARROW: + len += sprintf(buf + len, ", 1 - narrow mode"); + break; + case SCA3000_OP_MODE_BYPASS: + len += sprintf(buf + len, ", 1 - bypass mode"); + break; + } + switch (st->info->option_mode_2) { + case SCA3000_OP_MODE_WIDE: + len += sprintf(buf + len, ", 2 - wide mode"); + break; + } + /* always supported */ + len += sprintf(buf + len, " 3 - motion detection\n"); + + return len; +} + +/** + * sca3000_show_measurement_mode() sysfs read of current mode + **/ +static ssize_t +sca3000_show_measurement_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int len = 0, ret; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + /* mask bottom 2 bits - only ones that are relevant */ + st->rx[0] &= 0x03; + switch (st->rx[0]) { + case SCA3000_MEAS_MODE_NORMAL: + len += sprintf(buf + len, "0 - normal mode\n"); + break; + case SCA3000_MEAS_MODE_MOT_DET: + len += sprintf(buf + len, "3 - motion detection\n"); + break; + case SCA3000_MEAS_MODE_OP_1: + switch (st->info->option_mode_1) { + case SCA3000_OP_MODE_NARROW: + len += sprintf(buf + len, "1 - narrow mode\n"); + break; + case SCA3000_OP_MODE_BYPASS: + len += sprintf(buf + len, "1 - bypass mode\n"); + break; + } + break; + case SCA3000_MEAS_MODE_OP_2: + switch (st->info->option_mode_2) { + case SCA3000_OP_MODE_WIDE: + len += sprintf(buf + len, "2 - wide mode\n"); + break; + } + break; + } + +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_store_measurement_mode() set the current mode + **/ +static ssize_t +sca3000_store_measurement_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + u8 mask = 0x03; + u8 val; + + mutex_lock(&st->lock); + ret = kstrtou8(buf, 10, &val); + if (ret) + goto error_ret; + if (val > 3) { + ret = -EINVAL; + goto error_ret; + } + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + st->rx[0] &= ~mask; + st->rx[0] |= (val & mask); + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, st->rx[0]); + if (ret) + goto error_ret; + mutex_unlock(&st->lock); + + return len; + +error_ret: + mutex_unlock(&st->lock); + + return ret; +} + + +/* + * Not even vaguely standard attributes so defined here rather than + * in the relevant IIO core headers + */ +static IIO_DEVICE_ATTR(measurement_mode_available, S_IRUGO, + sca3000_show_available_measurement_modes, + NULL, 0); + +static IIO_DEVICE_ATTR(measurement_mode, S_IRUGO | S_IWUSR, + sca3000_show_measurement_mode, + sca3000_store_measurement_mode, + 0); + +/* More standard attributes */ + +static IIO_DEVICE_ATTR(revision, S_IRUGO, sca3000_show_rev, NULL, 0); + +static const struct iio_event_spec sca3000_event = { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; + +#define SCA3000_CHAN(index, mod) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = mod, \ + .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 = 's', \ + .realbits = 11, \ + .storagebits = 16, \ + .shift = 5, \ + }, \ + .event_spec = &sca3000_event, \ + .num_event_specs = 1, \ + } + +static const struct iio_chan_spec sca3000_channels[] = { + SCA3000_CHAN(0, IIO_MOD_X), + SCA3000_CHAN(1, IIO_MOD_Y), + SCA3000_CHAN(2, IIO_MOD_Z), +}; + +static const struct iio_chan_spec sca3000_channels_with_temp[] = { + SCA3000_CHAN(0, IIO_MOD_X), + SCA3000_CHAN(1, IIO_MOD_Y), + SCA3000_CHAN(2, IIO_MOD_Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + /* No buffer support */ + .scan_index = -1, + }, +}; + +static u8 sca3000_addresses[3][3] = { + [0] = {SCA3000_REG_ADDR_X_MSB, SCA3000_REG_CTRL_SEL_MD_X_TH, + SCA3000_MD_CTRL_OR_X}, + [1] = {SCA3000_REG_ADDR_Y_MSB, SCA3000_REG_CTRL_SEL_MD_Y_TH, + SCA3000_MD_CTRL_OR_Y}, + [2] = {SCA3000_REG_ADDR_Z_MSB, SCA3000_REG_CTRL_SEL_MD_Z_TH, + SCA3000_MD_CTRL_OR_Z}, +}; + +static int sca3000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + u8 address; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + if (chan->type == IIO_ACCEL) { + if (st->mo_det_use_count) { + mutex_unlock(&st->lock); + return -EBUSY; + } + address = sca3000_addresses[chan->address][0]; + ret = sca3000_read_data_short(st, address, 2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + *val = (be16_to_cpup((__be16 *)st->rx) >> 3) & 0x1FFF; + *val = ((*val) << (sizeof(*val)*8 - 13)) >> + (sizeof(*val)*8 - 13); + } else { + /* get the temperature when available */ + ret = sca3000_read_data_short(st, + SCA3000_REG_ADDR_TEMP_MSB, 2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + *val = ((st->rx[0] & 0x3F) << 3) | + ((st->rx[1] & 0xE0) >> 5); + } + mutex_unlock(&st->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (chan->type == IIO_ACCEL) + *val2 = st->info->scale; + else /* temperature */ + *val2 = 555556; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = -214; + *val2 = 600000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +/** + * sca3000_read_av_freq() sysfs function to get available frequencies + * + * The later modes are only relevant to the ring buffer - and depend on current + * mode. Note that data sheet gives rather wide tolerances for these so integer + * division will give good enough answer and not all chips have them specified + * at all. + **/ +static ssize_t sca3000_read_av_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int len = 0, ret, val; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + val = st->rx[0]; + mutex_unlock(&st->lock); + if (ret) + goto error_ret; + + switch (val & 0x03) { + case SCA3000_MEAS_MODE_NORMAL: + len += sprintf(buf + len, "%d %d %d\n", + st->info->measurement_mode_freq, + st->info->measurement_mode_freq/2, + st->info->measurement_mode_freq/4); + break; + case SCA3000_MEAS_MODE_OP_1: + len += sprintf(buf + len, "%d %d %d\n", + st->info->option_mode_1_freq, + st->info->option_mode_1_freq/2, + st->info->option_mode_1_freq/4); + break; + case SCA3000_MEAS_MODE_OP_2: + len += sprintf(buf + len, "%d %d %d\n", + st->info->option_mode_2_freq, + st->info->option_mode_2_freq/2, + st->info->option_mode_2_freq/4); + break; + } + return len; +error_ret: + return ret; +} +/** + * __sca3000_get_base_freq() obtain mode specific base frequency + * + * lock must be held + **/ +static inline int __sca3000_get_base_freq(struct sca3000_state *st, + const struct sca3000_chip_info *info, + int *base_freq) +{ + int ret; + + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + switch (0x03 & st->rx[0]) { + case SCA3000_MEAS_MODE_NORMAL: + *base_freq = info->measurement_mode_freq; + break; + case SCA3000_MEAS_MODE_OP_1: + *base_freq = info->option_mode_1_freq; + break; + case SCA3000_MEAS_MODE_OP_2: + *base_freq = info->option_mode_2_freq; + break; + } +error_ret: + return ret; +} + +/** + * sca3000_read_frequency() sysfs interface to get the current frequency + **/ +static ssize_t sca3000_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int ret, len = 0, base_freq = 0, val; + + mutex_lock(&st->lock); + ret = __sca3000_get_base_freq(st, st->info, &base_freq); + if (ret) + goto error_ret_mut; + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL); + mutex_unlock(&st->lock); + if (ret) + goto error_ret; + val = ret; + if (base_freq > 0) + switch (val & 0x03) { + case 0x00: + case 0x03: + len = sprintf(buf, "%d\n", base_freq); + break; + case 0x01: + len = sprintf(buf, "%d\n", base_freq/2); + break; + case 0x02: + len = sprintf(buf, "%d\n", base_freq/4); + break; + } + + return len; +error_ret_mut: + mutex_unlock(&st->lock); +error_ret: + return ret; +} + +/** + * sca3000_set_frequency() sysfs interface to set the current frequency + **/ +static ssize_t sca3000_set_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 sca3000_state *st = iio_priv(indio_dev); + int ret, base_freq = 0; + int ctrlval; + int val; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&st->lock); + /* What mode are we in? */ + ret = __sca3000_get_base_freq(st, st->info, &base_freq); + if (ret) + goto error_free_lock; + + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL); + if (ret < 0) + goto error_free_lock; + ctrlval = ret; + /* clear the bits */ + ctrlval &= ~0x03; + + if (val == base_freq/2) { + ctrlval |= SCA3000_OUT_CTRL_BUF_DIV_2; + } else if (val == base_freq/4) { + ctrlval |= SCA3000_OUT_CTRL_BUF_DIV_4; + } else if (val != base_freq) { + ret = -EINVAL; + goto error_free_lock; + } + ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL, + ctrlval); +error_free_lock: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/* + * Should only really be registered if ring buffer support is compiled in. + * Does no harm however and doing it right would add a fair bit of complexity + */ +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(sca3000_read_av_freq); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + sca3000_read_frequency, + sca3000_set_frequency); + +/** + * sca3000_read_thresh() - query of a threshold + **/ +static int sca3000_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + int ret, i; + struct sca3000_state *st = iio_priv(indio_dev); + int num = chan->channel2; + + mutex_lock(&st->lock); + ret = sca3000_read_ctrl_reg(st, sca3000_addresses[num][1]); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + *val = 0; + if (num == 1) + for_each_set_bit(i, (unsigned long *)&ret, + ARRAY_SIZE(st->info->mot_det_mult_y)) + *val += st->info->mot_det_mult_y[i]; + else + for_each_set_bit(i, (unsigned long *)&ret, + ARRAY_SIZE(st->info->mot_det_mult_xz)) + *val += st->info->mot_det_mult_xz[i]; + + return IIO_VAL_INT; +} + +/** + * sca3000_write_thresh() control of threshold + **/ +static int sca3000_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int num = chan->channel2; + int ret; + int i; + u8 nonlinear = 0; + + if (num == 1) { + i = ARRAY_SIZE(st->info->mot_det_mult_y); + while (i > 0) + if (val >= st->info->mot_det_mult_y[--i]) { + nonlinear |= (1 << i); + val -= st->info->mot_det_mult_y[i]; + } + } else { + i = ARRAY_SIZE(st->info->mot_det_mult_xz); + while (i > 0) + if (val >= st->info->mot_det_mult_xz[--i]) { + nonlinear |= (1 << i); + val -= st->info->mot_det_mult_xz[i]; + } + } + + mutex_lock(&st->lock); + ret = sca3000_write_ctrl_reg(st, sca3000_addresses[num][1], nonlinear); + mutex_unlock(&st->lock); + + return ret; +} + +static struct attribute *sca3000_attributes[] = { + &iio_dev_attr_revision.dev_attr.attr, + &iio_dev_attr_measurement_mode_available.dev_attr.attr, + &iio_dev_attr_measurement_mode.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL, +}; + +static const struct attribute_group sca3000_attribute_group = { + .attrs = sca3000_attributes, +}; + +/** + * sca3000_event_handler() - handling ring and non ring events + * + * Ring related interrupt handler. Depending on event, push to + * the ring buffer event chrdev or the event one. + * + * This function is complicated by the fact that the devices can signify ring + * and non ring events via the same interrupt line and they can only + * be distinguished via a read of the relevant status register. + **/ +static irqreturn_t sca3000_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sca3000_state *st = iio_priv(indio_dev); + int ret, val; + s64 last_timestamp = iio_get_time_ns(); + + /* + * Could lead if badly timed to an extra read of status reg, + * but ensures no interrupt is missed. + */ + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_STATUS, 1); + val = st->rx[0]; + mutex_unlock(&st->lock); + if (ret) + goto done; + + sca3000_ring_int_process(val, indio_dev->buffer); + + if (val & SCA3000_INT_STATUS_FREE_FALL) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + last_timestamp); + + if (val & SCA3000_INT_STATUS_Y_TRIGGER) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + last_timestamp); + + if (val & SCA3000_INT_STATUS_X_TRIGGER) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + last_timestamp); + + if (val & SCA3000_INT_STATUS_Z_TRIGGER) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + last_timestamp); + +done: + return IRQ_HANDLED; +} + +/** + * sca3000_read_event_config() what events are enabled + **/ +static int sca3000_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + u8 protect_mask = 0x03; + int num = chan->channel2; + + /* read current value of mode register */ + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + + if ((st->rx[0] & protect_mask) != SCA3000_MEAS_MODE_MOT_DET) + ret = 0; + else { + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL); + if (ret < 0) + goto error_ret; + /* only supporting logical or's for now */ + ret = !!(ret & sca3000_addresses[num][2]); + } +error_ret: + mutex_unlock(&st->lock); + + return ret; +} +/** + * sca3000_query_free_fall_mode() is free fall mode enabled + **/ +static ssize_t sca3000_query_free_fall_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int val; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + val = st->rx[0]; + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + return sprintf(buf, "%d\n", !!(val & SCA3000_FREE_FALL_DETECT)); +} + +/** + * sca3000_set_free_fall_mode() simple on off control for free fall int + * + * In these chips the free fall detector should send an interrupt if + * the device falls more than 25cm. This has not been tested due + * to fragile wiring. + **/ +static ssize_t sca3000_set_free_fall_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + u8 val; + int ret; + u8 protect_mask = SCA3000_FREE_FALL_DETECT; + + mutex_lock(&st->lock); + ret = kstrtou8(buf, 10, &val); + if (ret) + goto error_ret; + + /* read current value of mode register */ + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + + /* if off and should be on */ + if (val && !(st->rx[0] & protect_mask)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (st->rx[0] | SCA3000_FREE_FALL_DETECT)); + /* if on and should be off */ + else if (!val && (st->rx[0] & protect_mask)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (st->rx[0] & ~protect_mask)); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_write_event_config() simple on off control for motion detector + * + * This is a per axis control, but enabling any will result in the + * motion detector unit being enabled. + * N.B. enabling motion detector stops normal data acquisition. + * There is a complexity in knowing which mode to return to when + * this mode is disabled. Currently normal mode is assumed. + **/ +static int sca3000_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret, ctrlval; + u8 protect_mask = 0x03; + int num = chan->channel2; + + mutex_lock(&st->lock); + /* + * First read the motion detector config to find out if + * this axis is on + */ + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL); + if (ret < 0) + goto exit_point; + ctrlval = ret; + /* if off and should be on */ + if (state && !(ctrlval & sca3000_addresses[num][2])) { + ret = sca3000_write_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + ctrlval | + sca3000_addresses[num][2]); + if (ret) + goto exit_point; + st->mo_det_use_count++; + } else if (!state && (ctrlval & sca3000_addresses[num][2])) { + ret = sca3000_write_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + ctrlval & + ~(sca3000_addresses[num][2])); + if (ret) + goto exit_point; + st->mo_det_use_count--; + } + + /* read current value of mode register */ + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto exit_point; + /* if off and should be on */ + if ((st->mo_det_use_count) + && ((st->rx[0] & protect_mask) != SCA3000_MEAS_MODE_MOT_DET)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (st->rx[0] & ~protect_mask) + | SCA3000_MEAS_MODE_MOT_DET); + /* if on and should be off */ + else if (!(st->mo_det_use_count) + && ((st->rx[0] & protect_mask) == SCA3000_MEAS_MODE_MOT_DET)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (st->rx[0] & ~protect_mask)); +exit_point: + mutex_unlock(&st->lock); + + return ret; +} + +/* Free fall detector related event attribute */ +static IIO_DEVICE_ATTR_NAMED(accel_xayaz_mag_falling_en, + in_accel_x&y&z_mag_falling_en, + S_IRUGO | S_IWUSR, + sca3000_query_free_fall_mode, + sca3000_set_free_fall_mode, + 0); + +static IIO_CONST_ATTR_NAMED(accel_xayaz_mag_falling_period, + in_accel_x&y&z_mag_falling_period, + "0.226"); + +static struct attribute *sca3000_event_attributes[] = { + &iio_dev_attr_accel_xayaz_mag_falling_en.dev_attr.attr, + &iio_const_attr_accel_xayaz_mag_falling_period.dev_attr.attr, + NULL, +}; + +static struct attribute_group sca3000_event_attribute_group = { + .attrs = sca3000_event_attributes, + .name = "events", +}; + +/** + * sca3000_clean_setup() get the device into a predictable state + * + * Devices use flash memory to store many of the register values + * and hence can come up in somewhat unpredictable states. + * Hence reset everything on driver load. + **/ +static int sca3000_clean_setup(struct sca3000_state *st) +{ + int ret; + + mutex_lock(&st->lock); + /* Ensure all interrupts have been acknowledged */ + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_STATUS, 1); + if (ret) + goto error_ret; + + /* Turn off all motion detection channels */ + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL); + if (ret < 0) + goto error_ret; + ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL, + ret & SCA3000_MD_CTRL_PROT_MASK); + if (ret) + goto error_ret; + + /* Disable ring buffer */ + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL); + ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL, + (ret & SCA3000_OUT_CTRL_PROT_MASK) + | SCA3000_OUT_CTRL_BUF_X_EN + | SCA3000_OUT_CTRL_BUF_Y_EN + | SCA3000_OUT_CTRL_BUF_Z_EN + | SCA3000_OUT_CTRL_BUF_DIV_4); + if (ret) + goto error_ret; + /* Enable interrupts, relevant to mode and set up as active low */ + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_MASK, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_INT_MASK, + (ret & SCA3000_INT_MASK_PROT_MASK) + | SCA3000_INT_MASK_ACTIVE_LOW); + if (ret) + goto error_ret; + /* + * Select normal measurement mode, free fall off, ring off + * Ring in 12 bit mode - it is fine to overwrite reserved bits 3,5 + * as that occurs in one of the example on the datasheet + */ + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (st->rx[0] & SCA3000_MODE_PROT_MASK)); + st->bpse = 11; + +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +static const struct iio_info sca3000_info = { + .attrs = &sca3000_attribute_group, + .read_raw = &sca3000_read_raw, + .event_attrs = &sca3000_event_attribute_group, + .read_event_value = &sca3000_read_thresh, + .write_event_value = &sca3000_write_thresh, + .read_event_config = &sca3000_read_event_config, + .write_event_config = &sca3000_write_event_config, + .driver_module = THIS_MODULE, +}; + +static int sca3000_probe(struct spi_device *spi) +{ + int ret; + struct sca3000_state *st; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->us = spi; + mutex_init(&st->lock); + st->info = &sca3000_spi_chip_info_tbl[spi_get_device_id(spi) + ->driver_data]; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &sca3000_info; + if (st->info->temp_output) { + indio_dev->channels = sca3000_channels_with_temp; + indio_dev->num_channels = + ARRAY_SIZE(sca3000_channels_with_temp); + } else { + indio_dev->channels = sca3000_channels; + indio_dev->num_channels = ARRAY_SIZE(sca3000_channels); + } + indio_dev->modes = INDIO_DIRECT_MODE; + + sca3000_configure_ring(indio_dev); + ret = iio_device_register(indio_dev); + if (ret < 0) + return ret; + + if (spi->irq) { + ret = request_threaded_irq(spi->irq, + NULL, + &sca3000_event_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "sca3000", + indio_dev); + if (ret) + goto error_unregister_dev; + } + sca3000_register_ring_funcs(indio_dev); + ret = sca3000_clean_setup(st); + if (ret) + goto error_free_irq; + return 0; + +error_free_irq: + if (spi->irq) + free_irq(spi->irq, indio_dev); +error_unregister_dev: + iio_device_unregister(indio_dev); + return ret; +} + +static int sca3000_stop_all_interrupts(struct sca3000_state *st) +{ + int ret; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_MASK, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_INT_MASK, + (st->rx[0] & + ~(SCA3000_INT_MASK_RING_THREE_QUARTER | + SCA3000_INT_MASK_RING_HALF | + SCA3000_INT_MASK_ALL_INTS))); +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +static int sca3000_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct sca3000_state *st = iio_priv(indio_dev); + + /* Must ensure no interrupts can be generated after this! */ + sca3000_stop_all_interrupts(st); + if (spi->irq) + free_irq(spi->irq, indio_dev); + iio_device_unregister(indio_dev); + sca3000_unconfigure_ring(indio_dev); + + return 0; +} + +static const struct spi_device_id sca3000_id[] = { + {"sca3000_d01", d01}, + {"sca3000_e02", e02}, + {"sca3000_e04", e04}, + {"sca3000_e05", e05}, + {} +}; +MODULE_DEVICE_TABLE(spi, sca3000_id); + +static struct spi_driver sca3000_driver = { + .driver = { + .name = "sca3000", + .owner = THIS_MODULE, + }, + .probe = sca3000_probe, + .remove = sca3000_remove, + .id_table = sca3000_id, +}; +module_spi_driver(sca3000_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("VTI SCA3000 Series Accelerometers SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/accel/sca3000_ring.c b/kernel/drivers/staging/iio/accel/sca3000_ring.c new file mode 100644 index 000000000..8589eade1 --- /dev/null +++ b/kernel/drivers/staging/iio/accel/sca3000_ring.c @@ -0,0 +1,348 @@ +/* + * sca3000_ring.c -- support VTI sca3000 series accelerometers via SPI + * + * 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. + * + * Copyright (c) 2009 Jonathan Cameron + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "../ring_hw.h" +#include "sca3000.h" + +/* RFC / future work + * + * The internal ring buffer doesn't actually change what it holds depending + * on which signals are enabled etc, merely whether you can read them. + * As such the scan mode selection is somewhat different than for a software + * ring buffer and changing it actually covers any data already in the buffer. + * Currently scan elements aren't configured so it doesn't matter. + */ + +static int sca3000_read_data(struct sca3000_state *st, + uint8_t reg_address_high, + u8 **rx_p, + int len) +{ + int ret; + struct spi_transfer xfer[2] = { + { + .len = 1, + .tx_buf = st->tx, + }, { + .len = len, + } + }; + *rx_p = kmalloc(len, GFP_KERNEL); + if (*rx_p == NULL) { + ret = -ENOMEM; + goto error_ret; + } + xfer[1].rx_buf = *rx_p; + st->tx[0] = SCA3000_READ_REG(reg_address_high); + ret = spi_sync_transfer(st->us, xfer, ARRAY_SIZE(xfer)); + if (ret) { + dev_err(get_device(&st->us->dev), "problem reading register"); + goto error_free_rx; + } + + return 0; +error_free_rx: + kfree(*rx_p); +error_ret: + return ret; +} + +/** + * sca3000_read_first_n_hw_rb() - main ring access, pulls data from ring + * @r: the ring + * @count: number of samples to try and pull + * @data: output the actual samples pulled from the hw ring + * + * Currently does not provide timestamps. As the hardware doesn't add them they + * can only be inferred approximately from ring buffer events such as 50% full + * and knowledge of when buffer was last emptied. This is left to userspace. + **/ +static int sca3000_read_first_n_hw_rb(struct iio_buffer *r, + size_t count, char __user *buf) +{ + struct iio_hw_buffer *hw_ring = iio_to_hw_buf(r); + struct iio_dev *indio_dev = hw_ring->private; + struct sca3000_state *st = iio_priv(indio_dev); + u8 *rx; + int ret, i, num_available, num_read = 0; + int bytes_per_sample = 1; + + if (st->bpse == 11) + bytes_per_sample = 2; + + mutex_lock(&st->lock); + if (count % bytes_per_sample) { + ret = -EINVAL; + goto error_ret; + } + + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_BUF_COUNT, 1); + if (ret) + goto error_ret; + else + num_available = st->rx[0]; + /* + * num_available is the total number of samples available + * i.e. number of time points * number of channels. + */ + if (count > num_available * bytes_per_sample) + num_read = num_available*bytes_per_sample; + else + num_read = count; + + ret = sca3000_read_data(st, + SCA3000_REG_ADDR_RING_OUT, + &rx, num_read); + if (ret) + goto error_ret; + + for (i = 0; i < num_read; i++) + *(((u16 *)rx) + i) = be16_to_cpup((__be16 *)rx + i); + + if (copy_to_user(buf, rx, num_read)) + ret = -EFAULT; + kfree(rx); + r->stufftoread = 0; +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : num_read; +} + +static size_t sca3000_ring_buf_data_available(struct iio_buffer *r) +{ + return r->stufftoread ? r->watermark : 0; +} + +/** + * sca3000_query_ring_int() is the hardware ring status interrupt enabled + **/ +static ssize_t sca3000_query_ring_int(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret, val; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_MASK, 1); + val = st->rx[0]; + mutex_unlock(&st->lock); + if (ret) + return ret; + + return sprintf(buf, "%d\n", !!(val & this_attr->address)); +} + +/** + * sca3000_set_ring_int() set state of ring status interrupt + **/ +static ssize_t sca3000_set_ring_int(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + u8 val; + int ret; + + mutex_lock(&st->lock); + ret = kstrtou8(buf, 10, &val); + if (ret) + goto error_ret; + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_MASK, 1); + if (ret) + goto error_ret; + if (val) + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_INT_MASK, + st->rx[0] | this_attr->address); + else + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_INT_MASK, + st->rx[0] & ~this_attr->address); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(50_percent, S_IRUGO | S_IWUSR, + sca3000_query_ring_int, + sca3000_set_ring_int, + SCA3000_INT_MASK_RING_HALF); + +static IIO_DEVICE_ATTR(75_percent, S_IRUGO | S_IWUSR, + sca3000_query_ring_int, + sca3000_set_ring_int, + SCA3000_INT_MASK_RING_THREE_QUARTER); + +static ssize_t sca3000_show_buffer_scale(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sca3000_state *st = iio_priv(indio_dev); + + return sprintf(buf, "0.%06d\n", 4*st->info->scale); +} + +static IIO_DEVICE_ATTR(in_accel_scale, + S_IRUGO, + sca3000_show_buffer_scale, + NULL, + 0); + +/* + * Ring buffer attributes + * This device is a bit unusual in that the sampling frequency and bpse + * only apply to the ring buffer. At all times full rate and accuracy + * is available via direct reading from registers. + */ +static const struct attribute *sca3000_ring_attributes[] = { + &iio_dev_attr_50_percent.dev_attr.attr, + &iio_dev_attr_75_percent.dev_attr.attr, + &iio_dev_attr_in_accel_scale.dev_attr.attr, + NULL, +}; + +static struct iio_buffer *sca3000_rb_allocate(struct iio_dev *indio_dev) +{ + struct iio_buffer *buf; + struct iio_hw_buffer *ring; + + ring = kzalloc(sizeof(*ring), GFP_KERNEL); + if (!ring) + return NULL; + + ring->private = indio_dev; + buf = &ring->buf; + buf->stufftoread = 0; + buf->length = 64; + buf->attrs = sca3000_ring_attributes; + iio_buffer_init(buf); + + return buf; +} + +static void sca3000_ring_release(struct iio_buffer *r) +{ + kfree(iio_to_hw_buf(r)); +} + +static const struct iio_buffer_access_funcs sca3000_ring_access_funcs = { + .read_first_n = &sca3000_read_first_n_hw_rb, + .data_available = sca3000_ring_buf_data_available, + .release = sca3000_ring_release, +}; + +int sca3000_configure_ring(struct iio_dev *indio_dev) +{ + struct iio_buffer *buffer; + + buffer = sca3000_rb_allocate(indio_dev); + if (buffer == NULL) + return -ENOMEM; + indio_dev->modes |= INDIO_BUFFER_HARDWARE; + + indio_dev->buffer->access = &sca3000_ring_access_funcs; + + iio_device_attach_buffer(indio_dev, buffer); + + return 0; +} + +void sca3000_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_buffer_put(indio_dev->buffer); +} + +static inline +int __sca3000_hw_ring_state_set(struct iio_dev *indio_dev, bool state) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + if (state) { + dev_info(&indio_dev->dev, "supposedly enabling ring buffer\n"); + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_MODE, + (st->rx[0] | SCA3000_RING_BUF_ENABLE)); + } else + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_MODE, + (st->rx[0] & ~SCA3000_RING_BUF_ENABLE)); +error_ret: + mutex_unlock(&st->lock); + + return ret; +} +/** + * sca3000_hw_ring_preenable() hw ring buffer preenable function + * + * Very simple enable function as the chip will allows normal reads + * during ring buffer operation so as long as it is indeed running + * before we notify the core, the precise ordering does not matter. + **/ +static int sca3000_hw_ring_preenable(struct iio_dev *indio_dev) +{ + return __sca3000_hw_ring_state_set(indio_dev, 1); +} + +static int sca3000_hw_ring_postdisable(struct iio_dev *indio_dev) +{ + return __sca3000_hw_ring_state_set(indio_dev, 0); +} + +static const struct iio_buffer_setup_ops sca3000_ring_setup_ops = { + .preenable = &sca3000_hw_ring_preenable, + .postdisable = &sca3000_hw_ring_postdisable, +}; + +void sca3000_register_ring_funcs(struct iio_dev *indio_dev) +{ + indio_dev->setup_ops = &sca3000_ring_setup_ops; +} + +/** + * sca3000_ring_int_process() ring specific interrupt handling. + * + * This is only split from the main interrupt handler so as to + * reduce the amount of code if the ring buffer is not enabled. + **/ +void sca3000_ring_int_process(u8 val, struct iio_buffer *ring) +{ + if (val & (SCA3000_INT_STATUS_THREE_QUARTERS | + SCA3000_INT_STATUS_HALF)) { + ring->stufftoread = true; + wake_up_interruptible(&ring->pollq); + } +} diff --git a/kernel/drivers/staging/iio/adc/Kconfig b/kernel/drivers/staging/iio/adc/Kconfig new file mode 100644 index 000000000..d0016ce6e --- /dev/null +++ b/kernel/drivers/staging/iio/adc/Kconfig @@ -0,0 +1,118 @@ +# +# ADC drivers +# +menu "Analog to digital converters" + +config AD7606 + tristate "Analog Devices AD7606 ADC driver" + depends on GPIOLIB + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices: + ad7606, ad7606-6, ad7606-4 analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the + module will be called ad7606. + +config AD7606_IFACE_PARALLEL + tristate "parallel interface support" + depends on AD7606 + help + Say yes here to include parallel interface support on the AD7606 + ADC driver. + + To compile this driver as a module, choose M here: the + module will be called ad7606_iface_parallel. + +config AD7606_IFACE_SPI + tristate "spi interface support" + depends on AD7606 + depends on SPI + help + Say yes here to include parallel interface support on the AD7606 + ADC driver. + + To compile this driver as a module, choose M here: the + module will be called ad7606_iface_spi. + +config AD7780 + tristate "Analog Devices AD7780 and similar ADCs driver" + depends on SPI + depends on GPIOLIB + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7170, AD7171, + AD7780 and AD7781 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 ad7780. + +config AD7816 + tristate "Analog Devices AD7816/7/8 temperature sensor and ADC driver" + depends on SPI + depends on GPIOLIB + help + Say yes here to build support for Analog Devices AD7816/7/8 + temperature sensors and ADC. + +config AD7192 + tristate "Analog Devices AD7190 AD7192 AD7195 ADC driver" + depends on SPI + select AD_SIGMA_DELTA + help + Say yes here to build support for Analog Devices AD7190, + AD7192 or AD7195 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 ad7192. + +config AD7280 + tristate "Analog Devices AD7280A Lithium Ion Battery Monitoring System" + depends on SPI + help + Say yes here to build support for Analog Devices AD7280A + Lithium Ion Battery Monitoring System. + + To compile this driver as a module, choose M here: the + module will be called ad7280a + +config LPC32XX_ADC + tristate "NXP LPC32XX ADC" + depends on ARCH_LPC32XX || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for the integrated ADC inside the + LPC32XX SoC. Note that this feature uses the same hardware as the + touchscreen driver, so you should either select only one of the two + drivers (lpc32xx_adc or lpc32xx_ts) or, in the OpenFirmware case, + activate only one via device tree selection. Provides direct access + via sysfs. + +config MXS_LRADC + tristate "Freescale i.MX23/i.MX28 LRADC" + depends on (ARCH_MXS || COMPILE_TEST) && HAS_IOMEM + depends on INPUT + select STMP_DEVICE + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for i.MX23/i.MX28 LRADC convertor + built into these chips. + + To compile this driver as a module, choose M here: the + module will be called mxs-lradc. + +config SPEAR_ADC + tristate "ST SPEAr ADC" + depends on PLAT_SPEAR || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for the integrated ADC inside the + ST SPEAr SoC. Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called spear_adc. +endmenu diff --git a/kernel/drivers/staging/iio/adc/Makefile b/kernel/drivers/staging/iio/adc/Makefile new file mode 100644 index 000000000..1c4277dbd --- /dev/null +++ b/kernel/drivers/staging/iio/adc/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for industrial I/O ADC drivers +# + +ad7606-y := ad7606_core.o +ad7606-$(CONFIG_IIO_BUFFER) += ad7606_ring.o +ad7606-$(CONFIG_AD7606_IFACE_PARALLEL) += ad7606_par.o +ad7606-$(CONFIG_AD7606_IFACE_SPI) += ad7606_spi.o +obj-$(CONFIG_AD7606) += ad7606.o + +obj-$(CONFIG_AD7780) += ad7780.o +obj-$(CONFIG_AD7816) += ad7816.o +obj-$(CONFIG_AD7192) += ad7192.o +obj-$(CONFIG_AD7280) += ad7280a.o +obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o +obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o +obj-$(CONFIG_SPEAR_ADC) += spear_adc.o diff --git a/kernel/drivers/staging/iio/adc/ad7192.c b/kernel/drivers/staging/iio/adc/ad7192.c new file mode 100644 index 000000000..fe56fb6c7 --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7192.c @@ -0,0 +1,720 @@ +/* + * AD7190 AD7192 AD7195 SPI ADC driver + * + * Copyright 2011-2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ad7192.h" + +/* Registers */ +#define AD7192_REG_COMM 0 /* Communications Register (WO, 8-bit) */ +#define AD7192_REG_STAT 0 /* Status Register (RO, 8-bit) */ +#define AD7192_REG_MODE 1 /* Mode Register (RW, 24-bit */ +#define AD7192_REG_CONF 2 /* Configuration Register (RW, 24-bit) */ +#define AD7192_REG_DATA 3 /* Data Register (RO, 24/32-bit) */ +#define AD7192_REG_ID 4 /* ID Register (RO, 8-bit) */ +#define AD7192_REG_GPOCON 5 /* GPOCON Register (RO, 8-bit) */ +#define AD7192_REG_OFFSET 6 /* Offset Register (RW, 16-bit + * (AD7792)/24-bit (AD7192)) */ +#define AD7192_REG_FULLSALE 7 /* Full-Scale Register + * (RW, 16-bit (AD7792)/24-bit (AD7192)) */ + +/* Communications Register Bit Designations (AD7192_REG_COMM) */ +#define AD7192_COMM_WEN BIT(7) /* Write Enable */ +#define AD7192_COMM_WRITE 0 /* Write Operation */ +#define AD7192_COMM_READ BIT(6) /* Read Operation */ +#define AD7192_COMM_ADDR(x) (((x) & 0x7) << 3) /* Register Address */ +#define AD7192_COMM_CREAD BIT(2) /* Continuous Read of Data Register */ + +/* Status Register Bit Designations (AD7192_REG_STAT) */ +#define AD7192_STAT_RDY BIT(7) /* Ready */ +#define AD7192_STAT_ERR BIT(6) /* Error (Overrange, Underrange) */ +#define AD7192_STAT_NOREF BIT(5) /* Error no external reference */ +#define AD7192_STAT_PARITY BIT(4) /* Parity */ +#define AD7192_STAT_CH3 BIT(2) /* Channel 3 */ +#define AD7192_STAT_CH2 BIT(1) /* Channel 2 */ +#define AD7192_STAT_CH1 BIT(0) /* Channel 1 */ + +/* Mode Register Bit Designations (AD7192_REG_MODE) */ +#define AD7192_MODE_SEL(x) (((x) & 0x7) << 21) /* Operation Mode Select */ +#define AD7192_MODE_SEL_MASK (0x7 << 21) /* Operation Mode Select Mask */ +#define AD7192_MODE_DAT_STA BIT(20) /* Status Register transmission */ +#define AD7192_MODE_CLKSRC(x) (((x) & 0x3) << 18) /* Clock Source Select */ +#define AD7192_MODE_SINC3 BIT(15) /* SINC3 Filter Select */ +#define AD7192_MODE_ACX BIT(14) /* AC excitation enable(AD7195 only)*/ +#define AD7192_MODE_ENPAR BIT(13) /* Parity Enable */ +#define AD7192_MODE_CLKDIV BIT(12) /* Clock divide by 2 (AD7190/2 only)*/ +#define AD7192_MODE_SCYCLE BIT(11) /* Single cycle conversion */ +#define AD7192_MODE_REJ60 BIT(10) /* 50/60Hz notch filter */ +#define AD7192_MODE_RATE(x) ((x) & 0x3FF) /* Filter Update Rate Select */ + +/* Mode Register: AD7192_MODE_SEL options */ +#define AD7192_MODE_CONT 0 /* Continuous Conversion Mode */ +#define AD7192_MODE_SINGLE 1 /* Single Conversion Mode */ +#define AD7192_MODE_IDLE 2 /* Idle Mode */ +#define AD7192_MODE_PWRDN 3 /* Power-Down Mode */ +#define AD7192_MODE_CAL_INT_ZERO 4 /* Internal Zero-Scale Calibration */ +#define AD7192_MODE_CAL_INT_FULL 5 /* Internal Full-Scale Calibration */ +#define AD7192_MODE_CAL_SYS_ZERO 6 /* System Zero-Scale Calibration */ +#define AD7192_MODE_CAL_SYS_FULL 7 /* System Full-Scale Calibration */ + +/* Mode Register: AD7192_MODE_CLKSRC options */ +#define AD7192_CLK_EXT_MCLK1_2 0 /* External 4.92 MHz Clock connected + * from MCLK1 to MCLK2 */ +#define AD7192_CLK_EXT_MCLK2 1 /* External Clock applied to MCLK2 */ +#define AD7192_CLK_INT 2 /* Internal 4.92 MHz Clock not + * available at the MCLK2 pin */ +#define AD7192_CLK_INT_CO 3 /* Internal 4.92 MHz Clock available + * at the MCLK2 pin */ + + +/* Configuration Register Bit Designations (AD7192_REG_CONF) */ + +#define AD7192_CONF_CHOP BIT(23) /* CHOP enable */ +#define AD7192_CONF_REFSEL BIT(20) /* REFIN1/REFIN2 Reference Select */ +#define AD7192_CONF_CHAN(x) (((1 << (x)) & 0xFF) << 8) /* Channel select */ +#define AD7192_CONF_CHAN_MASK (0xFF << 8) /* Channel select mask */ +#define AD7192_CONF_BURN BIT(7) /* Burnout current enable */ +#define AD7192_CONF_REFDET BIT(6) /* Reference detect enable */ +#define AD7192_CONF_BUF BIT(4) /* Buffered Mode Enable */ +#define AD7192_CONF_UNIPOLAR BIT(3) /* Unipolar/Bipolar Enable */ +#define AD7192_CONF_GAIN(x) ((x) & 0x7) /* Gain Select */ + +#define AD7192_CH_AIN1P_AIN2M 0 /* AIN1(+) - AIN2(-) */ +#define AD7192_CH_AIN3P_AIN4M 1 /* AIN3(+) - AIN4(-) */ +#define AD7192_CH_TEMP 2 /* Temp Sensor */ +#define AD7192_CH_AIN2P_AIN2M 3 /* AIN2(+) - AIN2(-) */ +#define AD7192_CH_AIN1 4 /* AIN1 - AINCOM */ +#define AD7192_CH_AIN2 5 /* AIN2 - AINCOM */ +#define AD7192_CH_AIN3 6 /* AIN3 - AINCOM */ +#define AD7192_CH_AIN4 7 /* AIN4 - AINCOM */ + +/* ID Register Bit Designations (AD7192_REG_ID) */ +#define ID_AD7190 0x4 +#define ID_AD7192 0x0 +#define ID_AD7195 0x6 +#define AD7192_ID_MASK 0x0F + +/* GPOCON Register Bit Designations (AD7192_REG_GPOCON) */ +#define AD7192_GPOCON_BPDSW BIT(6) /* Bridge power-down switch enable */ +#define AD7192_GPOCON_GP32EN BIT(5) /* Digital Output P3 and P2 enable */ +#define AD7192_GPOCON_GP10EN BIT(4) /* Digital Output P1 and P0 enable */ +#define AD7192_GPOCON_P3DAT BIT(3) /* P3 state */ +#define AD7192_GPOCON_P2DAT BIT(2) /* P2 state */ +#define AD7192_GPOCON_P1DAT BIT(1) /* P1 state */ +#define AD7192_GPOCON_P0DAT BIT(0) /* P0 state */ + +#define AD7192_INT_FREQ_MHz 4915200 + +/* NOTE: + * The AD7190/2/5 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. + */ + +struct ad7192_state { + struct regulator *reg; + u16 int_vref_mv; + u32 mclk; + u32 f_order; + u32 mode; + u32 conf; + u32 scale_avail[8][2]; + u8 gpocon; + u8 devid; + + struct ad_sigma_delta sd; +}; + +static struct ad7192_state *ad_sigma_delta_to_ad7192(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7192_state, sd); +} + +static int ad7192_set_channel(struct ad_sigma_delta *sd, unsigned int channel) +{ + struct ad7192_state *st = ad_sigma_delta_to_ad7192(sd); + + st->conf &= ~AD7192_CONF_CHAN_MASK; + st->conf |= AD7192_CONF_CHAN(channel); + + return ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, st->conf); +} + +static int ad7192_set_mode(struct ad_sigma_delta *sd, + enum ad_sigma_delta_mode mode) +{ + struct ad7192_state *st = ad_sigma_delta_to_ad7192(sd); + + st->mode &= ~AD7192_MODE_SEL_MASK; + st->mode |= AD7192_MODE_SEL(mode); + + return ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); +} + +static const struct ad_sigma_delta_info ad7192_sigma_delta_info = { + .set_channel = ad7192_set_channel, + .set_mode = ad7192_set_mode, + .has_registers = true, + .addr_shift = 3, + .read_mask = BIT(6), +}; + +static const struct ad_sd_calib_data ad7192_calib_arr[8] = { + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN1}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN1}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN2}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN2}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN3}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN3}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN4}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN4} +}; + +static int ad7192_calibrate_all(struct ad7192_state *st) +{ + return ad_sd_calibrate_all(&st->sd, ad7192_calib_arr, + ARRAY_SIZE(ad7192_calib_arr)); +} + +static int ad7192_setup(struct ad7192_state *st, + const struct ad7192_platform_data *pdata) +{ + struct iio_dev *indio_dev = spi_get_drvdata(st->sd.spi); + unsigned long long scale_uv; + int i, ret, id; + u8 ones[6]; + + /* reset the serial interface */ + memset(&ones, 0xFF, 6); + ret = spi_write(st->sd.spi, &ones, 6); + if (ret < 0) + goto out; + usleep_range(500, 1000); /* Wait for at least 500us */ + + /* write/read test for device presence */ + ret = ad_sd_read_reg(&st->sd, AD7192_REG_ID, 1, &id); + if (ret) + goto out; + + id &= AD7192_ID_MASK; + + if (id != st->devid) + dev_warn(&st->sd.spi->dev, "device ID query failed (0x%X)\n", + id); + + switch (pdata->clock_source_sel) { + case AD7192_CLK_EXT_MCLK1_2: + case AD7192_CLK_EXT_MCLK2: + st->mclk = AD7192_INT_FREQ_MHz; + break; + case AD7192_CLK_INT: + case AD7192_CLK_INT_CO: + if (pdata->ext_clk_Hz) + st->mclk = pdata->ext_clk_Hz; + else + st->mclk = AD7192_INT_FREQ_MHz; + break; + default: + ret = -EINVAL; + goto out; + } + + st->mode = AD7192_MODE_SEL(AD7192_MODE_IDLE) | + AD7192_MODE_CLKSRC(pdata->clock_source_sel) | + AD7192_MODE_RATE(480); + + st->conf = AD7192_CONF_GAIN(0); + + if (pdata->rej60_en) + st->mode |= AD7192_MODE_REJ60; + + if (pdata->sinc3_en) + st->mode |= AD7192_MODE_SINC3; + + if (pdata->refin2_en && (st->devid != ID_AD7195)) + st->conf |= AD7192_CONF_REFSEL; + + if (pdata->chop_en) { + st->conf |= AD7192_CONF_CHOP; + if (pdata->sinc3_en) + st->f_order = 3; /* SINC 3rd order */ + else + st->f_order = 4; /* SINC 4th order */ + } else { + st->f_order = 1; + } + + if (pdata->buf_en) + st->conf |= AD7192_CONF_BUF; + + if (pdata->unipolar_en) + st->conf |= AD7192_CONF_UNIPOLAR; + + if (pdata->burnout_curr_en) + st->conf |= AD7192_CONF_BURN; + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + if (ret) + goto out; + + ret = ad_sd_write_reg(&st->sd, AD7192_REG_CONF, 3, st->conf); + if (ret) + goto out; + + ret = ad7192_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)st->int_vref_mv * 100000000) + >> (indio_dev->channels[0].scan_type.realbits - + ((st->conf & AD7192_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 ssize_t ad7192_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->mclk / + (st->f_order * 1024 * AD7192_MODE_RATE(st->mode))); +} + +static ssize_t ad7192_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 ad7192_state *st = iio_priv(indio_dev); + unsigned long lval; + int div, ret; + + ret = kstrtoul(buf, 10, &lval); + if (ret) + return ret; + if (lval == 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + div = st->mclk / (lval * st->f_order * 1024); + if (div < 1 || div > 1023) { + ret = -EINVAL; + goto out; + } + + st->mode &= ~AD7192_MODE_RATE(-1); + st->mode |= AD7192_MODE_RATE(div); + ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ad7192_read_frequency, + ad7192_write_frequency); + +static ssize_t ad7192_show_scale_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_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_v_m_v_scale_available, + in_voltage-voltage_scale_available, + S_IRUGO, ad7192_show_scale_available, NULL, 0); + +static IIO_DEVICE_ATTR(in_voltage_scale_available, S_IRUGO, + ad7192_show_scale_available, NULL, 0); + +static ssize_t ad7192_show_ac_excitation(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", !!(st->mode & AD7192_MODE_ACX)); +} + +static ssize_t ad7192_show_bridge_switch(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", !!(st->gpocon & AD7192_GPOCON_BPDSW)); +} + +static ssize_t ad7192_set(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7192_state *st = iio_priv(indio_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; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + switch ((u32) this_attr->address) { + case AD7192_REG_GPOCON: + if (val) + st->gpocon |= AD7192_GPOCON_BPDSW; + else + st->gpocon &= ~AD7192_GPOCON_BPDSW; + + ad_sd_write_reg(&st->sd, AD7192_REG_GPOCON, 1, st->gpocon); + break; + case AD7192_REG_MODE: + if (val) + st->mode |= AD7192_MODE_ACX; + else + st->mode &= ~AD7192_MODE_ACX; + + ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(bridge_switch_en, S_IRUGO | S_IWUSR, + ad7192_show_bridge_switch, ad7192_set, + AD7192_REG_GPOCON); + +static IIO_DEVICE_ATTR(ac_excitation_en, S_IRUGO | S_IWUSR, + ad7192_show_ac_excitation, ad7192_set, + AD7192_REG_MODE); + +static struct attribute *ad7192_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_v_m_v_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + &iio_dev_attr_bridge_switch_en.dev_attr.attr, + &iio_dev_attr_ac_excitation_en.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7192_attribute_group = { + .attrs = ad7192_attributes, +}; + +static struct attribute *ad7195_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_v_m_v_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + &iio_dev_attr_bridge_switch_en.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7195_attribute_group = { + .attrs = ad7195_attributes, +}; + +static unsigned int ad7192_get_temp_scale(bool unipolar) +{ + return unipolar ? 2815 * 2 : 2815; +} + +static int ad7192_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7192_state *st = iio_priv(indio_dev); + bool unipolar = !!(st->conf & AD7192_CONF_UNIPOLAR); + + switch (m) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + mutex_lock(&indio_dev->mlock); + *val = st->scale_avail[AD7192_CONF_GAIN(st->conf)][0]; + *val2 = st->scale_avail[AD7192_CONF_GAIN(st->conf)][1]; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + *val = 0; + *val2 = 1000000000 / ad7192_get_temp_scale(unipolar); + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + 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) + *val -= 273 * ad7192_get_temp_scale(unipolar); + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ad7192_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7192_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 &= ~AD7192_CONF_GAIN(-1); + st->conf |= AD7192_CONF_GAIN(i); + if (tmp == st->conf) + break; + ad_sd_write_reg(&st->sd, AD7192_REG_CONF, + 3, st->conf); + ad7192_calibrate_all(st); + break; + } + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad7192_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 ad7192_info = { + .read_raw = &ad7192_read_raw, + .write_raw = &ad7192_write_raw, + .write_raw_get_fmt = &ad7192_write_raw_get_fmt, + .attrs = &ad7192_attribute_group, + .validate_trigger = ad_sd_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad7195_info = { + .read_raw = &ad7192_read_raw, + .write_raw = &ad7192_write_raw, + .write_raw_get_fmt = &ad7192_write_raw_get_fmt, + .attrs = &ad7195_attribute_group, + .validate_trigger = ad_sd_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec ad7192_channels[] = { + AD_SD_DIFF_CHANNEL(0, 1, 2, AD7192_CH_AIN1P_AIN2M, 24, 32, 0), + AD_SD_DIFF_CHANNEL(1, 3, 4, AD7192_CH_AIN3P_AIN4M, 24, 32, 0), + AD_SD_TEMP_CHANNEL(2, AD7192_CH_TEMP, 24, 32, 0), + AD_SD_SHORTED_CHANNEL(3, 2, AD7192_CH_AIN2P_AIN2M, 24, 32, 0), + AD_SD_CHANNEL(4, 1, AD7192_CH_AIN1, 24, 32, 0), + AD_SD_CHANNEL(5, 2, AD7192_CH_AIN2, 24, 32, 0), + AD_SD_CHANNEL(6, 3, AD7192_CH_AIN3, 24, 32, 0), + AD_SD_CHANNEL(7, 4, AD7192_CH_AIN4, 24, 32, 0), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static int ad7192_probe(struct spi_device *spi) +{ + const struct ad7192_platform_data *pdata = spi->dev.platform_data; + struct ad7192_state *st; + struct iio_dev *indio_dev; + int ret, voltage_uv = 0; + + if (!pdata) { + dev_err(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + if (!spi->irq) { + dev_err(&spi->dev, "no IRQ?\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + voltage_uv = regulator_get_voltage(st->reg); + } + + if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (voltage_uv) + st->int_vref_mv = voltage_uv / 1000; + else + dev_warn(&spi->dev, "reference voltage undefined\n"); + + spi_set_drvdata(spi, indio_dev); + st->devid = spi_get_device_id(spi)->driver_data; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7192_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7192_channels); + if (st->devid == ID_AD7195) + indio_dev->info = &ad7195_info; + else + indio_dev->info = &ad7192_info; + + ad_sd_init(&st->sd, indio_dev, spi, &ad7192_sigma_delta_info); + + ret = ad_sd_setup_buffer_and_trigger(indio_dev); + if (ret) + goto error_disable_reg; + + ret = ad7192_setup(st, pdata); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_remove_trigger; + return 0; + +error_remove_trigger: + ad_sd_cleanup_buffer_and_trigger(indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ad7192_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7192_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + ad_sd_cleanup_buffer_and_trigger(indio_dev); + + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7192_id[] = { + {"ad7190", ID_AD7190}, + {"ad7192", ID_AD7192}, + {"ad7195", ID_AD7195}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7192_id); + +static struct spi_driver ad7192_driver = { + .driver = { + .name = "ad7192", + .owner = THIS_MODULE, + }, + .probe = ad7192_probe, + .remove = ad7192_remove, + .id_table = ad7192_id, +}; +module_spi_driver(ad7192_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7190, AD7192, AD7195 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/adc/ad7192.h b/kernel/drivers/staging/iio/adc/ad7192.h new file mode 100644 index 000000000..a0a5b61a4 --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7192.h @@ -0,0 +1,47 @@ +/* + * AD7190 AD7192 AD7195 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef IIO_ADC_AD7192_H_ +#define IIO_ADC_AD7192_H_ + +/* + * TODO: struct ad7192_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad7192_platform_data - platform/board specific information + * @vref_mv: the external reference voltage in millivolt + * @clock_source_sel: [0..3] + * 0 External 4.92 MHz clock connected from MCLK1 to MCLK2 + * 1 External Clock applied to MCLK2 + * 2 Internal 4.92 MHz Clock not available at the MCLK2 pin + * 3 Internal 4.92 MHz Clock available at the MCLK2 pin + * @ext_clk_Hz: the external clock frequency in Hz, if not set + * the driver uses the internal clock (16.776 MHz) + * @refin2_en: REFIN1/REFIN2 Reference Select (AD7190/2 only) + * @rej60_en: 50/60Hz notch filter enable + * @sinc3_en: SINC3 filter enable (default SINC4) + * @chop_en: CHOP mode enable + * @buf_en: buffered input mode enable + * @unipolar_en: unipolar mode enable + * @burnout_curr_en: constant current generators on AIN(+|-) enable + */ + +struct ad7192_platform_data { + u16 vref_mv; + u8 clock_source_sel; + u32 ext_clk_Hz; + bool refin2_en; + bool rej60_en; + bool sinc3_en; + bool chop_en; + bool buf_en; + bool unipolar_en; + bool burnout_curr_en; +}; + +#endif /* IIO_ADC_AD7192_H_ */ diff --git a/kernel/drivers/staging/iio/adc/ad7280a.c b/kernel/drivers/staging/iio/adc/ad7280a.c new file mode 100644 index 000000000..d98e229c4 --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7280a.c @@ -0,0 +1,985 @@ +/* + * AD7280A Lithium Ion Battery Monitoring System + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ad7280a.h" + +/* Registers */ +#define AD7280A_CELL_VOLTAGE_1 0x0 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_2 0x1 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_3 0x2 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_4 0x3 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_5 0x4 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_6 0x5 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_1 0x6 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_2 0x7 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_3 0x8 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_4 0x9 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_5 0xA /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_6 0xB /* D11 to D0, Read only */ +#define AD7280A_SELF_TEST 0xC /* D11 to D0, Read only */ +#define AD7280A_CONTROL_HB 0xD /* D15 to D8, Read/write */ +#define AD7280A_CONTROL_LB 0xE /* D7 to D0, Read/write */ +#define AD7280A_CELL_OVERVOLTAGE 0xF /* D7 to D0, Read/write */ +#define AD7280A_CELL_UNDERVOLTAGE 0x10 /* D7 to D0, Read/write */ +#define AD7280A_AUX_ADC_OVERVOLTAGE 0x11 /* D7 to D0, Read/write */ +#define AD7280A_AUX_ADC_UNDERVOLTAGE 0x12 /* D7 to D0, Read/write */ +#define AD7280A_ALERT 0x13 /* D7 to D0, Read/write */ +#define AD7280A_CELL_BALANCE 0x14 /* D7 to D0, Read/write */ +#define AD7280A_CB1_TIMER 0x15 /* D7 to D0, Read/write */ +#define AD7280A_CB2_TIMER 0x16 /* D7 to D0, Read/write */ +#define AD7280A_CB3_TIMER 0x17 /* D7 to D0, Read/write */ +#define AD7280A_CB4_TIMER 0x18 /* D7 to D0, Read/write */ +#define AD7280A_CB5_TIMER 0x19 /* D7 to D0, Read/write */ +#define AD7280A_CB6_TIMER 0x1A /* D7 to D0, Read/write */ +#define AD7280A_PD_TIMER 0x1B /* D7 to D0, Read/write */ +#define AD7280A_READ 0x1C /* D7 to D0, Read/write */ +#define AD7280A_CNVST_CONTROL 0x1D /* D7 to D0, Read/write */ + +/* Bits and Masks */ +#define AD7280A_CTRL_HB_CONV_INPUT_ALL 0 +#define AD7280A_CTRL_HB_CONV_INPUT_6CELL_AUX1_3_4 BIT(6) +#define AD7280A_CTRL_HB_CONV_INPUT_6CELL BIT(7) +#define AD7280A_CTRL_HB_CONV_INPUT_SELF_TEST (BIT(7) | BIT(6)) +#define AD7280A_CTRL_HB_CONV_RES_READ_ALL 0 +#define AD7280A_CTRL_HB_CONV_RES_READ_6CELL_AUX1_3_4 BIT(4) +#define AD7280A_CTRL_HB_CONV_RES_READ_6CELL BIT(5) +#define AD7280A_CTRL_HB_CONV_RES_READ_NO (BIT(5) | BIT(4)) +#define AD7280A_CTRL_HB_CONV_START_CNVST 0 +#define AD7280A_CTRL_HB_CONV_START_CS BIT(3) +#define AD7280A_CTRL_HB_CONV_AVG_DIS 0 +#define AD7280A_CTRL_HB_CONV_AVG_2 BIT(1) +#define AD7280A_CTRL_HB_CONV_AVG_4 BIT(2) +#define AD7280A_CTRL_HB_CONV_AVG_8 (BIT(2) | BIT(1)) +#define AD7280A_CTRL_HB_CONV_AVG(x) ((x) << 1) +#define AD7280A_CTRL_HB_PWRDN_SW BIT(0) + +#define AD7280A_CTRL_LB_SWRST BIT(7) +#define AD7280A_CTRL_LB_ACQ_TIME_400ns 0 +#define AD7280A_CTRL_LB_ACQ_TIME_800ns BIT(5) +#define AD7280A_CTRL_LB_ACQ_TIME_1200ns BIT(6) +#define AD7280A_CTRL_LB_ACQ_TIME_1600ns (BIT(6) | BIT(5)) +#define AD7280A_CTRL_LB_ACQ_TIME(x) ((x) << 5) +#define AD7280A_CTRL_LB_MUST_SET BIT(4) +#define AD7280A_CTRL_LB_THERMISTOR_EN BIT(3) +#define AD7280A_CTRL_LB_LOCK_DEV_ADDR BIT(2) +#define AD7280A_CTRL_LB_INC_DEV_ADDR BIT(1) +#define AD7280A_CTRL_LB_DAISY_CHAIN_RB_EN BIT(0) + +#define AD7280A_ALERT_GEN_STATIC_HIGH BIT(6) +#define AD7280A_ALERT_RELAY_SIG_CHAIN_DOWN (BIT(7) | BIT(6)) + +#define AD7280A_ALL_CELLS (0xAD << 16) + +#define AD7280A_MAX_SPI_CLK_Hz 700000 /* < 1MHz */ +#define AD7280A_MAX_CHAIN 8 +#define AD7280A_CELLS_PER_DEV 6 +#define AD7280A_BITS 12 +#define AD7280A_NUM_CH (AD7280A_AUX_ADC_6 - \ + AD7280A_CELL_VOLTAGE_1 + 1) + +#define AD7280A_DEVADDR_MASTER 0 +#define AD7280A_DEVADDR_ALL 0x1F +/* 5-bit device address is sent LSB first */ +#define AD7280A_DEVADDR(addr) (((addr & 0x1) << 4) | ((addr & 0x2) << 3) | \ + (addr & 0x4) | ((addr & 0x8) >> 3) | \ + ((addr & 0x10) >> 4)) + +/* During a read a valid write is mandatory. + * So writing to the highest available address (Address 0x1F) + * and setting the address all parts bit to 0 is recommended + * So the TXVAL is AD7280A_DEVADDR_ALL + CRC + */ +#define AD7280A_READ_TXVAL 0xF800030A + +/* + * AD7280 CRC + * + * P(x) = x^8 + x^5 + x^3 + x^2 + x^1 + x^0 = 0b100101111 => 0x2F + */ +#define POLYNOM 0x2F +#define POLYNOM_ORDER 8 +#define HIGHBIT (1 << (POLYNOM_ORDER - 1)) + +struct ad7280_state { + struct spi_device *spi; + struct iio_chan_spec *channels; + struct iio_dev_attr *iio_attr; + int slave_num; + int scan_cnt; + int readback_delay_us; + unsigned char crc_tab[256]; + unsigned char ctrl_hb; + unsigned char ctrl_lb; + unsigned char cell_threshhigh; + unsigned char cell_threshlow; + unsigned char aux_threshhigh; + unsigned char aux_threshlow; + unsigned char cb_mask[AD7280A_MAX_CHAIN]; + + __be32 buf[2] ____cacheline_aligned; +}; + +static void ad7280_crc8_build_table(unsigned char *crc_tab) +{ + unsigned char bit, crc; + int cnt, i; + + for (cnt = 0; cnt < 256; cnt++) { + crc = cnt; + for (i = 0; i < 8; i++) { + bit = crc & HIGHBIT; + crc <<= 1; + if (bit) + crc ^= POLYNOM; + } + crc_tab[cnt] = crc; + } +} + +static unsigned char ad7280_calc_crc8(unsigned char *crc_tab, unsigned val) +{ + unsigned char crc; + + crc = crc_tab[val >> 16 & 0xFF]; + crc = crc_tab[crc ^ (val >> 8 & 0xFF)]; + + return crc ^ (val & 0xFF); +} + +static int ad7280_check_crc(struct ad7280_state *st, unsigned val) +{ + unsigned char crc = ad7280_calc_crc8(st->crc_tab, val >> 10); + + if (crc != ((val >> 2) & 0xFF)) + return -EIO; + + return 0; +} + +/* After initiating a conversion sequence we need to wait until the + * conversion is done. The delay is typically in the range of 15..30 us + * however depending an the number of devices in the daisy chain and the + * number of averages taken, conversion delays and acquisition time options + * it may take up to 250us, in this case we better sleep instead of busy + * wait. + */ + +static void ad7280_delay(struct ad7280_state *st) +{ + if (st->readback_delay_us < 50) + udelay(st->readback_delay_us); + else + usleep_range(250, 500); +} + +static int __ad7280_read32(struct ad7280_state *st, unsigned *val) +{ + int ret; + struct spi_transfer t = { + .tx_buf = &st->buf[0], + .rx_buf = &st->buf[1], + .len = 4, + }; + + st->buf[0] = cpu_to_be32(AD7280A_READ_TXVAL); + + ret = spi_sync_transfer(st->spi, &t, 1); + if (ret) + return ret; + + *val = be32_to_cpu(st->buf[1]); + + return 0; +} + +static int ad7280_write(struct ad7280_state *st, unsigned devaddr, + unsigned addr, bool all, unsigned val) +{ + unsigned reg = (devaddr << 27 | addr << 21 | + (val & 0xFF) << 13 | all << 12); + + reg |= ad7280_calc_crc8(st->crc_tab, reg >> 11) << 3 | 0x2; + st->buf[0] = cpu_to_be32(reg); + + return spi_write(st->spi, &st->buf[0], 4); +} + +static int ad7280_read(struct ad7280_state *st, unsigned devaddr, + unsigned addr) +{ + int ret; + unsigned tmp; + + /* turns off the read operation on all parts */ + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_NO | + st->ctrl_hb); + if (ret) + return ret; + + /* turns on the read operation on the addressed part */ + ret = ad7280_write(st, devaddr, AD7280A_CONTROL_HB, 0, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_ALL | + st->ctrl_hb); + if (ret) + return ret; + + /* Set register address on the part to be read from */ + ret = ad7280_write(st, devaddr, AD7280A_READ, 0, addr << 2); + if (ret) + return ret; + + __ad7280_read32(st, &tmp); + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if (((tmp >> 27) != devaddr) || (((tmp >> 21) & 0x3F) != addr)) + return -EFAULT; + + return (tmp >> 13) & 0xFF; +} + +static int ad7280_read_channel(struct ad7280_state *st, unsigned devaddr, + unsigned addr) +{ + int ret; + unsigned tmp; + + ret = ad7280_write(st, devaddr, AD7280A_READ, 0, addr << 2); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_NO | + st->ctrl_hb); + if (ret) + return ret; + + ret = ad7280_write(st, devaddr, AD7280A_CONTROL_HB, 0, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_ALL | + AD7280A_CTRL_HB_CONV_START_CS | + st->ctrl_hb); + if (ret) + return ret; + + ad7280_delay(st); + + __ad7280_read32(st, &tmp); + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if (((tmp >> 27) != devaddr) || (((tmp >> 23) & 0xF) != addr)) + return -EFAULT; + + return (tmp >> 11) & 0xFFF; +} + +static int ad7280_read_all_channels(struct ad7280_state *st, unsigned cnt, + unsigned *array) +{ + int i, ret; + unsigned tmp, sum = 0; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_READ, 1, + AD7280A_CELL_VOLTAGE_1 << 2); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_ALL | + AD7280A_CTRL_HB_CONV_START_CS | + st->ctrl_hb); + if (ret) + return ret; + + ad7280_delay(st); + + for (i = 0; i < cnt; i++) { + __ad7280_read32(st, &tmp); + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if (array) + array[i] = tmp; + /* only sum cell voltages */ + if (((tmp >> 23) & 0xF) <= AD7280A_CELL_VOLTAGE_6) + sum += ((tmp >> 11) & 0xFFF); + } + + return sum; +} + +static int ad7280_chain_setup(struct ad7280_state *st) +{ + unsigned val, n; + int ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_LB, 1, + AD7280A_CTRL_LB_DAISY_CHAIN_RB_EN | + AD7280A_CTRL_LB_LOCK_DEV_ADDR | + AD7280A_CTRL_LB_MUST_SET | + AD7280A_CTRL_LB_SWRST | + st->ctrl_lb); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_LB, 1, + AD7280A_CTRL_LB_DAISY_CHAIN_RB_EN | + AD7280A_CTRL_LB_LOCK_DEV_ADDR | + AD7280A_CTRL_LB_MUST_SET | + st->ctrl_lb); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_READ, 1, + AD7280A_CONTROL_LB << 2); + if (ret) + return ret; + + for (n = 0; n <= AD7280A_MAX_CHAIN; n++) { + __ad7280_read32(st, &val); + if (val == 0) + return n - 1; + + if (ad7280_check_crc(st, val)) + return -EIO; + + if (n != AD7280A_DEVADDR(val >> 27)) + return -EIO; + } + + return -EFAULT; +} + +static ssize_t ad7280_show_balance_sw(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + return sprintf(buf, "%d\n", + !!(st->cb_mask[this_attr->address >> 8] & + (1 << ((this_attr->address & 0xFF) + 2)))); +} + +static ssize_t ad7280_store_balance_sw(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + bool readin; + int ret; + unsigned devaddr, ch; + + ret = strtobool(buf, &readin); + if (ret) + return ret; + + devaddr = this_attr->address >> 8; + ch = this_attr->address & 0xFF; + + mutex_lock(&indio_dev->mlock); + if (readin) + st->cb_mask[devaddr] |= 1 << (ch + 2); + else + st->cb_mask[devaddr] &= ~(1 << (ch + 2)); + + ret = ad7280_write(st, devaddr, AD7280A_CELL_BALANCE, + 0, st->cb_mask[devaddr]); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static ssize_t ad7280_show_balance_timer(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + unsigned msecs; + + mutex_lock(&indio_dev->mlock); + ret = ad7280_read(st, this_attr->address >> 8, + this_attr->address & 0xFF); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + msecs = (ret >> 3) * 71500; + + return sprintf(buf, "%u\n", msecs); +} + +static ssize_t ad7280_store_balance_timer(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + val /= 71500; + + if (val > 31) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = ad7280_write(st, this_attr->address >> 8, + this_attr->address & 0xFF, + 0, (val & 0x1F) << 3); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static struct attribute *ad7280_attributes[AD7280A_MAX_CHAIN * + AD7280A_CELLS_PER_DEV * 2 + 1]; + +static struct attribute_group ad7280_attrs_group = { + .attrs = ad7280_attributes, +}; + +static int ad7280_channel_init(struct ad7280_state *st) +{ + int dev, ch, cnt; + + st->channels = kcalloc((st->slave_num + 1) * 12 + 2, + sizeof(*st->channels), GFP_KERNEL); + if (st->channels == NULL) + return -ENOMEM; + + for (dev = 0, cnt = 0; dev <= st->slave_num; dev++) + for (ch = AD7280A_CELL_VOLTAGE_1; ch <= AD7280A_AUX_ADC_6; ch++, + cnt++) { + if (ch < AD7280A_AUX_ADC_1) { + st->channels[cnt].type = IIO_VOLTAGE; + st->channels[cnt].differential = 1; + st->channels[cnt].channel = (dev * 6) + ch; + st->channels[cnt].channel2 = + st->channels[cnt].channel + 1; + } else { + st->channels[cnt].type = IIO_TEMP; + st->channels[cnt].channel = (dev * 6) + ch - 6; + } + st->channels[cnt].indexed = 1; + st->channels[cnt].info_mask_separate = + BIT(IIO_CHAN_INFO_RAW); + st->channels[cnt].info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE); + st->channels[cnt].address = + AD7280A_DEVADDR(dev) << 8 | ch; + st->channels[cnt].scan_index = cnt; + st->channels[cnt].scan_type.sign = 'u'; + st->channels[cnt].scan_type.realbits = 12; + st->channels[cnt].scan_type.storagebits = 32; + st->channels[cnt].scan_type.shift = 0; + } + + st->channels[cnt].type = IIO_VOLTAGE; + st->channels[cnt].differential = 1; + st->channels[cnt].channel = 0; + st->channels[cnt].channel2 = dev * 6; + st->channels[cnt].address = AD7280A_ALL_CELLS; + st->channels[cnt].indexed = 1; + st->channels[cnt].info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + st->channels[cnt].info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + st->channels[cnt].scan_index = cnt; + st->channels[cnt].scan_type.sign = 'u'; + st->channels[cnt].scan_type.realbits = 32; + st->channels[cnt].scan_type.storagebits = 32; + st->channels[cnt].scan_type.shift = 0; + cnt++; + st->channels[cnt].type = IIO_TIMESTAMP; + st->channels[cnt].channel = -1; + st->channels[cnt].scan_index = cnt; + st->channels[cnt].scan_type.sign = 's'; + st->channels[cnt].scan_type.realbits = 64; + st->channels[cnt].scan_type.storagebits = 64; + st->channels[cnt].scan_type.shift = 0; + + return cnt + 1; +} + +static int ad7280_attr_init(struct ad7280_state *st) +{ + int dev, ch, cnt; + + st->iio_attr = kcalloc(2, sizeof(*st->iio_attr) * + (st->slave_num + 1) * AD7280A_CELLS_PER_DEV, + GFP_KERNEL); + if (st->iio_attr == NULL) + return -ENOMEM; + + for (dev = 0, cnt = 0; dev <= st->slave_num; dev++) + for (ch = AD7280A_CELL_VOLTAGE_1; ch <= AD7280A_CELL_VOLTAGE_6; + ch++, cnt++) { + st->iio_attr[cnt].address = + AD7280A_DEVADDR(dev) << 8 | ch; + st->iio_attr[cnt].dev_attr.attr.mode = + S_IWUSR | S_IRUGO; + st->iio_attr[cnt].dev_attr.show = + ad7280_show_balance_sw; + st->iio_attr[cnt].dev_attr.store = + ad7280_store_balance_sw; + st->iio_attr[cnt].dev_attr.attr.name = + kasprintf(GFP_KERNEL, + "in%d-in%d_balance_switch_en", + (dev * AD7280A_CELLS_PER_DEV) + ch, + (dev * AD7280A_CELLS_PER_DEV) + ch + 1); + ad7280_attributes[cnt] = + &st->iio_attr[cnt].dev_attr.attr; + cnt++; + st->iio_attr[cnt].address = + AD7280A_DEVADDR(dev) << 8 | + (AD7280A_CB1_TIMER + ch); + st->iio_attr[cnt].dev_attr.attr.mode = + S_IWUSR | S_IRUGO; + st->iio_attr[cnt].dev_attr.show = + ad7280_show_balance_timer; + st->iio_attr[cnt].dev_attr.store = + ad7280_store_balance_timer; + st->iio_attr[cnt].dev_attr.attr.name = + kasprintf(GFP_KERNEL, "in%d-in%d_balance_timer", + (dev * AD7280A_CELLS_PER_DEV) + ch, + (dev * AD7280A_CELLS_PER_DEV) + ch + 1); + ad7280_attributes[cnt] = + &st->iio_attr[cnt].dev_attr.attr; + } + + ad7280_attributes[cnt] = NULL; + + return 0; +} + +static ssize_t ad7280_read_channel_config(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned val; + + switch ((u32) this_attr->address) { + case AD7280A_CELL_OVERVOLTAGE: + val = 1000 + (st->cell_threshhigh * 1568) / 100; + break; + case AD7280A_CELL_UNDERVOLTAGE: + val = 1000 + (st->cell_threshlow * 1568) / 100; + break; + case AD7280A_AUX_ADC_OVERVOLTAGE: + val = (st->aux_threshhigh * 196) / 10; + break; + case AD7280A_AUX_ADC_UNDERVOLTAGE: + val = (st->aux_threshlow * 196) / 10; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ad7280_write_channel_config(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + long val; + int ret; + + ret = kstrtol(buf, 10, &val); + if (ret) + return ret; + + switch ((u32) this_attr->address) { + case AD7280A_CELL_OVERVOLTAGE: + case AD7280A_CELL_UNDERVOLTAGE: + val = ((val - 1000) * 100) / 1568; /* LSB 15.68mV */ + break; + case AD7280A_AUX_ADC_OVERVOLTAGE: + case AD7280A_AUX_ADC_UNDERVOLTAGE: + val = (val * 10) / 196; /* LSB 19.6mV */ + break; + default: + return -EFAULT; + } + + val = clamp(val, 0L, 0xFFL); + + mutex_lock(&indio_dev->mlock); + switch ((u32) this_attr->address) { + case AD7280A_CELL_OVERVOLTAGE: + st->cell_threshhigh = val; + break; + case AD7280A_CELL_UNDERVOLTAGE: + st->cell_threshlow = val; + break; + case AD7280A_AUX_ADC_OVERVOLTAGE: + st->aux_threshhigh = val; + break; + case AD7280A_AUX_ADC_UNDERVOLTAGE: + st->aux_threshlow = val; + break; + } + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, + this_attr->address, 1, val); + + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static irqreturn_t ad7280_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad7280_state *st = iio_priv(indio_dev); + unsigned *channels; + int i, ret; + + channels = kcalloc(st->scan_cnt, sizeof(*channels), GFP_KERNEL); + if (channels == NULL) + return IRQ_HANDLED; + + ret = ad7280_read_all_channels(st, st->scan_cnt, channels); + if (ret < 0) + goto out; + + for (i = 0; i < st->scan_cnt; i++) { + if (((channels[i] >> 23) & 0xF) <= AD7280A_CELL_VOLTAGE_6) { + if (((channels[i] >> 11) & 0xFFF) >= + st->cell_threshhigh) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, + 1, + 0, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + iio_get_time_ns()); + else if (((channels[i] >> 11) & 0xFFF) <= + st->cell_threshlow) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, + 1, + 0, + IIO_EV_DIR_FALLING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + iio_get_time_ns()); + } else { + if (((channels[i] >> 11) & 0xFFF) >= st->aux_threshhigh) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + else if (((channels[i] >> 11) & 0xFFF) <= + st->aux_threshlow) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns()); + } + } + +out: + kfree(channels); + + return IRQ_HANDLED; +} + +static IIO_DEVICE_ATTR_NAMED(in_thresh_low_value, + in_voltage-voltage_thresh_low_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_CELL_UNDERVOLTAGE); + +static IIO_DEVICE_ATTR_NAMED(in_thresh_high_value, + in_voltage-voltage_thresh_high_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_CELL_OVERVOLTAGE); + +static IIO_DEVICE_ATTR(in_temp_thresh_low_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_AUX_ADC_UNDERVOLTAGE); + +static IIO_DEVICE_ATTR(in_temp_thresh_high_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_AUX_ADC_OVERVOLTAGE); + + +static struct attribute *ad7280_event_attributes[] = { + &iio_dev_attr_in_thresh_low_value.dev_attr.attr, + &iio_dev_attr_in_thresh_high_value.dev_attr.attr, + &iio_dev_attr_in_temp_thresh_low_value.dev_attr.attr, + &iio_dev_attr_in_temp_thresh_high_value.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad7280_event_attrs_group = { + .attrs = ad7280_event_attributes, +}; + +static int ad7280_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7280_state *st = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + if (chan->address == AD7280A_ALL_CELLS) + ret = ad7280_read_all_channels(st, st->scan_cnt, NULL); + else + ret = ad7280_read_channel(st, chan->address >> 8, + chan->address & 0xFF); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if ((chan->address & 0xFF) <= AD7280A_CELL_VOLTAGE_6) + *val = 4000; + else + *val = 5000; + + *val2 = AD7280A_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static const struct iio_info ad7280_info = { + .read_raw = &ad7280_read_raw, + .event_attrs = &ad7280_event_attrs_group, + .attrs = &ad7280_attrs_group, + .driver_module = THIS_MODULE, +}; + +static const struct ad7280_platform_data ad7793_default_pdata = { + .acquisition_time = AD7280A_ACQ_TIME_400ns, + .conversion_averaging = AD7280A_CONV_AVG_DIS, + .thermistor_term_en = true, +}; + +static int ad7280_probe(struct spi_device *spi) +{ + const struct ad7280_platform_data *pdata = spi->dev.platform_data; + struct ad7280_state *st; + int ret; + const unsigned short tACQ_ns[4] = {465, 1010, 1460, 1890}; + const unsigned short nAVG[4] = {1, 2, 4, 8}; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + + if (!pdata) + pdata = &ad7793_default_pdata; + + ad7280_crc8_build_table(st->crc_tab); + + st->spi->max_speed_hz = AD7280A_MAX_SPI_CLK_Hz; + st->spi->mode = SPI_MODE_1; + spi_setup(st->spi); + + st->ctrl_lb = AD7280A_CTRL_LB_ACQ_TIME(pdata->acquisition_time & 0x3); + st->ctrl_hb = AD7280A_CTRL_HB_CONV_AVG(pdata->conversion_averaging + & 0x3) | (pdata->thermistor_term_en ? + AD7280A_CTRL_LB_THERMISTOR_EN : 0); + + ret = ad7280_chain_setup(st); + if (ret < 0) + return ret; + + st->slave_num = ret; + st->scan_cnt = (st->slave_num + 1) * AD7280A_NUM_CH; + st->cell_threshhigh = 0xFF; + st->aux_threshhigh = 0xFF; + + /* + * Total Conversion Time = ((tACQ + tCONV) * + * (Number of Conversions per Part)) − + * tACQ + ((N - 1) * tDELAY) + * + * Readback Delay = Total Conversion Time + tWAIT + */ + + st->readback_delay_us = + ((tACQ_ns[pdata->acquisition_time & 0x3] + 695) * + (AD7280A_NUM_CH * nAVG[pdata->conversion_averaging & 0x3])) + - tACQ_ns[pdata->acquisition_time & 0x3] + + st->slave_num * 250; + + /* Convert to usecs */ + st->readback_delay_us = DIV_ROUND_UP(st->readback_delay_us, 1000); + st->readback_delay_us += 5; /* Add tWAIT */ + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ad7280_channel_init(st); + if (ret < 0) + return ret; + + indio_dev->num_channels = ret; + indio_dev->channels = st->channels; + indio_dev->info = &ad7280_info; + + ret = ad7280_attr_init(st); + if (ret < 0) + goto error_free_channels; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_attr; + + if (spi->irq > 0) { + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, + AD7280A_ALERT, 1, + AD7280A_ALERT_RELAY_SIG_CHAIN_DOWN); + if (ret) + goto error_unregister; + + ret = ad7280_write(st, AD7280A_DEVADDR(st->slave_num), + AD7280A_ALERT, 0, + AD7280A_ALERT_GEN_STATIC_HIGH | + (pdata->chain_last_alert_ignore & 0xF)); + if (ret) + goto error_unregister; + + ret = request_threaded_irq(spi->irq, + NULL, + ad7280_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + indio_dev->name, + indio_dev); + if (ret) + goto error_unregister; + } + + return 0; +error_unregister: + iio_device_unregister(indio_dev); + +error_free_attr: + kfree(st->iio_attr); + +error_free_channels: + kfree(st->channels); + + return ret; +} + +static int ad7280_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7280_state *st = iio_priv(indio_dev); + + if (spi->irq > 0) + free_irq(spi->irq, indio_dev); + iio_device_unregister(indio_dev); + + ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_PWRDN_SW | st->ctrl_hb); + + kfree(st->channels); + kfree(st->iio_attr); + + return 0; +} + +static const struct spi_device_id ad7280_id[] = { + {"ad7280a", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7280_id); + +static struct spi_driver ad7280_driver = { + .driver = { + .name = "ad7280", + .owner = THIS_MODULE, + }, + .probe = ad7280_probe, + .remove = ad7280_remove, + .id_table = ad7280_id, +}; +module_spi_driver(ad7280_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7280A"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/adc/ad7280a.h b/kernel/drivers/staging/iio/adc/ad7280a.h new file mode 100644 index 000000000..732347a9b --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7280a.h @@ -0,0 +1,38 @@ +/* + * AD7280A Lithium Ion Battery Monitoring System + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_ADC_AD7280_H_ +#define IIO_ADC_AD7280_H_ + +/* + * TODO: struct ad7280_platform_data needs to go into include/linux/iio + */ + +#define AD7280A_ACQ_TIME_400ns 0 +#define AD7280A_ACQ_TIME_800ns 1 +#define AD7280A_ACQ_TIME_1200ns 2 +#define AD7280A_ACQ_TIME_1600ns 3 + +#define AD7280A_CONV_AVG_DIS 0 +#define AD7280A_CONV_AVG_2 1 +#define AD7280A_CONV_AVG_4 2 +#define AD7280A_CONV_AVG_8 3 + +#define AD7280A_ALERT_REMOVE_VIN5 BIT(2) +#define AD7280A_ALERT_REMOVE_VIN4_VIN5 BIT(3) +#define AD7280A_ALERT_REMOVE_AUX5 BIT(0) +#define AD7280A_ALERT_REMOVE_AUX4_AUX5 BIT(1) + +struct ad7280_platform_data { + unsigned acquisition_time; + unsigned conversion_averaging; + unsigned chain_last_alert_ignore; + bool thermistor_term_en; +}; + +#endif /* IIO_ADC_AD7280_H_ */ diff --git a/kernel/drivers/staging/iio/adc/ad7606.h b/kernel/drivers/staging/iio/adc/ad7606.h new file mode 100644 index 000000000..ec89d055c --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7606.h @@ -0,0 +1,104 @@ +/* + * AD7606 ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_ADC_AD7606_H_ +#define IIO_ADC_AD7606_H_ + +/* + * TODO: struct ad7606_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad7606_platform_data - platform/board specific information + * @default_os: default oversampling value {0, 2, 4, 8, 16, 32, 64} + * @default_range: default range +/-{5000, 10000} mVolt + * @gpio_convst: number of gpio connected to the CONVST pin + * @gpio_reset: gpio connected to the RESET pin, if not used set to -1 + * @gpio_range: gpio connected to the RANGE pin, if not used set to -1 + * @gpio_os0: gpio connected to the OS0 pin, if not used set to -1 + * @gpio_os1: gpio connected to the OS1 pin, if not used set to -1 + * @gpio_os2: gpio connected to the OS2 pin, if not used set to -1 + * @gpio_frstdata: gpio connected to the FRSTDAT pin, if not used set to -1 + * @gpio_stby: gpio connected to the STBY pin, if not used set to -1 + */ + +struct ad7606_platform_data { + unsigned default_os; + unsigned default_range; + unsigned gpio_convst; + unsigned gpio_reset; + unsigned gpio_range; + unsigned gpio_os0; + unsigned gpio_os1; + unsigned gpio_os2; + unsigned gpio_frstdata; + unsigned gpio_stby; +}; + +/** + * struct ad7606_chip_info - chip specific information + * @name: identification string for chip + * @int_vref_mv: the internal reference voltage + * @channels: channel specification + * @num_channels: number of channels + */ + +struct ad7606_chip_info { + const char *name; + u16 int_vref_mv; + const struct iio_chan_spec *channels; + unsigned num_channels; +}; + +/** + * struct ad7606_state - driver instance specific data + */ + +struct ad7606_state { + struct device *dev; + const struct ad7606_chip_info *chip_info; + struct ad7606_platform_data *pdata; + struct regulator *reg; + struct work_struct poll_work; + wait_queue_head_t wq_data_avail; + const struct ad7606_bus_ops *bops; + unsigned range; + unsigned oversampling; + bool done; + void __iomem *base_address; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + + unsigned short data[8] ____cacheline_aligned; +}; + +struct ad7606_bus_ops { + /* more methods added in future? */ + int (*read_block)(struct device *, int, void *); +}; + +void ad7606_suspend(struct iio_dev *indio_dev); +void ad7606_resume(struct iio_dev *indio_dev); +struct iio_dev *ad7606_probe(struct device *dev, int irq, + void __iomem *base_address, unsigned id, + const struct ad7606_bus_ops *bops); +int ad7606_remove(struct iio_dev *indio_dev, int irq); +int ad7606_reset(struct ad7606_state *st); + +enum ad7606_supported_device_ids { + ID_AD7606_8, + ID_AD7606_6, + ID_AD7606_4 +}; + +int ad7606_register_ring_funcs_and_init(struct iio_dev *indio_dev); +void ad7606_ring_cleanup(struct iio_dev *indio_dev); +#endif /* IIO_ADC_AD7606_H_ */ diff --git a/kernel/drivers/staging/iio/adc/ad7606_core.c b/kernel/drivers/staging/iio/adc/ad7606_core.c new file mode 100644 index 000000000..bf2c80131 --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7606_core.c @@ -0,0 +1,603 @@ +/* + * AD7606 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ad7606.h" + +int ad7606_reset(struct ad7606_state *st) +{ + if (gpio_is_valid(st->pdata->gpio_reset)) { + gpio_set_value(st->pdata->gpio_reset, 1); + ndelay(100); /* t_reset >= 100ns */ + gpio_set_value(st->pdata->gpio_reset, 0); + return 0; + } + + return -ENODEV; +} + +static int ad7606_scan_direct(struct iio_dev *indio_dev, unsigned ch) +{ + struct ad7606_state *st = iio_priv(indio_dev); + int ret; + + st->done = false; + gpio_set_value(st->pdata->gpio_convst, 1); + + ret = wait_event_interruptible(st->wq_data_avail, st->done); + if (ret) + goto error_ret; + + if (gpio_is_valid(st->pdata->gpio_frstdata)) { + ret = st->bops->read_block(st->dev, 1, st->data); + if (ret) + goto error_ret; + if (!gpio_get_value(st->pdata->gpio_frstdata)) { + /* This should never happen */ + ad7606_reset(st); + ret = -EIO; + goto error_ret; + } + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels - 1, &st->data[1]); + if (ret) + goto error_ret; + } else { + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels, st->data); + if (ret) + goto error_ret; + } + + ret = st->data[ch]; + +error_ret: + gpio_set_value(st->pdata->gpio_convst, 0); + + return ret; +} + +static int ad7606_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7606_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 = ad7606_scan_direct(indio_dev, chan->address); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + *val = (short) ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->range * 2; + *val2 = st->chip_info->channels[0].scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + } + return -EINVAL; +} + +static ssize_t ad7606_show_range(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7606_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", st->range); +} + +static ssize_t ad7606_store_range(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7606_state *st = iio_priv(indio_dev); + unsigned long lval; + int ret; + + ret = kstrtoul(buf, 10, &lval); + if (ret) + return ret; + + if (!(lval == 5000 || lval == 10000)) { + dev_err(dev, "range is not supported\n"); + return -EINVAL; + } + mutex_lock(&indio_dev->mlock); + gpio_set_value(st->pdata->gpio_range, lval == 10000); + st->range = lval; + mutex_unlock(&indio_dev->mlock); + + return count; +} + +static IIO_DEVICE_ATTR(in_voltage_range, S_IRUGO | S_IWUSR, + ad7606_show_range, ad7606_store_range, 0); +static IIO_CONST_ATTR(in_voltage_range_available, "5000 10000"); + +static ssize_t ad7606_show_oversampling_ratio(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7606_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", st->oversampling); +} + +static int ad7606_oversampling_get_index(unsigned val) +{ + unsigned char supported[] = {0, 2, 4, 8, 16, 32, 64}; + int i; + + for (i = 0; i < ARRAY_SIZE(supported); i++) + if (val == supported[i]) + return i; + + return -EINVAL; +} + +static ssize_t ad7606_store_oversampling_ratio(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7606_state *st = iio_priv(indio_dev); + unsigned long lval; + int ret; + + ret = kstrtoul(buf, 10, &lval); + if (ret) + return ret; + + ret = ad7606_oversampling_get_index(lval); + if (ret < 0) { + dev_err(dev, "oversampling %lu is not supported\n", lval); + return ret; + } + + mutex_lock(&indio_dev->mlock); + gpio_set_value(st->pdata->gpio_os0, (ret >> 0) & 1); + gpio_set_value(st->pdata->gpio_os1, (ret >> 1) & 1); + gpio_set_value(st->pdata->gpio_os1, (ret >> 2) & 1); + st->oversampling = lval; + mutex_unlock(&indio_dev->mlock); + + return count; +} + +static IIO_DEVICE_ATTR(oversampling_ratio, S_IRUGO | S_IWUSR, + ad7606_show_oversampling_ratio, + ad7606_store_oversampling_ratio, 0); +static IIO_CONST_ATTR(oversampling_ratio_available, "0 2 4 8 16 32 64"); + +static struct attribute *ad7606_attributes_os_and_range[] = { + &iio_dev_attr_in_voltage_range.dev_attr.attr, + &iio_const_attr_in_voltage_range_available.dev_attr.attr, + &iio_dev_attr_oversampling_ratio.dev_attr.attr, + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_os_and_range = { + .attrs = ad7606_attributes_os_and_range, +}; + +static struct attribute *ad7606_attributes_os[] = { + &iio_dev_attr_oversampling_ratio.dev_attr.attr, + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_os = { + .attrs = ad7606_attributes_os, +}; + +static struct attribute *ad7606_attributes_range[] = { + &iio_dev_attr_in_voltage_range.dev_attr.attr, + &iio_const_attr_in_voltage_range_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_range = { + .attrs = ad7606_attributes_range, +}; + +#define AD7606_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = num, \ + .address = num, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\ + .scan_index = num, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec ad7606_8_channels[] = { + AD7606_CHANNEL(0), + AD7606_CHANNEL(1), + AD7606_CHANNEL(2), + AD7606_CHANNEL(3), + AD7606_CHANNEL(4), + AD7606_CHANNEL(5), + AD7606_CHANNEL(6), + AD7606_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static const struct iio_chan_spec ad7606_6_channels[] = { + AD7606_CHANNEL(0), + AD7606_CHANNEL(1), + AD7606_CHANNEL(2), + AD7606_CHANNEL(3), + AD7606_CHANNEL(4), + AD7606_CHANNEL(5), + IIO_CHAN_SOFT_TIMESTAMP(6), +}; + +static const struct iio_chan_spec ad7606_4_channels[] = { + AD7606_CHANNEL(0), + AD7606_CHANNEL(1), + AD7606_CHANNEL(2), + AD7606_CHANNEL(3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct ad7606_chip_info ad7606_chip_info_tbl[] = { + /* + * More devices added in future + */ + [ID_AD7606_8] = { + .name = "ad7606", + .int_vref_mv = 2500, + .channels = ad7606_8_channels, + .num_channels = 8, + }, + [ID_AD7606_6] = { + .name = "ad7606-6", + .int_vref_mv = 2500, + .channels = ad7606_6_channels, + .num_channels = 6, + }, + [ID_AD7606_4] = { + .name = "ad7606-4", + .int_vref_mv = 2500, + .channels = ad7606_4_channels, + .num_channels = 4, + }, +}; + +static int ad7606_request_gpios(struct ad7606_state *st) +{ + struct gpio gpio_array[3] = { + [0] = { + .gpio = st->pdata->gpio_os0, + .flags = GPIOF_DIR_OUT | ((st->oversampling & 1) ? + GPIOF_INIT_HIGH : GPIOF_INIT_LOW), + .label = "AD7606_OS0", + }, + [1] = { + .gpio = st->pdata->gpio_os1, + .flags = GPIOF_DIR_OUT | ((st->oversampling & 2) ? + GPIOF_INIT_HIGH : GPIOF_INIT_LOW), + .label = "AD7606_OS1", + }, + [2] = { + .gpio = st->pdata->gpio_os2, + .flags = GPIOF_DIR_OUT | ((st->oversampling & 4) ? + GPIOF_INIT_HIGH : GPIOF_INIT_LOW), + .label = "AD7606_OS2", + }, + }; + int ret; + + if (gpio_is_valid(st->pdata->gpio_convst)) { + ret = gpio_request_one(st->pdata->gpio_convst, + GPIOF_OUT_INIT_LOW, + "AD7606_CONVST"); + if (ret) { + dev_err(st->dev, "failed to request GPIO CONVST\n"); + goto error_ret; + } + } else { + ret = -EIO; + goto error_ret; + } + + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) { + ret = gpio_request_array(gpio_array, ARRAY_SIZE(gpio_array)); + if (ret < 0) + goto error_free_convst; + } + + if (gpio_is_valid(st->pdata->gpio_reset)) { + ret = gpio_request_one(st->pdata->gpio_reset, + GPIOF_OUT_INIT_LOW, + "AD7606_RESET"); + if (ret < 0) + goto error_free_os; + } + + if (gpio_is_valid(st->pdata->gpio_range)) { + ret = gpio_request_one(st->pdata->gpio_range, GPIOF_DIR_OUT | + ((st->range == 10000) ? GPIOF_INIT_HIGH : + GPIOF_INIT_LOW), "AD7606_RANGE"); + if (ret < 0) + goto error_free_reset; + } + if (gpio_is_valid(st->pdata->gpio_stby)) { + ret = gpio_request_one(st->pdata->gpio_stby, + GPIOF_OUT_INIT_HIGH, + "AD7606_STBY"); + if (ret < 0) + goto error_free_range; + } + + if (gpio_is_valid(st->pdata->gpio_frstdata)) { + ret = gpio_request_one(st->pdata->gpio_frstdata, GPIOF_IN, + "AD7606_FRSTDATA"); + if (ret < 0) + goto error_free_stby; + } + + return 0; + +error_free_stby: + if (gpio_is_valid(st->pdata->gpio_stby)) + gpio_free(st->pdata->gpio_stby); +error_free_range: + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_free(st->pdata->gpio_range); +error_free_reset: + if (gpio_is_valid(st->pdata->gpio_reset)) + gpio_free(st->pdata->gpio_reset); +error_free_os: + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) + gpio_free_array(gpio_array, ARRAY_SIZE(gpio_array)); +error_free_convst: + gpio_free(st->pdata->gpio_convst); +error_ret: + return ret; +} + +static void ad7606_free_gpios(struct ad7606_state *st) +{ + if (gpio_is_valid(st->pdata->gpio_frstdata)) + gpio_free(st->pdata->gpio_frstdata); + if (gpio_is_valid(st->pdata->gpio_stby)) + gpio_free(st->pdata->gpio_stby); + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_free(st->pdata->gpio_range); + if (gpio_is_valid(st->pdata->gpio_reset)) + gpio_free(st->pdata->gpio_reset); + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) { + gpio_free(st->pdata->gpio_os2); + gpio_free(st->pdata->gpio_os1); + gpio_free(st->pdata->gpio_os0); + } + gpio_free(st->pdata->gpio_convst); +} + +/** + * Interrupt handler + */ +static irqreturn_t ad7606_interrupt(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct ad7606_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) { + schedule_work(&st->poll_work); + } else { + st->done = true; + wake_up_interruptible(&st->wq_data_avail); + } + + return IRQ_HANDLED; +}; + +static const struct iio_info ad7606_info_no_os_or_range = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, +}; + +static const struct iio_info ad7606_info_os_and_range = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, + .attrs = &ad7606_attribute_group_os_and_range, +}; + +static const struct iio_info ad7606_info_os = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, + .attrs = &ad7606_attribute_group_os, +}; + +static const struct iio_info ad7606_info_range = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, + .attrs = &ad7606_attribute_group_range, +}; + +struct iio_dev *ad7606_probe(struct device *dev, int irq, + void __iomem *base_address, + unsigned id, + const struct ad7606_bus_ops *bops) +{ + struct ad7606_platform_data *pdata = dev->platform_data; + struct ad7606_state *st; + int ret; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + + st = iio_priv(indio_dev); + + st->dev = dev; + st->bops = bops; + st->base_address = base_address; + st->range = pdata->default_range == 10000 ? 10000 : 5000; + + ret = ad7606_oversampling_get_index(pdata->default_os); + if (ret < 0) { + dev_warn(dev, "oversampling %d is not supported\n", + pdata->default_os); + st->oversampling = 0; + } else { + st->oversampling = pdata->default_os; + } + + st->reg = devm_regulator_get(dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ERR_PTR(ret); + } + + st->pdata = pdata; + st->chip_info = &ad7606_chip_info_tbl[id]; + + indio_dev->dev.parent = dev; + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) { + if (gpio_is_valid(st->pdata->gpio_range)) + indio_dev->info = &ad7606_info_os_and_range; + else + indio_dev->info = &ad7606_info_os; + } else { + if (gpio_is_valid(st->pdata->gpio_range)) + indio_dev->info = &ad7606_info_range; + else + indio_dev->info = &ad7606_info_no_os_or_range; + } + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = st->chip_info->name; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + init_waitqueue_head(&st->wq_data_avail); + + ret = ad7606_request_gpios(st); + if (ret) + goto error_disable_reg; + + ret = ad7606_reset(st); + if (ret) + dev_warn(st->dev, "failed to RESET: no RESET GPIO specified\n"); + + ret = request_irq(irq, ad7606_interrupt, + IRQF_TRIGGER_FALLING, st->chip_info->name, indio_dev); + if (ret) + goto error_free_gpios; + + ret = ad7606_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_free_irq; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unregister_ring; + + return indio_dev; +error_unregister_ring: + ad7606_ring_cleanup(indio_dev); + +error_free_irq: + free_irq(irq, indio_dev); + +error_free_gpios: + ad7606_free_gpios(st); + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + return ERR_PTR(ret); +} + +int ad7606_remove(struct iio_dev *indio_dev, int irq) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + ad7606_ring_cleanup(indio_dev); + + free_irq(irq, indio_dev); + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + ad7606_free_gpios(st); + + return 0; +} + +void ad7606_suspend(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + if (gpio_is_valid(st->pdata->gpio_stby)) { + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_set_value(st->pdata->gpio_range, 1); + gpio_set_value(st->pdata->gpio_stby, 0); + } +} + +void ad7606_resume(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + if (gpio_is_valid(st->pdata->gpio_stby)) { + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_set_value(st->pdata->gpio_range, + st->range == 10000); + + gpio_set_value(st->pdata->gpio_stby, 1); + ad7606_reset(st); + } +} + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/adc/ad7606_par.c b/kernel/drivers/staging/iio/adc/ad7606_par.c new file mode 100644 index 000000000..9e24b4d44 --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7606_par.c @@ -0,0 +1,152 @@ +/* + * AD7606 Parallel Interface ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include + +#include +#include "ad7606.h" + +static int ad7606_par16_read_block(struct device *dev, + int count, void *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct ad7606_state *st = iio_priv(indio_dev); + + insw((unsigned long) st->base_address, buf, count); + + return 0; +} + +static const struct ad7606_bus_ops ad7606_par16_bops = { + .read_block = ad7606_par16_read_block, +}; + +static int ad7606_par8_read_block(struct device *dev, + int count, void *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct ad7606_state *st = iio_priv(indio_dev); + + insb((unsigned long) st->base_address, buf, count * 2); + + return 0; +} + +static const struct ad7606_bus_ops ad7606_par8_bops = { + .read_block = ad7606_par8_read_block, +}; + +static int ad7606_par_probe(struct platform_device *pdev) +{ + struct resource *res; + struct iio_dev *indio_dev; + void __iomem *addr; + resource_size_t remap_size; + int irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + addr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(addr)) + return PTR_ERR(addr); + + remap_size = resource_size(res); + + indio_dev = ad7606_probe(&pdev->dev, irq, addr, + platform_get_device_id(pdev)->driver_data, + remap_size > 1 ? &ad7606_par16_bops : + &ad7606_par8_bops); + + if (IS_ERR(indio_dev)) + return PTR_ERR(indio_dev); + + platform_set_drvdata(pdev, indio_dev); + + return 0; +} + +static int ad7606_par_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + ad7606_remove(indio_dev, platform_get_irq(pdev, 0)); + + return 0; +} + +#ifdef CONFIG_PM +static int ad7606_par_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_suspend(indio_dev); + + return 0; +} + +static int ad7606_par_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_resume(indio_dev); + + return 0; +} + +static const struct dev_pm_ops ad7606_pm_ops = { + .suspend = ad7606_par_suspend, + .resume = ad7606_par_resume, +}; +#define AD7606_PAR_PM_OPS (&ad7606_pm_ops) + +#else +#define AD7606_PAR_PM_OPS NULL +#endif /* CONFIG_PM */ + +static struct platform_device_id ad7606_driver_ids[] = { + { + .name = "ad7606-8", + .driver_data = ID_AD7606_8, + }, { + .name = "ad7606-6", + .driver_data = ID_AD7606_6, + }, { + .name = "ad7606-4", + .driver_data = ID_AD7606_4, + }, + { } +}; + +MODULE_DEVICE_TABLE(platform, ad7606_driver_ids); + +static struct platform_driver ad7606_driver = { + .probe = ad7606_par_probe, + .remove = ad7606_par_remove, + .id_table = ad7606_driver_ids, + .driver = { + .name = "ad7606", + .pm = AD7606_PAR_PM_OPS, + }, +}; + +module_platform_driver(ad7606_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/adc/ad7606_ring.c b/kernel/drivers/staging/iio/adc/ad7606_ring.c new file mode 100644 index 000000000..a6f8eb112 --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7606_ring.c @@ -0,0 +1,101 @@ +/* + * Copyright 2011-2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ad7606.h" + +/** + * ad7606_trigger_handler_th() th/bh of trigger launched polling to ring buffer + * + **/ +static irqreturn_t ad7606_trigger_handler_th_bh(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct ad7606_state *st = iio_priv(pf->indio_dev); + + gpio_set_value(st->pdata->gpio_convst, 1); + + return IRQ_HANDLED; +} + +/** + * ad7606_poll_bh_to_ring() bh of trigger launched polling to ring buffer + * @work_s: the work struct through which this was scheduled + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + * I think the one copy of this at a time was to avoid problems if the + * trigger was set far too high and the reads then locked up the computer. + **/ +static void ad7606_poll_bh_to_ring(struct work_struct *work_s) +{ + struct ad7606_state *st = container_of(work_s, struct ad7606_state, + poll_work); + struct iio_dev *indio_dev = iio_priv_to_dev(st); + __u8 *buf; + int ret; + + buf = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!buf) + return; + + if (gpio_is_valid(st->pdata->gpio_frstdata)) { + ret = st->bops->read_block(st->dev, 1, buf); + if (ret) + goto done; + if (!gpio_get_value(st->pdata->gpio_frstdata)) { + /* This should never happen. However + * some signal glitch caused by bad PCB desgin or + * electrostatic discharge, could cause an extra read + * or clock. This allows recovery. + */ + ad7606_reset(st); + goto done; + } + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels - 1, buf + 2); + if (ret) + goto done; + } else { + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels, buf); + if (ret) + goto done; + } + + iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns()); +done: + gpio_set_value(st->pdata->gpio_convst, 0); + iio_trigger_notify_done(indio_dev->trig); + kfree(buf); +} + +int ad7606_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + INIT_WORK(&st->poll_work, &ad7606_poll_bh_to_ring); + + return iio_triggered_buffer_setup(indio_dev, + &ad7606_trigger_handler_th_bh, &ad7606_trigger_handler_th_bh, + NULL); +} + +void ad7606_ring_cleanup(struct iio_dev *indio_dev) +{ + iio_triggered_buffer_cleanup(indio_dev); +} diff --git a/kernel/drivers/staging/iio/adc/ad7606_spi.c b/kernel/drivers/staging/iio/adc/ad7606_spi.c new file mode 100644 index 000000000..7303983e6 --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7606_spi.c @@ -0,0 +1,116 @@ +/* + * AD7606 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include + +#include +#include "ad7606.h" + +#define MAX_SPI_FREQ_HZ 23500000 /* VDRIVE above 4.75 V */ + +static int ad7606_spi_read_block(struct device *dev, + int count, void *buf) +{ + struct spi_device *spi = to_spi_device(dev); + int i, ret; + unsigned short *data = buf; + + ret = spi_read(spi, buf, count * 2); + if (ret < 0) { + dev_err(&spi->dev, "SPI read error\n"); + return ret; + } + + for (i = 0; i < count; i++) + data[i] = be16_to_cpu(data[i]); + + return 0; +} + +static const struct ad7606_bus_ops ad7606_spi_bops = { + .read_block = ad7606_spi_read_block, +}; + +static int ad7606_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + + indio_dev = ad7606_probe(&spi->dev, spi->irq, NULL, + spi_get_device_id(spi)->driver_data, + &ad7606_spi_bops); + + if (IS_ERR(indio_dev)) + return PTR_ERR(indio_dev); + + spi_set_drvdata(spi, indio_dev); + + return 0; +} + +static int ad7606_spi_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = dev_get_drvdata(&spi->dev); + + return ad7606_remove(indio_dev, spi->irq); +} + +#ifdef CONFIG_PM +static int ad7606_spi_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_suspend(indio_dev); + + return 0; +} + +static int ad7606_spi_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_resume(indio_dev); + + return 0; +} + +static const struct dev_pm_ops ad7606_pm_ops = { + .suspend = ad7606_spi_suspend, + .resume = ad7606_spi_resume, +}; +#define AD7606_SPI_PM_OPS (&ad7606_pm_ops) + +#else +#define AD7606_SPI_PM_OPS NULL +#endif + +static const struct spi_device_id ad7606_id[] = { + {"ad7606-8", ID_AD7606_8}, + {"ad7606-6", ID_AD7606_6}, + {"ad7606-4", ID_AD7606_4}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7606_id); + +static struct spi_driver ad7606_driver = { + .driver = { + .name = "ad7606", + .owner = THIS_MODULE, + .pm = AD7606_SPI_PM_OPS, + }, + .probe = ad7606_spi_probe, + .remove = ad7606_spi_remove, + .id_table = ad7606_id, +}; +module_spi_driver(ad7606_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/adc/ad7780.c b/kernel/drivers/staging/iio/adc/ad7780.c new file mode 100644 index 000000000..9f03fe3ee --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7780.c @@ -0,0 +1,276 @@ +/* + * AD7170/AD7171 and AD7780/AD7781 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ad7780.h" + +#define AD7780_RDY BIT(7) +#define AD7780_FILTER BIT(6) +#define AD7780_ERR BIT(5) +#define AD7780_ID1 BIT(4) +#define AD7780_ID0 BIT(3) +#define AD7780_GAIN BIT(2) +#define AD7780_PAT1 BIT(1) +#define AD7780_PAT0 BIT(0) + +struct ad7780_chip_info { + struct iio_chan_spec channel; + unsigned int pattern_mask; + unsigned int pattern; +}; + +struct ad7780_state { + const struct ad7780_chip_info *chip_info; + struct regulator *reg; + int powerdown_gpio; + unsigned int gain; + u16 int_vref_mv; + + struct ad_sigma_delta sd; +}; + +enum ad7780_supported_device_ids { + ID_AD7170, + ID_AD7171, + ID_AD7780, + ID_AD7781, +}; + +static struct ad7780_state *ad_sigma_delta_to_ad7780(struct ad_sigma_delta *sd) +{ + return container_of(sd, struct ad7780_state, sd); +} + +static int ad7780_set_mode(struct ad_sigma_delta *sigma_delta, + enum ad_sigma_delta_mode mode) +{ + struct ad7780_state *st = ad_sigma_delta_to_ad7780(sigma_delta); + unsigned val; + + switch (mode) { + case AD_SD_MODE_SINGLE: + case AD_SD_MODE_CONTINUOUS: + val = 1; + break; + default: + val = 0; + break; + } + + if (gpio_is_valid(st->powerdown_gpio)) + gpio_set_value(st->powerdown_gpio, val); + + return 0; +} + +static int ad7780_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7780_state *st = iio_priv(indio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + return ad_sigma_delta_single_conversion(indio_dev, chan, val); + case IIO_CHAN_INFO_SCALE: + *val = st->int_vref_mv * st->gain; + *val2 = chan->scan_type.realbits - 1; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + *val -= (1 << (chan->scan_type.realbits - 1)); + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ad7780_postprocess_sample(struct ad_sigma_delta *sigma_delta, + unsigned int raw_sample) +{ + struct ad7780_state *st = ad_sigma_delta_to_ad7780(sigma_delta); + const struct ad7780_chip_info *chip_info = st->chip_info; + + if ((raw_sample & AD7780_ERR) || + ((raw_sample & chip_info->pattern_mask) != chip_info->pattern)) + return -EIO; + + if (raw_sample & AD7780_GAIN) + st->gain = 1; + else + st->gain = 128; + + return 0; +} + +static const struct ad_sigma_delta_info ad7780_sigma_delta_info = { + .set_mode = ad7780_set_mode, + .postprocess_sample = ad7780_postprocess_sample, + .has_registers = false, +}; + +#define AD7780_CHANNEL(bits, wordsize) \ + AD_SD_CHANNEL(1, 0, 0, bits, 32, wordsize - bits) + +static const struct ad7780_chip_info ad7780_chip_info_tbl[] = { + [ID_AD7170] = { + .channel = AD7780_CHANNEL(12, 24), + .pattern = 0x5, + .pattern_mask = 0x7, + }, + [ID_AD7171] = { + .channel = AD7780_CHANNEL(16, 24), + .pattern = 0x5, + .pattern_mask = 0x7, + }, + [ID_AD7780] = { + .channel = AD7780_CHANNEL(24, 32), + .pattern = 0x1, + .pattern_mask = 0x3, + }, + [ID_AD7781] = { + .channel = AD7780_CHANNEL(20, 32), + .pattern = 0x1, + .pattern_mask = 0x3, + }, +}; + +static const struct iio_info ad7780_info = { + .read_raw = &ad7780_read_raw, + .driver_module = THIS_MODULE, +}; + +static int ad7780_probe(struct spi_device *spi) +{ + struct ad7780_platform_data *pdata = spi->dev.platform_data; + struct ad7780_state *st; + struct iio_dev *indio_dev; + int ret, voltage_uv = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->gain = 1; + + ad_sd_init(&st->sd, indio_dev, spi, &ad7780_sigma_delta_info); + + st->reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + voltage_uv = regulator_get_voltage(st->reg); + } + + st->chip_info = + &ad7780_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (voltage_uv) + st->int_vref_mv = voltage_uv / 1000; + else + dev_warn(&spi->dev, "reference voltage unspecified\n"); + + 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->channel; + indio_dev->num_channels = 1; + indio_dev->info = &ad7780_info; + + if (pdata && gpio_is_valid(pdata->gpio_pdrst)) { + + ret = devm_gpio_request_one(&spi->dev, pdata->gpio_pdrst, + GPIOF_OUT_INIT_LOW, "AD7780 /PDRST"); + if (ret) { + dev_err(&spi->dev, "failed to request GPIO PDRST\n"); + goto error_disable_reg; + } + st->powerdown_gpio = pdata->gpio_pdrst; + } else { + st->powerdown_gpio = -1; + } + + ret = ad_sd_setup_buffer_and_trigger(indio_dev); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_cleanup_buffer_and_trigger; + + return 0; + +error_cleanup_buffer_and_trigger: + ad_sd_cleanup_buffer_and_trigger(indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ad7780_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7780_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + ad_sd_cleanup_buffer_and_trigger(indio_dev); + + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad7780_id[] = { + {"ad7170", ID_AD7170}, + {"ad7171", ID_AD7171}, + {"ad7780", ID_AD7780}, + {"ad7781", ID_AD7781}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7780_id); + +static struct spi_driver ad7780_driver = { + .driver = { + .name = "ad7780", + .owner = THIS_MODULE, + }, + .probe = ad7780_probe, + .remove = ad7780_remove, + .id_table = ad7780_id, +}; +module_spi_driver(ad7780_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7780 and similar ADCs"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/adc/ad7780.h b/kernel/drivers/staging/iio/adc/ad7780.h new file mode 100644 index 000000000..67e511c3d --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7780.h @@ -0,0 +1,30 @@ +/* + * AD7780/AD7781 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef IIO_ADC_AD7780_H_ +#define IIO_ADC_AD7780_H_ + +/* + * TODO: struct ad7780_platform_data needs to go into include/linux/iio + */ + +/* NOTE: + * The AD7780 doesn't feature a dedicated SPI chip select, in addition it + * 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 combined with a dedicated GPIO to control the + * power down reset signal of the AD7780. + * + * The DOUT/RDY output must also be wired to an interrupt capable GPIO. + */ + +struct ad7780_platform_data { + u16 vref_mv; + int gpio_pdrst; +}; + +#endif /* IIO_ADC_AD7780_H_ */ diff --git a/kernel/drivers/staging/iio/adc/ad7816.c b/kernel/drivers/staging/iio/adc/ad7816.c new file mode 100644 index 000000000..48b1c3740 --- /dev/null +++ b/kernel/drivers/staging/iio/adc/ad7816.c @@ -0,0 +1,446 @@ +/* + * AD7816 digital temperature sensor driver supporting AD7816/7/8 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * AD7816 config masks + */ +#define AD7816_FULL 0x1 +#define AD7816_PD 0x2 +#define AD7816_CS_MASK 0x7 +#define AD7816_CS_MAX 0x4 + +/* + * AD7816 temperature masks + */ +#define AD7816_VALUE_OFFSET 6 +#define AD7816_BOUND_VALUE_BASE 0x8 +#define AD7816_BOUND_VALUE_MIN -95 +#define AD7816_BOUND_VALUE_MAX 152 +#define AD7816_TEMP_FLOAT_OFFSET 2 +#define AD7816_TEMP_FLOAT_MASK 0x3 + + +/* + * struct ad7816_chip_info - chip specific information + */ + +struct ad7816_chip_info { + struct spi_device *spi_dev; + u16 rdwr_pin; + u16 convert_pin; + u16 busy_pin; + u8 oti_data[AD7816_CS_MAX+1]; + u8 channel_id; /* 0 always be temperature */ + u8 mode; +}; + +/* + * ad7816 data access by SPI + */ +static int ad7816_spi_read(struct ad7816_chip_info *chip, u16 *data) +{ + struct spi_device *spi_dev = chip->spi_dev; + int ret = 0; + + gpio_set_value(chip->rdwr_pin, 1); + gpio_set_value(chip->rdwr_pin, 0); + ret = spi_write(spi_dev, &chip->channel_id, sizeof(chip->channel_id)); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI channel setting error\n"); + return ret; + } + gpio_set_value(chip->rdwr_pin, 1); + + + if (chip->mode == AD7816_PD) { /* operating mode 2 */ + gpio_set_value(chip->convert_pin, 1); + gpio_set_value(chip->convert_pin, 0); + } else { /* operating mode 1 */ + gpio_set_value(chip->convert_pin, 0); + gpio_set_value(chip->convert_pin, 1); + } + + while (gpio_get_value(chip->busy_pin)) + cpu_relax(); + + gpio_set_value(chip->rdwr_pin, 0); + gpio_set_value(chip->rdwr_pin, 1); + ret = spi_read(spi_dev, (u8 *)data, sizeof(*data)); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI data read error\n"); + return ret; + } + + *data = be16_to_cpu(*data); + + return ret; +} + +static int ad7816_spi_write(struct ad7816_chip_info *chip, u8 data) +{ + struct spi_device *spi_dev = chip->spi_dev; + int ret = 0; + + gpio_set_value(chip->rdwr_pin, 1); + gpio_set_value(chip->rdwr_pin, 0); + ret = spi_write(spi_dev, &data, sizeof(data)); + if (ret < 0) + dev_err(&spi_dev->dev, "SPI oti data write error\n"); + + return ret; +} + +static ssize_t ad7816_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + + if (chip->mode) + return sprintf(buf, "power-save\n"); + return sprintf(buf, "full\n"); +} + +static ssize_t ad7816_store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + + if (strcmp(buf, "full")) { + gpio_set_value(chip->rdwr_pin, 1); + chip->mode = AD7816_FULL; + } else { + gpio_set_value(chip->rdwr_pin, 0); + chip->mode = AD7816_PD; + } + + return len; +} + +static IIO_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, + ad7816_show_mode, + ad7816_store_mode, + 0); + +static ssize_t ad7816_show_available_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "full\npower-save\n"); +} + +static IIO_DEVICE_ATTR(available_modes, S_IRUGO, ad7816_show_available_modes, + NULL, 0); + +static ssize_t ad7816_show_channel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->channel_id); +} + +static ssize_t ad7816_store_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + unsigned long data; + int ret; + + ret = kstrtoul(buf, 10, &data); + if (ret) + return ret; + + if (data > AD7816_CS_MAX && data != AD7816_CS_MASK) { + dev_err(&chip->spi_dev->dev, "Invalid channel id %lu for %s.\n", + data, indio_dev->name); + return -EINVAL; + } else if (strcmp(indio_dev->name, "ad7818") == 0 && data > 1) { + dev_err(&chip->spi_dev->dev, + "Invalid channel id %lu for ad7818.\n", data); + return -EINVAL; + } else if (strcmp(indio_dev->name, "ad7816") == 0 && data > 0) { + dev_err(&chip->spi_dev->dev, + "Invalid channel id %lu for ad7816.\n", data); + return -EINVAL; + } + + chip->channel_id = data; + + return len; +} + +static IIO_DEVICE_ATTR(channel, S_IRUGO | S_IWUSR, + ad7816_show_channel, + ad7816_store_channel, + 0); + + +static ssize_t ad7816_show_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + u16 data; + s8 value; + int ret; + + ret = ad7816_spi_read(chip, &data); + if (ret) + return -EIO; + + data >>= AD7816_VALUE_OFFSET; + + if (chip->channel_id == 0) { + value = (s8)((data >> AD7816_TEMP_FLOAT_OFFSET) - 103); + data &= AD7816_TEMP_FLOAT_MASK; + if (value < 0) + data = (1 << AD7816_TEMP_FLOAT_OFFSET) - data; + return sprintf(buf, "%d.%.2d\n", value, data * 25); + } + return sprintf(buf, "%u\n", data); +} + +static IIO_DEVICE_ATTR(value, S_IRUGO, ad7816_show_value, NULL, 0); + +static struct attribute *ad7816_attributes[] = { + &iio_dev_attr_available_modes.dev_attr.attr, + &iio_dev_attr_mode.dev_attr.attr, + &iio_dev_attr_channel.dev_attr.attr, + &iio_dev_attr_value.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7816_attribute_group = { + .attrs = ad7816_attributes, +}; + +/* + * temperature bound events + */ + +#define IIO_EVENT_CODE_AD7816_OTI IIO_UNMOD_EVENT_CODE(IIO_TEMP, \ + 0, \ + IIO_EV_TYPE_THRESH, \ + IIO_EV_DIR_FALLING) + +static irqreturn_t ad7816_event_handler(int irq, void *private) +{ + iio_push_event(private, IIO_EVENT_CODE_AD7816_OTI, iio_get_time_ns()); + return IRQ_HANDLED; +} + +static ssize_t ad7816_show_oti(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + int value; + + if (chip->channel_id > AD7816_CS_MAX) { + dev_err(dev, "Invalid oti channel id %d.\n", chip->channel_id); + return -EINVAL; + } else if (chip->channel_id == 0) { + value = AD7816_BOUND_VALUE_MIN + + (chip->oti_data[chip->channel_id] - + AD7816_BOUND_VALUE_BASE); + return sprintf(buf, "%d\n", value); + } + return sprintf(buf, "%u\n", chip->oti_data[chip->channel_id]); +} + +static inline ssize_t ad7816_set_oti(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + long value; + u8 data; + int ret; + + ret = kstrtol(buf, 10, &value); + if (ret) + return ret; + + if (chip->channel_id > AD7816_CS_MAX) { + dev_err(dev, "Invalid oti channel id %d.\n", chip->channel_id); + return -EINVAL; + } else if (chip->channel_id == 0) { + if (ret || value < AD7816_BOUND_VALUE_MIN || + value > AD7816_BOUND_VALUE_MAX) + return -EINVAL; + + data = (u8)(value - AD7816_BOUND_VALUE_MIN + + AD7816_BOUND_VALUE_BASE); + } else { + if (ret || value < AD7816_BOUND_VALUE_BASE || value > 255) + return -EINVAL; + + data = (u8)value; + } + + ret = ad7816_spi_write(chip, data); + if (ret) + return -EIO; + + chip->oti_data[chip->channel_id] = data; + + return len; +} + +static IIO_DEVICE_ATTR(oti, S_IRUGO | S_IWUSR, + ad7816_show_oti, ad7816_set_oti, 0); + +static struct attribute *ad7816_event_attributes[] = { + &iio_dev_attr_oti.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad7816_event_attribute_group = { + .attrs = ad7816_event_attributes, + .name = "events", +}; + +static const struct iio_info ad7816_info = { + .attrs = &ad7816_attribute_group, + .event_attrs = &ad7816_event_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* + * device probe and remove + */ + +static int ad7816_probe(struct spi_device *spi_dev) +{ + struct ad7816_chip_info *chip; + struct iio_dev *indio_dev; + unsigned short *pins = spi_dev->dev.platform_data; + int ret = 0; + int i; + + if (!pins) { + dev_err(&spi_dev->dev, "No necessary GPIO platform data.\n"); + return -EINVAL; + } + + indio_dev = devm_iio_device_alloc(&spi_dev->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + dev_set_drvdata(&spi_dev->dev, indio_dev); + + chip->spi_dev = spi_dev; + for (i = 0; i <= AD7816_CS_MAX; i++) + chip->oti_data[i] = 203; + chip->rdwr_pin = pins[0]; + chip->convert_pin = pins[1]; + chip->busy_pin = pins[2]; + + ret = devm_gpio_request(&spi_dev->dev, chip->rdwr_pin, + spi_get_device_id(spi_dev)->name); + if (ret) { + dev_err(&spi_dev->dev, "Fail to request rdwr gpio PIN %d.\n", + chip->rdwr_pin); + return ret; + } + gpio_direction_input(chip->rdwr_pin); + ret = devm_gpio_request(&spi_dev->dev, chip->convert_pin, + spi_get_device_id(spi_dev)->name); + if (ret) { + dev_err(&spi_dev->dev, "Fail to request convert gpio PIN %d.\n", + chip->convert_pin); + return ret; + } + gpio_direction_input(chip->convert_pin); + ret = devm_gpio_request(&spi_dev->dev, chip->busy_pin, + spi_get_device_id(spi_dev)->name); + if (ret) { + dev_err(&spi_dev->dev, "Fail to request busy gpio PIN %d.\n", + chip->busy_pin); + return ret; + } + gpio_direction_input(chip->busy_pin); + + indio_dev->name = spi_get_device_id(spi_dev)->name; + indio_dev->dev.parent = &spi_dev->dev; + indio_dev->info = &ad7816_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (spi_dev->irq) { + /* Only low trigger is supported in ad7816/7/8 */ + ret = devm_request_threaded_irq(&spi_dev->dev, spi_dev->irq, + NULL, + &ad7816_event_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + indio_dev->name, + indio_dev); + if (ret) + return ret; + } + + ret = devm_iio_device_register(&spi_dev->dev, indio_dev); + if (ret) + return ret; + + dev_info(&spi_dev->dev, "%s temperature sensor and ADC registered.\n", + indio_dev->name); + + return 0; +} + +static const struct spi_device_id ad7816_id[] = { + { "ad7816", 0 }, + { "ad7817", 0 }, + { "ad7818", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(spi, ad7816_id); + +static struct spi_driver ad7816_driver = { + .driver = { + .name = "ad7816", + .owner = THIS_MODULE, + }, + .probe = ad7816_probe, + .id_table = ad7816_id, +}; +module_spi_driver(ad7816_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Analog Devices AD7816/7/8 digital temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/adc/lpc32xx_adc.c b/kernel/drivers/staging/iio/adc/lpc32xx_adc.c new file mode 100644 index 000000000..5331c442f --- /dev/null +++ b/kernel/drivers/staging/iio/adc/lpc32xx_adc.c @@ -0,0 +1,215 @@ +/* + * lpc32xx_adc.c - Support for ADC in LPC32XX + * + * 3-channel, 10-bit ADC + * + * Copyright (C) 2011, 2012 Roland Stigge + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * LPC32XX registers definitions + */ +#define LPC32XX_ADC_SELECT(x) ((x) + 0x04) +#define LPC32XX_ADC_CTRL(x) ((x) + 0x08) +#define LPC32XX_ADC_VALUE(x) ((x) + 0x48) + +/* Bit definitions for LPC32XX_ADC_SELECT: */ +#define AD_REFm 0x00000200 /* constant, always write this value! */ +#define AD_REFp 0x00000080 /* constant, always write this value! */ +#define AD_IN 0x00000010 /* multiple of this is the */ + /* channel number: 0, 1, 2 */ +#define AD_INTERNAL 0x00000004 /* constant, always write this value! */ + +/* Bit definitions for LPC32XX_ADC_CTRL: */ +#define AD_STROBE 0x00000002 +#define AD_PDN_CTRL 0x00000004 + +/* Bit definitions for LPC32XX_ADC_VALUE: */ +#define ADC_VALUE_MASK 0x000003FF + +#define MOD_NAME "lpc32xx-adc" + +struct lpc32xx_adc_info { + void __iomem *adc_base; + struct clk *clk; + struct completion completion; + + u32 value; +}; + +static int lpc32xx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct lpc32xx_adc_info *info = iio_priv(indio_dev); + + if (mask == IIO_CHAN_INFO_RAW) { + mutex_lock(&indio_dev->mlock); + clk_enable(info->clk); + /* Measurement setup */ + __raw_writel(AD_INTERNAL | (chan->address) | AD_REFp | AD_REFm, + LPC32XX_ADC_SELECT(info->adc_base)); + /* Trigger conversion */ + __raw_writel(AD_PDN_CTRL | AD_STROBE, + LPC32XX_ADC_CTRL(info->adc_base)); + wait_for_completion(&info->completion); /* set by ISR */ + clk_disable(info->clk); + *val = info->value; + mutex_unlock(&indio_dev->mlock); + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info lpc32xx_adc_iio_info = { + .read_raw = &lpc32xx_read_raw, + .driver_module = THIS_MODULE, +}; + +#define LPC32XX_ADC_CHANNEL(_index) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .address = AD_IN * _index, \ + .scan_index = _index, \ +} + +static const struct iio_chan_spec lpc32xx_adc_iio_channels[] = { + LPC32XX_ADC_CHANNEL(0), + LPC32XX_ADC_CHANNEL(1), + LPC32XX_ADC_CHANNEL(2), +}; + +static irqreturn_t lpc32xx_adc_isr(int irq, void *dev_id) +{ + struct lpc32xx_adc_info *info = dev_id; + + /* Read value and clear irq */ + info->value = __raw_readl(LPC32XX_ADC_VALUE(info->adc_base)) & + ADC_VALUE_MASK; + complete(&info->completion); + + return IRQ_HANDLED; +} + +static int lpc32xx_adc_probe(struct platform_device *pdev) +{ + struct lpc32xx_adc_info *info = NULL; + struct resource *res; + int retval = -ENODEV; + struct iio_dev *iodev = NULL; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform I/O memory\n"); + return -EBUSY; + } + + iodev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!iodev) + return -ENOMEM; + + info = iio_priv(iodev); + + info->adc_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!info->adc_base) { + dev_err(&pdev->dev, "failed mapping memory\n"); + return -EBUSY; + } + + info->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "failed getting clock\n"); + return PTR_ERR(info->clk); + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, "failed getting interrupt resource\n"); + return -EINVAL; + } + + retval = devm_request_irq(&pdev->dev, irq, lpc32xx_adc_isr, 0, + MOD_NAME, info); + if (retval < 0) { + dev_err(&pdev->dev, "failed requesting interrupt\n"); + return retval; + } + + platform_set_drvdata(pdev, iodev); + + init_completion(&info->completion); + + iodev->name = MOD_NAME; + iodev->dev.parent = &pdev->dev; + iodev->info = &lpc32xx_adc_iio_info; + iodev->modes = INDIO_DIRECT_MODE; + iodev->channels = lpc32xx_adc_iio_channels; + iodev->num_channels = ARRAY_SIZE(lpc32xx_adc_iio_channels); + + retval = devm_iio_device_register(&pdev->dev, iodev); + if (retval) + return retval; + + dev_info(&pdev->dev, "LPC32XX ADC driver loaded, IRQ %d\n", irq); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id lpc32xx_adc_match[] = { + { .compatible = "nxp,lpc3220-adc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, lpc32xx_adc_match); +#endif + +static struct platform_driver lpc32xx_adc_driver = { + .probe = lpc32xx_adc_probe, + .driver = { + .name = MOD_NAME, + .of_match_table = of_match_ptr(lpc32xx_adc_match), + }, +}; + +module_platform_driver(lpc32xx_adc_driver); + +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("LPC32XX ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/staging/iio/adc/mxs-lradc.c b/kernel/drivers/staging/iio/adc/mxs-lradc.c new file mode 100644 index 000000000..d7c5223f1 --- /dev/null +++ b/kernel/drivers/staging/iio/adc/mxs-lradc.c @@ -0,0 +1,1712 @@ +/* + * Freescale i.MX28 LRADC driver + * + * Copyright (c) 2012 DENX Software Engineering, GmbH. + * Marek Vasut + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "mxs-lradc" + +#define LRADC_MAX_DELAY_CHANS 4 +#define LRADC_MAX_MAPPED_CHANS 8 +#define LRADC_MAX_TOTAL_CHANS 16 + +#define LRADC_DELAY_TIMER_HZ 2000 + +/* + * Make this runtime configurable if necessary. Currently, if the buffered mode + * is enabled, the LRADC takes LRADC_DELAY_TIMER_LOOP samples of data before + * triggering IRQ. The sampling happens every (LRADC_DELAY_TIMER_PER / 2000) + * seconds. The result is that the samples arrive every 500mS. + */ +#define LRADC_DELAY_TIMER_PER 200 +#define LRADC_DELAY_TIMER_LOOP 5 + +/* + * Once the pen touches the touchscreen, the touchscreen switches from + * IRQ-driven mode to polling mode to prevent interrupt storm. The polling + * is realized by worker thread, which is called every 20 or so milliseconds. + * This gives the touchscreen enough fluence and does not strain the system + * too much. + */ +#define LRADC_TS_SAMPLE_DELAY_MS 5 + +/* + * The LRADC reads the following amount of samples from each touchscreen + * channel and the driver then computes avarage of these. + */ +#define LRADC_TS_SAMPLE_AMOUNT 4 + +enum mxs_lradc_id { + IMX23_LRADC, + IMX28_LRADC, +}; + +static const char * const mx23_lradc_irq_names[] = { + "mxs-lradc-touchscreen", + "mxs-lradc-channel0", + "mxs-lradc-channel1", + "mxs-lradc-channel2", + "mxs-lradc-channel3", + "mxs-lradc-channel4", + "mxs-lradc-channel5", + "mxs-lradc-channel6", + "mxs-lradc-channel7", +}; + +static const char * const mx28_lradc_irq_names[] = { + "mxs-lradc-touchscreen", + "mxs-lradc-thresh0", + "mxs-lradc-thresh1", + "mxs-lradc-channel0", + "mxs-lradc-channel1", + "mxs-lradc-channel2", + "mxs-lradc-channel3", + "mxs-lradc-channel4", + "mxs-lradc-channel5", + "mxs-lradc-channel6", + "mxs-lradc-channel7", + "mxs-lradc-button0", + "mxs-lradc-button1", +}; + +struct mxs_lradc_of_config { + const int irq_count; + const char * const *irq_name; + const uint32_t *vref_mv; +}; + +#define VREF_MV_BASE 1850 + +static const uint32_t mx23_vref_mv[LRADC_MAX_TOTAL_CHANS] = { + VREF_MV_BASE, /* CH0 */ + VREF_MV_BASE, /* CH1 */ + VREF_MV_BASE, /* CH2 */ + VREF_MV_BASE, /* CH3 */ + VREF_MV_BASE, /* CH4 */ + VREF_MV_BASE, /* CH5 */ + VREF_MV_BASE * 2, /* CH6 VDDIO */ + VREF_MV_BASE * 4, /* CH7 VBATT */ + VREF_MV_BASE, /* CH8 Temp sense 0 */ + VREF_MV_BASE, /* CH9 Temp sense 1 */ + VREF_MV_BASE, /* CH10 */ + VREF_MV_BASE, /* CH11 */ + VREF_MV_BASE, /* CH12 USB_DP */ + VREF_MV_BASE, /* CH13 USB_DN */ + VREF_MV_BASE, /* CH14 VBG */ + VREF_MV_BASE * 4, /* CH15 VDD5V */ +}; + +static const uint32_t mx28_vref_mv[LRADC_MAX_TOTAL_CHANS] = { + VREF_MV_BASE, /* CH0 */ + VREF_MV_BASE, /* CH1 */ + VREF_MV_BASE, /* CH2 */ + VREF_MV_BASE, /* CH3 */ + VREF_MV_BASE, /* CH4 */ + VREF_MV_BASE, /* CH5 */ + VREF_MV_BASE, /* CH6 */ + VREF_MV_BASE * 4, /* CH7 VBATT */ + VREF_MV_BASE, /* CH8 Temp sense 0 */ + VREF_MV_BASE, /* CH9 Temp sense 1 */ + VREF_MV_BASE * 2, /* CH10 VDDIO */ + VREF_MV_BASE, /* CH11 VTH */ + VREF_MV_BASE * 2, /* CH12 VDDA */ + VREF_MV_BASE, /* CH13 VDDD */ + VREF_MV_BASE, /* CH14 VBG */ + VREF_MV_BASE * 4, /* CH15 VDD5V */ +}; + +static const struct mxs_lradc_of_config mxs_lradc_of_config[] = { + [IMX23_LRADC] = { + .irq_count = ARRAY_SIZE(mx23_lradc_irq_names), + .irq_name = mx23_lradc_irq_names, + .vref_mv = mx23_vref_mv, + }, + [IMX28_LRADC] = { + .irq_count = ARRAY_SIZE(mx28_lradc_irq_names), + .irq_name = mx28_lradc_irq_names, + .vref_mv = mx28_vref_mv, + }, +}; + +enum mxs_lradc_ts { + MXS_LRADC_TOUCHSCREEN_NONE = 0, + MXS_LRADC_TOUCHSCREEN_4WIRE, + MXS_LRADC_TOUCHSCREEN_5WIRE, +}; + +/* + * Touchscreen handling + */ +enum lradc_ts_plate { + LRADC_TOUCH = 0, + LRADC_SAMPLE_X, + LRADC_SAMPLE_Y, + LRADC_SAMPLE_PRESSURE, + LRADC_SAMPLE_VALID, +}; + +enum mxs_lradc_divbytwo { + MXS_LRADC_DIV_DISABLED = 0, + MXS_LRADC_DIV_ENABLED, +}; + +struct mxs_lradc_scale { + unsigned int integer; + unsigned int nano; +}; + +struct mxs_lradc { + struct device *dev; + void __iomem *base; + int irq[13]; + + struct clk *clk; + + uint32_t *buffer; + struct iio_trigger *trig; + + struct mutex lock; + + struct completion completion; + + const uint32_t *vref_mv; + struct mxs_lradc_scale scale_avail[LRADC_MAX_TOTAL_CHANS][2]; + unsigned long is_divided; + + /* + * When the touchscreen is enabled, we give it two private virtual + * channels: #6 and #7. This means that only 6 virtual channels (instead + * of 8) will be available for buffered capture. + */ +#define TOUCHSCREEN_VCHANNEL1 7 +#define TOUCHSCREEN_VCHANNEL2 6 +#define BUFFER_VCHANS_LIMITED 0x3f +#define BUFFER_VCHANS_ALL 0xff + u8 buffer_vchans; + + /* + * Furthermore, certain LRADC channels are shared between touchscreen + * and/or touch-buttons and generic LRADC block. Therefore when using + * either of these, these channels are not available for the regular + * sampling. The shared channels are as follows: + * + * CH0 -- Touch button #0 + * CH1 -- Touch button #1 + * CH2 -- Touch screen XPUL + * CH3 -- Touch screen YPLL + * CH4 -- Touch screen XNUL + * CH5 -- Touch screen YNLR + * CH6 -- Touch screen WIPER (5-wire only) + * + * The bitfields below represents which parts of the LRADC block are + * switched into special mode of operation. These channels can not + * be sampled as regular LRADC channels. The driver will refuse any + * attempt to sample these channels. + */ +#define CHAN_MASK_TOUCHBUTTON (BIT(1) | BIT(0)) +#define CHAN_MASK_TOUCHSCREEN_4WIRE (0xf << 2) +#define CHAN_MASK_TOUCHSCREEN_5WIRE (0x1f << 2) + enum mxs_lradc_ts use_touchscreen; + bool use_touchbutton; + + struct input_dev *ts_input; + + enum mxs_lradc_id soc; + enum lradc_ts_plate cur_plate; /* statemachine */ + bool ts_valid; + unsigned ts_x_pos; + unsigned ts_y_pos; + unsigned ts_pressure; + + /* handle touchscreen's physical behaviour */ + /* samples per coordinate */ + unsigned over_sample_cnt; + /* time clocks between samples */ + unsigned over_sample_delay; + /* time in clocks to wait after the plates where switched */ + unsigned settling_delay; +}; + +#define LRADC_CTRL0 0x00 +# define LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE BIT(23) +# define LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE BIT(22) +# define LRADC_CTRL0_MX28_YNNSW /* YM */ BIT(21) +# define LRADC_CTRL0_MX28_YPNSW /* YP */ BIT(20) +# define LRADC_CTRL0_MX28_YPPSW /* YP */ BIT(19) +# define LRADC_CTRL0_MX28_XNNSW /* XM */ BIT(18) +# define LRADC_CTRL0_MX28_XNPSW /* XM */ BIT(17) +# define LRADC_CTRL0_MX28_XPPSW /* XP */ BIT(16) + +# define LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE BIT(20) +# define LRADC_CTRL0_MX23_YM BIT(19) +# define LRADC_CTRL0_MX23_XM BIT(18) +# define LRADC_CTRL0_MX23_YP BIT(17) +# define LRADC_CTRL0_MX23_XP BIT(16) + +# define LRADC_CTRL0_MX28_PLATE_MASK \ + (LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE | \ + LRADC_CTRL0_MX28_YNNSW | LRADC_CTRL0_MX28_YPNSW | \ + LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_XNNSW | \ + LRADC_CTRL0_MX28_XNPSW | LRADC_CTRL0_MX28_XPPSW) + +# define LRADC_CTRL0_MX23_PLATE_MASK \ + (LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE | \ + LRADC_CTRL0_MX23_YM | LRADC_CTRL0_MX23_XM | \ + LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_XP) + +#define LRADC_CTRL1 0x10 +#define LRADC_CTRL1_TOUCH_DETECT_IRQ_EN BIT(24) +#define LRADC_CTRL1_LRADC_IRQ_EN(n) (1 << ((n) + 16)) +#define LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK (0x1fff << 16) +#define LRADC_CTRL1_MX23_LRADC_IRQ_EN_MASK (0x01ff << 16) +#define LRADC_CTRL1_LRADC_IRQ_EN_OFFSET 16 +#define LRADC_CTRL1_TOUCH_DETECT_IRQ BIT(8) +#define LRADC_CTRL1_LRADC_IRQ(n) (1 << (n)) +#define LRADC_CTRL1_MX28_LRADC_IRQ_MASK 0x1fff +#define LRADC_CTRL1_MX23_LRADC_IRQ_MASK 0x01ff +#define LRADC_CTRL1_LRADC_IRQ_OFFSET 0 + +#define LRADC_CTRL2 0x20 +#define LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET 24 +#define LRADC_CTRL2_TEMPSENSE_PWD BIT(15) + +#define LRADC_STATUS 0x40 +#define LRADC_STATUS_TOUCH_DETECT_RAW BIT(0) + +#define LRADC_CH(n) (0x50 + (0x10 * (n))) +#define LRADC_CH_ACCUMULATE BIT(29) +#define LRADC_CH_NUM_SAMPLES_MASK (0x1f << 24) +#define LRADC_CH_NUM_SAMPLES_OFFSET 24 +#define LRADC_CH_NUM_SAMPLES(x) \ + ((x) << LRADC_CH_NUM_SAMPLES_OFFSET) +#define LRADC_CH_VALUE_MASK 0x3ffff +#define LRADC_CH_VALUE_OFFSET 0 + +#define LRADC_DELAY(n) (0xd0 + (0x10 * (n))) +#define LRADC_DELAY_TRIGGER_LRADCS_MASK (0xff << 24) +#define LRADC_DELAY_TRIGGER_LRADCS_OFFSET 24 +#define LRADC_DELAY_TRIGGER(x) \ + (((x) << LRADC_DELAY_TRIGGER_LRADCS_OFFSET) & \ + LRADC_DELAY_TRIGGER_LRADCS_MASK) +#define LRADC_DELAY_KICK (1 << 20) +#define LRADC_DELAY_TRIGGER_DELAYS_MASK (0xf << 16) +#define LRADC_DELAY_TRIGGER_DELAYS_OFFSET 16 +#define LRADC_DELAY_TRIGGER_DELAYS(x) \ + (((x) << LRADC_DELAY_TRIGGER_DELAYS_OFFSET) & \ + LRADC_DELAY_TRIGGER_DELAYS_MASK) +#define LRADC_DELAY_LOOP_COUNT_MASK (0x1f << 11) +#define LRADC_DELAY_LOOP_COUNT_OFFSET 11 +#define LRADC_DELAY_LOOP(x) \ + (((x) << LRADC_DELAY_LOOP_COUNT_OFFSET) & \ + LRADC_DELAY_LOOP_COUNT_MASK) +#define LRADC_DELAY_DELAY_MASK 0x7ff +#define LRADC_DELAY_DELAY_OFFSET 0 +#define LRADC_DELAY_DELAY(x) \ + (((x) << LRADC_DELAY_DELAY_OFFSET) & \ + LRADC_DELAY_DELAY_MASK) + +#define LRADC_CTRL4 0x140 +#define LRADC_CTRL4_LRADCSELECT_MASK(n) (0xf << ((n) * 4)) +#define LRADC_CTRL4_LRADCSELECT_OFFSET(n) ((n) * 4) +#define LRADC_CTRL4_LRADCSELECT(n, x) \ + (((x) << LRADC_CTRL4_LRADCSELECT_OFFSET(n)) & \ + LRADC_CTRL4_LRADCSELECT_MASK(n)) + +#define LRADC_RESOLUTION 12 +#define LRADC_SINGLE_SAMPLE_MASK ((1 << LRADC_RESOLUTION) - 1) + +static void mxs_lradc_reg_set(struct mxs_lradc *lradc, u32 val, u32 reg) +{ + writel(val, lradc->base + reg + STMP_OFFSET_REG_SET); +} + +static void mxs_lradc_reg_clear(struct mxs_lradc *lradc, u32 val, u32 reg) +{ + writel(val, lradc->base + reg + STMP_OFFSET_REG_CLR); +} + +static void mxs_lradc_reg_wrt(struct mxs_lradc *lradc, u32 val, u32 reg) +{ + writel(val, lradc->base + reg); +} + +static u32 mxs_lradc_plate_mask(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_PLATE_MASK; + return LRADC_CTRL0_MX28_PLATE_MASK; +} + +static u32 mxs_lradc_irq_en_mask(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL1_MX23_LRADC_IRQ_EN_MASK; + return LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK; +} + +static u32 mxs_lradc_irq_mask(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL1_MX23_LRADC_IRQ_MASK; + return LRADC_CTRL1_MX28_LRADC_IRQ_MASK; +} + +static u32 mxs_lradc_touch_detect_bit(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE; + return LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE; +} + +static u32 mxs_lradc_drive_x_plate(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_XP | LRADC_CTRL0_MX23_XM; + return LRADC_CTRL0_MX28_XPPSW | LRADC_CTRL0_MX28_XNNSW; +} + +static u32 mxs_lradc_drive_y_plate(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_YM; + return LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_YNNSW; +} + +static u32 mxs_lradc_drive_pressure(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_XM; + return LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_XNNSW; +} + +static bool mxs_lradc_check_touch_event(struct mxs_lradc *lradc) +{ + return !!(readl(lradc->base + LRADC_STATUS) & + LRADC_STATUS_TOUCH_DETECT_RAW); +} + +static void mxs_lradc_map_channel(struct mxs_lradc *lradc, unsigned vch, + unsigned ch) +{ + mxs_lradc_reg_clear(lradc, LRADC_CTRL4_LRADCSELECT_MASK(vch), + LRADC_CTRL4); + mxs_lradc_reg_set(lradc, LRADC_CTRL4_LRADCSELECT(vch, ch), LRADC_CTRL4); +} + +static void mxs_lradc_setup_ts_channel(struct mxs_lradc *lradc, unsigned ch) +{ + /* + * prepare for oversampling conversion + * + * from the datasheet: + * "The ACCUMULATE bit in the appropriate channel register + * HW_LRADC_CHn must be set to 1 if NUM_SAMPLES is greater then 0; + * otherwise, the IRQs will not fire." + */ + mxs_lradc_reg_wrt(lradc, LRADC_CH_ACCUMULATE | + LRADC_CH_NUM_SAMPLES(lradc->over_sample_cnt - 1), + LRADC_CH(ch)); + + /* from the datasheet: + * "Software must clear this register in preparation for a + * multi-cycle accumulation. + */ + mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch)); + + /* + * prepare the delay/loop unit according to the oversampling count + * + * from the datasheet: + * "The DELAY fields in HW_LRADC_DELAY0, HW_LRADC_DELAY1, + * HW_LRADC_DELAY2, and HW_LRADC_DELAY3 must be non-zero; otherwise, + * the LRADC will not trigger the delay group." + */ + mxs_lradc_reg_wrt(lradc, LRADC_DELAY_TRIGGER(1 << ch) | + LRADC_DELAY_TRIGGER_DELAYS(0) | + LRADC_DELAY_LOOP(lradc->over_sample_cnt - 1) | + LRADC_DELAY_DELAY(lradc->over_sample_delay - 1), + LRADC_DELAY(3)); + + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ(ch), LRADC_CTRL1); + + /* + * after changing the touchscreen plates setting + * the signals need some initial time to settle. Start the + * SoC's delay unit and start the conversion later + * and automatically. + */ + mxs_lradc_reg_wrt(lradc, + LRADC_DELAY_TRIGGER(0) | /* don't trigger ADC */ + LRADC_DELAY_TRIGGER_DELAYS(BIT(3)) | /* trigger DELAY unit#3 */ + LRADC_DELAY_KICK | + LRADC_DELAY_DELAY(lradc->settling_delay), + LRADC_DELAY(2)); +} + +/* + * Pressure detection is special: + * We want to do both required measurements for the pressure detection in + * one turn. Use the hardware features to chain both conversions and let the + * hardware report one interrupt if both conversions are done + */ +static void mxs_lradc_setup_ts_pressure(struct mxs_lradc *lradc, unsigned ch1, + unsigned ch2) +{ + u32 reg; + + /* + * prepare for oversampling conversion + * + * from the datasheet: + * "The ACCUMULATE bit in the appropriate channel register + * HW_LRADC_CHn must be set to 1 if NUM_SAMPLES is greater then 0; + * otherwise, the IRQs will not fire." + */ + reg = LRADC_CH_ACCUMULATE | + LRADC_CH_NUM_SAMPLES(lradc->over_sample_cnt - 1); + mxs_lradc_reg_wrt(lradc, reg, LRADC_CH(ch1)); + mxs_lradc_reg_wrt(lradc, reg, LRADC_CH(ch2)); + + /* from the datasheet: + * "Software must clear this register in preparation for a + * multi-cycle accumulation. + */ + mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch1)); + mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch2)); + + /* prepare the delay/loop unit according to the oversampling count */ + mxs_lradc_reg_wrt(lradc, LRADC_DELAY_TRIGGER(1 << ch1) | + LRADC_DELAY_TRIGGER(1 << ch2) | /* start both channels */ + LRADC_DELAY_TRIGGER_DELAYS(0) | + LRADC_DELAY_LOOP(lradc->over_sample_cnt - 1) | + LRADC_DELAY_DELAY(lradc->over_sample_delay - 1), + LRADC_DELAY(3)); + + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ(ch2), LRADC_CTRL1); + + /* + * after changing the touchscreen plates setting + * the signals need some initial time to settle. Start the + * SoC's delay unit and start the conversion later + * and automatically. + */ + mxs_lradc_reg_wrt(lradc, + LRADC_DELAY_TRIGGER(0) | /* don't trigger ADC */ + LRADC_DELAY_TRIGGER_DELAYS(BIT(3)) | /* trigger DELAY unit#3 */ + LRADC_DELAY_KICK | + LRADC_DELAY_DELAY(lradc->settling_delay), LRADC_DELAY(2)); +} + +static unsigned mxs_lradc_read_raw_channel(struct mxs_lradc *lradc, + unsigned channel) +{ + u32 reg; + unsigned num_samples, val; + + reg = readl(lradc->base + LRADC_CH(channel)); + if (reg & LRADC_CH_ACCUMULATE) + num_samples = lradc->over_sample_cnt; + else + num_samples = 1; + + val = (reg & LRADC_CH_VALUE_MASK) >> LRADC_CH_VALUE_OFFSET; + return val / num_samples; +} + +static unsigned mxs_lradc_read_ts_pressure(struct mxs_lradc *lradc, + unsigned ch1, unsigned ch2) +{ + u32 reg, mask; + unsigned pressure, m1, m2; + + mask = LRADC_CTRL1_LRADC_IRQ(ch1) | LRADC_CTRL1_LRADC_IRQ(ch2); + reg = readl(lradc->base + LRADC_CTRL1) & mask; + + while (reg != mask) { + reg = readl(lradc->base + LRADC_CTRL1) & mask; + dev_dbg(lradc->dev, "One channel is still busy: %X\n", reg); + } + + m1 = mxs_lradc_read_raw_channel(lradc, ch1); + m2 = mxs_lradc_read_raw_channel(lradc, ch2); + + if (m2 == 0) { + dev_warn(lradc->dev, "Cannot calculate pressure\n"); + return 1 << (LRADC_RESOLUTION - 1); + } + + /* simply scale the value from 0 ... max ADC resolution */ + pressure = m1; + pressure *= (1 << LRADC_RESOLUTION); + pressure /= m2; + + dev_dbg(lradc->dev, "Pressure = %u\n", pressure); + return pressure; +} + +#define TS_CH_XP 2 +#define TS_CH_YP 3 +#define TS_CH_XM 4 +#define TS_CH_YM 5 + +/* + * YP(open)--+-------------+ + * | |--+ + * | | | + * YM(-)--+-------------+ | + * +--------------+ + * | | + * XP(weak+) XM(open) + * + * "weak+" means 200k Ohm VDDIO + * (-) means GND + */ +static void mxs_lradc_setup_touch_detection(struct mxs_lradc *lradc) +{ + /* + * In order to detect a touch event the 'touch detect enable' bit + * enables: + * - a weak pullup to the X+ connector + * - a strong ground at the Y- connector + */ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_touch_detect_bit(lradc), + LRADC_CTRL0); +} + +/* + * YP(meas)--+-------------+ + * | |--+ + * | | | + * YM(open)--+-------------+ | + * +--------------+ + * | | + * XP(+) XM(-) + * + * (+) means here 1.85 V + * (-) means here GND + */ +static void mxs_lradc_prepare_x_pos(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_drive_x_plate(lradc), LRADC_CTRL0); + + lradc->cur_plate = LRADC_SAMPLE_X; + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL1, TS_CH_YP); + mxs_lradc_setup_ts_channel(lradc, TOUCHSCREEN_VCHANNEL1); +} + +/* + * YP(+)--+-------------+ + * | |--+ + * | | | + * YM(-)--+-------------+ | + * +--------------+ + * | | + * XP(open) XM(meas) + * + * (+) means here 1.85 V + * (-) means here GND + */ +static void mxs_lradc_prepare_y_pos(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_drive_y_plate(lradc), LRADC_CTRL0); + + lradc->cur_plate = LRADC_SAMPLE_Y; + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL1, TS_CH_XM); + mxs_lradc_setup_ts_channel(lradc, TOUCHSCREEN_VCHANNEL1); +} + +/* + * YP(+)--+-------------+ + * | |--+ + * | | | + * YM(meas)--+-------------+ | + * +--------------+ + * | | + * XP(meas) XM(-) + * + * (+) means here 1.85 V + * (-) means here GND + */ +static void mxs_lradc_prepare_pressure(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_drive_pressure(lradc), LRADC_CTRL0); + + lradc->cur_plate = LRADC_SAMPLE_PRESSURE; + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL1, TS_CH_YM); + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL2, TS_CH_XP); + mxs_lradc_setup_ts_pressure(lradc, TOUCHSCREEN_VCHANNEL2, + TOUCHSCREEN_VCHANNEL1); +} + +static void mxs_lradc_enable_touch_detection(struct mxs_lradc *lradc) +{ + mxs_lradc_setup_touch_detection(lradc); + + lradc->cur_plate = LRADC_TOUCH; + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ | + LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1); + mxs_lradc_reg_set(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1); +} + +static void mxs_lradc_start_touch_event(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, + LRADC_CTRL1); + mxs_lradc_reg_set(lradc, + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1), LRADC_CTRL1); + /* + * start with the Y-pos, because it uses nearly the same plate + * settings like the touch detection + */ + mxs_lradc_prepare_y_pos(lradc); +} + +static void mxs_lradc_report_ts_event(struct mxs_lradc *lradc) +{ + input_report_abs(lradc->ts_input, ABS_X, lradc->ts_x_pos); + input_report_abs(lradc->ts_input, ABS_Y, lradc->ts_y_pos); + input_report_abs(lradc->ts_input, ABS_PRESSURE, lradc->ts_pressure); + input_report_key(lradc->ts_input, BTN_TOUCH, 1); + input_sync(lradc->ts_input); +} + +static void mxs_lradc_complete_touch_event(struct mxs_lradc *lradc) +{ + mxs_lradc_setup_touch_detection(lradc); + lradc->cur_plate = LRADC_SAMPLE_VALID; + /* + * start a dummy conversion to burn time to settle the signals + * note: we are not interested in the conversion's value + */ + mxs_lradc_reg_wrt(lradc, 0, LRADC_CH(TOUCHSCREEN_VCHANNEL1)); + mxs_lradc_reg_clear(lradc, + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2), LRADC_CTRL1); + mxs_lradc_reg_wrt(lradc, + LRADC_DELAY_TRIGGER(1 << TOUCHSCREEN_VCHANNEL1) | + LRADC_DELAY_KICK | LRADC_DELAY_DELAY(10), /* waste 5 ms */ + LRADC_DELAY(2)); +} + +/* + * in order to avoid false measurements, report only samples where + * the surface is still touched after the position measurement + */ +static void mxs_lradc_finish_touch_event(struct mxs_lradc *lradc, bool valid) +{ + /* if it is still touched, report the sample */ + if (valid && mxs_lradc_check_touch_event(lradc)) { + lradc->ts_valid = true; + mxs_lradc_report_ts_event(lradc); + } + + /* if it is even still touched, continue with the next measurement */ + if (mxs_lradc_check_touch_event(lradc)) { + mxs_lradc_prepare_y_pos(lradc); + return; + } + + if (lradc->ts_valid) { + /* signal the release */ + lradc->ts_valid = false; + input_report_key(lradc->ts_input, BTN_TOUCH, 0); + input_sync(lradc->ts_input); + } + + /* if it is released, wait for the next touch via IRQ */ + lradc->cur_plate = LRADC_TOUCH; + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(2)); + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(3)); + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ | + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1), LRADC_CTRL1); + mxs_lradc_reg_set(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1); +} + +/* touchscreen's state machine */ +static void mxs_lradc_handle_touch(struct mxs_lradc *lradc) +{ + switch (lradc->cur_plate) { + case LRADC_TOUCH: + if (mxs_lradc_check_touch_event(lradc)) + mxs_lradc_start_touch_event(lradc); + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ, + LRADC_CTRL1); + return; + + case LRADC_SAMPLE_Y: + lradc->ts_y_pos = mxs_lradc_read_raw_channel(lradc, + TOUCHSCREEN_VCHANNEL1); + mxs_lradc_prepare_x_pos(lradc); + return; + + case LRADC_SAMPLE_X: + lradc->ts_x_pos = mxs_lradc_read_raw_channel(lradc, + TOUCHSCREEN_VCHANNEL1); + mxs_lradc_prepare_pressure(lradc); + return; + + case LRADC_SAMPLE_PRESSURE: + lradc->ts_pressure = mxs_lradc_read_ts_pressure(lradc, + TOUCHSCREEN_VCHANNEL2, + TOUCHSCREEN_VCHANNEL1); + mxs_lradc_complete_touch_event(lradc); + return; + + case LRADC_SAMPLE_VALID: + mxs_lradc_finish_touch_event(lradc, 1); + break; + } +} + +/* + * Raw I/O operations + */ +static int mxs_lradc_read_single(struct iio_dev *iio_dev, int chan, int *val) +{ + struct mxs_lradc *lradc = iio_priv(iio_dev); + int ret; + + /* + * See if there is no buffered operation in progess. If there is, simply + * bail out. This can be improved to support both buffered and raw IO at + * the same time, yet the code becomes horribly complicated. Therefore I + * applied KISS principle here. + */ + ret = mutex_trylock(&lradc->lock); + if (!ret) + return -EBUSY; + + reinit_completion(&lradc->completion); + + /* + * No buffered operation in progress, map the channel and trigger it. + * Virtual channel 0 is always used here as the others are always not + * used if doing raw sampling. + */ + if (lradc->soc == IMX28_LRADC) + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), + LRADC_CTRL1); + mxs_lradc_reg_clear(lradc, 0x1, LRADC_CTRL0); + + /* Enable / disable the divider per requirement */ + if (test_bit(chan, &lradc->is_divided)) + mxs_lradc_reg_set(lradc, 1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET, + LRADC_CTRL2); + else + mxs_lradc_reg_clear(lradc, + 1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET, LRADC_CTRL2); + + /* Clean the slot's previous content, then set new one. */ + mxs_lradc_reg_clear(lradc, LRADC_CTRL4_LRADCSELECT_MASK(0), + LRADC_CTRL4); + mxs_lradc_reg_set(lradc, chan, LRADC_CTRL4); + + mxs_lradc_reg_wrt(lradc, 0, LRADC_CH(0)); + + /* Enable the IRQ and start sampling the channel. */ + mxs_lradc_reg_set(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), LRADC_CTRL1); + mxs_lradc_reg_set(lradc, BIT(0), LRADC_CTRL0); + + /* Wait for completion on the channel, 1 second max. */ + ret = wait_for_completion_killable_timeout(&lradc->completion, HZ); + if (!ret) + ret = -ETIMEDOUT; + if (ret < 0) + goto err; + + /* Read the data. */ + *val = readl(lradc->base + LRADC_CH(0)) & LRADC_CH_VALUE_MASK; + ret = IIO_VAL_INT; + +err: + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), LRADC_CTRL1); + + mutex_unlock(&lradc->lock); + + return ret; +} + +static int mxs_lradc_read_temp(struct iio_dev *iio_dev, int *val) +{ + int ret, min, max; + + ret = mxs_lradc_read_single(iio_dev, 8, &min); + if (ret != IIO_VAL_INT) + return ret; + + ret = mxs_lradc_read_single(iio_dev, 9, &max); + if (ret != IIO_VAL_INT) + return ret; + + *val = max - min; + + return IIO_VAL_INT; +} + +static int mxs_lradc_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long m) +{ + struct mxs_lradc *lradc = iio_priv(iio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_TEMP) + return mxs_lradc_read_temp(iio_dev, val); + + return mxs_lradc_read_single(iio_dev, chan->channel, val); + + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + /* From the datasheet, we have to multiply by 1.012 and + * divide by 4 + */ + *val = 0; + *val2 = 253000; + return IIO_VAL_INT_PLUS_MICRO; + } + + *val = lradc->vref_mv[chan->channel]; + *val2 = chan->scan_type.realbits - + test_bit(chan->channel, &lradc->is_divided); + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + /* The calculated value from the ADC is in Kelvin, we + * want Celsius for hwmon so the offset is + * -272.15 * scale + */ + *val = -1075; + *val2 = 691699; + + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; + + default: + break; + } + + return -EINVAL; +} + +static int mxs_lradc_write_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long m) +{ + struct mxs_lradc *lradc = iio_priv(iio_dev); + struct mxs_lradc_scale *scale_avail = + lradc->scale_avail[chan->channel]; + int ret; + + ret = mutex_trylock(&lradc->lock); + if (!ret) + return -EBUSY; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + ret = -EINVAL; + if (val == scale_avail[MXS_LRADC_DIV_DISABLED].integer && + val2 == scale_avail[MXS_LRADC_DIV_DISABLED].nano) { + /* divider by two disabled */ + clear_bit(chan->channel, &lradc->is_divided); + ret = 0; + } else if (val == scale_avail[MXS_LRADC_DIV_ENABLED].integer && + val2 == scale_avail[MXS_LRADC_DIV_ENABLED].nano) { + /* divider by two enabled */ + set_bit(chan->channel, &lradc->is_divided); + ret = 0; + } + + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&lradc->lock); + + return ret; +} + +static int mxs_lradc_write_raw_get_fmt(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + long m) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static ssize_t mxs_lradc_show_scale_available_ch(struct device *dev, + struct device_attribute *attr, + char *buf, + int ch) +{ + struct iio_dev *iio = dev_to_iio_dev(dev); + struct mxs_lradc *lradc = iio_priv(iio); + int i, len = 0; + + for (i = 0; i < ARRAY_SIZE(lradc->scale_avail[ch]); i++) + len += sprintf(buf + len, "%u.%09u ", + lradc->scale_avail[ch][i].integer, + lradc->scale_avail[ch][i].nano); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static ssize_t mxs_lradc_show_scale_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *iio_attr = to_iio_dev_attr(attr); + + return mxs_lradc_show_scale_available_ch(dev, attr, buf, + iio_attr->address); +} + +#define SHOW_SCALE_AVAILABLE_ATTR(ch) \ +static IIO_DEVICE_ATTR(in_voltage##ch##_scale_available, S_IRUGO, \ + mxs_lradc_show_scale_available, NULL, ch) + +SHOW_SCALE_AVAILABLE_ATTR(0); +SHOW_SCALE_AVAILABLE_ATTR(1); +SHOW_SCALE_AVAILABLE_ATTR(2); +SHOW_SCALE_AVAILABLE_ATTR(3); +SHOW_SCALE_AVAILABLE_ATTR(4); +SHOW_SCALE_AVAILABLE_ATTR(5); +SHOW_SCALE_AVAILABLE_ATTR(6); +SHOW_SCALE_AVAILABLE_ATTR(7); +SHOW_SCALE_AVAILABLE_ATTR(10); +SHOW_SCALE_AVAILABLE_ATTR(11); +SHOW_SCALE_AVAILABLE_ATTR(12); +SHOW_SCALE_AVAILABLE_ATTR(13); +SHOW_SCALE_AVAILABLE_ATTR(14); +SHOW_SCALE_AVAILABLE_ATTR(15); + +static struct attribute *mxs_lradc_attributes[] = { + &iio_dev_attr_in_voltage0_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage1_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage2_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage3_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage4_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage5_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage6_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage7_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage10_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage11_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage12_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage13_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage14_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage15_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mxs_lradc_attribute_group = { + .attrs = mxs_lradc_attributes, +}; + +static const struct iio_info mxs_lradc_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = mxs_lradc_read_raw, + .write_raw = mxs_lradc_write_raw, + .write_raw_get_fmt = mxs_lradc_write_raw_get_fmt, + .attrs = &mxs_lradc_attribute_group, +}; + +static int mxs_lradc_ts_open(struct input_dev *dev) +{ + struct mxs_lradc *lradc = input_get_drvdata(dev); + + /* Enable the touch-detect circuitry. */ + mxs_lradc_enable_touch_detection(lradc); + + return 0; +} + +static void mxs_lradc_disable_ts(struct mxs_lradc *lradc) +{ + /* stop all interrupts from firing */ + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN | + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL2), LRADC_CTRL1); + + /* Power-down touchscreen touch-detect circuitry. */ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); +} + +static void mxs_lradc_ts_close(struct input_dev *dev) +{ + struct mxs_lradc *lradc = input_get_drvdata(dev); + + mxs_lradc_disable_ts(lradc); +} + +static int mxs_lradc_ts_register(struct mxs_lradc *lradc) +{ + struct input_dev *input; + struct device *dev = lradc->dev; + int ret; + + if (!lradc->use_touchscreen) + return 0; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + input->name = DRIVER_NAME; + input->id.bustype = BUS_HOST; + input->dev.parent = dev; + input->open = mxs_lradc_ts_open; + input->close = mxs_lradc_ts_close; + + __set_bit(EV_ABS, input->evbit); + __set_bit(EV_KEY, input->evbit); + __set_bit(BTN_TOUCH, input->keybit); + input_set_abs_params(input, ABS_X, 0, LRADC_SINGLE_SAMPLE_MASK, 0, 0); + input_set_abs_params(input, ABS_Y, 0, LRADC_SINGLE_SAMPLE_MASK, 0, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, LRADC_SINGLE_SAMPLE_MASK, + 0, 0); + + lradc->ts_input = input; + input_set_drvdata(input, lradc); + ret = input_register_device(input); + if (ret) + input_free_device(lradc->ts_input); + + return ret; +} + +static void mxs_lradc_ts_unregister(struct mxs_lradc *lradc) +{ + if (!lradc->use_touchscreen) + return; + + mxs_lradc_disable_ts(lradc); + input_unregister_device(lradc->ts_input); +} + +/* + * IRQ Handling + */ +static irqreturn_t mxs_lradc_handle_irq(int irq, void *data) +{ + struct iio_dev *iio = data; + struct mxs_lradc *lradc = iio_priv(iio); + unsigned long reg = readl(lradc->base + LRADC_CTRL1); + uint32_t clr_irq = mxs_lradc_irq_mask(lradc); + const uint32_t ts_irq_mask = + LRADC_CTRL1_TOUCH_DETECT_IRQ | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2); + + if (!(reg & mxs_lradc_irq_mask(lradc))) + return IRQ_NONE; + + if (lradc->use_touchscreen && (reg & ts_irq_mask)) { + mxs_lradc_handle_touch(lradc); + + /* Make sure we don't clear the next conversion's interrupt. */ + clr_irq &= ~(LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2)); + } + + if (iio_buffer_enabled(iio)) { + if (reg & lradc->buffer_vchans) + iio_trigger_poll(iio->trig); + } else if (reg & LRADC_CTRL1_LRADC_IRQ(0)) { + complete(&lradc->completion); + } + + mxs_lradc_reg_clear(lradc, reg & clr_irq, LRADC_CTRL1); + + return IRQ_HANDLED; +} + +/* + * Trigger handling + */ +static irqreturn_t mxs_lradc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct mxs_lradc *lradc = iio_priv(iio); + const uint32_t chan_value = LRADC_CH_ACCUMULATE | + ((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET); + unsigned int i, j = 0; + + for_each_set_bit(i, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) { + lradc->buffer[j] = readl(lradc->base + LRADC_CH(j)); + mxs_lradc_reg_wrt(lradc, chan_value, LRADC_CH(j)); + lradc->buffer[j] &= LRADC_CH_VALUE_MASK; + lradc->buffer[j] /= LRADC_DELAY_TIMER_LOOP; + j++; + } + + iio_push_to_buffers_with_timestamp(iio, lradc->buffer, pf->timestamp); + + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int mxs_lradc_configure_trigger(struct iio_trigger *trig, bool state) +{ + struct iio_dev *iio = iio_trigger_get_drvdata(trig); + struct mxs_lradc *lradc = iio_priv(iio); + const uint32_t st = state ? STMP_OFFSET_REG_SET : STMP_OFFSET_REG_CLR; + + mxs_lradc_reg_wrt(lradc, LRADC_DELAY_KICK, LRADC_DELAY(0) + st); + + return 0; +} + +static const struct iio_trigger_ops mxs_lradc_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &mxs_lradc_configure_trigger, +}; + +static int mxs_lradc_trigger_init(struct iio_dev *iio) +{ + int ret; + struct iio_trigger *trig; + struct mxs_lradc *lradc = iio_priv(iio); + + trig = iio_trigger_alloc("%s-dev%i", iio->name, iio->id); + if (trig == NULL) + return -ENOMEM; + + trig->dev.parent = lradc->dev; + iio_trigger_set_drvdata(trig, iio); + trig->ops = &mxs_lradc_trigger_ops; + + ret = iio_trigger_register(trig); + if (ret) { + iio_trigger_free(trig); + return ret; + } + + lradc->trig = trig; + + return 0; +} + +static void mxs_lradc_trigger_remove(struct iio_dev *iio) +{ + struct mxs_lradc *lradc = iio_priv(iio); + + iio_trigger_unregister(lradc->trig); + iio_trigger_free(lradc->trig); +} + +static int mxs_lradc_buffer_preenable(struct iio_dev *iio) +{ + struct mxs_lradc *lradc = iio_priv(iio); + int ret = 0, chan, ofs = 0; + unsigned long enable = 0; + uint32_t ctrl4_set = 0; + uint32_t ctrl4_clr = 0; + uint32_t ctrl1_irq = 0; + const uint32_t chan_value = LRADC_CH_ACCUMULATE | + ((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET); + const int len = bitmap_weight(iio->active_scan_mask, + LRADC_MAX_TOTAL_CHANS); + + if (!len) + return -EINVAL; + + /* + * Lock the driver so raw access can not be done during buffered + * operation. This simplifies the code a lot. + */ + ret = mutex_trylock(&lradc->lock); + if (!ret) + return -EBUSY; + + lradc->buffer = kmalloc_array(len, sizeof(*lradc->buffer), GFP_KERNEL); + if (!lradc->buffer) { + ret = -ENOMEM; + goto err_mem; + } + + if (lradc->soc == IMX28_LRADC) + mxs_lradc_reg_clear(lradc, + lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET, + LRADC_CTRL1); + mxs_lradc_reg_clear(lradc, lradc->buffer_vchans, LRADC_CTRL0); + + for_each_set_bit(chan, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) { + ctrl4_set |= chan << LRADC_CTRL4_LRADCSELECT_OFFSET(ofs); + ctrl4_clr |= LRADC_CTRL4_LRADCSELECT_MASK(ofs); + ctrl1_irq |= LRADC_CTRL1_LRADC_IRQ_EN(ofs); + mxs_lradc_reg_wrt(lradc, chan_value, LRADC_CH(ofs)); + bitmap_set(&enable, ofs, 1); + ofs++; + } + + mxs_lradc_reg_clear(lradc, LRADC_DELAY_TRIGGER_LRADCS_MASK | + LRADC_DELAY_KICK, LRADC_DELAY(0)); + mxs_lradc_reg_clear(lradc, ctrl4_clr, LRADC_CTRL4); + mxs_lradc_reg_set(lradc, ctrl4_set, LRADC_CTRL4); + mxs_lradc_reg_set(lradc, ctrl1_irq, LRADC_CTRL1); + mxs_lradc_reg_set(lradc, enable << LRADC_DELAY_TRIGGER_LRADCS_OFFSET, + LRADC_DELAY(0)); + + return 0; + +err_mem: + mutex_unlock(&lradc->lock); + return ret; +} + +static int mxs_lradc_buffer_postdisable(struct iio_dev *iio) +{ + struct mxs_lradc *lradc = iio_priv(iio); + + mxs_lradc_reg_clear(lradc, LRADC_DELAY_TRIGGER_LRADCS_MASK | + LRADC_DELAY_KICK, LRADC_DELAY(0)); + + mxs_lradc_reg_clear(lradc, lradc->buffer_vchans, LRADC_CTRL0); + if (lradc->soc == IMX28_LRADC) + mxs_lradc_reg_clear(lradc, + lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET, + LRADC_CTRL1); + + kfree(lradc->buffer); + mutex_unlock(&lradc->lock); + + return 0; +} + +static bool mxs_lradc_validate_scan_mask(struct iio_dev *iio, + const unsigned long *mask) +{ + struct mxs_lradc *lradc = iio_priv(iio); + const int map_chans = bitmap_weight(mask, LRADC_MAX_TOTAL_CHANS); + int rsvd_chans = 0; + unsigned long rsvd_mask = 0; + + if (lradc->use_touchbutton) + rsvd_mask |= CHAN_MASK_TOUCHBUTTON; + if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_4WIRE) + rsvd_mask |= CHAN_MASK_TOUCHSCREEN_4WIRE; + if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_5WIRE) + rsvd_mask |= CHAN_MASK_TOUCHSCREEN_5WIRE; + + if (lradc->use_touchbutton) + rsvd_chans++; + if (lradc->use_touchscreen) + rsvd_chans += 2; + + /* Test for attempts to map channels with special mode of operation. */ + if (bitmap_intersects(mask, &rsvd_mask, LRADC_MAX_TOTAL_CHANS)) + return false; + + /* Test for attempts to map more channels then available slots. */ + if (map_chans + rsvd_chans > LRADC_MAX_MAPPED_CHANS) + return false; + + return true; +} + +static const struct iio_buffer_setup_ops mxs_lradc_buffer_ops = { + .preenable = &mxs_lradc_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &mxs_lradc_buffer_postdisable, + .validate_scan_mask = &mxs_lradc_validate_scan_mask, +}; + +/* + * Driver initialization + */ + +#define MXS_ADC_CHAN(idx, chan_type) { \ + .type = (chan_type), \ + .indexed = 1, \ + .scan_index = (idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .channel = (idx), \ + .address = (idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = LRADC_RESOLUTION, \ + .storagebits = 32, \ + }, \ +} + +static const struct iio_chan_spec mxs_lradc_chan_spec[] = { + MXS_ADC_CHAN(0, IIO_VOLTAGE), + MXS_ADC_CHAN(1, IIO_VOLTAGE), + MXS_ADC_CHAN(2, IIO_VOLTAGE), + MXS_ADC_CHAN(3, IIO_VOLTAGE), + MXS_ADC_CHAN(4, IIO_VOLTAGE), + MXS_ADC_CHAN(5, IIO_VOLTAGE), + MXS_ADC_CHAN(6, IIO_VOLTAGE), + MXS_ADC_CHAN(7, IIO_VOLTAGE), /* VBATT */ + /* Combined Temperature sensors */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = 8, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .channel = 8, + .scan_type = {.sign = 'u', .realbits = 18, .storagebits = 32,}, + }, + /* Hidden channel to keep indexes */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = -1, + .channel = 9, + }, + MXS_ADC_CHAN(10, IIO_VOLTAGE), /* VDDIO */ + MXS_ADC_CHAN(11, IIO_VOLTAGE), /* VTH */ + MXS_ADC_CHAN(12, IIO_VOLTAGE), /* VDDA */ + MXS_ADC_CHAN(13, IIO_VOLTAGE), /* VDDD */ + MXS_ADC_CHAN(14, IIO_VOLTAGE), /* VBG */ + MXS_ADC_CHAN(15, IIO_VOLTAGE), /* VDD5V */ +}; + +static int mxs_lradc_hw_init(struct mxs_lradc *lradc) +{ + /* The ADC always uses DELAY CHANNEL 0. */ + const uint32_t adc_cfg = + (1 << (LRADC_DELAY_TRIGGER_DELAYS_OFFSET + 0)) | + (LRADC_DELAY_TIMER_PER << LRADC_DELAY_DELAY_OFFSET); + + int ret = stmp_reset_block(lradc->base); + + if (ret) + return ret; + + /* Configure DELAY CHANNEL 0 for generic ADC sampling. */ + mxs_lradc_reg_wrt(lradc, adc_cfg, LRADC_DELAY(0)); + + /* Disable remaining DELAY CHANNELs */ + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(1)); + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(2)); + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(3)); + + /* Configure the touchscreen type */ + if (lradc->soc == IMX28_LRADC) { + mxs_lradc_reg_clear(lradc, LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE, + LRADC_CTRL0); + + if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_5WIRE) + mxs_lradc_reg_set(lradc, LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE, + LRADC_CTRL0); + } + + /* Start internal temperature sensing. */ + mxs_lradc_reg_wrt(lradc, 0, LRADC_CTRL2); + + return 0; +} + +static void mxs_lradc_hw_stop(struct mxs_lradc *lradc) +{ + int i; + + mxs_lradc_reg_clear(lradc, mxs_lradc_irq_en_mask(lradc), LRADC_CTRL1); + + for (i = 0; i < LRADC_MAX_DELAY_CHANS; i++) + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(i)); +} + +static const struct of_device_id mxs_lradc_dt_ids[] = { + { .compatible = "fsl,imx23-lradc", .data = (void *)IMX23_LRADC, }, + { .compatible = "fsl,imx28-lradc", .data = (void *)IMX28_LRADC, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_lradc_dt_ids); + +static int mxs_lradc_probe_touchscreen(struct mxs_lradc *lradc, + struct device_node *lradc_node) +{ + int ret; + u32 ts_wires = 0, adapt; + + ret = of_property_read_u32(lradc_node, "fsl,lradc-touchscreen-wires", + &ts_wires); + if (ret) + return -ENODEV; /* touchscreen feature disabled */ + + switch (ts_wires) { + case 4: + lradc->use_touchscreen = MXS_LRADC_TOUCHSCREEN_4WIRE; + break; + case 5: + if (lradc->soc == IMX28_LRADC) { + lradc->use_touchscreen = MXS_LRADC_TOUCHSCREEN_5WIRE; + break; + } + /* fall through an error message for i.MX23 */ + default: + dev_err(lradc->dev, + "Unsupported number of touchscreen wires (%d)\n", + ts_wires); + return -EINVAL; + } + + if (of_property_read_u32(lradc_node, "fsl,ave-ctrl", &adapt)) { + lradc->over_sample_cnt = 4; + } else { + if (adapt < 1 || adapt > 32) { + dev_err(lradc->dev, "Invalid sample count (%u)\n", + adapt); + return -EINVAL; + } + lradc->over_sample_cnt = adapt; + } + + if (of_property_read_u32(lradc_node, "fsl,ave-delay", &adapt)) { + lradc->over_sample_delay = 2; + } else { + if (adapt < 2 || adapt > LRADC_DELAY_DELAY_MASK + 1) { + dev_err(lradc->dev, "Invalid sample delay (%u)\n", + adapt); + return -EINVAL; + } + lradc->over_sample_delay = adapt; + } + + if (of_property_read_u32(lradc_node, "fsl,settling", &adapt)) { + lradc->settling_delay = 10; + } else { + if (adapt < 1 || adapt > LRADC_DELAY_DELAY_MASK) { + dev_err(lradc->dev, "Invalid settling delay (%u)\n", + adapt); + return -EINVAL; + } + lradc->settling_delay = adapt; + } + + return 0; +} + +static int mxs_lradc_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(mxs_lradc_dt_ids, &pdev->dev); + const struct mxs_lradc_of_config *of_cfg = + &mxs_lradc_of_config[(enum mxs_lradc_id)of_id->data]; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct mxs_lradc *lradc; + struct iio_dev *iio; + struct resource *iores; + int ret = 0, touch_ret; + int i, s; + uint64_t scale_uv; + + /* Allocate the IIO device. */ + iio = devm_iio_device_alloc(dev, sizeof(*lradc)); + if (!iio) { + dev_err(dev, "Failed to allocate IIO device\n"); + return -ENOMEM; + } + + lradc = iio_priv(iio); + lradc->soc = (enum mxs_lradc_id)of_id->data; + + /* Grab the memory area */ + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + lradc->dev = &pdev->dev; + lradc->base = devm_ioremap_resource(dev, iores); + if (IS_ERR(lradc->base)) + return PTR_ERR(lradc->base); + + lradc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(lradc->clk)) { + dev_err(dev, "Failed to get the delay unit clock\n"); + return PTR_ERR(lradc->clk); + } + ret = clk_prepare_enable(lradc->clk); + if (ret != 0) { + dev_err(dev, "Failed to enable the delay unit clock\n"); + return ret; + } + + touch_ret = mxs_lradc_probe_touchscreen(lradc, node); + + if (touch_ret == 0) + lradc->buffer_vchans = BUFFER_VCHANS_LIMITED; + else + lradc->buffer_vchans = BUFFER_VCHANS_ALL; + + /* Grab all IRQ sources */ + for (i = 0; i < of_cfg->irq_count; i++) { + lradc->irq[i] = platform_get_irq(pdev, i); + if (lradc->irq[i] < 0) { + ret = lradc->irq[i]; + goto err_clk; + } + + ret = devm_request_irq(dev, lradc->irq[i], + mxs_lradc_handle_irq, 0, + of_cfg->irq_name[i], iio); + if (ret) + goto err_clk; + } + + lradc->vref_mv = of_cfg->vref_mv; + + platform_set_drvdata(pdev, iio); + + init_completion(&lradc->completion); + mutex_init(&lradc->lock); + + iio->name = pdev->name; + iio->dev.parent = &pdev->dev; + iio->info = &mxs_lradc_iio_info; + iio->modes = INDIO_DIRECT_MODE; + iio->channels = mxs_lradc_chan_spec; + iio->num_channels = ARRAY_SIZE(mxs_lradc_chan_spec); + iio->masklength = LRADC_MAX_TOTAL_CHANS; + + ret = iio_triggered_buffer_setup(iio, &iio_pollfunc_store_time, + &mxs_lradc_trigger_handler, + &mxs_lradc_buffer_ops); + if (ret) + goto err_clk; + + ret = mxs_lradc_trigger_init(iio); + if (ret) + goto err_trig; + + /* Populate available ADC input ranges */ + for (i = 0; i < LRADC_MAX_TOTAL_CHANS; i++) { + for (s = 0; s < ARRAY_SIZE(lradc->scale_avail[i]); s++) { + /* + * [s=0] = optional divider by two disabled (default) + * [s=1] = optional divider by two enabled + * + * The scale is calculated by doing: + * Vref >> (realbits - s) + * which multiplies by two on the second component + * of the array. + */ + scale_uv = ((u64)lradc->vref_mv[i] * 100000000) >> + (LRADC_RESOLUTION - s); + lradc->scale_avail[i][s].nano = + do_div(scale_uv, 100000000) * 10; + lradc->scale_avail[i][s].integer = scale_uv; + } + } + + /* Configure the hardware. */ + ret = mxs_lradc_hw_init(lradc); + if (ret) + goto err_dev; + + /* Register the touchscreen input device. */ + if (touch_ret == 0) { + ret = mxs_lradc_ts_register(lradc); + if (ret) + goto err_ts_register; + } + + /* Register IIO device. */ + ret = iio_device_register(iio); + if (ret) { + dev_err(dev, "Failed to register IIO device\n"); + goto err_ts; + } + + return 0; + +err_ts: + mxs_lradc_ts_unregister(lradc); +err_ts_register: + mxs_lradc_hw_stop(lradc); +err_dev: + mxs_lradc_trigger_remove(iio); +err_trig: + iio_triggered_buffer_cleanup(iio); +err_clk: + clk_disable_unprepare(lradc->clk); + return ret; +} + +static int mxs_lradc_remove(struct platform_device *pdev) +{ + struct iio_dev *iio = platform_get_drvdata(pdev); + struct mxs_lradc *lradc = iio_priv(iio); + + iio_device_unregister(iio); + mxs_lradc_ts_unregister(lradc); + mxs_lradc_hw_stop(lradc); + mxs_lradc_trigger_remove(iio); + iio_triggered_buffer_cleanup(iio); + + clk_disable_unprepare(lradc->clk); + return 0; +} + +static struct platform_driver mxs_lradc_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = mxs_lradc_dt_ids, + }, + .probe = mxs_lradc_probe, + .remove = mxs_lradc_remove, +}; + +module_platform_driver(mxs_lradc_driver); + +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("Freescale i.MX28 LRADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/kernel/drivers/staging/iio/adc/spear_adc.c b/kernel/drivers/staging/iio/adc/spear_adc.c new file mode 100644 index 000000000..c5382374d --- /dev/null +++ b/kernel/drivers/staging/iio/adc/spear_adc.c @@ -0,0 +1,400 @@ +/* + * ST SPEAr ADC driver + * + * Copyright 2012 Stefan Roese + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* SPEAR registers definitions */ +#define SPEAR600_ADC_SCAN_RATE_LO(x) ((x) & 0xFFFF) +#define SPEAR600_ADC_SCAN_RATE_HI(x) (((x) >> 0x10) & 0xFFFF) +#define SPEAR_ADC_CLK_LOW(x) (((x) & 0xf) << 0) +#define SPEAR_ADC_CLK_HIGH(x) (((x) & 0xf) << 4) + +/* Bit definitions for SPEAR_ADC_STATUS */ +#define SPEAR_ADC_STATUS_START_CONVERSION BIT(0) +#define SPEAR_ADC_STATUS_CHANNEL_NUM(x) ((x) << 1) +#define SPEAR_ADC_STATUS_ADC_ENABLE BIT(4) +#define SPEAR_ADC_STATUS_AVG_SAMPLE(x) ((x) << 5) +#define SPEAR_ADC_STATUS_VREF_INTERNAL BIT(9) + +#define SPEAR_ADC_DATA_MASK 0x03ff +#define SPEAR_ADC_DATA_BITS 10 + +#define SPEAR_ADC_MOD_NAME "spear-adc" + +#define SPEAR_ADC_CHANNEL_NUM 8 + +#define SPEAR_ADC_CLK_MIN 2500000 +#define SPEAR_ADC_CLK_MAX 20000000 + +struct adc_regs_spear3xx { + u32 status; + u32 average; + u32 scan_rate; + u32 clk; /* Not avail for 1340 & 1310 */ + u32 ch_ctrl[SPEAR_ADC_CHANNEL_NUM]; + u32 ch_data[SPEAR_ADC_CHANNEL_NUM]; +}; + +struct chan_data { + u32 lsb; + u32 msb; +}; + +struct adc_regs_spear6xx { + u32 status; + u32 pad[2]; + u32 clk; + u32 ch_ctrl[SPEAR_ADC_CHANNEL_NUM]; + struct chan_data ch_data[SPEAR_ADC_CHANNEL_NUM]; + u32 scan_rate_lo; + u32 scan_rate_hi; + struct chan_data average; +}; + +struct spear_adc_state { + struct device_node *np; + struct adc_regs_spear3xx __iomem *adc_base_spear3xx; + struct adc_regs_spear6xx __iomem *adc_base_spear6xx; + struct clk *clk; + struct completion completion; + u32 current_clk; + u32 sampling_freq; + u32 avg_samples; + u32 vref_external; + u32 value; +}; + +/* + * Functions to access some SPEAr ADC register. Abstracted into + * static inline functions, because of different register offsets + * on different SoC variants (SPEAr300 vs SPEAr600 etc). + */ +static void spear_adc_set_status(struct spear_adc_state *st, u32 val) +{ + __raw_writel(val, &st->adc_base_spear6xx->status); +} + +static void spear_adc_set_clk(struct spear_adc_state *st, u32 val) +{ + u32 clk_high, clk_low, count; + u32 apb_clk = clk_get_rate(st->clk); + + count = DIV_ROUND_UP(apb_clk, val); + clk_low = count / 2; + clk_high = count - clk_low; + st->current_clk = apb_clk / count; + + __raw_writel(SPEAR_ADC_CLK_LOW(clk_low) | SPEAR_ADC_CLK_HIGH(clk_high), + &st->adc_base_spear6xx->clk); +} + +static void spear_adc_set_ctrl(struct spear_adc_state *st, int n, + u32 val) +{ + __raw_writel(val, &st->adc_base_spear6xx->ch_ctrl[n]); +} + +static u32 spear_adc_get_average(struct spear_adc_state *st) +{ + if (of_device_is_compatible(st->np, "st,spear600-adc")) { + return __raw_readl(&st->adc_base_spear6xx->average.msb) & + SPEAR_ADC_DATA_MASK; + } else { + return __raw_readl(&st->adc_base_spear3xx->average) & + SPEAR_ADC_DATA_MASK; + } +} + +static void spear_adc_set_scanrate(struct spear_adc_state *st, u32 rate) +{ + if (of_device_is_compatible(st->np, "st,spear600-adc")) { + __raw_writel(SPEAR600_ADC_SCAN_RATE_LO(rate), + &st->adc_base_spear6xx->scan_rate_lo); + __raw_writel(SPEAR600_ADC_SCAN_RATE_HI(rate), + &st->adc_base_spear6xx->scan_rate_hi); + } else { + __raw_writel(rate, &st->adc_base_spear3xx->scan_rate); + } +} + +static int spear_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct spear_adc_state *st = iio_priv(indio_dev); + u32 status; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + + status = SPEAR_ADC_STATUS_CHANNEL_NUM(chan->channel) | + SPEAR_ADC_STATUS_AVG_SAMPLE(st->avg_samples) | + SPEAR_ADC_STATUS_START_CONVERSION | + SPEAR_ADC_STATUS_ADC_ENABLE; + if (st->vref_external == 0) + status |= SPEAR_ADC_STATUS_VREF_INTERNAL; + + spear_adc_set_status(st, status); + wait_for_completion(&st->completion); /* set by ISR */ + *val = st->value; + + mutex_unlock(&indio_dev->mlock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = st->vref_external; + *val2 = SPEAR_ADC_DATA_BITS; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = st->current_clk; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int spear_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct spear_adc_state *st = iio_priv(indio_dev); + int ret = 0; + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + if ((val < SPEAR_ADC_CLK_MIN) || + (val > SPEAR_ADC_CLK_MAX) || + (val2 != 0)) { + ret = -EINVAL; + goto out; + } + + spear_adc_set_clk(st, val); + +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +#define SPEAR_ADC_CHAN(idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .channel = idx, \ +} + +static const struct iio_chan_spec spear_adc_iio_channels[] = { + SPEAR_ADC_CHAN(0), + SPEAR_ADC_CHAN(1), + SPEAR_ADC_CHAN(2), + SPEAR_ADC_CHAN(3), + SPEAR_ADC_CHAN(4), + SPEAR_ADC_CHAN(5), + SPEAR_ADC_CHAN(6), + SPEAR_ADC_CHAN(7), +}; + +static irqreturn_t spear_adc_isr(int irq, void *dev_id) +{ + struct spear_adc_state *st = dev_id; + + /* Read value to clear IRQ */ + st->value = spear_adc_get_average(st); + complete(&st->completion); + + return IRQ_HANDLED; +} + +static int spear_adc_configure(struct spear_adc_state *st) +{ + int i; + + /* Reset ADC core */ + spear_adc_set_status(st, 0); + __raw_writel(0, &st->adc_base_spear6xx->clk); + for (i = 0; i < 8; i++) + spear_adc_set_ctrl(st, i, 0); + spear_adc_set_scanrate(st, 0); + + spear_adc_set_clk(st, st->sampling_freq); + + return 0; +} + +static const struct iio_info spear_adc_info = { + .read_raw = &spear_adc_read_raw, + .write_raw = &spear_adc_write_raw, + .driver_module = THIS_MODULE, +}; + +static int spear_adc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct spear_adc_state *st; + struct iio_dev *indio_dev = NULL; + int ret = -ENODEV; + int irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(struct spear_adc_state)); + if (!indio_dev) { + dev_err(dev, "failed allocating iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + st->np = np; + + /* + * SPEAr600 has a different register layout than other SPEAr SoC's + * (e.g. SPEAr3xx). Let's provide two register base addresses + * to support multi-arch kernels. + */ + st->adc_base_spear6xx = of_iomap(np, 0); + if (!st->adc_base_spear6xx) { + dev_err(dev, "failed mapping memory\n"); + return -ENOMEM; + } + st->adc_base_spear3xx = + (struct adc_regs_spear3xx __iomem *)st->adc_base_spear6xx; + + st->clk = clk_get(dev, NULL); + if (IS_ERR(st->clk)) { + dev_err(dev, "failed getting clock\n"); + goto errout1; + } + + ret = clk_prepare_enable(st->clk); + if (ret) { + dev_err(dev, "failed enabling clock\n"); + goto errout2; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(dev, "failed getting interrupt resource\n"); + ret = -EINVAL; + goto errout3; + } + + ret = devm_request_irq(dev, irq, spear_adc_isr, 0, SPEAR_ADC_MOD_NAME, + st); + if (ret < 0) { + dev_err(dev, "failed requesting interrupt\n"); + goto errout3; + } + + if (of_property_read_u32(np, "sampling-frequency", + &st->sampling_freq)) { + dev_err(dev, "sampling-frequency missing in DT\n"); + ret = -EINVAL; + goto errout3; + } + + /* + * Optional avg_samples defaults to 0, resulting in single data + * conversion + */ + of_property_read_u32(np, "average-samples", &st->avg_samples); + + /* + * Optional vref_external defaults to 0, resulting in internal vref + * selection + */ + of_property_read_u32(np, "vref-external", &st->vref_external); + + spear_adc_configure(st); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&st->completion); + + indio_dev->name = SPEAR_ADC_MOD_NAME; + indio_dev->dev.parent = dev; + indio_dev->info = &spear_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = spear_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(spear_adc_iio_channels); + + ret = iio_device_register(indio_dev); + if (ret) + goto errout3; + + dev_info(dev, "SPEAR ADC driver loaded, IRQ %d\n", irq); + + return 0; + +errout3: + clk_disable_unprepare(st->clk); +errout2: + clk_put(st->clk); +errout1: + iounmap(st->adc_base_spear6xx); + return ret; +} + +static int spear_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct spear_adc_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + clk_disable_unprepare(st->clk); + clk_put(st->clk); + iounmap(st->adc_base_spear6xx); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id spear_adc_dt_ids[] = { + { .compatible = "st,spear600-adc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, spear_adc_dt_ids); +#endif + +static struct platform_driver spear_adc_driver = { + .probe = spear_adc_probe, + .remove = spear_adc_remove, + .driver = { + .name = SPEAR_ADC_MOD_NAME, + .of_match_table = of_match_ptr(spear_adc_dt_ids), + }, +}; + +module_platform_driver(spear_adc_driver); + +MODULE_AUTHOR("Stefan Roese "); +MODULE_DESCRIPTION("SPEAr ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/staging/iio/addac/Kconfig b/kernel/drivers/staging/iio/addac/Kconfig new file mode 100644 index 000000000..0ed7e13e2 --- /dev/null +++ b/kernel/drivers/staging/iio/addac/Kconfig @@ -0,0 +1,37 @@ +# +# ADDAC drivers +# +menu "Analog digital bi-direction converters" + +config ADT7316 + tristate "Analog Devices ADT7316/7/8 ADT7516/7/9 temperature sensor, ADC and DAC driver" + depends on GPIOLIB + help + Say yes here to build support for Analog Devices ADT7316, ADT7317, ADT7318 + and ADT7516, ADT7517, ADT7519 temperature sensors, ADC and DAC. + + To compile this driver as a module, choose M here: the module will + be called adt7316. + +config ADT7316_SPI + tristate "support SPI bus connection" + depends on SPI && ADT7316 + default y + help + Say yes here to build SPI bus support for Analog Devices ADT7316/7/8 + and ADT7516/7/9. + + To compile this driver as a module, choose M here: the module will + be called adt7316_spi. + +config ADT7316_I2C + tristate "support I2C bus connection" + depends on I2C && ADT7316 + help + Say yes here to build I2C bus support for Analog Devices ADT7316/7/8 + and ADT7516/7/9. + + To compile this driver as a module, choose M here: the module will + be called adt7316_i2c. + +endmenu diff --git a/kernel/drivers/staging/iio/addac/Makefile b/kernel/drivers/staging/iio/addac/Makefile new file mode 100644 index 000000000..4c7686133 --- /dev/null +++ b/kernel/drivers/staging/iio/addac/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for industrial I/O ADDAC drivers +# + +obj-$(CONFIG_ADT7316) += adt7316.o +obj-$(CONFIG_ADT7316_SPI) += adt7316-spi.o +obj-$(CONFIG_ADT7316_I2C) += adt7316-i2c.o diff --git a/kernel/drivers/staging/iio/addac/adt7316-i2c.c b/kernel/drivers/staging/iio/addac/adt7316-i2c.c new file mode 100644 index 000000000..75ddd4f80 --- /dev/null +++ b/kernel/drivers/staging/iio/addac/adt7316-i2c.c @@ -0,0 +1,136 @@ +/* + * I2C bus driver for ADT7316/7/8 ADT7516/7/9 digital temperature + * sensor, ADC and DAC + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include "adt7316.h" + +/* + * adt7316 register access by I2C + */ +static int adt7316_i2c_read(void *client, u8 reg, u8 *data) +{ + struct i2c_client *cl = client; + int ret = 0; + + ret = i2c_smbus_write_byte(cl, reg); + if (ret < 0) { + dev_err(&cl->dev, "I2C fail to select reg\n"); + return ret; + } + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(&cl->dev, "I2C read error\n"); + return ret; + } + + return 0; +} + +static int adt7316_i2c_write(void *client, u8 reg, u8 data) +{ + struct i2c_client *cl = client; + int ret = 0; + + ret = i2c_smbus_write_byte_data(cl, reg, data); + if (ret < 0) + dev_err(&cl->dev, "I2C write error\n"); + + return ret; +} + +static int adt7316_i2c_multi_read(void *client, u8 reg, u8 count, u8 *data) +{ + struct i2c_client *cl = client; + int i, ret = 0; + + if (count > ADT7316_REG_MAX_ADDR) + count = ADT7316_REG_MAX_ADDR; + + for (i = 0; i < count; i++) { + ret = adt7316_i2c_read(cl, reg, &data[i]); + if (ret < 0) { + dev_err(&cl->dev, "I2C multi read error\n"); + return ret; + } + } + + return 0; +} + +static int adt7316_i2c_multi_write(void *client, u8 reg, u8 count, u8 *data) +{ + struct i2c_client *cl = client; + int i, ret = 0; + + if (count > ADT7316_REG_MAX_ADDR) + count = ADT7316_REG_MAX_ADDR; + + for (i = 0; i < count; i++) { + ret = adt7316_i2c_write(cl, reg, data[i]); + if (ret < 0) { + dev_err(&cl->dev, "I2C multi write error\n"); + return ret; + } + } + + return 0; +} + +/* + * device probe and remove + */ + +static int adt7316_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adt7316_bus bus = { + .client = client, + .irq = client->irq, + .irq_flags = IRQF_TRIGGER_LOW, + .read = adt7316_i2c_read, + .write = adt7316_i2c_write, + .multi_read = adt7316_i2c_multi_read, + .multi_write = adt7316_i2c_multi_write, + }; + + return adt7316_probe(&client->dev, &bus, id->name); +} + +static const struct i2c_device_id adt7316_i2c_id[] = { + { "adt7316", 0 }, + { "adt7317", 0 }, + { "adt7318", 0 }, + { "adt7516", 0 }, + { "adt7517", 0 }, + { "adt7519", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, adt7316_i2c_id); + +static struct i2c_driver adt7316_driver = { + .driver = { + .name = "adt7316", + .pm = ADT7316_PM_OPS, + .owner = THIS_MODULE, + }, + .probe = adt7316_i2c_probe, + .id_table = adt7316_i2c_id, +}; +module_i2c_driver(adt7316_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("I2C bus driver for Analog Devices ADT7316/7/9 and ADT7516/7/8 digital temperature sensor, ADC and DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/addac/adt7316-spi.c b/kernel/drivers/staging/iio/addac/adt7316-spi.c new file mode 100644 index 000000000..e480abb72 --- /dev/null +++ b/kernel/drivers/staging/iio/addac/adt7316-spi.c @@ -0,0 +1,144 @@ +/* + * API bus driver for ADT7316/7/8 ADT7516/7/9 digital temperature + * sensor, ADC and DAC + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include "adt7316.h" + +#define ADT7316_SPI_MAX_FREQ_HZ 5000000 +#define ADT7316_SPI_CMD_READ 0x91 +#define ADT7316_SPI_CMD_WRITE 0x90 + +/* + * adt7316 register access by SPI + */ + +static int adt7316_spi_multi_read(void *client, u8 reg, u8 count, u8 *data) +{ + struct spi_device *spi_dev = client; + u8 cmd[2]; + int ret = 0; + + if (count > ADT7316_REG_MAX_ADDR) + count = ADT7316_REG_MAX_ADDR; + + cmd[0] = ADT7316_SPI_CMD_WRITE; + cmd[1] = reg; + + ret = spi_write(spi_dev, cmd, 2); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI fail to select reg\n"); + return ret; + } + + cmd[0] = ADT7316_SPI_CMD_READ; + + ret = spi_write_then_read(spi_dev, cmd, 1, data, count); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI read data error\n"); + return ret; + } + + return 0; +} + +static int adt7316_spi_multi_write(void *client, u8 reg, u8 count, u8 *data) +{ + struct spi_device *spi_dev = client; + u8 buf[ADT7316_REG_MAX_ADDR + 2]; + int i, ret = 0; + + if (count > ADT7316_REG_MAX_ADDR) + count = ADT7316_REG_MAX_ADDR; + + buf[0] = ADT7316_SPI_CMD_WRITE; + buf[1] = reg; + for (i = 0; i < count; i++) + buf[i + 2] = data[i]; + + ret = spi_write(spi_dev, buf, count + 2); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI write error\n"); + return ret; + } + + return ret; +} + +static int adt7316_spi_read(void *client, u8 reg, u8 *data) +{ + return adt7316_spi_multi_read(client, reg, 1, data); +} + +static int adt7316_spi_write(void *client, u8 reg, u8 val) +{ + return adt7316_spi_multi_write(client, reg, 1, &val); +} + +/* + * device probe and remove + */ + +static int adt7316_spi_probe(struct spi_device *spi_dev) +{ + struct adt7316_bus bus = { + .client = spi_dev, + .irq = spi_dev->irq, + .irq_flags = IRQF_TRIGGER_LOW, + .read = adt7316_spi_read, + .write = adt7316_spi_write, + .multi_read = adt7316_spi_multi_read, + .multi_write = adt7316_spi_multi_write, + }; + + /* don't exceed max specified SPI CLK frequency */ + if (spi_dev->max_speed_hz > ADT7316_SPI_MAX_FREQ_HZ) { + dev_err(&spi_dev->dev, "SPI CLK %d Hz?\n", + spi_dev->max_speed_hz); + return -EINVAL; + } + + /* switch from default I2C protocol to SPI protocol */ + adt7316_spi_write(spi_dev, 0, 0); + adt7316_spi_write(spi_dev, 0, 0); + adt7316_spi_write(spi_dev, 0, 0); + + return adt7316_probe(&spi_dev->dev, &bus, spi_dev->modalias); +} + +static const struct spi_device_id adt7316_spi_id[] = { + { "adt7316", 0 }, + { "adt7317", 0 }, + { "adt7318", 0 }, + { "adt7516", 0 }, + { "adt7517", 0 }, + { "adt7519", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(spi, adt7316_spi_id); + +static struct spi_driver adt7316_driver = { + .driver = { + .name = "adt7316", + .pm = ADT7316_PM_OPS, + .owner = THIS_MODULE, + }, + .probe = adt7316_spi_probe, + .id_table = adt7316_spi_id, +}; +module_spi_driver(adt7316_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("SPI bus driver for Analog Devices ADT7316/7/8 and ADT7516/7/9 digital temperature sensor, ADC and DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/addac/adt7316.c b/kernel/drivers/staging/iio/addac/adt7316.c new file mode 100644 index 000000000..5b11b42c0 --- /dev/null +++ b/kernel/drivers/staging/iio/addac/adt7316.c @@ -0,0 +1,2188 @@ +/* + * ADT7316 digital temperature sensor driver supporting ADT7316/7/8 ADT7516/7/9 + * + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "adt7316.h" + +/* + * ADT7316 registers definition + */ +#define ADT7316_INT_STAT1 0x0 +#define ADT7316_INT_STAT2 0x1 +#define ADT7316_LSB_IN_TEMP_VDD 0x3 +#define ADT7316_LSB_IN_TEMP_MASK 0x3 +#define ADT7316_LSB_VDD_MASK 0xC +#define ADT7316_LSB_VDD_OFFSET 2 +#define ADT7316_LSB_EX_TEMP_AIN 0x4 +#define ADT7316_LSB_EX_TEMP_MASK 0x3 +#define ADT7516_LSB_AIN_SHIFT 2 +#define ADT7316_AD_MSB_DATA_BASE 0x6 +#define ADT7316_AD_MSB_DATA_REGS 3 +#define ADT7516_AD_MSB_DATA_REGS 6 +#define ADT7316_MSB_VDD 0x6 +#define ADT7316_MSB_IN_TEMP 0x7 +#define ADT7316_MSB_EX_TEMP 0x8 +#define ADT7516_MSB_AIN1 0x8 +#define ADT7516_MSB_AIN2 0x9 +#define ADT7516_MSB_AIN3 0xA +#define ADT7516_MSB_AIN4 0xB +#define ADT7316_DA_DATA_BASE 0x10 +#define ADT7316_DA_MSB_DATA_REGS 4 +#define ADT7316_LSB_DAC_A 0x10 +#define ADT7316_MSB_DAC_A 0x11 +#define ADT7316_LSB_DAC_B 0x12 +#define ADT7316_MSB_DAC_B 0x13 +#define ADT7316_LSB_DAC_C 0x14 +#define ADT7316_MSB_DAC_C 0x15 +#define ADT7316_LSB_DAC_D 0x16 +#define ADT7316_MSB_DAC_D 0x17 +#define ADT7316_CONFIG1 0x18 +#define ADT7316_CONFIG2 0x19 +#define ADT7316_CONFIG3 0x1A +#define ADT7316_LDAC_CONFIG 0x1B +#define ADT7316_DAC_CONFIG 0x1C +#define ADT7316_INT_MASK1 0x1D +#define ADT7316_INT_MASK2 0x1E +#define ADT7316_IN_TEMP_OFFSET 0x1F +#define ADT7316_EX_TEMP_OFFSET 0x20 +#define ADT7316_IN_ANALOG_TEMP_OFFSET 0x21 +#define ADT7316_EX_ANALOG_TEMP_OFFSET 0x22 +#define ADT7316_VDD_HIGH 0x23 +#define ADT7316_VDD_LOW 0x24 +#define ADT7316_IN_TEMP_HIGH 0x25 +#define ADT7316_IN_TEMP_LOW 0x26 +#define ADT7316_EX_TEMP_HIGH 0x27 +#define ADT7316_EX_TEMP_LOW 0x28 +#define ADT7516_AIN2_HIGH 0x2B +#define ADT7516_AIN2_LOW 0x2C +#define ADT7516_AIN3_HIGH 0x2D +#define ADT7516_AIN3_LOW 0x2E +#define ADT7516_AIN4_HIGH 0x2F +#define ADT7516_AIN4_LOW 0x30 +#define ADT7316_DEVICE_ID 0x4D +#define ADT7316_MANUFACTURE_ID 0x4E +#define ADT7316_DEVICE_REV 0x4F +#define ADT7316_SPI_LOCK_STAT 0x7F + +/* + * ADT7316 config1 + */ +#define ADT7316_EN 0x1 +#define ADT7516_SEL_EX_TEMP 0x4 +#define ADT7516_SEL_AIN1_2_EX_TEMP_MASK 0x6 +#define ADT7516_SEL_AIN3 0x8 +#define ADT7316_INT_EN 0x20 +#define ADT7316_INT_POLARITY 0x40 +#define ADT7316_PD 0x80 + +/* + * ADT7316 config2 + */ +#define ADT7316_AD_SINGLE_CH_MASK 0x3 +#define ADT7516_AD_SINGLE_CH_MASK 0x7 +#define ADT7316_AD_SINGLE_CH_VDD 0 +#define ADT7316_AD_SINGLE_CH_IN 1 +#define ADT7316_AD_SINGLE_CH_EX 2 +#define ADT7516_AD_SINGLE_CH_AIN1 2 +#define ADT7516_AD_SINGLE_CH_AIN2 3 +#define ADT7516_AD_SINGLE_CH_AIN3 4 +#define ADT7516_AD_SINGLE_CH_AIN4 5 +#define ADT7316_AD_SINGLE_CH_MODE 0x10 +#define ADT7316_DISABLE_AVERAGING 0x20 +#define ADT7316_EN_SMBUS_TIMEOUT 0x40 +#define ADT7316_RESET 0x80 + +/* + * ADT7316 config3 + */ +#define ADT7316_ADCLK_22_5 0x1 +#define ADT7316_DA_HIGH_RESOLUTION 0x2 +#define ADT7316_DA_EN_VIA_DAC_LDCA 0x4 +#define ADT7516_AIN_IN_VREF 0x10 +#define ADT7316_EN_IN_TEMP_PROP_DACA 0x20 +#define ADT7316_EN_EX_TEMP_PROP_DACB 0x40 + +/* + * ADT7316 DAC config + */ +#define ADT7316_DA_2VREF_CH_MASK 0xF +#define ADT7316_DA_EN_MODE_MASK 0x30 +#define ADT7316_DA_EN_MODE_SINGLE 0x00 +#define ADT7316_DA_EN_MODE_AB_CD 0x10 +#define ADT7316_DA_EN_MODE_ABCD 0x20 +#define ADT7316_DA_EN_MODE_LDAC 0x30 +#define ADT7316_VREF_BYPASS_DAC_AB 0x40 +#define ADT7316_VREF_BYPASS_DAC_CD 0x80 + +/* + * ADT7316 LDAC config + */ +#define ADT7316_LDAC_EN_DA_MASK 0xF +#define ADT7316_DAC_IN_VREF 0x10 +#define ADT7516_DAC_AB_IN_VREF 0x10 +#define ADT7516_DAC_CD_IN_VREF 0x20 +#define ADT7516_DAC_IN_VREF_OFFSET 4 +#define ADT7516_DAC_IN_VREF_MASK 0x30 + +/* + * ADT7316 INT_MASK2 + */ +#define ADT7316_INT_MASK2_VDD 0x10 + +/* + * ADT7316 value masks + */ +#define ADT7316_VALUE_MASK 0xfff +#define ADT7316_T_VALUE_SIGN 0x400 +#define ADT7316_T_VALUE_FLOAT_OFFSET 2 +#define ADT7316_T_VALUE_FLOAT_MASK 0x2 + +/* + * Chip ID + */ +#define ID_ADT7316 0x1 +#define ID_ADT7317 0x2 +#define ID_ADT7318 0x3 +#define ID_ADT7516 0x11 +#define ID_ADT7517 0x12 +#define ID_ADT7519 0x14 + +#define ID_FAMILY_MASK 0xF0 +#define ID_ADT73XX 0x0 +#define ID_ADT75XX 0x10 + +/* + * struct adt7316_chip_info - chip specific information + */ + +struct adt7316_chip_info { + struct adt7316_bus bus; + u16 ldac_pin; + u16 int_mask; /* 0x2f */ + u8 config1; + u8 config2; + u8 config3; + u8 dac_config; /* DAC config */ + u8 ldac_config; /* LDAC config */ + u8 dac_bits; /* 8, 10, 12 */ + u8 id; /* chip id */ +}; + +/* + * Logic interrupt mask for user application to enable + * interrupts. + */ +#define ADT7316_IN_TEMP_HIGH_INT_MASK 0x1 +#define ADT7316_IN_TEMP_LOW_INT_MASK 0x2 +#define ADT7316_EX_TEMP_HIGH_INT_MASK 0x4 +#define ADT7316_EX_TEMP_LOW_INT_MASK 0x8 +#define ADT7316_EX_TEMP_FAULT_INT_MASK 0x10 +#define ADT7516_AIN1_INT_MASK 0x4 +#define ADT7516_AIN2_INT_MASK 0x20 +#define ADT7516_AIN3_INT_MASK 0x40 +#define ADT7516_AIN4_INT_MASK 0x80 +#define ADT7316_VDD_INT_MASK 0x100 +#define ADT7316_TEMP_INT_MASK 0x1F +#define ADT7516_AIN_INT_MASK 0xE0 +#define ADT7316_TEMP_AIN_INT_MASK \ + (ADT7316_TEMP_INT_MASK) + +/* + * struct adt7316_chip_info - chip specific information + */ + +struct adt7316_limit_regs { + u16 data_high; + u16 data_low; +}; + +static ssize_t adt7316_show_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", !!(chip->config1 & ADT7316_EN)); +} + +static ssize_t _adt7316_store_enabled(struct adt7316_chip_info *chip, + int enable) +{ + u8 config1; + int ret; + + if (enable) + config1 = chip->config1 | ADT7316_EN; + else + config1 = chip->config1 & ~ADT7316_EN; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG1, config1); + if (ret) + return -EIO; + + chip->config1 = config1; + + return ret; + +} + +static ssize_t adt7316_store_enabled(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + int enable; + + if (buf[0] == '1') + enable = 1; + else + enable = 0; + + if (_adt7316_store_enabled(chip, enable) < 0) + return -EIO; + + return len; +} + +static IIO_DEVICE_ATTR(enabled, S_IRUGO | S_IWUSR, + adt7316_show_enabled, + adt7316_store_enabled, + 0); + +static ssize_t adt7316_show_select_ex_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) + return -EPERM; + + return sprintf(buf, "%d\n", !!(chip->config1 & ADT7516_SEL_EX_TEMP)); +} + +static ssize_t adt7316_store_select_ex_temp(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config1; + int ret; + + if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) + return -EPERM; + + config1 = chip->config1 & (~ADT7516_SEL_EX_TEMP); + if (buf[0] == '1') + config1 |= ADT7516_SEL_EX_TEMP; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG1, config1); + if (ret) + return -EIO; + + chip->config1 = config1; + + return len; +} + +static IIO_DEVICE_ATTR(select_ex_temp, S_IRUGO | S_IWUSR, + adt7316_show_select_ex_temp, + adt7316_store_select_ex_temp, + 0); + +static ssize_t adt7316_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (chip->config2 & ADT7316_AD_SINGLE_CH_MODE) + return sprintf(buf, "single_channel\n"); + + return sprintf(buf, "round_robin\n"); +} + +static ssize_t adt7316_store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config2; + int ret; + + config2 = chip->config2 & (~ADT7316_AD_SINGLE_CH_MODE); + if (!memcmp(buf, "single_channel", 14)) + config2 |= ADT7316_AD_SINGLE_CH_MODE; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG2, config2); + if (ret) + return -EIO; + + chip->config2 = config2; + + return len; +} + +static IIO_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, + adt7316_show_mode, + adt7316_store_mode, + 0); + +static ssize_t adt7316_show_all_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "single_channel\nround_robin\n"); +} + +static IIO_DEVICE_ATTR(all_modes, S_IRUGO, adt7316_show_all_modes, NULL, 0); + +static ssize_t adt7316_show_ad_channel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (!(chip->config2 & ADT7316_AD_SINGLE_CH_MODE)) + return -EPERM; + + switch (chip->config2 & ADT7516_AD_SINGLE_CH_MASK) { + case ADT7316_AD_SINGLE_CH_VDD: + return sprintf(buf, "0 - VDD\n"); + case ADT7316_AD_SINGLE_CH_IN: + return sprintf(buf, "1 - Internal Temperature\n"); + case ADT7316_AD_SINGLE_CH_EX: + if (((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) && + (chip->config1 & ADT7516_SEL_AIN1_2_EX_TEMP_MASK) == 0) + return sprintf(buf, "2 - AIN1\n"); + + return sprintf(buf, "2 - External Temperature\n"); + case ADT7516_AD_SINGLE_CH_AIN2: + if ((chip->config1 & ADT7516_SEL_AIN1_2_EX_TEMP_MASK) == 0) + return sprintf(buf, "3 - AIN2\n"); + + return sprintf(buf, "N/A\n"); + case ADT7516_AD_SINGLE_CH_AIN3: + if (chip->config1 & ADT7516_SEL_AIN3) + return sprintf(buf, "4 - AIN3\n"); + + return sprintf(buf, "N/A\n"); + case ADT7516_AD_SINGLE_CH_AIN4: + return sprintf(buf, "5 - AIN4\n"); + default: + return sprintf(buf, "N/A\n"); + } +} + +static ssize_t adt7316_store_ad_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config2; + u8 data; + int ret; + + if (!(chip->config2 & ADT7316_AD_SINGLE_CH_MODE)) + return -EPERM; + + ret = kstrtou8(buf, 10, &data); + if (ret) + return -EINVAL; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) { + if (data > 5) + return -EINVAL; + + config2 = chip->config2 & (~ADT7516_AD_SINGLE_CH_MASK); + } else { + if (data > 2) + return -EINVAL; + + config2 = chip->config2 & (~ADT7316_AD_SINGLE_CH_MASK); + } + + + config2 |= data; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG2, config2); + if (ret) + return -EIO; + + chip->config2 = config2; + + return len; +} + +static IIO_DEVICE_ATTR(ad_channel, S_IRUGO | S_IWUSR, + adt7316_show_ad_channel, + adt7316_store_ad_channel, + 0); + +static ssize_t adt7316_show_all_ad_channels(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (!(chip->config2 & ADT7316_AD_SINGLE_CH_MODE)) + return -EPERM; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return sprintf(buf, "0 - VDD\n1 - Internal Temperature\n" + "2 - External Temperature or AIN1\n" + "3 - AIN2\n4 - AIN3\n5 - AIN4\n"); + else + return sprintf(buf, "0 - VDD\n1 - Internal Temperature\n" + "2 - External Temperature\n"); +} + +static IIO_DEVICE_ATTR(all_ad_channels, S_IRUGO, + adt7316_show_all_ad_channels, NULL, 0); + +static ssize_t adt7316_show_disable_averaging(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", + !!(chip->config2 & ADT7316_DISABLE_AVERAGING)); +} + +static ssize_t adt7316_store_disable_averaging(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config2; + int ret; + + config2 = chip->config2 & (~ADT7316_DISABLE_AVERAGING); + if (buf[0] == '1') + config2 |= ADT7316_DISABLE_AVERAGING; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG2, config2); + if (ret) + return -EIO; + + chip->config2 = config2; + + return len; +} + +static IIO_DEVICE_ATTR(disable_averaging, S_IRUGO | S_IWUSR, + adt7316_show_disable_averaging, + adt7316_store_disable_averaging, + 0); + +static ssize_t adt7316_show_enable_smbus_timeout(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", + !!(chip->config2 & ADT7316_EN_SMBUS_TIMEOUT)); +} + +static ssize_t adt7316_store_enable_smbus_timeout(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config2; + int ret; + + config2 = chip->config2 & (~ADT7316_EN_SMBUS_TIMEOUT); + if (buf[0] == '1') + config2 |= ADT7316_EN_SMBUS_TIMEOUT; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG2, config2); + if (ret) + return -EIO; + + chip->config2 = config2; + + return len; +} + +static IIO_DEVICE_ATTR(enable_smbus_timeout, S_IRUGO | S_IWUSR, + adt7316_show_enable_smbus_timeout, + adt7316_store_enable_smbus_timeout, + 0); + +static ssize_t adt7316_show_powerdown(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", !!(chip->config1 & ADT7316_PD)); +} + +static ssize_t adt7316_store_powerdown(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config1; + int ret; + + config1 = chip->config1 & (~ADT7316_PD); + if (buf[0] == '1') + config1 |= ADT7316_PD; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG1, config1); + if (ret) + return -EIO; + + chip->config1 = config1; + + return len; +} + +static IIO_DEVICE_ATTR(powerdown, S_IRUGO | S_IWUSR, + adt7316_show_powerdown, + adt7316_store_powerdown, + 0); + +static ssize_t adt7316_show_fast_ad_clock(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", !!(chip->config3 & ADT7316_ADCLK_22_5)); +} + +static ssize_t adt7316_store_fast_ad_clock(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config3; + int ret; + + config3 = chip->config3 & (~ADT7316_ADCLK_22_5); + if (buf[0] == '1') + config3 |= ADT7316_ADCLK_22_5; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, config3); + if (ret) + return -EIO; + + chip->config3 = config3; + + return len; +} + +static IIO_DEVICE_ATTR(fast_ad_clock, S_IRUGO | S_IWUSR, + adt7316_show_fast_ad_clock, + adt7316_store_fast_ad_clock, + 0); + +static ssize_t adt7316_show_da_high_resolution(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (chip->config3 & ADT7316_DA_HIGH_RESOLUTION) { + if (chip->id == ID_ADT7316 || chip->id == ID_ADT7516) + return sprintf(buf, "1 (12 bits)\n"); + else if (chip->id == ID_ADT7317 || chip->id == ID_ADT7517) + return sprintf(buf, "1 (10 bits)\n"); + } + + return sprintf(buf, "0 (8 bits)\n"); +} + +static ssize_t adt7316_store_da_high_resolution(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config3; + int ret; + + chip->dac_bits = 8; + + if (buf[0] == '1') { + config3 = chip->config3 | ADT7316_DA_HIGH_RESOLUTION; + if (chip->id == ID_ADT7316 || chip->id == ID_ADT7516) + chip->dac_bits = 12; + else if (chip->id == ID_ADT7317 || chip->id == ID_ADT7517) + chip->dac_bits = 10; + } else + config3 = chip->config3 & (~ADT7316_DA_HIGH_RESOLUTION); + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, config3); + if (ret) + return -EIO; + + chip->config3 = config3; + + return len; +} + +static IIO_DEVICE_ATTR(da_high_resolution, S_IRUGO | S_IWUSR, + adt7316_show_da_high_resolution, + adt7316_store_da_high_resolution, + 0); + +static ssize_t adt7316_show_AIN_internal_Vref(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) + return -EPERM; + + return sprintf(buf, "%d\n", + !!(chip->config3 & ADT7516_AIN_IN_VREF)); +} + +static ssize_t adt7316_store_AIN_internal_Vref(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config3; + int ret; + + if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) + return -EPERM; + + if (buf[0] != '1') + config3 = chip->config3 & (~ADT7516_AIN_IN_VREF); + else + config3 = chip->config3 | ADT7516_AIN_IN_VREF; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, config3); + if (ret) + return -EIO; + + chip->config3 = config3; + + return len; +} + +static IIO_DEVICE_ATTR(AIN_internal_Vref, S_IRUGO | S_IWUSR, + adt7316_show_AIN_internal_Vref, + adt7316_store_AIN_internal_Vref, + 0); + + +static ssize_t adt7316_show_enable_prop_DACA(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", + !!(chip->config3 & ADT7316_EN_IN_TEMP_PROP_DACA)); +} + +static ssize_t adt7316_store_enable_prop_DACA(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config3; + int ret; + + config3 = chip->config3 & (~ADT7316_EN_IN_TEMP_PROP_DACA); + if (buf[0] == '1') + config3 |= ADT7316_EN_IN_TEMP_PROP_DACA; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, config3); + if (ret) + return -EIO; + + chip->config3 = config3; + + return len; +} + +static IIO_DEVICE_ATTR(enable_proportion_DACA, S_IRUGO | S_IWUSR, + adt7316_show_enable_prop_DACA, + adt7316_store_enable_prop_DACA, + 0); + +static ssize_t adt7316_show_enable_prop_DACB(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", + !!(chip->config3 & ADT7316_EN_EX_TEMP_PROP_DACB)); +} + +static ssize_t adt7316_store_enable_prop_DACB(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config3; + int ret; + + config3 = chip->config3 & (~ADT7316_EN_EX_TEMP_PROP_DACB); + if (buf[0] == '1') + config3 |= ADT7316_EN_EX_TEMP_PROP_DACB; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, config3); + if (ret) + return -EIO; + + chip->config3 = config3; + + return len; +} + +static IIO_DEVICE_ATTR(enable_proportion_DACB, S_IRUGO | S_IWUSR, + adt7316_show_enable_prop_DACB, + adt7316_store_enable_prop_DACB, + 0); + +static ssize_t adt7316_show_DAC_2Vref_ch_mask(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "0x%x\n", + chip->dac_config & ADT7316_DA_2VREF_CH_MASK); +} + +static ssize_t adt7316_store_DAC_2Vref_ch_mask(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 dac_config; + u8 data; + int ret; + + ret = kstrtou8(buf, 16, &data); + if (ret || data > ADT7316_DA_2VREF_CH_MASK) + return -EINVAL; + + dac_config = chip->dac_config & (~ADT7316_DA_2VREF_CH_MASK); + dac_config |= data; + + ret = chip->bus.write(chip->bus.client, ADT7316_DAC_CONFIG, dac_config); + if (ret) + return -EIO; + + chip->dac_config = dac_config; + + return len; +} + +static IIO_DEVICE_ATTR(DAC_2Vref_channels_mask, S_IRUGO | S_IWUSR, + adt7316_show_DAC_2Vref_ch_mask, + adt7316_store_DAC_2Vref_ch_mask, + 0); + +static ssize_t adt7316_show_DAC_update_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (!(chip->config3 & ADT7316_DA_EN_VIA_DAC_LDCA)) + return sprintf(buf, "manual\n"); + + switch (chip->dac_config & ADT7316_DA_EN_MODE_MASK) { + case ADT7316_DA_EN_MODE_SINGLE: + return sprintf(buf, + "0 - auto at any MSB DAC writing\n"); + case ADT7316_DA_EN_MODE_AB_CD: + return sprintf(buf, + "1 - auto at MSB DAC AB and CD writing\n"); + case ADT7316_DA_EN_MODE_ABCD: + return sprintf(buf, + "2 - auto at MSB DAC ABCD writing\n"); + default: /* ADT7316_DA_EN_MODE_LDAC */ + return sprintf(buf, "3 - manual\n"); + } +} + +static ssize_t adt7316_store_DAC_update_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 dac_config; + u8 data; + int ret; + + if (!(chip->config3 & ADT7316_DA_EN_VIA_DAC_LDCA)) + return -EPERM; + + ret = kstrtou8(buf, 10, &data); + if (ret || data > ADT7316_DA_EN_MODE_MASK) + return -EINVAL; + + dac_config = chip->dac_config & (~ADT7316_DA_EN_MODE_MASK); + dac_config |= data; + + ret = chip->bus.write(chip->bus.client, ADT7316_DAC_CONFIG, dac_config); + if (ret) + return -EIO; + + chip->dac_config = dac_config; + + return len; +} + +static IIO_DEVICE_ATTR(DAC_update_mode, S_IRUGO | S_IWUSR, + adt7316_show_DAC_update_mode, + adt7316_store_DAC_update_mode, + 0); + +static ssize_t adt7316_show_all_DAC_update_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (chip->config3 & ADT7316_DA_EN_VIA_DAC_LDCA) + return sprintf(buf, "0 - auto at any MSB DAC writing\n" + "1 - auto at MSB DAC AB and CD writing\n" + "2 - auto at MSB DAC ABCD writing\n" + "3 - manual\n"); + else + return sprintf(buf, "manual\n"); +} + +static IIO_DEVICE_ATTR(all_DAC_update_modes, S_IRUGO, + adt7316_show_all_DAC_update_modes, NULL, 0); + + +static ssize_t adt7316_store_update_DAC(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 ldac_config; + u8 data; + int ret; + + if (chip->config3 & ADT7316_DA_EN_VIA_DAC_LDCA) { + if ((chip->dac_config & ADT7316_DA_EN_MODE_MASK) != + ADT7316_DA_EN_MODE_LDAC) + return -EPERM; + + ret = kstrtou8(buf, 16, &data); + if (ret || data > ADT7316_LDAC_EN_DA_MASK) + return -EINVAL; + + ldac_config = chip->ldac_config & (~ADT7316_LDAC_EN_DA_MASK); + ldac_config |= data; + + ret = chip->bus.write(chip->bus.client, ADT7316_LDAC_CONFIG, + ldac_config); + if (ret) + return -EIO; + } else { + gpio_set_value(chip->ldac_pin, 0); + gpio_set_value(chip->ldac_pin, 1); + } + + return len; +} + +static IIO_DEVICE_ATTR(update_DAC, S_IRUGO | S_IWUSR, + NULL, + adt7316_store_update_DAC, + 0); + +static ssize_t adt7316_show_DA_AB_Vref_bypass(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return -EPERM; + + return sprintf(buf, "%d\n", + !!(chip->dac_config & ADT7316_VREF_BYPASS_DAC_AB)); +} + +static ssize_t adt7316_store_DA_AB_Vref_bypass(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 dac_config; + int ret; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return -EPERM; + + dac_config = chip->dac_config & (~ADT7316_VREF_BYPASS_DAC_AB); + if (buf[0] == '1') + dac_config |= ADT7316_VREF_BYPASS_DAC_AB; + + ret = chip->bus.write(chip->bus.client, ADT7316_DAC_CONFIG, dac_config); + if (ret) + return -EIO; + + chip->dac_config = dac_config; + + return len; +} + +static IIO_DEVICE_ATTR(DA_AB_Vref_bypass, S_IRUGO | S_IWUSR, + adt7316_show_DA_AB_Vref_bypass, + adt7316_store_DA_AB_Vref_bypass, + 0); + +static ssize_t adt7316_show_DA_CD_Vref_bypass(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return -EPERM; + + return sprintf(buf, "%d\n", + !!(chip->dac_config & ADT7316_VREF_BYPASS_DAC_CD)); +} + +static ssize_t adt7316_store_DA_CD_Vref_bypass(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 dac_config; + int ret; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return -EPERM; + + dac_config = chip->dac_config & (~ADT7316_VREF_BYPASS_DAC_CD); + if (buf[0] == '1') + dac_config |= ADT7316_VREF_BYPASS_DAC_CD; + + ret = chip->bus.write(chip->bus.client, ADT7316_DAC_CONFIG, dac_config); + if (ret) + return -EIO; + + chip->dac_config = dac_config; + + return len; +} + +static IIO_DEVICE_ATTR(DA_CD_Vref_bypass, S_IRUGO | S_IWUSR, + adt7316_show_DA_CD_Vref_bypass, + adt7316_store_DA_CD_Vref_bypass, + 0); + +static ssize_t adt7316_show_DAC_internal_Vref(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return sprintf(buf, "0x%x\n", + (chip->dac_config & ADT7516_DAC_IN_VREF_MASK) >> + ADT7516_DAC_IN_VREF_OFFSET); + else + return sprintf(buf, "%d\n", + !!(chip->dac_config & ADT7316_DAC_IN_VREF)); +} + +static ssize_t adt7316_store_DAC_internal_Vref(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 ldac_config; + u8 data; + int ret; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) { + ret = kstrtou8(buf, 16, &data); + if (ret || data > 3) + return -EINVAL; + + ldac_config = chip->ldac_config & (~ADT7516_DAC_IN_VREF_MASK); + if (data & 0x1) + ldac_config |= ADT7516_DAC_AB_IN_VREF; + else if (data & 0x2) + ldac_config |= ADT7516_DAC_CD_IN_VREF; + } else { + ret = kstrtou8(buf, 16, &data); + if (ret) + return -EINVAL; + + ldac_config = chip->ldac_config & (~ADT7316_DAC_IN_VREF); + if (data) + ldac_config = chip->ldac_config | ADT7316_DAC_IN_VREF; + } + + ret = chip->bus.write(chip->bus.client, ADT7316_LDAC_CONFIG, + ldac_config); + if (ret) + return -EIO; + + chip->ldac_config = ldac_config; + + return len; +} + +static IIO_DEVICE_ATTR(DAC_internal_Vref, S_IRUGO | S_IWUSR, + adt7316_show_DAC_internal_Vref, + adt7316_store_DAC_internal_Vref, + 0); + +static ssize_t adt7316_show_ad(struct adt7316_chip_info *chip, + int channel, char *buf) +{ + u16 data; + u8 msb, lsb; + char sign = ' '; + int ret; + + if ((chip->config2 & ADT7316_AD_SINGLE_CH_MODE) && + channel != (chip->config2 & ADT7516_AD_SINGLE_CH_MASK)) + return -EPERM; + + switch (channel) { + case ADT7316_AD_SINGLE_CH_IN: + ret = chip->bus.read(chip->bus.client, + ADT7316_LSB_IN_TEMP_VDD, &lsb); + if (ret) + return -EIO; + + ret = chip->bus.read(chip->bus.client, + ADT7316_AD_MSB_DATA_BASE + channel, &msb); + if (ret) + return -EIO; + + data = msb << ADT7316_T_VALUE_FLOAT_OFFSET; + data |= lsb & ADT7316_LSB_IN_TEMP_MASK; + break; + case ADT7316_AD_SINGLE_CH_VDD: + ret = chip->bus.read(chip->bus.client, + ADT7316_LSB_IN_TEMP_VDD, &lsb); + if (ret) + return -EIO; + + ret = chip->bus.read(chip->bus.client, + + ADT7316_AD_MSB_DATA_BASE + channel, &msb); + if (ret) + return -EIO; + + data = msb << ADT7316_T_VALUE_FLOAT_OFFSET; + data |= (lsb & ADT7316_LSB_VDD_MASK) >> ADT7316_LSB_VDD_OFFSET; + return sprintf(buf, "%d\n", data); + default: /* ex_temp and ain */ + ret = chip->bus.read(chip->bus.client, + ADT7316_LSB_EX_TEMP_AIN, &lsb); + if (ret) + return -EIO; + + ret = chip->bus.read(chip->bus.client, + ADT7316_AD_MSB_DATA_BASE + channel, &msb); + if (ret) + return -EIO; + + data = msb << ADT7316_T_VALUE_FLOAT_OFFSET; + data |= lsb & (ADT7316_LSB_EX_TEMP_MASK << + (ADT7516_LSB_AIN_SHIFT * (channel - + (ADT7316_MSB_EX_TEMP - ADT7316_AD_MSB_DATA_BASE)))); + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return sprintf(buf, "%d\n", data); + + break; + } + + if (data & ADT7316_T_VALUE_SIGN) { + /* convert supplement to positive value */ + data = (ADT7316_T_VALUE_SIGN << 1) - data; + sign = '-'; + } + + return sprintf(buf, "%c%d.%.2d\n", sign, + (data >> ADT7316_T_VALUE_FLOAT_OFFSET), + (data & ADT7316_T_VALUE_FLOAT_MASK) * 25); +} + +static ssize_t adt7316_show_VDD(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7316_AD_SINGLE_CH_VDD, buf); +} +static IIO_DEVICE_ATTR(VDD, S_IRUGO, adt7316_show_VDD, NULL, 0); + +static ssize_t adt7316_show_in_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7316_AD_SINGLE_CH_IN, buf); +} + +static IIO_DEVICE_ATTR(in_temp, S_IRUGO, adt7316_show_in_temp, NULL, 0); + +static ssize_t adt7316_show_ex_temp_AIN1(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7316_AD_SINGLE_CH_EX, buf); +} + +static IIO_DEVICE_ATTR(ex_temp_AIN1, S_IRUGO, adt7316_show_ex_temp_AIN1, + NULL, 0); +static IIO_DEVICE_ATTR(ex_temp, S_IRUGO, adt7316_show_ex_temp_AIN1, NULL, 0); + +static ssize_t adt7316_show_AIN2(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7516_AD_SINGLE_CH_AIN2, buf); +} +static IIO_DEVICE_ATTR(AIN2, S_IRUGO, adt7316_show_AIN2, NULL, 0); + +static ssize_t adt7316_show_AIN3(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7516_AD_SINGLE_CH_AIN3, buf); +} +static IIO_DEVICE_ATTR(AIN3, S_IRUGO, adt7316_show_AIN3, NULL, 0); + +static ssize_t adt7316_show_AIN4(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7516_AD_SINGLE_CH_AIN4, buf); +} +static IIO_DEVICE_ATTR(AIN4, S_IRUGO, adt7316_show_AIN4, NULL, 0); + +static ssize_t adt7316_show_temp_offset(struct adt7316_chip_info *chip, + int offset_addr, char *buf) +{ + int data; + u8 val; + int ret; + + ret = chip->bus.read(chip->bus.client, offset_addr, &val); + if (ret) + return -EIO; + + data = (int)val; + if (val & 0x80) + data -= 256; + + return sprintf(buf, "%d\n", data); +} + +static ssize_t adt7316_store_temp_offset(struct adt7316_chip_info *chip, + int offset_addr, const char *buf, size_t len) +{ + int data; + u8 val; + int ret; + + ret = kstrtoint(buf, 10, &data); + if (ret || data > 127 || data < -128) + return -EINVAL; + + if (data < 0) + data += 256; + + val = (u8)data; + + ret = chip->bus.write(chip->bus.client, offset_addr, val); + if (ret) + return -EIO; + + return len; +} + +static ssize_t adt7316_show_in_temp_offset(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_temp_offset(chip, ADT7316_IN_TEMP_OFFSET, buf); +} + +static ssize_t adt7316_store_in_temp_offset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_temp_offset(chip, ADT7316_IN_TEMP_OFFSET, buf, + len); +} + +static IIO_DEVICE_ATTR(in_temp_offset, S_IRUGO | S_IWUSR, + adt7316_show_in_temp_offset, + adt7316_store_in_temp_offset, 0); + +static ssize_t adt7316_show_ex_temp_offset(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_temp_offset(chip, ADT7316_EX_TEMP_OFFSET, buf); +} + +static ssize_t adt7316_store_ex_temp_offset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_temp_offset(chip, ADT7316_EX_TEMP_OFFSET, buf, + len); +} + +static IIO_DEVICE_ATTR(ex_temp_offset, S_IRUGO | S_IWUSR, + adt7316_show_ex_temp_offset, + adt7316_store_ex_temp_offset, 0); + +static ssize_t adt7316_show_in_analog_temp_offset(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_temp_offset(chip, + ADT7316_IN_ANALOG_TEMP_OFFSET, buf); +} + +static ssize_t adt7316_store_in_analog_temp_offset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_temp_offset(chip, + ADT7316_IN_ANALOG_TEMP_OFFSET, buf, len); +} + +static IIO_DEVICE_ATTR(in_analog_temp_offset, S_IRUGO | S_IWUSR, + adt7316_show_in_analog_temp_offset, + adt7316_store_in_analog_temp_offset, 0); + +static ssize_t adt7316_show_ex_analog_temp_offset(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_temp_offset(chip, + ADT7316_EX_ANALOG_TEMP_OFFSET, buf); +} + +static ssize_t adt7316_store_ex_analog_temp_offset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_temp_offset(chip, + ADT7316_EX_ANALOG_TEMP_OFFSET, buf, len); +} + +static IIO_DEVICE_ATTR(ex_analog_temp_offset, S_IRUGO | S_IWUSR, + adt7316_show_ex_analog_temp_offset, + adt7316_store_ex_analog_temp_offset, 0); + +static ssize_t adt7316_show_DAC(struct adt7316_chip_info *chip, + int channel, char *buf) +{ + u16 data; + u8 msb, lsb, offset; + int ret; + + if (channel >= ADT7316_DA_MSB_DATA_REGS || + (channel == 0 && + (chip->config3 & ADT7316_EN_IN_TEMP_PROP_DACA)) || + (channel == 1 && + (chip->config3 & ADT7316_EN_EX_TEMP_PROP_DACB))) + return -EPERM; + + offset = chip->dac_bits - 8; + + if (chip->dac_bits > 8) { + ret = chip->bus.read(chip->bus.client, + ADT7316_DA_DATA_BASE + channel * 2, &lsb); + if (ret) + return -EIO; + } + + ret = chip->bus.read(chip->bus.client, + ADT7316_DA_DATA_BASE + 1 + channel * 2, &msb); + if (ret) + return -EIO; + + data = (msb << offset) + (lsb & ((1 << offset) - 1)); + + return sprintf(buf, "%d\n", data); +} + +static ssize_t adt7316_store_DAC(struct adt7316_chip_info *chip, + int channel, const char *buf, size_t len) +{ + u8 msb, lsb, offset; + u16 data; + int ret; + + if (channel >= ADT7316_DA_MSB_DATA_REGS || + (channel == 0 && + (chip->config3 & ADT7316_EN_IN_TEMP_PROP_DACA)) || + (channel == 1 && + (chip->config3 & ADT7316_EN_EX_TEMP_PROP_DACB))) + return -EPERM; + + offset = chip->dac_bits - 8; + + ret = kstrtou16(buf, 10, &data); + if (ret || data >= (1 << chip->dac_bits)) + return -EINVAL; + + if (chip->dac_bits > 8) { + lsb = data & (1 << offset); + ret = chip->bus.write(chip->bus.client, + ADT7316_DA_DATA_BASE + channel * 2, lsb); + if (ret) + return -EIO; + } + + msb = data >> offset; + ret = chip->bus.write(chip->bus.client, + ADT7316_DA_DATA_BASE + 1 + channel * 2, msb); + if (ret) + return -EIO; + + return len; +} + +static ssize_t adt7316_show_DAC_A(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_DAC(chip, 0, buf); +} + +static ssize_t adt7316_store_DAC_A(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_DAC(chip, 0, buf, len); +} + +static IIO_DEVICE_ATTR(DAC_A, S_IRUGO | S_IWUSR, adt7316_show_DAC_A, + adt7316_store_DAC_A, 0); + +static ssize_t adt7316_show_DAC_B(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_DAC(chip, 1, buf); +} + +static ssize_t adt7316_store_DAC_B(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_DAC(chip, 1, buf, len); +} + +static IIO_DEVICE_ATTR(DAC_B, S_IRUGO | S_IWUSR, adt7316_show_DAC_B, + adt7316_store_DAC_B, 0); + +static ssize_t adt7316_show_DAC_C(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_DAC(chip, 2, buf); +} + +static ssize_t adt7316_store_DAC_C(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_DAC(chip, 2, buf, len); +} + +static IIO_DEVICE_ATTR(DAC_C, S_IRUGO | S_IWUSR, adt7316_show_DAC_C, + adt7316_store_DAC_C, 0); + +static ssize_t adt7316_show_DAC_D(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_DAC(chip, 3, buf); +} + +static ssize_t adt7316_store_DAC_D(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_DAC(chip, 3, buf, len); +} + +static IIO_DEVICE_ATTR(DAC_D, S_IRUGO | S_IWUSR, adt7316_show_DAC_D, + adt7316_store_DAC_D, 0); + +static ssize_t adt7316_show_device_id(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 id; + int ret; + + ret = chip->bus.read(chip->bus.client, ADT7316_DEVICE_ID, &id); + if (ret) + return -EIO; + + return sprintf(buf, "%d\n", id); +} + +static IIO_DEVICE_ATTR(device_id, S_IRUGO, adt7316_show_device_id, NULL, 0); + +static ssize_t adt7316_show_manufactorer_id(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 id; + int ret; + + ret = chip->bus.read(chip->bus.client, ADT7316_MANUFACTURE_ID, &id); + if (ret) + return -EIO; + + return sprintf(buf, "%d\n", id); +} + +static IIO_DEVICE_ATTR(manufactorer_id, S_IRUGO, + adt7316_show_manufactorer_id, NULL, 0); + +static ssize_t adt7316_show_device_rev(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 rev; + int ret; + + ret = chip->bus.read(chip->bus.client, ADT7316_DEVICE_REV, &rev); + if (ret) + return -EIO; + + return sprintf(buf, "%d\n", rev); +} + +static IIO_DEVICE_ATTR(device_rev, S_IRUGO, adt7316_show_device_rev, NULL, 0); + +static ssize_t adt7316_show_bus_type(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 stat; + int ret; + + ret = chip->bus.read(chip->bus.client, ADT7316_SPI_LOCK_STAT, &stat); + if (ret) + return -EIO; + + if (stat) + return sprintf(buf, "spi\n"); + + return sprintf(buf, "i2c\n"); +} + +static IIO_DEVICE_ATTR(bus_type, S_IRUGO, adt7316_show_bus_type, NULL, 0); + +static struct attribute *adt7316_attributes[] = { + &iio_dev_attr_all_modes.dev_attr.attr, + &iio_dev_attr_mode.dev_attr.attr, + &iio_dev_attr_enabled.dev_attr.attr, + &iio_dev_attr_ad_channel.dev_attr.attr, + &iio_dev_attr_all_ad_channels.dev_attr.attr, + &iio_dev_attr_disable_averaging.dev_attr.attr, + &iio_dev_attr_enable_smbus_timeout.dev_attr.attr, + &iio_dev_attr_powerdown.dev_attr.attr, + &iio_dev_attr_fast_ad_clock.dev_attr.attr, + &iio_dev_attr_da_high_resolution.dev_attr.attr, + &iio_dev_attr_enable_proportion_DACA.dev_attr.attr, + &iio_dev_attr_enable_proportion_DACB.dev_attr.attr, + &iio_dev_attr_DAC_2Vref_channels_mask.dev_attr.attr, + &iio_dev_attr_DAC_update_mode.dev_attr.attr, + &iio_dev_attr_all_DAC_update_modes.dev_attr.attr, + &iio_dev_attr_update_DAC.dev_attr.attr, + &iio_dev_attr_DA_AB_Vref_bypass.dev_attr.attr, + &iio_dev_attr_DA_CD_Vref_bypass.dev_attr.attr, + &iio_dev_attr_DAC_internal_Vref.dev_attr.attr, + &iio_dev_attr_VDD.dev_attr.attr, + &iio_dev_attr_in_temp.dev_attr.attr, + &iio_dev_attr_ex_temp.dev_attr.attr, + &iio_dev_attr_in_temp_offset.dev_attr.attr, + &iio_dev_attr_ex_temp_offset.dev_attr.attr, + &iio_dev_attr_in_analog_temp_offset.dev_attr.attr, + &iio_dev_attr_ex_analog_temp_offset.dev_attr.attr, + &iio_dev_attr_DAC_A.dev_attr.attr, + &iio_dev_attr_DAC_B.dev_attr.attr, + &iio_dev_attr_DAC_C.dev_attr.attr, + &iio_dev_attr_DAC_D.dev_attr.attr, + &iio_dev_attr_device_id.dev_attr.attr, + &iio_dev_attr_manufactorer_id.dev_attr.attr, + &iio_dev_attr_device_rev.dev_attr.attr, + &iio_dev_attr_bus_type.dev_attr.attr, + NULL, +}; + +static const struct attribute_group adt7316_attribute_group = { + .attrs = adt7316_attributes, +}; + +static struct attribute *adt7516_attributes[] = { + &iio_dev_attr_all_modes.dev_attr.attr, + &iio_dev_attr_mode.dev_attr.attr, + &iio_dev_attr_select_ex_temp.dev_attr.attr, + &iio_dev_attr_enabled.dev_attr.attr, + &iio_dev_attr_ad_channel.dev_attr.attr, + &iio_dev_attr_all_ad_channels.dev_attr.attr, + &iio_dev_attr_disable_averaging.dev_attr.attr, + &iio_dev_attr_enable_smbus_timeout.dev_attr.attr, + &iio_dev_attr_powerdown.dev_attr.attr, + &iio_dev_attr_fast_ad_clock.dev_attr.attr, + &iio_dev_attr_AIN_internal_Vref.dev_attr.attr, + &iio_dev_attr_da_high_resolution.dev_attr.attr, + &iio_dev_attr_enable_proportion_DACA.dev_attr.attr, + &iio_dev_attr_enable_proportion_DACB.dev_attr.attr, + &iio_dev_attr_DAC_2Vref_channels_mask.dev_attr.attr, + &iio_dev_attr_DAC_update_mode.dev_attr.attr, + &iio_dev_attr_all_DAC_update_modes.dev_attr.attr, + &iio_dev_attr_update_DAC.dev_attr.attr, + &iio_dev_attr_DA_AB_Vref_bypass.dev_attr.attr, + &iio_dev_attr_DA_CD_Vref_bypass.dev_attr.attr, + &iio_dev_attr_DAC_internal_Vref.dev_attr.attr, + &iio_dev_attr_VDD.dev_attr.attr, + &iio_dev_attr_in_temp.dev_attr.attr, + &iio_dev_attr_ex_temp_AIN1.dev_attr.attr, + &iio_dev_attr_AIN2.dev_attr.attr, + &iio_dev_attr_AIN3.dev_attr.attr, + &iio_dev_attr_AIN4.dev_attr.attr, + &iio_dev_attr_in_temp_offset.dev_attr.attr, + &iio_dev_attr_ex_temp_offset.dev_attr.attr, + &iio_dev_attr_in_analog_temp_offset.dev_attr.attr, + &iio_dev_attr_ex_analog_temp_offset.dev_attr.attr, + &iio_dev_attr_DAC_A.dev_attr.attr, + &iio_dev_attr_DAC_B.dev_attr.attr, + &iio_dev_attr_DAC_C.dev_attr.attr, + &iio_dev_attr_DAC_D.dev_attr.attr, + &iio_dev_attr_device_id.dev_attr.attr, + &iio_dev_attr_manufactorer_id.dev_attr.attr, + &iio_dev_attr_device_rev.dev_attr.attr, + &iio_dev_attr_bus_type.dev_attr.attr, + NULL, +}; + +static const struct attribute_group adt7516_attribute_group = { + .attrs = adt7516_attributes, +}; + +static irqreturn_t adt7316_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct adt7316_chip_info *chip = iio_priv(indio_dev); + u8 stat1, stat2; + int ret; + s64 time; + + ret = chip->bus.read(chip->bus.client, ADT7316_INT_STAT1, &stat1); + if (!ret) { + if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) + stat1 &= 0x1F; + + time = iio_get_time_ns(); + if (stat1 & (1 << 0)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + time); + if (stat1 & (1 << 1)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + time); + if (stat1 & (1 << 2)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + time); + if (stat1 & (1 << 3)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + time); + if (stat1 & (1 << 5)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + time); + if (stat1 & (1 << 6)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 2, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + time); + if (stat1 & (1 << 7)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 3, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + time); + } + ret = chip->bus.read(chip->bus.client, ADT7316_INT_STAT2, &stat2); + if (!ret) { + if (stat2 & ADT7316_INT_MASK2_VDD) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + } + + return IRQ_HANDLED; +} + +/* + * Show mask of enabled interrupts in Hex. + */ +static ssize_t adt7316_show_int_mask(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "0x%x\n", chip->int_mask); +} + +/* + * Set 1 to the mask in Hex to enabled interrupts. + */ +static ssize_t adt7316_set_int_mask(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u16 data; + int ret; + u8 mask; + + ret = kstrtou16(buf, 16, &data); + if (ret || data >= ADT7316_VDD_INT_MASK + 1) + return -EINVAL; + + if (data & ADT7316_VDD_INT_MASK) + mask = 0; /* enable vdd int */ + else + mask = ADT7316_INT_MASK2_VDD; /* disable vdd int */ + + ret = chip->bus.write(chip->bus.client, ADT7316_INT_MASK2, mask); + if (!ret) { + chip->int_mask &= ~ADT7316_VDD_INT_MASK; + chip->int_mask |= data & ADT7316_VDD_INT_MASK; + } + + if (data & ADT7316_TEMP_AIN_INT_MASK) { + if ((chip->id & ID_FAMILY_MASK) == ID_ADT73XX) + /* mask in reg is opposite, set 1 to disable */ + mask = (~data) & ADT7316_TEMP_INT_MASK; + else + /* mask in reg is opposite, set 1 to disable */ + mask = (~data) & ADT7316_TEMP_AIN_INT_MASK; + } + ret = chip->bus.write(chip->bus.client, ADT7316_INT_MASK1, mask); + + chip->int_mask = mask; + + return len; +} +static inline ssize_t adt7316_show_ad_bound(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 val; + int data; + int ret; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT73XX && + this_attr->address > ADT7316_EX_TEMP_LOW) + return -EPERM; + + ret = chip->bus.read(chip->bus.client, this_attr->address, &val); + if (ret) + return -EIO; + + data = (int)val; + + if (!((chip->id & ID_FAMILY_MASK) == ID_ADT75XX && + (chip->config1 & ADT7516_SEL_AIN1_2_EX_TEMP_MASK) == 0)) { + if (data & 0x80) + data -= 256; + } + + return sprintf(buf, "%d\n", data); +} + +static inline ssize_t adt7316_set_ad_bound(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + int data; + u8 val; + int ret; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT73XX && + this_attr->address > ADT7316_EX_TEMP_LOW) + return -EPERM; + + ret = kstrtoint(buf, 10, &data); + if (ret) + return -EINVAL; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX && + (chip->config1 & ADT7516_SEL_AIN1_2_EX_TEMP_MASK) == 0) { + if (data > 255 || data < 0) + return -EINVAL; + } else { + if (data > 127 || data < -128) + return -EINVAL; + + if (data < 0) + data += 256; + } + + val = (u8)data; + + ret = chip->bus.write(chip->bus.client, this_attr->address, val); + if (ret) + return -EIO; + + return len; +} + +static ssize_t adt7316_show_int_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", !!(chip->config1 & ADT7316_INT_EN)); +} + +static ssize_t adt7316_set_int_enabled(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_to_iio_dev(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config1; + int ret; + + config1 = chip->config1 & (~ADT7316_INT_EN); + if (buf[0] == '1') + config1 |= ADT7316_INT_EN; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG1, config1); + if (ret) + return -EIO; + + chip->config1 = config1; + + return len; +} + +static IIO_DEVICE_ATTR(int_mask, + S_IRUGO | S_IWUSR, + adt7316_show_int_mask, adt7316_set_int_mask, + 0); +static IIO_DEVICE_ATTR(in_temp_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_IN_TEMP_HIGH); +static IIO_DEVICE_ATTR(in_temp_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_IN_TEMP_LOW); +static IIO_DEVICE_ATTR(ex_temp_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_EX_TEMP_HIGH); +static IIO_DEVICE_ATTR(ex_temp_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_EX_TEMP_LOW); + +/* NASTY duplication to be fixed */ +static IIO_DEVICE_ATTR(ex_temp_ain1_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_EX_TEMP_HIGH); +static IIO_DEVICE_ATTR(ex_temp_ain1_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_EX_TEMP_LOW); +static IIO_DEVICE_ATTR(ain2_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN2_HIGH); +static IIO_DEVICE_ATTR(ain2_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN2_LOW); +static IIO_DEVICE_ATTR(ain3_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN3_HIGH); +static IIO_DEVICE_ATTR(ain3_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN3_LOW); +static IIO_DEVICE_ATTR(ain4_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN4_HIGH); +static IIO_DEVICE_ATTR(ain4_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN4_LOW); +static IIO_DEVICE_ATTR(int_enabled, + S_IRUGO | S_IWUSR, + adt7316_show_int_enabled, + adt7316_set_int_enabled, 0); + +static struct attribute *adt7316_event_attributes[] = { + &iio_dev_attr_int_mask.dev_attr.attr, + &iio_dev_attr_in_temp_high_value.dev_attr.attr, + &iio_dev_attr_in_temp_low_value.dev_attr.attr, + &iio_dev_attr_ex_temp_high_value.dev_attr.attr, + &iio_dev_attr_ex_temp_low_value.dev_attr.attr, + &iio_dev_attr_int_enabled.dev_attr.attr, + NULL, +}; + +static struct attribute_group adt7316_event_attribute_group = { + .attrs = adt7316_event_attributes, + .name = "events", +}; + +static struct attribute *adt7516_event_attributes[] = { + &iio_dev_attr_int_mask.dev_attr.attr, + &iio_dev_attr_in_temp_high_value.dev_attr.attr, + &iio_dev_attr_in_temp_low_value.dev_attr.attr, + &iio_dev_attr_ex_temp_ain1_high_value.dev_attr.attr, + &iio_dev_attr_ex_temp_ain1_low_value.dev_attr.attr, + &iio_dev_attr_ain2_high_value.dev_attr.attr, + &iio_dev_attr_ain2_low_value.dev_attr.attr, + &iio_dev_attr_ain3_high_value.dev_attr.attr, + &iio_dev_attr_ain3_low_value.dev_attr.attr, + &iio_dev_attr_ain4_high_value.dev_attr.attr, + &iio_dev_attr_ain4_low_value.dev_attr.attr, + &iio_dev_attr_int_enabled.dev_attr.attr, + NULL, +}; + +static struct attribute_group adt7516_event_attribute_group = { + .attrs = adt7516_event_attributes, + .name = "events", +}; + +#ifdef CONFIG_PM_SLEEP +static int adt7316_disable(struct device *dev) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return _adt7316_store_enabled(chip, 0); +} + +static int adt7316_enable(struct device *dev) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return _adt7316_store_enabled(chip, 1); +} + +SIMPLE_DEV_PM_OPS(adt7316_pm_ops, adt7316_disable, adt7316_enable); +EXPORT_SYMBOL_GPL(adt7316_pm_ops); +#endif + +static const struct iio_info adt7316_info = { + .attrs = &adt7316_attribute_group, + .event_attrs = &adt7316_event_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info adt7516_info = { + .attrs = &adt7516_attribute_group, + .event_attrs = &adt7516_event_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* + * device probe and remove + */ +int adt7316_probe(struct device *dev, struct adt7316_bus *bus, + const char *name) +{ + struct adt7316_chip_info *chip; + struct iio_dev *indio_dev; + unsigned short *adt7316_platform_data = dev->platform_data; + int ret = 0; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + dev_set_drvdata(dev, indio_dev); + + chip->bus = *bus; + + if (name[4] == '3') + chip->id = ID_ADT7316 + (name[6] - '6'); + else if (name[4] == '5') + chip->id = ID_ADT7516 + (name[6] - '6'); + else + return -ENODEV; + + chip->ldac_pin = adt7316_platform_data[1]; + if (chip->ldac_pin) { + chip->config3 |= ADT7316_DA_EN_VIA_DAC_LDCA; + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + chip->config1 |= ADT7516_SEL_AIN3; + } + chip->int_mask = ADT7316_TEMP_INT_MASK | ADT7316_VDD_INT_MASK; + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + chip->int_mask |= ADT7516_AIN_INT_MASK; + + indio_dev->dev.parent = dev; + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + indio_dev->info = &adt7516_info; + else + indio_dev->info = &adt7316_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (chip->bus.irq > 0) { + if (adt7316_platform_data[0]) + chip->bus.irq_flags = adt7316_platform_data[0]; + + ret = devm_request_threaded_irq(dev, chip->bus.irq, + NULL, + &adt7316_event_handler, + chip->bus.irq_flags | + IRQF_ONESHOT, + indio_dev->name, + indio_dev); + if (ret) + return ret; + + if (chip->bus.irq_flags & IRQF_TRIGGER_HIGH) + chip->config1 |= ADT7316_INT_POLARITY; + } + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG1, chip->config1); + if (ret) + return -EIO; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, chip->config3); + if (ret) + return -EIO; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + dev_info(dev, "%s temperature sensor, ADC and DAC registered.\n", + indio_dev->name); + + return 0; +} +EXPORT_SYMBOL(adt7316_probe); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Analog Devices ADT7316/7/8 and ADT7516/7/9 digital temperature sensor, ADC and DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/addac/adt7316.h b/kernel/drivers/staging/iio/addac/adt7316.h new file mode 100644 index 000000000..ec40fbb69 --- /dev/null +++ b/kernel/drivers/staging/iio/addac/adt7316.h @@ -0,0 +1,36 @@ +/* + * ADT7316 digital temperature sensor driver supporting ADT7316/7/8 ADT7516/7/9 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _ADT7316_H_ +#define _ADT7316_H_ + +#include +#include + +#define ADT7316_REG_MAX_ADDR 0x3F + +struct adt7316_bus { + void *client; + int irq; + int irq_flags; + int (*read)(void *client, u8 reg, u8 *data); + int (*write)(void *client, u8 reg, u8 val); + int (*multi_read)(void *client, u8 first_reg, u8 count, u8 *data); + int (*multi_write)(void *client, u8 first_reg, u8 count, u8 *data); +}; + +#ifdef CONFIG_PM_SLEEP +extern const struct dev_pm_ops adt7316_pm_ops; +#define ADT7316_PM_OPS (&adt7316_pm_ops) +#else +#define ADT7316_PM_OPS NULL +#endif +int adt7316_probe(struct device *dev, struct adt7316_bus *bus, + const char *name); + +#endif diff --git a/kernel/drivers/staging/iio/cdc/Kconfig b/kernel/drivers/staging/iio/cdc/Kconfig new file mode 100644 index 000000000..80211df8c --- /dev/null +++ b/kernel/drivers/staging/iio/cdc/Kconfig @@ -0,0 +1,36 @@ +# +# CDC drivers +# +menu "Capacitance to digital converters" + +config AD7150 + tristate "Analog Devices ad7150/1/6 capacitive sensor driver" + depends on I2C + help + Say yes here to build support for Analog Devices capacitive sensors. + (ad7150, ad7151, ad7156) Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad7150. + +config AD7152 + tristate "Analog Devices ad7152/3 capacitive sensor driver" + depends on I2C + help + Say yes here to build support for Analog Devices capacitive sensors. + (ad7152, ad7153) Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad7152. + +config AD7746 + tristate "Analog Devices AD7745, AD7746 AD7747 capacitive sensor driver" + depends on I2C + help + Say yes here to build support for Analog Devices capacitive sensors. + (AD7745, AD7746, AD7747) Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad7746. + +endmenu diff --git a/kernel/drivers/staging/iio/cdc/Makefile b/kernel/drivers/staging/iio/cdc/Makefile new file mode 100644 index 000000000..a5fbabf5c --- /dev/null +++ b/kernel/drivers/staging/iio/cdc/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for industrial I/O DAC drivers +# + +obj-$(CONFIG_AD7150) += ad7150.o +obj-$(CONFIG_AD7152) += ad7152.o +obj-$(CONFIG_AD7746) += ad7746.o diff --git a/kernel/drivers/staging/iio/cdc/ad7150.c b/kernel/drivers/staging/iio/cdc/ad7150.c new file mode 100644 index 000000000..a2b7ae332 --- /dev/null +++ b/kernel/drivers/staging/iio/cdc/ad7150.c @@ -0,0 +1,679 @@ +/* + * AD7150 capacitive sensor driver supporting AD7150/1/6 + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +/* + * AD7150 registers definition + */ + +#define AD7150_STATUS 0 +#define AD7150_STATUS_OUT1 (1 << 3) +#define AD7150_STATUS_OUT2 (1 << 5) +#define AD7150_CH1_DATA_HIGH 1 +#define AD7150_CH2_DATA_HIGH 3 +#define AD7150_CH1_AVG_HIGH 5 +#define AD7150_CH2_AVG_HIGH 7 +#define AD7150_CH1_SENSITIVITY 9 +#define AD7150_CH1_THR_HOLD_H 9 +#define AD7150_CH1_TIMEOUT 10 +#define AD7150_CH1_SETUP 11 +#define AD7150_CH2_SENSITIVITY 12 +#define AD7150_CH2_THR_HOLD_H 12 +#define AD7150_CH2_TIMEOUT 13 +#define AD7150_CH2_SETUP 14 +#define AD7150_CFG 15 +#define AD7150_CFG_FIX (1 << 7) +#define AD7150_PD_TIMER 16 +#define AD7150_CH1_CAPDAC 17 +#define AD7150_CH2_CAPDAC 18 +#define AD7150_SN3 19 +#define AD7150_SN2 20 +#define AD7150_SN1 21 +#define AD7150_SN0 22 +#define AD7150_ID 23 + +/** + * struct ad7150_chip_info - instance specific chip data + * @client: i2c client for this device + * @current_event: device always has one type of event enabled. + * This element stores the event code of the current one. + * @threshold: thresholds for simple capacitance value events + * @thresh_sensitivity: threshold for simple capacitance offset + * from 'average' value. + * @mag_sensitity: threshold for magnitude of capacitance offset from + * from 'average' value. + * @thresh_timeout: a timeout, in samples from the moment an + * adaptive threshold event occurs to when the average + * value jumps to current value. + * @mag_timeout: a timeout, in sample from the moment an + * adaptive magnitude event occurs to when the average + * value jumps to the current value. + * @old_state: store state from previous event, allowing confirmation + * of new condition. + * @conversion_mode: the current conversion mode. + * @state_lock: ensure consistent state of this structure wrt the + * hardware. + */ +struct ad7150_chip_info { + struct i2c_client *client; + u64 current_event; + u16 threshold[2][2]; + u8 thresh_sensitivity[2][2]; + u8 mag_sensitivity[2][2]; + u8 thresh_timeout[2][2]; + u8 mag_timeout[2][2]; + int old_state; + char *conversion_mode; + struct mutex state_lock; +}; + +/* + * sysfs nodes + */ + +static const u8 ad7150_addresses[][6] = { + { AD7150_CH1_DATA_HIGH, AD7150_CH1_AVG_HIGH, + AD7150_CH1_SETUP, AD7150_CH1_THR_HOLD_H, + AD7150_CH1_SENSITIVITY, AD7150_CH1_TIMEOUT }, + { AD7150_CH2_DATA_HIGH, AD7150_CH2_AVG_HIGH, + AD7150_CH2_SETUP, AD7150_CH2_THR_HOLD_H, + AD7150_CH2_SENSITIVITY, AD7150_CH2_TIMEOUT }, +}; + +static int ad7150_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_smbus_read_word_data(chip->client, + ad7150_addresses[chan->channel][0]); + if (ret < 0) + return ret; + *val = swab16(ret); + return IIO_VAL_INT; + case IIO_CHAN_INFO_AVERAGE_RAW: + ret = i2c_smbus_read_word_data(chip->client, + ad7150_addresses[chan->channel][1]); + if (ret < 0) + return ret; + *val = swab16(ret); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7150_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + int ret; + u8 threshtype; + bool adaptive; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + + ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG); + if (ret < 0) + return ret; + + threshtype = (ret >> 5) & 0x03; + adaptive = !!(ret & 0x80); + + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + if (dir == IIO_EV_DIR_RISING) + return adaptive && (threshtype == 0x1); + return adaptive && (threshtype == 0x0); + case IIO_EV_TYPE_THRESH_ADAPTIVE: + if (dir == IIO_EV_DIR_RISING) + return adaptive && (threshtype == 0x3); + return adaptive && (threshtype == 0x2); + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_RISING) + return !adaptive && (threshtype == 0x1); + return !adaptive && (threshtype == 0x0); + default: + break; + } + return -EINVAL; +} + +/* lock should be held */ +static int ad7150_write_event_params(struct iio_dev *indio_dev, + unsigned int chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + int ret; + u16 value; + u8 sens, timeout; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int rising = (dir == IIO_EV_DIR_RISING); + u64 event_code; + + event_code = IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, chan, type, dir); + + if (event_code != chip->current_event) + return 0; + + switch (type) { + /* Note completely different from the adaptive versions */ + case IIO_EV_TYPE_THRESH: + value = chip->threshold[rising][chan]; + ret = i2c_smbus_write_word_data(chip->client, + ad7150_addresses[chan][3], + swab16(value)); + if (ret < 0) + return ret; + return 0; + case IIO_EV_TYPE_MAG_ADAPTIVE: + sens = chip->mag_sensitivity[rising][chan]; + timeout = chip->mag_timeout[rising][chan]; + break; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + sens = chip->thresh_sensitivity[rising][chan]; + timeout = chip->thresh_timeout[rising][chan]; + break; + default: + return -EINVAL; + } + ret = i2c_smbus_write_byte_data(chip->client, + ad7150_addresses[chan][4], + sens); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(chip->client, + ad7150_addresses[chan][5], + timeout); + if (ret < 0) + return ret; + + return 0; +} + +static int ad7150_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, int state) +{ + u8 thresh_type, cfg, adaptive; + int ret; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int rising = (dir == IIO_EV_DIR_RISING); + u64 event_code; + + /* Something must always be turned on */ + if (state == 0) + return -EINVAL; + + event_code = IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir); + if (event_code == chip->current_event) + return 0; + mutex_lock(&chip->state_lock); + ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG); + if (ret < 0) + goto error_ret; + + cfg = ret & ~((0x03 << 5) | (0x1 << 7)); + + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + adaptive = 1; + if (rising) + thresh_type = 0x1; + else + thresh_type = 0x0; + break; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + adaptive = 1; + if (rising) + thresh_type = 0x3; + else + thresh_type = 0x2; + break; + case IIO_EV_TYPE_THRESH: + adaptive = 0; + if (rising) + thresh_type = 0x1; + else + thresh_type = 0x0; + break; + default: + ret = -EINVAL; + goto error_ret; + } + + cfg |= (!adaptive << 7) | (thresh_type << 5); + + ret = i2c_smbus_write_byte_data(chip->client, AD7150_CFG, cfg); + if (ret < 0) + goto error_ret; + + chip->current_event = event_code; + + /* update control attributes */ + ret = ad7150_write_event_params(indio_dev, chan->channel, type, dir); +error_ret: + mutex_unlock(&chip->state_lock); + + return 0; +} + +static int ad7150_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int rising = (dir == IIO_EV_DIR_RISING); + + /* Complex register sharing going on here */ + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + *val = chip->mag_sensitivity[rising][chan->channel]; + return IIO_VAL_INT; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + *val = chip->thresh_sensitivity[rising][chan->channel]; + return IIO_VAL_INT; + case IIO_EV_TYPE_THRESH: + *val = chip->threshold[rising][chan->channel]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7150_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + int ret; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int rising = (dir == IIO_EV_DIR_RISING); + + mutex_lock(&chip->state_lock); + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + chip->mag_sensitivity[rising][chan->channel] = val; + break; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + chip->thresh_sensitivity[rising][chan->channel] = val; + break; + case IIO_EV_TYPE_THRESH: + chip->threshold[rising][chan->channel] = val; + break; + default: + ret = -EINVAL; + goto error_ret; + } + + /* write back if active */ + ret = ad7150_write_event_params(indio_dev, chan->channel, type, dir); + +error_ret: + mutex_unlock(&chip->state_lock); + return ret; +} + +static ssize_t ad7150_show_timeout(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7150_chip_info *chip = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + u8 value; + + /* use the event code for consistency reasons */ + int chan = IIO_EVENT_CODE_EXTRACT_CHAN(this_attr->address); + int rising = !!(IIO_EVENT_CODE_EXTRACT_DIR(this_attr->address) + == IIO_EV_DIR_RISING); + + switch (IIO_EVENT_CODE_EXTRACT_TYPE(this_attr->address)) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + value = chip->mag_timeout[rising][chan]; + break; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + value = chip->thresh_timeout[rising][chan]; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%d\n", value); +} + +static ssize_t ad7150_store_timeout(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7150_chip_info *chip = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int chan = IIO_EVENT_CODE_EXTRACT_CHAN(this_attr->address); + enum iio_event_direction dir; + enum iio_event_type type; + int rising; + u8 data; + int ret; + + type = IIO_EVENT_CODE_EXTRACT_TYPE(this_attr->address); + dir = IIO_EVENT_CODE_EXTRACT_DIR(this_attr->address); + rising = (dir == IIO_EV_DIR_RISING); + + ret = kstrtou8(buf, 10, &data); + if (ret < 0) + return ret; + + mutex_lock(&chip->state_lock); + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + chip->mag_timeout[rising][chan] = data; + break; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + chip->thresh_timeout[rising][chan] = data; + break; + default: + ret = -EINVAL; + goto error_ret; + } + + ret = ad7150_write_event_params(indio_dev, chan, type, dir); +error_ret: + mutex_unlock(&chip->state_lock); + + if (ret < 0) + return ret; + + return len; +} + +#define AD7150_TIMEOUT(chan, type, dir, ev_type, ev_dir) \ + IIO_DEVICE_ATTR(in_capacitance##chan##_##type##_##dir##_timeout, \ + S_IRUGO | S_IWUSR, \ + &ad7150_show_timeout, \ + &ad7150_store_timeout, \ + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, \ + chan, \ + IIO_EV_TYPE_##ev_type, \ + IIO_EV_DIR_##ev_dir)) +static AD7150_TIMEOUT(0, mag_adaptive, rising, MAG_ADAPTIVE, RISING); +static AD7150_TIMEOUT(0, mag_adaptive, falling, MAG_ADAPTIVE, FALLING); +static AD7150_TIMEOUT(1, mag_adaptive, rising, MAG_ADAPTIVE, RISING); +static AD7150_TIMEOUT(1, mag_adaptive, falling, MAG_ADAPTIVE, FALLING); +static AD7150_TIMEOUT(0, thresh_adaptive, rising, THRESH_ADAPTIVE, RISING); +static AD7150_TIMEOUT(0, thresh_adaptive, falling, THRESH_ADAPTIVE, FALLING); +static AD7150_TIMEOUT(1, thresh_adaptive, rising, THRESH_ADAPTIVE, RISING); +static AD7150_TIMEOUT(1, thresh_adaptive, falling, THRESH_ADAPTIVE, FALLING); + +static const struct iio_event_spec ad7150_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH_ADAPTIVE, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH_ADAPTIVE, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec ad7150_channels[] = { + { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_AVERAGE_RAW), + .event_spec = ad7150_events, + .num_event_specs = ARRAY_SIZE(ad7150_events), + }, { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_AVERAGE_RAW), + .event_spec = ad7150_events, + .num_event_specs = ARRAY_SIZE(ad7150_events), + }, +}; + +/* + * threshold events + */ + +static irqreturn_t ad7150_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + u8 int_status; + s64 timestamp = iio_get_time_ns(); + int ret; + + ret = i2c_smbus_read_byte_data(chip->client, AD7150_STATUS); + if (ret < 0) + return IRQ_HANDLED; + + int_status = ret; + + if ((int_status & AD7150_STATUS_OUT1) && + !(chip->old_state & AD7150_STATUS_OUT1)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if ((!(int_status & AD7150_STATUS_OUT1)) && + (chip->old_state & AD7150_STATUS_OUT1)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + if ((int_status & AD7150_STATUS_OUT2) && + !(chip->old_state & AD7150_STATUS_OUT2)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, + 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if ((!(int_status & AD7150_STATUS_OUT2)) && + (chip->old_state & AD7150_STATUS_OUT2)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, + 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + /* store the status to avoid repushing same events */ + chip->old_state = int_status; + + return IRQ_HANDLED; +} + +/* Timeouts not currently handled by core */ +static struct attribute *ad7150_event_attributes[] = { + &iio_dev_attr_in_capacitance0_mag_adaptive_rising_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance0_mag_adaptive_falling_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance1_mag_adaptive_rising_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance1_mag_adaptive_falling_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance0_thresh_adaptive_rising_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance0_thresh_adaptive_falling_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance1_thresh_adaptive_rising_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance1_thresh_adaptive_falling_timeout + .dev_attr.attr, + NULL, +}; + +static struct attribute_group ad7150_event_attribute_group = { + .attrs = ad7150_event_attributes, + .name = "events", +}; + +static const struct iio_info ad7150_info = { + .event_attrs = &ad7150_event_attribute_group, + .driver_module = THIS_MODULE, + .read_raw = &ad7150_read_raw, + .read_event_config = &ad7150_read_event_config, + .write_event_config = &ad7150_write_event_config, + .read_event_value = &ad7150_read_event_value, + .write_event_value = &ad7150_write_event_value, +}; + +/* + * device probe and remove + */ + +static int ad7150_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct ad7150_chip_info *chip; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + chip = iio_priv(indio_dev); + mutex_init(&chip->state_lock); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + chip->client = client; + + indio_dev->name = id->name; + indio_dev->channels = ad7150_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7150_channels); + /* Establish that the iio_dev is a child of the i2c device */ + indio_dev->dev.parent = &client->dev; + + indio_dev->info = &ad7150_info; + + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, + &ad7150_event_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "ad7150_irq1", + indio_dev); + if (ret) + return ret; + } + + if (client->dev.platform_data) { + ret = devm_request_threaded_irq(&client->dev, *(unsigned int *) + client->dev.platform_data, + NULL, + &ad7150_event_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "ad7150_irq2", + indio_dev); + if (ret) + return ret; + } + + ret = iio_device_register(indio_dev); + if (ret) + return ret; + + dev_info(&client->dev, "%s capacitive sensor registered,irq: %d\n", + id->name, client->irq); + + return 0; +} + +static int ad7150_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + return 0; +} + +static const struct i2c_device_id ad7150_id[] = { + { "ad7150", 0 }, + { "ad7151", 0 }, + { "ad7156", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad7150_id); + +static struct i2c_driver ad7150_driver = { + .driver = { + .name = "ad7150", + }, + .probe = ad7150_probe, + .remove = ad7150_remove, + .id_table = ad7150_id, +}; +module_i2c_driver(ad7150_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices AD7150/1/6 capacitive sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/cdc/ad7152.c b/kernel/drivers/staging/iio/cdc/ad7152.c new file mode 100644 index 000000000..87110d940 --- /dev/null +++ b/kernel/drivers/staging/iio/cdc/ad7152.c @@ -0,0 +1,543 @@ +/* + * AD7152 capacitive sensor driver supporting AD7152/3 + * + * Copyright 2010-2011a Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * TODO: Check compliance of calibbias with abi (units) + */ +/* + * AD7152 registers definition + */ + +#define AD7152_REG_STATUS 0 +#define AD7152_REG_CH1_DATA_HIGH 1 +#define AD7152_REG_CH2_DATA_HIGH 3 +#define AD7152_REG_CH1_OFFS_HIGH 5 +#define AD7152_REG_CH2_OFFS_HIGH 7 +#define AD7152_REG_CH1_GAIN_HIGH 9 +#define AD7152_REG_CH1_SETUP 11 +#define AD7152_REG_CH2_GAIN_HIGH 12 +#define AD7152_REG_CH2_SETUP 14 +#define AD7152_REG_CFG 15 +#define AD7152_REG_RESEVERD 16 +#define AD7152_REG_CAPDAC_POS 17 +#define AD7152_REG_CAPDAC_NEG 18 +#define AD7152_REG_CFG2 26 + +/* Status Register Bit Designations (AD7152_REG_STATUS) */ +#define AD7152_STATUS_RDY1 (1 << 0) +#define AD7152_STATUS_RDY2 (1 << 1) +#define AD7152_STATUS_C1C2 (1 << 2) +#define AD7152_STATUS_PWDN (1 << 7) + +/* Setup Register Bit Designations (AD7152_REG_CHx_SETUP) */ +#define AD7152_SETUP_CAPDIFF (1 << 5) +#define AD7152_SETUP_RANGE_2pF (0 << 6) +#define AD7152_SETUP_RANGE_0_5pF (1 << 6) +#define AD7152_SETUP_RANGE_1pF (2 << 6) +#define AD7152_SETUP_RANGE_4pF (3 << 6) +#define AD7152_SETUP_RANGE(x) ((x) << 6) + +/* Config Register Bit Designations (AD7152_REG_CFG) */ +#define AD7152_CONF_CH2EN (1 << 3) +#define AD7152_CONF_CH1EN (1 << 4) +#define AD7152_CONF_MODE_IDLE (0 << 0) +#define AD7152_CONF_MODE_CONT_CONV (1 << 0) +#define AD7152_CONF_MODE_SINGLE_CONV (2 << 0) +#define AD7152_CONF_MODE_OFFS_CAL (5 << 0) +#define AD7152_CONF_MODE_GAIN_CAL (6 << 0) + +/* Capdac Register Bit Designations (AD7152_REG_CAPDAC_XXX) */ +#define AD7152_CAPDAC_DACEN (1 << 7) +#define AD7152_CAPDAC_DACP(x) ((x) & 0x1F) + +/* CFG2 Register Bit Designations (AD7152_REG_CFG2) */ +#define AD7152_CFG2_OSR(x) (((x) & 0x3) << 4) + +enum { + AD7152_DATA, + AD7152_OFFS, + AD7152_GAIN, + AD7152_SETUP +}; + +/* + * struct ad7152_chip_info - chip specific information + */ + +struct ad7152_chip_info { + struct i2c_client *client; + /* + * Capacitive channel digital filter setup; + * conversion time/update rate setup per channel + */ + u8 filter_rate_setup; + u8 setup[2]; +}; + +static inline ssize_t ad7152_start_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len, + u8 regval) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7152_chip_info *chip = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + bool doit; + int ret, timeout = 10; + + ret = strtobool(buf, &doit); + if (ret < 0) + return ret; + + if (!doit) + return 0; + + if (this_attr->address == 0) + regval |= AD7152_CONF_CH1EN; + else + regval |= AD7152_CONF_CH2EN; + + mutex_lock(&indio_dev->mlock); + ret = i2c_smbus_write_byte_data(chip->client, AD7152_REG_CFG, regval); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + do { + mdelay(20); + ret = i2c_smbus_read_byte_data(chip->client, AD7152_REG_CFG); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + } while ((ret == regval) && timeout--); + + mutex_unlock(&indio_dev->mlock); + return len; +} +static ssize_t ad7152_start_offset_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + return ad7152_start_calib(dev, attr, buf, len, + AD7152_CONF_MODE_OFFS_CAL); +} +static ssize_t ad7152_start_gain_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + return ad7152_start_calib(dev, attr, buf, len, + AD7152_CONF_MODE_GAIN_CAL); +} + +static IIO_DEVICE_ATTR(in_capacitance0_calibbias_calibration, + S_IWUSR, NULL, ad7152_start_offset_calib, 0); +static IIO_DEVICE_ATTR(in_capacitance1_calibbias_calibration, + S_IWUSR, NULL, ad7152_start_offset_calib, 1); +static IIO_DEVICE_ATTR(in_capacitance0_calibscale_calibration, + S_IWUSR, NULL, ad7152_start_gain_calib, 0); +static IIO_DEVICE_ATTR(in_capacitance1_calibscale_calibration, + S_IWUSR, NULL, ad7152_start_gain_calib, 1); + +/* Values are Update Rate (Hz), Conversion Time (ms) + 1*/ +static const unsigned char ad7152_filter_rate_table[][2] = { + {200, 5 + 1}, {50, 20 + 1}, {20, 50 + 1}, {17, 60 + 1}, +}; + +static ssize_t ad7152_show_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7152_chip_info *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", + ad7152_filter_rate_table[chip->filter_rate_setup][0]); +} + +static ssize_t ad7152_store_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7152_chip_info *chip = iio_priv(indio_dev); + u8 data; + int ret, i; + + ret = kstrtou8(buf, 10, &data); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ad7152_filter_rate_table); i++) + if (data >= ad7152_filter_rate_table[i][0]) + break; + + if (i >= ARRAY_SIZE(ad7152_filter_rate_table)) + i = ARRAY_SIZE(ad7152_filter_rate_table) - 1; + + mutex_lock(&indio_dev->mlock); + ret = i2c_smbus_write_byte_data(chip->client, + AD7152_REG_CFG2, AD7152_CFG2_OSR(i)); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + chip->filter_rate_setup = i; + mutex_unlock(&indio_dev->mlock); + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, + ad7152_show_filter_rate_setup, + ad7152_store_filter_rate_setup); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("200 50 20 17"); + +static IIO_CONST_ATTR(in_capacitance_scale_available, + "0.000061050 0.000030525 0.000015263 0.000007631"); + +static struct attribute *ad7152_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_capacitance0_calibbias_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance1_calibbias_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance0_calibscale_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance1_calibscale_calibration.dev_attr.attr, + &iio_const_attr_in_capacitance_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7152_attribute_group = { + .attrs = ad7152_attributes, +}; + +static const u8 ad7152_addresses[][4] = { + { AD7152_REG_CH1_DATA_HIGH, AD7152_REG_CH1_OFFS_HIGH, + AD7152_REG_CH1_GAIN_HIGH, AD7152_REG_CH1_SETUP }, + { AD7152_REG_CH2_DATA_HIGH, AD7152_REG_CH2_OFFS_HIGH, + AD7152_REG_CH2_GAIN_HIGH, AD7152_REG_CH2_SETUP }, +}; + +/* Values are nano relative to pf base. */ +static const int ad7152_scale_table[] = { + 30525, 7631, 15263, 61050 +}; + +static int ad7152_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7152_chip_info *chip = iio_priv(indio_dev); + int ret, i; + + mutex_lock(&indio_dev->mlock); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (val != 1) { + ret = -EINVAL; + goto out; + } + + val = (val2 * 1024) / 15625; + + ret = i2c_smbus_write_word_data(chip->client, + ad7152_addresses[chan->channel][AD7152_GAIN], + swab16(val)); + if (ret < 0) + goto out; + + ret = 0; + break; + + case IIO_CHAN_INFO_CALIBBIAS: + if ((val < 0) | (val > 0xFFFF)) { + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_word_data(chip->client, + ad7152_addresses[chan->channel][AD7152_OFFS], + swab16(val)); + if (ret < 0) + goto out; + + ret = 0; + break; + case IIO_CHAN_INFO_SCALE: + if (val != 0) { + ret = -EINVAL; + goto out; + } + for (i = 0; i < ARRAY_SIZE(ad7152_scale_table); i++) + if (val2 == ad7152_scale_table[i]) + break; + + chip->setup[chan->channel] &= ~AD7152_SETUP_RANGE_4pF; + chip->setup[chan->channel] |= AD7152_SETUP_RANGE(i); + + ret = i2c_smbus_write_byte_data(chip->client, + ad7152_addresses[chan->channel][AD7152_SETUP], + chip->setup[chan->channel]); + if (ret < 0) + goto out; + + ret = 0; + break; + default: + ret = -EINVAL; + } + +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} +static int ad7152_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct ad7152_chip_info *chip = iio_priv(indio_dev); + int ret; + u8 regval = 0; + + mutex_lock(&indio_dev->mlock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* First set whether in differential mode */ + + regval = chip->setup[chan->channel]; + + if (chan->differential) + chip->setup[chan->channel] |= AD7152_SETUP_CAPDIFF; + else + chip->setup[chan->channel] &= ~AD7152_SETUP_CAPDIFF; + + if (regval != chip->setup[chan->channel]) { + ret = i2c_smbus_write_byte_data(chip->client, + ad7152_addresses[chan->channel][AD7152_SETUP], + chip->setup[chan->channel]); + if (ret < 0) + goto out; + } + /* Make sure the channel is enabled */ + if (chan->channel == 0) + regval = AD7152_CONF_CH1EN; + else + regval = AD7152_CONF_CH2EN; + + /* Trigger a single read */ + regval |= AD7152_CONF_MODE_SINGLE_CONV; + ret = i2c_smbus_write_byte_data(chip->client, AD7152_REG_CFG, + regval); + if (ret < 0) + goto out; + + msleep(ad7152_filter_rate_table[chip->filter_rate_setup][1]); + /* Now read the actual register */ + ret = i2c_smbus_read_word_data(chip->client, + ad7152_addresses[chan->channel][AD7152_DATA]); + if (ret < 0) + goto out; + *val = swab16(ret); + + if (chan->differential) + *val -= 0x8000; + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + + ret = i2c_smbus_read_word_data(chip->client, + ad7152_addresses[chan->channel][AD7152_GAIN]); + if (ret < 0) + goto out; + /* 1 + gain_val / 2^16 */ + *val = 1; + *val2 = (15625 * swab16(ret)) / 1024; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_CALIBBIAS: + ret = i2c_smbus_read_word_data(chip->client, + ad7152_addresses[chan->channel][AD7152_OFFS]); + if (ret < 0) + goto out; + *val = swab16(ret); + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + ret = i2c_smbus_read_byte_data(chip->client, + ad7152_addresses[chan->channel][AD7152_SETUP]); + if (ret < 0) + goto out; + *val = 0; + *val2 = ad7152_scale_table[ret >> 6]; + + ret = IIO_VAL_INT_PLUS_NANO; + break; + default: + ret = -EINVAL; + } +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static int ad7152_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static const struct iio_info ad7152_info = { + .attrs = &ad7152_attribute_group, + .read_raw = &ad7152_read_raw, + .write_raw = &ad7152_write_raw, + .write_raw_get_fmt = &ad7152_write_raw_get_fmt, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec ad7152_channels[] = { + { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_CAPACITANCE, + .differential = 1, + .indexed = 1, + .channel = 0, + .channel2 = 2, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_CAPACITANCE, + .differential = 1, + .indexed = 1, + .channel = 1, + .channel2 = 3, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE), + } +}; +/* + * device probe and remove + */ + +static int ad7152_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct ad7152_chip_info *chip; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + chip->client = client; + + /* Establish that the iio_dev is a child of the i2c device */ + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ad7152_info; + indio_dev->channels = ad7152_channels; + if (id->driver_data == 0) + indio_dev->num_channels = ARRAY_SIZE(ad7152_channels); + else + indio_dev->num_channels = 2; + indio_dev->num_channels = ARRAY_SIZE(ad7152_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + return ret; + + dev_err(&client->dev, "%s capacitive sensor registered\n", id->name); + + return 0; +} + +static int ad7152_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + return 0; +} + +static const struct i2c_device_id ad7152_id[] = { + { "ad7152", 0 }, + { "ad7153", 1 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad7152_id); + +static struct i2c_driver ad7152_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = ad7152_probe, + .remove = ad7152_remove, + .id_table = ad7152_id, +}; +module_i2c_driver(ad7152_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices AD7152/3 capacitive sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/cdc/ad7746.c b/kernel/drivers/staging/iio/cdc/ad7746.c new file mode 100644 index 000000000..e6e9eaa9e --- /dev/null +++ b/kernel/drivers/staging/iio/cdc/ad7746.c @@ -0,0 +1,791 @@ +/* + * AD7746 capacitive sensor driver supporting AD7745, AD7746 and AD7747 + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ad7746.h" + +/* + * AD7746 Register Definition + */ + +#define AD7746_REG_STATUS 0 +#define AD7746_REG_CAP_DATA_HIGH 1 +#define AD7746_REG_CAP_DATA_MID 2 +#define AD7746_REG_CAP_DATA_LOW 3 +#define AD7746_REG_VT_DATA_HIGH 4 +#define AD7746_REG_VT_DATA_MID 5 +#define AD7746_REG_VT_DATA_LOW 6 +#define AD7746_REG_CAP_SETUP 7 +#define AD7746_REG_VT_SETUP 8 +#define AD7746_REG_EXC_SETUP 9 +#define AD7746_REG_CFG 10 +#define AD7746_REG_CAPDACA 11 +#define AD7746_REG_CAPDACB 12 +#define AD7746_REG_CAP_OFFH 13 +#define AD7746_REG_CAP_OFFL 14 +#define AD7746_REG_CAP_GAINH 15 +#define AD7746_REG_CAP_GAINL 16 +#define AD7746_REG_VOLT_GAINH 17 +#define AD7746_REG_VOLT_GAINL 18 + +/* Status Register Bit Designations (AD7746_REG_STATUS) */ +#define AD7746_STATUS_EXCERR (1 << 3) +#define AD7746_STATUS_RDY (1 << 2) +#define AD7746_STATUS_RDYVT (1 << 1) +#define AD7746_STATUS_RDYCAP (1 << 0) + +/* Capacitive Channel Setup Register Bit Designations (AD7746_REG_CAP_SETUP) */ +#define AD7746_CAPSETUP_CAPEN (1 << 7) +#define AD7746_CAPSETUP_CIN2 (1 << 6) /* AD7746 only */ +#define AD7746_CAPSETUP_CAPDIFF (1 << 5) +#define AD7746_CAPSETUP_CACHOP (1 << 0) + +/* Voltage/Temperature Setup Register Bit Designations (AD7746_REG_VT_SETUP) */ +#define AD7746_VTSETUP_VTEN (1 << 7) +#define AD7746_VTSETUP_VTMD_INT_TEMP (0 << 5) +#define AD7746_VTSETUP_VTMD_EXT_TEMP (1 << 5) +#define AD7746_VTSETUP_VTMD_VDD_MON (2 << 5) +#define AD7746_VTSETUP_VTMD_EXT_VIN (3 << 5) +#define AD7746_VTSETUP_EXTREF (1 << 4) +#define AD7746_VTSETUP_VTSHORT (1 << 1) +#define AD7746_VTSETUP_VTCHOP (1 << 0) + +/* Excitation Setup Register Bit Designations (AD7746_REG_EXC_SETUP) */ +#define AD7746_EXCSETUP_CLKCTRL (1 << 7) +#define AD7746_EXCSETUP_EXCON (1 << 6) +#define AD7746_EXCSETUP_EXCB (1 << 5) +#define AD7746_EXCSETUP_NEXCB (1 << 4) +#define AD7746_EXCSETUP_EXCA (1 << 3) +#define AD7746_EXCSETUP_NEXCA (1 << 2) +#define AD7746_EXCSETUP_EXCLVL(x) (((x) & 0x3) << 0) + +/* Config Register Bit Designations (AD7746_REG_CFG) */ +#define AD7746_CONF_VTFS(x) ((x) << 6) +#define AD7746_CONF_CAPFS(x) ((x) << 3) +#define AD7746_CONF_MODE_IDLE (0 << 0) +#define AD7746_CONF_MODE_CONT_CONV (1 << 0) +#define AD7746_CONF_MODE_SINGLE_CONV (2 << 0) +#define AD7746_CONF_MODE_PWRDN (3 << 0) +#define AD7746_CONF_MODE_OFFS_CAL (5 << 0) +#define AD7746_CONF_MODE_GAIN_CAL (6 << 0) + +/* CAPDAC Register Bit Designations (AD7746_REG_CAPDACx) */ +#define AD7746_CAPDAC_DACEN (1 << 7) +#define AD7746_CAPDAC_DACP(x) ((x) & 0x7F) + +/* + * struct ad7746_chip_info - chip specific information + */ + +struct ad7746_chip_info { + struct i2c_client *client; + /* + * Capacitive channel digital filter setup; + * conversion time/update rate setup per channel + */ + u8 config; + u8 cap_setup; + u8 vt_setup; + u8 capdac[2][2]; + s8 capdac_set; + + union { + __be32 d32; + u8 d8[4]; + } data ____cacheline_aligned; +}; + +enum ad7746_chan { + VIN, + VIN_VDD, + TEMP_INT, + TEMP_EXT, + CIN1, + CIN1_DIFF, + CIN2, + CIN2_DIFF, +}; + +static const struct iio_chan_spec ad7746_channels[] = { + [VIN] = { + .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 = AD7746_REG_VT_DATA_HIGH << 8 | + AD7746_VTSETUP_VTMD_EXT_VIN, + }, + [VIN_VDD] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .extend_name = "supply", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .address = AD7746_REG_VT_DATA_HIGH << 8 | + AD7746_VTSETUP_VTMD_VDD_MON, + }, + [TEMP_INT] = { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AD7746_REG_VT_DATA_HIGH << 8 | + AD7746_VTSETUP_VTMD_INT_TEMP, + }, + [TEMP_EXT] = { + .type = IIO_TEMP, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AD7746_REG_VT_DATA_HIGH << 8 | + AD7746_VTSETUP_VTMD_EXT_TEMP, + }, + [CIN1] = { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE), + .address = AD7746_REG_CAP_DATA_HIGH << 8, + }, + [CIN1_DIFF] = { + .type = IIO_CAPACITANCE, + .differential = 1, + .indexed = 1, + .channel = 0, + .channel2 = 2, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE), + .address = AD7746_REG_CAP_DATA_HIGH << 8 | + AD7746_CAPSETUP_CAPDIFF + }, + [CIN2] = { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE), + .address = AD7746_REG_CAP_DATA_HIGH << 8 | + AD7746_CAPSETUP_CIN2, + }, + [CIN2_DIFF] = { + .type = IIO_CAPACITANCE, + .differential = 1, + .indexed = 1, + .channel = 1, + .channel2 = 3, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_CALIBBIAS) | + BIT(IIO_CHAN_INFO_SCALE), + .address = AD7746_REG_CAP_DATA_HIGH << 8 | + AD7746_CAPSETUP_CAPDIFF | AD7746_CAPSETUP_CIN2, + } +}; + +/* Values are Update Rate (Hz), Conversion Time (ms) + 1*/ +static const unsigned char ad7746_vt_filter_rate_table[][2] = { + {50, 20 + 1}, {31, 32 + 1}, {16, 62 + 1}, {8, 122 + 1}, +}; + +static const unsigned char ad7746_cap_filter_rate_table[][2] = { + {91, 11 + 1}, {84, 12 + 1}, {50, 20 + 1}, {26, 38 + 1}, + {16, 62 + 1}, {13, 77 + 1}, {11, 92 + 1}, {9, 110 + 1}, +}; + +static int ad7746_select_channel(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad7746_chip_info *chip = iio_priv(indio_dev); + int ret, delay; + u8 vt_setup, cap_setup; + + switch (chan->type) { + case IIO_CAPACITANCE: + cap_setup = (chan->address & 0xFF) | AD7746_CAPSETUP_CAPEN; + vt_setup = chip->vt_setup & ~AD7746_VTSETUP_VTEN; + delay = ad7746_cap_filter_rate_table[(chip->config >> 3) & + 0x7][1]; + + if (chip->capdac_set != chan->channel) { + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAPDACA, + chip->capdac[chan->channel][0]); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAPDACB, + chip->capdac[chan->channel][1]); + if (ret < 0) + return ret; + + chip->capdac_set = chan->channel; + } + break; + case IIO_VOLTAGE: + case IIO_TEMP: + vt_setup = (chan->address & 0xFF) | AD7746_VTSETUP_VTEN; + cap_setup = chip->cap_setup & ~AD7746_CAPSETUP_CAPEN; + delay = ad7746_cap_filter_rate_table[(chip->config >> 6) & + 0x3][1]; + break; + default: + return -EINVAL; + } + + if (chip->cap_setup != cap_setup) { + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAP_SETUP, + cap_setup); + if (ret < 0) + return ret; + + chip->cap_setup = cap_setup; + } + + if (chip->vt_setup != vt_setup) { + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_VT_SETUP, + vt_setup); + if (ret < 0) + return ret; + + chip->vt_setup = vt_setup; + } + + return delay; +} + +static inline ssize_t ad7746_start_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len, + u8 regval) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + bool doit; + int ret, timeout = 10; + + ret = strtobool(buf, &doit); + if (ret < 0) + return ret; + + if (!doit) + return 0; + + mutex_lock(&indio_dev->mlock); + regval |= chip->config; + ret = i2c_smbus_write_byte_data(chip->client, AD7746_REG_CFG, regval); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + do { + msleep(20); + ret = i2c_smbus_read_byte_data(chip->client, AD7746_REG_CFG); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + } while ((ret == regval) && timeout--); + + mutex_unlock(&indio_dev->mlock); + + return len; +} + +static ssize_t ad7746_start_offset_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + int ret = ad7746_select_channel(indio_dev, + &ad7746_channels[to_iio_dev_attr(attr)->address]); + if (ret < 0) + return ret; + + return ad7746_start_calib(dev, attr, buf, len, + AD7746_CONF_MODE_OFFS_CAL); +} + +static ssize_t ad7746_start_gain_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + int ret = ad7746_select_channel(indio_dev, + &ad7746_channels[to_iio_dev_attr(attr)->address]); + if (ret < 0) + return ret; + + return ad7746_start_calib(dev, attr, buf, len, + AD7746_CONF_MODE_GAIN_CAL); +} + +static IIO_DEVICE_ATTR(in_capacitance0_calibbias_calibration, + S_IWUSR, NULL, ad7746_start_offset_calib, CIN1); +static IIO_DEVICE_ATTR(in_capacitance1_calibbias_calibration, + S_IWUSR, NULL, ad7746_start_offset_calib, CIN2); +static IIO_DEVICE_ATTR(in_capacitance0_calibscale_calibration, + S_IWUSR, NULL, ad7746_start_gain_calib, CIN1); +static IIO_DEVICE_ATTR(in_capacitance1_calibscale_calibration, + S_IWUSR, NULL, ad7746_start_gain_calib, CIN2); +static IIO_DEVICE_ATTR(in_voltage0_calibscale_calibration, + S_IWUSR, NULL, ad7746_start_gain_calib, VIN); + +static ssize_t ad7746_show_cap_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", ad7746_cap_filter_rate_table[ + (chip->config >> 3) & 0x7][0]); +} + +static ssize_t ad7746_store_cap_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + u8 data; + int ret, i; + + ret = kstrtou8(buf, 10, &data); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ad7746_cap_filter_rate_table); i++) + if (data >= ad7746_cap_filter_rate_table[i][0]) + break; + + if (i >= ARRAY_SIZE(ad7746_cap_filter_rate_table)) + i = ARRAY_SIZE(ad7746_cap_filter_rate_table) - 1; + + mutex_lock(&indio_dev->mlock); + chip->config &= ~AD7746_CONF_CAPFS(0x7); + chip->config |= AD7746_CONF_CAPFS(i); + mutex_unlock(&indio_dev->mlock); + + return len; +} + +static ssize_t ad7746_show_vt_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", ad7746_vt_filter_rate_table[ + (chip->config >> 6) & 0x3][0]); +} + +static ssize_t ad7746_store_vt_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + u8 data; + int ret, i; + + ret = kstrtou8(buf, 10, &data); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ad7746_vt_filter_rate_table); i++) + if (data >= ad7746_vt_filter_rate_table[i][0]) + break; + + if (i >= ARRAY_SIZE(ad7746_vt_filter_rate_table)) + i = ARRAY_SIZE(ad7746_vt_filter_rate_table) - 1; + + mutex_lock(&indio_dev->mlock); + chip->config &= ~AD7746_CONF_VTFS(0x3); + chip->config |= AD7746_CONF_VTFS(i); + mutex_unlock(&indio_dev->mlock); + + return len; +} + +static IIO_DEVICE_ATTR(in_capacitance_sampling_frequency, + S_IRUGO | S_IWUSR, ad7746_show_cap_filter_rate_setup, + ad7746_store_cap_filter_rate_setup, 0); + +static IIO_DEVICE_ATTR(in_voltage_sampling_frequency, + S_IRUGO | S_IWUSR, ad7746_show_vt_filter_rate_setup, + ad7746_store_vt_filter_rate_setup, 0); + +static IIO_CONST_ATTR(in_voltage_sampling_frequency_available, "50 31 16 8"); +static IIO_CONST_ATTR(in_capacitance_sampling_frequency_available, + "91 84 50 26 16 13 11 9"); + +static struct attribute *ad7746_attributes[] = { + &iio_dev_attr_in_capacitance_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_voltage_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_capacitance0_calibbias_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance0_calibscale_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance1_calibscale_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance1_calibbias_calibration.dev_attr.attr, + &iio_dev_attr_in_voltage0_calibscale_calibration.dev_attr.attr, + &iio_const_attr_in_voltage_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_capacitance_sampling_frequency_available. + dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7746_attribute_group = { + .attrs = ad7746_attributes, +}; + +static int ad7746_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7746_chip_info *chip = iio_priv(indio_dev); + int ret, reg; + + mutex_lock(&indio_dev->mlock); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (val != 1) { + ret = -EINVAL; + goto out; + } + + val = (val2 * 1024) / 15625; + + switch (chan->type) { + case IIO_CAPACITANCE: + reg = AD7746_REG_CAP_GAINH; + break; + case IIO_VOLTAGE: + reg = AD7746_REG_VOLT_GAINH; + break; + default: + ret = -EINVAL; + goto out; + } + + ret = i2c_smbus_write_word_data(chip->client, reg, swab16(val)); + if (ret < 0) + goto out; + + ret = 0; + break; + case IIO_CHAN_INFO_CALIBBIAS: + if ((val < 0) | (val > 0xFFFF)) { + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_word_data(chip->client, + AD7746_REG_CAP_OFFH, swab16(val)); + if (ret < 0) + goto out; + + ret = 0; + break; + case IIO_CHAN_INFO_OFFSET: + if ((val < 0) | (val > 43008000)) { /* 21pF */ + ret = -EINVAL; + goto out; + } + + /* CAPDAC Scale = 21pF_typ / 127 + * CIN Scale = 8.192pF / 2^24 + * Offset Scale = CAPDAC Scale / CIN Scale = 338646 + * */ + + val /= 338646; + + chip->capdac[chan->channel][chan->differential] = (val > 0 ? + AD7746_CAPDAC_DACP(val) | AD7746_CAPDAC_DACEN : 0); + + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAPDACA, + chip->capdac[chan->channel][0]); + if (ret < 0) + goto out; + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAPDACB, + chip->capdac[chan->channel][1]); + if (ret < 0) + goto out; + + chip->capdac_set = chan->channel; + + ret = 0; + break; + default: + ret = -EINVAL; + } + +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static int ad7746_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct ad7746_chip_info *chip = iio_priv(indio_dev); + int ret, delay; + u8 regval, reg; + + mutex_lock(&indio_dev->mlock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + ret = ad7746_select_channel(indio_dev, chan); + if (ret < 0) + goto out; + delay = ret; + + regval = chip->config | AD7746_CONF_MODE_SINGLE_CONV; + ret = i2c_smbus_write_byte_data(chip->client, AD7746_REG_CFG, + regval); + if (ret < 0) + goto out; + + msleep(delay); + /* Now read the actual register */ + + ret = i2c_smbus_read_i2c_block_data(chip->client, + chan->address >> 8, 3, &chip->data.d8[1]); + + if (ret < 0) + goto out; + + *val = (be32_to_cpu(chip->data.d32) & 0xFFFFFF) - 0x800000; + + switch (chan->type) { + case IIO_TEMP: + /* temperature in milli degrees Celsius + * T = ((*val / 2048) - 4096) * 1000 + */ + *val = (*val * 125) / 256; + break; + case IIO_VOLTAGE: + if (chan->channel == 1) /* supply_raw*/ + *val = *val * 6; + break; + default: + break; + } + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + switch (chan->type) { + case IIO_CAPACITANCE: + reg = AD7746_REG_CAP_GAINH; + break; + case IIO_VOLTAGE: + reg = AD7746_REG_VOLT_GAINH; + break; + default: + ret = -EINVAL; + goto out; + } + + ret = i2c_smbus_read_word_data(chip->client, reg); + if (ret < 0) + goto out; + /* 1 + gain_val / 2^16 */ + *val = 1; + *val2 = (15625 * swab16(ret)) / 1024; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_CALIBBIAS: + ret = i2c_smbus_read_word_data(chip->client, + AD7746_REG_CAP_OFFH); + if (ret < 0) + goto out; + *val = swab16(ret); + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_OFFSET: + *val = AD7746_CAPDAC_DACP(chip->capdac[chan->channel] + [chan->differential]) * 338646; + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_CAPACITANCE: + /* 8.192pf / 2^24 */ + *val = 0; + *val2 = 488; + ret = IIO_VAL_INT_PLUS_NANO; + break; + case IIO_VOLTAGE: + /* 1170mV / 2^23 */ + *val = 1170; + *val2 = 23; + ret = IIO_VAL_FRACTIONAL_LOG2; + break; + default: + ret = -EINVAL; + break; + } + + break; + default: + ret = -EINVAL; + } +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static const struct iio_info ad7746_info = { + .attrs = &ad7746_attribute_group, + .read_raw = &ad7746_read_raw, + .write_raw = &ad7746_write_raw, + .driver_module = THIS_MODULE, +}; + +/* + * device probe and remove + */ + +static int ad7746_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ad7746_platform_data *pdata = client->dev.platform_data; + struct ad7746_chip_info *chip; + struct iio_dev *indio_dev; + int ret = 0; + unsigned char regval = 0; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + chip->client = client; + chip->capdac_set = -1; + + /* Establish that the iio_dev is a child of the i2c device */ + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ad7746_info; + indio_dev->channels = ad7746_channels; + if (id->driver_data == 7746) + indio_dev->num_channels = ARRAY_SIZE(ad7746_channels); + else + indio_dev->num_channels = ARRAY_SIZE(ad7746_channels) - 2; + indio_dev->num_channels = ARRAY_SIZE(ad7746_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + if (pdata) { + if (pdata->exca_en) { + if (pdata->exca_inv_en) + regval |= AD7746_EXCSETUP_NEXCA; + else + regval |= AD7746_EXCSETUP_EXCA; + } + + if (pdata->excb_en) { + if (pdata->excb_inv_en) + regval |= AD7746_EXCSETUP_NEXCB; + else + regval |= AD7746_EXCSETUP_EXCB; + } + + regval |= AD7746_EXCSETUP_EXCLVL(pdata->exclvl); + } else { + dev_warn(&client->dev, "No platform data? using default\n"); + regval = AD7746_EXCSETUP_EXCA | AD7746_EXCSETUP_EXCB | + AD7746_EXCSETUP_EXCLVL(3); + } + + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_EXC_SETUP, regval); + if (ret < 0) + return ret; + + ret = iio_device_register(indio_dev); + if (ret) + return ret; + + dev_info(&client->dev, "%s capacitive sensor registered\n", id->name); + + return 0; +} + +static int ad7746_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + + return 0; +} + +static const struct i2c_device_id ad7746_id[] = { + { "ad7745", 7745 }, + { "ad7746", 7746 }, + { "ad7747", 7747 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad7746_id); + +static struct i2c_driver ad7746_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = ad7746_probe, + .remove = ad7746_remove, + .id_table = ad7746_id, +}; +module_i2c_driver(ad7746_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7746/5/7 capacitive sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/cdc/ad7746.h b/kernel/drivers/staging/iio/cdc/ad7746.h new file mode 100644 index 000000000..ea8572d1d --- /dev/null +++ b/kernel/drivers/staging/iio/cdc/ad7746.h @@ -0,0 +1,29 @@ +/* + * AD7746 capacitive sensor driver supporting AD7745, AD7746 and AD7747 + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_CDC_AD7746_H_ +#define IIO_CDC_AD7746_H_ + +/* + * TODO: struct ad7746_platform_data needs to go into include/linux/iio + */ + +#define AD7466_EXCLVL_0 0 /* +-VDD/8 */ +#define AD7466_EXCLVL_1 1 /* +-VDD/4 */ +#define AD7466_EXCLVL_2 2 /* +-VDD * 3/8 */ +#define AD7466_EXCLVL_3 3 /* +-VDD/2 */ + +struct ad7746_platform_data { + unsigned char exclvl; /*Excitation Voltage Level */ + bool exca_en; /* enables EXCA pin as the excitation output */ + bool exca_inv_en; /* enables /EXCA pin as the excitation output */ + bool excb_en; /* enables EXCB pin as the excitation output */ + bool excb_inv_en; /* enables /EXCB pin as the excitation output */ +}; + +#endif /* IIO_CDC_AD7746_H_ */ diff --git a/kernel/drivers/staging/iio/frequency/Kconfig b/kernel/drivers/staging/iio/frequency/Kconfig new file mode 100644 index 000000000..fc726d3c6 --- /dev/null +++ b/kernel/drivers/staging/iio/frequency/Kconfig @@ -0,0 +1,26 @@ +# +# Direct Digital Synthesis drivers +# +menu "Direct Digital Synthesis" + +config AD9832 + tristate "Analog Devices ad9832/5 driver" + depends on SPI + help + Say yes here to build support for Analog Devices DDS chip + AD9832 and AD9835, provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad9832. + +config AD9834 + tristate "Analog Devices AD9833/4/7/8 driver" + depends on SPI + help + Say yes here to build support for Analog Devices DDS chip + AD9833, AD9834, AD9837 and AD9838, provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad9834. + +endmenu diff --git a/kernel/drivers/staging/iio/frequency/Makefile b/kernel/drivers/staging/iio/frequency/Makefile new file mode 100644 index 000000000..e5dbcfce4 --- /dev/null +++ b/kernel/drivers/staging/iio/frequency/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for Direct Digital Synthesis drivers +# + +obj-$(CONFIG_AD9832) += ad9832.o +obj-$(CONFIG_AD9834) += ad9834.o diff --git a/kernel/drivers/staging/iio/frequency/ad9832.c b/kernel/drivers/staging/iio/frequency/ad9832.c new file mode 100644 index 000000000..a861fe014 --- /dev/null +++ b/kernel/drivers/staging/iio/frequency/ad9832.c @@ -0,0 +1,352 @@ +/* + * AD9832 SPI DDS driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "dds.h" + +#include "ad9832.h" + +static unsigned long ad9832_calc_freqreg(unsigned long mclk, unsigned long fout) +{ + unsigned long long freqreg = (u64)fout * + (u64)((u64)1L << AD9832_FREQ_BITS); + do_div(freqreg, mclk); + return freqreg; +} + +static int ad9832_write_frequency(struct ad9832_state *st, + unsigned addr, unsigned long fout) +{ + unsigned long regval; + + if (fout > (st->mclk / 2)) + return -EINVAL; + + regval = ad9832_calc_freqreg(st->mclk, fout); + + st->freq_data[0] = cpu_to_be16((AD9832_CMD_FRE8BITSW << CMD_SHIFT) | + (addr << ADD_SHIFT) | + ((regval >> 24) & 0xFF)); + st->freq_data[1] = cpu_to_be16((AD9832_CMD_FRE16BITSW << CMD_SHIFT) | + ((addr - 1) << ADD_SHIFT) | + ((regval >> 16) & 0xFF)); + st->freq_data[2] = cpu_to_be16((AD9832_CMD_FRE8BITSW << CMD_SHIFT) | + ((addr - 2) << ADD_SHIFT) | + ((regval >> 8) & 0xFF)); + st->freq_data[3] = cpu_to_be16((AD9832_CMD_FRE16BITSW << CMD_SHIFT) | + ((addr - 3) << ADD_SHIFT) | + ((regval >> 0) & 0xFF)); + + return spi_sync(st->spi, &st->freq_msg); +} + +static int ad9832_write_phase(struct ad9832_state *st, + unsigned long addr, unsigned long phase) +{ + if (phase > BIT(AD9832_PHASE_BITS)) + return -EINVAL; + + st->phase_data[0] = cpu_to_be16((AD9832_CMD_PHA8BITSW << CMD_SHIFT) | + (addr << ADD_SHIFT) | + ((phase >> 8) & 0xFF)); + st->phase_data[1] = cpu_to_be16((AD9832_CMD_PHA16BITSW << CMD_SHIFT) | + ((addr - 1) << ADD_SHIFT) | + (phase & 0xFF)); + + return spi_sync(st->spi, &st->phase_msg); +} + +static ssize_t ad9832_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad9832_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + unsigned long val; + + ret = kstrtoul(buf, 10, &val); + if (ret) + goto error_ret; + + mutex_lock(&indio_dev->mlock); + switch ((u32)this_attr->address) { + case AD9832_FREQ0HM: + case AD9832_FREQ1HM: + ret = ad9832_write_frequency(st, this_attr->address, val); + break; + case AD9832_PHASE0H: + case AD9832_PHASE1H: + case AD9832_PHASE2H: + case AD9832_PHASE3H: + ret = ad9832_write_phase(st, this_attr->address, val); + break; + case AD9832_PINCTRL_EN: + if (val) + st->ctrl_ss &= ~AD9832_SELSRC; + else + st->ctrl_ss |= AD9832_SELSRC; + st->data = cpu_to_be16((AD9832_CMD_SYNCSELSRC << CMD_SHIFT) | + st->ctrl_ss); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9832_FREQ_SYM: + if (val == 1) { + st->ctrl_fp |= AD9832_FREQ; + } else if (val == 0) { + st->ctrl_fp &= ~AD9832_FREQ; + } else { + ret = -EINVAL; + break; + } + st->data = cpu_to_be16((AD9832_CMD_FPSELECT << CMD_SHIFT) | + st->ctrl_fp); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9832_PHASE_SYM: + if (val > 3) { + ret = -EINVAL; + break; + } + + st->ctrl_fp &= ~AD9832_PHASE(3); + st->ctrl_fp |= AD9832_PHASE(val); + + st->data = cpu_to_be16((AD9832_CMD_FPSELECT << CMD_SHIFT) | + st->ctrl_fp); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9832_OUTPUT_EN: + if (val) + st->ctrl_src &= ~(AD9832_RESET | AD9832_SLEEP | + AD9832_CLR); + else + st->ctrl_src |= AD9832_RESET; + + st->data = cpu_to_be16((AD9832_CMD_SLEEPRESCLR << CMD_SHIFT) | + st->ctrl_src); + ret = spi_sync(st->spi, &st->msg); + break; + default: + ret = -ENODEV; + } + mutex_unlock(&indio_dev->mlock); + +error_ret: + return ret ? ret : len; +} + +/** + * see dds.h for further information + */ + +static IIO_DEV_ATTR_FREQ(0, 0, S_IWUSR, NULL, ad9832_write, AD9832_FREQ0HM); +static IIO_DEV_ATTR_FREQ(0, 1, S_IWUSR, NULL, ad9832_write, AD9832_FREQ1HM); +static IIO_DEV_ATTR_FREQSYMBOL(0, S_IWUSR, NULL, ad9832_write, AD9832_FREQ_SYM); +static IIO_CONST_ATTR_FREQ_SCALE(0, "1"); /* 1Hz */ + +static IIO_DEV_ATTR_PHASE(0, 0, S_IWUSR, NULL, ad9832_write, AD9832_PHASE0H); +static IIO_DEV_ATTR_PHASE(0, 1, S_IWUSR, NULL, ad9832_write, AD9832_PHASE1H); +static IIO_DEV_ATTR_PHASE(0, 2, S_IWUSR, NULL, ad9832_write, AD9832_PHASE2H); +static IIO_DEV_ATTR_PHASE(0, 3, S_IWUSR, NULL, ad9832_write, AD9832_PHASE3H); +static IIO_DEV_ATTR_PHASESYMBOL(0, S_IWUSR, NULL, + ad9832_write, AD9832_PHASE_SYM); +static IIO_CONST_ATTR_PHASE_SCALE(0, "0.0015339808"); /* 2PI/2^12 rad*/ + +static IIO_DEV_ATTR_PINCONTROL_EN(0, S_IWUSR, NULL, + ad9832_write, AD9832_PINCTRL_EN); +static IIO_DEV_ATTR_OUT_ENABLE(0, S_IWUSR, NULL, + ad9832_write, AD9832_OUTPUT_EN); + +static struct attribute *ad9832_attributes[] = { + &iio_dev_attr_out_altvoltage0_frequency0.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_frequency1.dev_attr.attr, + &iio_const_attr_out_altvoltage0_frequency_scale.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phase0.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phase1.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phase2.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phase3.dev_attr.attr, + &iio_const_attr_out_altvoltage0_phase_scale.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_pincontrol_en.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_frequencysymbol.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phasesymbol.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_out_enable.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad9832_attribute_group = { + .attrs = ad9832_attributes, +}; + +static const struct iio_info ad9832_info = { + .attrs = &ad9832_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ad9832_probe(struct spi_device *spi) +{ + struct ad9832_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev; + struct ad9832_state *st; + struct regulator *reg; + int ret; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(reg)) { + ret = regulator_enable(reg); + if (ret) + return ret; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) { + ret = -ENOMEM; + goto error_disable_reg; + } + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + st->reg = reg; + st->mclk = pdata->mclk; + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad9832_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Setup default messages */ + + st->xfer.tx_buf = &st->data; + st->xfer.len = 2; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + st->freq_xfer[0].tx_buf = &st->freq_data[0]; + st->freq_xfer[0].len = 2; + st->freq_xfer[0].cs_change = 1; + st->freq_xfer[1].tx_buf = &st->freq_data[1]; + st->freq_xfer[1].len = 2; + st->freq_xfer[1].cs_change = 1; + st->freq_xfer[2].tx_buf = &st->freq_data[2]; + st->freq_xfer[2].len = 2; + st->freq_xfer[2].cs_change = 1; + st->freq_xfer[3].tx_buf = &st->freq_data[3]; + st->freq_xfer[3].len = 2; + + spi_message_init(&st->freq_msg); + spi_message_add_tail(&st->freq_xfer[0], &st->freq_msg); + spi_message_add_tail(&st->freq_xfer[1], &st->freq_msg); + spi_message_add_tail(&st->freq_xfer[2], &st->freq_msg); + spi_message_add_tail(&st->freq_xfer[3], &st->freq_msg); + + st->phase_xfer[0].tx_buf = &st->phase_data[0]; + st->phase_xfer[0].len = 2; + st->phase_xfer[0].cs_change = 1; + st->phase_xfer[1].tx_buf = &st->phase_data[1]; + st->phase_xfer[1].len = 2; + + spi_message_init(&st->phase_msg); + spi_message_add_tail(&st->phase_xfer[0], &st->phase_msg); + spi_message_add_tail(&st->phase_xfer[1], &st->phase_msg); + + st->ctrl_src = AD9832_SLEEP | AD9832_RESET | AD9832_CLR; + st->data = cpu_to_be16((AD9832_CMD_SLEEPRESCLR << CMD_SHIFT) | + st->ctrl_src); + ret = spi_sync(st->spi, &st->msg); + if (ret) { + dev_err(&spi->dev, "device init failed\n"); + goto error_disable_reg; + } + + ret = ad9832_write_frequency(st, AD9832_FREQ0HM, pdata->freq0); + if (ret) + goto error_disable_reg; + + ret = ad9832_write_frequency(st, AD9832_FREQ1HM, pdata->freq1); + if (ret) + goto error_disable_reg; + + ret = ad9832_write_phase(st, AD9832_PHASE0H, pdata->phase0); + if (ret) + goto error_disable_reg; + + ret = ad9832_write_phase(st, AD9832_PHASE1H, pdata->phase1); + if (ret) + goto error_disable_reg; + + ret = ad9832_write_phase(st, AD9832_PHASE2H, pdata->phase2); + if (ret) + goto error_disable_reg; + + ret = ad9832_write_phase(st, AD9832_PHASE3H, pdata->phase3); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!IS_ERR(reg)) + regulator_disable(reg); + + return ret; +} + +static int ad9832_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad9832_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad9832_id[] = { + {"ad9832", 0}, + {"ad9835", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad9832_id); + +static struct spi_driver ad9832_driver = { + .driver = { + .name = "ad9832", + .owner = THIS_MODULE, + }, + .probe = ad9832_probe, + .remove = ad9832_remove, + .id_table = ad9832_id, +}; +module_spi_driver(ad9832_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD9832/AD9835 DDS"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/frequency/ad9832.h b/kernel/drivers/staging/iio/frequency/ad9832.h new file mode 100644 index 000000000..d32323b46 --- /dev/null +++ b/kernel/drivers/staging/iio/frequency/ad9832.h @@ -0,0 +1,126 @@ +/* + * AD9832 SPI DDS driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ +#ifndef IIO_DDS_AD9832_H_ +#define IIO_DDS_AD9832_H_ + +/* Registers */ + +#define AD9832_FREQ0LL 0x0 +#define AD9832_FREQ0HL 0x1 +#define AD9832_FREQ0LM 0x2 +#define AD9832_FREQ0HM 0x3 +#define AD9832_FREQ1LL 0x4 +#define AD9832_FREQ1HL 0x5 +#define AD9832_FREQ1LM 0x6 +#define AD9832_FREQ1HM 0x7 +#define AD9832_PHASE0L 0x8 +#define AD9832_PHASE0H 0x9 +#define AD9832_PHASE1L 0xA +#define AD9832_PHASE1H 0xB +#define AD9832_PHASE2L 0xC +#define AD9832_PHASE2H 0xD +#define AD9832_PHASE3L 0xE +#define AD9832_PHASE3H 0xF + +#define AD9832_PHASE_SYM 0x10 +#define AD9832_FREQ_SYM 0x11 +#define AD9832_PINCTRL_EN 0x12 +#define AD9832_OUTPUT_EN 0x13 + +/* Command Control Bits */ + +#define AD9832_CMD_PHA8BITSW 0x1 +#define AD9832_CMD_PHA16BITSW 0x0 +#define AD9832_CMD_FRE8BITSW 0x3 +#define AD9832_CMD_FRE16BITSW 0x2 +#define AD9832_CMD_FPSELECT 0x6 +#define AD9832_CMD_SYNCSELSRC 0x8 +#define AD9832_CMD_SLEEPRESCLR 0xC + +#define AD9832_FREQ BIT(11) +#define AD9832_PHASE(x) (((x) & 3) << 9) +#define AD9832_SYNC BIT(13) +#define AD9832_SELSRC BIT(12) +#define AD9832_SLEEP BIT(13) +#define AD9832_RESET BIT(12) +#define AD9832_CLR BIT(11) +#define CMD_SHIFT 12 +#define ADD_SHIFT 8 +#define AD9832_FREQ_BITS 32 +#define AD9832_PHASE_BITS 12 +#define RES_MASK(bits) ((1 << (bits)) - 1) + +/** + * struct ad9832_state - driver instance specific data + * @spi: spi_device + * @reg: supply regulator + * @mclk: external master clock + * @ctrl_fp: cached frequency/phase control word + * @ctrl_ss: cached sync/selsrc control word + * @ctrl_src: cached sleep/reset/clr word + * @xfer: default spi transfer + * @msg: default spi message + * @freq_xfer: tuning word spi transfer + * @freq_msg: tuning word spi message + * @phase_xfer: tuning word spi transfer + * @phase_msg: tuning word spi message + * @data: spi transmit buffer + * @phase_data: tuning word spi transmit buffer + * @freq_data: tuning word spi transmit buffer + */ + +struct ad9832_state { + struct spi_device *spi; + struct regulator *reg; + unsigned long mclk; + unsigned short ctrl_fp; + unsigned short ctrl_ss; + unsigned short ctrl_src; + struct spi_transfer xfer; + struct spi_message msg; + struct spi_transfer freq_xfer[4]; + struct spi_message freq_msg; + struct spi_transfer phase_xfer[2]; + struct spi_message phase_msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + __be16 freq_data[4]____cacheline_aligned; + __be16 phase_data[2]; + __be16 data; + }; +}; + +/* + * TODO: struct ad9832_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad9832_platform_data - platform specific information + * @mclk: master clock in Hz + * @freq0: power up freq0 tuning word in Hz + * @freq1: power up freq1 tuning word in Hz + * @phase0: power up phase0 value [0..4095] correlates with 0..2PI + * @phase1: power up phase1 value [0..4095] correlates with 0..2PI + * @phase2: power up phase2 value [0..4095] correlates with 0..2PI + * @phase3: power up phase3 value [0..4095] correlates with 0..2PI + */ + +struct ad9832_platform_data { + unsigned long mclk; + unsigned long freq0; + unsigned long freq1; + unsigned short phase0; + unsigned short phase1; + unsigned short phase2; + unsigned short phase3; +}; + +#endif /* IIO_DDS_AD9832_H_ */ diff --git a/kernel/drivers/staging/iio/frequency/ad9834.c b/kernel/drivers/staging/iio/frequency/ad9834.c new file mode 100644 index 000000000..d02bb44fb --- /dev/null +++ b/kernel/drivers/staging/iio/frequency/ad9834.c @@ -0,0 +1,459 @@ +/* + * AD9833/AD9834/AD9837/AD9838 SPI DDS driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "dds.h" + +#include "ad9834.h" + +static unsigned int ad9834_calc_freqreg(unsigned long mclk, unsigned long fout) +{ + unsigned long long freqreg = (u64)fout * (u64)BIT(AD9834_FREQ_BITS); + + do_div(freqreg, mclk); + return freqreg; +} + +static int ad9834_write_frequency(struct ad9834_state *st, + unsigned long addr, unsigned long fout) +{ + unsigned long regval; + + if (fout > (st->mclk / 2)) + return -EINVAL; + + regval = ad9834_calc_freqreg(st->mclk, fout); + + st->freq_data[0] = cpu_to_be16(addr | (regval & + RES_MASK(AD9834_FREQ_BITS / 2))); + st->freq_data[1] = cpu_to_be16(addr | ((regval >> + (AD9834_FREQ_BITS / 2)) & + RES_MASK(AD9834_FREQ_BITS / 2))); + + return spi_sync(st->spi, &st->freq_msg); +} + +static int ad9834_write_phase(struct ad9834_state *st, + unsigned long addr, unsigned long phase) +{ + if (phase > BIT(AD9834_PHASE_BITS)) + return -EINVAL; + st->data = cpu_to_be16(addr | phase); + + return spi_sync(st->spi, &st->msg); +} + +static ssize_t ad9834_write(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad9834_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + unsigned long val; + + ret = kstrtoul(buf, 10, &val); + if (ret) + goto error_ret; + + mutex_lock(&indio_dev->mlock); + switch ((u32)this_attr->address) { + case AD9834_REG_FREQ0: + case AD9834_REG_FREQ1: + ret = ad9834_write_frequency(st, this_attr->address, val); + break; + case AD9834_REG_PHASE0: + case AD9834_REG_PHASE1: + ret = ad9834_write_phase(st, this_attr->address, val); + break; + case AD9834_OPBITEN: + if (st->control & AD9834_MODE) { + ret = -EINVAL; /* AD9843 reserved mode */ + break; + } + + if (val) + st->control |= AD9834_OPBITEN; + else + st->control &= ~AD9834_OPBITEN; + + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9834_PIN_SW: + if (val) + st->control |= AD9834_PIN_SW; + else + st->control &= ~AD9834_PIN_SW; + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9834_FSEL: + case AD9834_PSEL: + if (val == 0) { + st->control &= ~(this_attr->address | AD9834_PIN_SW); + } else if (val == 1) { + st->control |= this_attr->address; + st->control &= ~AD9834_PIN_SW; + } else { + ret = -EINVAL; + break; + } + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9834_RESET: + if (val) + st->control &= ~AD9834_RESET; + else + st->control |= AD9834_RESET; + + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + break; + default: + ret = -ENODEV; + } + mutex_unlock(&indio_dev->mlock); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ad9834_store_wavetype(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad9834_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret = 0; + bool is_ad9833_7 = (st->devid == ID_AD9833) || (st->devid == ID_AD9837); + + mutex_lock(&indio_dev->mlock); + + switch ((u32)this_attr->address) { + case 0: + if (sysfs_streq(buf, "sine")) { + st->control &= ~AD9834_MODE; + if (is_ad9833_7) + st->control &= ~AD9834_OPBITEN; + } else if (sysfs_streq(buf, "triangle")) { + if (is_ad9833_7) { + st->control &= ~AD9834_OPBITEN; + st->control |= AD9834_MODE; + } else if (st->control & AD9834_OPBITEN) { + ret = -EINVAL; /* AD9843 reserved mode */ + } else { + st->control |= AD9834_MODE; + } + } else if (is_ad9833_7 && sysfs_streq(buf, "square")) { + st->control &= ~AD9834_MODE; + st->control |= AD9834_OPBITEN; + } else { + ret = -EINVAL; + } + + break; + case 1: + if (sysfs_streq(buf, "square") && + !(st->control & AD9834_MODE)) { + st->control &= ~AD9834_MODE; + st->control |= AD9834_OPBITEN; + } else { + ret = -EINVAL; + } + break; + default: + ret = -EINVAL; + break; + } + + if (!ret) { + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + } + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static +ssize_t ad9834_show_out0_wavetype_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad9834_state *st = iio_priv(indio_dev); + char *str; + + if ((st->devid == ID_AD9833) || (st->devid == ID_AD9837)) + str = "sine triangle square"; + else if (st->control & AD9834_OPBITEN) + str = "sine"; + else + str = "sine triangle"; + + return sprintf(buf, "%s\n", str); +} + +static IIO_DEVICE_ATTR(out_altvoltage0_out0_wavetype_available, S_IRUGO, + ad9834_show_out0_wavetype_available, NULL, 0); + +static +ssize_t ad9834_show_out1_wavetype_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad9834_state *st = iio_priv(indio_dev); + char *str; + + if (st->control & AD9834_MODE) + str = ""; + else + str = "square"; + + return sprintf(buf, "%s\n", str); +} + +static IIO_DEVICE_ATTR(out_altvoltage0_out1_wavetype_available, S_IRUGO, + ad9834_show_out1_wavetype_available, NULL, 0); + +/** + * see dds.h for further information + */ + +static IIO_DEV_ATTR_FREQ(0, 0, S_IWUSR, NULL, ad9834_write, AD9834_REG_FREQ0); +static IIO_DEV_ATTR_FREQ(0, 1, S_IWUSR, NULL, ad9834_write, AD9834_REG_FREQ1); +static IIO_DEV_ATTR_FREQSYMBOL(0, S_IWUSR, NULL, ad9834_write, AD9834_FSEL); +static IIO_CONST_ATTR_FREQ_SCALE(0, "1"); /* 1Hz */ + +static IIO_DEV_ATTR_PHASE(0, 0, S_IWUSR, NULL, ad9834_write, AD9834_REG_PHASE0); +static IIO_DEV_ATTR_PHASE(0, 1, S_IWUSR, NULL, ad9834_write, AD9834_REG_PHASE1); +static IIO_DEV_ATTR_PHASESYMBOL(0, S_IWUSR, NULL, ad9834_write, AD9834_PSEL); +static IIO_CONST_ATTR_PHASE_SCALE(0, "0.0015339808"); /* 2PI/2^12 rad*/ + +static IIO_DEV_ATTR_PINCONTROL_EN(0, S_IWUSR, NULL, + ad9834_write, AD9834_PIN_SW); +static IIO_DEV_ATTR_OUT_ENABLE(0, S_IWUSR, NULL, ad9834_write, AD9834_RESET); +static IIO_DEV_ATTR_OUTY_ENABLE(0, 1, S_IWUSR, NULL, + ad9834_write, AD9834_OPBITEN); +static IIO_DEV_ATTR_OUT_WAVETYPE(0, 0, ad9834_store_wavetype, 0); +static IIO_DEV_ATTR_OUT_WAVETYPE(0, 1, ad9834_store_wavetype, 1); + +static struct attribute *ad9834_attributes[] = { + &iio_dev_attr_out_altvoltage0_frequency0.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_frequency1.dev_attr.attr, + &iio_const_attr_out_altvoltage0_frequency_scale.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phase0.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phase1.dev_attr.attr, + &iio_const_attr_out_altvoltage0_phase_scale.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_pincontrol_en.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_frequencysymbol.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phasesymbol.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_out_enable.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_out1_enable.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_out0_wavetype.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_out1_wavetype.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_out0_wavetype_available.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_out1_wavetype_available.dev_attr.attr, + NULL, +}; + +static struct attribute *ad9833_attributes[] = { + &iio_dev_attr_out_altvoltage0_frequency0.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_frequency1.dev_attr.attr, + &iio_const_attr_out_altvoltage0_frequency_scale.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phase0.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phase1.dev_attr.attr, + &iio_const_attr_out_altvoltage0_phase_scale.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_frequencysymbol.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_phasesymbol.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_out_enable.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_out0_wavetype.dev_attr.attr, + &iio_dev_attr_out_altvoltage0_out0_wavetype_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad9834_attribute_group = { + .attrs = ad9834_attributes, +}; + +static const struct attribute_group ad9833_attribute_group = { + .attrs = ad9833_attributes, +}; + +static const struct iio_info ad9834_info = { + .attrs = &ad9834_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad9833_info = { + .attrs = &ad9833_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ad9834_probe(struct spi_device *spi) +{ + struct ad9834_platform_data *pdata = spi->dev.platform_data; + struct ad9834_state *st; + struct iio_dev *indio_dev; + struct regulator *reg; + int ret; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + reg = devm_regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(reg)) { + ret = regulator_enable(reg); + if (ret) + return ret; + } + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) { + ret = -ENOMEM; + goto error_disable_reg; + } + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + st->mclk = pdata->mclk; + st->spi = spi; + st->devid = spi_get_device_id(spi)->driver_data; + st->reg = reg; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + switch (st->devid) { + case ID_AD9833: + case ID_AD9837: + indio_dev->info = &ad9833_info; + break; + default: + indio_dev->info = &ad9834_info; + break; + } + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Setup default messages */ + + st->xfer.tx_buf = &st->data; + st->xfer.len = 2; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + st->freq_xfer[0].tx_buf = &st->freq_data[0]; + st->freq_xfer[0].len = 2; + st->freq_xfer[0].cs_change = 1; + st->freq_xfer[1].tx_buf = &st->freq_data[1]; + st->freq_xfer[1].len = 2; + + spi_message_init(&st->freq_msg); + spi_message_add_tail(&st->freq_xfer[0], &st->freq_msg); + spi_message_add_tail(&st->freq_xfer[1], &st->freq_msg); + + st->control = AD9834_B28 | AD9834_RESET; + + if (!pdata->en_div2) + st->control |= AD9834_DIV2; + + if (!pdata->en_signbit_msb_out && (st->devid == ID_AD9834)) + st->control |= AD9834_SIGN_PIB; + + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + if (ret) { + dev_err(&spi->dev, "device init failed\n"); + goto error_disable_reg; + } + + ret = ad9834_write_frequency(st, AD9834_REG_FREQ0, pdata->freq0); + if (ret) + goto error_disable_reg; + + ret = ad9834_write_frequency(st, AD9834_REG_FREQ1, pdata->freq1); + if (ret) + goto error_disable_reg; + + ret = ad9834_write_phase(st, AD9834_REG_PHASE0, pdata->phase0); + if (ret) + goto error_disable_reg; + + ret = ad9834_write_phase(st, AD9834_REG_PHASE1, pdata->phase1); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!IS_ERR(reg)) + regulator_disable(reg); + + return ret; +} + +static int ad9834_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad9834_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ad9834_id[] = { + {"ad9833", ID_AD9833}, + {"ad9834", ID_AD9834}, + {"ad9837", ID_AD9837}, + {"ad9838", ID_AD9838}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad9834_id); + +static struct spi_driver ad9834_driver = { + .driver = { + .name = "ad9834", + .owner = THIS_MODULE, + }, + .probe = ad9834_probe, + .remove = ad9834_remove, + .id_table = ad9834_id, +}; +module_spi_driver(ad9834_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD9833/AD9834/AD9837/AD9838 DDS"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/frequency/ad9834.h b/kernel/drivers/staging/iio/frequency/ad9834.h new file mode 100644 index 000000000..40fdd5da7 --- /dev/null +++ b/kernel/drivers/staging/iio/frequency/ad9834.h @@ -0,0 +1,111 @@ +/* + * AD9833/AD9834/AD9837/AD9838 SPI DDS driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef IIO_DDS_AD9834_H_ +#define IIO_DDS_AD9834_H_ + +/* Registers */ + +#define AD9834_REG_CMD 0 +#define AD9834_REG_FREQ0 BIT(14) +#define AD9834_REG_FREQ1 BIT(15) +#define AD9834_REG_PHASE0 (BIT(15) | BIT(14)) +#define AD9834_REG_PHASE1 (BIT(15) | BIT(14) | BIT(13)) + +/* Command Control Bits */ + +#define AD9834_B28 BIT(13) +#define AD9834_HLB BIT(12) +#define AD9834_FSEL BIT(11) +#define AD9834_PSEL BIT(10) +#define AD9834_PIN_SW BIT(9) +#define AD9834_RESET BIT(8) +#define AD9834_SLEEP1 BIT(7) +#define AD9834_SLEEP12 BIT(6) +#define AD9834_OPBITEN BIT(5) +#define AD9834_SIGN_PIB BIT(4) +#define AD9834_DIV2 BIT(3) +#define AD9834_MODE BIT(1) + +#define AD9834_FREQ_BITS 28 +#define AD9834_PHASE_BITS 12 + +#define RES_MASK(bits) (BIT(bits) - 1) + +/** + * struct ad9834_state - driver instance specific data + * @spi: spi_device + * @reg: supply regulator + * @mclk: external master clock + * @control: cached control word + * @xfer: default spi transfer + * @msg: default spi message + * @freq_xfer: tuning word spi transfer + * @freq_msg: tuning word spi message + * @data: spi transmit buffer + * @freq_data: tuning word spi transmit buffer + */ + +struct ad9834_state { + struct spi_device *spi; + struct regulator *reg; + unsigned int mclk; + unsigned short control; + unsigned short devid; + struct spi_transfer xfer; + struct spi_message msg; + struct spi_transfer freq_xfer[2]; + struct spi_message freq_msg; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __be16 data ____cacheline_aligned; + __be16 freq_data[2]; +}; + +/* + * TODO: struct ad7887_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad9834_platform_data - platform specific information + * @mclk: master clock in Hz + * @freq0: power up freq0 tuning word in Hz + * @freq1: power up freq1 tuning word in Hz + * @phase0: power up phase0 value [0..4095] correlates with 0..2PI + * @phase1: power up phase1 value [0..4095] correlates with 0..2PI + * @en_div2: digital output/2 is passed to the SIGN BIT OUT pin + * @en_signbit_msb_out: the MSB (or MSB/2) of the DAC data is connected to the + * SIGN BIT OUT pin. en_div2 controls whether it is the MSB + * or MSB/2 that is output. if en_signbit_msb_out=false, + * the on-board comparator is connected to SIGN BIT OUT + */ + +struct ad9834_platform_data { + unsigned int mclk; + unsigned int freq0; + unsigned int freq1; + unsigned short phase0; + unsigned short phase1; + bool en_div2; + bool en_signbit_msb_out; +}; + +/** + * ad9834_supported_device_ids: + */ + +enum ad9834_supported_device_ids { + ID_AD9833, + ID_AD9834, + ID_AD9837, + ID_AD9838, +}; + +#endif /* IIO_DDS_AD9834_H_ */ diff --git a/kernel/drivers/staging/iio/frequency/dds.h b/kernel/drivers/staging/iio/frequency/dds.h new file mode 100644 index 000000000..fe53e7324 --- /dev/null +++ b/kernel/drivers/staging/iio/frequency/dds.h @@ -0,0 +1,114 @@ +/* + * dds.h - sysfs attributes associated with DDS devices + * + * Copyright (c) 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ +#ifndef IIO_DDS_H_ +#define IIO_DDS_H_ + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_frequencyY + */ + +#define IIO_DEV_ATTR_FREQ(_channel, _num, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(out_altvoltage##_channel##_frequency##_num, \ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_frequencyY_scale + */ + +#define IIO_CONST_ATTR_FREQ_SCALE(_channel, _string) \ + IIO_CONST_ATTR(out_altvoltage##_channel##_frequency_scale, _string) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_frequencysymbol + */ + +#define IIO_DEV_ATTR_FREQSYMBOL(_channel, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(out_altvoltage##_channel##_frequencysymbol, \ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_phaseY + */ + +#define IIO_DEV_ATTR_PHASE(_channel, _num, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(out_altvoltage##_channel##_phase##_num, \ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_phaseY_scale + */ + +#define IIO_CONST_ATTR_PHASE_SCALE(_channel, _string) \ + IIO_CONST_ATTR(out_altvoltage##_channel##_phase_scale, _string) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_phasesymbol + */ + +#define IIO_DEV_ATTR_PHASESYMBOL(_channel, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(out_altvoltage##_channel##_phasesymbol, \ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_en + */ + +#define IIO_DEV_ATTR_PINCONTROL_EN(_channel, _mode, _show, _store, _addr)\ + IIO_DEVICE_ATTR(out_altvoltage##_channel##_pincontrol_en, \ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_frequency_en + */ + +#define IIO_DEV_ATTR_PINCONTROL_FREQ_EN(_channel, _mode, _show, _store, _addr)\ + IIO_DEVICE_ATTR(out_altvoltage##_channel##_pincontrol_frequency_en,\ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_pincontrol_phase_en + */ + +#define IIO_DEV_ATTR_PINCONTROL_PHASE_EN(_channel, _mode, _show, _store, _addr)\ + IIO_DEVICE_ATTR(out_altvoltage##_channel##_pincontrol_phase_en, \ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_out_enable + */ + +#define IIO_DEV_ATTR_OUT_ENABLE(_channel, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(out_altvoltage##_channel##_out_enable, \ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_outY_enable + */ + +#define IIO_DEV_ATTR_OUTY_ENABLE(_channel, _output, \ + _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(out_altvoltage##_channel##_out##_output##_enable,\ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_outY_wavetype + */ + +#define IIO_DEV_ATTR_OUT_WAVETYPE(_channel, _output, _store, _addr) \ + IIO_DEVICE_ATTR(out_altvoltage##_channel##_out##_output##_wavetype,\ + S_IWUSR, NULL, _store, _addr) + +/** + * /sys/bus/iio/devices/.../out_altvoltageX_outY_wavetype_available + */ + +#define IIO_CONST_ATTR_OUT_WAVETYPES_AVAILABLE(_channel, _output, _modes)\ + IIO_CONST_ATTR( \ + out_altvoltage##_channel##_out##_output##_wavetype_available, _modes) + +#endif /* IIO_DDS_H_ */ diff --git a/kernel/drivers/staging/iio/gyro/Kconfig b/kernel/drivers/staging/iio/gyro/Kconfig new file mode 100644 index 000000000..f62f68fd6 --- /dev/null +++ b/kernel/drivers/staging/iio/gyro/Kconfig @@ -0,0 +1,16 @@ +# +# IIO Digital Gyroscope Sensor drivers configuration +# +menu "Digital gyroscope sensors" + +config ADIS16060 + tristate "Analog Devices ADIS16060 Yaw Rate Gyroscope with SPI driver" + depends on SPI + help + Say Y (yes) here to build support for Analog Devices adis16060 wide bandwidth + yaw rate gyroscope with SPI. + + To compile this driver as a module, say M here: the module will be + called adis16060. If unsure, say N. + +endmenu diff --git a/kernel/drivers/staging/iio/gyro/Makefile b/kernel/drivers/staging/iio/gyro/Makefile new file mode 100644 index 000000000..cf22d6d55 --- /dev/null +++ b/kernel/drivers/staging/iio/gyro/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for digital gyroscope sensor drivers +# + +adis16060-y := adis16060_core.o +obj-$(CONFIG_ADIS16060) += adis16060.o diff --git a/kernel/drivers/staging/iio/gyro/adis16060_core.c b/kernel/drivers/staging/iio/gyro/adis16060_core.c new file mode 100644 index 000000000..4c5869dd8 --- /dev/null +++ b/kernel/drivers/staging/iio/gyro/adis16060_core.c @@ -0,0 +1,246 @@ +/* + * ADIS16060 Wide Bandwidth Yaw Rate Gyroscope with SPI driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ADIS16060_GYRO 0x20 /* Measure Angular Rate (Gyro) */ +#define ADIS16060_TEMP_OUT 0x10 /* Measure Temperature */ +#define ADIS16060_AIN2 0x80 /* Measure AIN2 */ +#define ADIS16060_AIN1 0x40 /* Measure AIN1 */ + +/** + * struct adis16060_state - device instance specific data + * @us_w: actual spi_device to write config + * @us_r: actual spi_device to read back data + * @buf: transmit or receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16060_state { + struct spi_device *us_w; + struct spi_device *us_r; + struct mutex buf_lock; + + u8 buf[3] ____cacheline_aligned; +}; + +static struct iio_dev *adis16060_iio_dev; + +static int adis16060_spi_write(struct iio_dev *indio_dev, u8 val) +{ + int ret; + struct adis16060_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->buf[2] = val; /* The last 8 bits clocked in are latched */ + ret = spi_write(st->us_w, st->buf, 3); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int adis16060_spi_read(struct iio_dev *indio_dev, u16 *val) +{ + int ret; + struct adis16060_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + + ret = spi_read(st->us_r, st->buf, 3); + + /* The internal successive approximation ADC begins the + * conversion process on the falling edge of MSEL1 and + * starts to place data MSB first on the DOUT line at + * the 6th falling edge of SCLK + */ + if (ret == 0) + *val = ((st->buf[0] & 0x3) << 12) | + (st->buf[1] << 4) | + ((st->buf[2] >> 4) & 0xF); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int adis16060_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + u16 tval = 0; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + ret = adis16060_spi_write(indio_dev, chan->address); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + ret = adis16060_spi_read(indio_dev, &tval); + mutex_unlock(&indio_dev->mlock); + *val = tval; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = -7; + *val2 = 461117; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 34000; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static const struct iio_info adis16060_info = { + .read_raw = &adis16060_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec adis16060_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = ADIS16060_GYRO, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = ADIS16060_AIN1, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .address = ADIS16060_AIN2, + }, { + .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 = ADIS16060_TEMP_OUT, + } +}; + +static int adis16060_r_probe(struct spi_device *spi) +{ + int ret; + struct adis16060_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + st->us_r = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16060_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adis16060_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16060_channels); + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + adis16060_iio_dev = indio_dev; + return 0; +} + +static int adis16060_w_probe(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev = adis16060_iio_dev; + struct adis16060_state *st; + + if (!indio_dev) { + ret = -ENODEV; + goto error_ret; + } + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->us_w = spi; + return 0; + +error_ret: + return ret; +} + +static int adis16060_w_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver adis16060_r_driver = { + .driver = { + .name = "adis16060_r", + .owner = THIS_MODULE, + }, + .probe = adis16060_r_probe, +}; + +static struct spi_driver adis16060_w_driver = { + .driver = { + .name = "adis16060_w", + .owner = THIS_MODULE, + }, + .probe = adis16060_w_probe, + .remove = adis16060_w_remove, +}; + +static __init int adis16060_init(void) +{ + int ret; + + ret = spi_register_driver(&adis16060_r_driver); + if (ret < 0) + return ret; + + ret = spi_register_driver(&adis16060_w_driver); + if (ret < 0) { + spi_unregister_driver(&adis16060_r_driver); + return ret; + } + + return 0; +} +module_init(adis16060_init); + +static __exit void adis16060_exit(void) +{ + spi_unregister_driver(&adis16060_w_driver); + spi_unregister_driver(&adis16060_r_driver); +} +module_exit(adis16060_exit); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16060 Yaw Rate Gyroscope Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/iio_dummy_evgen.c b/kernel/drivers/staging/iio/iio_dummy_evgen.c new file mode 100644 index 000000000..0c9c86d7b --- /dev/null +++ b/kernel/drivers/staging/iio/iio_dummy_evgen.c @@ -0,0 +1,239 @@ +/** + * 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. + * + * Companion module to the iio simple dummy example driver. + * The purpose of this is to generate 'fake' event interrupts thus + * allowing that driver's code to be as close as possible to that of + * a normal driver talking to hardware. The approach used here + * is not intended to be general and just happens to work for this + * particular use case. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "iio_dummy_evgen.h" +#include +#include + +/* Fiddly bit of faking and irq without hardware */ +#define IIO_EVENTGEN_NO 10 +/** + * struct iio_dummy_evgen - evgen state + * @chip: irq chip we are faking + * @base: base of irq range + * @enabled: mask of which irqs are enabled + * @inuse: mask of which irqs are connected + * @regs: irq regs we are faking + * @lock: protect the evgen state + */ +struct iio_dummy_eventgen { + struct irq_chip chip; + int base; + bool enabled[IIO_EVENTGEN_NO]; + bool inuse[IIO_EVENTGEN_NO]; + struct iio_dummy_regs regs[IIO_EVENTGEN_NO]; + struct mutex lock; +}; + +/* We can only ever have one instance of this 'device' */ +static struct iio_dummy_eventgen *iio_evgen; +static const char *iio_evgen_name = "iio_dummy_evgen"; + +static void iio_dummy_event_irqmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_dummy_eventgen *evgen = + container_of(chip, struct iio_dummy_eventgen, chip); + + evgen->enabled[d->irq - evgen->base] = false; +} + +static void iio_dummy_event_irqunmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_dummy_eventgen *evgen = + container_of(chip, struct iio_dummy_eventgen, chip); + + evgen->enabled[d->irq - evgen->base] = true; +} + +static int iio_dummy_evgen_create(void) +{ + int ret, i; + + iio_evgen = kzalloc(sizeof(*iio_evgen), GFP_KERNEL); + if (!iio_evgen) + return -ENOMEM; + + iio_evgen->base = irq_alloc_descs(-1, 0, IIO_EVENTGEN_NO, 0); + if (iio_evgen->base < 0) { + ret = iio_evgen->base; + kfree(iio_evgen); + return ret; + } + iio_evgen->chip.name = iio_evgen_name; + iio_evgen->chip.irq_mask = &iio_dummy_event_irqmask; + iio_evgen->chip.irq_unmask = &iio_dummy_event_irqunmask; + for (i = 0; i < IIO_EVENTGEN_NO; i++) { + irq_set_chip(iio_evgen->base + i, &iio_evgen->chip); + irq_set_handler(iio_evgen->base + i, &handle_simple_irq); + irq_modify_status(iio_evgen->base + i, + IRQ_NOREQUEST | IRQ_NOAUTOEN, + IRQ_NOPROBE); + } + mutex_init(&iio_evgen->lock); + return 0; +} + +/** + * iio_dummy_evgen_get_irq() - get an evgen provided irq for a device + * + * This function will give a free allocated irq to a client device. + * That irq can then be caused to 'fire' by using the associated sysfs file. + */ +int iio_dummy_evgen_get_irq(void) +{ + int i, ret = 0; + + if (!iio_evgen) + return -ENODEV; + + mutex_lock(&iio_evgen->lock); + for (i = 0; i < IIO_EVENTGEN_NO; i++) + if (!iio_evgen->inuse[i]) { + ret = iio_evgen->base + i; + iio_evgen->inuse[i] = true; + break; + } + mutex_unlock(&iio_evgen->lock); + if (i == IIO_EVENTGEN_NO) + return -ENOMEM; + return ret; +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_get_irq); + +/** + * iio_dummy_evgen_release_irq() - give the irq back. + * @irq: irq being returned to the pool + * + * Used by client driver instances to give the irqs back when they disconnect + */ +int iio_dummy_evgen_release_irq(int irq) +{ + mutex_lock(&iio_evgen->lock); + iio_evgen->inuse[irq - iio_evgen->base] = false; + mutex_unlock(&iio_evgen->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_release_irq); + +struct iio_dummy_regs *iio_dummy_evgen_get_regs(int irq) +{ + return &iio_evgen->regs[irq - iio_evgen->base]; +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_get_regs); + +static void iio_dummy_evgen_free(void) +{ + irq_free_descs(iio_evgen->base, IIO_EVENTGEN_NO); + kfree(iio_evgen); +} + +static void iio_evgen_release(struct device *dev) +{ + iio_dummy_evgen_free(); +} + +static ssize_t iio_evgen_poke(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned long event; + int ret; + + ret = kstrtoul(buf, 10, &event); + if (ret) + return ret; + + iio_evgen->regs[this_attr->address].reg_id = this_attr->address; + iio_evgen->regs[this_attr->address].reg_data = event; + + if (iio_evgen->enabled[this_attr->address]) + handle_nested_irq(iio_evgen->base + this_attr->address); + + return len; +} + +static IIO_DEVICE_ATTR(poke_ev0, S_IWUSR, NULL, &iio_evgen_poke, 0); +static IIO_DEVICE_ATTR(poke_ev1, S_IWUSR, NULL, &iio_evgen_poke, 1); +static IIO_DEVICE_ATTR(poke_ev2, S_IWUSR, NULL, &iio_evgen_poke, 2); +static IIO_DEVICE_ATTR(poke_ev3, S_IWUSR, NULL, &iio_evgen_poke, 3); +static IIO_DEVICE_ATTR(poke_ev4, S_IWUSR, NULL, &iio_evgen_poke, 4); +static IIO_DEVICE_ATTR(poke_ev5, S_IWUSR, NULL, &iio_evgen_poke, 5); +static IIO_DEVICE_ATTR(poke_ev6, S_IWUSR, NULL, &iio_evgen_poke, 6); +static IIO_DEVICE_ATTR(poke_ev7, S_IWUSR, NULL, &iio_evgen_poke, 7); +static IIO_DEVICE_ATTR(poke_ev8, S_IWUSR, NULL, &iio_evgen_poke, 8); +static IIO_DEVICE_ATTR(poke_ev9, S_IWUSR, NULL, &iio_evgen_poke, 9); + +static struct attribute *iio_evgen_attrs[] = { + &iio_dev_attr_poke_ev0.dev_attr.attr, + &iio_dev_attr_poke_ev1.dev_attr.attr, + &iio_dev_attr_poke_ev2.dev_attr.attr, + &iio_dev_attr_poke_ev3.dev_attr.attr, + &iio_dev_attr_poke_ev4.dev_attr.attr, + &iio_dev_attr_poke_ev5.dev_attr.attr, + &iio_dev_attr_poke_ev6.dev_attr.attr, + &iio_dev_attr_poke_ev7.dev_attr.attr, + &iio_dev_attr_poke_ev8.dev_attr.attr, + &iio_dev_attr_poke_ev9.dev_attr.attr, + NULL, +}; + +static const struct attribute_group iio_evgen_group = { + .attrs = iio_evgen_attrs, +}; + +static const struct attribute_group *iio_evgen_groups[] = { + &iio_evgen_group, + NULL +}; + +static struct device iio_evgen_dev = { + .bus = &iio_bus_type, + .groups = iio_evgen_groups, + .release = &iio_evgen_release, +}; +static __init int iio_dummy_evgen_init(void) +{ + int ret = iio_dummy_evgen_create(); + + if (ret < 0) + return ret; + device_initialize(&iio_evgen_dev); + dev_set_name(&iio_evgen_dev, "iio_evgen"); + return device_add(&iio_evgen_dev); +} +module_init(iio_dummy_evgen_init); + +static __exit void iio_dummy_evgen_exit(void) +{ + device_unregister(&iio_evgen_dev); +} +module_exit(iio_dummy_evgen_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/iio_dummy_evgen.h b/kernel/drivers/staging/iio/iio_dummy_evgen.h new file mode 100644 index 000000000..2ac293ab7 --- /dev/null +++ b/kernel/drivers/staging/iio/iio_dummy_evgen.h @@ -0,0 +1,13 @@ +#ifndef _IIO_DUMMY_EVGEN_H_ +#define _IIO_DUMMY_EVGEN_H_ + +struct iio_dummy_regs { + u32 reg_id; + u32 reg_data; +}; + +struct iio_dummy_regs *iio_dummy_evgen_get_regs(int irq); +int iio_dummy_evgen_get_irq(void); +int iio_dummy_evgen_release_irq(int irq); + +#endif /* _IIO_DUMMY_EVGEN_H_ */ diff --git a/kernel/drivers/staging/iio/iio_simple_dummy.c b/kernel/drivers/staging/iio/iio_simple_dummy.c new file mode 100644 index 000000000..b47bf9fb6 --- /dev/null +++ b/kernel/drivers/staging/iio/iio_simple_dummy.c @@ -0,0 +1,749 @@ +/** + * 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. + * + * A reference industrial I/O driver to illustrate the functionality available. + * + * There are numerous real drivers to illustrate the finer points. + * The purpose of this driver is to provide a driver with far more comments + * and explanatory notes than any 'real' driver would have. + * Anyone starting out writing an IIO driver should first make sure they + * understand all of this driver except those bits specifically marked + * as being present to allow us to 'fake' the presence of hardware. + */ +#include +#include +#include + +#include +#include +#include +#include +#include "iio_simple_dummy.h" + +/* + * A few elements needed to fake a bus for this driver + * Note instances parameter controls how many of these + * dummy devices are registered. + */ +static unsigned instances = 1; +module_param(instances, int, 0); + +/* Pointer array used to fake bus elements */ +static struct iio_dev **iio_dummy_devs; + +/* Fake a name for the part number, usually obtained from the id table */ +static const char *iio_dummy_part_number = "iio_dummy_part_no"; + +/** + * struct iio_dummy_accel_calibscale - realworld to register mapping + * @val: first value in read_raw - here integer part. + * @val2: second value in read_raw etc - here micro part. + * @regval: register value - magic device specific numbers. + */ +struct iio_dummy_accel_calibscale { + int val; + int val2; + int regval; /* what would be written to hardware */ +}; + +static const struct iio_dummy_accel_calibscale dummy_scales[] = { + { 0, 100, 0x8 }, /* 0.000100 */ + { 0, 133, 0x7 }, /* 0.000133 */ + { 733, 13, 0x9 }, /* 733.000013 */ +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + +/* + * simple event - triggered when value rises above + * a threshold + */ +static const struct iio_event_spec iio_dummy_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple step detect event - triggered when a step is detected + */ +static const struct iio_event_spec step_detect_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple transition event - triggered when the reported running confidence + * value rises above a threshold value + */ +static const struct iio_event_spec iio_running_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple transition event - triggered when the reported walking confidence + * value falls under a threshold value + */ +static const struct iio_event_spec iio_walking_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; +#endif + +/* + * iio_dummy_channels - Description of available channels + * + * This array of structures tells the IIO core about what the device + * actually provides for a given channel. + */ +static const struct iio_chan_spec iio_dummy_channels[] = { + /* indexed ADC channel in_voltage0_raw etc */ + { + .type = IIO_VOLTAGE, + /* Channel has a numeric index of 0 */ + .indexed = 1, + .channel = 0, + /* What other information is available? */ + .info_mask_separate = + /* + * in_voltage0_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + BIT(IIO_CHAN_INFO_RAW) | + /* + * in_voltage0_offset + * Offset for userspace to apply prior to scale + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_OFFSET) | + /* + * in_voltage0_scale + * Multipler for userspace to apply post offset + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + /* The ordering of elements in the buffer via an enum */ + .scan_index = voltage0, + .scan_type = { /* Description of storage in buffer */ + .sign = 'u', /* unsigned */ + .realbits = 13, /* 13 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_dummy_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + /* Differential ADC channel in_voltage1-voltage2_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + /* + * Indexing for differential channels uses channel + * for the positive part, channel2 for the negative. + */ + .indexed = 1, + .channel = 1, + .channel2 = 2, + /* + * in_voltage1-voltage2_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + /* + * in_voltage-voltage_scale + * Shared version of scale - shared by differential + * input channels of type IIO_VOLTAGE. + */ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .scan_index = diffvoltage1m2, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 12, /* 12 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* Differential ADC channel in_voltage3-voltage4_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 3, + .channel2 = 4, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = diffvoltage3m4, + .scan_type = { + .sign = 's', + .realbits = 11, + .storagebits = 16, + .shift = 0, + }, + }, + /* + * 'modified' (i.e. axis specified) acceleration channel + * in_accel_z_raw + */ + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + /* + * Internal bias and gain correction values. Applied + * by the hardware or driver prior to userspace + * seeing the readings. Typically part of hardware + * calibration. + */ + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = accelx, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 16 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* + * Convenience macro for timestamps. 4 is the index in + * the buffer. + */ + IIO_CHAN_SOFT_TIMESTAMP(4), + /* DAC channel out_voltage0_raw */ + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = -1, /* No buffer support */ + .output = 1, + .indexed = 1, + .channel = 0, + }, + { + .type = IIO_STEPS, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_CALIBHEIGHT), + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &step_detect_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_RUNNING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_running_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_WALKING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_walking_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, +}; + +/** + * iio_dummy_read_raw() - data read function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be read + * @val: first element of returned value (typically INT) + * @val2: second element of returned value (typically MICRO) + * @mask: what we actually want to read as per the info_mask_* + * in iio_chan_spec. + */ +static int iio_dummy_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: /* magic value - channel value read */ + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) { + /* Set integer part to cached value */ + *val = st->dac_val; + ret = IIO_VAL_INT; + } else if (chan->differential) { + if (chan->channel == 1) + *val = st->differential_adc_val[0]; + else + *val = st->differential_adc_val[1]; + ret = IIO_VAL_INT; + } else { + *val = st->single_ended_adc_val; + ret = IIO_VAL_INT; + } + break; + case IIO_ACCEL: + *val = st->accel_val; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + *val = st->steps; + ret = IIO_VAL_INT; + break; + case IIO_ACTIVITY: + switch (chan->channel2) { + case IIO_MOD_RUNNING: + *val = st->activity_running; + ret = IIO_VAL_INT; + break; + case IIO_MOD_WALKING: + *val = st->activity_walking; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + default: + break; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* only single ended adc -> 7 */ + *val = 7; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + switch (chan->differential) { + case 0: + /* only single ended adc -> 0.001333 */ + *val = 0; + *val2 = 1333; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case 1: + /* all differential adc channels -> + * 0.000001344 */ + *val = 0; + *val2 = 1344; + ret = IIO_VAL_INT_PLUS_NANO; + } + break; + default: + break; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + /* only the acceleration axis - read from cache */ + *val = st->accel_calibbias; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + *val = st->accel_calibscale->val; + *val2 = st->accel_calibscale->val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = 3; + *val2 = 33; + ret = IIO_VAL_INT_PLUS_NANO; + break; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + *val = st->steps_enabled; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + *val = st->height; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + default: + break; + } + mutex_unlock(&st->lock); + return ret; +} + +/** + * iio_dummy_write_raw() - data write function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be written + * @val: first element of value to set (typically INT) + * @val2: second element of value to set (typically MICRO) + * @mask: what we actually want to write as per the info_mask_* + * in iio_chan_spec. + * + * Note that all raw writes are assumed IIO_VAL_INT and info mask elements + * are assumed to be IIO_INT_PLUS_MICRO unless the callback write_raw_get_fmt + * in struct iio_info is provided by the driver. + */ +static int iio_dummy_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int i; + int ret = 0; + struct iio_dummy_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output == 0) + return -EINVAL; + + /* Locking not required as writing single value */ + mutex_lock(&st->lock); + st->dac_val = val; + mutex_unlock(&st->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + mutex_lock(&st->lock); + st->steps = val; + mutex_unlock(&st->lock); + return 0; + case IIO_ACTIVITY: + if (val < 0) + val = 0; + if (val > 100) + val = 100; + switch (chan->channel2) { + case IIO_MOD_RUNNING: + st->activity_running = val; + return 0; + case IIO_MOD_WALKING: + st->activity_walking = val; + return 0; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: + mutex_lock(&st->lock); + /* Compare against table - hard matching here */ + for (i = 0; i < ARRAY_SIZE(dummy_scales); i++) + if (val == dummy_scales[i].val && + val2 == dummy_scales[i].val2) + break; + if (i == ARRAY_SIZE(dummy_scales)) + ret = -EINVAL; + else + st->accel_calibscale = &dummy_scales[i]; + mutex_unlock(&st->lock); + return ret; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&st->lock); + st->accel_calibbias = val; + mutex_unlock(&st->lock); + return 0; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + mutex_lock(&st->lock); + st->steps_enabled = val; + mutex_unlock(&st->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + st->height = val; + return 0; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +/* + * Device type specific information. + */ +static const struct iio_info iio_dummy_info = { + .driver_module = THIS_MODULE, + .read_raw = &iio_dummy_read_raw, + .write_raw = &iio_dummy_write_raw, +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .read_event_config = &iio_simple_dummy_read_event_config, + .write_event_config = &iio_simple_dummy_write_event_config, + .read_event_value = &iio_simple_dummy_read_event_value, + .write_event_value = &iio_simple_dummy_write_event_value, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ +}; + +/** + * iio_dummy_init_device() - device instance specific init + * @indio_dev: the iio device structure + * + * Most drivers have one of these to set up default values, + * reset the device to known state etc. + */ +static int iio_dummy_init_device(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->dac_val = 0; + st->single_ended_adc_val = 73; + st->differential_adc_val[0] = 33; + st->differential_adc_val[1] = -34; + st->accel_val = 34; + st->accel_calibbias = -7; + st->accel_calibscale = &dummy_scales[0]; + st->steps = 47; + st->activity_running = 98; + st->activity_walking = 4; + + return 0; +} + +/** + * iio_dummy_probe() - device instance probe + * @index: an id number for this instance. + * + * Arguments are bus type specific. + * I2C: iio_dummy_probe(struct i2c_client *client, + * const struct i2c_device_id *id) + * SPI: iio_dummy_probe(struct spi_device *spi) + */ +static int iio_dummy_probe(int index) +{ + int ret; + struct iio_dev *indio_dev; + struct iio_dummy_state *st; + + /* + * Allocate an IIO device. + * + * This structure contains all generic state + * information about the device instance. + * It also has a region (accessed by iio_priv() + * for chip specific state information. + */ + indio_dev = iio_device_alloc(sizeof(*st)); + if (!indio_dev) { + ret = -ENOMEM; + goto error_ret; + } + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + + iio_dummy_init_device(indio_dev); + /* + * With hardware: Set the parent device. + * indio_dev->dev.parent = &spi->dev; + * indio_dev->dev.parent = &client->dev; + */ + + /* + * Make the iio_dev struct available to remove function. + * Bus equivalents + * i2c_set_clientdata(client, indio_dev); + * spi_set_drvdata(spi, indio_dev); + */ + iio_dummy_devs[index] = indio_dev; + + + /* + * Set the device name. + * + * This is typically a part number and obtained from the module + * id table. + * e.g. for i2c and spi: + * indio_dev->name = id->name; + * indio_dev->name = spi_get_device_id(spi)->name; + */ + indio_dev->name = iio_dummy_part_number; + + /* Provide description of available channels */ + indio_dev->channels = iio_dummy_channels; + indio_dev->num_channels = ARRAY_SIZE(iio_dummy_channels); + + /* + * Provide device type specific interface functions and + * constant data. + */ + indio_dev->info = &iio_dummy_info; + + /* Specify that device provides sysfs type interfaces */ + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_simple_dummy_events_register(indio_dev); + if (ret < 0) + goto error_free_device; + + ret = iio_simple_dummy_configure_buffer(indio_dev); + if (ret < 0) + goto error_unregister_events; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_unconfigure_buffer; + + return 0; +error_unconfigure_buffer: + iio_simple_dummy_unconfigure_buffer(indio_dev); +error_unregister_events: + iio_simple_dummy_events_unregister(indio_dev); +error_free_device: + iio_device_free(indio_dev); +error_ret: + return ret; +} + +/** + * iio_dummy_remove() - device instance removal function + * @index: device index. + * + * Parameters follow those of iio_dummy_probe for buses. + */ +static int iio_dummy_remove(int index) +{ + int ret; + /* + * Get a pointer to the device instance iio_dev structure + * from the bus subsystem. E.g. + * struct iio_dev *indio_dev = i2c_get_clientdata(client); + * struct iio_dev *indio_dev = spi_get_drvdata(spi); + */ + struct iio_dev *indio_dev = iio_dummy_devs[index]; + + + /* Unregister the device */ + iio_device_unregister(indio_dev); + + /* Device specific code to power down etc */ + + /* Buffered capture related cleanup */ + iio_simple_dummy_unconfigure_buffer(indio_dev); + + ret = iio_simple_dummy_events_unregister(indio_dev); + if (ret) + goto error_ret; + + /* Free all structures */ + iio_device_free(indio_dev); + +error_ret: + return ret; +} + +/** + * iio_dummy_init() - device driver registration + * + * Varies depending on bus type of the device. As there is no device + * here, call probe directly. For information on device registration + * i2c: + * Documentation/i2c/writing-clients + * spi: + * Documentation/spi/spi-summary + */ +static __init int iio_dummy_init(void) +{ + int i, ret; + + if (instances > 10) { + instances = 1; + return -EINVAL; + } + + /* Fake a bus */ + iio_dummy_devs = kcalloc(instances, sizeof(*iio_dummy_devs), + GFP_KERNEL); + /* Here we have no actual device so call probe */ + for (i = 0; i < instances; i++) { + ret = iio_dummy_probe(i); + if (ret < 0) + return ret; + } + return 0; +} +module_init(iio_dummy_init); + +/** + * iio_dummy_exit() - device driver removal + * + * Varies depending on bus type of the device. + * As there is no device here, call remove directly. + */ +static __exit void iio_dummy_exit(void) +{ + int i; + + for (i = 0; i < instances; i++) + iio_dummy_remove(i); + kfree(iio_dummy_devs); +} +module_exit(iio_dummy_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/iio_simple_dummy.h b/kernel/drivers/staging/iio/iio_simple_dummy.h new file mode 100644 index 000000000..34989bf24 --- /dev/null +++ b/kernel/drivers/staging/iio/iio_simple_dummy.h @@ -0,0 +1,129 @@ +/** + * 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. + * + * Join together the various functionality of iio_simple_dummy driver + */ + +#ifndef _IIO_SIMPLE_DUMMY_H_ +#define _IIO_SIMPLE_DUMMY_H_ +#include + +struct iio_dummy_accel_calibscale; +struct iio_dummy_regs; + +/** + * struct iio_dummy_state - device instance specific state. + * @dac_val: cache for dac value + * @single_ended_adc_val: cache for single ended adc value + * @differential_adc_val: cache for differential adc value + * @accel_val: cache for acceleration value + * @accel_calibbias: cache for acceleration calibbias + * @accel_calibscale: cache for acceleration calibscale + * @lock: lock to ensure state is consistent + * @event_irq: irq number for event line (faked) + * @event_val: cache for event theshold value + * @event_en: cache of whether event is enabled + */ +struct iio_dummy_state { + int dac_val; + int single_ended_adc_val; + int differential_adc_val[2]; + int accel_val; + int accel_calibbias; + int activity_running; + int activity_walking; + const struct iio_dummy_accel_calibscale *accel_calibscale; + struct mutex lock; + struct iio_dummy_regs *regs; + int steps_enabled; + int steps; + int height; +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + int event_irq; + int event_val; + bool event_en; +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + +struct iio_dev; + +int iio_simple_dummy_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir); + +int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state); + +int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2); + +int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, + int val2); + +int iio_simple_dummy_events_register(struct iio_dev *indio_dev); +int iio_simple_dummy_events_unregister(struct iio_dev *indio_dev); + +#else /* Stubs for when events are disabled at compile time */ + +static inline int +iio_simple_dummy_events_register(struct iio_dev *indio_dev) +{ + return 0; +}; + +static inline int +iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) +{ + return 0; +}; + +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS*/ + +/** + * enum iio_simple_dummy_scan_elements - scan index enum + * @voltage0: the single ended voltage channel + * @diffvoltage1m2: first differential channel + * @diffvoltage3m4: second differenial channel + * @accelx: acceleration channel + * + * Enum provides convenient numbering for the scan index. + */ +enum iio_simple_dummy_scan_elements { + voltage0, + diffvoltage1m2, + diffvoltage3m4, + accelx, +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_BUFFER +int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev); +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev); +#else +static inline int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) +{ + return 0; +}; +static inline +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) +{}; + +#endif /* CONFIG_IIO_SIMPLE_DUMMY_BUFFER */ +#endif /* _IIO_SIMPLE_DUMMY_H_ */ diff --git a/kernel/drivers/staging/iio/iio_simple_dummy_buffer.c b/kernel/drivers/staging/iio/iio_simple_dummy_buffer.c new file mode 100644 index 000000000..a651b8922 --- /dev/null +++ b/kernel/drivers/staging/iio/iio_simple_dummy_buffer.c @@ -0,0 +1,192 @@ +/** + * 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. + * + * Buffer handling elements of industrial I/O reference driver. + * Uses the kfifo buffer. + * + * To test without hardware use the sysfs trigger. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "iio_simple_dummy.h" + +/* Some fake data */ + +static const s16 fakedata[] = { + [voltage0] = 7, + [diffvoltage1m2] = -33, + [diffvoltage3m4] = -2, + [accelx] = 344, +}; +/** + * iio_simple_dummy_trigger_h() - the trigger handler function + * @irq: the interrupt number + * @p: private data - always a pointer to the poll func. + * + * This is the guts of buffered capture. On a trigger event occurring, + * if the pollfunc is attached then this handler is called as a threaded + * interrupt (and hence may sleep). It is responsible for grabbing data + * from the device and pushing it into the associated buffer. + */ +static irqreturn_t iio_simple_dummy_trigger_h(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + int len = 0; + u16 *data; + + data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!data) + goto done; + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) { + /* + * Three common options here: + * hardware scans: certain combinations of channels make + * up a fast read. The capture will consist of all of them. + * Hence we just call the grab data function and fill the + * buffer without processing. + * software scans: can be considered to be random access + * so efficient reading is just a case of minimal bus + * transactions. + * software culled hardware scans: + * occasionally a driver may process the nearest hardware + * scan to avoid storing elements that are not desired. This + * is the fiddliest option by far. + * Here let's pretend we have random access. And the values are + * in the constant table fakedata. + */ + int i, j; + + for (i = 0, j = 0; + i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + i++, j++) { + j = find_next_bit(indio_dev->active_scan_mask, + indio_dev->masklength, j); + /* random access read from the 'device' */ + data[i] = fakedata[j]; + len += 2; + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, data, iio_get_time_ns()); + + kfree(data); + +done: + /* + * Tell the core we are done with this trigger and ready for the + * next one. + */ + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops iio_simple_dummy_buffer_setup_ops = { + /* + * iio_triggered_buffer_postenable: + * Generic function that simply attaches the pollfunc to the trigger. + * Replace this to mess with hardware state before we attach the + * trigger. + */ + .postenable = &iio_triggered_buffer_postenable, + /* + * iio_triggered_buffer_predisable: + * Generic function that simple detaches the pollfunc from the trigger. + * Replace this to put hardware state back again after the trigger is + * detached but before userspace knows we have disabled the ring. + */ + .predisable = &iio_triggered_buffer_predisable, +}; + +int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) +{ + int ret; + struct iio_buffer *buffer; + + /* Allocate a buffer to use - here a kfifo */ + buffer = iio_kfifo_allocate(); + if (!buffer) { + ret = -ENOMEM; + goto error_ret; + } + + iio_device_attach_buffer(indio_dev, buffer); + + /* Enable timestamps by default */ + buffer->scan_timestamp = true; + + /* + * Tell the core what device type specific functions should + * be run on either side of buffer capture enable / disable. + */ + indio_dev->setup_ops = &iio_simple_dummy_buffer_setup_ops; + + /* + * Configure a polling function. + * When a trigger event with this polling function connected + * occurs, this function is run. Typically this grabs data + * from the device. + * + * NULL for the bottom half. This is normally implemented only if we + * either want to ping a capture now pin (no sleeping) or grab + * a timestamp as close as possible to a data ready trigger firing. + * + * IRQF_ONESHOT ensures irqs are masked such that only one instance + * of the handler can run at a time. + * + * "iio_simple_dummy_consumer%d" formatting string for the irq 'name' + * as seen under /proc/interrupts. Remaining parameters as per printk. + */ + indio_dev->pollfunc = iio_alloc_pollfunc(NULL, + &iio_simple_dummy_trigger_h, + IRQF_ONESHOT, + indio_dev, + "iio_simple_dummy_consumer%d", + indio_dev->id); + + if (!indio_dev->pollfunc) { + ret = -ENOMEM; + goto error_free_buffer; + } + + /* + * Notify the core that this device is capable of buffered capture + * driven by a trigger. + */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + return 0; + +error_free_buffer: + iio_kfifo_free(indio_dev->buffer); +error_ret: + return ret; + +} + +/** + * iio_simple_dummy_unconfigure_buffer() - release buffer resources + * @indo_dev: device instance state + */ +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_kfifo_free(indio_dev->buffer); +} diff --git a/kernel/drivers/staging/iio/iio_simple_dummy_events.c b/kernel/drivers/staging/iio/iio_simple_dummy_events.c new file mode 100644 index 000000000..a5cd3bb21 --- /dev/null +++ b/kernel/drivers/staging/iio/iio_simple_dummy_events.c @@ -0,0 +1,267 @@ +/** + * 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. + * + * Event handling elements of industrial I/O reference driver. + */ +#include +#include +#include +#include + +#include +#include +#include +#include "iio_simple_dummy.h" + +/* Evgen 'fakes' interrupt events for this example */ +#include "iio_dummy_evgen.h" + +/** + * iio_simple_dummy_read_event_config() - is event enabled? + * @indio_dev: the device instance data + * @chan: channel for the event whose state is being queried + * @type: type of the event whose state is being queried + * @dir: direction of the vent whose state is being queried + * + * This function would normally query the relevant registers or a cache to + * discover if the event generation is enabled on the device. + */ +int iio_simple_dummy_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + return st->event_en; +} + +/** + * iio_simple_dummy_write_event_config() - set whether event is enabled + * @indio_dev: the device instance data + * @chan: channel for the event whose state is being set + * @type: type of the event whose state is being set + * @dir: direction of the vent whose state is being set + * @state: whether to enable or disable the device. + * + * This function would normally set the relevant registers on the devices + * so that it generates the specified event. Here it just sets up a cached + * value. + */ +int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + /* + * Deliberately over the top code splitting to illustrate + * how this is done when multiple events exist. + */ + switch (chan->type) { + case IIO_VOLTAGE: + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_RISING) + st->event_en = state; + else + return -EINVAL; + default: + return -EINVAL; + } + break; + case IIO_ACTIVITY: + switch (type) { + case IIO_EV_TYPE_THRESH: + st->event_en = state; + break; + default: + return -EINVAL; + } + case IIO_STEPS: + switch (type) { + case IIO_EV_TYPE_CHANGE: + st->event_en = state; + break; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + return 0; +} + +/** + * iio_simple_dummy_read_event_value() - get value associated with event + * @indio_dev: device instance specific data + * @chan: channel for the event whose value is being read + * @type: type of the event whose value is being read + * @dir: direction of the vent whose value is being read + * @info: info type of the event whose value is being read + * @val: value for the event code. + * + * Many devices provide a large set of events of which only a subset may + * be enabled at a time, with value registers whose meaning changes depending + * on the event enabled. This often means that the driver must cache the values + * associated with each possible events so that the right value is in place when + * the enabled event is changed. + */ +int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + *val = st->event_val; + + return IIO_VAL_INT; +} + +/** + * iio_simple_dummy_write_event_value() - set value associate with event + * @indio_dev: device instance specific data + * @chan: channel for the event whose value is being set + * @type: type of the event whose value is being set + * @dir: direction of the vent whose value is being set + * @info: info type of the event whose value is being set + * @val: the value to be set. + */ +int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->event_val = val; + + return 0; +} + +/** + * iio_simple_dummy_event_handler() - identify and pass on event + * @irq: irq of event line + * @private: pointer to device instance state. + * + * This handler is responsible for querying the device to find out what + * event occurred and for then pushing that event towards userspace. + * Here only one event occurs so we push that directly on with locally + * grabbed timestamp. + */ +static irqreturn_t iio_simple_dummy_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct iio_dummy_state *st = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "id %x event %x\n", + st->regs->reg_id, st->regs->reg_data); + + switch (st->regs->reg_data) { + case 0: + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, 0, 0, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, 0, 0, 0), + iio_get_time_ns()); + break; + case 1: + if (st->activity_running > st->event_val) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_ACTIVITY, 0, + IIO_MOD_RUNNING, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + iio_get_time_ns()); + break; + case 2: + if (st->activity_walking < st->event_val) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_ACTIVITY, 0, + IIO_MOD_WALKING, + IIO_EV_DIR_FALLING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + iio_get_time_ns()); + break; + case 3: + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_STEPS, 0, IIO_NO_MOD, + IIO_EV_DIR_NONE, + IIO_EV_TYPE_CHANGE, 0, 0, 0), + iio_get_time_ns()); + break; + default: + break; + } + + return IRQ_HANDLED; +} + +/** + * iio_simple_dummy_events_register() - setup interrupt handling for events + * @indio_dev: device instance data + * + * This function requests the threaded interrupt to handle the events. + * Normally the irq is a hardware interrupt and the number comes + * from board configuration files. Here we get it from a companion + * module that fakes the interrupt for us. Note that module in + * no way forms part of this example. Just assume that events magically + * appear via the provided interrupt. + */ +int iio_simple_dummy_events_register(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret; + + /* Fire up event source - normally not present */ + st->event_irq = iio_dummy_evgen_get_irq(); + if (st->event_irq < 0) { + ret = st->event_irq; + goto error_ret; + } + st->regs = iio_dummy_evgen_get_regs(st->event_irq); + + ret = request_threaded_irq(st->event_irq, + NULL, + &iio_simple_dummy_event_handler, + IRQF_ONESHOT, + "iio_simple_event", + indio_dev); + if (ret < 0) + goto error_free_evgen; + return 0; + +error_free_evgen: + iio_dummy_evgen_release_irq(st->event_irq); +error_ret: + return ret; +} + +/** + * iio_simple_dummy_events_unregister() - tidy up interrupt handling on remove + * @indio_dev: device instance data + */ +int iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + free_irq(st->event_irq, indio_dev); + /* Not part of normal driver */ + iio_dummy_evgen_release_irq(st->event_irq); + + return 0; +} diff --git a/kernel/drivers/staging/iio/impedance-analyzer/Kconfig b/kernel/drivers/staging/iio/impedance-analyzer/Kconfig new file mode 100644 index 000000000..dd97b6bb3 --- /dev/null +++ b/kernel/drivers/staging/iio/impedance-analyzer/Kconfig @@ -0,0 +1,18 @@ +# +# Impedance Converter, Network Analyzer drivers +# +menu "Network Analyzer, Impedance Converters" + +config AD5933 + tristate "Analog Devices AD5933, AD5934 driver" + depends on I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for Analog Devices Impedance Converter, + Network Analyzer, AD5933/4, provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad5933. + +endmenu diff --git a/kernel/drivers/staging/iio/impedance-analyzer/Makefile b/kernel/drivers/staging/iio/impedance-analyzer/Makefile new file mode 100644 index 000000000..7604d7865 --- /dev/null +++ b/kernel/drivers/staging/iio/impedance-analyzer/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for Impedance Converter, Network Analyzer drivers +# + +obj-$(CONFIG_AD5933) += ad5933.o diff --git a/kernel/drivers/staging/iio/impedance-analyzer/ad5933.c b/kernel/drivers/staging/iio/impedance-analyzer/ad5933.c new file mode 100644 index 000000000..c18109c55 --- /dev/null +++ b/kernel/drivers/staging/iio/impedance-analyzer/ad5933.c @@ -0,0 +1,804 @@ +/* + * AD5933 AD5934 Impedance Converter, Network Analyzer + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "ad5933.h" + +/* AD5933/AD5934 Registers */ +#define AD5933_REG_CONTROL_HB 0x80 /* R/W, 2 bytes */ +#define AD5933_REG_CONTROL_LB 0x81 /* R/W, 2 bytes */ +#define AD5933_REG_FREQ_START 0x82 /* R/W, 3 bytes */ +#define AD5933_REG_FREQ_INC 0x85 /* R/W, 3 bytes */ +#define AD5933_REG_INC_NUM 0x88 /* R/W, 2 bytes, 9 bit */ +#define AD5933_REG_SETTLING_CYCLES 0x8A /* R/W, 2 bytes */ +#define AD5933_REG_STATUS 0x8F /* R, 1 byte */ +#define AD5933_REG_TEMP_DATA 0x92 /* R, 2 bytes*/ +#define AD5933_REG_REAL_DATA 0x94 /* R, 2 bytes*/ +#define AD5933_REG_IMAG_DATA 0x96 /* R, 2 bytes*/ + +/* AD5933_REG_CONTROL_HB Bits */ +#define AD5933_CTRL_INIT_START_FREQ (0x1 << 4) +#define AD5933_CTRL_START_SWEEP (0x2 << 4) +#define AD5933_CTRL_INC_FREQ (0x3 << 4) +#define AD5933_CTRL_REPEAT_FREQ (0x4 << 4) +#define AD5933_CTRL_MEASURE_TEMP (0x9 << 4) +#define AD5933_CTRL_POWER_DOWN (0xA << 4) +#define AD5933_CTRL_STANDBY (0xB << 4) + +#define AD5933_CTRL_RANGE_2000mVpp (0x0 << 1) +#define AD5933_CTRL_RANGE_200mVpp (0x1 << 1) +#define AD5933_CTRL_RANGE_400mVpp (0x2 << 1) +#define AD5933_CTRL_RANGE_1000mVpp (0x3 << 1) +#define AD5933_CTRL_RANGE(x) ((x) << 1) + +#define AD5933_CTRL_PGA_GAIN_1 (0x1 << 0) +#define AD5933_CTRL_PGA_GAIN_5 (0x0 << 0) + +/* AD5933_REG_CONTROL_LB Bits */ +#define AD5933_CTRL_RESET (0x1 << 4) +#define AD5933_CTRL_INT_SYSCLK (0x0 << 3) +#define AD5933_CTRL_EXT_SYSCLK (0x1 << 3) + +/* AD5933_REG_STATUS Bits */ +#define AD5933_STAT_TEMP_VALID (0x1 << 0) +#define AD5933_STAT_DATA_VALID (0x1 << 1) +#define AD5933_STAT_SWEEP_DONE (0x1 << 2) + +/* I2C Block Commands */ +#define AD5933_I2C_BLOCK_WRITE 0xA0 +#define AD5933_I2C_BLOCK_READ 0xA1 +#define AD5933_I2C_ADDR_POINTER 0xB0 + +/* Device Specs */ +#define AD5933_INT_OSC_FREQ_Hz 16776000 +#define AD5933_MAX_OUTPUT_FREQ_Hz 100000 +#define AD5933_MAX_RETRIES 100 + +#define AD5933_OUT_RANGE 1 +#define AD5933_OUT_RANGE_AVAIL 2 +#define AD5933_OUT_SETTLING_CYCLES 3 +#define AD5933_IN_PGA_GAIN 4 +#define AD5933_IN_PGA_GAIN_AVAIL 5 +#define AD5933_FREQ_POINTS 6 + +#define AD5933_POLL_TIME_ms 10 +#define AD5933_INIT_EXCITATION_TIME_ms 100 + +struct ad5933_state { + struct i2c_client *client; + struct regulator *reg; + struct delayed_work work; + unsigned long mclk_hz; + unsigned char ctrl_hb; + unsigned char ctrl_lb; + unsigned range_avail[4]; + unsigned short vref_mv; + unsigned short settling_cycles; + unsigned short freq_points; + unsigned freq_start; + unsigned freq_inc; + unsigned state; + unsigned poll_time_jiffies; +}; + +static struct ad5933_platform_data ad5933_default_pdata = { + .vref_mv = 3300, +}; + +static const struct iio_chan_spec ad5933_channels[] = { + { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .address = AD5933_REG_TEMP_DATA, + .scan_index = -1, + .scan_type = { + .sign = 's', + .realbits = 14, + .storagebits = 16, + }, + }, { /* Ring Channels */ + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .extend_name = "real", + .address = AD5933_REG_REAL_DATA, + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + }, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .extend_name = "imag", + .address = AD5933_REG_IMAG_DATA, + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + }, + }, +}; + +static int ad5933_i2c_write(struct i2c_client *client, + u8 reg, u8 len, u8 *data) +{ + int ret; + + while (len--) { + ret = i2c_smbus_write_byte_data(client, reg++, *data++); + if (ret < 0) { + dev_err(&client->dev, "I2C write error\n"); + return ret; + } + } + return 0; +} + +static int ad5933_i2c_read(struct i2c_client *client, + u8 reg, u8 len, u8 *data) +{ + int ret; + + while (len--) { + ret = i2c_smbus_read_byte_data(client, reg++); + if (ret < 0) { + dev_err(&client->dev, "I2C read error\n"); + return ret; + } + *data++ = ret; + } + return 0; +} + +static int ad5933_cmd(struct ad5933_state *st, unsigned char cmd) +{ + unsigned char dat = st->ctrl_hb | cmd; + + return ad5933_i2c_write(st->client, + AD5933_REG_CONTROL_HB, 1, &dat); +} + +static int ad5933_reset(struct ad5933_state *st) +{ + unsigned char dat = st->ctrl_lb | AD5933_CTRL_RESET; + + return ad5933_i2c_write(st->client, + AD5933_REG_CONTROL_LB, 1, &dat); +} + +static int ad5933_wait_busy(struct ad5933_state *st, unsigned char event) +{ + unsigned char val, timeout = AD5933_MAX_RETRIES; + int ret; + + while (timeout--) { + ret = ad5933_i2c_read(st->client, AD5933_REG_STATUS, 1, &val); + if (ret < 0) + return ret; + if (val & event) + return val; + cpu_relax(); + mdelay(1); + } + + return -EAGAIN; +} + +static int ad5933_set_freq(struct ad5933_state *st, + unsigned reg, unsigned long freq) +{ + unsigned long long freqreg; + union { + __be32 d32; + u8 d8[4]; + } dat; + + freqreg = (u64) freq * (u64) (1 << 27); + do_div(freqreg, st->mclk_hz / 4); + + switch (reg) { + case AD5933_REG_FREQ_START: + st->freq_start = freq; + break; + case AD5933_REG_FREQ_INC: + st->freq_inc = freq; + break; + default: + return -EINVAL; + } + + dat.d32 = cpu_to_be32(freqreg); + return ad5933_i2c_write(st->client, reg, 3, &dat.d8[1]); +} + +static int ad5933_setup(struct ad5933_state *st) +{ + __be16 dat; + int ret; + + ret = ad5933_reset(st); + if (ret < 0) + return ret; + + ret = ad5933_set_freq(st, AD5933_REG_FREQ_START, 10000); + if (ret < 0) + return ret; + + ret = ad5933_set_freq(st, AD5933_REG_FREQ_INC, 200); + if (ret < 0) + return ret; + + st->settling_cycles = 10; + dat = cpu_to_be16(st->settling_cycles); + + ret = ad5933_i2c_write(st->client, + AD5933_REG_SETTLING_CYCLES, 2, (u8 *)&dat); + if (ret < 0) + return ret; + + st->freq_points = 100; + dat = cpu_to_be16(st->freq_points); + + return ad5933_i2c_write(st->client, AD5933_REG_INC_NUM, 2, (u8 *)&dat); +} + +static void ad5933_calc_out_ranges(struct ad5933_state *st) +{ + int i; + unsigned normalized_3v3[4] = {1980, 198, 383, 970}; + + for (i = 0; i < 4; i++) + st->range_avail[i] = normalized_3v3[i] * st->vref_mv / 3300; + +} + +/* + * handles: AD5933_REG_FREQ_START and AD5933_REG_FREQ_INC + */ + +static ssize_t ad5933_show_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad5933_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + unsigned long long freqreg; + union { + __be32 d32; + u8 d8[4]; + } dat; + + mutex_lock(&indio_dev->mlock); + ret = ad5933_i2c_read(st->client, this_attr->address, 3, &dat.d8[1]); + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + + freqreg = be32_to_cpu(dat.d32) & 0xFFFFFF; + + freqreg = (u64) freqreg * (u64) (st->mclk_hz / 4); + do_div(freqreg, 1 << 27); + + return sprintf(buf, "%d\n", (int) freqreg); +} + +static ssize_t ad5933_store_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 ad5933_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (val > AD5933_MAX_OUTPUT_FREQ_Hz) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = ad5933_set_freq(st, this_attr->address, val); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage0_freq_start, S_IRUGO | S_IWUSR, + ad5933_show_frequency, + ad5933_store_frequency, + AD5933_REG_FREQ_START); + +static IIO_DEVICE_ATTR(out_voltage0_freq_increment, S_IRUGO | S_IWUSR, + ad5933_show_frequency, + ad5933_store_frequency, + AD5933_REG_FREQ_INC); + +static ssize_t ad5933_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ad5933_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret = 0, len = 0; + + mutex_lock(&indio_dev->mlock); + switch ((u32) this_attr->address) { + case AD5933_OUT_RANGE: + len = sprintf(buf, "%u\n", + st->range_avail[(st->ctrl_hb >> 1) & 0x3]); + break; + case AD5933_OUT_RANGE_AVAIL: + len = sprintf(buf, "%u %u %u %u\n", st->range_avail[0], + st->range_avail[3], st->range_avail[2], + st->range_avail[1]); + break; + case AD5933_OUT_SETTLING_CYCLES: + len = sprintf(buf, "%d\n", st->settling_cycles); + break; + case AD5933_IN_PGA_GAIN: + len = sprintf(buf, "%s\n", + (st->ctrl_hb & AD5933_CTRL_PGA_GAIN_1) ? + "1" : "0.2"); + break; + case AD5933_IN_PGA_GAIN_AVAIL: + len = sprintf(buf, "1 0.2\n"); + break; + case AD5933_FREQ_POINTS: + len = sprintf(buf, "%d\n", st->freq_points); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + return ret ? ret : len; +} + +static ssize_t ad5933_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 ad5933_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + u16 val; + int i, ret = 0; + __be16 dat; + + if (this_attr->address != AD5933_IN_PGA_GAIN) { + ret = kstrtou16(buf, 10, &val); + if (ret) + return ret; + } + + mutex_lock(&indio_dev->mlock); + switch ((u32) this_attr->address) { + case AD5933_OUT_RANGE: + for (i = 0; i < 4; i++) + if (val == st->range_avail[i]) { + st->ctrl_hb &= ~AD5933_CTRL_RANGE(0x3); + st->ctrl_hb |= AD5933_CTRL_RANGE(i); + ret = ad5933_cmd(st, 0); + break; + } + ret = -EINVAL; + break; + case AD5933_IN_PGA_GAIN: + if (sysfs_streq(buf, "1")) { + st->ctrl_hb |= AD5933_CTRL_PGA_GAIN_1; + } else if (sysfs_streq(buf, "0.2")) { + st->ctrl_hb &= ~AD5933_CTRL_PGA_GAIN_1; + } else { + ret = -EINVAL; + break; + } + ret = ad5933_cmd(st, 0); + break; + case AD5933_OUT_SETTLING_CYCLES: + val = clamp(val, (u16)0, (u16)0x7FF); + st->settling_cycles = val; + + /* 2x, 4x handling, see datasheet */ + if (val > 511) + val = (val >> 1) | (1 << 9); + else if (val > 1022) + val = (val >> 2) | (3 << 9); + + dat = cpu_to_be16(val); + ret = ad5933_i2c_write(st->client, + AD5933_REG_SETTLING_CYCLES, 2, (u8 *)&dat); + break; + case AD5933_FREQ_POINTS: + val = clamp(val, (u16)0, (u16)511); + st->freq_points = val; + + dat = cpu_to_be16(val); + ret = ad5933_i2c_write(st->client, AD5933_REG_INC_NUM, 2, + (u8 *)&dat); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage0_scale, S_IRUGO | S_IWUSR, + ad5933_show, + ad5933_store, + AD5933_OUT_RANGE); + +static IIO_DEVICE_ATTR(out_voltage0_scale_available, S_IRUGO, + ad5933_show, + NULL, + AD5933_OUT_RANGE_AVAIL); + +static IIO_DEVICE_ATTR(in_voltage0_scale, S_IRUGO | S_IWUSR, + ad5933_show, + ad5933_store, + AD5933_IN_PGA_GAIN); + +static IIO_DEVICE_ATTR(in_voltage0_scale_available, S_IRUGO, + ad5933_show, + NULL, + AD5933_IN_PGA_GAIN_AVAIL); + +static IIO_DEVICE_ATTR(out_voltage0_freq_points, S_IRUGO | S_IWUSR, + ad5933_show, + ad5933_store, + AD5933_FREQ_POINTS); + +static IIO_DEVICE_ATTR(out_voltage0_settling_cycles, S_IRUGO | S_IWUSR, + ad5933_show, + ad5933_store, + AD5933_OUT_SETTLING_CYCLES); + +/* note: + * ideally we would handle the scale attributes via the iio_info + * (read|write)_raw methods, however this part is a untypical since we + * don't create dedicated sysfs channel attributes for out0 and in0. + */ +static struct attribute *ad5933_attributes[] = { + &iio_dev_attr_out_voltage0_scale.dev_attr.attr, + &iio_dev_attr_out_voltage0_scale_available.dev_attr.attr, + &iio_dev_attr_out_voltage0_freq_start.dev_attr.attr, + &iio_dev_attr_out_voltage0_freq_increment.dev_attr.attr, + &iio_dev_attr_out_voltage0_freq_points.dev_attr.attr, + &iio_dev_attr_out_voltage0_settling_cycles.dev_attr.attr, + &iio_dev_attr_in_voltage0_scale.dev_attr.attr, + &iio_dev_attr_in_voltage0_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad5933_attribute_group = { + .attrs = ad5933_attributes, +}; + +static int ad5933_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5933_state *st = iio_priv(indio_dev); + __be16 dat; + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto out; + } + ret = ad5933_cmd(st, AD5933_CTRL_MEASURE_TEMP); + if (ret < 0) + goto out; + ret = ad5933_wait_busy(st, AD5933_STAT_TEMP_VALID); + if (ret < 0) + goto out; + + ret = ad5933_i2c_read(st->client, + AD5933_REG_TEMP_DATA, 2, + (u8 *)&dat); + if (ret < 0) + goto out; + mutex_unlock(&indio_dev->mlock); + *val = sign_extend32(be16_to_cpu(dat), 13); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1000; + *val2 = 5; + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static const struct iio_info ad5933_info = { + .read_raw = &ad5933_read_raw, + .attrs = &ad5933_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ad5933_ring_preenable(struct iio_dev *indio_dev) +{ + struct ad5933_state *st = iio_priv(indio_dev); + int ret; + + if (bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + return -EINVAL; + + ret = ad5933_reset(st); + if (ret < 0) + return ret; + + ret = ad5933_cmd(st, AD5933_CTRL_STANDBY); + if (ret < 0) + return ret; + + ret = ad5933_cmd(st, AD5933_CTRL_INIT_START_FREQ); + if (ret < 0) + return ret; + + st->state = AD5933_CTRL_INIT_START_FREQ; + + return 0; +} + +static int ad5933_ring_postenable(struct iio_dev *indio_dev) +{ + struct ad5933_state *st = iio_priv(indio_dev); + + /* AD5933_CTRL_INIT_START_FREQ: + * High Q complex circuits require a long time to reach steady state. + * To facilitate the measurement of such impedances, this mode allows + * the user full control of the settling time requirement before + * entering start frequency sweep mode where the impedance measurement + * takes place. In this mode the impedance is excited with the + * programmed start frequency (ad5933_ring_preenable), + * but no measurement takes place. + */ + + schedule_delayed_work(&st->work, + msecs_to_jiffies(AD5933_INIT_EXCITATION_TIME_ms)); + return 0; +} + +static int ad5933_ring_postdisable(struct iio_dev *indio_dev) +{ + struct ad5933_state *st = iio_priv(indio_dev); + + cancel_delayed_work_sync(&st->work); + return ad5933_cmd(st, AD5933_CTRL_POWER_DOWN); +} + +static const struct iio_buffer_setup_ops ad5933_ring_setup_ops = { + .preenable = &ad5933_ring_preenable, + .postenable = &ad5933_ring_postenable, + .postdisable = &ad5933_ring_postdisable, +}; + +static int ad5933_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + struct iio_buffer *buffer; + + buffer = iio_kfifo_allocate(); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = &ad5933_ring_setup_ops; + + indio_dev->modes |= INDIO_BUFFER_HARDWARE; + + return 0; +} + +static void ad5933_work(struct work_struct *work) +{ + struct ad5933_state *st = container_of(work, + struct ad5933_state, work.work); + struct iio_dev *indio_dev = i2c_get_clientdata(st->client); + signed short buf[2]; + unsigned char status; + + mutex_lock(&indio_dev->mlock); + if (st->state == AD5933_CTRL_INIT_START_FREQ) { + /* start sweep */ + ad5933_cmd(st, AD5933_CTRL_START_SWEEP); + st->state = AD5933_CTRL_START_SWEEP; + schedule_delayed_work(&st->work, st->poll_time_jiffies); + mutex_unlock(&indio_dev->mlock); + return; + } + + ad5933_i2c_read(st->client, AD5933_REG_STATUS, 1, &status); + + if (status & AD5933_STAT_DATA_VALID) { + int scan_count = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + ad5933_i2c_read(st->client, + test_bit(1, indio_dev->active_scan_mask) ? + AD5933_REG_REAL_DATA : AD5933_REG_IMAG_DATA, + scan_count * 2, (u8 *)buf); + + if (scan_count == 2) { + buf[0] = be16_to_cpu(buf[0]); + buf[1] = be16_to_cpu(buf[1]); + } else { + buf[0] = be16_to_cpu(buf[0]); + } + iio_push_to_buffers(indio_dev, buf); + } else { + /* no data available - try again later */ + schedule_delayed_work(&st->work, st->poll_time_jiffies); + mutex_unlock(&indio_dev->mlock); + return; + } + + if (status & AD5933_STAT_SWEEP_DONE) { + /* last sample received - power down do nothing until + * the ring enable is toggled */ + ad5933_cmd(st, AD5933_CTRL_POWER_DOWN); + } else { + /* we just received a valid datum, move on to the next */ + ad5933_cmd(st, AD5933_CTRL_INC_FREQ); + schedule_delayed_work(&st->work, st->poll_time_jiffies); + } + + mutex_unlock(&indio_dev->mlock); +} + +static int ad5933_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret, voltage_uv = 0; + struct ad5933_platform_data *pdata = client->dev.platform_data; + struct ad5933_state *st; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + st->client = client; + + if (!pdata) + pdata = &ad5933_default_pdata; + + st->reg = devm_regulator_get(&client->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + voltage_uv = regulator_get_voltage(st->reg); + } + + if (voltage_uv) + st->vref_mv = voltage_uv / 1000; + else + st->vref_mv = pdata->vref_mv; + + if (pdata->ext_clk_Hz) { + st->mclk_hz = pdata->ext_clk_Hz; + st->ctrl_lb = AD5933_CTRL_EXT_SYSCLK; + } else { + st->mclk_hz = AD5933_INT_OSC_FREQ_Hz; + st->ctrl_lb = AD5933_CTRL_INT_SYSCLK; + } + + ad5933_calc_out_ranges(st); + INIT_DELAYED_WORK(&st->work, ad5933_work); + st->poll_time_jiffies = msecs_to_jiffies(AD5933_POLL_TIME_ms); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ad5933_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad5933_channels; + indio_dev->num_channels = ARRAY_SIZE(ad5933_channels); + + ret = ad5933_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_disable_reg; + + ret = ad5933_setup(st); + if (ret) + goto error_unreg_ring; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_ring; + + return 0; + +error_unreg_ring: + iio_kfifo_free(indio_dev->buffer); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ad5933_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ad5933_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_kfifo_free(indio_dev->buffer); + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct i2c_device_id ad5933_id[] = { + { "ad5933", 0 }, + { "ad5934", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad5933_id); + +static struct i2c_driver ad5933_driver = { + .driver = { + .name = "ad5933", + }, + .probe = ad5933_probe, + .remove = ad5933_remove, + .id_table = ad5933_id, +}; +module_i2c_driver(ad5933_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD5933 Impedance Conv. Network Analyzer"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/impedance-analyzer/ad5933.h b/kernel/drivers/staging/iio/impedance-analyzer/ad5933.h new file mode 100644 index 000000000..b140e42d6 --- /dev/null +++ b/kernel/drivers/staging/iio/impedance-analyzer/ad5933.h @@ -0,0 +1,28 @@ +/* + * AD5933 AD5934 Impedance Converter, Network Analyzer + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_ADC_AD5933_H_ +#define IIO_ADC_AD5933_H_ + +/* + * TODO: struct ad5933_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad5933_platform_data - platform specific data + * @ext_clk_Hz: the external clock frequency in Hz, if not set + * the driver uses the internal clock (16.776 MHz) + * @vref_mv: the external reference voltage in millivolt + */ + +struct ad5933_platform_data { + unsigned long ext_clk_Hz; + unsigned short vref_mv; +}; + +#endif /* IIO_ADC_AD5933_H_ */ diff --git a/kernel/drivers/staging/iio/light/Kconfig b/kernel/drivers/staging/iio/light/Kconfig new file mode 100644 index 000000000..ca8d6e66c --- /dev/null +++ b/kernel/drivers/staging/iio/light/Kconfig @@ -0,0 +1,43 @@ +# +# Light sensors +# +menu "Light sensors" + +config SENSORS_ISL29018 + tristate "ISL 29018 light and proximity sensor" + depends on I2C + select REGMAP_I2C + default n + help + If you say yes here you get support for ambient light sensing and + proximity infrared sensing from Intersil ISL29018. + This driver will provide the measurements of ambient light intensity + in lux, proximity infrared sensing and normal infrared sensing. + Data from sensor is accessible via sysfs. + +config SENSORS_ISL29028 + tristate "Intersil ISL29028 Concurrent Light and Proximity Sensor" + depends on I2C + select REGMAP_I2C + help + Provides driver for the Intersil's ISL29028 device. + This driver supports the sysfs interface to get the ALS, IR intensity, + Proximity value via iio. The ISL29028 provides the concurrent sensing + of ambient light and proximity. + +config TSL2583 + tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters" + depends on I2C + help + Provides support for the TAOS tsl2580, tsl2581 and tsl2583 devices. + Access ALS data via iio, sysfs. + +config TSL2x7x + tristate "TAOS TSL/TMD2x71 and TSL/TMD2x72 Family of light and proximity sensors" + depends on I2C + help + Support for: tsl2571, tsl2671, tmd2671, tsl2771, tmd2771, tsl2572, tsl2672, + tmd2672, tsl2772, tmd2772 devices. + Provides iio_events and direct access via sysfs. + +endmenu diff --git a/kernel/drivers/staging/iio/light/Makefile b/kernel/drivers/staging/iio/light/Makefile new file mode 100644 index 000000000..9960fdf7c --- /dev/null +++ b/kernel/drivers/staging/iio/light/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for industrial I/O Light sensors +# + +obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o +obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o +obj-$(CONFIG_TSL2583) += tsl2583.o +obj-$(CONFIG_TSL2x7x) += tsl2x7x_core.o diff --git a/kernel/drivers/staging/iio/light/isl29018.c b/kernel/drivers/staging/iio/light/isl29018.c new file mode 100644 index 000000000..a3489187a --- /dev/null +++ b/kernel/drivers/staging/iio/light/isl29018.c @@ -0,0 +1,815 @@ +/* + * A iio driver for the light sensor ISL 29018/29023/29035. + * + * IIO driver for monitoring ambient light intensity in luxi, proximity + * sensing and infrared sensing. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONVERSION_TIME_MS 100 + +#define ISL29018_REG_ADD_COMMAND1 0x00 +#define COMMMAND1_OPMODE_SHIFT 5 +#define COMMMAND1_OPMODE_MASK (7 << COMMMAND1_OPMODE_SHIFT) +#define COMMMAND1_OPMODE_POWER_DOWN 0 +#define COMMMAND1_OPMODE_ALS_ONCE 1 +#define COMMMAND1_OPMODE_IR_ONCE 2 +#define COMMMAND1_OPMODE_PROX_ONCE 3 + +#define ISL29018_REG_ADD_COMMANDII 0x01 +#define COMMANDII_RESOLUTION_SHIFT 2 +#define COMMANDII_RESOLUTION_MASK (0x3 << COMMANDII_RESOLUTION_SHIFT) + +#define COMMANDII_RANGE_SHIFT 0 +#define COMMANDII_RANGE_MASK (0x3 << COMMANDII_RANGE_SHIFT) + +#define COMMANDII_SCHEME_SHIFT 7 +#define COMMANDII_SCHEME_MASK (0x1 << COMMANDII_SCHEME_SHIFT) + +#define ISL29018_REG_ADD_DATA_LSB 0x02 +#define ISL29018_REG_ADD_DATA_MSB 0x03 + +#define ISL29018_REG_TEST 0x08 +#define ISL29018_TEST_SHIFT 0 +#define ISL29018_TEST_MASK (0xFF << ISL29018_TEST_SHIFT) + +#define ISL29035_REG_DEVICE_ID 0x0F +#define ISL29035_DEVICE_ID_SHIFT 0x03 +#define ISL29035_DEVICE_ID_MASK (0x7 << ISL29035_DEVICE_ID_SHIFT) +#define ISL29035_DEVICE_ID 0x5 +#define ISL29035_BOUT_SHIFT 0x07 +#define ISL29035_BOUT_MASK (0x01 << ISL29035_BOUT_SHIFT) + +struct isl29018_chip { + struct device *dev; + struct regmap *regmap; + struct mutex lock; + int type; + unsigned int lux_scale; + unsigned int lux_uscale; + unsigned int range; + unsigned int adc_bit; + int prox_scheme; + bool suspended; +}; + +static int isl29018_set_range(struct isl29018_chip *chip, unsigned long range, + unsigned int *new_range) +{ + static const unsigned long supp_ranges[] = {1000, 4000, 16000, 64000}; + int i; + + for (i = 0; i < ARRAY_SIZE(supp_ranges); ++i) { + if (range <= supp_ranges[i]) { + *new_range = (unsigned int)supp_ranges[i]; + break; + } + } + + if (i >= ARRAY_SIZE(supp_ranges)) + return -EINVAL; + + return regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMANDII, + COMMANDII_RANGE_MASK, i << COMMANDII_RANGE_SHIFT); +} + +static int isl29018_set_resolution(struct isl29018_chip *chip, + unsigned long adcbit, unsigned int *conf_adc_bit) +{ + static const unsigned long supp_adcbit[] = {16, 12, 8, 4}; + int i; + + for (i = 0; i < ARRAY_SIZE(supp_adcbit); ++i) { + if (adcbit >= supp_adcbit[i]) { + *conf_adc_bit = (unsigned int)supp_adcbit[i]; + break; + } + } + + if (i >= ARRAY_SIZE(supp_adcbit)) + return -EINVAL; + + return regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMANDII, + COMMANDII_RESOLUTION_MASK, + i << COMMANDII_RESOLUTION_SHIFT); +} + +static int isl29018_read_sensor_input(struct isl29018_chip *chip, int mode) +{ + int status; + unsigned int lsb; + unsigned int msb; + + /* Set mode */ + status = regmap_write(chip->regmap, ISL29018_REG_ADD_COMMAND1, + mode << COMMMAND1_OPMODE_SHIFT); + if (status) { + dev_err(chip->dev, + "Error in setting operating mode err %d\n", status); + return status; + } + msleep(CONVERSION_TIME_MS); + status = regmap_read(chip->regmap, ISL29018_REG_ADD_DATA_LSB, &lsb); + if (status < 0) { + dev_err(chip->dev, + "Error in reading LSB DATA with err %d\n", status); + return status; + } + + status = regmap_read(chip->regmap, ISL29018_REG_ADD_DATA_MSB, &msb); + if (status < 0) { + dev_err(chip->dev, + "Error in reading MSB DATA with error %d\n", status); + return status; + } + dev_vdbg(chip->dev, "MSB 0x%x and LSB 0x%x\n", msb, lsb); + + return (msb << 8) | lsb; +} + +static int isl29018_read_lux(struct isl29018_chip *chip, int *lux) +{ + int lux_data; + unsigned int data_x_range, lux_unshifted; + + lux_data = isl29018_read_sensor_input(chip, COMMMAND1_OPMODE_ALS_ONCE); + + if (lux_data < 0) + return lux_data; + + /* To support fractional scaling, separate the unshifted lux + * into two calculations: int scaling and micro-scaling. + * lux_uscale ranges from 0-999999, so about 20 bits. Split + * the /1,000,000 in two to reduce the risk of over/underflow. + */ + data_x_range = lux_data * chip->range; + lux_unshifted = data_x_range * chip->lux_scale; + lux_unshifted += data_x_range / 1000 * chip->lux_uscale / 1000; + *lux = lux_unshifted >> chip->adc_bit; + + return 0; +} + +static int isl29018_read_ir(struct isl29018_chip *chip, int *ir) +{ + int ir_data; + + ir_data = isl29018_read_sensor_input(chip, COMMMAND1_OPMODE_IR_ONCE); + + if (ir_data < 0) + return ir_data; + + *ir = ir_data; + + return 0; +} + +static int isl29018_read_proximity_ir(struct isl29018_chip *chip, int scheme, + int *near_ir) +{ + int status; + int prox_data = -1; + int ir_data = -1; + + /* Do proximity sensing with required scheme */ + status = regmap_update_bits(chip->regmap, ISL29018_REG_ADD_COMMANDII, + COMMANDII_SCHEME_MASK, + scheme << COMMANDII_SCHEME_SHIFT); + if (status) { + dev_err(chip->dev, "Error in setting operating mode\n"); + return status; + } + + prox_data = isl29018_read_sensor_input(chip, + COMMMAND1_OPMODE_PROX_ONCE); + if (prox_data < 0) + return prox_data; + + if (scheme == 1) { + *near_ir = prox_data; + return 0; + } + + ir_data = isl29018_read_sensor_input(chip, COMMMAND1_OPMODE_IR_ONCE); + + if (ir_data < 0) + return ir_data; + + if (prox_data >= ir_data) + *near_ir = prox_data - ir_data; + else + *near_ir = 0; + + return 0; +} + +/* Sysfs interface */ +/* range */ +static ssize_t show_range(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", chip->range); +} + +static ssize_t store_range(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + int status; + unsigned long lval; + unsigned int new_range; + + if (kstrtoul(buf, 10, &lval)) + return -EINVAL; + + if (!(lval == 1000UL || lval == 4000UL || + lval == 16000UL || lval == 64000UL)) { + dev_err(dev, "The range is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + status = isl29018_set_range(chip, lval, &new_range); + if (status < 0) { + mutex_unlock(&chip->lock); + dev_err(dev, + "Error in setting max range with err %d\n", status); + return status; + } + chip->range = new_range; + mutex_unlock(&chip->lock); + + return count; +} + +/* resolution */ +static ssize_t show_resolution(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", chip->adc_bit); +} + +static ssize_t store_resolution(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + int status; + unsigned int val; + unsigned int new_adc_bit; + + if (kstrtouint(buf, 10, &val)) + return -EINVAL; + if (!(val == 4 || val == 8 || val == 12 || val == 16)) { + dev_err(dev, "The resolution is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + status = isl29018_set_resolution(chip, val, &new_adc_bit); + if (status < 0) { + mutex_unlock(&chip->lock); + dev_err(dev, "Error in setting resolution\n"); + return status; + } + chip->adc_bit = new_adc_bit; + mutex_unlock(&chip->lock); + + return count; +} + +/* proximity scheme */ +static ssize_t show_prox_infrared_suppression(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + + /* return the "proximity scheme" i.e. if the chip does on chip + infrared suppression (1 means perform on chip suppression) */ + return sprintf(buf, "%d\n", chip->prox_scheme); +} + +static ssize_t store_prox_infrared_suppression(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + int val; + + if (kstrtoint(buf, 10, &val)) + return -EINVAL; + if (!(val == 0 || val == 1)) { + dev_err(dev, "The mode is not supported\n"); + return -EINVAL; + } + + /* get the "proximity scheme" i.e. if the chip does on chip + infrared suppression (1 means perform on chip suppression) */ + mutex_lock(&chip->lock); + chip->prox_scheme = val; + mutex_unlock(&chip->lock); + + return count; +} + +/* Channel IO */ +static int isl29018_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct isl29018_chip *chip = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&chip->lock); + if (mask == IIO_CHAN_INFO_CALIBSCALE && chan->type == IIO_LIGHT) { + chip->lux_scale = val; + /* With no write_raw_get_fmt(), val2 is a MICRO fraction. */ + chip->lux_uscale = val2; + ret = 0; + } + mutex_unlock(&chip->lock); + + return ret; +} + +static int isl29018_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret = -EINVAL; + struct isl29018_chip *chip = iio_priv(indio_dev); + + mutex_lock(&chip->lock); + if (chip->suspended) { + mutex_unlock(&chip->lock); + return -EBUSY; + } + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = isl29018_read_lux(chip, val); + break; + case IIO_INTENSITY: + ret = isl29018_read_ir(chip, val); + break; + case IIO_PROXIMITY: + ret = isl29018_read_proximity_ir(chip, + chip->prox_scheme, val); + break; + default: + break; + } + if (!ret) + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + *val = chip->lux_scale; + *val2 = chip->lux_uscale; + ret = IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + mutex_unlock(&chip->lock); + return ret; +} + +#define ISL29018_LIGHT_CHANNEL { \ + .type = IIO_LIGHT, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ +} + +#define ISL29018_IR_CHANNEL { \ + .type = IIO_INTENSITY, \ + .modified = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .channel2 = IIO_MOD_LIGHT_IR, \ +} + +#define ISL29018_PROXIMITY_CHANNEL { \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ +} + +static const struct iio_chan_spec isl29018_channels[] = { + ISL29018_LIGHT_CHANNEL, + ISL29018_IR_CHANNEL, + ISL29018_PROXIMITY_CHANNEL, +}; + +static const struct iio_chan_spec isl29023_channels[] = { + ISL29018_LIGHT_CHANNEL, + ISL29018_IR_CHANNEL, +}; + +static IIO_DEVICE_ATTR(range, S_IRUGO | S_IWUSR, show_range, store_range, 0); +static IIO_CONST_ATTR(range_available, "1000 4000 16000 64000"); +static IIO_CONST_ATTR(adc_resolution_available, "4 8 12 16"); +static IIO_DEVICE_ATTR(adc_resolution, S_IRUGO | S_IWUSR, + show_resolution, store_resolution, 0); +static IIO_DEVICE_ATTR(proximity_on_chip_ambient_infrared_suppression, + S_IRUGO | S_IWUSR, + show_prox_infrared_suppression, + store_prox_infrared_suppression, 0); + +#define ISL29018_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr) +#define ISL29018_CONST_ATTR(name) (&iio_const_attr_##name.dev_attr.attr) +static struct attribute *isl29018_attributes[] = { + ISL29018_DEV_ATTR(range), + ISL29018_CONST_ATTR(range_available), + ISL29018_DEV_ATTR(adc_resolution), + ISL29018_CONST_ATTR(adc_resolution_available), + ISL29018_DEV_ATTR(proximity_on_chip_ambient_infrared_suppression), + NULL +}; + +static struct attribute *isl29023_attributes[] = { + ISL29018_DEV_ATTR(range), + ISL29018_CONST_ATTR(range_available), + ISL29018_DEV_ATTR(adc_resolution), + ISL29018_CONST_ATTR(adc_resolution_available), + NULL +}; + +static const struct attribute_group isl29018_group = { + .attrs = isl29018_attributes, +}; + +static const struct attribute_group isl29023_group = { + .attrs = isl29023_attributes, +}; + +static int isl29035_detect(struct isl29018_chip *chip) +{ + int status; + unsigned int id; + + status = regmap_read(chip->regmap, ISL29035_REG_DEVICE_ID, &id); + if (status < 0) { + dev_err(chip->dev, + "Error reading ID register with error %d\n", + status); + return status; + } + + id = (id & ISL29035_DEVICE_ID_MASK) >> ISL29035_DEVICE_ID_SHIFT; + + if (id != ISL29035_DEVICE_ID) + return -ENODEV; + + /* clear out brownout bit */ + return regmap_update_bits(chip->regmap, ISL29035_REG_DEVICE_ID, + ISL29035_BOUT_MASK, 0); +} + +enum { + isl29018, + isl29023, + isl29035, +}; + +static int isl29018_chip_init(struct isl29018_chip *chip) +{ + int status; + unsigned int new_adc_bit; + unsigned int new_range; + + if (chip->type == isl29035) { + status = isl29035_detect(chip); + if (status < 0) + return status; + } + + /* Code added per Intersil Application Note 1534: + * When VDD sinks to approximately 1.8V or below, some of + * the part's registers may change their state. When VDD + * recovers to 2.25V (or greater), the part may thus be in an + * unknown mode of operation. The user can return the part to + * a known mode of operation either by (a) setting VDD = 0V for + * 1 second or more and then powering back up with a slew rate + * of 0.5V/ms or greater, or (b) via I2C disable all ALS/PROX + * conversions, clear the test registers, and then rewrite all + * registers to the desired values. + * ... + * FOR ISL29011, ISL29018, ISL29021, ISL29023 + * 1. Write 0x00 to register 0x08 (TEST) + * 2. Write 0x00 to register 0x00 (CMD1) + * 3. Rewrite all registers to the desired values + * + * ISL29018 Data Sheet (FN6619.1, Feb 11, 2010) essentially says + * the same thing EXCEPT the data sheet asks for a 1ms delay after + * writing the CMD1 register. + */ + status = regmap_write(chip->regmap, ISL29018_REG_TEST, 0x0); + if (status < 0) { + dev_err(chip->dev, "Failed to clear isl29018 TEST reg.(%d)\n", + status); + return status; + } + + /* See Intersil AN1534 comments above. + * "Operating Mode" (COMMAND1) register is reprogrammed when + * data is read from the device. + */ + status = regmap_write(chip->regmap, ISL29018_REG_ADD_COMMAND1, 0); + if (status < 0) { + dev_err(chip->dev, "Failed to clear isl29018 CMD1 reg.(%d)\n", + status); + return status; + } + + usleep_range(1000, 2000); /* per data sheet, page 10 */ + + /* set defaults */ + status = isl29018_set_range(chip, chip->range, &new_range); + if (status < 0) { + dev_err(chip->dev, "Init of isl29018 fails\n"); + return status; + } + + status = isl29018_set_resolution(chip, chip->adc_bit, + &new_adc_bit); + + return 0; +} + +static const struct iio_info isl29018_info = { + .attrs = &isl29018_group, + .driver_module = THIS_MODULE, + .read_raw = &isl29018_read_raw, + .write_raw = &isl29018_write_raw, +}; + +static const struct iio_info isl29023_info = { + .attrs = &isl29023_group, + .driver_module = THIS_MODULE, + .read_raw = &isl29018_read_raw, + .write_raw = &isl29018_write_raw, +}; + +static bool is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL29018_REG_ADD_DATA_LSB: + case ISL29018_REG_ADD_DATA_MSB: + case ISL29018_REG_ADD_COMMAND1: + case ISL29018_REG_TEST: + case ISL29035_REG_DEVICE_ID: + return true; + default: + return false; + } +} + +/* + * isl29018_regmap_config: regmap configuration. + * Use RBTREE mechanism for caching. + */ +static const struct regmap_config isl29018_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = is_volatile_reg, + .max_register = ISL29018_REG_TEST, + .num_reg_defaults_raw = ISL29018_REG_TEST + 1, + .cache_type = REGCACHE_RBTREE, +}; + +/* isl29035_regmap_config: regmap configuration for ISL29035 */ +static const struct regmap_config isl29035_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = is_volatile_reg, + .max_register = ISL29035_REG_DEVICE_ID, + .num_reg_defaults_raw = ISL29035_REG_DEVICE_ID + 1, + .cache_type = REGCACHE_RBTREE, +}; + +struct chip_info { + const struct iio_chan_spec *channels; + int num_channels; + const struct iio_info *indio_info; + const struct regmap_config *regmap_cfg; +}; + +static const struct chip_info chip_info_tbl[] = { + [isl29018] = { + .channels = isl29018_channels, + .num_channels = ARRAY_SIZE(isl29018_channels), + .indio_info = &isl29018_info, + .regmap_cfg = &isl29018_regmap_config, + }, + [isl29023] = { + .channels = isl29023_channels, + .num_channels = ARRAY_SIZE(isl29023_channels), + .indio_info = &isl29023_info, + .regmap_cfg = &isl29018_regmap_config, + }, + [isl29035] = { + .channels = isl29023_channels, + .num_channels = ARRAY_SIZE(isl29023_channels), + .indio_info = &isl29023_info, + .regmap_cfg = &isl29035_regmap_config, + }, +}; + +static const char *isl29018_match_acpi_device(struct device *dev, int *data) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + + if (!id) + return NULL; + + *data = (int) id->driver_data; + + return dev_name(dev); +} + +static int isl29018_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isl29018_chip *chip; + struct iio_dev *indio_dev; + int err; + const char *name = NULL; + int dev_id = 0; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (indio_dev == NULL) { + dev_err(&client->dev, "iio allocation fails\n"); + return -ENOMEM; + } + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + chip->dev = &client->dev; + + if (id) { + name = id->name; + dev_id = id->driver_data; + } + + if (ACPI_HANDLE(&client->dev)) + name = isl29018_match_acpi_device(&client->dev, &dev_id); + + mutex_init(&chip->lock); + + chip->type = dev_id; + chip->lux_scale = 1; + chip->lux_uscale = 0; + chip->range = 1000; + chip->adc_bit = 16; + chip->suspended = false; + + chip->regmap = devm_regmap_init_i2c(client, + chip_info_tbl[dev_id].regmap_cfg); + if (IS_ERR(chip->regmap)) { + err = PTR_ERR(chip->regmap); + dev_err(chip->dev, "regmap initialization failed: %d\n", err); + return err; + } + + err = isl29018_chip_init(chip); + if (err) + return err; + + indio_dev->info = chip_info_tbl[dev_id].indio_info; + indio_dev->channels = chip_info_tbl[dev_id].channels; + indio_dev->num_channels = chip_info_tbl[dev_id].num_channels; + indio_dev->name = name; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + err = devm_iio_device_register(&client->dev, indio_dev); + if (err) { + dev_err(&client->dev, "iio registration fails\n"); + return err; + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int isl29018_suspend(struct device *dev) +{ + struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev)); + + mutex_lock(&chip->lock); + + /* Since this driver uses only polling commands, we are by default in + * auto shutdown (ie, power-down) mode. + * So we do not have much to do here. + */ + chip->suspended = true; + + mutex_unlock(&chip->lock); + return 0; +} + +static int isl29018_resume(struct device *dev) +{ + struct isl29018_chip *chip = iio_priv(dev_get_drvdata(dev)); + int err; + + mutex_lock(&chip->lock); + + err = isl29018_chip_init(chip); + if (!err) + chip->suspended = false; + + mutex_unlock(&chip->lock); + return err; +} + +static SIMPLE_DEV_PM_OPS(isl29018_pm_ops, isl29018_suspend, isl29018_resume); +#define ISL29018_PM_OPS (&isl29018_pm_ops) +#else +#define ISL29018_PM_OPS NULL +#endif + +static const struct acpi_device_id isl29018_acpi_match[] = { + {"ISL29018", isl29018}, + {"ISL29023", isl29023}, + {"ISL29035", isl29035}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, isl29018_acpi_match); + +static const struct i2c_device_id isl29018_id[] = { + {"isl29018", isl29018}, + {"isl29023", isl29023}, + {"isl29035", isl29035}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, isl29018_id); + +static const struct of_device_id isl29018_of_match[] = { + { .compatible = "isil,isl29018", }, + { .compatible = "isil,isl29023", }, + { .compatible = "isil,isl29035", }, + { }, +}; +MODULE_DEVICE_TABLE(of, isl29018_of_match); + +static struct i2c_driver isl29018_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "isl29018", + .acpi_match_table = ACPI_PTR(isl29018_acpi_match), + .pm = ISL29018_PM_OPS, + .owner = THIS_MODULE, + .of_match_table = isl29018_of_match, + }, + .probe = isl29018_probe, + .id_table = isl29018_id, +}; +module_i2c_driver(isl29018_driver); + +MODULE_DESCRIPTION("ISL29018 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/staging/iio/light/isl29028.c b/kernel/drivers/staging/iio/light/isl29028.c new file mode 100644 index 000000000..e5b2fdc23 --- /dev/null +++ b/kernel/drivers/staging/iio/light/isl29028.c @@ -0,0 +1,562 @@ +/* + * IIO driver for the light sensor ISL29028. + * ISL29028 is Concurrent Ambient Light and Proximity Sensor + * + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONVERSION_TIME_MS 100 + +#define ISL29028_REG_CONFIGURE 0x01 + +#define CONFIGURE_ALS_IR_MODE_ALS 0 +#define CONFIGURE_ALS_IR_MODE_IR BIT(0) +#define CONFIGURE_ALS_IR_MODE_MASK BIT(0) + +#define CONFIGURE_ALS_RANGE_LOW_LUX 0 +#define CONFIGURE_ALS_RANGE_HIGH_LUX BIT(1) +#define CONFIGURE_ALS_RANGE_MASK BIT(1) + +#define CONFIGURE_ALS_DIS 0 +#define CONFIGURE_ALS_EN BIT(2) +#define CONFIGURE_ALS_EN_MASK BIT(2) + +#define CONFIGURE_PROX_DRIVE BIT(3) + +#define CONFIGURE_PROX_SLP_SH 4 +#define CONFIGURE_PROX_SLP_MASK (7 << CONFIGURE_PROX_SLP_SH) + +#define CONFIGURE_PROX_EN BIT(7) +#define CONFIGURE_PROX_EN_MASK BIT(7) + +#define ISL29028_REG_INTERRUPT 0x02 + +#define ISL29028_REG_PROX_DATA 0x08 +#define ISL29028_REG_ALSIR_L 0x09 +#define ISL29028_REG_ALSIR_U 0x0A + +#define ISL29028_REG_TEST1_MODE 0x0E +#define ISL29028_REG_TEST2_MODE 0x0F + +#define ISL29028_NUM_REGS (ISL29028_REG_TEST2_MODE + 1) + +enum als_ir_mode { + MODE_NONE = 0, + MODE_ALS, + MODE_IR +}; + +struct isl29028_chip { + struct device *dev; + struct mutex lock; + struct regmap *regmap; + + unsigned int prox_sampling; + bool enable_prox; + + int lux_scale; + int als_ir_mode; +}; + +static int isl29028_set_proxim_sampling(struct isl29028_chip *chip, + unsigned int sampling) +{ + static unsigned int prox_period[] = {800, 400, 200, 100, 75, 50, 12, 0}; + int sel; + unsigned int period = DIV_ROUND_UP(1000, sampling); + + for (sel = 0; sel < ARRAY_SIZE(prox_period); ++sel) { + if (period >= prox_period[sel]) + break; + } + return regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + CONFIGURE_PROX_SLP_MASK, sel << CONFIGURE_PROX_SLP_SH); +} + +static int isl29028_enable_proximity(struct isl29028_chip *chip, bool enable) +{ + int ret; + int val = 0; + + if (enable) + val = CONFIGURE_PROX_EN; + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + CONFIGURE_PROX_EN_MASK, val); + if (ret < 0) + return ret; + + /* Wait for conversion to be complete for first sample */ + mdelay(DIV_ROUND_UP(1000, chip->prox_sampling)); + return 0; +} + +static int isl29028_set_als_scale(struct isl29028_chip *chip, int lux_scale) +{ + int val = (lux_scale == 2000) ? CONFIGURE_ALS_RANGE_HIGH_LUX : + CONFIGURE_ALS_RANGE_LOW_LUX; + + return regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + CONFIGURE_ALS_RANGE_MASK, val); +} + +static int isl29028_set_als_ir_mode(struct isl29028_chip *chip, + enum als_ir_mode mode) +{ + int ret = 0; + + switch (mode) { + case MODE_ALS: + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + CONFIGURE_ALS_IR_MODE_MASK, CONFIGURE_ALS_IR_MODE_ALS); + if (ret < 0) + return ret; + + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + CONFIGURE_ALS_RANGE_MASK, CONFIGURE_ALS_RANGE_HIGH_LUX); + break; + + case MODE_IR: + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + CONFIGURE_ALS_IR_MODE_MASK, CONFIGURE_ALS_IR_MODE_IR); + break; + + case MODE_NONE: + return regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + CONFIGURE_ALS_EN_MASK, CONFIGURE_ALS_DIS); + } + + if (ret < 0) + return ret; + + /* Enable the ALS/IR */ + ret = regmap_update_bits(chip->regmap, ISL29028_REG_CONFIGURE, + CONFIGURE_ALS_EN_MASK, CONFIGURE_ALS_EN); + if (ret < 0) + return ret; + + /* Need to wait for conversion time if ALS/IR mode enabled */ + mdelay(CONVERSION_TIME_MS); + return 0; +} + +static int isl29028_read_als_ir(struct isl29028_chip *chip, int *als_ir) +{ + unsigned int lsb; + unsigned int msb; + int ret; + + ret = regmap_read(chip->regmap, ISL29028_REG_ALSIR_L, &lsb); + if (ret < 0) { + dev_err(chip->dev, + "Error in reading register ALSIR_L err %d\n", ret); + return ret; + } + + ret = regmap_read(chip->regmap, ISL29028_REG_ALSIR_U, &msb); + if (ret < 0) { + dev_err(chip->dev, + "Error in reading register ALSIR_U err %d\n", ret); + return ret; + } + + *als_ir = ((msb & 0xF) << 8) | (lsb & 0xFF); + return 0; +} + +static int isl29028_read_proxim(struct isl29028_chip *chip, int *prox) +{ + unsigned int data; + int ret; + + ret = regmap_read(chip->regmap, ISL29028_REG_PROX_DATA, &data); + if (ret < 0) { + dev_err(chip->dev, "Error in reading register %d, error %d\n", + ISL29028_REG_PROX_DATA, ret); + return ret; + } + *prox = data; + return 0; +} + +static int isl29028_proxim_get(struct isl29028_chip *chip, int *prox_data) +{ + int ret; + + if (!chip->enable_prox) { + ret = isl29028_enable_proximity(chip, true); + if (ret < 0) + return ret; + chip->enable_prox = true; + } + return isl29028_read_proxim(chip, prox_data); +} + +static int isl29028_als_get(struct isl29028_chip *chip, int *als_data) +{ + int ret; + int als_ir_data; + + if (chip->als_ir_mode != MODE_ALS) { + ret = isl29028_set_als_ir_mode(chip, MODE_ALS); + if (ret < 0) { + dev_err(chip->dev, + "Error in enabling ALS mode err %d\n", ret); + return ret; + } + chip->als_ir_mode = MODE_ALS; + } + + ret = isl29028_read_als_ir(chip, &als_ir_data); + if (ret < 0) + return ret; + + /* + * convert als data count to lux. + * if lux_scale = 125, lux = count * 0.031 + * if lux_scale = 2000, lux = count * 0.49 + */ + if (chip->lux_scale == 125) + als_ir_data = (als_ir_data * 31) / 1000; + else + als_ir_data = (als_ir_data * 49) / 100; + + *als_data = als_ir_data; + return 0; +} + +static int isl29028_ir_get(struct isl29028_chip *chip, int *ir_data) +{ + int ret; + + if (chip->als_ir_mode != MODE_IR) { + ret = isl29028_set_als_ir_mode(chip, MODE_IR); + if (ret < 0) { + dev_err(chip->dev, + "Error in enabling IR mode err %d\n", ret); + return ret; + } + chip->als_ir_mode = MODE_IR; + } + return isl29028_read_als_ir(chip, ir_data); +} + +/* Channel IO */ +static int isl29028_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct isl29028_chip *chip = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&chip->lock); + switch (chan->type) { + case IIO_PROXIMITY: + if (mask != IIO_CHAN_INFO_SAMP_FREQ) { + dev_err(chip->dev, + "proximity: mask value 0x%08lx not supported\n", + mask); + break; + } + if (val < 1 || val > 100) { + dev_err(chip->dev, + "Samp_freq %d is not in range[1:100]\n", val); + break; + } + ret = isl29028_set_proxim_sampling(chip, val); + if (ret < 0) { + dev_err(chip->dev, + "Setting proximity samp_freq fail, err %d\n", + ret); + break; + } + chip->prox_sampling = val; + break; + + case IIO_LIGHT: + if (mask != IIO_CHAN_INFO_SCALE) { + dev_err(chip->dev, + "light: mask value 0x%08lx not supported\n", + mask); + break; + } + if ((val != 125) && (val != 2000)) { + dev_err(chip->dev, + "lux scale %d is invalid [125, 2000]\n", val); + break; + } + ret = isl29028_set_als_scale(chip, val); + if (ret < 0) { + dev_err(chip->dev, + "Setting lux scale fail with error %d\n", ret); + break; + } + chip->lux_scale = val; + break; + + default: + dev_err(chip->dev, "Unsupported channel type\n"); + break; + } + mutex_unlock(&chip->lock); + return ret; +} + +static int isl29028_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct isl29028_chip *chip = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&chip->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + ret = isl29028_als_get(chip, val); + break; + case IIO_INTENSITY: + ret = isl29028_ir_get(chip, val); + break; + case IIO_PROXIMITY: + ret = isl29028_proxim_get(chip, val); + break; + default: + break; + } + if (ret < 0) + break; + ret = IIO_VAL_INT; + break; + + case IIO_CHAN_INFO_SAMP_FREQ: + if (chan->type != IIO_PROXIMITY) + break; + *val = chip->prox_sampling; + ret = IIO_VAL_INT; + break; + + case IIO_CHAN_INFO_SCALE: + if (chan->type != IIO_LIGHT) + break; + *val = chip->lux_scale; + ret = IIO_VAL_INT; + break; + + default: + dev_err(chip->dev, "mask value 0x%08lx not supported\n", mask); + break; + } + mutex_unlock(&chip->lock); + return ret; +} + +static IIO_CONST_ATTR(in_proximity_sampling_frequency_available, + "1, 3, 5, 10, 13, 20, 83, 100"); +static IIO_CONST_ATTR(in_illuminance_scale_available, "125, 2000"); + +#define ISL29028_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr) +#define ISL29028_CONST_ATTR(name) (&iio_const_attr_##name.dev_attr.attr) +static struct attribute *isl29028_attributes[] = { + ISL29028_CONST_ATTR(in_proximity_sampling_frequency_available), + ISL29028_CONST_ATTR(in_illuminance_scale_available), + NULL, +}; + +static const struct attribute_group isl29108_group = { + .attrs = isl29028_attributes, +}; + +static const struct iio_chan_spec isl29028_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE), + }, { + .type = IIO_INTENSITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + } +}; + +static const struct iio_info isl29028_info = { + .attrs = &isl29108_group, + .driver_module = THIS_MODULE, + .read_raw = &isl29028_read_raw, + .write_raw = &isl29028_write_raw, +}; + +static int isl29028_chip_init(struct isl29028_chip *chip) +{ + int ret; + + chip->enable_prox = false; + chip->prox_sampling = 20; + chip->lux_scale = 2000; + chip->als_ir_mode = MODE_NONE; + + ret = regmap_write(chip->regmap, ISL29028_REG_TEST1_MODE, 0x0); + if (ret < 0) { + dev_err(chip->dev, "%s(): write to reg %d failed, err = %d\n", + __func__, ISL29028_REG_TEST1_MODE, ret); + return ret; + } + ret = regmap_write(chip->regmap, ISL29028_REG_TEST2_MODE, 0x0); + if (ret < 0) { + dev_err(chip->dev, "%s(): write to reg %d failed, err = %d\n", + __func__, ISL29028_REG_TEST2_MODE, ret); + return ret; + } + + ret = regmap_write(chip->regmap, ISL29028_REG_CONFIGURE, 0x0); + if (ret < 0) { + dev_err(chip->dev, "%s(): write to reg %d failed, err = %d\n", + __func__, ISL29028_REG_CONFIGURE, ret); + return ret; + } + + ret = isl29028_set_proxim_sampling(chip, chip->prox_sampling); + if (ret < 0) { + dev_err(chip->dev, "setting the proximity, err = %d\n", + ret); + return ret; + } + + ret = isl29028_set_als_scale(chip, chip->lux_scale); + if (ret < 0) + dev_err(chip->dev, + "setting als scale failed, err = %d\n", ret); + return ret; +} + +static bool is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ISL29028_REG_INTERRUPT: + case ISL29028_REG_PROX_DATA: + case ISL29028_REG_ALSIR_L: + case ISL29028_REG_ALSIR_U: + return true; + default: + return false; + } +} + +static const struct regmap_config isl29028_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = is_volatile_reg, + .max_register = ISL29028_NUM_REGS - 1, + .num_reg_defaults_raw = ISL29028_NUM_REGS, + .cache_type = REGCACHE_RBTREE, +}; + +static int isl29028_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isl29028_chip *chip; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) { + dev_err(&client->dev, "iio allocation fails\n"); + return -ENOMEM; + } + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + chip->dev = &client->dev; + mutex_init(&chip->lock); + + chip->regmap = devm_regmap_init_i2c(client, &isl29028_regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + dev_err(chip->dev, "regmap initialization failed: %d\n", ret); + return ret; + } + + ret = isl29028_chip_init(chip); + if (ret < 0) { + dev_err(chip->dev, "chip initialization failed: %d\n", ret); + return ret; + } + + indio_dev->info = &isl29028_info; + indio_dev->channels = isl29028_channels; + indio_dev->num_channels = ARRAY_SIZE(isl29028_channels); + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(chip->dev, "iio registration fails with error %d\n", + ret); + return ret; + } + return 0; +} + +static int isl29028_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + return 0; +} + +static const struct i2c_device_id isl29028_id[] = { + {"isl29028", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, isl29028_id); + +static const struct of_device_id isl29028_of_match[] = { + { .compatible = "isl,isl29028", }, /* for backward compat., don't use */ + { .compatible = "isil,isl29028", }, + { }, +}; +MODULE_DEVICE_TABLE(of, isl29028_of_match); + +static struct i2c_driver isl29028_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "isl29028", + .owner = THIS_MODULE, + .of_match_table = isl29028_of_match, + }, + .probe = isl29028_probe, + .remove = isl29028_remove, + .id_table = isl29028_id, +}; + +module_i2c_driver(isl29028_driver); + +MODULE_DESCRIPTION("ISL29028 Ambient Light and Proximity Sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Laxman Dewangan "); diff --git a/kernel/drivers/staging/iio/light/tsl2583.c b/kernel/drivers/staging/iio/light/tsl2583.c new file mode 100644 index 000000000..b5e1b8b0a --- /dev/null +++ b/kernel/drivers/staging/iio/light/tsl2583.c @@ -0,0 +1,949 @@ +/* + * Device driver for monitoring ambient light intensity (lux) + * within the TAOS tsl258x family of devices (tsl2580, tsl2581). + * + * Copyright (c) 2011, TAOS 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TSL258X_MAX_DEVICE_REGS 32 + +/* Triton register offsets */ +#define TSL258X_REG_MAX 8 + +/* Device Registers and Masks */ +#define TSL258X_CNTRL 0x00 +#define TSL258X_ALS_TIME 0X01 +#define TSL258X_INTERRUPT 0x02 +#define TSL258X_GAIN 0x07 +#define TSL258X_REVID 0x11 +#define TSL258X_CHIPID 0x12 +#define TSL258X_ALS_CHAN0LO 0x14 +#define TSL258X_ALS_CHAN0HI 0x15 +#define TSL258X_ALS_CHAN1LO 0x16 +#define TSL258X_ALS_CHAN1HI 0x17 +#define TSL258X_TMR_LO 0x18 +#define TSL258X_TMR_HI 0x19 + +/* tsl2583 cmd reg masks */ +#define TSL258X_CMD_REG 0x80 +#define TSL258X_CMD_SPL_FN 0x60 +#define TSL258X_CMD_ALS_INT_CLR 0X01 + +/* tsl2583 cntrl reg masks */ +#define TSL258X_CNTL_ADC_ENBL 0x02 +#define TSL258X_CNTL_PWR_ON 0x01 + +/* tsl2583 status reg masks */ +#define TSL258X_STA_ADC_VALID 0x01 +#define TSL258X_STA_ADC_INTR 0x10 + +/* Lux calculation constants */ +#define TSL258X_LUX_CALC_OVER_FLOW 65535 + +enum { + TSL258X_CHIP_UNKNOWN = 0, + TSL258X_CHIP_WORKING = 1, + TSL258X_CHIP_SUSPENDED = 2 +}; + +/* Per-device data */ +struct taos_als_info { + u16 als_ch0; + u16 als_ch1; + u16 lux; +}; + +struct taos_settings { + int als_time; + int als_gain; + int als_gain_trim; + int als_cal_target; +}; + +struct tsl2583_chip { + struct mutex als_mutex; + struct i2c_client *client; + struct taos_als_info als_cur_info; + struct taos_settings taos_settings; + int als_time_scale; + int als_saturation; + int taos_chip_status; + u8 taos_config[8]; +}; + +/* + * Initial values for device - this values can/will be changed by driver. + * and applications as needed. + * These values are dynamic. + */ +static const u8 taos_config[8] = { + 0x00, 0xee, 0x00, 0x03, 0x00, 0xFF, 0xFF, 0x00 +}; /* cntrl atime intC Athl0 Athl1 Athh0 Athh1 gain */ + +struct taos_lux { + unsigned int ratio; + unsigned int ch0; + unsigned int ch1; +}; + +/* This structure is intentionally large to accommodate updates via sysfs. */ +/* Sized to 11 = max 10 segments + 1 termination segment */ +/* Assumption is one and only one type of glass used */ +static struct taos_lux taos_device_lux[11] = { + { 9830, 8520, 15729 }, + { 12452, 10807, 23344 }, + { 14746, 6383, 11705 }, + { 17695, 4063, 6554 }, +}; + +struct gainadj { + s16 ch0; + s16 ch1; +}; + +/* Index = (0 - 3) Used to validate the gain selection index */ +static const struct gainadj gainadj[] = { + { 1, 1 }, + { 8, 8 }, + { 16, 16 }, + { 107, 115 } +}; + +/* + * Provides initial operational parameter defaults. + * These defaults may be changed through the device's sysfs files. + */ +static void taos_defaults(struct tsl2583_chip *chip) +{ + /* Operational parameters */ + chip->taos_settings.als_time = 100; + /* must be a multiple of 50mS */ + chip->taos_settings.als_gain = 0; + /* this is actually an index into the gain table */ + /* assume clear glass as default */ + chip->taos_settings.als_gain_trim = 1000; + /* default gain trim to account for aperture effects */ + chip->taos_settings.als_cal_target = 130; + /* Known external ALS reading used for calibration */ +} + +/* + * Read a number of bytes starting at register (reg) location. + * Return 0, or i2c_smbus_write_byte ERROR code. + */ +static int +taos_i2c_read(struct i2c_client *client, u8 reg, u8 *val, unsigned int len) +{ + int i, ret; + + for (i = 0; i < len; i++) { + /* select register to write */ + ret = i2c_smbus_write_byte(client, (TSL258X_CMD_REG | reg)); + if (ret < 0) { + dev_err(&client->dev, + "taos_i2c_read failed to write register %x\n", + reg); + return ret; + } + /* read the data */ + *val = i2c_smbus_read_byte(client); + val++; + reg++; + } + return 0; +} + +/* + * Reads and calculates current lux value. + * The raw ch0 and ch1 values of the ambient light sensed in the last + * integration cycle are read from the device. + * Time scale factor array values are adjusted based on the integration time. + * The raw values are multiplied by a scale factor, and device gain is obtained + * using gain index. Limit checks are done next, then the ratio of a multiple + * of ch1 value, to the ch0 value, is calculated. The array taos_device_lux[] + * declared above is then scanned to find the first ratio value that is just + * above the ratio we just calculated. The ch0 and ch1 multiplier constants in + * the array are then used along with the time scale factor array values, to + * calculate the lux. + */ +static int taos_get_lux(struct iio_dev *indio_dev) +{ + u16 ch0, ch1; /* separated ch0/ch1 data from device */ + u32 lux; /* raw lux calculated from device data */ + u64 lux64; + u32 ratio; + u8 buf[5]; + struct taos_lux *p; + struct tsl2583_chip *chip = iio_priv(indio_dev); + int i, ret; + u32 ch0lux = 0; + u32 ch1lux = 0; + + if (mutex_trylock(&chip->als_mutex) == 0) { + dev_info(&chip->client->dev, "taos_get_lux device is busy\n"); + return chip->als_cur_info.lux; /* busy, so return LAST VALUE */ + } + + if (chip->taos_chip_status != TSL258X_CHIP_WORKING) { + /* device is not enabled */ + dev_err(&chip->client->dev, "taos_get_lux device is not enabled\n"); + ret = -EBUSY; + goto out_unlock; + } + + ret = taos_i2c_read(chip->client, (TSL258X_CMD_REG), &buf[0], 1); + if (ret < 0) { + dev_err(&chip->client->dev, "taos_get_lux failed to read CMD_REG\n"); + goto out_unlock; + } + /* is data new & valid */ + if (!(buf[0] & TSL258X_STA_ADC_INTR)) { + dev_err(&chip->client->dev, "taos_get_lux data not valid\n"); + ret = chip->als_cur_info.lux; /* return LAST VALUE */ + goto out_unlock; + } + + for (i = 0; i < 4; i++) { + int reg = TSL258X_CMD_REG | (TSL258X_ALS_CHAN0LO + i); + + ret = taos_i2c_read(chip->client, reg, &buf[i], 1); + if (ret < 0) { + dev_err(&chip->client->dev, + "taos_get_lux failed to read register %x\n", + reg); + goto out_unlock; + } + } + + /* clear status, really interrupt status (interrupts are off), but + * we use the bit anyway - don't forget 0x80 - this is a command*/ + ret = i2c_smbus_write_byte(chip->client, + (TSL258X_CMD_REG | TSL258X_CMD_SPL_FN | + TSL258X_CMD_ALS_INT_CLR)); + + if (ret < 0) { + dev_err(&chip->client->dev, + "taos_i2c_write_command failed in taos_get_lux, err = %d\n", + ret); + goto out_unlock; /* have no data, so return failure */ + } + + /* extract ALS/lux data */ + ch0 = le16_to_cpup((const __le16 *)&buf[0]); + ch1 = le16_to_cpup((const __le16 *)&buf[2]); + + chip->als_cur_info.als_ch0 = ch0; + chip->als_cur_info.als_ch1 = ch1; + + if ((ch0 >= chip->als_saturation) || (ch1 >= chip->als_saturation)) + goto return_max; + + if (ch0 == 0) { + /* have no data, so return LAST VALUE */ + ret = chip->als_cur_info.lux = 0; + goto out_unlock; + } + /* calculate ratio */ + ratio = (ch1 << 15) / ch0; + /* convert to unscaled lux using the pointer to the table */ + for (p = (struct taos_lux *) taos_device_lux; + p->ratio != 0 && p->ratio < ratio; p++) + ; + + if (p->ratio == 0) { + lux = 0; + } else { + ch0lux = ((ch0 * p->ch0) + + (gainadj[chip->taos_settings.als_gain].ch0 >> 1)) + / gainadj[chip->taos_settings.als_gain].ch0; + ch1lux = ((ch1 * p->ch1) + + (gainadj[chip->taos_settings.als_gain].ch1 >> 1)) + / gainadj[chip->taos_settings.als_gain].ch1; + lux = ch0lux - ch1lux; + } + + /* note: lux is 31 bit max at this point */ + if (ch1lux > ch0lux) { + dev_dbg(&chip->client->dev, "No Data - Return last value\n"); + ret = chip->als_cur_info.lux = 0; + goto out_unlock; + } + + /* adjust for active time scale */ + if (chip->als_time_scale == 0) + lux = 0; + else + lux = (lux + (chip->als_time_scale >> 1)) / + chip->als_time_scale; + + /* Adjust for active gain scale. + * The taos_device_lux tables above have a factor of 8192 built in, + * so we need to shift right. + * User-specified gain provides a multiplier. + * Apply user-specified gain before shifting right to retain precision. + * Use 64 bits to avoid overflow on multiplication. + * Then go back to 32 bits before division to avoid using div_u64(). + */ + lux64 = lux; + lux64 = lux64 * chip->taos_settings.als_gain_trim; + lux64 >>= 13; + lux = lux64; + lux = (lux + 500) / 1000; + if (lux > TSL258X_LUX_CALC_OVER_FLOW) { /* check for overflow */ +return_max: + lux = TSL258X_LUX_CALC_OVER_FLOW; + } + + /* Update the structure with the latest VALID lux. */ + chip->als_cur_info.lux = lux; + ret = lux; + +out_unlock: + mutex_unlock(&chip->als_mutex); + return ret; +} + +/* + * Obtain single reading and calculate the als_gain_trim (later used + * to derive actual lux). + * Return updated gain_trim value. + */ +static int taos_als_calibrate(struct iio_dev *indio_dev) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + u8 reg_val; + unsigned int gain_trim_val; + int ret; + int lux_val; + + ret = i2c_smbus_write_byte(chip->client, + (TSL258X_CMD_REG | TSL258X_CNTRL)); + if (ret < 0) { + dev_err(&chip->client->dev, + "taos_als_calibrate failed to reach the CNTRL register, ret=%d\n", + ret); + return ret; + } + + reg_val = i2c_smbus_read_byte(chip->client); + if ((reg_val & (TSL258X_CNTL_ADC_ENBL | TSL258X_CNTL_PWR_ON)) + != (TSL258X_CNTL_ADC_ENBL | TSL258X_CNTL_PWR_ON)) { + dev_err(&chip->client->dev, + "taos_als_calibrate failed: device not powered on with ADC enabled\n"); + return -1; + } + + ret = i2c_smbus_write_byte(chip->client, + (TSL258X_CMD_REG | TSL258X_CNTRL)); + if (ret < 0) { + dev_err(&chip->client->dev, + "taos_als_calibrate failed to reach the STATUS register, ret=%d\n", + ret); + return ret; + } + reg_val = i2c_smbus_read_byte(chip->client); + + if ((reg_val & TSL258X_STA_ADC_VALID) != TSL258X_STA_ADC_VALID) { + dev_err(&chip->client->dev, + "taos_als_calibrate failed: STATUS - ADC not valid.\n"); + return -ENODATA; + } + lux_val = taos_get_lux(indio_dev); + if (lux_val < 0) { + dev_err(&chip->client->dev, "taos_als_calibrate failed to get lux\n"); + return lux_val; + } + gain_trim_val = (unsigned int) (((chip->taos_settings.als_cal_target) + * chip->taos_settings.als_gain_trim) / lux_val); + + if ((gain_trim_val < 250) || (gain_trim_val > 4000)) { + dev_err(&chip->client->dev, + "taos_als_calibrate failed: trim_val of %d is out of range\n", + gain_trim_val); + return -ENODATA; + } + chip->taos_settings.als_gain_trim = (int) gain_trim_val; + + return (int) gain_trim_val; +} + +/* + * Turn the device on. + * Configuration must be set before calling this function. + */ +static int taos_chip_on(struct iio_dev *indio_dev) +{ + int i; + int ret; + u8 *uP; + u8 utmp; + int als_count; + int als_time; + struct tsl2583_chip *chip = iio_priv(indio_dev); + + /* and make sure we're not already on */ + if (chip->taos_chip_status == TSL258X_CHIP_WORKING) { + /* if forcing a register update - turn off, then on */ + dev_info(&chip->client->dev, "device is already enabled\n"); + return -EINVAL; + } + + /* determine als integration register */ + als_count = (chip->taos_settings.als_time * 100 + 135) / 270; + if (als_count == 0) + als_count = 1; /* ensure at least one cycle */ + + /* convert back to time (encompasses overrides) */ + als_time = (als_count * 27 + 5) / 10; + chip->taos_config[TSL258X_ALS_TIME] = 256 - als_count; + + /* Set the gain based on taos_settings struct */ + chip->taos_config[TSL258X_GAIN] = chip->taos_settings.als_gain; + + /* set chip struct re scaling and saturation */ + chip->als_saturation = als_count * 922; /* 90% of full scale */ + chip->als_time_scale = (als_time + 25) / 50; + + /* TSL258x Specific power-on / adc enable sequence + * Power on the device 1st. */ + utmp = TSL258X_CNTL_PWR_ON; + ret = i2c_smbus_write_byte_data(chip->client, + TSL258X_CMD_REG | TSL258X_CNTRL, utmp); + if (ret < 0) { + dev_err(&chip->client->dev, "taos_chip_on failed on CNTRL reg.\n"); + return ret; + } + + /* Use the following shadow copy for our delay before enabling ADC. + * Write all the registers. */ + for (i = 0, uP = chip->taos_config; i < TSL258X_REG_MAX; i++) { + ret = i2c_smbus_write_byte_data(chip->client, + TSL258X_CMD_REG + i, + *uP++); + if (ret < 0) { + dev_err(&chip->client->dev, + "taos_chip_on failed on reg %d.\n", i); + return ret; + } + } + + usleep_range(3000, 3500); + /* NOW enable the ADC + * initialize the desired mode of operation */ + utmp = TSL258X_CNTL_PWR_ON | TSL258X_CNTL_ADC_ENBL; + ret = i2c_smbus_write_byte_data(chip->client, + TSL258X_CMD_REG | TSL258X_CNTRL, + utmp); + if (ret < 0) { + dev_err(&chip->client->dev, "taos_chip_on failed on 2nd CTRL reg.\n"); + return ret; + } + chip->taos_chip_status = TSL258X_CHIP_WORKING; + + return ret; +} + +static int taos_chip_off(struct iio_dev *indio_dev) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + + /* turn device off */ + chip->taos_chip_status = TSL258X_CHIP_SUSPENDED; + return i2c_smbus_write_byte_data(chip->client, + TSL258X_CMD_REG | TSL258X_CNTRL, + 0x00); +} + +/* Sysfs Interface Functions */ + +static ssize_t taos_power_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->taos_chip_status); +} + +static ssize_t taos_power_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); + int value; + + if (kstrtoint(buf, 0, &value)) + return -EINVAL; + + if (value == 0) + taos_chip_off(indio_dev); + else + taos_chip_on(indio_dev); + + return len; +} + +static ssize_t taos_gain_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + char gain[4] = {0}; + + switch (chip->taos_settings.als_gain) { + case 0: + strcpy(gain, "001"); + break; + case 1: + strcpy(gain, "008"); + break; + case 2: + strcpy(gain, "016"); + break; + case 3: + strcpy(gain, "111"); + break; + } + + return sprintf(buf, "%s\n", gain); +} + +static ssize_t taos_gain_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 tsl2583_chip *chip = iio_priv(indio_dev); + int value; + + if (kstrtoint(buf, 0, &value)) + return -EINVAL; + + switch (value) { + case 1: + chip->taos_settings.als_gain = 0; + break; + case 8: + chip->taos_settings.als_gain = 1; + break; + case 16: + chip->taos_settings.als_gain = 2; + break; + case 111: + chip->taos_settings.als_gain = 3; + break; + default: + dev_err(dev, "Invalid Gain Index (must be 1,8,16,111)\n"); + return -1; + } + + return len; +} + +static ssize_t taos_gain_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", "1 8 16 111"); +} + +static ssize_t taos_als_time_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->taos_settings.als_time); +} + +static ssize_t taos_als_time_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 tsl2583_chip *chip = iio_priv(indio_dev); + int value; + + if (kstrtoint(buf, 0, &value)) + return -EINVAL; + + if ((value < 50) || (value > 650)) + return -EINVAL; + + if (value % 50) + return -EINVAL; + + chip->taos_settings.als_time = value; + + return len; +} + +static ssize_t taos_als_time_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", + "50 100 150 200 250 300 350 400 450 500 550 600 650"); +} + +static ssize_t taos_als_trim_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->taos_settings.als_gain_trim); +} + +static ssize_t taos_als_trim_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 tsl2583_chip *chip = iio_priv(indio_dev); + int value; + + if (kstrtoint(buf, 0, &value)) + return -EINVAL; + + if (value) + chip->taos_settings.als_gain_trim = value; + + return len; +} + +static ssize_t taos_als_cal_target_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->taos_settings.als_cal_target); +} + +static ssize_t taos_als_cal_target_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 tsl2583_chip *chip = iio_priv(indio_dev); + int value; + + if (kstrtoint(buf, 0, &value)) + return -EINVAL; + + if (value) + chip->taos_settings.als_cal_target = value; + + return len; +} + +static ssize_t taos_lux_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = taos_get_lux(dev_to_iio_dev(dev)); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t taos_do_calibrate(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + int value; + + if (kstrtoint(buf, 0, &value)) + return -EINVAL; + + if (value == 1) + taos_als_calibrate(indio_dev); + + return len; +} + +static ssize_t taos_luxtable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + int offset = 0; + + for (i = 0; i < ARRAY_SIZE(taos_device_lux); i++) { + offset += sprintf(buf + offset, "%u,%u,%u,", + taos_device_lux[i].ratio, + taos_device_lux[i].ch0, + taos_device_lux[i].ch1); + if (taos_device_lux[i].ratio == 0) { + /* We just printed the first "0" entry. + * Now get rid of the extra "," and break. */ + offset--; + break; + } + } + + offset += sprintf(buf + offset, "\n"); + return offset; +} + +static ssize_t taos_luxtable_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 tsl2583_chip *chip = iio_priv(indio_dev); + int value[ARRAY_SIZE(taos_device_lux)*3 + 1]; + int n; + + get_options(buf, ARRAY_SIZE(value), value); + + /* We now have an array of ints starting at value[1], and + * enumerated by value[0]. + * We expect each group of three ints is one table entry, + * and the last table entry is all 0. + */ + n = value[0]; + if ((n % 3) || n < 6 || n > ((ARRAY_SIZE(taos_device_lux) - 1) * 3)) { + dev_info(dev, "LUX TABLE INPUT ERROR 1 Value[0]=%d\n", n); + return -EINVAL; + } + if ((value[(n - 2)] | value[(n - 1)] | value[n]) != 0) { + dev_info(dev, "LUX TABLE INPUT ERROR 2 Value[0]=%d\n", n); + return -EINVAL; + } + + if (chip->taos_chip_status == TSL258X_CHIP_WORKING) + taos_chip_off(indio_dev); + + /* Zero out the table */ + memset(taos_device_lux, 0, sizeof(taos_device_lux)); + memcpy(taos_device_lux, &value[1], (value[0] * 4)); + + taos_chip_on(indio_dev); + + return len; +} + +static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, + taos_power_state_show, taos_power_state_store); + +static DEVICE_ATTR(illuminance0_calibscale, S_IRUGO | S_IWUSR, + taos_gain_show, taos_gain_store); +static DEVICE_ATTR(illuminance0_calibscale_available, S_IRUGO, + taos_gain_available_show, NULL); + +static DEVICE_ATTR(illuminance0_integration_time, S_IRUGO | S_IWUSR, + taos_als_time_show, taos_als_time_store); +static DEVICE_ATTR(illuminance0_integration_time_available, S_IRUGO, + taos_als_time_available_show, NULL); + +static DEVICE_ATTR(illuminance0_calibbias, S_IRUGO | S_IWUSR, + taos_als_trim_show, taos_als_trim_store); + +static DEVICE_ATTR(illuminance0_input_target, S_IRUGO | S_IWUSR, + taos_als_cal_target_show, taos_als_cal_target_store); + +static DEVICE_ATTR(illuminance0_input, S_IRUGO, taos_lux_show, NULL); +static DEVICE_ATTR(illuminance0_calibrate, S_IWUSR, NULL, taos_do_calibrate); +static DEVICE_ATTR(illuminance0_lux_table, S_IRUGO | S_IWUSR, + taos_luxtable_show, taos_luxtable_store); + +static struct attribute *sysfs_attrs_ctrl[] = { + &dev_attr_power_state.attr, + &dev_attr_illuminance0_calibscale.attr, /* Gain */ + &dev_attr_illuminance0_calibscale_available.attr, + &dev_attr_illuminance0_integration_time.attr, /* I time*/ + &dev_attr_illuminance0_integration_time_available.attr, + &dev_attr_illuminance0_calibbias.attr, /* trim */ + &dev_attr_illuminance0_input_target.attr, + &dev_attr_illuminance0_input.attr, + &dev_attr_illuminance0_calibrate.attr, + &dev_attr_illuminance0_lux_table.attr, + NULL +}; + +static struct attribute_group tsl2583_attribute_group = { + .attrs = sysfs_attrs_ctrl, +}; + +/* Use the default register values to identify the Taos device */ +static int taos_tsl258x_device(unsigned char *bufp) +{ + return ((bufp[TSL258X_CHIPID] & 0xf0) == 0x90); +} + +static const struct iio_info tsl2583_info = { + .attrs = &tsl2583_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* + * Client probe function - When a valid device is found, the driver's device + * data structure is updated, and initialization completes successfully. + */ +static int taos_probe(struct i2c_client *clientp, + const struct i2c_device_id *idp) +{ + int i, ret; + unsigned char buf[TSL258X_MAX_DEVICE_REGS]; + struct tsl2583_chip *chip; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(clientp->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&clientp->dev, "taos_probe() - i2c smbus byte data func unsupported\n"); + return -EOPNOTSUPP; + } + + indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + chip = iio_priv(indio_dev); + chip->client = clientp; + i2c_set_clientdata(clientp, indio_dev); + + mutex_init(&chip->als_mutex); + chip->taos_chip_status = TSL258X_CHIP_UNKNOWN; + memcpy(chip->taos_config, taos_config, sizeof(chip->taos_config)); + + for (i = 0; i < TSL258X_MAX_DEVICE_REGS; i++) { + ret = i2c_smbus_write_byte(clientp, + (TSL258X_CMD_REG | (TSL258X_CNTRL + i))); + if (ret < 0) { + dev_err(&clientp->dev, + "i2c_smbus_write_byte to cmd reg failed in taos_probe(), err = %d\n", + ret); + return ret; + } + ret = i2c_smbus_read_byte(clientp); + if (ret < 0) { + dev_err(&clientp->dev, + "i2c_smbus_read_byte from reg failed in taos_probe(), err = %d\n", + ret); + return ret; + } + buf[i] = ret; + } + + if (!taos_tsl258x_device(buf)) { + dev_info(&clientp->dev, + "i2c device found but does not match expected id in taos_probe()\n"); + return -EINVAL; + } + + ret = i2c_smbus_write_byte(clientp, (TSL258X_CMD_REG | TSL258X_CNTRL)); + if (ret < 0) { + dev_err(&clientp->dev, + "i2c_smbus_write_byte() to cmd reg failed in taos_probe(), err = %d\n", + ret); + return ret; + } + + indio_dev->info = &tsl2583_info; + indio_dev->dev.parent = &clientp->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&clientp->dev, "iio registration failed\n"); + return ret; + } + + /* Load up the V2 defaults (these are hard coded defaults for now) */ + taos_defaults(chip); + + /* Make sure the chip is on */ + taos_chip_on(indio_dev); + + dev_info(&clientp->dev, "Light sensor found.\n"); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int taos_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&chip->als_mutex); + + if (chip->taos_chip_status == TSL258X_CHIP_WORKING) { + ret = taos_chip_off(indio_dev); + chip->taos_chip_status = TSL258X_CHIP_SUSPENDED; + } + + mutex_unlock(&chip->als_mutex); + return ret; +} + +static int taos_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&chip->als_mutex); + + if (chip->taos_chip_status == TSL258X_CHIP_SUSPENDED) + ret = taos_chip_on(indio_dev); + + mutex_unlock(&chip->als_mutex); + return ret; +} + +static SIMPLE_DEV_PM_OPS(taos_pm_ops, taos_suspend, taos_resume); +#define TAOS_PM_OPS (&taos_pm_ops) +#else +#define TAOS_PM_OPS NULL +#endif + +static int taos_remove(struct i2c_client *client) +{ + iio_device_unregister(i2c_get_clientdata(client)); + + return 0; +} + +static struct i2c_device_id taos_idtable[] = { + { "tsl2580", 0 }, + { "tsl2581", 1 }, + { "tsl2583", 2 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, taos_idtable); + +/* Driver definition */ +static struct i2c_driver taos_driver = { + .driver = { + .name = "tsl2583", + .pm = TAOS_PM_OPS, + }, + .id_table = taos_idtable, + .probe = taos_probe, + .remove = taos_remove, +}; +module_i2c_driver(taos_driver); + +MODULE_AUTHOR("J. August Brenner"); +MODULE_DESCRIPTION("TAOS tsl2583 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/staging/iio/light/tsl2x7x.h b/kernel/drivers/staging/iio/light/tsl2x7x.h new file mode 100644 index 000000000..ecae92211 --- /dev/null +++ b/kernel/drivers/staging/iio/light/tsl2x7x.h @@ -0,0 +1,100 @@ +/* + * Device driver for monitoring ambient light intensity (lux) + * and proximity (prox) within the TAOS TSL2X7X family of devices. + * + * Copyright (c) 2012, TAOS 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. + */ + +#ifndef __TSL2X7X_H +#define __TSL2X7X_H +#include + +/* Max number of segments allowable in LUX table */ +#define TSL2X7X_MAX_LUX_TABLE_SIZE 9 +#define MAX_DEFAULT_TABLE_BYTES (sizeof(int) * TSL2X7X_MAX_LUX_TABLE_SIZE) + +struct iio_dev; + +struct tsl2x7x_lux { + unsigned int ratio; + unsigned int ch0; + unsigned int ch1; +}; + +/** + * struct tsl2x7x_default_settings - power on defaults unless + * overridden by platform data. + * @als_time: ALS Integration time - multiple of 50mS + * @als_gain: Index into the ALS gain table. + * @als_gain_trim: default gain trim to account for + * aperture effects. + * @wait_time: Time between PRX and ALS cycles + * in 2.7 periods + * @prx_time: 5.2ms prox integration time - + * decrease in 2.7ms periods + * @prx_gain: Proximity gain index + * @prox_config: Prox configuration filters. + * @als_cal_target: Known external ALS reading for + * calibration. + * @interrupts_en: Enable/Disable - 0x00 = none, 0x10 = als, + * 0x20 = prx, 0x30 = bth + * @persistence: H/W Filters, Number of 'out of limits' + * ADC readings PRX/ALS. + * @als_thresh_low: CH0 'low' count to trigger interrupt. + * @als_thresh_high: CH0 'high' count to trigger interrupt. + * @prox_thres_low: Low threshold proximity detection. + * @prox_thres_high: High threshold proximity detection + * @prox_pulse_count: Number if proximity emitter pulses + * @prox_max_samples_cal: Used for prox cal. + */ +struct tsl2x7x_settings { + int als_time; + int als_gain; + int als_gain_trim; + int wait_time; + int prx_time; + int prox_gain; + int prox_config; + int als_cal_target; + u8 interrupts_en; + u8 persistence; + int als_thresh_low; + int als_thresh_high; + int prox_thres_low; + int prox_thres_high; + int prox_pulse_count; + int prox_max_samples_cal; +}; + +/** + * struct tsl2X7X_platform_data - Platform callback, glass and defaults + * @platform_power: Suspend/resume platform callback + * @power_on: Power on callback + * @power_off: Power off callback + * @platform_lux_table: Device specific glass coefficents + * @platform_default_settings: Device specific power on defaults + * + */ +struct tsl2X7X_platform_data { + int (*platform_power)(struct device *dev, pm_message_t); + int (*power_on)(struct iio_dev *indio_dev); + int (*power_off)(struct i2c_client *dev); + struct tsl2x7x_lux platform_lux_table[TSL2X7X_MAX_LUX_TABLE_SIZE]; + struct tsl2x7x_settings *platform_default_settings; +}; + +#endif /* __TSL2X7X_H */ diff --git a/kernel/drivers/staging/iio/light/tsl2x7x_core.c b/kernel/drivers/staging/iio/light/tsl2x7x_core.c new file mode 100644 index 000000000..010e607dd --- /dev/null +++ b/kernel/drivers/staging/iio/light/tsl2x7x_core.c @@ -0,0 +1,2030 @@ +/* + * Device driver for monitoring ambient light intensity in (lux) + * and proximity detection (prox) within the TAOS TSL2X7X family of devices. + * + * Copyright (c) 2012, TAOS 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tsl2x7x.h" + +/* Cal defs*/ +#define PROX_STAT_CAL 0 +#define PROX_STAT_SAMP 1 +#define MAX_SAMPLES_CAL 200 + +/* TSL2X7X Device ID */ +#define TRITON_ID 0x00 +#define SWORDFISH_ID 0x30 +#define HALIBUT_ID 0x20 + +/* Lux calculation constants */ +#define TSL2X7X_LUX_CALC_OVER_FLOW 65535 + +/* TAOS Register definitions - note: + * depending on device, some of these register are not used and the + * register address is benign. + */ +/* 2X7X register offsets */ +#define TSL2X7X_MAX_CONFIG_REG 16 + +/* Device Registers and Masks */ +#define TSL2X7X_CNTRL 0x00 +#define TSL2X7X_ALS_TIME 0X01 +#define TSL2X7X_PRX_TIME 0x02 +#define TSL2X7X_WAIT_TIME 0x03 +#define TSL2X7X_ALS_MINTHRESHLO 0X04 +#define TSL2X7X_ALS_MINTHRESHHI 0X05 +#define TSL2X7X_ALS_MAXTHRESHLO 0X06 +#define TSL2X7X_ALS_MAXTHRESHHI 0X07 +#define TSL2X7X_PRX_MINTHRESHLO 0X08 +#define TSL2X7X_PRX_MINTHRESHHI 0X09 +#define TSL2X7X_PRX_MAXTHRESHLO 0X0A +#define TSL2X7X_PRX_MAXTHRESHHI 0X0B +#define TSL2X7X_PERSISTENCE 0x0C +#define TSL2X7X_PRX_CONFIG 0x0D +#define TSL2X7X_PRX_COUNT 0x0E +#define TSL2X7X_GAIN 0x0F +#define TSL2X7X_NOTUSED 0x10 +#define TSL2X7X_REVID 0x11 +#define TSL2X7X_CHIPID 0x12 +#define TSL2X7X_STATUS 0x13 +#define TSL2X7X_ALS_CHAN0LO 0x14 +#define TSL2X7X_ALS_CHAN0HI 0x15 +#define TSL2X7X_ALS_CHAN1LO 0x16 +#define TSL2X7X_ALS_CHAN1HI 0x17 +#define TSL2X7X_PRX_LO 0x18 +#define TSL2X7X_PRX_HI 0x19 + +/* tsl2X7X cmd reg masks */ +#define TSL2X7X_CMD_REG 0x80 +#define TSL2X7X_CMD_SPL_FN 0x60 + +#define TSL2X7X_CMD_PROX_INT_CLR 0X05 +#define TSL2X7X_CMD_ALS_INT_CLR 0x06 +#define TSL2X7X_CMD_PROXALS_INT_CLR 0X07 + +/* tsl2X7X cntrl reg masks */ +#define TSL2X7X_CNTL_ADC_ENBL 0x02 +#define TSL2X7X_CNTL_PWR_ON 0x01 + +/* tsl2X7X status reg masks */ +#define TSL2X7X_STA_ADC_VALID 0x01 +#define TSL2X7X_STA_PRX_VALID 0x02 +#define TSL2X7X_STA_ADC_PRX_VALID (TSL2X7X_STA_ADC_VALID |\ + TSL2X7X_STA_PRX_VALID) +#define TSL2X7X_STA_ALS_INTR 0x10 +#define TSL2X7X_STA_PRX_INTR 0x20 + +/* tsl2X7X cntrl reg masks */ +#define TSL2X7X_CNTL_REG_CLEAR 0x00 +#define TSL2X7X_CNTL_PROX_INT_ENBL 0X20 +#define TSL2X7X_CNTL_ALS_INT_ENBL 0X10 +#define TSL2X7X_CNTL_WAIT_TMR_ENBL 0X08 +#define TSL2X7X_CNTL_PROX_DET_ENBL 0X04 +#define TSL2X7X_CNTL_PWRON 0x01 +#define TSL2X7X_CNTL_ALSPON_ENBL 0x03 +#define TSL2X7X_CNTL_INTALSPON_ENBL 0x13 +#define TSL2X7X_CNTL_PROXPON_ENBL 0x0F +#define TSL2X7X_CNTL_INTPROXPON_ENBL 0x2F + +/*Prox diode to use */ +#define TSL2X7X_DIODE0 0x10 +#define TSL2X7X_DIODE1 0x20 +#define TSL2X7X_DIODE_BOTH 0x30 + +/* LED Power */ +#define TSL2X7X_mA100 0x00 +#define TSL2X7X_mA50 0x40 +#define TSL2X7X_mA25 0x80 +#define TSL2X7X_mA13 0xD0 +#define TSL2X7X_MAX_TIMER_CNT (0xFF) + +#define TSL2X7X_MIN_ITIME 3 + +/* TAOS txx2x7x Device family members */ +enum { + tsl2571, + tsl2671, + tmd2671, + tsl2771, + tmd2771, + tsl2572, + tsl2672, + tmd2672, + tsl2772, + tmd2772 +}; + +enum { + TSL2X7X_CHIP_UNKNOWN = 0, + TSL2X7X_CHIP_WORKING = 1, + TSL2X7X_CHIP_SUSPENDED = 2 +}; + +struct tsl2x7x_parse_result { + int integer; + int fract; +}; + +/* Per-device data */ +struct tsl2x7x_als_info { + u16 als_ch0; + u16 als_ch1; + u16 lux; +}; + +struct tsl2x7x_prox_stat { + int min; + int max; + int mean; + unsigned long stddev; +}; + +struct tsl2x7x_chip_info { + int chan_table_elements; + struct iio_chan_spec channel[4]; + const struct iio_info *info; +}; + +struct tsl2X7X_chip { + kernel_ulong_t id; + struct mutex prox_mutex; + struct mutex als_mutex; + struct i2c_client *client; + u16 prox_data; + struct tsl2x7x_als_info als_cur_info; + struct tsl2x7x_settings tsl2x7x_settings; + struct tsl2X7X_platform_data *pdata; + int als_time_scale; + int als_saturation; + int tsl2x7x_chip_status; + u8 tsl2x7x_config[TSL2X7X_MAX_CONFIG_REG]; + const struct tsl2x7x_chip_info *chip_info; + const struct iio_info *info; + s64 event_timestamp; + /* This structure is intentionally large to accommodate + * updates via sysfs. */ + /* Sized to 9 = max 8 segments + 1 termination segment */ + struct tsl2x7x_lux tsl2x7x_device_lux[TSL2X7X_MAX_LUX_TABLE_SIZE]; +}; + +/* Different devices require different coefficents */ +static const struct tsl2x7x_lux tsl2x71_lux_table[] = { + { 14461, 611, 1211 }, + { 18540, 352, 623 }, + { 0, 0, 0 }, +}; + +static const struct tsl2x7x_lux tmd2x71_lux_table[] = { + { 11635, 115, 256 }, + { 15536, 87, 179 }, + { 0, 0, 0 }, +}; + +static const struct tsl2x7x_lux tsl2x72_lux_table[] = { + { 14013, 466, 917 }, + { 18222, 310, 552 }, + { 0, 0, 0 }, +}; + +static const struct tsl2x7x_lux tmd2x72_lux_table[] = { + { 13218, 130, 262 }, + { 17592, 92, 169 }, + { 0, 0, 0 }, +}; + +static const struct tsl2x7x_lux *tsl2x7x_default_lux_table_group[] = { + [tsl2571] = tsl2x71_lux_table, + [tsl2671] = tsl2x71_lux_table, + [tmd2671] = tmd2x71_lux_table, + [tsl2771] = tsl2x71_lux_table, + [tmd2771] = tmd2x71_lux_table, + [tsl2572] = tsl2x72_lux_table, + [tsl2672] = tsl2x72_lux_table, + [tmd2672] = tmd2x72_lux_table, + [tsl2772] = tsl2x72_lux_table, + [tmd2772] = tmd2x72_lux_table, +}; + +static const struct tsl2x7x_settings tsl2x7x_default_settings = { + .als_time = 219, /* 101 ms */ + .als_gain = 0, + .prx_time = 254, /* 5.4 ms */ + .prox_gain = 1, + .wait_time = 245, + .prox_config = 0, + .als_gain_trim = 1000, + .als_cal_target = 150, + .als_thresh_low = 200, + .als_thresh_high = 256, + .persistence = 255, + .interrupts_en = 0, + .prox_thres_low = 0, + .prox_thres_high = 512, + .prox_max_samples_cal = 30, + .prox_pulse_count = 8 +}; + +static const s16 tsl2X7X_als_gainadj[] = { + 1, + 8, + 16, + 120 +}; + +static const s16 tsl2X7X_prx_gainadj[] = { + 1, + 2, + 4, + 8 +}; + +/* Channel variations */ +enum { + ALS, + PRX, + ALSPRX, + PRX2, + ALSPRX2, +}; + +static const u8 device_channel_config[] = { + ALS, + PRX, + PRX, + ALSPRX, + ALSPRX, + ALS, + PRX2, + PRX2, + ALSPRX2, + ALSPRX2 +}; + +/** + * tsl2x7x_i2c_read() - Read a byte from a register. + * @client: i2c client + * @reg: device register to read from + * @*val: pointer to location to store register contents. + * + */ +static int +tsl2x7x_i2c_read(struct i2c_client *client, u8 reg, u8 *val) +{ + int ret = 0; + + /* select register to write */ + ret = i2c_smbus_write_byte(client, (TSL2X7X_CMD_REG | reg)); + if (ret < 0) { + dev_err(&client->dev, "failed to write register %x\n", reg); + return ret; + } + + /* read the data */ + ret = i2c_smbus_read_byte(client); + if (ret >= 0) + *val = (u8)ret; + else + dev_err(&client->dev, "failed to read register %x\n", reg); + + return ret; +} + +/** + * tsl2x7x_get_lux() - Reads and calculates current lux value. + * @indio_dev: pointer to IIO device + * + * The raw ch0 and ch1 values of the ambient light sensed in the last + * integration cycle are read from the device. + * Time scale factor array values are adjusted based on the integration time. + * The raw values are multiplied by a scale factor, and device gain is obtained + * using gain index. Limit checks are done next, then the ratio of a multiple + * of ch1 value, to the ch0 value, is calculated. Array tsl2x7x_device_lux[] + * is then scanned to find the first ratio value that is just above the ratio + * we just calculated. The ch0 and ch1 multiplier constants in the array are + * then used along with the time scale factor array values, to calculate the + * lux. + */ +static int tsl2x7x_get_lux(struct iio_dev *indio_dev) +{ + u16 ch0, ch1; /* separated ch0/ch1 data from device */ + u32 lux; /* raw lux calculated from device data */ + u64 lux64; + u32 ratio; + u8 buf[4]; + struct tsl2x7x_lux *p; + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + int i, ret; + u32 ch0lux = 0; + u32 ch1lux = 0; + + if (mutex_trylock(&chip->als_mutex) == 0) + return chip->als_cur_info.lux; /* busy, so return LAST VALUE */ + + if (chip->tsl2x7x_chip_status != TSL2X7X_CHIP_WORKING) { + /* device is not enabled */ + dev_err(&chip->client->dev, "%s: device is not enabled\n", + __func__); + ret = -EBUSY; + goto out_unlock; + } + + ret = tsl2x7x_i2c_read(chip->client, + (TSL2X7X_CMD_REG | TSL2X7X_STATUS), &buf[0]); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: Failed to read STATUS Reg\n", __func__); + goto out_unlock; + } + /* is data new & valid */ + if (!(buf[0] & TSL2X7X_STA_ADC_VALID)) { + dev_err(&chip->client->dev, + "%s: data not valid yet\n", __func__); + ret = chip->als_cur_info.lux; /* return LAST VALUE */ + goto out_unlock; + } + + for (i = 0; i < 4; i++) { + ret = tsl2x7x_i2c_read(chip->client, + (TSL2X7X_CMD_REG | (TSL2X7X_ALS_CHAN0LO + i)), + &buf[i]); + if (ret < 0) { + dev_err(&chip->client->dev, + "failed to read. err=%x\n", ret); + goto out_unlock; + } + } + + /* clear any existing interrupt status */ + ret = i2c_smbus_write_byte(chip->client, + (TSL2X7X_CMD_REG | + TSL2X7X_CMD_SPL_FN | + TSL2X7X_CMD_ALS_INT_CLR)); + if (ret < 0) { + dev_err(&chip->client->dev, + "i2c_write_command failed - err = %d\n", ret); + goto out_unlock; /* have no data, so return failure */ + } + + /* extract ALS/lux data */ + ch0 = le16_to_cpup((const __le16 *)&buf[0]); + ch1 = le16_to_cpup((const __le16 *)&buf[2]); + + chip->als_cur_info.als_ch0 = ch0; + chip->als_cur_info.als_ch1 = ch1; + + if ((ch0 >= chip->als_saturation) || (ch1 >= chip->als_saturation)) { + lux = TSL2X7X_LUX_CALC_OVER_FLOW; + goto return_max; + } + + if (ch0 == 0) { + /* have no data, so return LAST VALUE */ + ret = chip->als_cur_info.lux; + goto out_unlock; + } + /* calculate ratio */ + ratio = (ch1 << 15) / ch0; + /* convert to unscaled lux using the pointer to the table */ + p = (struct tsl2x7x_lux *) chip->tsl2x7x_device_lux; + while (p->ratio != 0 && p->ratio < ratio) + p++; + + if (p->ratio == 0) { + lux = 0; + } else { + ch0lux = DIV_ROUND_UP((ch0 * p->ch0), + tsl2X7X_als_gainadj[chip->tsl2x7x_settings.als_gain]); + ch1lux = DIV_ROUND_UP((ch1 * p->ch1), + tsl2X7X_als_gainadj[chip->tsl2x7x_settings.als_gain]); + lux = ch0lux - ch1lux; + } + + /* note: lux is 31 bit max at this point */ + if (ch1lux > ch0lux) { + dev_dbg(&chip->client->dev, "ch1lux > ch0lux-return last value\n"); + ret = chip->als_cur_info.lux; + goto out_unlock; + } + + /* adjust for active time scale */ + if (chip->als_time_scale == 0) + lux = 0; + else + lux = (lux + (chip->als_time_scale >> 1)) / + chip->als_time_scale; + + /* adjust for active gain scale + * The tsl2x7x_device_lux tables have a factor of 256 built-in. + * User-specified gain provides a multiplier. + * Apply user-specified gain before shifting right to retain precision. + * Use 64 bits to avoid overflow on multiplication. + * Then go back to 32 bits before division to avoid using div_u64(). + */ + + lux64 = lux; + lux64 = lux64 * chip->tsl2x7x_settings.als_gain_trim; + lux64 >>= 8; + lux = lux64; + lux = (lux + 500) / 1000; + + if (lux > TSL2X7X_LUX_CALC_OVER_FLOW) /* check for overflow */ + lux = TSL2X7X_LUX_CALC_OVER_FLOW; + + /* Update the structure with the latest lux. */ +return_max: + chip->als_cur_info.lux = lux; + ret = lux; + +out_unlock: + mutex_unlock(&chip->als_mutex); + + return ret; +} + +/** + * tsl2x7x_get_prox() - Reads proximity data registers and updates + * chip->prox_data. + * + * @indio_dev: pointer to IIO device + */ +static int tsl2x7x_get_prox(struct iio_dev *indio_dev) +{ + int i; + int ret; + u8 status; + u8 chdata[2]; + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + + if (mutex_trylock(&chip->prox_mutex) == 0) { + dev_err(&chip->client->dev, + "%s: Can't get prox mutex\n", __func__); + return -EBUSY; + } + + ret = tsl2x7x_i2c_read(chip->client, + (TSL2X7X_CMD_REG | TSL2X7X_STATUS), &status); + if (ret < 0) { + dev_err(&chip->client->dev, "i2c err=%d\n", ret); + goto prox_poll_err; + } + + switch (chip->id) { + case tsl2571: + case tsl2671: + case tmd2671: + case tsl2771: + case tmd2771: + if (!(status & TSL2X7X_STA_ADC_VALID)) + goto prox_poll_err; + break; + case tsl2572: + case tsl2672: + case tmd2672: + case tsl2772: + case tmd2772: + if (!(status & TSL2X7X_STA_PRX_VALID)) + goto prox_poll_err; + break; + } + + for (i = 0; i < 2; i++) { + ret = tsl2x7x_i2c_read(chip->client, + (TSL2X7X_CMD_REG | + (TSL2X7X_PRX_LO + i)), &chdata[i]); + if (ret < 0) + goto prox_poll_err; + } + + chip->prox_data = + le16_to_cpup((const __le16 *)&chdata[0]); + +prox_poll_err: + + mutex_unlock(&chip->prox_mutex); + + return chip->prox_data; +} + +/** + * tsl2x7x_defaults() - Populates the device nominal operating parameters + * with those provided by a 'platform' data struct or + * with prefined defaults. + * + * @chip: pointer to device structure. + */ +static void tsl2x7x_defaults(struct tsl2X7X_chip *chip) +{ + /* If Operational settings defined elsewhere.. */ + if (chip->pdata && chip->pdata->platform_default_settings) + memcpy(&(chip->tsl2x7x_settings), + chip->pdata->platform_default_settings, + sizeof(tsl2x7x_default_settings)); + else + memcpy(&(chip->tsl2x7x_settings), + &tsl2x7x_default_settings, + sizeof(tsl2x7x_default_settings)); + + /* Load up the proper lux table. */ + if (chip->pdata && chip->pdata->platform_lux_table[0].ratio != 0) + memcpy(chip->tsl2x7x_device_lux, + chip->pdata->platform_lux_table, + sizeof(chip->pdata->platform_lux_table)); + else + memcpy(chip->tsl2x7x_device_lux, + (struct tsl2x7x_lux *)tsl2x7x_default_lux_table_group[chip->id], + MAX_DEFAULT_TABLE_BYTES); +} + +/** + * tsl2x7x_als_calibrate() - Obtain single reading and calculate + * the als_gain_trim. + * + * @indio_dev: pointer to IIO device + */ +static int tsl2x7x_als_calibrate(struct iio_dev *indio_dev) +{ + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + u8 reg_val; + int gain_trim_val; + int ret; + int lux_val; + + ret = i2c_smbus_write_byte(chip->client, + (TSL2X7X_CMD_REG | TSL2X7X_CNTRL)); + if (ret < 0) { + dev_err(&chip->client->dev, + "failed to write CNTRL register, ret=%d\n", ret); + return ret; + } + + reg_val = i2c_smbus_read_byte(chip->client); + if ((reg_val & (TSL2X7X_CNTL_ADC_ENBL | TSL2X7X_CNTL_PWR_ON)) + != (TSL2X7X_CNTL_ADC_ENBL | TSL2X7X_CNTL_PWR_ON)) { + dev_err(&chip->client->dev, + "%s: failed: ADC not enabled\n", __func__); + return -1; + } + + ret = i2c_smbus_write_byte(chip->client, + (TSL2X7X_CMD_REG | TSL2X7X_CNTRL)); + if (ret < 0) { + dev_err(&chip->client->dev, + "failed to write ctrl reg: ret=%d\n", ret); + return ret; + } + + reg_val = i2c_smbus_read_byte(chip->client); + if ((reg_val & TSL2X7X_STA_ADC_VALID) != TSL2X7X_STA_ADC_VALID) { + dev_err(&chip->client->dev, + "%s: failed: STATUS - ADC not valid.\n", __func__); + return -ENODATA; + } + + lux_val = tsl2x7x_get_lux(indio_dev); + if (lux_val < 0) { + dev_err(&chip->client->dev, + "%s: failed to get lux\n", __func__); + return lux_val; + } + + gain_trim_val = ((chip->tsl2x7x_settings.als_cal_target) + * chip->tsl2x7x_settings.als_gain_trim) / lux_val; + if ((gain_trim_val < 250) || (gain_trim_val > 4000)) + return -ERANGE; + + chip->tsl2x7x_settings.als_gain_trim = gain_trim_val; + dev_info(&chip->client->dev, + "%s als_calibrate completed\n", chip->client->name); + + return (int) gain_trim_val; +} + +static int tsl2x7x_chip_on(struct iio_dev *indio_dev) +{ + int i; + int ret = 0; + u8 *dev_reg; + u8 utmp; + int als_count; + int als_time; + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + u8 reg_val = 0; + + if (chip->pdata && chip->pdata->power_on) + chip->pdata->power_on(indio_dev); + + /* Non calculated parameters */ + chip->tsl2x7x_config[TSL2X7X_PRX_TIME] = + chip->tsl2x7x_settings.prx_time; + chip->tsl2x7x_config[TSL2X7X_WAIT_TIME] = + chip->tsl2x7x_settings.wait_time; + chip->tsl2x7x_config[TSL2X7X_PRX_CONFIG] = + chip->tsl2x7x_settings.prox_config; + + chip->tsl2x7x_config[TSL2X7X_ALS_MINTHRESHLO] = + (chip->tsl2x7x_settings.als_thresh_low) & 0xFF; + chip->tsl2x7x_config[TSL2X7X_ALS_MINTHRESHHI] = + (chip->tsl2x7x_settings.als_thresh_low >> 8) & 0xFF; + chip->tsl2x7x_config[TSL2X7X_ALS_MAXTHRESHLO] = + (chip->tsl2x7x_settings.als_thresh_high) & 0xFF; + chip->tsl2x7x_config[TSL2X7X_ALS_MAXTHRESHHI] = + (chip->tsl2x7x_settings.als_thresh_high >> 8) & 0xFF; + chip->tsl2x7x_config[TSL2X7X_PERSISTENCE] = + chip->tsl2x7x_settings.persistence; + + chip->tsl2x7x_config[TSL2X7X_PRX_COUNT] = + chip->tsl2x7x_settings.prox_pulse_count; + chip->tsl2x7x_config[TSL2X7X_PRX_MINTHRESHLO] = + (chip->tsl2x7x_settings.prox_thres_low) & 0xFF; + chip->tsl2x7x_config[TSL2X7X_PRX_MINTHRESHHI] = + (chip->tsl2x7x_settings.prox_thres_low >> 8) & 0xFF; + chip->tsl2x7x_config[TSL2X7X_PRX_MAXTHRESHLO] = + (chip->tsl2x7x_settings.prox_thres_high) & 0xFF; + chip->tsl2x7x_config[TSL2X7X_PRX_MAXTHRESHHI] = + (chip->tsl2x7x_settings.prox_thres_high >> 8) & 0xFF; + + /* and make sure we're not already on */ + if (chip->tsl2x7x_chip_status == TSL2X7X_CHIP_WORKING) { + /* if forcing a register update - turn off, then on */ + dev_info(&chip->client->dev, "device is already enabled\n"); + return -EINVAL; + } + + /* determine als integration register */ + als_count = (chip->tsl2x7x_settings.als_time * 100 + 135) / 270; + if (als_count == 0) + als_count = 1; /* ensure at least one cycle */ + + /* convert back to time (encompasses overrides) */ + als_time = (als_count * 27 + 5) / 10; + chip->tsl2x7x_config[TSL2X7X_ALS_TIME] = 256 - als_count; + + /* Set the gain based on tsl2x7x_settings struct */ + chip->tsl2x7x_config[TSL2X7X_GAIN] = + (chip->tsl2x7x_settings.als_gain | + (TSL2X7X_mA100 | TSL2X7X_DIODE1) + | ((chip->tsl2x7x_settings.prox_gain) << 2)); + + /* set chip struct re scaling and saturation */ + chip->als_saturation = als_count * 922; /* 90% of full scale */ + chip->als_time_scale = (als_time + 25) / 50; + + /* TSL2X7X Specific power-on / adc enable sequence + * Power on the device 1st. */ + utmp = TSL2X7X_CNTL_PWR_ON; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2X7X_CMD_REG | TSL2X7X_CNTRL, utmp); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed on CNTRL reg.\n", __func__); + return ret; + } + + /* Use the following shadow copy for our delay before enabling ADC. + * Write all the registers. */ + for (i = 0, dev_reg = chip->tsl2x7x_config; + i < TSL2X7X_MAX_CONFIG_REG; i++) { + ret = i2c_smbus_write_byte_data(chip->client, + TSL2X7X_CMD_REG + i, *dev_reg++); + if (ret < 0) { + dev_err(&chip->client->dev, + "failed on write to reg %d.\n", i); + return ret; + } + } + + mdelay(3); /* Power-on settling time */ + + /* NOW enable the ADC + * initialize the desired mode of operation */ + utmp = TSL2X7X_CNTL_PWR_ON | + TSL2X7X_CNTL_ADC_ENBL | + TSL2X7X_CNTL_PROX_DET_ENBL; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2X7X_CMD_REG | TSL2X7X_CNTRL, utmp); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: failed on 2nd CTRL reg.\n", __func__); + return ret; + } + + chip->tsl2x7x_chip_status = TSL2X7X_CHIP_WORKING; + + if (chip->tsl2x7x_settings.interrupts_en != 0) { + dev_info(&chip->client->dev, "Setting Up Interrupt(s)\n"); + + reg_val = TSL2X7X_CNTL_PWR_ON | TSL2X7X_CNTL_ADC_ENBL; + if ((chip->tsl2x7x_settings.interrupts_en == 0x20) || + (chip->tsl2x7x_settings.interrupts_en == 0x30)) + reg_val |= TSL2X7X_CNTL_PROX_DET_ENBL; + + reg_val |= chip->tsl2x7x_settings.interrupts_en; + ret = i2c_smbus_write_byte_data(chip->client, + (TSL2X7X_CMD_REG | TSL2X7X_CNTRL), reg_val); + if (ret < 0) + dev_err(&chip->client->dev, + "%s: failed in tsl2x7x_IOCTL_INT_SET.\n", + __func__); + + /* Clear out any initial interrupts */ + ret = i2c_smbus_write_byte(chip->client, + TSL2X7X_CMD_REG | TSL2X7X_CMD_SPL_FN | + TSL2X7X_CMD_PROXALS_INT_CLR); + if (ret < 0) { + dev_err(&chip->client->dev, + "%s: Failed to clear Int status\n", + __func__); + return ret; + } + } + + return ret; +} + +static int tsl2x7x_chip_off(struct iio_dev *indio_dev) +{ + int ret; + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + + /* turn device off */ + chip->tsl2x7x_chip_status = TSL2X7X_CHIP_SUSPENDED; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2X7X_CMD_REG | TSL2X7X_CNTRL, 0x00); + + if (chip->pdata && chip->pdata->power_off) + chip->pdata->power_off(chip->client); + + return ret; +} + +/** + * tsl2x7x_invoke_change + * @indio_dev: pointer to IIO device + * + * Obtain and lock both ALS and PROX resources, + * determine and save device state (On/Off), + * cycle device to implement updated parameter, + * put device back into proper state, and unlock + * resource. + */ +static +int tsl2x7x_invoke_change(struct iio_dev *indio_dev) +{ + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + int device_status = chip->tsl2x7x_chip_status; + + mutex_lock(&chip->als_mutex); + mutex_lock(&chip->prox_mutex); + + if (device_status == TSL2X7X_CHIP_WORKING) + tsl2x7x_chip_off(indio_dev); + + tsl2x7x_chip_on(indio_dev); + + if (device_status != TSL2X7X_CHIP_WORKING) + tsl2x7x_chip_off(indio_dev); + + mutex_unlock(&chip->prox_mutex); + mutex_unlock(&chip->als_mutex); + + return 0; +} + +static +void tsl2x7x_prox_calculate(int *data, int length, + struct tsl2x7x_prox_stat *statP) +{ + int i; + int sample_sum; + int tmp; + + if (length == 0) + length = 1; + + sample_sum = 0; + statP->min = INT_MAX; + statP->max = INT_MIN; + for (i = 0; i < length; i++) { + sample_sum += data[i]; + statP->min = min(statP->min, data[i]); + statP->max = max(statP->max, data[i]); + } + + statP->mean = sample_sum / length; + sample_sum = 0; + for (i = 0; i < length; i++) { + tmp = data[i] - statP->mean; + sample_sum += tmp * tmp; + } + statP->stddev = int_sqrt((long)sample_sum)/length; +} + +/** + * tsl2x7x_prox_cal() - Calculates std. and sets thresholds. + * @indio_dev: pointer to IIO device + * + * Calculates a standard deviation based on the samples, + * and sets the threshold accordingly. + */ +static void tsl2x7x_prox_cal(struct iio_dev *indio_dev) +{ + int prox_history[MAX_SAMPLES_CAL + 1]; + int i; + struct tsl2x7x_prox_stat prox_stat_data[2]; + struct tsl2x7x_prox_stat *calP; + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + u8 tmp_irq_settings; + u8 current_state = chip->tsl2x7x_chip_status; + + if (chip->tsl2x7x_settings.prox_max_samples_cal > MAX_SAMPLES_CAL) { + dev_err(&chip->client->dev, + "max prox samples cal is too big: %d\n", + chip->tsl2x7x_settings.prox_max_samples_cal); + chip->tsl2x7x_settings.prox_max_samples_cal = MAX_SAMPLES_CAL; + } + + /* have to stop to change settings */ + tsl2x7x_chip_off(indio_dev); + + /* Enable proximity detection save just in case prox not wanted yet*/ + tmp_irq_settings = chip->tsl2x7x_settings.interrupts_en; + chip->tsl2x7x_settings.interrupts_en |= TSL2X7X_CNTL_PROX_INT_ENBL; + + /*turn on device if not already on*/ + tsl2x7x_chip_on(indio_dev); + + /*gather the samples*/ + for (i = 0; i < chip->tsl2x7x_settings.prox_max_samples_cal; i++) { + mdelay(15); + tsl2x7x_get_prox(indio_dev); + prox_history[i] = chip->prox_data; + dev_info(&chip->client->dev, "2 i=%d prox data= %d\n", + i, chip->prox_data); + } + + tsl2x7x_chip_off(indio_dev); + calP = &prox_stat_data[PROX_STAT_CAL]; + tsl2x7x_prox_calculate(prox_history, + chip->tsl2x7x_settings.prox_max_samples_cal, calP); + chip->tsl2x7x_settings.prox_thres_high = (calP->max << 1) - calP->mean; + + dev_info(&chip->client->dev, " cal min=%d mean=%d max=%d\n", + calP->min, calP->mean, calP->max); + dev_info(&chip->client->dev, + "%s proximity threshold set to %d\n", + chip->client->name, chip->tsl2x7x_settings.prox_thres_high); + + /* back to the way they were */ + chip->tsl2x7x_settings.interrupts_en = tmp_irq_settings; + if (current_state == TSL2X7X_CHIP_WORKING) + tsl2x7x_chip_on(indio_dev); +} + +static ssize_t tsl2x7x_power_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2X7X_chip *chip = iio_priv(dev_to_iio_dev(dev)); + + return snprintf(buf, PAGE_SIZE, "%d\n", chip->tsl2x7x_chip_status); +} + +static ssize_t tsl2x7x_power_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); + bool value; + + if (strtobool(buf, &value)) + return -EINVAL; + + if (value) + tsl2x7x_chip_on(indio_dev); + else + tsl2x7x_chip_off(indio_dev); + + return len; +} + +static ssize_t tsl2x7x_gain_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2X7X_chip *chip = iio_priv(dev_to_iio_dev(dev)); + + switch (chip->id) { + case tsl2571: + case tsl2671: + case tmd2671: + case tsl2771: + case tmd2771: + return snprintf(buf, PAGE_SIZE, "%s\n", "1 8 16 128"); + } + + return snprintf(buf, PAGE_SIZE, "%s\n", "1 8 16 120"); +} + +static ssize_t tsl2x7x_prox_gain_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", "1 2 4 8"); +} + +static ssize_t tsl2x7x_als_time_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2X7X_chip *chip = iio_priv(dev_to_iio_dev(dev)); + int y, z; + + y = (TSL2X7X_MAX_TIMER_CNT - (u8)chip->tsl2x7x_settings.als_time) + 1; + z = y * TSL2X7X_MIN_ITIME; + y /= 1000; + z %= 1000; + + return snprintf(buf, PAGE_SIZE, "%d.%03d\n", y, z); +} + +static ssize_t tsl2x7x_als_time_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 tsl2X7X_chip *chip = iio_priv(indio_dev); + struct tsl2x7x_parse_result result; + int ret; + + ret = iio_str_to_fixpoint(buf, 100, &result.integer, &result.fract); + if (ret) + return ret; + + result.fract /= 3; + chip->tsl2x7x_settings.als_time = + (TSL2X7X_MAX_TIMER_CNT - (u8)result.fract); + + dev_info(&chip->client->dev, "%s: als time = %d", + __func__, chip->tsl2x7x_settings.als_time); + + tsl2x7x_invoke_change(indio_dev); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static IIO_CONST_ATTR(in_illuminance0_integration_time_available, + ".00272 - .696"); + +static ssize_t tsl2x7x_als_cal_target_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2X7X_chip *chip = iio_priv(dev_to_iio_dev(dev)); + + return snprintf(buf, PAGE_SIZE, "%d\n", + chip->tsl2x7x_settings.als_cal_target); +} + +static ssize_t tsl2x7x_als_cal_target_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 tsl2X7X_chip *chip = iio_priv(indio_dev); + unsigned long value; + + if (kstrtoul(buf, 0, &value)) + return -EINVAL; + + if (value) + chip->tsl2x7x_settings.als_cal_target = value; + + tsl2x7x_invoke_change(indio_dev); + + return len; +} + +/* persistence settings */ +static ssize_t tsl2x7x_als_persistence_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2X7X_chip *chip = iio_priv(dev_to_iio_dev(dev)); + int y, z, filter_delay; + + /* Determine integration time */ + y = (TSL2X7X_MAX_TIMER_CNT - (u8)chip->tsl2x7x_settings.als_time) + 1; + z = y * TSL2X7X_MIN_ITIME; + filter_delay = z * (chip->tsl2x7x_settings.persistence & 0x0F); + y = filter_delay / 1000; + z = filter_delay % 1000; + + return snprintf(buf, PAGE_SIZE, "%d.%03d\n", y, z); +} + +static ssize_t tsl2x7x_als_persistence_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 tsl2X7X_chip *chip = iio_priv(indio_dev); + struct tsl2x7x_parse_result result; + int y, z, filter_delay; + int ret; + + ret = iio_str_to_fixpoint(buf, 100, &result.integer, &result.fract); + if (ret) + return ret; + + y = (TSL2X7X_MAX_TIMER_CNT - (u8)chip->tsl2x7x_settings.als_time) + 1; + z = y * TSL2X7X_MIN_ITIME; + + filter_delay = + DIV_ROUND_UP(((result.integer * 1000) + result.fract), z); + + chip->tsl2x7x_settings.persistence &= 0xF0; + chip->tsl2x7x_settings.persistence |= (filter_delay & 0x0F); + + dev_info(&chip->client->dev, "%s: als persistence = %d", + __func__, filter_delay); + + tsl2x7x_invoke_change(indio_dev); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static ssize_t tsl2x7x_prox_persistence_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2X7X_chip *chip = iio_priv(dev_to_iio_dev(dev)); + int y, z, filter_delay; + + /* Determine integration time */ + y = (TSL2X7X_MAX_TIMER_CNT - (u8)chip->tsl2x7x_settings.prx_time) + 1; + z = y * TSL2X7X_MIN_ITIME; + filter_delay = z * ((chip->tsl2x7x_settings.persistence & 0xF0) >> 4); + y = filter_delay / 1000; + z = filter_delay % 1000; + + return snprintf(buf, PAGE_SIZE, "%d.%03d\n", y, z); +} + +static ssize_t tsl2x7x_prox_persistence_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 tsl2X7X_chip *chip = iio_priv(indio_dev); + struct tsl2x7x_parse_result result; + int y, z, filter_delay; + int ret; + + ret = iio_str_to_fixpoint(buf, 100, &result.integer, &result.fract); + if (ret) + return ret; + + y = (TSL2X7X_MAX_TIMER_CNT - (u8)chip->tsl2x7x_settings.prx_time) + 1; + z = y * TSL2X7X_MIN_ITIME; + + filter_delay = + DIV_ROUND_UP(((result.integer * 1000) + result.fract), z); + + chip->tsl2x7x_settings.persistence &= 0x0F; + chip->tsl2x7x_settings.persistence |= ((filter_delay << 4) & 0xF0); + + dev_info(&chip->client->dev, "%s: prox persistence = %d", + __func__, filter_delay); + + tsl2x7x_invoke_change(indio_dev); + + return IIO_VAL_INT_PLUS_MICRO; +} + +static ssize_t tsl2x7x_do_calibrate(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool value; + + if (strtobool(buf, &value)) + return -EINVAL; + + if (value) + tsl2x7x_als_calibrate(indio_dev); + + tsl2x7x_invoke_change(indio_dev); + + return len; +} + +static ssize_t tsl2x7x_luxtable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tsl2X7X_chip *chip = iio_priv(dev_to_iio_dev(dev)); + int i = 0; + int offset = 0; + + while (i < (TSL2X7X_MAX_LUX_TABLE_SIZE * 3)) { + offset += snprintf(buf + offset, PAGE_SIZE, "%u,%u,%u,", + chip->tsl2x7x_device_lux[i].ratio, + chip->tsl2x7x_device_lux[i].ch0, + chip->tsl2x7x_device_lux[i].ch1); + if (chip->tsl2x7x_device_lux[i].ratio == 0) { + /* We just printed the first "0" entry. + * Now get rid of the extra "," and break. */ + offset--; + break; + } + i++; + } + + offset += snprintf(buf + offset, PAGE_SIZE, "\n"); + return offset; +} + +static ssize_t tsl2x7x_luxtable_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 tsl2X7X_chip *chip = iio_priv(indio_dev); + int value[ARRAY_SIZE(chip->tsl2x7x_device_lux)*3 + 1]; + int n; + + get_options(buf, ARRAY_SIZE(value), value); + + /* We now have an array of ints starting at value[1], and + * enumerated by value[0]. + * We expect each group of three ints is one table entry, + * and the last table entry is all 0. + */ + n = value[0]; + if ((n % 3) || n < 6 || + n > ((ARRAY_SIZE(chip->tsl2x7x_device_lux) - 1) * 3)) { + dev_info(dev, "LUX TABLE INPUT ERROR 1 Value[0]=%d\n", n); + return -EINVAL; + } + + if ((value[(n - 2)] | value[(n - 1)] | value[n]) != 0) { + dev_info(dev, "LUX TABLE INPUT ERROR 2 Value[0]=%d\n", n); + return -EINVAL; + } + + if (chip->tsl2x7x_chip_status == TSL2X7X_CHIP_WORKING) + tsl2x7x_chip_off(indio_dev); + + /* Zero out the table */ + memset(chip->tsl2x7x_device_lux, 0, sizeof(chip->tsl2x7x_device_lux)); + memcpy(chip->tsl2x7x_device_lux, &value[1], (value[0] * 4)); + + tsl2x7x_invoke_change(indio_dev); + + return len; +} + +static ssize_t tsl2x7x_do_prox_calibrate(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + bool value; + + if (strtobool(buf, &value)) + return -EINVAL; + + if (value) + tsl2x7x_prox_cal(indio_dev); + + tsl2x7x_invoke_change(indio_dev); + + return len; +} + +static int tsl2x7x_read_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + int ret; + + if (chan->type == IIO_INTENSITY) + ret = !!(chip->tsl2x7x_settings.interrupts_en & 0x10); + else + ret = !!(chip->tsl2x7x_settings.interrupts_en & 0x20); + + return ret; +} + +static int tsl2x7x_write_interrupt_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int val) +{ + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + + if (chan->type == IIO_INTENSITY) { + if (val) + chip->tsl2x7x_settings.interrupts_en |= 0x10; + else + chip->tsl2x7x_settings.interrupts_en &= 0x20; + } else { + if (val) + chip->tsl2x7x_settings.interrupts_en |= 0x20; + else + chip->tsl2x7x_settings.interrupts_en &= 0x10; + } + + tsl2x7x_invoke_change(indio_dev); + + return 0; +} + +static int tsl2x7x_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + + if (chan->type == IIO_INTENSITY) { + switch (dir) { + case IIO_EV_DIR_RISING: + chip->tsl2x7x_settings.als_thresh_high = val; + break; + case IIO_EV_DIR_FALLING: + chip->tsl2x7x_settings.als_thresh_low = val; + break; + default: + return -EINVAL; + } + } else { + switch (dir) { + case IIO_EV_DIR_RISING: + chip->tsl2x7x_settings.prox_thres_high = val; + break; + case IIO_EV_DIR_FALLING: + chip->tsl2x7x_settings.prox_thres_low = val; + break; + default: + return -EINVAL; + } + } + + tsl2x7x_invoke_change(indio_dev); + + return 0; +} + +static int tsl2x7x_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + + if (chan->type == IIO_INTENSITY) { + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->tsl2x7x_settings.als_thresh_high; + break; + case IIO_EV_DIR_FALLING: + *val = chip->tsl2x7x_settings.als_thresh_low; + break; + default: + return -EINVAL; + } + } else { + switch (dir) { + case IIO_EV_DIR_RISING: + *val = chip->tsl2x7x_settings.prox_thres_high; + break; + case IIO_EV_DIR_FALLING: + *val = chip->tsl2x7x_settings.prox_thres_low; + break; + default: + return -EINVAL; + } + } + + return IIO_VAL_INT; +} + +static int tsl2x7x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret = -EINVAL; + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_LIGHT: + tsl2x7x_get_lux(indio_dev); + *val = chip->als_cur_info.lux; + ret = IIO_VAL_INT; + break; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_INTENSITY: + tsl2x7x_get_lux(indio_dev); + if (chan->channel == 0) + *val = chip->als_cur_info.als_ch0; + else + *val = chip->als_cur_info.als_ch1; + ret = IIO_VAL_INT; + break; + case IIO_PROXIMITY: + tsl2x7x_get_prox(indio_dev); + *val = chip->prox_data; + ret = IIO_VAL_INT; + break; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) + *val = + tsl2X7X_als_gainadj[chip->tsl2x7x_settings.als_gain]; + else + *val = + tsl2X7X_prx_gainadj[chip->tsl2x7x_settings.prox_gain]; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBBIAS: + *val = chip->tsl2x7x_settings.als_gain_trim; + ret = IIO_VAL_INT; + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static int tsl2x7x_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_INTENSITY) { + switch (val) { + case 1: + chip->tsl2x7x_settings.als_gain = 0; + break; + case 8: + chip->tsl2x7x_settings.als_gain = 1; + break; + case 16: + chip->tsl2x7x_settings.als_gain = 2; + break; + case 120: + switch (chip->id) { + case tsl2572: + case tsl2672: + case tmd2672: + case tsl2772: + case tmd2772: + return -EINVAL; + } + chip->tsl2x7x_settings.als_gain = 3; + break; + case 128: + switch (chip->id) { + case tsl2571: + case tsl2671: + case tmd2671: + case tsl2771: + case tmd2771: + return -EINVAL; + } + chip->tsl2x7x_settings.als_gain = 3; + break; + default: + return -EINVAL; + } + } else { + switch (val) { + case 1: + chip->tsl2x7x_settings.prox_gain = 0; + break; + case 2: + chip->tsl2x7x_settings.prox_gain = 1; + break; + case 4: + chip->tsl2x7x_settings.prox_gain = 2; + break; + case 8: + chip->tsl2x7x_settings.prox_gain = 3; + break; + default: + return -EINVAL; + } + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + chip->tsl2x7x_settings.als_gain_trim = val; + break; + + default: + return -EINVAL; + } + + tsl2x7x_invoke_change(indio_dev); + + return 0; +} + +static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, + tsl2x7x_power_state_show, tsl2x7x_power_state_store); + +static DEVICE_ATTR(in_proximity0_calibscale_available, S_IRUGO, + tsl2x7x_prox_gain_available_show, NULL); + +static DEVICE_ATTR(in_illuminance0_calibscale_available, S_IRUGO, + tsl2x7x_gain_available_show, NULL); + +static DEVICE_ATTR(in_illuminance0_integration_time, S_IRUGO | S_IWUSR, + tsl2x7x_als_time_show, tsl2x7x_als_time_store); + +static DEVICE_ATTR(in_illuminance0_target_input, S_IRUGO | S_IWUSR, + tsl2x7x_als_cal_target_show, tsl2x7x_als_cal_target_store); + +static DEVICE_ATTR(in_illuminance0_calibrate, S_IWUSR, NULL, + tsl2x7x_do_calibrate); + +static DEVICE_ATTR(in_proximity0_calibrate, S_IWUSR, NULL, + tsl2x7x_do_prox_calibrate); + +static DEVICE_ATTR(in_illuminance0_lux_table, S_IRUGO | S_IWUSR, + tsl2x7x_luxtable_show, tsl2x7x_luxtable_store); + +static DEVICE_ATTR(in_intensity0_thresh_period, S_IRUGO | S_IWUSR, + tsl2x7x_als_persistence_show, tsl2x7x_als_persistence_store); + +static DEVICE_ATTR(in_proximity0_thresh_period, S_IRUGO | S_IWUSR, + tsl2x7x_prox_persistence_show, tsl2x7x_prox_persistence_store); + +/* Use the default register values to identify the Taos device */ +static int tsl2x7x_device_id(unsigned char *id, int target) +{ + switch (target) { + case tsl2571: + case tsl2671: + case tsl2771: + return (*id & 0xf0) == TRITON_ID; + case tmd2671: + case tmd2771: + return (*id & 0xf0) == HALIBUT_ID; + case tsl2572: + case tsl2672: + case tmd2672: + case tsl2772: + case tmd2772: + return (*id & 0xf0) == SWORDFISH_ID; + } + + return -EINVAL; +} + +static irqreturn_t tsl2x7x_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(); + int ret; + u8 value; + + value = i2c_smbus_read_byte_data(chip->client, + TSL2X7X_CMD_REG | TSL2X7X_STATUS); + + /* What type of interrupt do we need to process */ + if (value & TSL2X7X_STA_PRX_INTR) { + tsl2x7x_get_prox(indio_dev); /* freshen data for ABI */ + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + } + + if (value & TSL2X7X_STA_ALS_INTR) { + tsl2x7x_get_lux(indio_dev); /* freshen data for ABI */ + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + timestamp); + } + /* Clear interrupt now that we have handled it. */ + ret = i2c_smbus_write_byte(chip->client, + TSL2X7X_CMD_REG | TSL2X7X_CMD_SPL_FN | + TSL2X7X_CMD_PROXALS_INT_CLR); + if (ret < 0) + dev_err(&chip->client->dev, + "Failed to clear irq from event handler. err = %d\n", + ret); + + return IRQ_HANDLED; +} + +static struct attribute *tsl2x7x_ALS_device_attrs[] = { + &dev_attr_power_state.attr, + &dev_attr_in_illuminance0_calibscale_available.attr, + &dev_attr_in_illuminance0_integration_time.attr, + &iio_const_attr_in_illuminance0_integration_time_available.dev_attr.attr, + &dev_attr_in_illuminance0_target_input.attr, + &dev_attr_in_illuminance0_calibrate.attr, + &dev_attr_in_illuminance0_lux_table.attr, + NULL +}; + +static struct attribute *tsl2x7x_PRX_device_attrs[] = { + &dev_attr_power_state.attr, + &dev_attr_in_proximity0_calibrate.attr, + NULL +}; + +static struct attribute *tsl2x7x_ALSPRX_device_attrs[] = { + &dev_attr_power_state.attr, + &dev_attr_in_illuminance0_calibscale_available.attr, + &dev_attr_in_illuminance0_integration_time.attr, + &iio_const_attr_in_illuminance0_integration_time_available.dev_attr.attr, + &dev_attr_in_illuminance0_target_input.attr, + &dev_attr_in_illuminance0_calibrate.attr, + &dev_attr_in_illuminance0_lux_table.attr, + &dev_attr_in_proximity0_calibrate.attr, + NULL +}; + +static struct attribute *tsl2x7x_PRX2_device_attrs[] = { + &dev_attr_power_state.attr, + &dev_attr_in_proximity0_calibrate.attr, + &dev_attr_in_proximity0_calibscale_available.attr, + NULL +}; + +static struct attribute *tsl2x7x_ALSPRX2_device_attrs[] = { + &dev_attr_power_state.attr, + &dev_attr_in_illuminance0_calibscale_available.attr, + &dev_attr_in_illuminance0_integration_time.attr, + &iio_const_attr_in_illuminance0_integration_time_available.dev_attr.attr, + &dev_attr_in_illuminance0_target_input.attr, + &dev_attr_in_illuminance0_calibrate.attr, + &dev_attr_in_illuminance0_lux_table.attr, + &dev_attr_in_proximity0_calibrate.attr, + &dev_attr_in_proximity0_calibscale_available.attr, + NULL +}; + +static struct attribute *tsl2X7X_ALS_event_attrs[] = { + &dev_attr_in_intensity0_thresh_period.attr, + NULL, +}; +static struct attribute *tsl2X7X_PRX_event_attrs[] = { + &dev_attr_in_proximity0_thresh_period.attr, + NULL, +}; + +static struct attribute *tsl2X7X_ALSPRX_event_attrs[] = { + &dev_attr_in_intensity0_thresh_period.attr, + &dev_attr_in_proximity0_thresh_period.attr, + NULL, +}; + +static const struct attribute_group tsl2X7X_device_attr_group_tbl[] = { + [ALS] = { + .attrs = tsl2x7x_ALS_device_attrs, + }, + [PRX] = { + .attrs = tsl2x7x_PRX_device_attrs, + }, + [ALSPRX] = { + .attrs = tsl2x7x_ALSPRX_device_attrs, + }, + [PRX2] = { + .attrs = tsl2x7x_PRX2_device_attrs, + }, + [ALSPRX2] = { + .attrs = tsl2x7x_ALSPRX2_device_attrs, + }, +}; + +static struct attribute_group tsl2X7X_event_attr_group_tbl[] = { + [ALS] = { + .attrs = tsl2X7X_ALS_event_attrs, + .name = "events", + }, + [PRX] = { + .attrs = tsl2X7X_PRX_event_attrs, + .name = "events", + }, + [ALSPRX] = { + .attrs = tsl2X7X_ALSPRX_event_attrs, + .name = "events", + }, +}; + +static const struct iio_info tsl2X7X_device_info[] = { + [ALS] = { + .attrs = &tsl2X7X_device_attr_group_tbl[ALS], + .event_attrs = &tsl2X7X_event_attr_group_tbl[ALS], + .driver_module = THIS_MODULE, + .read_raw = &tsl2x7x_read_raw, + .write_raw = &tsl2x7x_write_raw, + .read_event_value = &tsl2x7x_read_thresh, + .write_event_value = &tsl2x7x_write_thresh, + .read_event_config = &tsl2x7x_read_interrupt_config, + .write_event_config = &tsl2x7x_write_interrupt_config, + }, + [PRX] = { + .attrs = &tsl2X7X_device_attr_group_tbl[PRX], + .event_attrs = &tsl2X7X_event_attr_group_tbl[PRX], + .driver_module = THIS_MODULE, + .read_raw = &tsl2x7x_read_raw, + .write_raw = &tsl2x7x_write_raw, + .read_event_value = &tsl2x7x_read_thresh, + .write_event_value = &tsl2x7x_write_thresh, + .read_event_config = &tsl2x7x_read_interrupt_config, + .write_event_config = &tsl2x7x_write_interrupt_config, + }, + [ALSPRX] = { + .attrs = &tsl2X7X_device_attr_group_tbl[ALSPRX], + .event_attrs = &tsl2X7X_event_attr_group_tbl[ALSPRX], + .driver_module = THIS_MODULE, + .read_raw = &tsl2x7x_read_raw, + .write_raw = &tsl2x7x_write_raw, + .read_event_value = &tsl2x7x_read_thresh, + .write_event_value = &tsl2x7x_write_thresh, + .read_event_config = &tsl2x7x_read_interrupt_config, + .write_event_config = &tsl2x7x_write_interrupt_config, + }, + [PRX2] = { + .attrs = &tsl2X7X_device_attr_group_tbl[PRX2], + .event_attrs = &tsl2X7X_event_attr_group_tbl[PRX], + .driver_module = THIS_MODULE, + .read_raw = &tsl2x7x_read_raw, + .write_raw = &tsl2x7x_write_raw, + .read_event_value = &tsl2x7x_read_thresh, + .write_event_value = &tsl2x7x_write_thresh, + .read_event_config = &tsl2x7x_read_interrupt_config, + .write_event_config = &tsl2x7x_write_interrupt_config, + }, + [ALSPRX2] = { + .attrs = &tsl2X7X_device_attr_group_tbl[ALSPRX2], + .event_attrs = &tsl2X7X_event_attr_group_tbl[ALSPRX], + .driver_module = THIS_MODULE, + .read_raw = &tsl2x7x_read_raw, + .write_raw = &tsl2x7x_write_raw, + .read_event_value = &tsl2x7x_read_thresh, + .write_event_value = &tsl2x7x_write_thresh, + .read_event_config = &tsl2x7x_read_interrupt_config, + .write_event_config = &tsl2x7x_write_interrupt_config, + }, +}; + +static const struct iio_event_spec tsl2x7x_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct tsl2x7x_chip_info tsl2x7x_chip_info_tbl[] = { + [ALS] = { + .channel = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .event_spec = tsl2x7x_events, + .num_event_specs = ARRAY_SIZE(tsl2x7x_events), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + }, + }, + .chan_table_elements = 3, + .info = &tsl2X7X_device_info[ALS], + }, + [PRX] = { + .channel = { + { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = tsl2x7x_events, + .num_event_specs = ARRAY_SIZE(tsl2x7x_events), + }, + }, + .chan_table_elements = 1, + .info = &tsl2X7X_device_info[PRX], + }, + [ALSPRX] = { + .channel = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .event_spec = tsl2x7x_events, + .num_event_specs = ARRAY_SIZE(tsl2x7x_events), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = tsl2x7x_events, + .num_event_specs = ARRAY_SIZE(tsl2x7x_events), + }, + }, + .chan_table_elements = 4, + .info = &tsl2X7X_device_info[ALSPRX], + }, + [PRX2] = { + .channel = { + { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2x7x_events, + .num_event_specs = ARRAY_SIZE(tsl2x7x_events), + }, + }, + .chan_table_elements = 1, + .info = &tsl2X7X_device_info[PRX2], + }, + [ALSPRX2] = { + .channel = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .event_spec = tsl2x7x_events, + .num_event_specs = ARRAY_SIZE(tsl2x7x_events), + }, { + .type = IIO_INTENSITY, + .indexed = 1, + .channel = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_PROXIMITY, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE), + .event_spec = tsl2x7x_events, + .num_event_specs = ARRAY_SIZE(tsl2x7x_events), + }, + }, + .chan_table_elements = 4, + .info = &tsl2X7X_device_info[ALSPRX2], + }, +}; + +static int tsl2x7x_probe(struct i2c_client *clientp, + const struct i2c_device_id *id) +{ + int ret; + unsigned char device_id; + struct iio_dev *indio_dev; + struct tsl2X7X_chip *chip; + + indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->client = clientp; + i2c_set_clientdata(clientp, indio_dev); + + ret = tsl2x7x_i2c_read(chip->client, + TSL2X7X_CHIPID, &device_id); + if (ret < 0) + return ret; + + if ((!tsl2x7x_device_id(&device_id, id->driver_data)) || + (tsl2x7x_device_id(&device_id, id->driver_data) == -EINVAL)) { + dev_info(&chip->client->dev, + "%s: i2c device found does not match expected id\n", + __func__); + return -EINVAL; + } + + ret = i2c_smbus_write_byte(clientp, (TSL2X7X_CMD_REG | TSL2X7X_CNTRL)); + if (ret < 0) { + dev_err(&clientp->dev, "write to cmd reg failed. err = %d\n", + ret); + return ret; + } + + /* ALS and PROX functions can be invoked via user space poll + * or H/W interrupt. If busy return last sample. */ + mutex_init(&chip->als_mutex); + mutex_init(&chip->prox_mutex); + + chip->tsl2x7x_chip_status = TSL2X7X_CHIP_UNKNOWN; + chip->pdata = clientp->dev.platform_data; + chip->id = id->driver_data; + chip->chip_info = + &tsl2x7x_chip_info_tbl[device_channel_config[id->driver_data]]; + + indio_dev->info = chip->chip_info->info; + indio_dev->dev.parent = &clientp->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + indio_dev->channels = chip->chip_info->channel; + indio_dev->num_channels = chip->chip_info->chan_table_elements; + + if (clientp->irq) { + ret = devm_request_threaded_irq(&clientp->dev, clientp->irq, + NULL, + &tsl2x7x_event_handler, + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "TSL2X7X_event", + indio_dev); + if (ret) { + dev_err(&clientp->dev, + "%s: irq request failed", __func__); + return ret; + } + } + + /* Load up the defaults */ + tsl2x7x_defaults(chip); + /* Make sure the chip is on */ + tsl2x7x_chip_on(indio_dev); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&clientp->dev, + "%s: iio registration failed\n", __func__); + return ret; + } + + dev_info(&clientp->dev, "%s Light sensor found.\n", id->name); + + return 0; +} + +static int tsl2x7x_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + int ret = 0; + + if (chip->tsl2x7x_chip_status == TSL2X7X_CHIP_WORKING) { + ret = tsl2x7x_chip_off(indio_dev); + chip->tsl2x7x_chip_status = TSL2X7X_CHIP_SUSPENDED; + } + + if (chip->pdata && chip->pdata->platform_power) { + pm_message_t pmm = {PM_EVENT_SUSPEND}; + + chip->pdata->platform_power(dev, pmm); + } + + return ret; +} + +static int tsl2x7x_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2X7X_chip *chip = iio_priv(indio_dev); + int ret = 0; + + if (chip->pdata && chip->pdata->platform_power) { + pm_message_t pmm = {PM_EVENT_RESUME}; + + chip->pdata->platform_power(dev, pmm); + } + + if (chip->tsl2x7x_chip_status == TSL2X7X_CHIP_SUSPENDED) + ret = tsl2x7x_chip_on(indio_dev); + + return ret; +} + +static int tsl2x7x_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + tsl2x7x_chip_off(indio_dev); + + iio_device_unregister(indio_dev); + + return 0; +} + +static struct i2c_device_id tsl2x7x_idtable[] = { + { "tsl2571", tsl2571 }, + { "tsl2671", tsl2671 }, + { "tmd2671", tmd2671 }, + { "tsl2771", tsl2771 }, + { "tmd2771", tmd2771 }, + { "tsl2572", tsl2572 }, + { "tsl2672", tsl2672 }, + { "tmd2672", tmd2672 }, + { "tsl2772", tsl2772 }, + { "tmd2772", tmd2772 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tsl2x7x_idtable); + +static const struct dev_pm_ops tsl2x7x_pm_ops = { + .suspend = tsl2x7x_suspend, + .resume = tsl2x7x_resume, +}; + +/* Driver definition */ +static struct i2c_driver tsl2x7x_driver = { + .driver = { + .name = "tsl2x7x", + .pm = &tsl2x7x_pm_ops, + }, + .id_table = tsl2x7x_idtable, + .probe = tsl2x7x_probe, + .remove = tsl2x7x_remove, +}; + +module_i2c_driver(tsl2x7x_driver); + +MODULE_AUTHOR("J. August Brenner"); +MODULE_DESCRIPTION("TAOS tsl2x7x ambient and proximity light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/staging/iio/magnetometer/Kconfig b/kernel/drivers/staging/iio/magnetometer/Kconfig new file mode 100644 index 000000000..dec814a7a --- /dev/null +++ b/kernel/drivers/staging/iio/magnetometer/Kconfig @@ -0,0 +1,40 @@ +# +# Magnetometer sensors +# +menu "Magnetometer sensors" + +config SENSORS_HMC5843 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config SENSORS_HMC5843_I2C + tristate "Honeywell HMC5843/5883/5883L 3-Axis Magnetometer (I2C)" + depends on I2C + select SENSORS_HMC5843 + select REGMAP_I2C + help + Say Y here to add support for the Honeywell HMC5843, HMC5883 and + HMC5883L 3-Axis Magnetometer (digital compass). + + This driver can also be compiled as a set of modules. + If so, these modules will be created: + - hmc5843_core (core functions) + - hmc5843_i2c (support for HMC5843, HMC5883, HMC5883L and HMC5983) + +config SENSORS_HMC5843_SPI + tristate "Honeywell HMC5983 3-Axis Magnetometer (SPI)" + depends on SPI_MASTER + select SENSORS_HMC5843 + select REGMAP_SPI + help + Say Y here to add support for the Honeywell HMC5983 3-Axis Magnetometer + (digital compass). + + This driver can also be compiled as a set of modules. + If so, these modules will be created: + - hmc5843_core (core functions) + - hmc5843_spi (support for HMC5983) + + +endmenu diff --git a/kernel/drivers/staging/iio/magnetometer/Makefile b/kernel/drivers/staging/iio/magnetometer/Makefile new file mode 100644 index 000000000..33761a19a --- /dev/null +++ b/kernel/drivers/staging/iio/magnetometer/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for industrial I/O Magnetometer sensors +# + +obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o +obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o +obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o diff --git a/kernel/drivers/staging/iio/magnetometer/hmc5843.h b/kernel/drivers/staging/iio/magnetometer/hmc5843.h new file mode 100644 index 000000000..f3d0da2fe --- /dev/null +++ b/kernel/drivers/staging/iio/magnetometer/hmc5843.h @@ -0,0 +1,66 @@ +/* + * Header file for hmc5843 driver + * + * Split from hmc5843.c + * Copyright (C) Josef Gajdusek + * + * 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 HMC5843_CORE_H +#define HMC5843_CORE_H + +#include +#include + +#define HMC5843_CONFIG_REG_A 0x00 +#define HMC5843_CONFIG_REG_B 0x01 +#define HMC5843_MODE_REG 0x02 +#define HMC5843_DATA_OUT_MSB_REGS 0x03 +#define HMC5843_STATUS_REG 0x09 +#define HMC5843_ID_REG 0x0a +#define HMC5843_ID_END 0x0c + +enum hmc5843_ids { + HMC5843_ID, + HMC5883_ID, + HMC5883L_ID, + HMC5983_ID, +}; + +/** + * struct hcm5843_data - device specific data + * @dev: actual device + * @lock: update and read regmap data + * @regmap: hardware access register maps + * @variant: describe chip variants + * @buffer: 3x 16-bit channels + padding + 64-bit timestamp + **/ +struct hmc5843_data { + struct device *dev; + struct mutex lock; + struct regmap *regmap; + const struct hmc5843_chip_info *variant; + __be16 buffer[8]; +}; + +int hmc5843_common_probe(struct device *dev, struct regmap *regmap, + enum hmc5843_ids id); +int hmc5843_common_remove(struct device *dev); + +int hmc5843_common_suspend(struct device *dev); +int hmc5843_common_resume(struct device *dev); + +#ifdef CONFIG_PM_SLEEP +static SIMPLE_DEV_PM_OPS(hmc5843_pm_ops, + hmc5843_common_suspend, + hmc5843_common_resume); +#define HMC5843_PM_OPS (&hmc5843_pm_ops) +#else +#define HMC5843_PM_OPS NULL +#endif + +#endif /* HMC5843_CORE_H */ diff --git a/kernel/drivers/staging/iio/magnetometer/hmc5843_core.c b/kernel/drivers/staging/iio/magnetometer/hmc5843_core.c new file mode 100644 index 000000000..fffca3a9f --- /dev/null +++ b/kernel/drivers/staging/iio/magnetometer/hmc5843_core.c @@ -0,0 +1,646 @@ +/* + * Device driver for the the HMC5843 multi-chip module designed + * for low field magnetic sensing. + * + * Copyright (C) 2010 Texas Instruments + * + * Author: Shubhrajyoti Datta + * Acknowledgment: Jonathan Cameron for valuable inputs. + * Support for HMC5883 and HMC5883L by Peter Meerwald . + * Split to multiple files by Josef Gajdusek - 2014 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hmc5843.h" + +/* + * Range gain settings in (+-)Ga + * Beware: HMC5843 and HMC5883 have different recommended sensor field + * ranges; default corresponds to +-1.0 Ga and +-1.3 Ga, respectively + */ +#define HMC5843_RANGE_GAIN_OFFSET 0x05 +#define HMC5843_RANGE_GAIN_DEFAULT 0x01 +#define HMC5843_RANGE_GAIN_MASK 0xe0 + +/* Device status */ +#define HMC5843_DATA_READY 0x01 +#define HMC5843_DATA_OUTPUT_LOCK 0x02 + +/* Mode register configuration */ +#define HMC5843_MODE_CONVERSION_CONTINUOUS 0x00 +#define HMC5843_MODE_CONVERSION_SINGLE 0x01 +#define HMC5843_MODE_IDLE 0x02 +#define HMC5843_MODE_SLEEP 0x03 +#define HMC5843_MODE_MASK 0x03 + +/* + * HMC5843: Minimum data output rate + * HMC5883: Typical data output rate + */ +#define HMC5843_RATE_OFFSET 0x02 +#define HMC5843_RATE_DEFAULT 0x04 +#define HMC5843_RATE_MASK 0x1c + +/* Device measurement configuration */ +#define HMC5843_MEAS_CONF_NORMAL 0x00 +#define HMC5843_MEAS_CONF_POSITIVE_BIAS 0x01 +#define HMC5843_MEAS_CONF_NEGATIVE_BIAS 0x02 +#define HMC5843_MEAS_CONF_MASK 0x03 + +/* Scaling factors: 10000000/Gain */ +static const int hmc5843_regval_to_nanoscale[] = { + 6173, 7692, 10309, 12821, 18868, 21739, 25641, 35714 +}; + +static const int hmc5883_regval_to_nanoscale[] = { + 7812, 9766, 13021, 16287, 24096, 27701, 32573, 45662 +}; + +static const int hmc5883l_regval_to_nanoscale[] = { + 7299, 9174, 12195, 15152, 22727, 25641, 30303, 43478 +}; + +/* + * From the datasheet: + * Value | HMC5843 | HMC5883/HMC5883L + * | Data output rate (Hz) | Data output rate (Hz) + * 0 | 0.5 | 0.75 + * 1 | 1 | 1.5 + * 2 | 2 | 3 + * 3 | 5 | 7.5 + * 4 | 10 (default) | 15 + * 5 | 20 | 30 + * 6 | 50 | 75 + * 7 | Not used | Not used + */ +static const int hmc5843_regval_to_samp_freq[][2] = { + {0, 500000}, {1, 0}, {2, 0}, {5, 0}, {10, 0}, {20, 0}, {50, 0} +}; + +static const int hmc5883_regval_to_samp_freq[][2] = { + {0, 750000}, {1, 500000}, {3, 0}, {7, 500000}, {15, 0}, {30, 0}, + {75, 0} +}; + +static const int hmc5983_regval_to_samp_freq[][2] = { + {0, 750000}, {1, 500000}, {3, 0}, {7, 500000}, {15, 0}, {30, 0}, + {75, 0}, {220, 0} +}; + +/* Describe chip variants */ +struct hmc5843_chip_info { + const struct iio_chan_spec *channels; + const int (*regval_to_samp_freq)[2]; + const int n_regval_to_samp_freq; + const int *regval_to_nanoscale; + const int n_regval_to_nanoscale; +}; + +/* The lower two bits contain the current conversion mode */ +static s32 hmc5843_set_mode(struct hmc5843_data *data, u8 operating_mode) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_MODE_REG, + HMC5843_MODE_MASK, operating_mode); + mutex_unlock(&data->lock); + + return ret; +} + +static int hmc5843_wait_measurement(struct hmc5843_data *data) +{ + int tries = 150; + unsigned int val; + int ret; + + while (tries-- > 0) { + ret = regmap_read(data->regmap, HMC5843_STATUS_REG, &val); + if (ret < 0) + return ret; + if (val & HMC5843_DATA_READY) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(data->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +/* Return the measurement value from the specified channel */ +static int hmc5843_read_measurement(struct hmc5843_data *data, + int idx, int *val) +{ + __be16 values[3]; + int ret; + + mutex_lock(&data->lock); + ret = hmc5843_wait_measurement(data); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + ret = regmap_bulk_read(data->regmap, HMC5843_DATA_OUT_MSB_REGS, + values, sizeof(values)); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + *val = sign_extend32(be16_to_cpu(values[idx]), 15); + return IIO_VAL_INT; +} + +/* + * API for setting the measurement configuration to + * Normal, Positive bias and Negative bias + * + * From the datasheet: + * 0 - Normal measurement configuration (default): In normal measurement + * configuration the device follows normal measurement flow. Pins BP + * and BN are left floating and high impedance. + * + * 1 - Positive bias configuration: In positive bias configuration, a + * positive current is forced across the resistive load on pins BP + * and BN. + * + * 2 - Negative bias configuration. In negative bias configuration, a + * negative current is forced across the resistive load on pins BP + * and BN. + * + */ +static int hmc5843_set_meas_conf(struct hmc5843_data *data, u8 meas_conf) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_A, + HMC5843_MEAS_CONF_MASK, meas_conf); + mutex_unlock(&data->lock); + + return ret; +} + +static +ssize_t hmc5843_show_measurement_configuration(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hmc5843_data *data = iio_priv(dev_to_iio_dev(dev)); + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_A, &val); + if (ret) + return ret; + val &= HMC5843_MEAS_CONF_MASK; + + return sprintf(buf, "%d\n", val); +} + +static +ssize_t hmc5843_set_measurement_configuration(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct hmc5843_data *data = iio_priv(dev_to_iio_dev(dev)); + unsigned long meas_conf = 0; + int ret; + + ret = kstrtoul(buf, 10, &meas_conf); + if (ret) + return ret; + if (meas_conf >= HMC5843_MEAS_CONF_MASK) + return -EINVAL; + + ret = hmc5843_set_meas_conf(data, meas_conf); + + return (ret < 0) ? ret : count; +} + +static IIO_DEVICE_ATTR(meas_conf, + S_IWUSR | S_IRUGO, + hmc5843_show_measurement_configuration, + hmc5843_set_measurement_configuration, + 0); + +static +ssize_t hmc5843_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hmc5843_data *data = iio_priv(dev_to_iio_dev(dev)); + size_t len = 0; + int i; + + for (i = 0; i < data->variant->n_regval_to_samp_freq; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%d.%d ", data->variant->regval_to_samp_freq[i][0], + data->variant->regval_to_samp_freq[i][1]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(hmc5843_show_samp_freq_avail); + +static int hmc5843_set_samp_freq(struct hmc5843_data *data, u8 rate) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_A, + HMC5843_RATE_MASK, + rate << HMC5843_RATE_OFFSET); + mutex_unlock(&data->lock); + + return ret; +} + +static int hmc5843_get_samp_freq_index(struct hmc5843_data *data, + int val, int val2) +{ + int i; + + for (i = 0; i < data->variant->n_regval_to_samp_freq; i++) + if (val == data->variant->regval_to_samp_freq[i][0] && + val2 == data->variant->regval_to_samp_freq[i][1]) + return i; + + return -EINVAL; +} + +static int hmc5843_set_range_gain(struct hmc5843_data *data, u8 range) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_B, + HMC5843_RANGE_GAIN_MASK, + range << HMC5843_RANGE_GAIN_OFFSET); + mutex_unlock(&data->lock); + + return ret; +} + +static ssize_t hmc5843_show_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hmc5843_data *data = iio_priv(dev_to_iio_dev(dev)); + + size_t len = 0; + int i; + + for (i = 0; i < data->variant->n_regval_to_nanoscale; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "0.%09d ", data->variant->regval_to_nanoscale[i]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEVICE_ATTR(scale_available, S_IRUGO, + hmc5843_show_scale_avail, NULL, 0); + +static int hmc5843_get_scale_index(struct hmc5843_data *data, int val, int val2) +{ + int i; + + if (val != 0) + return -EINVAL; + + for (i = 0; i < data->variant->n_regval_to_nanoscale; i++) + if (val2 == data->variant->regval_to_nanoscale[i]) + return i; + + return -EINVAL; +} + +static int hmc5843_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + unsigned int rval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return hmc5843_read_measurement(data, chan->scan_index, val); + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_B, &rval); + if (ret < 0) + return ret; + rval >>= HMC5843_RANGE_GAIN_OFFSET; + *val = 0; + *val2 = data->variant->regval_to_nanoscale[rval]; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_A, &rval); + if (ret < 0) + return ret; + rval >>= HMC5843_RATE_OFFSET; + *val = data->variant->regval_to_samp_freq[rval][0]; + *val2 = data->variant->regval_to_samp_freq[rval][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int hmc5843_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + int rate, range; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rate = hmc5843_get_samp_freq_index(data, val, val2); + if (rate < 0) + return -EINVAL; + + return hmc5843_set_samp_freq(data, rate); + case IIO_CHAN_INFO_SCALE: + range = hmc5843_get_scale_index(data, val, val2); + if (range < 0) + return -EINVAL; + + return hmc5843_set_range_gain(data, range); + default: + return -EINVAL; + } +} + +static int hmc5843_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static irqreturn_t hmc5843_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct hmc5843_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = hmc5843_wait_measurement(data); + if (ret < 0) { + mutex_unlock(&data->lock); + goto done; + } + + ret = regmap_bulk_read(data->regmap, HMC5843_DATA_OUT_MSB_REGS, + data->buffer, 3 * sizeof(__be16)); + + mutex_unlock(&data->lock); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define HMC5843_CHANNEL(axis, idx) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + } + +static const struct iio_chan_spec hmc5843_channels[] = { + HMC5843_CHANNEL(X, 0), + HMC5843_CHANNEL(Y, 1), + HMC5843_CHANNEL(Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +/* Beware: Y and Z are exchanged on HMC5883 and 5983 */ +static const struct iio_chan_spec hmc5883_channels[] = { + HMC5843_CHANNEL(X, 0), + HMC5843_CHANNEL(Z, 1), + HMC5843_CHANNEL(Y, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static struct attribute *hmc5843_attributes[] = { + &iio_dev_attr_meas_conf.dev_attr.attr, + &iio_dev_attr_scale_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group hmc5843_group = { + .attrs = hmc5843_attributes, +}; + +static const struct hmc5843_chip_info hmc5843_chip_info_tbl[] = { + [HMC5843_ID] = { + .channels = hmc5843_channels, + .regval_to_samp_freq = hmc5843_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5843_regval_to_samp_freq), + .regval_to_nanoscale = hmc5843_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5843_regval_to_nanoscale), + }, + [HMC5883_ID] = { + .channels = hmc5883_channels, + .regval_to_samp_freq = hmc5883_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5883_regval_to_samp_freq), + .regval_to_nanoscale = hmc5883_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5883_regval_to_nanoscale), + }, + [HMC5883L_ID] = { + .channels = hmc5883_channels, + .regval_to_samp_freq = hmc5883_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5883_regval_to_samp_freq), + .regval_to_nanoscale = hmc5883l_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5883l_regval_to_nanoscale), + }, + [HMC5983_ID] = { + .channels = hmc5883_channels, + .regval_to_samp_freq = hmc5983_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5983_regval_to_samp_freq), + .regval_to_nanoscale = hmc5883l_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5883l_regval_to_nanoscale), + } +}; + +static int hmc5843_init(struct hmc5843_data *data) +{ + int ret; + u8 id[3]; + + ret = regmap_bulk_read(data->regmap, HMC5843_ID_REG, + id, ARRAY_SIZE(id)); + if (ret < 0) + return ret; + if (id[0] != 'H' || id[1] != '4' || id[2] != '3') { + dev_err(data->dev, "no HMC5843/5883/5883L/5983 sensor\n"); + return -ENODEV; + } + + ret = hmc5843_set_meas_conf(data, HMC5843_MEAS_CONF_NORMAL); + if (ret < 0) + return ret; + ret = hmc5843_set_samp_freq(data, HMC5843_RATE_DEFAULT); + if (ret < 0) + return ret; + ret = hmc5843_set_range_gain(data, HMC5843_RANGE_GAIN_DEFAULT); + if (ret < 0) + return ret; + return hmc5843_set_mode(data, HMC5843_MODE_CONVERSION_CONTINUOUS); +} + +static const struct iio_info hmc5843_info = { + .attrs = &hmc5843_group, + .read_raw = &hmc5843_read_raw, + .write_raw = &hmc5843_write_raw, + .write_raw_get_fmt = &hmc5843_write_raw_get_fmt, + .driver_module = THIS_MODULE, +}; + +static const unsigned long hmc5843_scan_masks[] = {0x7, 0}; + +int hmc5843_common_suspend(struct device *dev) +{ + return hmc5843_set_mode(iio_priv(dev_get_drvdata(dev)), + HMC5843_MODE_CONVERSION_CONTINUOUS); +} +EXPORT_SYMBOL(hmc5843_common_suspend); + +int hmc5843_common_resume(struct device *dev) +{ + return hmc5843_set_mode(iio_priv(dev_get_drvdata(dev)), + HMC5843_MODE_SLEEP); +} +EXPORT_SYMBOL(hmc5843_common_resume); + +int hmc5843_common_probe(struct device *dev, struct regmap *regmap, + enum hmc5843_ids id) +{ + struct hmc5843_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + dev_set_drvdata(dev, indio_dev); + + /* default settings at probe */ + data = iio_priv(indio_dev); + data->dev = dev; + data->regmap = regmap; + data->variant = &hmc5843_chip_info_tbl[id]; + mutex_init(&data->lock); + + indio_dev->dev.parent = dev; + indio_dev->name = dev->driver->name; + indio_dev->info = &hmc5843_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = data->variant->channels; + indio_dev->num_channels = 4; + indio_dev->available_scan_masks = hmc5843_scan_masks; + + ret = hmc5843_init(data); + if (ret < 0) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + hmc5843_trigger_handler, NULL); + if (ret < 0) + goto buffer_setup_err; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +buffer_setup_err: + hmc5843_set_mode(iio_priv(indio_dev), HMC5843_MODE_SLEEP); + return ret; +} +EXPORT_SYMBOL(hmc5843_common_probe); + +int hmc5843_common_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + /* sleep mode to save power */ + hmc5843_set_mode(iio_priv(indio_dev), HMC5843_MODE_SLEEP); + + return 0; +} +EXPORT_SYMBOL(hmc5843_common_remove); + +MODULE_AUTHOR("Shubhrajyoti Datta "); +MODULE_DESCRIPTION("HMC5843/5883/5883L/5983 core driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/staging/iio/magnetometer/hmc5843_i2c.c b/kernel/drivers/staging/iio/magnetometer/hmc5843_i2c.c new file mode 100644 index 000000000..ff08667fa --- /dev/null +++ b/kernel/drivers/staging/iio/magnetometer/hmc5843_i2c.c @@ -0,0 +1,104 @@ +/* + * i2c driver for hmc5843/5843/5883/5883l/5983 + * + * Split from hmc5843.c + * Copyright (C) Josef Gajdusek + * + * 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 +#include +#include +#include +#include + +#include "hmc5843.h" + +static const struct regmap_range hmc5843_readable_ranges[] = { + regmap_reg_range(0, HMC5843_ID_END), +}; + +static const struct regmap_access_table hmc5843_readable_table = { + .yes_ranges = hmc5843_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_readable_ranges), +}; + +static const struct regmap_range hmc5843_writable_ranges[] = { + regmap_reg_range(0, HMC5843_MODE_REG), +}; + +static const struct regmap_access_table hmc5843_writable_table = { + .yes_ranges = hmc5843_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_writable_ranges), +}; + +static const struct regmap_range hmc5843_volatile_ranges[] = { + regmap_reg_range(HMC5843_DATA_OUT_MSB_REGS, HMC5843_STATUS_REG), +}; + +static const struct regmap_access_table hmc5843_volatile_table = { + .yes_ranges = hmc5843_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_volatile_ranges), +}; + +static const struct regmap_config hmc5843_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .rd_table = &hmc5843_readable_table, + .wr_table = &hmc5843_writable_table, + .volatile_table = &hmc5843_volatile_table, + + .cache_type = REGCACHE_RBTREE, +}; + +static int hmc5843_i2c_probe(struct i2c_client *cli, + const struct i2c_device_id *id) +{ + return hmc5843_common_probe(&cli->dev, + devm_regmap_init_i2c(cli, &hmc5843_i2c_regmap_config), + id->driver_data); +} + +static int hmc5843_i2c_remove(struct i2c_client *client) +{ + return hmc5843_common_remove(&client->dev); +} + +static const struct i2c_device_id hmc5843_id[] = { + { "hmc5843", HMC5843_ID }, + { "hmc5883", HMC5883_ID }, + { "hmc5883l", HMC5883L_ID }, + { "hmc5983", HMC5983_ID }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hmc5843_id); + +static const struct of_device_id hmc5843_of_match[] = { + { .compatible = "honeywell,hmc5843", .data = (void *)HMC5843_ID }, + { .compatible = "honeywell,hmc5883", .data = (void *)HMC5883_ID }, + { .compatible = "honeywell,hmc5883l", .data = (void *)HMC5883L_ID }, + { .compatible = "honeywell,hmc5983", .data = (void *)HMC5983_ID }, + {} +}; +MODULE_DEVICE_TABLE(of, hmc5843_of_match); + +static struct i2c_driver hmc5843_driver = { + .driver = { + .name = "hmc5843", + .pm = HMC5843_PM_OPS, + .of_match_table = hmc5843_of_match, + }, + .id_table = hmc5843_id, + .probe = hmc5843_i2c_probe, + .remove = hmc5843_i2c_remove, +}; +module_i2c_driver(hmc5843_driver); + +MODULE_AUTHOR("Josef Gajdusek "); +MODULE_DESCRIPTION("HMC5843/5883/5883L/5983 i2c driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/staging/iio/magnetometer/hmc5843_spi.c b/kernel/drivers/staging/iio/magnetometer/hmc5843_spi.c new file mode 100644 index 000000000..8e658f736 --- /dev/null +++ b/kernel/drivers/staging/iio/magnetometer/hmc5843_spi.c @@ -0,0 +1,100 @@ +/* + * SPI driver for hmc5983 + * + * Copyright (C) Josef Gajdusek + * + * 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 +#include +#include + +#include "hmc5843.h" + +static const struct regmap_range hmc5843_readable_ranges[] = { + regmap_reg_range(0, HMC5843_ID_END), +}; + +static const struct regmap_access_table hmc5843_readable_table = { + .yes_ranges = hmc5843_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_readable_ranges), +}; + +static const struct regmap_range hmc5843_writable_ranges[] = { + regmap_reg_range(0, HMC5843_MODE_REG), +}; + +static const struct regmap_access_table hmc5843_writable_table = { + .yes_ranges = hmc5843_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_writable_ranges), +}; + +static const struct regmap_range hmc5843_volatile_ranges[] = { + regmap_reg_range(HMC5843_DATA_OUT_MSB_REGS, HMC5843_STATUS_REG), +}; + +static const struct regmap_access_table hmc5843_volatile_table = { + .yes_ranges = hmc5843_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_volatile_ranges), +}; + +static const struct regmap_config hmc5843_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .rd_table = &hmc5843_readable_table, + .wr_table = &hmc5843_writable_table, + .volatile_table = &hmc5843_volatile_table, + + /* Autoincrement address pointer */ + .read_flag_mask = 0xc0, + + .cache_type = REGCACHE_RBTREE, +}; + +static int hmc5843_spi_probe(struct spi_device *spi) +{ + int ret; + + spi->mode = SPI_MODE_3; + spi->max_speed_hz = 8000000; + spi->bits_per_word = 8; + ret = spi_setup(spi); + if (ret) + return ret; + + return hmc5843_common_probe(&spi->dev, + devm_regmap_init_spi(spi, &hmc5843_spi_regmap_config), + HMC5983_ID); +} + +static int hmc5843_spi_remove(struct spi_device *spi) +{ + return hmc5843_common_remove(&spi->dev); +} + +static const struct spi_device_id hmc5843_id[] = { + { "hmc5983", HMC5983_ID }, + { } +}; + +static struct spi_driver hmc5843_driver = { + .driver = { + .name = "hmc5843", + .pm = HMC5843_PM_OPS, + .owner = THIS_MODULE, + }, + .id_table = hmc5843_id, + .probe = hmc5843_spi_probe, + .remove = hmc5843_spi_remove, +}; + +module_spi_driver(hmc5843_driver); + +MODULE_AUTHOR("Josef Gajdusek "); +MODULE_DESCRIPTION("HMC5983 SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/staging/iio/meter/Kconfig b/kernel/drivers/staging/iio/meter/Kconfig new file mode 100644 index 000000000..64cd3704e --- /dev/null +++ b/kernel/drivers/staging/iio/meter/Kconfig @@ -0,0 +1,78 @@ +# +# IIO meter drivers configuration +# +menu "Active energy metering IC" + +config ADE7753 + tristate "Analog Devices ADE7753/6 Single-Phase Multifunction Metering IC Driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADE7753 Single-Phase Multifunction + Metering IC with di/dt Sensor Interface. + + To compile this driver as a module, choose M here: the + module will be called ade7753. + +config ADE7754 + tristate "Analog Devices ADE7754 Polyphase Multifunction Energy Metering IC Driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADE7754 Polyphase + Multifunction Energy Metering IC Driver. + + To compile this driver as a module, choose M here: the + module will be called ade7754. + +config ADE7758 + tristate "Analog Devices ADE7758 Poly Phase Multifunction Energy Metering IC Driver" + depends on SPI + select IIO_TRIGGER if IIO_BUFFER + select IIO_KFIFO_BUF if IIO_BUFFER + help + Say yes here to build support for Analog Devices ADE7758 Polyphase + Multifunction Energy Metering IC with Per Phase Information Driver. + + To compile this driver as a module, choose M here: the + module will be called ade7758. + +config ADE7759 + tristate "Analog Devices ADE7759 Active Energy Metering IC Driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADE7758 Active Energy + Metering IC with di/dt Sensor Interface. + + To compile this driver as a module, choose M here: the + module will be called ade7759. + +config ADE7854 + tristate "Analog Devices ADE7854/58/68/78 Polyphase Multifunction Energy Metering IC Driver" + depends on SPI || I2C + help + Say yes here to build support for Analog Devices ADE7854/58/68/78 Polyphase + Multifunction Energy Metering IC Driver. + + To compile this driver as a module, choose M here: the + module will be called ade7854. + +config ADE7854_I2C + tristate "support I2C bus connection" + depends on ADE7854 && I2C + default y + help + Say Y here if you have ADE7854/58/68/78 hooked to an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called ade7854-i2c. + +config ADE7854_SPI + tristate "support SPI bus connection" + depends on ADE7854 && SPI + default y + help + Say Y here if you have ADE7854/58/68/78 hooked to a SPI bus. + + To compile this driver as a module, choose M here: the + module will be called ade7854-spi. + +endmenu diff --git a/kernel/drivers/staging/iio/meter/Makefile b/kernel/drivers/staging/iio/meter/Makefile new file mode 100644 index 000000000..de3863d6b --- /dev/null +++ b/kernel/drivers/staging/iio/meter/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for metering ic drivers +# + +obj-$(CONFIG_ADE7753) += ade7753.o +obj-$(CONFIG_ADE7754) += ade7754.o + +ade7758-y := ade7758_core.o +ade7758-$(CONFIG_IIO_BUFFER) += ade7758_ring.o ade7758_trigger.o +obj-$(CONFIG_ADE7758) += ade7758.o + +obj-$(CONFIG_ADE7759) += ade7759.o +obj-$(CONFIG_ADE7854) += ade7854.o +obj-$(CONFIG_ADE7854_I2C) += ade7854-i2c.o +obj-$(CONFIG_ADE7854_SPI) += ade7854-spi.o diff --git a/kernel/drivers/staging/iio/meter/ade7753.c b/kernel/drivers/staging/iio/meter/ade7753.c new file mode 100644 index 000000000..ffc7f0ddf --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7753.c @@ -0,0 +1,547 @@ +/* + * ADE7753 Single-Phase Multifunction Metering IC with di/dt Sensor Interface + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "meter.h" +#include "ade7753.h" + +static int ade7753_spi_write_reg_8(struct device *dev, + u8 reg_address, + u8 val) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7753_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7753_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7753_spi_write_reg_16(struct device *dev, + u8 reg_address, + u16 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7753_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7753_WRITE_REG(reg_address); + st->tx[1] = (value >> 8) & 0xFF; + st->tx[2] = value & 0xFF; + ret = spi_write(st->us, st->tx, 3); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7753_spi_read_reg_8(struct device *dev, + u8 reg_address, + u8 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7753_state *st = iio_priv(indio_dev); + ssize_t ret; + + ret = spi_w8r8(st->us, ADE7753_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 8 bit register 0x%02X", + reg_address); + return ret; + } + *val = ret; + + return 0; +} + +static int ade7753_spi_read_reg_16(struct device *dev, + u8 reg_address, + u16 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7753_state *st = iio_priv(indio_dev); + ssize_t ret; + + ret = spi_w8r16be(st->us, ADE7753_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + reg_address); + return ret; + } + + *val = ret; + + return 0; +} + +static int ade7753_spi_read_reg_24(struct device *dev, + u8 reg_address, + u32 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7753_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 1, + }, { + .rx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + } + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7753_READ_REG(reg_address); + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "problem when reading 24 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = (st->rx[0] << 16) | (st->rx[1] << 8) | st->rx[2]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t ade7753_read_8bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7753_spi_read_reg_8(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7753_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7753_spi_read_reg_16(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7753_read_24bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7753_spi_read_reg_24(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7753_write_8bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7753_spi_write_reg_8(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7753_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + u16 val; + + ret = kstrtou16(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7753_spi_write_reg_16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int ade7753_reset(struct device *dev) +{ + u16 val; + + ade7753_spi_read_reg_16(dev, ADE7753_MODE, &val); + val |= 1 << 6; /* Software Chip Reset */ + + return ade7753_spi_write_reg_16(dev, ADE7753_MODE, val); +} + +static IIO_DEV_ATTR_AENERGY(ade7753_read_24bit, ADE7753_AENERGY); +static IIO_DEV_ATTR_LAENERGY(ade7753_read_24bit, ADE7753_LAENERGY); +static IIO_DEV_ATTR_VAENERGY(ade7753_read_24bit, ADE7753_VAENERGY); +static IIO_DEV_ATTR_LVAENERGY(ade7753_read_24bit, ADE7753_LVAENERGY); +static IIO_DEV_ATTR_CFDEN(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_CFDEN); +static IIO_DEV_ATTR_CFNUM(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_CFNUM); +static IIO_DEV_ATTR_CHKSUM(ade7753_read_8bit, ADE7753_CHKSUM); +static IIO_DEV_ATTR_PHCAL(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_PHCAL); +static IIO_DEV_ATTR_APOS(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_APOS); +static IIO_DEV_ATTR_SAGCYC(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_SAGCYC); +static IIO_DEV_ATTR_SAGLVL(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_SAGLVL); +static IIO_DEV_ATTR_LINECYC(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_LINECYC); +static IIO_DEV_ATTR_WDIV(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_WDIV); +static IIO_DEV_ATTR_IRMS(S_IWUSR | S_IRUGO, + ade7753_read_24bit, + NULL, + ADE7753_IRMS); +static IIO_DEV_ATTR_VRMS(S_IRUGO, + ade7753_read_24bit, + NULL, + ADE7753_VRMS); +static IIO_DEV_ATTR_IRMSOS(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_IRMSOS); +static IIO_DEV_ATTR_VRMSOS(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_VRMSOS); +static IIO_DEV_ATTR_WGAIN(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_WGAIN); +static IIO_DEV_ATTR_VAGAIN(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_VAGAIN); +static IIO_DEV_ATTR_PGA_GAIN(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_GAIN); +static IIO_DEV_ATTR_IPKLVL(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_IPKLVL); +static IIO_DEV_ATTR_VPKLVL(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_VPKLVL); +static IIO_DEV_ATTR_IPEAK(S_IRUGO, + ade7753_read_24bit, + NULL, + ADE7753_IPEAK); +static IIO_DEV_ATTR_VPEAK(S_IRUGO, + ade7753_read_24bit, + NULL, + ADE7753_VPEAK); +static IIO_DEV_ATTR_VPERIOD(S_IRUGO, + ade7753_read_16bit, + NULL, + ADE7753_PERIOD); +static IIO_DEV_ATTR_CH_OFF(1, S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_CH1OS); +static IIO_DEV_ATTR_CH_OFF(2, S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_CH2OS); + +static int ade7753_set_irq(struct device *dev, bool enable) +{ + int ret; + u8 irqen; + + ret = ade7753_spi_read_reg_8(dev, ADE7753_IRQEN, &irqen); + if (ret) + goto error_ret; + + if (enable) + irqen |= 1 << 3; /* Enables an interrupt when a data is + present in the waveform register */ + else + irqen &= ~(1 << 3); + + ret = ade7753_spi_write_reg_8(dev, ADE7753_IRQEN, irqen); + +error_ret: + return ret; +} + +/* Power down the device */ +static int ade7753_stop_device(struct device *dev) +{ + u16 val; + + ade7753_spi_read_reg_16(dev, ADE7753_MODE, &val); + val |= 1 << 4; /* AD converters can be turned off */ + + return ade7753_spi_write_reg_16(dev, ADE7753_MODE, val); +} + +static int ade7753_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct device *dev = &indio_dev->dev; + struct ade7753_state *st = iio_priv(indio_dev); + + /* use low spi speed for init */ + st->us->mode = SPI_MODE_3; + spi_setup(st->us); + + /* Disable IRQ */ + ret = ade7753_set_irq(dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + ade7753_reset(dev); + msleep(ADE7753_STARTUP_DELAY); + +err_ret: + return ret; +} + +static ssize_t ade7753_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 t; + int sps; + + ret = ade7753_spi_read_reg_16(dev, ADE7753_MODE, &t); + if (ret) + return ret; + + t = (t >> 11) & 0x3; + sps = 27900 / (1 + t); + + return sprintf(buf, "%d\n", sps); +} + +static ssize_t ade7753_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 ade7753_state *st = iio_priv(indio_dev); + u16 val; + int ret; + u16 reg, t; + + ret = kstrtou16(buf, 10, &val); + if (ret) + return ret; + if (val == 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + t = 27900 / val; + if (t > 0) + t--; + + if (t > 1) + st->us->max_speed_hz = ADE7753_SPI_SLOW; + else + st->us->max_speed_hz = ADE7753_SPI_FAST; + + ret = ade7753_spi_read_reg_16(dev, ADE7753_MODE, ®); + if (ret) + goto out; + + reg &= ~(3 << 11); + reg |= t << 11; + + ret = ade7753_spi_write_reg_16(dev, ADE7753_MODE, reg); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_TEMP_RAW(ade7753_read_8bit); +static IIO_CONST_ATTR(in_temp_offset, "-25 C"); +static IIO_CONST_ATTR(in_temp_scale, "0.67 C"); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ade7753_read_frequency, + ade7753_write_frequency); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("27900 14000 7000 3500"); + +static struct attribute *ade7753_attributes[] = { + &iio_dev_attr_in_temp_raw.dev_attr.attr, + &iio_const_attr_in_temp_offset.dev_attr.attr, + &iio_const_attr_in_temp_scale.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_phcal.dev_attr.attr, + &iio_dev_attr_cfden.dev_attr.attr, + &iio_dev_attr_aenergy.dev_attr.attr, + &iio_dev_attr_laenergy.dev_attr.attr, + &iio_dev_attr_vaenergy.dev_attr.attr, + &iio_dev_attr_lvaenergy.dev_attr.attr, + &iio_dev_attr_cfnum.dev_attr.attr, + &iio_dev_attr_apos.dev_attr.attr, + &iio_dev_attr_sagcyc.dev_attr.attr, + &iio_dev_attr_saglvl.dev_attr.attr, + &iio_dev_attr_linecyc.dev_attr.attr, + &iio_dev_attr_chksum.dev_attr.attr, + &iio_dev_attr_pga_gain.dev_attr.attr, + &iio_dev_attr_wgain.dev_attr.attr, + &iio_dev_attr_choff_1.dev_attr.attr, + &iio_dev_attr_choff_2.dev_attr.attr, + &iio_dev_attr_wdiv.dev_attr.attr, + &iio_dev_attr_irms.dev_attr.attr, + &iio_dev_attr_vrms.dev_attr.attr, + &iio_dev_attr_irmsos.dev_attr.attr, + &iio_dev_attr_vrmsos.dev_attr.attr, + &iio_dev_attr_vagain.dev_attr.attr, + &iio_dev_attr_ipklvl.dev_attr.attr, + &iio_dev_attr_vpklvl.dev_attr.attr, + &iio_dev_attr_ipeak.dev_attr.attr, + &iio_dev_attr_vpeak.dev_attr.attr, + &iio_dev_attr_vperiod.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ade7753_attribute_group = { + .attrs = ade7753_attributes, +}; + +static const struct iio_info ade7753_info = { + .attrs = &ade7753_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ade7753_probe(struct spi_device *spi) +{ + int ret; + struct ade7753_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ade7753_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Get the device into a sane initial state */ + ret = ade7753_initial_setup(indio_dev); + if (ret) + return ret; + + return iio_device_register(indio_dev); +} + +/* fixme, confirm ordering in this function */ +static int ade7753_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + ade7753_stop_device(&indio_dev->dev); + + return 0; +} + +static struct spi_driver ade7753_driver = { + .driver = { + .name = "ade7753", + .owner = THIS_MODULE, + }, + .probe = ade7753_probe, + .remove = ade7753_remove, +}; +module_spi_driver(ade7753_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7753/6 Single-Phase Multifunction Meter"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:ade7753"); diff --git a/kernel/drivers/staging/iio/meter/ade7753.h b/kernel/drivers/staging/iio/meter/ade7753.h new file mode 100644 index 000000000..a9d93cc1c --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7753.h @@ -0,0 +1,72 @@ +#ifndef _ADE7753_H +#define _ADE7753_H + +#define ADE7753_WAVEFORM 0x01 +#define ADE7753_AENERGY 0x02 +#define ADE7753_RAENERGY 0x03 +#define ADE7753_LAENERGY 0x04 +#define ADE7753_VAENERGY 0x05 +#define ADE7753_RVAENERGY 0x06 +#define ADE7753_LVAENERGY 0x07 +#define ADE7753_LVARENERGY 0x08 +#define ADE7753_MODE 0x09 +#define ADE7753_IRQEN 0x0A +#define ADE7753_STATUS 0x0B +#define ADE7753_RSTSTATUS 0x0C +#define ADE7753_CH1OS 0x0D +#define ADE7753_CH2OS 0x0E +#define ADE7753_GAIN 0x0F +#define ADE7753_PHCAL 0x10 +#define ADE7753_APOS 0x11 +#define ADE7753_WGAIN 0x12 +#define ADE7753_WDIV 0x13 +#define ADE7753_CFNUM 0x14 +#define ADE7753_CFDEN 0x15 +#define ADE7753_IRMS 0x16 +#define ADE7753_VRMS 0x17 +#define ADE7753_IRMSOS 0x18 +#define ADE7753_VRMSOS 0x19 +#define ADE7753_VAGAIN 0x1A +#define ADE7753_VADIV 0x1B +#define ADE7753_LINECYC 0x1C +#define ADE7753_ZXTOUT 0x1D +#define ADE7753_SAGCYC 0x1E +#define ADE7753_SAGLVL 0x1F +#define ADE7753_IPKLVL 0x20 +#define ADE7753_VPKLVL 0x21 +#define ADE7753_IPEAK 0x22 +#define ADE7753_RSTIPEAK 0x23 +#define ADE7753_VPEAK 0x24 +#define ADE7753_RSTVPEAK 0x25 +#define ADE7753_TEMP 0x26 +#define ADE7753_PERIOD 0x27 +#define ADE7753_TMODE 0x3D +#define ADE7753_CHKSUM 0x3E +#define ADE7753_DIEREV 0x3F + +#define ADE7753_READ_REG(a) a +#define ADE7753_WRITE_REG(a) ((a) | 0x80) + +#define ADE7753_MAX_TX 4 +#define ADE7753_MAX_RX 4 +#define ADE7753_STARTUP_DELAY 1 + +#define ADE7753_SPI_SLOW (u32)(300 * 1000) +#define ADE7753_SPI_BURST (u32)(1000 * 1000) +#define ADE7753_SPI_FAST (u32)(2000 * 1000) + +/** + * struct ade7753_state - device instance specific data + * @us: actual spi_device + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct ade7753_state { + struct spi_device *us; + struct mutex buf_lock; + u8 tx[ADE7753_MAX_TX] ____cacheline_aligned; + u8 rx[ADE7753_MAX_RX]; +}; + +#endif diff --git a/kernel/drivers/staging/iio/meter/ade7754.c b/kernel/drivers/staging/iio/meter/ade7754.c new file mode 100644 index 000000000..f12b2e503 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7754.c @@ -0,0 +1,588 @@ +/* + * ADE7754 Polyphase Multifunction Energy Metering IC Driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "meter.h" +#include "ade7754.h" + +static int ade7754_spi_write_reg_8(struct device *dev, + u8 reg_address, + u8 val) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7754_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7754_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7754_spi_write_reg_16(struct device *dev, + u8 reg_address, + u16 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7754_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7754_WRITE_REG(reg_address); + st->tx[1] = (value >> 8) & 0xFF; + st->tx[2] = value & 0xFF; + ret = spi_write(st->us, st->tx, 3); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7754_spi_read_reg_8(struct device *dev, + u8 reg_address, + u8 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7754_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_w8r8(st->us, ADE7754_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 8 bit register 0x%02X", + reg_address); + return ret; + } + *val = ret; + + return 0; +} + +static int ade7754_spi_read_reg_16(struct device *dev, + u8 reg_address, + u16 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7754_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_w8r16be(st->us, ADE7754_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + reg_address); + return ret; + } + + *val = ret; + + return 0; +} + +static int ade7754_spi_read_reg_24(struct device *dev, + u8 reg_address, + u32 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7754_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 4, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7754_READ_REG(reg_address); + st->tx[1] = 0; + st->tx[2] = 0; + st->tx[3] = 0; + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "problem when reading 24 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = (st->rx[1] << 16) | (st->rx[2] << 8) | st->rx[3]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t ade7754_read_8bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7754_spi_read_reg_8(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7754_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7754_spi_read_reg_16(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7754_read_24bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7754_spi_read_reg_24(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val & 0xFFFFFF); +} + +static ssize_t ade7754_write_8bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7754_spi_write_reg_8(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7754_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + u16 val; + + ret = kstrtou16(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7754_spi_write_reg_16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int ade7754_reset(struct device *dev) +{ + int ret; + u8 val; + + ret = ade7754_spi_read_reg_8(dev, ADE7754_OPMODE, &val); + if (ret < 0) + return ret; + + val |= 1 << 6; /* Software Chip Reset */ + return ade7754_spi_write_reg_8(dev, ADE7754_OPMODE, val); +} + +static IIO_DEV_ATTR_AENERGY(ade7754_read_24bit, ADE7754_AENERGY); +static IIO_DEV_ATTR_LAENERGY(ade7754_read_24bit, ADE7754_LAENERGY); +static IIO_DEV_ATTR_VAENERGY(ade7754_read_24bit, ADE7754_VAENERGY); +static IIO_DEV_ATTR_LVAENERGY(ade7754_read_24bit, ADE7754_LVAENERGY); +static IIO_DEV_ATTR_VPEAK(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_VPEAK); +static IIO_DEV_ATTR_IPEAK(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_VPEAK); +static IIO_DEV_ATTR_APHCAL(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_APHCAL); +static IIO_DEV_ATTR_BPHCAL(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_BPHCAL); +static IIO_DEV_ATTR_CPHCAL(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_CPHCAL); +static IIO_DEV_ATTR_AAPOS(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_AAPOS); +static IIO_DEV_ATTR_BAPOS(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_BAPOS); +static IIO_DEV_ATTR_CAPOS(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CAPOS); +static IIO_DEV_ATTR_WDIV(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_WDIV); +static IIO_DEV_ATTR_VADIV(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_VADIV); +static IIO_DEV_ATTR_CFNUM(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CFNUM); +static IIO_DEV_ATTR_CFDEN(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CFDEN); +static IIO_DEV_ATTR_ACTIVE_POWER_A_GAIN(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_AAPGAIN); +static IIO_DEV_ATTR_ACTIVE_POWER_B_GAIN(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_BAPGAIN); +static IIO_DEV_ATTR_ACTIVE_POWER_C_GAIN(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CAPGAIN); +static IIO_DEV_ATTR_AIRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_AIRMS); +static IIO_DEV_ATTR_BIRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_BIRMS); +static IIO_DEV_ATTR_CIRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_CIRMS); +static IIO_DEV_ATTR_AVRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_AVRMS); +static IIO_DEV_ATTR_BVRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_BVRMS); +static IIO_DEV_ATTR_CVRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_CVRMS); +static IIO_DEV_ATTR_AIRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_AIRMSOS); +static IIO_DEV_ATTR_BIRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_BIRMSOS); +static IIO_DEV_ATTR_CIRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CIRMSOS); +static IIO_DEV_ATTR_AVRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_AVRMSOS); +static IIO_DEV_ATTR_BVRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_BVRMSOS); +static IIO_DEV_ATTR_CVRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CVRMSOS); + +static int ade7754_set_irq(struct device *dev, bool enable) +{ + int ret; + u16 irqen; + + ret = ade7754_spi_read_reg_16(dev, ADE7754_IRQEN, &irqen); + if (ret) + goto error_ret; + + if (enable) + irqen |= 1 << 14; /* Enables an interrupt when a data is + present in the waveform register */ + else + irqen &= ~(1 << 14); + + ret = ade7754_spi_write_reg_16(dev, ADE7754_IRQEN, irqen); + if (ret) + goto error_ret; + +error_ret: + return ret; +} + +/* Power down the device */ +static int ade7754_stop_device(struct device *dev) +{ + int ret; + u8 val; + + ret = ade7754_spi_read_reg_8(dev, ADE7754_OPMODE, &val); + if (ret < 0) { + dev_err(dev, "unable to power down the device, error: %d", + ret); + return ret; + } + + val |= 7 << 3; /* ADE7754 powered down */ + return ade7754_spi_write_reg_8(dev, ADE7754_OPMODE, val); +} + +static int ade7754_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct ade7754_state *st = iio_priv(indio_dev); + struct device *dev = &indio_dev->dev; + + /* use low spi speed for init */ + st->us->mode = SPI_MODE_3; + spi_setup(st->us); + + /* Disable IRQ */ + ret = ade7754_set_irq(dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + ade7754_reset(dev); + msleep(ADE7754_STARTUP_DELAY); + +err_ret: + return ret; +} + +static ssize_t ade7754_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 t; + int sps; + + ret = ade7754_spi_read_reg_8(dev, + ADE7754_WAVMODE, + &t); + if (ret) + return ret; + + t = (t >> 3) & 0x3; + sps = 26000 / (1 + t); + + return sprintf(buf, "%d\n", sps); +} + +static ssize_t ade7754_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 ade7754_state *st = iio_priv(indio_dev); + u16 val; + int ret; + u8 reg, t; + + ret = kstrtou16(buf, 10, &val); + if (ret) + return ret; + if (val == 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + t = 26000 / val; + if (t > 0) + t--; + + if (t > 1) + st->us->max_speed_hz = ADE7754_SPI_SLOW; + else + st->us->max_speed_hz = ADE7754_SPI_FAST; + + ret = ade7754_spi_read_reg_8(dev, ADE7754_WAVMODE, ®); + if (ret) + goto out; + + reg &= ~(3 << 3); + reg |= t << 3; + + ret = ade7754_spi_write_reg_8(dev, ADE7754_WAVMODE, reg); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} +static IIO_DEV_ATTR_TEMP_RAW(ade7754_read_8bit); +static IIO_CONST_ATTR(in_temp_offset, "129 C"); +static IIO_CONST_ATTR(in_temp_scale, "4 C"); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ade7754_read_frequency, + ade7754_write_frequency); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("26000 13000 65000 33000"); + +static struct attribute *ade7754_attributes[] = { + &iio_dev_attr_in_temp_raw.dev_attr.attr, + &iio_const_attr_in_temp_offset.dev_attr.attr, + &iio_const_attr_in_temp_scale.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_aenergy.dev_attr.attr, + &iio_dev_attr_laenergy.dev_attr.attr, + &iio_dev_attr_vaenergy.dev_attr.attr, + &iio_dev_attr_lvaenergy.dev_attr.attr, + &iio_dev_attr_vpeak.dev_attr.attr, + &iio_dev_attr_ipeak.dev_attr.attr, + &iio_dev_attr_aphcal.dev_attr.attr, + &iio_dev_attr_bphcal.dev_attr.attr, + &iio_dev_attr_cphcal.dev_attr.attr, + &iio_dev_attr_aapos.dev_attr.attr, + &iio_dev_attr_bapos.dev_attr.attr, + &iio_dev_attr_capos.dev_attr.attr, + &iio_dev_attr_wdiv.dev_attr.attr, + &iio_dev_attr_vadiv.dev_attr.attr, + &iio_dev_attr_cfnum.dev_attr.attr, + &iio_dev_attr_cfden.dev_attr.attr, + &iio_dev_attr_active_power_a_gain.dev_attr.attr, + &iio_dev_attr_active_power_b_gain.dev_attr.attr, + &iio_dev_attr_active_power_c_gain.dev_attr.attr, + &iio_dev_attr_airms.dev_attr.attr, + &iio_dev_attr_birms.dev_attr.attr, + &iio_dev_attr_cirms.dev_attr.attr, + &iio_dev_attr_avrms.dev_attr.attr, + &iio_dev_attr_bvrms.dev_attr.attr, + &iio_dev_attr_cvrms.dev_attr.attr, + &iio_dev_attr_airmsos.dev_attr.attr, + &iio_dev_attr_birmsos.dev_attr.attr, + &iio_dev_attr_cirmsos.dev_attr.attr, + &iio_dev_attr_avrmsos.dev_attr.attr, + &iio_dev_attr_bvrmsos.dev_attr.attr, + &iio_dev_attr_cvrmsos.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ade7754_attribute_group = { + .attrs = ade7754_attributes, +}; + +static const struct iio_info ade7754_info = { + .attrs = &ade7754_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ade7754_probe(struct spi_device *spi) +{ + int ret; + struct ade7754_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ade7754_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Get the device into a sane initial state */ + ret = ade7754_initial_setup(indio_dev); + if (ret) + goto powerdown_on_error; + ret = iio_device_register(indio_dev); + if (ret) + goto powerdown_on_error; + return ret; + +powerdown_on_error: + ade7754_stop_device(&indio_dev->dev); + return ret; +} + +/* fixme, confirm ordering in this function */ +static int ade7754_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + ade7754_stop_device(&indio_dev->dev); + + return 0; +} + +static struct spi_driver ade7754_driver = { + .driver = { + .name = "ade7754", + .owner = THIS_MODULE, + }, + .probe = ade7754_probe, + .remove = ade7754_remove, +}; +module_spi_driver(ade7754_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7754 Polyphase Multifunction Energy Metering IC Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:ad7754"); diff --git a/kernel/drivers/staging/iio/meter/ade7754.h b/kernel/drivers/staging/iio/meter/ade7754.h new file mode 100644 index 000000000..e42ffc387 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7754.h @@ -0,0 +1,90 @@ +#ifndef _ADE7754_H +#define _ADE7754_H + +#define ADE7754_AENERGY 0x01 +#define ADE7754_RAENERGY 0x02 +#define ADE7754_LAENERGY 0x03 +#define ADE7754_VAENERGY 0x04 +#define ADE7754_RVAENERGY 0x05 +#define ADE7754_LVAENERGY 0x06 +#define ADE7754_PERIOD 0x07 +#define ADE7754_TEMP 0x08 +#define ADE7754_WFORM 0x09 +#define ADE7754_OPMODE 0x0A +#define ADE7754_MMODE 0x0B +#define ADE7754_WAVMODE 0x0C +#define ADE7754_WATMODE 0x0D +#define ADE7754_VAMODE 0x0E +#define ADE7754_IRQEN 0x0F +#define ADE7754_STATUS 0x10 +#define ADE7754_RSTATUS 0x11 +#define ADE7754_ZXTOUT 0x12 +#define ADE7754_LINCYC 0x13 +#define ADE7754_SAGCYC 0x14 +#define ADE7754_SAGLVL 0x15 +#define ADE7754_VPEAK 0x16 +#define ADE7754_IPEAK 0x17 +#define ADE7754_GAIN 0x18 +#define ADE7754_AWG 0x19 +#define ADE7754_BWG 0x1A +#define ADE7754_CWG 0x1B +#define ADE7754_AVAG 0x1C +#define ADE7754_BVAG 0x1D +#define ADE7754_CVAG 0x1E +#define ADE7754_APHCAL 0x1F +#define ADE7754_BPHCAL 0x20 +#define ADE7754_CPHCAL 0x21 +#define ADE7754_AAPOS 0x22 +#define ADE7754_BAPOS 0x23 +#define ADE7754_CAPOS 0x24 +#define ADE7754_CFNUM 0x25 +#define ADE7754_CFDEN 0x26 +#define ADE7754_WDIV 0x27 +#define ADE7754_VADIV 0x28 +#define ADE7754_AIRMS 0x29 +#define ADE7754_BIRMS 0x2A +#define ADE7754_CIRMS 0x2B +#define ADE7754_AVRMS 0x2C +#define ADE7754_BVRMS 0x2D +#define ADE7754_CVRMS 0x2E +#define ADE7754_AIRMSOS 0x2F +#define ADE7754_BIRMSOS 0x30 +#define ADE7754_CIRMSOS 0x31 +#define ADE7754_AVRMSOS 0x32 +#define ADE7754_BVRMSOS 0x33 +#define ADE7754_CVRMSOS 0x34 +#define ADE7754_AAPGAIN 0x35 +#define ADE7754_BAPGAIN 0x36 +#define ADE7754_CAPGAIN 0x37 +#define ADE7754_AVGAIN 0x38 +#define ADE7754_BVGAIN 0x39 +#define ADE7754_CVGAIN 0x3A +#define ADE7754_CHKSUM 0x3E +#define ADE7754_VERSION 0x3F + +#define ADE7754_READ_REG(a) a +#define ADE7754_WRITE_REG(a) ((a) | 0x80) + +#define ADE7754_MAX_TX 4 +#define ADE7754_MAX_RX 4 +#define ADE7754_STARTUP_DELAY 1 + +#define ADE7754_SPI_SLOW (u32)(300 * 1000) +#define ADE7754_SPI_BURST (u32)(1000 * 1000) +#define ADE7754_SPI_FAST (u32)(2000 * 1000) + +/** + * struct ade7754_state - device instance specific data + * @us: actual spi_device + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct ade7754_state { + struct spi_device *us; + struct mutex buf_lock; + u8 tx[ADE7754_MAX_TX] ____cacheline_aligned; + u8 rx[ADE7754_MAX_RX]; +}; + +#endif diff --git a/kernel/drivers/staging/iio/meter/ade7758.h b/kernel/drivers/staging/iio/meter/ade7758.h new file mode 100644 index 000000000..f6739e2c2 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7758.h @@ -0,0 +1,181 @@ +/* + * ADE7758 Poly Phase Multifunction Energy Metering IC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef _ADE7758_H +#define _ADE7758_H + +#define ADE7758_AWATTHR 0x01 +#define ADE7758_BWATTHR 0x02 +#define ADE7758_CWATTHR 0x03 +#define ADE7758_AVARHR 0x04 +#define ADE7758_BVARHR 0x05 +#define ADE7758_CVARHR 0x06 +#define ADE7758_AVAHR 0x07 +#define ADE7758_BVAHR 0x08 +#define ADE7758_CVAHR 0x09 +#define ADE7758_AIRMS 0x0A +#define ADE7758_BIRMS 0x0B +#define ADE7758_CIRMS 0x0C +#define ADE7758_AVRMS 0x0D +#define ADE7758_BVRMS 0x0E +#define ADE7758_CVRMS 0x0F +#define ADE7758_FREQ 0x10 +#define ADE7758_TEMP 0x11 +#define ADE7758_WFORM 0x12 +#define ADE7758_OPMODE 0x13 +#define ADE7758_MMODE 0x14 +#define ADE7758_WAVMODE 0x15 +#define ADE7758_COMPMODE 0x16 +#define ADE7758_LCYCMODE 0x17 +#define ADE7758_MASK 0x18 +#define ADE7758_STATUS 0x19 +#define ADE7758_RSTATUS 0x1A +#define ADE7758_ZXTOUT 0x1B +#define ADE7758_LINECYC 0x1C +#define ADE7758_SAGCYC 0x1D +#define ADE7758_SAGLVL 0x1E +#define ADE7758_VPINTLVL 0x1F +#define ADE7758_IPINTLVL 0x20 +#define ADE7758_VPEAK 0x21 +#define ADE7758_IPEAK 0x22 +#define ADE7758_GAIN 0x23 +#define ADE7758_AVRMSGAIN 0x24 +#define ADE7758_BVRMSGAIN 0x25 +#define ADE7758_CVRMSGAIN 0x26 +#define ADE7758_AIGAIN 0x27 +#define ADE7758_BIGAIN 0x28 +#define ADE7758_CIGAIN 0x29 +#define ADE7758_AWG 0x2A +#define ADE7758_BWG 0x2B +#define ADE7758_CWG 0x2C +#define ADE7758_AVARG 0x2D +#define ADE7758_BVARG 0x2E +#define ADE7758_CVARG 0x2F +#define ADE7758_AVAG 0x30 +#define ADE7758_BVAG 0x31 +#define ADE7758_CVAG 0x32 +#define ADE7758_AVRMSOS 0x33 +#define ADE7758_BVRMSOS 0x34 +#define ADE7758_CVRMSOS 0x35 +#define ADE7758_AIRMSOS 0x36 +#define ADE7758_BIRMSOS 0x37 +#define ADE7758_CIRMSOS 0x38 +#define ADE7758_AWAITOS 0x39 +#define ADE7758_BWAITOS 0x3A +#define ADE7758_CWAITOS 0x3B +#define ADE7758_AVAROS 0x3C +#define ADE7758_BVAROS 0x3D +#define ADE7758_CVAROS 0x3E +#define ADE7758_APHCAL 0x3F +#define ADE7758_BPHCAL 0x40 +#define ADE7758_CPHCAL 0x41 +#define ADE7758_WDIV 0x42 +#define ADE7758_VADIV 0x44 +#define ADE7758_VARDIV 0x43 +#define ADE7758_APCFNUM 0x45 +#define ADE7758_APCFDEN 0x46 +#define ADE7758_VARCFNUM 0x47 +#define ADE7758_VARCFDEN 0x48 +#define ADE7758_CHKSUM 0x7E +#define ADE7758_VERSION 0x7F + +#define ADE7758_READ_REG(a) a +#define ADE7758_WRITE_REG(a) ((a) | 0x80) + +#define ADE7758_MAX_TX 8 +#define ADE7758_MAX_RX 4 +#define ADE7758_STARTUP_DELAY 1 + +#define AD7758_NUM_WAVSEL 5 +#define AD7758_NUM_PHSEL 3 +#define AD7758_NUM_WAVESRC (AD7758_NUM_WAVSEL * AD7758_NUM_PHSEL) + +#define AD7758_PHASE_A 0 +#define AD7758_PHASE_B 1 +#define AD7758_PHASE_C 2 +#define AD7758_CURRENT 0 +#define AD7758_VOLTAGE 1 +#define AD7758_ACT_PWR 2 +#define AD7758_REACT_PWR 3 +#define AD7758_APP_PWR 4 +#define AD7758_WT(p, w) (((w) << 2) | (p)) + +/** + * struct ade7758_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct ade7758_state { + struct spi_device *us; + struct iio_trigger *trig; + u8 *tx; + u8 *rx; + struct mutex buf_lock; + struct spi_transfer ring_xfer[4]; + struct spi_message ring_msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + unsigned char rx_buf[8] ____cacheline_aligned; + unsigned char tx_buf[8]; + +}; +#ifdef CONFIG_IIO_BUFFER +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +void ade7758_remove_trigger(struct iio_dev *indio_dev); +int ade7758_probe_trigger(struct iio_dev *indio_dev); + +ssize_t ade7758_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + + +int ade7758_configure_ring(struct iio_dev *indio_dev); +void ade7758_unconfigure_ring(struct iio_dev *indio_dev); + +int ade7758_set_irq(struct device *dev, bool enable); + +int ade7758_spi_write_reg_8(struct device *dev, + u8 reg_address, u8 val); +int ade7758_spi_read_reg_8(struct device *dev, + u8 reg_address, u8 *val); + +#else /* CONFIG_IIO_BUFFER */ + +static inline void ade7758_remove_trigger(struct iio_dev *indio_dev) +{ +} +static inline int ade7758_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static int ade7758_configure_ring(struct iio_dev *indio_dev) +{ + return 0; +} +static inline void ade7758_unconfigure_ring(struct iio_dev *indio_dev) +{ +} +static inline int ade7758_initialize_ring(struct iio_ring_buffer *ring) +{ + return 0; +} +static inline void ade7758_uninitialize_ring(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_IIO_BUFFER */ + +#endif diff --git a/kernel/drivers/staging/iio/meter/ade7758_core.c b/kernel/drivers/staging/iio/meter/ade7758_core.c new file mode 100644 index 000000000..77141ae13 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7758_core.c @@ -0,0 +1,917 @@ +/* + * ADE7758 Poly Phase Multifunction Energy Metering IC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "meter.h" +#include "ade7758.h" + +int ade7758_spi_write_reg_8(struct device *dev, + u8 reg_address, + u8 val) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7758_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7758_spi_write_reg_16(struct device *dev, + u8 reg_address, + u16 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7758_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + } + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_WRITE_REG(reg_address); + st->tx[1] = (value >> 8) & 0xFF; + st->tx[2] = value & 0xFF; + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7758_spi_write_reg_24(struct device *dev, + u8 reg_address, + u32 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7758_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 4, + } + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_WRITE_REG(reg_address); + st->tx[1] = (value >> 16) & 0xFF; + st->tx[2] = (value >> 8) & 0xFF; + st->tx[3] = value & 0xFF; + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + mutex_unlock(&st->buf_lock); + + return ret; +} + +int ade7758_spi_read_reg_8(struct device *dev, + u8 reg_address, + u8 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 1, + .delay_usecs = 4, + }, + { + .tx_buf = &st->tx[1], + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 1, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_READ_REG(reg_address); + st->tx[1] = 0; + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "problem when reading 8 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = st->rx[0]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7758_spi_read_reg_16(struct device *dev, + u8 reg_address, + u16 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 1, + .delay_usecs = 4, + }, + { + .tx_buf = &st->tx[1], + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + }, + }; + + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_READ_REG(reg_address); + st->tx[1] = 0; + st->tx[2] = 0; + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + reg_address); + goto error_ret; + } + + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7758_spi_read_reg_24(struct device *dev, + u8 reg_address, + u32 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 1, + .delay_usecs = 4, + }, + { + .tx_buf = &st->tx[1], + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 3, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_READ_REG(reg_address); + st->tx[1] = 0; + st->tx[2] = 0; + st->tx[3] = 0; + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "problem when reading 24 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = (st->rx[0] << 16) | (st->rx[1] << 8) | st->rx[2]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t ade7758_read_8bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7758_spi_read_reg_8(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7758_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7758_spi_read_reg_16(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7758_read_24bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7758_spi_read_reg_24(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val & 0xFFFFFF); +} + +static ssize_t ade7758_write_8bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7758_spi_write_reg_8(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7758_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + u16 val; + + ret = kstrtou16(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7758_spi_write_reg_16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int ade7758_reset(struct device *dev) +{ + int ret; + u8 val; + + ret = ade7758_spi_read_reg_8(dev, ADE7758_OPMODE, &val); + if (ret < 0) { + dev_err(dev, "Failed to read opmode reg\n"); + return ret; + } + val |= 1 << 6; /* Software Chip Reset */ + ret = ade7758_spi_write_reg_8(dev, ADE7758_OPMODE, val); + if (ret < 0) + dev_err(dev, "Failed to write opmode reg\n"); + return ret; +} + +static IIO_DEV_ATTR_VPEAK(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_VPEAK); +static IIO_DEV_ATTR_IPEAK(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_VPEAK); +static IIO_DEV_ATTR_APHCAL(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_APHCAL); +static IIO_DEV_ATTR_BPHCAL(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_BPHCAL); +static IIO_DEV_ATTR_CPHCAL(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_CPHCAL); +static IIO_DEV_ATTR_WDIV(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_WDIV); +static IIO_DEV_ATTR_VADIV(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_VADIV); +static IIO_DEV_ATTR_AIRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_AIRMS); +static IIO_DEV_ATTR_BIRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_BIRMS); +static IIO_DEV_ATTR_CIRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_CIRMS); +static IIO_DEV_ATTR_AVRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_AVRMS); +static IIO_DEV_ATTR_BVRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_BVRMS); +static IIO_DEV_ATTR_CVRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_CVRMS); +static IIO_DEV_ATTR_AIRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_AIRMSOS); +static IIO_DEV_ATTR_BIRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_BIRMSOS); +static IIO_DEV_ATTR_CIRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_CIRMSOS); +static IIO_DEV_ATTR_AVRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_AVRMSOS); +static IIO_DEV_ATTR_BVRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_BVRMSOS); +static IIO_DEV_ATTR_CVRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_CVRMSOS); +static IIO_DEV_ATTR_AIGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_AIGAIN); +static IIO_DEV_ATTR_BIGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_BIGAIN); +static IIO_DEV_ATTR_CIGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_CIGAIN); +static IIO_DEV_ATTR_AVRMSGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_AVRMSGAIN); +static IIO_DEV_ATTR_BVRMSGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_BVRMSGAIN); +static IIO_DEV_ATTR_CVRMSGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_CVRMSGAIN); + +int ade7758_set_irq(struct device *dev, bool enable) +{ + int ret; + u32 irqen; + + ret = ade7758_spi_read_reg_24(dev, ADE7758_MASK, &irqen); + if (ret) + goto error_ret; + + if (enable) + irqen |= 1 << 16; /* Enables an interrupt when a data is + present in the waveform register */ + else + irqen &= ~(1 << 16); + + ret = ade7758_spi_write_reg_24(dev, ADE7758_MASK, irqen); + if (ret) + goto error_ret; + +error_ret: + return ret; +} + +/* Power down the device */ +static int ade7758_stop_device(struct device *dev) +{ + int ret; + u8 val; + + ret = ade7758_spi_read_reg_8(dev, ADE7758_OPMODE, &val); + if (ret < 0) { + dev_err(dev, "Failed to read opmode reg\n"); + return ret; + } + val |= 7 << 3; /* ADE7758 powered down */ + ret = ade7758_spi_write_reg_8(dev, ADE7758_OPMODE, val); + if (ret < 0) + dev_err(dev, "Failed to write opmode reg\n"); + return ret; +} + +static int ade7758_initial_setup(struct iio_dev *indio_dev) +{ + struct ade7758_state *st = iio_priv(indio_dev); + struct device *dev = &indio_dev->dev; + int ret; + + /* use low spi speed for init */ + st->us->mode = SPI_MODE_1; + spi_setup(st->us); + + /* Disable IRQ */ + ret = ade7758_set_irq(dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + ade7758_reset(dev); + msleep(ADE7758_STARTUP_DELAY); + +err_ret: + return ret; +} + +static ssize_t ade7758_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 t; + int sps; + + ret = ade7758_spi_read_reg_8(dev, + ADE7758_WAVMODE, + &t); + if (ret) + return ret; + + t = (t >> 5) & 0x3; + sps = 26040 / (1 << t); + + return sprintf(buf, "%d SPS\n", sps); +} + +static ssize_t ade7758_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); + u16 val; + int ret; + u8 reg, t; + + ret = kstrtou16(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + switch (val) { + case 26040: + t = 0; + break; + case 13020: + t = 1; + break; + case 6510: + t = 2; + break; + case 3255: + t = 3; + break; + default: + ret = -EINVAL; + goto out; + } + + ret = ade7758_spi_read_reg_8(dev, + ADE7758_WAVMODE, + ®); + if (ret) + goto out; + + reg &= ~(5 << 3); + reg |= t << 5; + + ret = ade7758_spi_write_reg_8(dev, + ADE7758_WAVMODE, + reg); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_TEMP_RAW(ade7758_read_8bit); +static IIO_CONST_ATTR(in_temp_offset, "129 C"); +static IIO_CONST_ATTR(in_temp_scale, "4 C"); + +static IIO_DEV_ATTR_AWATTHR(ade7758_read_16bit, + ADE7758_AWATTHR); +static IIO_DEV_ATTR_BWATTHR(ade7758_read_16bit, + ADE7758_BWATTHR); +static IIO_DEV_ATTR_CWATTHR(ade7758_read_16bit, + ADE7758_CWATTHR); +static IIO_DEV_ATTR_AVARHR(ade7758_read_16bit, + ADE7758_AVARHR); +static IIO_DEV_ATTR_BVARHR(ade7758_read_16bit, + ADE7758_BVARHR); +static IIO_DEV_ATTR_CVARHR(ade7758_read_16bit, + ADE7758_CVARHR); +static IIO_DEV_ATTR_AVAHR(ade7758_read_16bit, + ADE7758_AVAHR); +static IIO_DEV_ATTR_BVAHR(ade7758_read_16bit, + ADE7758_BVAHR); +static IIO_DEV_ATTR_CVAHR(ade7758_read_16bit, + ADE7758_CVAHR); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ade7758_read_frequency, + ade7758_write_frequency); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("26040 13020 6510 3255"); + +static struct attribute *ade7758_attributes[] = { + &iio_dev_attr_in_temp_raw.dev_attr.attr, + &iio_const_attr_in_temp_offset.dev_attr.attr, + &iio_const_attr_in_temp_scale.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_awatthr.dev_attr.attr, + &iio_dev_attr_bwatthr.dev_attr.attr, + &iio_dev_attr_cwatthr.dev_attr.attr, + &iio_dev_attr_avarhr.dev_attr.attr, + &iio_dev_attr_bvarhr.dev_attr.attr, + &iio_dev_attr_cvarhr.dev_attr.attr, + &iio_dev_attr_avahr.dev_attr.attr, + &iio_dev_attr_bvahr.dev_attr.attr, + &iio_dev_attr_cvahr.dev_attr.attr, + &iio_dev_attr_vpeak.dev_attr.attr, + &iio_dev_attr_ipeak.dev_attr.attr, + &iio_dev_attr_aphcal.dev_attr.attr, + &iio_dev_attr_bphcal.dev_attr.attr, + &iio_dev_attr_cphcal.dev_attr.attr, + &iio_dev_attr_wdiv.dev_attr.attr, + &iio_dev_attr_vadiv.dev_attr.attr, + &iio_dev_attr_airms.dev_attr.attr, + &iio_dev_attr_birms.dev_attr.attr, + &iio_dev_attr_cirms.dev_attr.attr, + &iio_dev_attr_avrms.dev_attr.attr, + &iio_dev_attr_bvrms.dev_attr.attr, + &iio_dev_attr_cvrms.dev_attr.attr, + &iio_dev_attr_aigain.dev_attr.attr, + &iio_dev_attr_bigain.dev_attr.attr, + &iio_dev_attr_cigain.dev_attr.attr, + &iio_dev_attr_avrmsgain.dev_attr.attr, + &iio_dev_attr_bvrmsgain.dev_attr.attr, + &iio_dev_attr_cvrmsgain.dev_attr.attr, + &iio_dev_attr_airmsos.dev_attr.attr, + &iio_dev_attr_birmsos.dev_attr.attr, + &iio_dev_attr_cirmsos.dev_attr.attr, + &iio_dev_attr_avrmsos.dev_attr.attr, + &iio_dev_attr_bvrmsos.dev_attr.attr, + &iio_dev_attr_cvrmsos.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ade7758_attribute_group = { + .attrs = ade7758_attributes, +}; + +static const struct iio_chan_spec ade7758_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .address = AD7758_WT(AD7758_PHASE_A, AD7758_VOLTAGE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_CURRENT, + .indexed = 1, + .channel = 0, + .address = AD7758_WT(AD7758_PHASE_A, AD7758_CURRENT), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_POWER, + .indexed = 1, + .channel = 0, + .extend_name = "apparent", + .address = AD7758_WT(AD7758_PHASE_A, AD7758_APP_PWR), + .scan_index = 2, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_POWER, + .indexed = 1, + .channel = 0, + .extend_name = "active", + .address = AD7758_WT(AD7758_PHASE_A, AD7758_ACT_PWR), + .scan_index = 3, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_POWER, + .indexed = 1, + .channel = 0, + .extend_name = "reactive", + .address = AD7758_WT(AD7758_PHASE_A, AD7758_REACT_PWR), + .scan_index = 4, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .address = AD7758_WT(AD7758_PHASE_B, AD7758_VOLTAGE), + .scan_index = 5, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_CURRENT, + .indexed = 1, + .channel = 1, + .address = AD7758_WT(AD7758_PHASE_B, AD7758_CURRENT), + .scan_index = 6, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_POWER, + .indexed = 1, + .channel = 1, + .extend_name = "apparent", + .address = AD7758_WT(AD7758_PHASE_B, AD7758_APP_PWR), + .scan_index = 7, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_POWER, + .indexed = 1, + .channel = 1, + .extend_name = "active", + .address = AD7758_WT(AD7758_PHASE_B, AD7758_ACT_PWR), + .scan_index = 8, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_POWER, + .indexed = 1, + .channel = 1, + .extend_name = "reactive", + .address = AD7758_WT(AD7758_PHASE_B, AD7758_REACT_PWR), + .scan_index = 9, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .address = AD7758_WT(AD7758_PHASE_C, AD7758_VOLTAGE), + .scan_index = 10, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_CURRENT, + .indexed = 1, + .channel = 2, + .address = AD7758_WT(AD7758_PHASE_C, AD7758_CURRENT), + .scan_index = 11, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_POWER, + .indexed = 1, + .channel = 2, + .extend_name = "apparent", + .address = AD7758_WT(AD7758_PHASE_C, AD7758_APP_PWR), + .scan_index = 12, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_POWER, + .indexed = 1, + .channel = 2, + .extend_name = "active", + .address = AD7758_WT(AD7758_PHASE_C, AD7758_ACT_PWR), + .scan_index = 13, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, { + .type = IIO_POWER, + .indexed = 1, + .channel = 2, + .extend_name = "reactive", + .address = AD7758_WT(AD7758_PHASE_C, AD7758_REACT_PWR), + .scan_index = 14, + .scan_type = { + .sign = 's', + .realbits = 24, + .storagebits = 32, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(15), +}; + +static const struct iio_info ade7758_info = { + .attrs = &ade7758_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ade7758_probe(struct spi_device *spi) +{ + int ret; + struct ade7758_state *st; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + /* Allocate the comms buffers */ + st->rx = kcalloc(ADE7758_MAX_RX, sizeof(*st->rx), GFP_KERNEL); + if (!st->rx) + return -ENOMEM; + st->tx = kcalloc(ADE7758_MAX_TX, sizeof(*st->tx), GFP_KERNEL); + if (!st->tx) { + ret = -ENOMEM; + goto error_free_rx; + } + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ade7758_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ade7758_channels; + indio_dev->num_channels = ARRAY_SIZE(ade7758_channels); + + ret = ade7758_configure_ring(indio_dev); + if (ret) + goto error_free_tx; + + /* Get the device into a sane initial state */ + ret = ade7758_initial_setup(indio_dev); + if (ret) + goto error_unreg_ring_funcs; + + if (spi->irq) { + ret = ade7758_probe_trigger(indio_dev); + if (ret) + goto error_unreg_ring_funcs; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + if (spi->irq) + ade7758_remove_trigger(indio_dev); +error_unreg_ring_funcs: + ade7758_unconfigure_ring(indio_dev); +error_free_tx: + kfree(st->tx); +error_free_rx: + kfree(st->rx); + return ret; +} + +static int ade7758_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ade7758_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + ade7758_stop_device(&indio_dev->dev); + ade7758_remove_trigger(indio_dev); + ade7758_unconfigure_ring(indio_dev); + kfree(st->tx); + kfree(st->rx); + + return 0; +} + +static const struct spi_device_id ade7758_id[] = { + {"ade7758", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, ade7758_id); + +static struct spi_driver ade7758_driver = { + .driver = { + .name = "ade7758", + .owner = THIS_MODULE, + }, + .probe = ade7758_probe, + .remove = ade7758_remove, + .id_table = ade7758_id, +}; +module_spi_driver(ade7758_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7758 Polyphase Multifunction Energy Metering IC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/meter/ade7758_ring.c b/kernel/drivers/staging/iio/meter/ade7758_ring.c new file mode 100644 index 000000000..9a24e0226 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7758_ring.c @@ -0,0 +1,180 @@ +/* + * ADE7758 Poly Phase Multifunction Energy Metering IC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "ade7758.h" + +/** + * ade7758_spi_read_burst() - read data registers + * @indio_dev: the IIO device + **/ +static int ade7758_spi_read_burst(struct iio_dev *indio_dev) +{ + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_sync(st->us, &st->ring_msg); + if (ret) + dev_err(&st->us->dev, "problem when reading WFORM value\n"); + + return ret; +} + +static int ade7758_write_waveform_type(struct device *dev, unsigned type) +{ + int ret; + u8 reg; + + ret = ade7758_spi_read_reg_8(dev, + ADE7758_WAVMODE, + ®); + if (ret) + goto out; + + reg &= ~0x1F; + reg |= type & 0x1F; + + ret = ade7758_spi_write_reg_8(dev, + ADE7758_WAVMODE, + reg); +out: + return ret; +} + +/* Whilst this makes a lot of calls to iio_sw_ring functions - it is too device + * specific to be rolled into the core. + */ +static irqreturn_t ade7758_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ade7758_state *st = iio_priv(indio_dev); + s64 dat64[2]; + u32 *dat32 = (u32 *)dat64; + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + if (ade7758_spi_read_burst(indio_dev) >= 0) + *dat32 = get_unaligned_be32(&st->rx_buf[5]) & 0xFFFFFF; + + iio_push_to_buffers_with_timestamp(indio_dev, dat64, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/** + * ade7758_ring_preenable() setup the parameters of the ring before enabling + * + * The complex nature of the setting of the number of bytes per datum is due + * to this driver currently ensuring that the timestamp is stored at an 8 + * byte boundary. + **/ +static int ade7758_ring_preenable(struct iio_dev *indio_dev) +{ + unsigned channel; + + if (bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + return -EINVAL; + + channel = find_first_bit(indio_dev->active_scan_mask, + indio_dev->masklength); + + ade7758_write_waveform_type(&indio_dev->dev, + indio_dev->channels[channel].address); + + return 0; +} + +static const struct iio_buffer_setup_ops ade7758_ring_setup_ops = { + .preenable = &ade7758_ring_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .validate_scan_mask = &iio_validate_scan_mask_onehot, +}; + +void ade7758_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_kfifo_free(indio_dev->buffer); +} + +int ade7758_configure_ring(struct iio_dev *indio_dev) +{ + struct ade7758_state *st = iio_priv(indio_dev); + struct iio_buffer *buffer; + int ret = 0; + + buffer = iio_kfifo_allocate(); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->setup_ops = &ade7758_ring_setup_ops; + + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &ade7758_trigger_handler, + 0, + indio_dev, + "ade7759_consumer%d", + indio_dev->id); + if (!indio_dev->pollfunc) { + ret = -ENOMEM; + goto error_iio_kfifo_free; + } + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + st->tx_buf[0] = ADE7758_READ_REG(ADE7758_RSTATUS); + st->tx_buf[1] = 0; + st->tx_buf[2] = 0; + st->tx_buf[3] = 0; + st->tx_buf[4] = ADE7758_READ_REG(ADE7758_WFORM); + st->tx_buf[5] = 0; + st->tx_buf[6] = 0; + st->tx_buf[7] = 0; + + /* build spi ring message */ + st->ring_xfer[0].tx_buf = &st->tx_buf[0]; + st->ring_xfer[0].len = 1; + st->ring_xfer[0].bits_per_word = 8; + st->ring_xfer[0].delay_usecs = 4; + st->ring_xfer[1].rx_buf = &st->rx_buf[1]; + st->ring_xfer[1].len = 3; + st->ring_xfer[1].bits_per_word = 8; + st->ring_xfer[1].cs_change = 1; + + st->ring_xfer[2].tx_buf = &st->tx_buf[4]; + st->ring_xfer[2].len = 1; + st->ring_xfer[2].bits_per_word = 8; + st->ring_xfer[2].delay_usecs = 1; + st->ring_xfer[3].rx_buf = &st->rx_buf[5]; + st->ring_xfer[3].len = 3; + st->ring_xfer[3].bits_per_word = 8; + + 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); + spi_message_add_tail(&st->ring_xfer[2], &st->ring_msg); + spi_message_add_tail(&st->ring_xfer[3], &st->ring_msg); + + return 0; + +error_iio_kfifo_free: + iio_kfifo_free(indio_dev->buffer); + return ret; +} diff --git a/kernel/drivers/staging/iio/meter/ade7758_trigger.c b/kernel/drivers/staging/iio/meter/ade7758_trigger.c new file mode 100644 index 000000000..5b35a7f08 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7758_trigger.c @@ -0,0 +1,109 @@ +/* + * ADE7758 Poly Phase Multifunction Energy Metering IC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include + +#include +#include +#include "ade7758.h" + +/** + * ade7758_data_rdy_trig_poll() the event handler for the data rdy trig + **/ +static irqreturn_t ade7758_data_rdy_trig_poll(int irq, void *private) +{ + disable_irq_nosync(irq); + iio_trigger_poll(private); + + return IRQ_HANDLED; +} + +/** + * ade7758_data_rdy_trigger_set_state() set datardy interrupt state + **/ +static int ade7758_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + + dev_dbg(&indio_dev->dev, "%s (%d)\n", __func__, state); + return ade7758_set_irq(&indio_dev->dev, state); +} + +/** + * ade7758_trig_try_reen() try renabling irq for data rdy trigger + * @trig: the datardy trigger + **/ +static int ade7758_trig_try_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct ade7758_state *st = iio_priv(indio_dev); + + enable_irq(st->us->irq); + /* irq reenabled so success! */ + return 0; +} + +static const struct iio_trigger_ops ade7758_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &ade7758_data_rdy_trigger_set_state, + .try_reenable = &ade7758_trig_try_reen, +}; + +int ade7758_probe_trigger(struct iio_dev *indio_dev) +{ + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + + st->trig = iio_trigger_alloc("%s-dev%d", + spi_get_device_id(st->us)->name, + indio_dev->id); + if (!st->trig) { + ret = -ENOMEM; + goto error_ret; + } + + ret = request_irq(st->us->irq, + ade7758_data_rdy_trig_poll, + IRQF_TRIGGER_LOW, + spi_get_device_id(st->us)->name, + st->trig); + if (ret) + goto error_free_trig; + + st->trig->dev.parent = &st->us->dev; + st->trig->ops = &ade7758_trigger_ops; + iio_trigger_set_drvdata(st->trig, indio_dev); + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = iio_trigger_get(st->trig); + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->us->irq, st->trig); +error_free_trig: + iio_trigger_free(st->trig); +error_ret: + return ret; +} + +void ade7758_remove_trigger(struct iio_dev *indio_dev) +{ + struct ade7758_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->us->irq, st->trig); + iio_trigger_free(st->trig); +} diff --git a/kernel/drivers/staging/iio/meter/ade7759.c b/kernel/drivers/staging/iio/meter/ade7759.c new file mode 100644 index 000000000..dbceda1e6 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7759.c @@ -0,0 +1,503 @@ +/* + * ADE7759 Active Energy Metering IC with di/dt Sensor Interface Driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "meter.h" +#include "ade7759.h" + +static int ade7759_spi_write_reg_8(struct device *dev, + u8 reg_address, + u8 val) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7759_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7759_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7759_spi_write_reg_16(struct device *dev, + u8 reg_address, + u16 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7759_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7759_WRITE_REG(reg_address); + st->tx[1] = (value >> 8) & 0xFF; + st->tx[2] = value & 0xFF; + ret = spi_write(st->us, st->tx, 3); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7759_spi_read_reg_8(struct device *dev, + u8 reg_address, + u8 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7759_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_w8r8(st->us, ADE7759_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 8 bit register 0x%02X", + reg_address); + return ret; + } + *val = ret; + + return 0; +} + +static int ade7759_spi_read_reg_16(struct device *dev, + u8 reg_address, + u16 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7759_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_w8r16be(st->us, ADE7759_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + reg_address); + return ret; + } + + *val = ret; + + return 0; +} + +static int ade7759_spi_read_reg_40(struct device *dev, + u8 reg_address, + u64 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7759_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 6, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7759_READ_REG(reg_address); + memset(&st->tx[1], 0, 5); + + ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->us->dev, "problem when reading 40 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = ((u64)st->rx[1] << 32) | (st->rx[2] << 24) | + (st->rx[3] << 16) | (st->rx[4] << 8) | st->rx[5]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t ade7759_read_8bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7759_spi_read_reg_8(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7759_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7759_spi_read_reg_16(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7759_read_40bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u64 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7759_spi_read_reg_40(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%llu\n", val); +} + +static ssize_t ade7759_write_8bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7759_spi_write_reg_8(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7759_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + u16 val; + + ret = kstrtou16(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7759_spi_write_reg_16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int ade7759_reset(struct device *dev) +{ + int ret; + u16 val; + + ret = ade7759_spi_read_reg_16(dev, + ADE7759_MODE, + &val); + if (ret < 0) + return ret; + + val |= 1 << 6; /* Software Chip Reset */ + return ade7759_spi_write_reg_16(dev, + ADE7759_MODE, + val); +} + +static IIO_DEV_ATTR_AENERGY(ade7759_read_40bit, ADE7759_AENERGY); +static IIO_DEV_ATTR_CFDEN(S_IWUSR | S_IRUGO, + ade7759_read_16bit, + ade7759_write_16bit, + ADE7759_CFDEN); +static IIO_DEV_ATTR_CFNUM(S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_CFNUM); +static IIO_DEV_ATTR_CHKSUM(ade7759_read_8bit, ADE7759_CHKSUM); +static IIO_DEV_ATTR_PHCAL(S_IWUSR | S_IRUGO, + ade7759_read_16bit, + ade7759_write_16bit, + ADE7759_PHCAL); +static IIO_DEV_ATTR_APOS(S_IWUSR | S_IRUGO, + ade7759_read_16bit, + ade7759_write_16bit, + ADE7759_APOS); +static IIO_DEV_ATTR_SAGCYC(S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_SAGCYC); +static IIO_DEV_ATTR_SAGLVL(S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_SAGLVL); +static IIO_DEV_ATTR_LINECYC(S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_LINECYC); +static IIO_DEV_ATTR_LENERGY(ade7759_read_40bit, ADE7759_LENERGY); +static IIO_DEV_ATTR_PGA_GAIN(S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_GAIN); +static IIO_DEV_ATTR_ACTIVE_POWER_GAIN(S_IWUSR | S_IRUGO, + ade7759_read_16bit, + ade7759_write_16bit, + ADE7759_APGAIN); +static IIO_DEV_ATTR_CH_OFF(1, S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_CH1OS); +static IIO_DEV_ATTR_CH_OFF(2, S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_CH2OS); + +static int ade7759_set_irq(struct device *dev, bool enable) +{ + int ret; + u8 irqen; + + ret = ade7759_spi_read_reg_8(dev, ADE7759_IRQEN, &irqen); + if (ret) + goto error_ret; + + if (enable) + irqen |= 1 << 3; /* Enables an interrupt when a data is + present in the waveform register */ + else + irqen &= ~(1 << 3); + + ret = ade7759_spi_write_reg_8(dev, ADE7759_IRQEN, irqen); + +error_ret: + return ret; +} + +/* Power down the device */ +static int ade7759_stop_device(struct device *dev) +{ + int ret; + u16 val; + + ret = ade7759_spi_read_reg_16(dev, + ADE7759_MODE, + &val); + if (ret < 0) { + dev_err(dev, "unable to power down the device, error: %d\n", + ret); + return ret; + } + + val |= 1 << 4; /* AD converters can be turned off */ + + return ade7759_spi_write_reg_16(dev, ADE7759_MODE, val); +} + +static int ade7759_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct ade7759_state *st = iio_priv(indio_dev); + struct device *dev = &indio_dev->dev; + + /* use low spi speed for init */ + st->us->mode = SPI_MODE_3; + spi_setup(st->us); + + /* Disable IRQ */ + ret = ade7759_set_irq(dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + ade7759_reset(dev); + msleep(ADE7759_STARTUP_DELAY); + +err_ret: + return ret; +} + +static ssize_t ade7759_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 t; + int sps; + + ret = ade7759_spi_read_reg_16(dev, + ADE7759_MODE, + &t); + if (ret) + return ret; + + t = (t >> 3) & 0x3; + sps = 27900 / (1 + t); + + return sprintf(buf, "%d\n", sps); +} + +static ssize_t ade7759_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 ade7759_state *st = iio_priv(indio_dev); + u16 val; + int ret; + u16 reg, t; + + ret = kstrtou16(buf, 10, &val); + if (ret) + return ret; + if (val == 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + t = 27900 / val; + if (t > 0) + t--; + + if (t > 1) + st->us->max_speed_hz = ADE7759_SPI_SLOW; + else + st->us->max_speed_hz = ADE7759_SPI_FAST; + + ret = ade7759_spi_read_reg_16(dev, ADE7759_MODE, ®); + if (ret) + goto out; + + reg &= ~(3 << 13); + reg |= t << 13; + + ret = ade7759_spi_write_reg_16(dev, ADE7759_MODE, reg); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} +static IIO_DEV_ATTR_TEMP_RAW(ade7759_read_8bit); +static IIO_CONST_ATTR(in_temp_offset, "70 C"); +static IIO_CONST_ATTR(in_temp_scale, "1 C"); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ade7759_read_frequency, + ade7759_write_frequency); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("27900 14000 7000 3500"); + +static struct attribute *ade7759_attributes[] = { + &iio_dev_attr_in_temp_raw.dev_attr.attr, + &iio_const_attr_in_temp_offset.dev_attr.attr, + &iio_const_attr_in_temp_scale.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_phcal.dev_attr.attr, + &iio_dev_attr_cfden.dev_attr.attr, + &iio_dev_attr_aenergy.dev_attr.attr, + &iio_dev_attr_cfnum.dev_attr.attr, + &iio_dev_attr_apos.dev_attr.attr, + &iio_dev_attr_sagcyc.dev_attr.attr, + &iio_dev_attr_saglvl.dev_attr.attr, + &iio_dev_attr_linecyc.dev_attr.attr, + &iio_dev_attr_lenergy.dev_attr.attr, + &iio_dev_attr_chksum.dev_attr.attr, + &iio_dev_attr_pga_gain.dev_attr.attr, + &iio_dev_attr_active_power_gain.dev_attr.attr, + &iio_dev_attr_choff_1.dev_attr.attr, + &iio_dev_attr_choff_2.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ade7759_attribute_group = { + .attrs = ade7759_attributes, +}; + +static const struct iio_info ade7759_info = { + .attrs = &ade7759_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ade7759_probe(struct spi_device *spi) +{ + int ret; + struct ade7759_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ade7759_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Get the device into a sane initial state */ + ret = ade7759_initial_setup(indio_dev); + if (ret) + return ret; + + return iio_device_register(indio_dev); +} + +/* fixme, confirm ordering in this function */ +static int ade7759_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + ade7759_stop_device(&indio_dev->dev); + + return 0; +} + +static struct spi_driver ade7759_driver = { + .driver = { + .name = "ade7759", + .owner = THIS_MODULE, + }, + .probe = ade7759_probe, + .remove = ade7759_remove, +}; +module_spi_driver(ade7759_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7759 Active Energy Metering IC Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:ad7759"); diff --git a/kernel/drivers/staging/iio/meter/ade7759.h b/kernel/drivers/staging/iio/meter/ade7759.h new file mode 100644 index 000000000..f9ff1f8e7 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7759.h @@ -0,0 +1,53 @@ +#ifndef _ADE7759_H +#define _ADE7759_H + +#define ADE7759_WAVEFORM 0x01 +#define ADE7759_AENERGY 0x02 +#define ADE7759_RSTENERGY 0x03 +#define ADE7759_STATUS 0x04 +#define ADE7759_RSTSTATUS 0x05 +#define ADE7759_MODE 0x06 +#define ADE7759_CFDEN 0x07 +#define ADE7759_CH1OS 0x08 +#define ADE7759_CH2OS 0x09 +#define ADE7759_GAIN 0x0A +#define ADE7759_APGAIN 0x0B +#define ADE7759_PHCAL 0x0C +#define ADE7759_APOS 0x0D +#define ADE7759_ZXTOUT 0x0E +#define ADE7759_SAGCYC 0x0F +#define ADE7759_IRQEN 0x10 +#define ADE7759_SAGLVL 0x11 +#define ADE7759_TEMP 0x12 +#define ADE7759_LINECYC 0x13 +#define ADE7759_LENERGY 0x14 +#define ADE7759_CFNUM 0x15 +#define ADE7759_CHKSUM 0x1E +#define ADE7759_DIEREV 0x1F + +#define ADE7759_READ_REG(a) a +#define ADE7759_WRITE_REG(a) ((a) | 0x80) + +#define ADE7759_MAX_TX 6 +#define ADE7759_MAX_RX 6 +#define ADE7759_STARTUP_DELAY 1 + +#define ADE7759_SPI_SLOW (u32)(300 * 1000) +#define ADE7759_SPI_BURST (u32)(1000 * 1000) +#define ADE7759_SPI_FAST (u32)(2000 * 1000) + +/** + * struct ade7759_state - device instance specific data + * @us: actual spi_device + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct ade7759_state { + struct spi_device *us; + struct mutex buf_lock; + u8 tx[ADE7759_MAX_TX] ____cacheline_aligned; + u8 rx[ADE7759_MAX_RX]; +}; + +#endif diff --git a/kernel/drivers/staging/iio/meter/ade7854-i2c.c b/kernel/drivers/staging/iio/meter/ade7854-i2c.c new file mode 100644 index 000000000..07cfe28b2 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7854-i2c.c @@ -0,0 +1,256 @@ +/* + * ADE7854/58/68/78 Polyphase Multifunction Energy Metering IC Driver (I2C Bus) + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include +#include "ade7854.h" + +static int ade7854_i2c_write_reg_8(struct device *dev, + u16 reg_address, + u8 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + st->tx[2] = value; + + ret = i2c_master_send(st->i2c, st->tx, 3); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_i2c_write_reg_16(struct device *dev, + u16 reg_address, + u16 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + st->tx[2] = (value >> 8) & 0xFF; + st->tx[3] = value & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 4); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_i2c_write_reg_24(struct device *dev, + u16 reg_address, + u32 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + st->tx[2] = (value >> 16) & 0xFF; + st->tx[3] = (value >> 8) & 0xFF; + st->tx[4] = value & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 5); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_i2c_write_reg_32(struct device *dev, + u16 reg_address, + u32 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + st->tx[2] = (value >> 24) & 0xFF; + st->tx[3] = (value >> 16) & 0xFF; + st->tx[4] = (value >> 8) & 0xFF; + st->tx[5] = value & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 6); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_i2c_read_reg_8(struct device *dev, + u16 reg_address, + u8 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 2); + if (ret) + goto out; + + ret = i2c_master_recv(st->i2c, st->rx, 1); + if (ret) + goto out; + + *val = st->rx[0]; +out: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_i2c_read_reg_16(struct device *dev, + u16 reg_address, + u16 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 2); + if (ret) + goto out; + + ret = i2c_master_recv(st->i2c, st->rx, 2); + if (ret) + goto out; + + *val = (st->rx[0] << 8) | st->rx[1]; +out: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_i2c_read_reg_24(struct device *dev, + u16 reg_address, + u32 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 2); + if (ret) + goto out; + + ret = i2c_master_recv(st->i2c, st->rx, 3); + if (ret) + goto out; + + *val = (st->rx[0] << 16) | (st->rx[1] << 8) | st->rx[2]; +out: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_i2c_read_reg_32(struct device *dev, + u16 reg_address, + u32 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 2); + if (ret) + goto out; + + ret = i2c_master_recv(st->i2c, st->rx, 3); + if (ret) + goto out; + + *val = (st->rx[0] << 24) | (st->rx[1] << 16) | + (st->rx[2] << 8) | st->rx[3]; +out: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ade7854_state *st; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + st->read_reg_8 = ade7854_i2c_read_reg_8; + st->read_reg_16 = ade7854_i2c_read_reg_16; + st->read_reg_24 = ade7854_i2c_read_reg_24; + st->read_reg_32 = ade7854_i2c_read_reg_32; + st->write_reg_8 = ade7854_i2c_write_reg_8; + st->write_reg_16 = ade7854_i2c_write_reg_16; + st->write_reg_24 = ade7854_i2c_write_reg_24; + st->write_reg_32 = ade7854_i2c_write_reg_32; + st->i2c = client; + st->irq = client->irq; + + return ade7854_probe(indio_dev, &client->dev); +} + +static int ade7854_i2c_remove(struct i2c_client *client) +{ + return ade7854_remove(i2c_get_clientdata(client)); +} + +static const struct i2c_device_id ade7854_id[] = { + { "ade7854", 0 }, + { "ade7858", 0 }, + { "ade7868", 0 }, + { "ade7878", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ade7854_id); + +static struct i2c_driver ade7854_i2c_driver = { + .driver = { + .name = "ade7854", + }, + .probe = ade7854_i2c_probe, + .remove = ade7854_i2c_remove, + .id_table = ade7854_id, +}; +module_i2c_driver(ade7854_i2c_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7854/58/68/78 Polyphase Multifunction Energy Metering IC I2C Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/meter/ade7854-spi.c b/kernel/drivers/staging/iio/meter/ade7854-spi.c new file mode 100644 index 000000000..9b255a5f6 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7854-spi.c @@ -0,0 +1,327 @@ +/* + * ADE7854/58/68/78 Polyphase Multifunction Energy Metering IC Driver (SPI Bus) + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include +#include "ade7854.h" + +static int ade7854_spi_write_reg_8(struct device *dev, + u16 reg_address, + u8 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 4, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7854_WRITE_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + st->tx[3] = value & 0xFF; + + ret = spi_sync_transfer(st->spi, &xfer, 1); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_spi_write_reg_16(struct device *dev, + u16 reg_address, + u16 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 5, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7854_WRITE_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + st->tx[3] = (value >> 8) & 0xFF; + st->tx[4] = value & 0xFF; + + ret = spi_sync_transfer(st->spi, &xfer, 1); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_spi_write_reg_24(struct device *dev, + u16 reg_address, + u32 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 6, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7854_WRITE_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + st->tx[3] = (value >> 16) & 0xFF; + st->tx[4] = (value >> 8) & 0xFF; + st->tx[5] = value & 0xFF; + + ret = spi_sync_transfer(st->spi, &xfer, 1); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_spi_write_reg_32(struct device *dev, + u16 reg_address, + u32 value) +{ + int ret; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 7, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7854_WRITE_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + st->tx[3] = (value >> 24) & 0xFF; + st->tx[4] = (value >> 16) & 0xFF; + st->tx[5] = (value >> 8) & 0xFF; + st->tx[6] = value & 0xFF; + + ret = spi_sync_transfer(st->spi, &xfer, 1); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_spi_read_reg_8(struct device *dev, + u16 reg_address, + u8 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 1, + } + }; + + mutex_lock(&st->buf_lock); + + st->tx[0] = ADE7854_READ_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->spi->dev, "problem when reading 8 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = st->rx[0]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_spi_read_reg_16(struct device *dev, + u16 reg_address, + u16 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + } + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7854_READ_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->spi->dev, "problem when reading 16 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = be16_to_cpup((const __be16 *)st->rx); + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_spi_read_reg_24(struct device *dev, + u16 reg_address, + u32 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 3, + } + }; + + mutex_lock(&st->buf_lock); + + st->tx[0] = ADE7854_READ_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->spi->dev, "problem when reading 24 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = (st->rx[0] << 16) | (st->rx[1] << 8) | st->rx[2]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_spi_read_reg_32(struct device *dev, + u16 reg_address, + u32 *val) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 4, + } + }; + + mutex_lock(&st->buf_lock); + + st->tx[0] = ADE7854_READ_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) { + dev_err(&st->spi->dev, "problem when reading 32 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = be32_to_cpup((const __be32 *)st->rx); + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_spi_probe(struct spi_device *spi) +{ + struct ade7854_state *st; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->read_reg_8 = ade7854_spi_read_reg_8; + st->read_reg_16 = ade7854_spi_read_reg_16; + st->read_reg_24 = ade7854_spi_read_reg_24; + st->read_reg_32 = ade7854_spi_read_reg_32; + st->write_reg_8 = ade7854_spi_write_reg_8; + st->write_reg_16 = ade7854_spi_write_reg_16; + st->write_reg_24 = ade7854_spi_write_reg_24; + st->write_reg_32 = ade7854_spi_write_reg_32; + st->irq = spi->irq; + st->spi = spi; + + return ade7854_probe(indio_dev, &spi->dev); +} + +static int ade7854_spi_remove(struct spi_device *spi) +{ + ade7854_remove(spi_get_drvdata(spi)); + + return 0; +} +static const struct spi_device_id ade7854_id[] = { + { "ade7854", 0 }, + { "ade7858", 0 }, + { "ade7868", 0 }, + { "ade7878", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ade7854_id); + +static struct spi_driver ade7854_driver = { + .driver = { + .name = "ade7854", + .owner = THIS_MODULE, + }, + .probe = ade7854_spi_probe, + .remove = ade7854_spi_remove, + .id_table = ade7854_id, +}; +module_spi_driver(ade7854_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7854/58/68/78 SPI Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/meter/ade7854.c b/kernel/drivers/staging/iio/meter/ade7854.c new file mode 100644 index 000000000..d620bbd60 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7854.c @@ -0,0 +1,578 @@ +/* + * ADE7854/58/68/78 Polyphase Multifunction Energy Metering IC Driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "meter.h" +#include "ade7854.h" + +static ssize_t ade7854_read_8bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val = 0; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = st->read_reg_8(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7854_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 val = 0; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = st->read_reg_16(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7854_read_24bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 val; + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = st->read_reg_24(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7854_read_32bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + ret = st->read_reg_32(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7854_write_8bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret) + goto error_ret; + ret = st->write_reg_8(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7854_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + int ret; + u16 val; + + ret = kstrtou16(buf, 10, &val); + if (ret) + goto error_ret; + ret = st->write_reg_16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7854_write_24bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + int ret; + u32 val; + + ret = kstrtou32(buf, 10, &val); + if (ret) + goto error_ret; + ret = st->write_reg_24(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7854_write_32bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + int ret; + u32 val; + + ret = kstrtou32(buf, 10, &val); + if (ret) + goto error_ret; + ret = st->write_reg_32(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int ade7854_reset(struct device *dev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + u16 val; + + st->read_reg_16(dev, ADE7854_CONFIG, &val); + val |= 1 << 7; /* Software Chip Reset */ + + return st->write_reg_16(dev, ADE7854_CONFIG, val); +} + +static IIO_DEV_ATTR_AIGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AIGAIN); +static IIO_DEV_ATTR_BIGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BIGAIN); +static IIO_DEV_ATTR_CIGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CIGAIN); +static IIO_DEV_ATTR_NIGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_NIGAIN); +static IIO_DEV_ATTR_AVGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AVGAIN); +static IIO_DEV_ATTR_BVGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BVGAIN); +static IIO_DEV_ATTR_CVGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CVGAIN); +static IIO_DEV_ATTR_APPARENT_POWER_A_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AVAGAIN); +static IIO_DEV_ATTR_APPARENT_POWER_B_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BVAGAIN); +static IIO_DEV_ATTR_APPARENT_POWER_C_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CVAGAIN); +static IIO_DEV_ATTR_ACTIVE_POWER_A_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AWATTOS); +static IIO_DEV_ATTR_ACTIVE_POWER_B_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BWATTOS); +static IIO_DEV_ATTR_ACTIVE_POWER_C_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CWATTOS); +static IIO_DEV_ATTR_REACTIVE_POWER_A_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AVARGAIN); +static IIO_DEV_ATTR_REACTIVE_POWER_B_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BVARGAIN); +static IIO_DEV_ATTR_REACTIVE_POWER_C_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CVARGAIN); +static IIO_DEV_ATTR_REACTIVE_POWER_A_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AVAROS); +static IIO_DEV_ATTR_REACTIVE_POWER_B_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BVAROS); +static IIO_DEV_ATTR_REACTIVE_POWER_C_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CVAROS); +static IIO_DEV_ATTR_VPEAK(S_IWUSR | S_IRUGO, + ade7854_read_32bit, + ade7854_write_32bit, + ADE7854_VPEAK); +static IIO_DEV_ATTR_IPEAK(S_IWUSR | S_IRUGO, + ade7854_read_32bit, + ade7854_write_32bit, + ADE7854_VPEAK); +static IIO_DEV_ATTR_APHCAL(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_APHCAL); +static IIO_DEV_ATTR_BPHCAL(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_BPHCAL); +static IIO_DEV_ATTR_CPHCAL(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CPHCAL); +static IIO_DEV_ATTR_CF1DEN(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CF1DEN); +static IIO_DEV_ATTR_CF2DEN(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CF2DEN); +static IIO_DEV_ATTR_CF3DEN(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CF3DEN); +static IIO_DEV_ATTR_LINECYC(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_LINECYC); +static IIO_DEV_ATTR_SAGCYC(S_IWUSR | S_IRUGO, + ade7854_read_8bit, + ade7854_write_8bit, + ADE7854_SAGCYC); +static IIO_DEV_ATTR_CFCYC(S_IWUSR | S_IRUGO, + ade7854_read_8bit, + ade7854_write_8bit, + ADE7854_CFCYC); +static IIO_DEV_ATTR_PEAKCYC(S_IWUSR | S_IRUGO, + ade7854_read_8bit, + ade7854_write_8bit, + ADE7854_PEAKCYC); +static IIO_DEV_ATTR_CHKSUM(ade7854_read_24bit, + ADE7854_CHECKSUM); +static IIO_DEV_ATTR_ANGLE0(ade7854_read_24bit, + ADE7854_ANGLE0); +static IIO_DEV_ATTR_ANGLE1(ade7854_read_24bit, + ADE7854_ANGLE1); +static IIO_DEV_ATTR_ANGLE2(ade7854_read_24bit, + ADE7854_ANGLE2); +static IIO_DEV_ATTR_AIRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_AIRMS); +static IIO_DEV_ATTR_BIRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_BIRMS); +static IIO_DEV_ATTR_CIRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_CIRMS); +static IIO_DEV_ATTR_NIRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_NIRMS); +static IIO_DEV_ATTR_AVRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_AVRMS); +static IIO_DEV_ATTR_BVRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_BVRMS); +static IIO_DEV_ATTR_CVRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_CVRMS); +static IIO_DEV_ATTR_AIRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_AIRMSOS); +static IIO_DEV_ATTR_BIRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_BIRMSOS); +static IIO_DEV_ATTR_CIRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CIRMSOS); +static IIO_DEV_ATTR_AVRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_AVRMSOS); +static IIO_DEV_ATTR_BVRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_BVRMSOS); +static IIO_DEV_ATTR_CVRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CVRMSOS); +static IIO_DEV_ATTR_VOLT_A(ade7854_read_24bit, + ADE7854_VAWV); +static IIO_DEV_ATTR_VOLT_B(ade7854_read_24bit, + ADE7854_VBWV); +static IIO_DEV_ATTR_VOLT_C(ade7854_read_24bit, + ADE7854_VCWV); +static IIO_DEV_ATTR_CURRENT_A(ade7854_read_24bit, + ADE7854_IAWV); +static IIO_DEV_ATTR_CURRENT_B(ade7854_read_24bit, + ADE7854_IBWV); +static IIO_DEV_ATTR_CURRENT_C(ade7854_read_24bit, + ADE7854_ICWV); +static IIO_DEV_ATTR_AWATTHR(ade7854_read_32bit, + ADE7854_AWATTHR); +static IIO_DEV_ATTR_BWATTHR(ade7854_read_32bit, + ADE7854_BWATTHR); +static IIO_DEV_ATTR_CWATTHR(ade7854_read_32bit, + ADE7854_CWATTHR); +static IIO_DEV_ATTR_AFWATTHR(ade7854_read_32bit, + ADE7854_AFWATTHR); +static IIO_DEV_ATTR_BFWATTHR(ade7854_read_32bit, + ADE7854_BFWATTHR); +static IIO_DEV_ATTR_CFWATTHR(ade7854_read_32bit, + ADE7854_CFWATTHR); +static IIO_DEV_ATTR_AVARHR(ade7854_read_32bit, + ADE7854_AVARHR); +static IIO_DEV_ATTR_BVARHR(ade7854_read_32bit, + ADE7854_BVARHR); +static IIO_DEV_ATTR_CVARHR(ade7854_read_32bit, + ADE7854_CVARHR); +static IIO_DEV_ATTR_AVAHR(ade7854_read_32bit, + ADE7854_AVAHR); +static IIO_DEV_ATTR_BVAHR(ade7854_read_32bit, + ADE7854_BVAHR); +static IIO_DEV_ATTR_CVAHR(ade7854_read_32bit, + ADE7854_CVAHR); + +static int ade7854_set_irq(struct device *dev, bool enable) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + int ret; + u32 irqen; + + ret = st->read_reg_32(dev, ADE7854_MASK0, &irqen); + if (ret) + goto error_ret; + + if (enable) + irqen |= 1 << 17; /* 1: interrupt enabled when all periodical + (at 8 kHz rate) DSP computations finish. */ + else + irqen &= ~(1 << 17); + + ret = st->write_reg_32(dev, ADE7854_MASK0, irqen); + if (ret) + goto error_ret; + +error_ret: + return ret; +} + +static int ade7854_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct device *dev = &indio_dev->dev; + + /* Disable IRQ */ + ret = ade7854_set_irq(dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + ade7854_reset(dev); + msleep(ADE7854_STARTUP_DELAY); + +err_ret: + return ret; +} + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("8000"); + +static IIO_CONST_ATTR(name, "ade7854"); + +static struct attribute *ade7854_attributes[] = { + &iio_dev_attr_aigain.dev_attr.attr, + &iio_dev_attr_bigain.dev_attr.attr, + &iio_dev_attr_cigain.dev_attr.attr, + &iio_dev_attr_nigain.dev_attr.attr, + &iio_dev_attr_avgain.dev_attr.attr, + &iio_dev_attr_bvgain.dev_attr.attr, + &iio_dev_attr_cvgain.dev_attr.attr, + &iio_dev_attr_linecyc.dev_attr.attr, + &iio_dev_attr_sagcyc.dev_attr.attr, + &iio_dev_attr_cfcyc.dev_attr.attr, + &iio_dev_attr_peakcyc.dev_attr.attr, + &iio_dev_attr_chksum.dev_attr.attr, + &iio_dev_attr_apparent_power_a_gain.dev_attr.attr, + &iio_dev_attr_apparent_power_b_gain.dev_attr.attr, + &iio_dev_attr_apparent_power_c_gain.dev_attr.attr, + &iio_dev_attr_active_power_a_offset.dev_attr.attr, + &iio_dev_attr_active_power_b_offset.dev_attr.attr, + &iio_dev_attr_active_power_c_offset.dev_attr.attr, + &iio_dev_attr_reactive_power_a_gain.dev_attr.attr, + &iio_dev_attr_reactive_power_b_gain.dev_attr.attr, + &iio_dev_attr_reactive_power_c_gain.dev_attr.attr, + &iio_dev_attr_reactive_power_a_offset.dev_attr.attr, + &iio_dev_attr_reactive_power_b_offset.dev_attr.attr, + &iio_dev_attr_reactive_power_c_offset.dev_attr.attr, + &iio_dev_attr_awatthr.dev_attr.attr, + &iio_dev_attr_bwatthr.dev_attr.attr, + &iio_dev_attr_cwatthr.dev_attr.attr, + &iio_dev_attr_afwatthr.dev_attr.attr, + &iio_dev_attr_bfwatthr.dev_attr.attr, + &iio_dev_attr_cfwatthr.dev_attr.attr, + &iio_dev_attr_avarhr.dev_attr.attr, + &iio_dev_attr_bvarhr.dev_attr.attr, + &iio_dev_attr_cvarhr.dev_attr.attr, + &iio_dev_attr_angle0.dev_attr.attr, + &iio_dev_attr_angle1.dev_attr.attr, + &iio_dev_attr_angle2.dev_attr.attr, + &iio_dev_attr_avahr.dev_attr.attr, + &iio_dev_attr_bvahr.dev_attr.attr, + &iio_dev_attr_cvahr.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_name.dev_attr.attr, + &iio_dev_attr_vpeak.dev_attr.attr, + &iio_dev_attr_ipeak.dev_attr.attr, + &iio_dev_attr_aphcal.dev_attr.attr, + &iio_dev_attr_bphcal.dev_attr.attr, + &iio_dev_attr_cphcal.dev_attr.attr, + &iio_dev_attr_cf1den.dev_attr.attr, + &iio_dev_attr_cf2den.dev_attr.attr, + &iio_dev_attr_cf3den.dev_attr.attr, + &iio_dev_attr_airms.dev_attr.attr, + &iio_dev_attr_birms.dev_attr.attr, + &iio_dev_attr_cirms.dev_attr.attr, + &iio_dev_attr_nirms.dev_attr.attr, + &iio_dev_attr_avrms.dev_attr.attr, + &iio_dev_attr_bvrms.dev_attr.attr, + &iio_dev_attr_cvrms.dev_attr.attr, + &iio_dev_attr_airmsos.dev_attr.attr, + &iio_dev_attr_birmsos.dev_attr.attr, + &iio_dev_attr_cirmsos.dev_attr.attr, + &iio_dev_attr_avrmsos.dev_attr.attr, + &iio_dev_attr_bvrmsos.dev_attr.attr, + &iio_dev_attr_cvrmsos.dev_attr.attr, + &iio_dev_attr_volt_a.dev_attr.attr, + &iio_dev_attr_volt_b.dev_attr.attr, + &iio_dev_attr_volt_c.dev_attr.attr, + &iio_dev_attr_current_a.dev_attr.attr, + &iio_dev_attr_current_b.dev_attr.attr, + &iio_dev_attr_current_c.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ade7854_attribute_group = { + .attrs = ade7854_attributes, +}; + +static const struct iio_info ade7854_info = { + .attrs = &ade7854_attribute_group, + .driver_module = THIS_MODULE, +}; + +int ade7854_probe(struct iio_dev *indio_dev, struct device *dev) +{ + int ret; + struct ade7854_state *st = iio_priv(indio_dev); + /* setup the industrialio driver allocated elements */ + mutex_init(&st->buf_lock); + + indio_dev->dev.parent = dev; + indio_dev->info = &ade7854_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + return ret; + + /* Get the device into a sane initial state */ + ret = ade7854_initial_setup(indio_dev); + if (ret) + goto error_unreg_dev; + + return 0; + +error_unreg_dev: + iio_device_unregister(indio_dev); + return ret; +} +EXPORT_SYMBOL(ade7854_probe); + +int ade7854_remove(struct iio_dev *indio_dev) +{ + iio_device_unregister(indio_dev); + + return 0; +} +EXPORT_SYMBOL(ade7854_remove); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7854/58/68/78 Polyphase Energy Meter"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/meter/ade7854.h b/kernel/drivers/staging/iio/meter/ade7854.h new file mode 100644 index 000000000..52ca5412a --- /dev/null +++ b/kernel/drivers/staging/iio/meter/ade7854.h @@ -0,0 +1,174 @@ +#ifndef _ADE7854_H +#define _ADE7854_H + +#define ADE7854_AIGAIN 0x4380 +#define ADE7854_AVGAIN 0x4381 +#define ADE7854_BIGAIN 0x4382 +#define ADE7854_BVGAIN 0x4383 +#define ADE7854_CIGAIN 0x4384 +#define ADE7854_CVGAIN 0x4385 +#define ADE7854_NIGAIN 0x4386 +#define ADE7854_AIRMSOS 0x4387 +#define ADE7854_AVRMSOS 0x4388 +#define ADE7854_BIRMSOS 0x4389 +#define ADE7854_BVRMSOS 0x438A +#define ADE7854_CIRMSOS 0x438B +#define ADE7854_CVRMSOS 0x438C +#define ADE7854_NIRMSOS 0x438D +#define ADE7854_AVAGAIN 0x438E +#define ADE7854_BVAGAIN 0x438F +#define ADE7854_CVAGAIN 0x4390 +#define ADE7854_AWGAIN 0x4391 +#define ADE7854_AWATTOS 0x4392 +#define ADE7854_BWGAIN 0x4393 +#define ADE7854_BWATTOS 0x4394 +#define ADE7854_CWGAIN 0x4395 +#define ADE7854_CWATTOS 0x4396 +#define ADE7854_AVARGAIN 0x4397 +#define ADE7854_AVAROS 0x4398 +#define ADE7854_BVARGAIN 0x4399 +#define ADE7854_BVAROS 0x439A +#define ADE7854_CVARGAIN 0x439B +#define ADE7854_CVAROS 0x439C +#define ADE7854_AFWGAIN 0x439D +#define ADE7854_AFWATTOS 0x439E +#define ADE7854_BFWGAIN 0x439F +#define ADE7854_BFWATTOS 0x43A0 +#define ADE7854_CFWGAIN 0x43A1 +#define ADE7854_CFWATTOS 0x43A2 +#define ADE7854_AFVARGAIN 0x43A3 +#define ADE7854_AFVAROS 0x43A4 +#define ADE7854_BFVARGAIN 0x43A5 +#define ADE7854_BFVAROS 0x43A6 +#define ADE7854_CFVARGAIN 0x43A7 +#define ADE7854_CFVAROS 0x43A8 +#define ADE7854_VATHR1 0x43A9 +#define ADE7854_VATHR0 0x43AA +#define ADE7854_WTHR1 0x43AB +#define ADE7854_WTHR0 0x43AC +#define ADE7854_VARTHR1 0x43AD +#define ADE7854_VARTHR0 0x43AE +#define ADE7854_RSV 0x43AF +#define ADE7854_VANOLOAD 0x43B0 +#define ADE7854_APNOLOAD 0x43B1 +#define ADE7854_VARNOLOAD 0x43B2 +#define ADE7854_VLEVEL 0x43B3 +#define ADE7854_DICOEFF 0x43B5 +#define ADE7854_HPFDIS 0x43B6 +#define ADE7854_ISUMLVL 0x43B8 +#define ADE7854_ISUM 0x43BF +#define ADE7854_AIRMS 0x43C0 +#define ADE7854_AVRMS 0x43C1 +#define ADE7854_BIRMS 0x43C2 +#define ADE7854_BVRMS 0x43C3 +#define ADE7854_CIRMS 0x43C4 +#define ADE7854_CVRMS 0x43C5 +#define ADE7854_NIRMS 0x43C6 +#define ADE7854_RUN 0xE228 +#define ADE7854_AWATTHR 0xE400 +#define ADE7854_BWATTHR 0xE401 +#define ADE7854_CWATTHR 0xE402 +#define ADE7854_AFWATTHR 0xE403 +#define ADE7854_BFWATTHR 0xE404 +#define ADE7854_CFWATTHR 0xE405 +#define ADE7854_AVARHR 0xE406 +#define ADE7854_BVARHR 0xE407 +#define ADE7854_CVARHR 0xE408 +#define ADE7854_AFVARHR 0xE409 +#define ADE7854_BFVARHR 0xE40A +#define ADE7854_CFVARHR 0xE40B +#define ADE7854_AVAHR 0xE40C +#define ADE7854_BVAHR 0xE40D +#define ADE7854_CVAHR 0xE40E +#define ADE7854_IPEAK 0xE500 +#define ADE7854_VPEAK 0xE501 +#define ADE7854_STATUS0 0xE502 +#define ADE7854_STATUS1 0xE503 +#define ADE7854_OILVL 0xE507 +#define ADE7854_OVLVL 0xE508 +#define ADE7854_SAGLVL 0xE509 +#define ADE7854_MASK0 0xE50A +#define ADE7854_MASK1 0xE50B +#define ADE7854_IAWV 0xE50C +#define ADE7854_IBWV 0xE50D +#define ADE7854_ICWV 0xE50E +#define ADE7854_VAWV 0xE510 +#define ADE7854_VBWV 0xE511 +#define ADE7854_VCWV 0xE512 +#define ADE7854_AWATT 0xE513 +#define ADE7854_BWATT 0xE514 +#define ADE7854_CWATT 0xE515 +#define ADE7854_AVA 0xE519 +#define ADE7854_BVA 0xE51A +#define ADE7854_CVA 0xE51B +#define ADE7854_CHECKSUM 0xE51F +#define ADE7854_VNOM 0xE520 +#define ADE7854_PHSTATUS 0xE600 +#define ADE7854_ANGLE0 0xE601 +#define ADE7854_ANGLE1 0xE602 +#define ADE7854_ANGLE2 0xE603 +#define ADE7854_PERIOD 0xE607 +#define ADE7854_PHNOLOAD 0xE608 +#define ADE7854_LINECYC 0xE60C +#define ADE7854_ZXTOUT 0xE60D +#define ADE7854_COMPMODE 0xE60E +#define ADE7854_GAIN 0xE60F +#define ADE7854_CFMODE 0xE610 +#define ADE7854_CF1DEN 0xE611 +#define ADE7854_CF2DEN 0xE612 +#define ADE7854_CF3DEN 0xE613 +#define ADE7854_APHCAL 0xE614 +#define ADE7854_BPHCAL 0xE615 +#define ADE7854_CPHCAL 0xE616 +#define ADE7854_PHSIGN 0xE617 +#define ADE7854_CONFIG 0xE618 +#define ADE7854_MMODE 0xE700 +#define ADE7854_ACCMODE 0xE701 +#define ADE7854_LCYCMODE 0xE702 +#define ADE7854_PEAKCYC 0xE703 +#define ADE7854_SAGCYC 0xE704 +#define ADE7854_CFCYC 0xE705 +#define ADE7854_HSDC_CFG 0xE706 +#define ADE7854_CONFIG2 0xEC01 + +#define ADE7854_READ_REG 0x1 +#define ADE7854_WRITE_REG 0x0 + +#define ADE7854_MAX_TX 7 +#define ADE7854_MAX_RX 7 +#define ADE7854_STARTUP_DELAY 1 + +#define ADE7854_SPI_SLOW (u32)(300 * 1000) +#define ADE7854_SPI_BURST (u32)(1000 * 1000) +#define ADE7854_SPI_FAST (u32)(2000 * 1000) + +/** + * struct ade7854_state - device instance specific data + * @spi: actual spi_device + * @indio_dev: industrial I/O device structure + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct ade7854_state { + struct spi_device *spi; + struct i2c_client *i2c; + int (*read_reg_8)(struct device *, u16, u8 *); + int (*read_reg_16)(struct device *, u16, u16 *); + int (*read_reg_24)(struct device *, u16, u32 *); + int (*read_reg_32)(struct device *, u16, u32 *); + int (*write_reg_8)(struct device *, u16, u8); + int (*write_reg_16)(struct device *, u16, u16); + int (*write_reg_24)(struct device *, u16, u32); + int (*write_reg_32)(struct device *, u16, u32); + int irq; + struct mutex buf_lock; + u8 tx[ADE7854_MAX_TX] ____cacheline_aligned; + u8 rx[ADE7854_MAX_RX]; + +}; + +extern int ade7854_probe(struct iio_dev *indio_dev, struct device *dev); +extern int ade7854_remove(struct iio_dev *indio_dev); + +#endif diff --git a/kernel/drivers/staging/iio/meter/meter.h b/kernel/drivers/staging/iio/meter/meter.h new file mode 100644 index 000000000..dfba510f2 --- /dev/null +++ b/kernel/drivers/staging/iio/meter/meter.h @@ -0,0 +1,400 @@ +#ifndef _METER_H +#define _METER_H + +#include + +/* metering ic types of attribute */ + +#define IIO_DEV_ATTR_CURRENT_A_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_a_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_B_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_b_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_C_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_c_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VOLT_A_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(volt_a_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VOLT_B_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(volt_b_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VOLT_C_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(volt_c_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_A_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_a_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_B_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_b_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_C_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_c_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_A_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_a_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_B_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_b_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_C_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_c_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_A_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_a_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_B_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_b_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_C_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_c_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_APPARENT_POWER_A_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(apparent_power_a_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_APPARENT_POWER_B_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(apparent_power_b_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_APPARENT_POWER_C_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(apparent_power_c_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_A_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_a_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_B_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_b_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_C_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_c_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_A_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_a_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_B_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_b_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_C_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_c_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_A(_show, _addr) \ + IIO_DEVICE_ATTR(current_a, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CURRENT_B(_show, _addr) \ + IIO_DEVICE_ATTR(current_b, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CURRENT_C(_show, _addr) \ + IIO_DEVICE_ATTR(current_c, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_VOLT_A(_show, _addr) \ + IIO_DEVICE_ATTR(volt_a, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_VOLT_B(_show, _addr) \ + IIO_DEVICE_ATTR(volt_b, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_VOLT_C(_show, _addr) \ + IIO_DEVICE_ATTR(volt_c, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_AENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(aenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(lenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_RAENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(raenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LAENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(laenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_VAENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(vaenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LVAENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(lvaenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_RVAENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(rvaenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LVARENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(lvarenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CHKSUM(_show, _addr) \ + IIO_DEVICE_ATTR(chksum, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_ANGLE0(_show, _addr) \ + IIO_DEVICE_ATTR(angle0, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_ANGLE1(_show, _addr) \ + IIO_DEVICE_ATTR(angle1, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_ANGLE2(_show, _addr) \ + IIO_DEVICE_ATTR(angle2, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_AWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(awatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_BWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(bwatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(cwatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_AFWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(afwatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_BFWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(bfwatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CFWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(cfwatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_AVARHR(_show, _addr) \ + IIO_DEVICE_ATTR(avarhr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_BVARHR(_show, _addr) \ + IIO_DEVICE_ATTR(bvarhr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CVARHR(_show, _addr) \ + IIO_DEVICE_ATTR(cvarhr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_AVAHR(_show, _addr) \ + IIO_DEVICE_ATTR(avahr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_BVAHR(_show, _addr) \ + IIO_DEVICE_ATTR(bvahr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CVAHR(_show, _addr) \ + IIO_DEVICE_ATTR(cvahr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_IOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(ios, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_PHCAL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(phcal, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_APHCAL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(aphcal, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BPHCAL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bphcal, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CPHCAL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cphcal, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_APOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(apos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AAPOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(aapos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BAPOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bapos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CAPOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(capos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AVRMSGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(avrmsgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BVRMSGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bvrmsgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CVRMSGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cvrmsgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AIGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(aigain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BIGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bigain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CIGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cigain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_NIGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(nigain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AVGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(avgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BVGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bvgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CVGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cvgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_WGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(wgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_WDIV(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(wdiv, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CFNUM(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cfnum, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CFDEN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cfden, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CF1DEN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cf1den, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CF2DEN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cf2den, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CF3DEN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cf3den, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_IRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(irms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vrms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AIRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(airms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BIRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(birms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CIRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cirms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_NIRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(nirms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AVRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(avrms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BVRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bvrms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CVRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cvrms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_IRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(irmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vrmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AIRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(airmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BIRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(birmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CIRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cirmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AVRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(avrmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BVRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bvrmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CVRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cvrmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VAGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vagain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_PGA_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(pga_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VADIV(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vadiv, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_LINECYC(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(linecyc, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_SAGCYC(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(sagcyc, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CFCYC(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cfcyc, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_PEAKCYC(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(peakcyc, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_SAGLVL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(saglvl, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_IPKLVL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(ipklvl, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VPKLVL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vpklvl, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_IPEAK(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(ipeak, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_RIPEAK(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(ripeak, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VPEAK(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vpeak, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_RVPEAK(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(rvpeak, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VPERIOD(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vperiod, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CH_OFF(_num, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(choff_##_num, _mode, _show, _store, _addr) + +/* active energy register, AENERGY, is more than half full */ +#define IIO_EVENT_ATTR_AENERGY_HALF_FULL(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(aenergy_half_full, _evlist, _show, _store, _mask) + +/* a SAG on the line voltage */ +#define IIO_EVENT_ATTR_LINE_VOLT_SAG(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(line_volt_sag, _evlist, _show, _store, _mask) + +/* + * Indicates the end of energy accumulation over an integer number + * of half line cycles + */ +#define IIO_EVENT_ATTR_CYCEND(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(cycend, _evlist, _show, _store, _mask) + +/* on the rising and falling edge of the voltage waveform */ +#define IIO_EVENT_ATTR_ZERO_CROSS(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(zero_cross, _evlist, _show, _store, _mask) + +/* the active energy register has overflowed */ +#define IIO_EVENT_ATTR_AENERGY_OVERFLOW(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(aenergy_overflow, _evlist, _show, _store, _mask) + +/* the apparent energy register has overflowed */ +#define IIO_EVENT_ATTR_VAENERGY_OVERFLOW(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(vaenergy_overflow, _evlist, _show, _store, _mask) + +/* the active energy register, VAENERGY, is more than half full */ +#define IIO_EVENT_ATTR_VAENERGY_HALF_FULL(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(vaenergy_half_full, _evlist, _show, _store, _mask) + +/* the power has gone from negative to positive */ +#define IIO_EVENT_ATTR_PPOS(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(ppos, _evlist, _show, _store, _mask) + +/* the power has gone from positive to negative */ +#define IIO_EVENT_ATTR_PNEG(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(pneg, _evlist, _show, _store, _mask) + +/* waveform sample from Channel 1 has exceeded the IPKLVL value */ +#define IIO_EVENT_ATTR_IPKLVL_EXC(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(ipklvl_exc, _evlist, _show, _store, _mask) + +/* waveform sample from Channel 2 has exceeded the VPKLVL value */ +#define IIO_EVENT_ATTR_VPKLVL_EXC(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(vpklvl_exc, _evlist, _show, _store, _mask) + +#endif /* _METER_H */ diff --git a/kernel/drivers/staging/iio/resolver/Kconfig b/kernel/drivers/staging/iio/resolver/Kconfig new file mode 100644 index 000000000..c7a742ec1 --- /dev/null +++ b/kernel/drivers/staging/iio/resolver/Kconfig @@ -0,0 +1,39 @@ +# +# Resolver/Synchro drivers +# +menu "Resolver to digital converters" + +config AD2S90 + tristate "Analog Devices ad2s90 driver" + depends on SPI + help + Say yes here to build support for Analog Devices spi resolver + to digital converters, ad2s90, provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad2s90. + +config AD2S1200 + tristate "Analog Devices ad2s1200/ad2s1205 driver" + depends on SPI + depends on GPIOLIB + help + Say yes here to build support for Analog Devices spi resolver + to digital converters, ad2s1200 and ad2s1205, provides direct access + via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad2s1200. + +config AD2S1210 + tristate "Analog Devices ad2s1210 driver" + depends on SPI + depends on GPIOLIB + help + Say yes here to build support for Analog Devices spi resolver + to digital converters, ad2s1210, provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad2s1210. + +endmenu diff --git a/kernel/drivers/staging/iio/resolver/Makefile b/kernel/drivers/staging/iio/resolver/Makefile new file mode 100644 index 000000000..14375e444 --- /dev/null +++ b/kernel/drivers/staging/iio/resolver/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for Resolver/Synchro drivers +# + +obj-$(CONFIG_AD2S90) += ad2s90.o +obj-$(CONFIG_AD2S1200) += ad2s1200.o +obj-$(CONFIG_AD2S1210) += ad2s1210.o diff --git a/kernel/drivers/staging/iio/resolver/ad2s1200.c b/kernel/drivers/staging/iio/resolver/ad2s1200.c new file mode 100644 index 000000000..c17893b49 --- /dev/null +++ b/kernel/drivers/staging/iio/resolver/ad2s1200.c @@ -0,0 +1,167 @@ +/* + * ad2s1200.c simple support for the ADI Resolver to Digital Converters: + * AD2S1200/1205 + * + * Copyright (c) 2010-2010 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DRV_NAME "ad2s1200" + +/* input pin sample and rdvel is controlled by driver */ +#define AD2S1200_PN 2 + +/* input clock on serial interface */ +#define AD2S1200_HZ 8192000 +/* clock period in nano second */ +#define AD2S1200_TSCLK (1000000000/AD2S1200_HZ) + +struct ad2s1200_state { + struct mutex lock; + struct spi_device *sdev; + int sample; + int rdvel; + u8 rx[2] ____cacheline_aligned; +}; + +static int ad2s1200_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret = 0; + s16 vel; + struct ad2s1200_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + gpio_set_value(st->sample, 0); + /* delay (6 * AD2S1200_TSCLK + 20) nano seconds */ + udelay(1); + gpio_set_value(st->sample, 1); + gpio_set_value(st->rdvel, !!(chan->type == IIO_ANGL)); + ret = spi_read(st->sdev, st->rx, 2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + + switch (chan->type) { + case IIO_ANGL: + *val = (((u16)(st->rx[0])) << 4) | ((st->rx[1] & 0xF0) >> 4); + break; + case IIO_ANGL_VEL: + vel = (((s16)(st->rx[0])) << 4) | ((st->rx[1] & 0xF0) >> 4); + vel = sign_extend32(vel, 11); + *val = vel; + break; + default: + mutex_unlock(&st->lock); + return -EINVAL; + } + /* delay (2 * AD2S1200_TSCLK + 20) ns for sample pulse */ + udelay(1); + mutex_unlock(&st->lock); + return IIO_VAL_INT; +} + +static const struct iio_chan_spec ad2s1200_channels[] = { + { + .type = IIO_ANGL, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_ANGL_VEL, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static const struct iio_info ad2s1200_info = { + .read_raw = &ad2s1200_read_raw, + .driver_module = THIS_MODULE, +}; + +static int ad2s1200_probe(struct spi_device *spi) +{ + struct ad2s1200_state *st; + struct iio_dev *indio_dev; + int pn, ret = 0; + unsigned short *pins = spi->dev.platform_data; + + for (pn = 0; pn < AD2S1200_PN; pn++) { + ret = devm_gpio_request_one(&spi->dev, pins[pn], GPIOF_DIR_OUT, + DRV_NAME); + if (ret) { + dev_err(&spi->dev, "request gpio pin %d failed\n", + pins[pn]); + return ret; + } + } + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + mutex_init(&st->lock); + st->sdev = spi; + st->sample = pins[0]; + st->rdvel = pins[1]; + + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ad2s1200_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad2s1200_channels; + indio_dev->num_channels = ARRAY_SIZE(ad2s1200_channels); + indio_dev->name = spi_get_device_id(spi)->name; + + ret = devm_iio_device_register(&spi->dev, indio_dev); + if (ret) + return ret; + + spi->max_speed_hz = AD2S1200_HZ; + spi->mode = SPI_MODE_3; + spi_setup(spi); + + return 0; +} + +static const struct spi_device_id ad2s1200_id[] = { + { "ad2s1200" }, + { "ad2s1205" }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad2s1200_id); + +static struct spi_driver ad2s1200_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ad2s1200_probe, + .id_table = ad2s1200_id, +}; +module_spi_driver(ad2s1200_driver); + +MODULE_AUTHOR("Graff Yang "); +MODULE_DESCRIPTION("Analog Devices AD2S1200/1205 Resolver to Digital SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/resolver/ad2s1210.c b/kernel/drivers/staging/iio/resolver/ad2s1210.c new file mode 100644 index 000000000..7bc3e4a73 --- /dev/null +++ b/kernel/drivers/staging/iio/resolver/ad2s1210.c @@ -0,0 +1,748 @@ +/* + * ad2s1210.c support for the ADI Resolver to Digital Converters: AD2S1210 + * + * Copyright (c) 2010-2010 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "ad2s1210.h" + +#define DRV_NAME "ad2s1210" + +#define AD2S1210_DEF_CONTROL 0x7E + +#define AD2S1210_MSB_IS_HIGH 0x80 +#define AD2S1210_MSB_IS_LOW 0x7F +#define AD2S1210_PHASE_LOCK_RANGE_44 0x20 +#define AD2S1210_ENABLE_HYSTERESIS 0x10 +#define AD2S1210_SET_ENRES1 0x08 +#define AD2S1210_SET_ENRES0 0x04 +#define AD2S1210_SET_RES1 0x02 +#define AD2S1210_SET_RES0 0x01 + +#define AD2S1210_SET_ENRESOLUTION (AD2S1210_SET_ENRES1 | \ + AD2S1210_SET_ENRES0) +#define AD2S1210_SET_RESOLUTION (AD2S1210_SET_RES1 | AD2S1210_SET_RES0) + +#define AD2S1210_REG_POSITION 0x80 +#define AD2S1210_REG_VELOCITY 0x82 +#define AD2S1210_REG_LOS_THRD 0x88 +#define AD2S1210_REG_DOS_OVR_THRD 0x89 +#define AD2S1210_REG_DOS_MIS_THRD 0x8A +#define AD2S1210_REG_DOS_RST_MAX_THRD 0x8B +#define AD2S1210_REG_DOS_RST_MIN_THRD 0x8C +#define AD2S1210_REG_LOT_HIGH_THRD 0x8D +#define AD2S1210_REG_LOT_LOW_THRD 0x8E +#define AD2S1210_REG_EXCIT_FREQ 0x91 +#define AD2S1210_REG_CONTROL 0x92 +#define AD2S1210_REG_SOFT_RESET 0xF0 +#define AD2S1210_REG_FAULT 0xFF + +/* pin SAMPLE, A0, A1, RES0, RES1, is controlled by driver */ +#define AD2S1210_SAA 3 +#define AD2S1210_PN (AD2S1210_SAA + AD2S1210_RES) + +#define AD2S1210_MIN_CLKIN 6144000 +#define AD2S1210_MAX_CLKIN 10240000 +#define AD2S1210_MIN_EXCIT 2000 +#define AD2S1210_MAX_EXCIT 20000 +#define AD2S1210_MIN_FCW 0x4 +#define AD2S1210_MAX_FCW 0x50 + +/* default input clock on serial interface */ +#define AD2S1210_DEF_CLKIN 8192000 +/* clock period in nano second */ +#define AD2S1210_DEF_TCK (1000000000/AD2S1210_DEF_CLKIN) +#define AD2S1210_DEF_EXCIT 10000 + +enum ad2s1210_mode { + MOD_POS = 0, + MOD_VEL, + MOD_CONFIG, + MOD_RESERVED, +}; + +static const unsigned int ad2s1210_resolution_value[] = { 10, 12, 14, 16 }; + +struct ad2s1210_state { + const struct ad2s1210_platform_data *pdata; + struct mutex lock; + struct spi_device *sdev; + unsigned int fclkin; + unsigned int fexcit; + bool hysteresis; + bool old_data; + u8 resolution; + enum ad2s1210_mode mode; + u8 rx[2] ____cacheline_aligned; + u8 tx[2] ____cacheline_aligned; +}; + +static const int ad2s1210_mode_vals[4][2] = { + [MOD_POS] = { 0, 0 }, + [MOD_VEL] = { 0, 1 }, + [MOD_CONFIG] = { 1, 0 }, +}; +static inline void ad2s1210_set_mode(enum ad2s1210_mode mode, + struct ad2s1210_state *st) +{ + gpio_set_value(st->pdata->a[0], ad2s1210_mode_vals[mode][0]); + gpio_set_value(st->pdata->a[1], ad2s1210_mode_vals[mode][1]); + st->mode = mode; +} + +/* write 1 bytes (address or data) to the chip */ +static int ad2s1210_config_write(struct ad2s1210_state *st, u8 data) +{ + int ret; + + ad2s1210_set_mode(MOD_CONFIG, st); + st->tx[0] = data; + ret = spi_write(st->sdev, st->tx, 1); + if (ret < 0) + return ret; + st->old_data = true; + + return 0; +} + +/* read value from one of the registers */ +static int ad2s1210_config_read(struct ad2s1210_state *st, + unsigned char address) +{ + struct spi_transfer xfer = { + .len = 2, + .rx_buf = st->rx, + .tx_buf = st->tx, + }; + int ret = 0; + + ad2s1210_set_mode(MOD_CONFIG, st); + st->tx[0] = address | AD2S1210_MSB_IS_HIGH; + st->tx[1] = AD2S1210_REG_FAULT; + ret = spi_sync_transfer(st->sdev, &xfer, 1); + if (ret < 0) + return ret; + st->old_data = true; + + return st->rx[1]; +} + +static inline +int ad2s1210_update_frequency_control_word(struct ad2s1210_state *st) +{ + int ret; + unsigned char fcw; + + fcw = (unsigned char)(st->fexcit * (1 << 15) / st->fclkin); + if (fcw < AD2S1210_MIN_FCW || fcw > AD2S1210_MAX_FCW) { + dev_err(&st->sdev->dev, "ad2s1210: FCW out of range\n"); + return -ERANGE; + } + + ret = ad2s1210_config_write(st, AD2S1210_REG_EXCIT_FREQ); + if (ret < 0) + return ret; + + return ad2s1210_config_write(st, fcw); +} + +static unsigned char ad2s1210_read_resolution_pin(struct ad2s1210_state *st) +{ + return ad2s1210_resolution_value[ + (gpio_get_value(st->pdata->res[0]) << 1) | + gpio_get_value(st->pdata->res[1])]; +} + +static const int ad2s1210_res_pins[4][2] = { + { 0, 0 }, {0, 1}, {1, 0}, {1, 1} +}; + +static inline void ad2s1210_set_resolution_pin(struct ad2s1210_state *st) +{ + gpio_set_value(st->pdata->res[0], + ad2s1210_res_pins[(st->resolution - 10)/2][0]); + gpio_set_value(st->pdata->res[1], + ad2s1210_res_pins[(st->resolution - 10)/2][1]); +} + +static inline int ad2s1210_soft_reset(struct ad2s1210_state *st) +{ + int ret; + + ret = ad2s1210_config_write(st, AD2S1210_REG_SOFT_RESET); + if (ret < 0) + return ret; + + return ad2s1210_config_write(st, 0x0); +} + +static ssize_t ad2s1210_show_fclkin(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%u\n", st->fclkin); +} + +static ssize_t ad2s1210_store_fclkin(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned int fclkin; + int ret; + + ret = kstrtouint(buf, 10, &fclkin); + if (ret) + return ret; + if (fclkin < AD2S1210_MIN_CLKIN || fclkin > AD2S1210_MAX_CLKIN) { + dev_err(dev, "ad2s1210: fclkin out of range\n"); + return -EINVAL; + } + + mutex_lock(&st->lock); + st->fclkin = fclkin; + + ret = ad2s1210_update_frequency_control_word(st); + if (ret < 0) + goto error_ret; + ret = ad2s1210_soft_reset(st); +error_ret: + mutex_unlock(&st->lock); + + return ret < 0 ? ret : len; +} + +static ssize_t ad2s1210_show_fexcit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%u\n", st->fexcit); +} + +static ssize_t ad2s1210_store_fexcit(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned int fexcit; + int ret; + + ret = kstrtouint(buf, 10, &fexcit); + if (ret < 0) + return ret; + if (fexcit < AD2S1210_MIN_EXCIT || fexcit > AD2S1210_MAX_EXCIT) { + dev_err(dev, + "ad2s1210: excitation frequency out of range\n"); + return -EINVAL; + } + mutex_lock(&st->lock); + st->fexcit = fexcit; + ret = ad2s1210_update_frequency_control_word(st); + if (ret < 0) + goto error_ret; + ret = ad2s1210_soft_reset(st); +error_ret: + mutex_unlock(&st->lock); + + return ret < 0 ? ret : len; +} + +static ssize_t ad2s1210_show_control(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + int ret; + + mutex_lock(&st->lock); + ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL); + mutex_unlock(&st->lock); + return ret < 0 ? ret : sprintf(buf, "0x%x\n", ret); +} + +static ssize_t ad2s1210_store_control(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned char udata; + unsigned char data; + int ret; + + ret = kstrtou8(buf, 16, &udata); + if (ret) + return -EINVAL; + + mutex_lock(&st->lock); + ret = ad2s1210_config_write(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + data = udata & AD2S1210_MSB_IS_LOW; + ret = ad2s1210_config_write(st, data); + if (ret < 0) + goto error_ret; + + ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + if (ret & AD2S1210_MSB_IS_HIGH) { + ret = -EIO; + dev_err(dev, + "ad2s1210: write control register fail\n"); + goto error_ret; + } + st->resolution + = ad2s1210_resolution_value[data & AD2S1210_SET_RESOLUTION]; + if (st->pdata->gpioin) { + data = ad2s1210_read_resolution_pin(st); + if (data != st->resolution) + dev_warn(dev, "ad2s1210: resolution settings not match\n"); + } else + ad2s1210_set_resolution_pin(st); + + ret = len; + st->hysteresis = !!(data & AD2S1210_ENABLE_HYSTERESIS); + +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +static ssize_t ad2s1210_show_resolution(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%d\n", st->resolution); +} + +static ssize_t ad2s1210_store_resolution(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned char data; + unsigned char udata; + int ret; + + ret = kstrtou8(buf, 10, &udata); + if (ret || udata < 10 || udata > 16) { + dev_err(dev, "ad2s1210: resolution out of range\n"); + return -EINVAL; + } + mutex_lock(&st->lock); + ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + data = ret; + data &= ~AD2S1210_SET_RESOLUTION; + data |= (udata - 10) >> 1; + ret = ad2s1210_config_write(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + ret = ad2s1210_config_write(st, data & AD2S1210_MSB_IS_LOW); + if (ret < 0) + goto error_ret; + ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + data = ret; + if (data & AD2S1210_MSB_IS_HIGH) { + ret = -EIO; + dev_err(dev, "ad2s1210: setting resolution fail\n"); + goto error_ret; + } + st->resolution + = ad2s1210_resolution_value[data & AD2S1210_SET_RESOLUTION]; + if (st->pdata->gpioin) { + data = ad2s1210_read_resolution_pin(st); + if (data != st->resolution) + dev_warn(dev, "ad2s1210: resolution settings not match\n"); + } else + ad2s1210_set_resolution_pin(st); + ret = len; +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +/* read the fault register since last sample */ +static ssize_t ad2s1210_show_fault(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + int ret; + + mutex_lock(&st->lock); + ret = ad2s1210_config_read(st, AD2S1210_REG_FAULT); + mutex_unlock(&st->lock); + + return ret ? ret : sprintf(buf, "0x%x\n", ret); +} + +static ssize_t ad2s1210_clear_fault(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + int ret; + + mutex_lock(&st->lock); + gpio_set_value(st->pdata->sample, 0); + /* delay (2 * tck + 20) nano seconds */ + udelay(1); + gpio_set_value(st->pdata->sample, 1); + ret = ad2s1210_config_read(st, AD2S1210_REG_FAULT); + if (ret < 0) + goto error_ret; + gpio_set_value(st->pdata->sample, 0); + gpio_set_value(st->pdata->sample, 1); +error_ret: + mutex_unlock(&st->lock); + + return ret < 0 ? ret : len; +} + +static ssize_t ad2s1210_show_reg(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + struct iio_dev_attr *iattr = to_iio_dev_attr(attr); + int ret; + + mutex_lock(&st->lock); + ret = ad2s1210_config_read(st, iattr->address); + mutex_unlock(&st->lock); + + return ret < 0 ? ret : sprintf(buf, "%d\n", ret); +} + +static ssize_t ad2s1210_store_reg(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev)); + unsigned char data; + int ret; + struct iio_dev_attr *iattr = to_iio_dev_attr(attr); + + ret = kstrtou8(buf, 10, &data); + if (ret) + return -EINVAL; + mutex_lock(&st->lock); + ret = ad2s1210_config_write(st, iattr->address); + if (ret < 0) + goto error_ret; + ret = ad2s1210_config_write(st, data & AD2S1210_MSB_IS_LOW); +error_ret: + mutex_unlock(&st->lock); + return ret < 0 ? ret : len; +} + +static int ad2s1210_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad2s1210_state *st = iio_priv(indio_dev); + bool negative; + int ret = 0; + u16 pos; + s16 vel; + + mutex_lock(&st->lock); + gpio_set_value(st->pdata->sample, 0); + /* delay (6 * tck + 20) nano seconds */ + udelay(1); + + switch (chan->type) { + case IIO_ANGL: + ad2s1210_set_mode(MOD_POS, st); + break; + case IIO_ANGL_VEL: + ad2s1210_set_mode(MOD_VEL, st); + break; + default: + ret = -EINVAL; + break; + } + if (ret < 0) + goto error_ret; + ret = spi_read(st->sdev, st->rx, 2); + if (ret < 0) + goto error_ret; + + switch (chan->type) { + case IIO_ANGL: + pos = be16_to_cpup((__be16 *) st->rx); + if (st->hysteresis) + pos >>= 16 - st->resolution; + *val = pos; + ret = IIO_VAL_INT; + break; + case IIO_ANGL_VEL: + negative = st->rx[0] & 0x80; + vel = be16_to_cpup((__be16 *) st->rx); + vel >>= 16 - st->resolution; + if (vel & 0x8000) { + negative = (0xffff >> st->resolution) << st->resolution; + vel |= negative; + } + *val = vel; + ret = IIO_VAL_INT; + break; + default: + mutex_unlock(&st->lock); + return -EINVAL; + } + +error_ret: + gpio_set_value(st->pdata->sample, 1); + /* delay (2 * tck + 20) nano seconds */ + udelay(1); + mutex_unlock(&st->lock); + return ret; +} + +static IIO_DEVICE_ATTR(fclkin, S_IRUGO | S_IWUSR, + ad2s1210_show_fclkin, ad2s1210_store_fclkin, 0); +static IIO_DEVICE_ATTR(fexcit, S_IRUGO | S_IWUSR, + ad2s1210_show_fexcit, ad2s1210_store_fexcit, 0); +static IIO_DEVICE_ATTR(control, S_IRUGO | S_IWUSR, + ad2s1210_show_control, ad2s1210_store_control, 0); +static IIO_DEVICE_ATTR(bits, S_IRUGO | S_IWUSR, + ad2s1210_show_resolution, ad2s1210_store_resolution, 0); +static IIO_DEVICE_ATTR(fault, S_IRUGO | S_IWUSR, + ad2s1210_show_fault, ad2s1210_clear_fault, 0); + +static IIO_DEVICE_ATTR(los_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_LOS_THRD); +static IIO_DEVICE_ATTR(dos_ovr_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_DOS_OVR_THRD); +static IIO_DEVICE_ATTR(dos_mis_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_DOS_MIS_THRD); +static IIO_DEVICE_ATTR(dos_rst_max_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_DOS_RST_MAX_THRD); +static IIO_DEVICE_ATTR(dos_rst_min_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_DOS_RST_MIN_THRD); +static IIO_DEVICE_ATTR(lot_high_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_LOT_HIGH_THRD); +static IIO_DEVICE_ATTR(lot_low_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_LOT_LOW_THRD); + + +static const struct iio_chan_spec ad2s1210_channels[] = { + { + .type = IIO_ANGL, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .type = IIO_ANGL_VEL, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static struct attribute *ad2s1210_attributes[] = { + &iio_dev_attr_fclkin.dev_attr.attr, + &iio_dev_attr_fexcit.dev_attr.attr, + &iio_dev_attr_control.dev_attr.attr, + &iio_dev_attr_bits.dev_attr.attr, + &iio_dev_attr_fault.dev_attr.attr, + &iio_dev_attr_los_thrd.dev_attr.attr, + &iio_dev_attr_dos_ovr_thrd.dev_attr.attr, + &iio_dev_attr_dos_mis_thrd.dev_attr.attr, + &iio_dev_attr_dos_rst_max_thrd.dev_attr.attr, + &iio_dev_attr_dos_rst_min_thrd.dev_attr.attr, + &iio_dev_attr_lot_high_thrd.dev_attr.attr, + &iio_dev_attr_lot_low_thrd.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad2s1210_attribute_group = { + .attrs = ad2s1210_attributes, +}; + +static int ad2s1210_initial(struct ad2s1210_state *st) +{ + unsigned char data; + int ret; + + mutex_lock(&st->lock); + if (st->pdata->gpioin) + st->resolution = ad2s1210_read_resolution_pin(st); + else + ad2s1210_set_resolution_pin(st); + + ret = ad2s1210_config_write(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + data = AD2S1210_DEF_CONTROL & ~(AD2S1210_SET_RESOLUTION); + data |= (st->resolution - 10) >> 1; + ret = ad2s1210_config_write(st, data); + if (ret < 0) + goto error_ret; + ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + + if (ret & AD2S1210_MSB_IS_HIGH) { + ret = -EIO; + goto error_ret; + } + + ret = ad2s1210_update_frequency_control_word(st); + if (ret < 0) + goto error_ret; + ret = ad2s1210_soft_reset(st); +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +static const struct iio_info ad2s1210_info = { + .read_raw = &ad2s1210_read_raw, + .attrs = &ad2s1210_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ad2s1210_setup_gpios(struct ad2s1210_state *st) +{ + unsigned long flags = st->pdata->gpioin ? GPIOF_DIR_IN : GPIOF_DIR_OUT; + struct gpio ad2s1210_gpios[] = { + { st->pdata->sample, GPIOF_DIR_IN, "sample" }, + { st->pdata->a[0], flags, "a0" }, + { st->pdata->a[1], flags, "a1" }, + { st->pdata->res[0], flags, "res0" }, + { st->pdata->res[0], flags, "res1" }, + }; + + return gpio_request_array(ad2s1210_gpios, ARRAY_SIZE(ad2s1210_gpios)); +} + +static void ad2s1210_free_gpios(struct ad2s1210_state *st) +{ + unsigned long flags = st->pdata->gpioin ? GPIOF_DIR_IN : GPIOF_DIR_OUT; + struct gpio ad2s1210_gpios[] = { + { st->pdata->sample, GPIOF_DIR_IN, "sample" }, + { st->pdata->a[0], flags, "a0" }, + { st->pdata->a[1], flags, "a1" }, + { st->pdata->res[0], flags, "res0" }, + { st->pdata->res[0], flags, "res1" }, + }; + + gpio_free_array(ad2s1210_gpios, ARRAY_SIZE(ad2s1210_gpios)); +} + +static int ad2s1210_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad2s1210_state *st; + int ret; + + if (spi->dev.platform_data == NULL) + return -EINVAL; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + st->pdata = spi->dev.platform_data; + ret = ad2s1210_setup_gpios(st); + if (ret < 0) + return ret; + + spi_set_drvdata(spi, indio_dev); + + mutex_init(&st->lock); + st->sdev = spi; + st->hysteresis = true; + st->mode = MOD_CONFIG; + st->resolution = 12; + st->fexcit = AD2S1210_DEF_EXCIT; + + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ad2s1210_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad2s1210_channels; + indio_dev->num_channels = ARRAY_SIZE(ad2s1210_channels); + indio_dev->name = spi_get_device_id(spi)->name; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_gpios; + + st->fclkin = spi->max_speed_hz; + spi->mode = SPI_MODE_3; + spi_setup(spi); + ad2s1210_initial(st); + + return 0; + +error_free_gpios: + ad2s1210_free_gpios(st); + return ret; +} + +static int ad2s1210_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + ad2s1210_free_gpios(iio_priv(indio_dev)); + + return 0; +} + +static const struct spi_device_id ad2s1210_id[] = { + { "ad2s1210" }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad2s1210_id); + +static struct spi_driver ad2s1210_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ad2s1210_probe, + .remove = ad2s1210_remove, + .id_table = ad2s1210_id, +}; +module_spi_driver(ad2s1210_driver); + +MODULE_AUTHOR("Graff Yang "); +MODULE_DESCRIPTION("Analog Devices AD2S1210 Resolver to Digital SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/resolver/ad2s1210.h b/kernel/drivers/staging/iio/resolver/ad2s1210.h new file mode 100644 index 000000000..c7158f6e6 --- /dev/null +++ b/kernel/drivers/staging/iio/resolver/ad2s1210.h @@ -0,0 +1,20 @@ +/* + * ad2s1210.h plaform data for the ADI Resolver to Digital Converters: + * AD2S1210 + * + * Copyright (c) 2010-2010 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. + */ +#ifndef _AD2S1210_H +#define _AD2S1210_H + +struct ad2s1210_platform_data { + unsigned sample; + unsigned a[2]; + unsigned res[2]; + bool gpioin; +}; +#endif /* _AD2S1210_H */ diff --git a/kernel/drivers/staging/iio/resolver/ad2s90.c b/kernel/drivers/staging/iio/resolver/ad2s90.c new file mode 100644 index 000000000..e24c58906 --- /dev/null +++ b/kernel/drivers/staging/iio/resolver/ad2s90.c @@ -0,0 +1,120 @@ +/* + * ad2s90.c simple support for the ADI Resolver to Digital Converters: AD2S90 + * + * Copyright (c) 2010-2010 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +struct ad2s90_state { + struct mutex lock; + struct spi_device *sdev; + u8 rx[2] ____cacheline_aligned; +}; + +static int ad2s90_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad2s90_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + ret = spi_read(st->sdev, st->rx, 2); + if (ret) + goto error_ret; + *val = (((u16)(st->rx[0])) << 4) | ((st->rx[1] & 0xF0) >> 4); + +error_ret: + mutex_unlock(&st->lock); + + return IIO_VAL_INT; +} + +static const struct iio_info ad2s90_info = { + .read_raw = &ad2s90_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec ad2s90_chan = { + .type = IIO_ANGL, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +}; + +static int ad2s90_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad2s90_state *st; + int ret = 0; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + mutex_init(&st->lock); + st->sdev = spi; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ad2s90_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &ad2s90_chan; + indio_dev->num_channels = 1; + indio_dev->name = spi_get_device_id(spi)->name; + + ret = iio_device_register(indio_dev); + if (ret) + return ret; + + /* need 600ns between CS and the first falling edge of SCLK */ + spi->max_speed_hz = 830000; + spi->mode = SPI_MODE_3; + spi_setup(spi); + + return 0; +} + +static int ad2s90_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id ad2s90_id[] = { + { "ad2s90" }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad2s90_id); + +static struct spi_driver ad2s90_driver = { + .driver = { + .name = "ad2s90", + .owner = THIS_MODULE, + }, + .probe = ad2s90_probe, + .remove = ad2s90_remove, + .id_table = ad2s90_id, +}; +module_spi_driver(ad2s90_driver); + +MODULE_AUTHOR("Graff Yang "); +MODULE_DESCRIPTION("Analog Devices AD2S90 Resolver to Digital SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/kernel/drivers/staging/iio/ring_hw.h b/kernel/drivers/staging/iio/ring_hw.h new file mode 100644 index 000000000..75bf47bfe --- /dev/null +++ b/kernel/drivers/staging/iio/ring_hw.h @@ -0,0 +1,27 @@ +/* + * ring_hw.h - common functionality for iio hardware ring buffers + * + * 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. + * + * Copyright (c) 2009 Jonathan Cameron + * + */ + +#ifndef _RING_HW_H_ +#define _RING_HW_H_ + +/** + * struct iio_hw_ring_buffer- hardware ring buffer + * @buf: generic ring buffer elements + * @private: device specific data + */ +struct iio_hw_buffer { + struct iio_buffer buf; + void *private; +}; + +#define iio_to_hw_buf(r) container_of(r, struct iio_hw_buffer, buf) + +#endif /* _RING_HW_H_ */ diff --git a/kernel/drivers/staging/iio/trigger/Kconfig b/kernel/drivers/staging/iio/trigger/Kconfig new file mode 100644 index 000000000..710a2f3e7 --- /dev/null +++ b/kernel/drivers/staging/iio/trigger/Kconfig @@ -0,0 +1,29 @@ + # +# Industrial I/O standalone triggers +# +comment "Triggers - standalone" + +if IIO_TRIGGER + +config IIO_PERIODIC_RTC_TRIGGER + tristate "Periodic RTC triggers" + depends on RTC_CLASS + help + Provides support for using periodic capable real time + clocks as IIO triggers. + + To compile this driver as a module, choose M here: the + module will be called iio-trig-periodic-rtc. + +config IIO_BFIN_TMR_TRIGGER + tristate "Blackfin TIMER trigger" + depends on BLACKFIN + select BFIN_GPTIMERS + help + Provides support for using a Blackfin timer as IIO triggers. + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called iio-trig-bfin-timer. + +endif # IIO_TRIGGER diff --git a/kernel/drivers/staging/iio/trigger/Makefile b/kernel/drivers/staging/iio/trigger/Makefile new file mode 100644 index 000000000..238481b78 --- /dev/null +++ b/kernel/drivers/staging/iio/trigger/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for triggers not associated with iio-devices +# + +obj-$(CONFIG_IIO_PERIODIC_RTC_TRIGGER) += iio-trig-periodic-rtc.o +obj-$(CONFIG_IIO_BFIN_TMR_TRIGGER) += iio-trig-bfin-timer.o diff --git a/kernel/drivers/staging/iio/trigger/iio-trig-bfin-timer.c b/kernel/drivers/staging/iio/trigger/iio-trig-bfin-timer.c new file mode 100644 index 000000000..3c1c8c6c4 --- /dev/null +++ b/kernel/drivers/staging/iio/trigger/iio-trig-bfin-timer.c @@ -0,0 +1,292 @@ +/* + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "iio-trig-bfin-timer.h" + +struct bfin_timer { + unsigned short id, bit; + unsigned long irqbit; + int irq; + int pin; +}; + +/* + * this covers all hardware timer configurations on + * all Blackfin derivatives out there today + */ + +static struct bfin_timer iio_bfin_timer_code[MAX_BLACKFIN_GPTIMERS] = { + {TIMER0_id, TIMER0bit, TIMER_STATUS_TIMIL0, IRQ_TIMER0, P_TMR0}, + {TIMER1_id, TIMER1bit, TIMER_STATUS_TIMIL1, IRQ_TIMER1, P_TMR1}, + {TIMER2_id, TIMER2bit, TIMER_STATUS_TIMIL2, IRQ_TIMER2, P_TMR2}, +#if (MAX_BLACKFIN_GPTIMERS > 3) + {TIMER3_id, TIMER3bit, TIMER_STATUS_TIMIL3, IRQ_TIMER3, P_TMR3}, + {TIMER4_id, TIMER4bit, TIMER_STATUS_TIMIL4, IRQ_TIMER4, P_TMR4}, + {TIMER5_id, TIMER5bit, TIMER_STATUS_TIMIL5, IRQ_TIMER5, P_TMR5}, + {TIMER6_id, TIMER6bit, TIMER_STATUS_TIMIL6, IRQ_TIMER6, P_TMR6}, + {TIMER7_id, TIMER7bit, TIMER_STATUS_TIMIL7, IRQ_TIMER7, P_TMR7}, +#endif +#if (MAX_BLACKFIN_GPTIMERS > 8) + {TIMER8_id, TIMER8bit, TIMER_STATUS_TIMIL8, IRQ_TIMER8, P_TMR8}, + {TIMER9_id, TIMER9bit, TIMER_STATUS_TIMIL9, IRQ_TIMER9, P_TMR9}, + {TIMER10_id, TIMER10bit, TIMER_STATUS_TIMIL10, IRQ_TIMER10, P_TMR10}, +#if (MAX_BLACKFIN_GPTIMERS > 11) + {TIMER11_id, TIMER11bit, TIMER_STATUS_TIMIL11, IRQ_TIMER11, P_TMR11}, +#endif +#endif +}; + +struct bfin_tmr_state { + struct iio_trigger *trig; + struct bfin_timer *t; + unsigned timer_num; + bool output_enable; + unsigned int duty; + int irq; +}; + +static int iio_bfin_tmr_set_state(struct iio_trigger *trig, bool state) +{ + struct bfin_tmr_state *st = iio_trigger_get_drvdata(trig); + + if (get_gptimer_period(st->t->id) == 0) + return -EINVAL; + + if (state) + enable_gptimers(st->t->bit); + else + disable_gptimers(st->t->bit); + + return 0; +} + +static ssize_t iio_bfin_tmr_frequency_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct bfin_tmr_state *st = iio_trigger_get_drvdata(trig); + unsigned int val; + bool enabled; + int ret; + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + if (val > 100000) + return -EINVAL; + + enabled = get_enabled_gptimers() & st->t->bit; + + if (enabled) + disable_gptimers(st->t->bit); + + if (val == 0) + return count; + + val = get_sclk() / val; + if (val <= 4 || val <= st->duty) + return -EINVAL; + + set_gptimer_period(st->t->id, val); + set_gptimer_pwidth(st->t->id, val - st->duty); + + if (enabled) + enable_gptimers(st->t->bit); + + return count; +} + +static ssize_t iio_bfin_tmr_frequency_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct bfin_tmr_state *st = iio_trigger_get_drvdata(trig); + unsigned int period = get_gptimer_period(st->t->id); + unsigned long val; + + if (period == 0) + val = 0; + else + val = get_sclk() / get_gptimer_period(st->t->id); + + return sprintf(buf, "%lu\n", val); +} + +static DEVICE_ATTR(frequency, S_IRUGO | S_IWUSR, iio_bfin_tmr_frequency_show, + iio_bfin_tmr_frequency_store); + +static struct attribute *iio_bfin_tmr_trigger_attrs[] = { + &dev_attr_frequency.attr, + NULL, +}; + +static const struct attribute_group iio_bfin_tmr_trigger_attr_group = { + .attrs = iio_bfin_tmr_trigger_attrs, +}; + +static const struct attribute_group *iio_bfin_tmr_trigger_attr_groups[] = { + &iio_bfin_tmr_trigger_attr_group, + NULL +}; + +static irqreturn_t iio_bfin_tmr_trigger_isr(int irq, void *devid) +{ + struct bfin_tmr_state *st = devid; + + clear_gptimer_intr(st->t->id); + iio_trigger_poll(st->trig); + + return IRQ_HANDLED; +} + +static int iio_bfin_tmr_get_number(int irq) +{ + int i; + + for (i = 0; i < MAX_BLACKFIN_GPTIMERS; i++) + if (iio_bfin_timer_code[i].irq == irq) + return i; + + return -ENODEV; +} + +static const struct iio_trigger_ops iio_bfin_tmr_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = iio_bfin_tmr_set_state, +}; + +static int iio_bfin_tmr_trigger_probe(struct platform_device *pdev) +{ + struct iio_bfin_timer_trigger_pdata *pdata = pdev->dev.platform_data; + struct bfin_tmr_state *st; + unsigned int config; + int ret; + + st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + st->irq = platform_get_irq(pdev, 0); + if (!st->irq) { + dev_err(&pdev->dev, "No IRQs specified"); + return -ENODEV; + } + + ret = iio_bfin_tmr_get_number(st->irq); + if (ret < 0) + return ret; + + st->timer_num = ret; + st->t = &iio_bfin_timer_code[st->timer_num]; + + st->trig = iio_trigger_alloc("bfintmr%d", st->timer_num); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &iio_bfin_tmr_trigger_ops; + st->trig->dev.groups = iio_bfin_tmr_trigger_attr_groups; + iio_trigger_set_drvdata(st->trig, st); + ret = iio_trigger_register(st->trig); + if (ret) + goto out; + + ret = request_irq(st->irq, iio_bfin_tmr_trigger_isr, + 0, st->trig->name, st); + if (ret) { + dev_err(&pdev->dev, + "request IRQ-%d failed", st->irq); + goto out1; + } + + config = PWM_OUT | PERIOD_CNT | IRQ_ENA; + + if (pdata && pdata->output_enable) { + unsigned long long val; + + st->output_enable = true; + + ret = peripheral_request(st->t->pin, st->trig->name); + if (ret) + goto out_free_irq; + + val = (unsigned long long)get_sclk() * pdata->duty_ns; + do_div(val, NSEC_PER_SEC); + st->duty = val; + + /** + * The interrupt will be generated at the end of the period, + * since we want the interrupt to be generated at end of the + * pulse we invert both polarity and duty cycle, so that the + * pulse will be generated directly before the interrupt. + */ + if (pdata->active_low) + config |= PULSE_HI; + } else { + st->duty = 1; + config |= OUT_DIS; + } + + set_gptimer_config(st->t->id, config); + + dev_info(&pdev->dev, "iio trigger Blackfin TMR%d, IRQ-%d", + st->timer_num, st->irq); + platform_set_drvdata(pdev, st); + + return 0; +out_free_irq: + free_irq(st->irq, st); +out1: + iio_trigger_unregister(st->trig); +out: + iio_trigger_put(st->trig); + return ret; +} + +static int iio_bfin_tmr_trigger_remove(struct platform_device *pdev) +{ + struct bfin_tmr_state *st = platform_get_drvdata(pdev); + + disable_gptimers(st->t->bit); + if (st->output_enable) + peripheral_free(st->t->pin); + free_irq(st->irq, st); + iio_trigger_unregister(st->trig); + iio_trigger_put(st->trig); + + return 0; +} + +static struct platform_driver iio_bfin_tmr_trigger_driver = { + .driver = { + .name = "iio_bfin_tmr_trigger", + }, + .probe = iio_bfin_tmr_trigger_probe, + .remove = iio_bfin_tmr_trigger_remove, +}; + +module_platform_driver(iio_bfin_tmr_trigger_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Blackfin system timer based trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iio-trig-bfin-timer"); diff --git a/kernel/drivers/staging/iio/trigger/iio-trig-bfin-timer.h b/kernel/drivers/staging/iio/trigger/iio-trig-bfin-timer.h new file mode 100644 index 000000000..c07321f8d --- /dev/null +++ b/kernel/drivers/staging/iio/trigger/iio-trig-bfin-timer.h @@ -0,0 +1,24 @@ +#ifndef __IIO_BFIN_TIMER_TRIGGER_H__ +#define __IIO_BFIN_TIMER_TRIGGER_H__ + +/** + * struct iio_bfin_timer_trigger_pdata - timer trigger platform data + * @output_enable: Enable external trigger pulse generation. + * @active_low: Whether the trigger pulse is active low. + * @duty_ns: Length of the trigger pulse in nanoseconds. + * + * This struct is used to configure the output pulse generation of the blackfin + * timer trigger. If output_enable is set to true an external trigger signal + * will generated on the pin corresponding to the timer. This is useful for + * converters which needs an external signal to start conversion. active_low and + * duty_ns are used to configure the type of the trigger pulse. If output_enable + * is set to false no external trigger pulse will be generated and active_low + * and duty_ns are ignored. + **/ +struct iio_bfin_timer_trigger_pdata { + bool output_enable; + bool active_low; + unsigned int duty_ns; +}; + +#endif diff --git a/kernel/drivers/staging/iio/trigger/iio-trig-periodic-rtc.c b/kernel/drivers/staging/iio/trigger/iio-trig-periodic-rtc.c new file mode 100644 index 000000000..0c1976dde --- /dev/null +++ b/kernel/drivers/staging/iio/trigger/iio-trig-periodic-rtc.c @@ -0,0 +1,215 @@ +/* The industrial I/O periodic RTC trigger driver + * + * 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. + * + * This is a heavily rewritten version of the periodic timer system in + * earlier version of industrialio. It supplies the same functionality + * but via a trigger rather than a specific periodic timer system. + */ + +#include +#include +#include +#include +#include +#include +#include + +static LIST_HEAD(iio_prtc_trigger_list); +static DEFINE_MUTEX(iio_prtc_trigger_list_lock); + +struct iio_prtc_trigger_info { + struct rtc_device *rtc; + unsigned int frequency; + struct rtc_task task; + bool state; +}; + +static int iio_trig_periodic_rtc_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_prtc_trigger_info *trig_info = iio_trigger_get_drvdata(trig); + int ret; + + if (trig_info->frequency == 0 && state) + return -EINVAL; + dev_dbg(&trig_info->rtc->dev, "trigger frequency is %u\n", + trig_info->frequency); + ret = rtc_irq_set_state(trig_info->rtc, &trig_info->task, state); + if (ret == 0) + trig_info->state = state; + + return ret; +} + +static ssize_t iio_trig_periodic_read_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_prtc_trigger_info *trig_info = iio_trigger_get_drvdata(trig); + + return sprintf(buf, "%u\n", trig_info->frequency); +} + +static ssize_t iio_trig_periodic_write_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_prtc_trigger_info *trig_info = iio_trigger_get_drvdata(trig); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 10, &val); + if (ret) + goto error_ret; + + if (val > 0) { + ret = rtc_irq_set_freq(trig_info->rtc, &trig_info->task, val); + if (ret == 0 && trig_info->state && trig_info->frequency == 0) + ret = rtc_irq_set_state(trig_info->rtc, + &trig_info->task, 1); + } else + ret = rtc_irq_set_state(trig_info->rtc, &trig_info->task, 0); + if (ret) + goto error_ret; + + trig_info->frequency = val; + + return len; + +error_ret: + return ret; +} + +static DEVICE_ATTR(frequency, S_IRUGO | S_IWUSR, + iio_trig_periodic_read_freq, + iio_trig_periodic_write_freq); + +static struct attribute *iio_trig_prtc_attrs[] = { + &dev_attr_frequency.attr, + NULL, +}; + +static const struct attribute_group iio_trig_prtc_attr_group = { + .attrs = iio_trig_prtc_attrs, +}; + +static const struct attribute_group *iio_trig_prtc_attr_groups[] = { + &iio_trig_prtc_attr_group, + NULL +}; + +static void iio_prtc_trigger_poll(void *private_data) +{ + iio_trigger_poll(private_data); +} + +static const struct iio_trigger_ops iio_prtc_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &iio_trig_periodic_rtc_set_state, +}; + +static int iio_trig_periodic_rtc_probe(struct platform_device *dev) +{ + char **pdata = dev->dev.platform_data; + struct iio_prtc_trigger_info *trig_info; + struct iio_trigger *trig, *trig2; + + int i, ret; + + for (i = 0;; i++) { + if (!pdata[i]) + break; + trig = iio_trigger_alloc("periodic%s", pdata[i]); + if (!trig) { + ret = -ENOMEM; + goto error_free_completed_registrations; + } + list_add(&trig->alloc_list, &iio_prtc_trigger_list); + + trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); + if (!trig_info) { + ret = -ENOMEM; + goto error_put_trigger_and_remove_from_list; + } + iio_trigger_set_drvdata(trig, trig_info); + trig->ops = &iio_prtc_trigger_ops; + /* RTC access */ + trig_info->rtc = rtc_class_open(pdata[i]); + if (!trig_info->rtc) { + ret = -EINVAL; + goto error_free_trig_info; + } + trig_info->task.func = iio_prtc_trigger_poll; + trig_info->task.private_data = trig; + ret = rtc_irq_register(trig_info->rtc, &trig_info->task); + if (ret) + goto error_close_rtc; + trig->dev.groups = iio_trig_prtc_attr_groups; + ret = iio_trigger_register(trig); + if (ret) + goto error_unregister_rtc_irq; + } + return 0; +error_unregister_rtc_irq: + rtc_irq_unregister(trig_info->rtc, &trig_info->task); +error_close_rtc: + rtc_class_close(trig_info->rtc); +error_free_trig_info: + kfree(trig_info); +error_put_trigger_and_remove_from_list: + list_del(&trig->alloc_list); + iio_trigger_put(trig); +error_free_completed_registrations: + list_for_each_entry_safe(trig, + trig2, + &iio_prtc_trigger_list, + alloc_list) { + trig_info = iio_trigger_get_drvdata(trig); + rtc_irq_unregister(trig_info->rtc, &trig_info->task); + rtc_class_close(trig_info->rtc); + kfree(trig_info); + iio_trigger_unregister(trig); + } + return ret; +} + +static int iio_trig_periodic_rtc_remove(struct platform_device *dev) +{ + struct iio_trigger *trig, *trig2; + struct iio_prtc_trigger_info *trig_info; + + mutex_lock(&iio_prtc_trigger_list_lock); + list_for_each_entry_safe(trig, + trig2, + &iio_prtc_trigger_list, + alloc_list) { + trig_info = iio_trigger_get_drvdata(trig); + rtc_irq_unregister(trig_info->rtc, &trig_info->task); + rtc_class_close(trig_info->rtc); + kfree(trig_info); + iio_trigger_unregister(trig); + } + mutex_unlock(&iio_prtc_trigger_list_lock); + return 0; +} + +static struct platform_driver iio_trig_periodic_rtc_driver = { + .probe = iio_trig_periodic_rtc_probe, + .remove = iio_trig_periodic_rtc_remove, + .driver = { + .name = "iio_prtc_trigger", + }, +}; + +module_platform_driver(iio_trig_periodic_rtc_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Periodic realtime clock trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); -- cgit 1.2.3-korg