Staging patches for 4.5-rc1
Here is the big staging driver pull request for 4.5-rc1. Lots of cleanups and fixes here, not as many as some releases, but 800+ isn't that bad. Full details in the shortlog. All of these have been in linux-next for a while. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iEYEABECAAYFAlaV0VgACgkQMUfUDdst+ykvAwCeIN/Ot3OuWD/d1RQrcUzh028H rlUAn3YOHCFlQhAnUI+9KMNjH0CwAJKp =Oij1 -----END PGP SIGNATURE----- Merge tag 'staging-4.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging Pull staging driver updates from Greg KH: "Here is the big staging driver pull request for 4.5-rc1. Lots of cleanups and fixes here, not as many as some releases, but 800+ isn't that bad. Full details in the shortlog. All of these have been in linux-next for a while" * tag 'staging-4.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (843 commits) Revert "arm64: dts: Add dts files to enable ION on Hi6220 SoC." staging: gdm724x: constify tty_port_operations structs staging: gdm72xx: add userspace data struct staging: gdm72xx: Replace timeval with ktime_t iio: adc: ina2xx: Fix incorrect report of data endianness to userspace. iio: light: us5182d: Refactor read_raw function iio: light: us5182d: Add interrupt support and events iio: light: us5182d: Fix enable status inconcistency iio: Make IIO value formating function globally available. staging: emxx_udc: use list_first_entry_or_null() staging/emxx_udc: fix 64-bit warnings STAGING: COMEDI: Using kernel types in plx9080.h STAGING: COMEDI: Added spaces around binary operators in plx9080.h STAGING: COMEDI: Fixed format of comments in plx9080.h staging: comedi: comedilib.h: Coding style warning fix for block comments staging: comedi: s526: add macros for counter control reg values staging: comedi: s526: replace counter mode bitfield struct staging: comedi: check for more errors for zero-length write staging: comedi: simplify returned errors for comedi_write() staging: comedi: return error on "write" if no command set up ...
This commit is contained in:
Коммит
39272dde8f
|
@ -0,0 +1,21 @@
|
|||
What: /config/iio
|
||||
Date: October 2015
|
||||
KernelVersion: 4.4
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
This represents Industrial IO configuration entry point
|
||||
directory. It contains sub-groups corresponding to IIO
|
||||
objects.
|
||||
|
||||
What: /config/iio/triggers
|
||||
Date: October 2015
|
||||
KernelVersion: 4.4
|
||||
Description:
|
||||
Industrial IO software triggers directory.
|
||||
|
||||
What: /config/iio/triggers/hrtimers
|
||||
Date: October 2015
|
||||
KernelVersion: 4.4
|
||||
Description:
|
||||
High resolution timers directory. Creating a directory here
|
||||
will result in creating a hrtimer trigger in the IIO subsystem.
|
|
@ -0,0 +1,24 @@
|
|||
What: /sys/bus/iio/devices/iio:deviceX/in_allow_async_readout
|
||||
Date: December 2015
|
||||
KernelVersion: 4.4
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
By default (value '0'), the capture thread checks for the Conversion
|
||||
Ready Flag to being set prior to committing a new value to the sample
|
||||
buffer. This synchronizes the in-chip conversion rate with the
|
||||
in-driver readout rate at the cost of an additional register read.
|
||||
|
||||
Writing '1' will remove the polling for the Conversion Ready Flags to
|
||||
save the additional i2c transaction, which will improve the bandwidth
|
||||
available for reading data. However, samples can be occasionally skipped
|
||||
or repeated, depending on the beat between the capture and conversion
|
||||
rates.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_shunt_resistor
|
||||
Date: December 2015
|
||||
KernelVersion: 4.4
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
The value of the shunt resistor may be known only at runtime fom an
|
||||
eeprom content read by a client application. This attribute allows to
|
||||
set its value in ohms.
|
|
@ -20,6 +20,7 @@ adi,adt7476 +/-1C TDM Extended Temp Range I.C
|
|||
adi,adt7490 +/-1C TDM Extended Temp Range I.C
|
||||
adi,adxl345 Three-Axis Digital Accelerometer
|
||||
adi,adxl346 Three-Axis Digital Accelerometer (backward-compatibility value "adi,adxl345" must be listed too)
|
||||
ams,iaq-core AMS iAQ-Core VOC Sensor
|
||||
at,24c08 i2c serial eeprom (24cxx)
|
||||
atmel,24c00 i2c serial eeprom (24cxx)
|
||||
atmel,24c01 i2c serial eeprom (24cxx)
|
||||
|
|
|
@ -7,13 +7,18 @@ Required properties:
|
|||
* "fsl,mma8453"
|
||||
* "fsl,mma8652"
|
||||
* "fsl,mma8653"
|
||||
|
||||
- reg: the I2C address of the chip
|
||||
|
||||
Optional properties:
|
||||
|
||||
- interrupt-parent: should be the phandle for the interrupt controller
|
||||
|
||||
- interrupts: interrupt mapping for GPIO IRQ
|
||||
|
||||
- interrupt-names: should contain "INT1" and/or "INT2", the accelerometer's
|
||||
interrupt line in use.
|
||||
|
||||
Example:
|
||||
|
||||
mma8453fc@1d {
|
||||
|
@ -21,4 +26,5 @@ Example:
|
|||
reg = <0x1d>;
|
||||
interrupt-parent = <&gpio1>;
|
||||
interrupts = <5 0>;
|
||||
interrupt-names = "INT2";
|
||||
};
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
Freescale imx7d ADC bindings
|
||||
|
||||
The devicetree bindings are for the ADC driver written for
|
||||
imx7d SoC.
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "fsl,imx7d-adc"
|
||||
- reg: Offset and length of the register set for the ADC device
|
||||
- interrupts: The interrupt number for the ADC device
|
||||
- clocks: The root clock of the ADC controller
|
||||
- clock-names: Must contain "adc", matching entry in the clocks property
|
||||
- vref-supply: The regulator supply ADC reference voltage
|
||||
|
||||
Example:
|
||||
adc1: adc@30610000 {
|
||||
compatible = "fsl,imx7d-adc";
|
||||
reg = <0x30610000 0x10000>;
|
||||
interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&clks IMX7D_ADC_ROOT_CLK>;
|
||||
clock-names = "adc";
|
||||
vref-supply = <®_vcc_3v3_mcu>;
|
||||
};
|
|
@ -10,16 +10,28 @@ must be specified.
|
|||
Required properties:
|
||||
- compatible: Must be one of the following, depending on the
|
||||
model:
|
||||
"mcp3001"
|
||||
"mcp3002"
|
||||
"mcp3004"
|
||||
"mcp3008"
|
||||
"mcp3201"
|
||||
"mcp3202"
|
||||
"mcp3204"
|
||||
"mcp3208"
|
||||
"mcp3301"
|
||||
"mcp3001" (DEPRECATED)
|
||||
"mcp3002" (DEPRECATED)
|
||||
"mcp3004" (DEPRECATED)
|
||||
"mcp3008" (DEPRECATED)
|
||||
"mcp3201" (DEPRECATED)
|
||||
"mcp3202" (DEPRECATED)
|
||||
"mcp3204" (DEPRECATED)
|
||||
"mcp3208" (DEPRECATED)
|
||||
"mcp3301" (DEPRECATED)
|
||||
|
||||
"microchip,mcp3001"
|
||||
"microchip,mcp3002"
|
||||
"microchip,mcp3004"
|
||||
"microchip,mcp3008"
|
||||
"microchip,mcp3201"
|
||||
"microchip,mcp3202"
|
||||
"microchip,mcp3204"
|
||||
"microchip,mcp3208"
|
||||
"microchip,mcp3301"
|
||||
|
||||
NOTE: The use of the compatibles with no vendor prefix
|
||||
is deprecated and only listed because old DT use them.
|
||||
|
||||
Examples:
|
||||
spi_controller {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
* Microchip mcp3422/3/4/6/7/8 chip family (ADC)
|
||||
* Microchip mcp3421/2/3/4/6/7/8 chip family (ADC)
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be
|
||||
"microchip,mcp3421" or
|
||||
"microchip,mcp3422" or
|
||||
"microchip,mcp3423" or
|
||||
"microchip,mcp3424" or
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
* Palmas general purpose ADC IP block devicetree bindings
|
||||
|
||||
Channels list:
|
||||
0 battery type
|
||||
1 battery temp NTC (optional current source)
|
||||
2 GP
|
||||
3 temp (with ext. diode, optional current source)
|
||||
4 GP
|
||||
5 GP
|
||||
6 VBAT_SENSE
|
||||
7 VCC_SENSE
|
||||
8 Backup Battery voltage
|
||||
9 external charger (VCHG)
|
||||
10 VBUS
|
||||
11 DC-DC current probe (how does this work?)
|
||||
12 internal die temp
|
||||
13 internal die temp
|
||||
14 USB ID pin voltage
|
||||
15 test network
|
||||
|
||||
Required properties:
|
||||
- compatible : Must be "ti,palmas-gpadc".
|
||||
- #io-channel-cells: Should be set to <1>.
|
||||
|
||||
Optional sub-nodes:
|
||||
ti,channel0-current-microamp: Channel 0 current in uA.
|
||||
Values are rounded to derive 0uA, 5uA, 15uA, 20uA.
|
||||
ti,channel3-current-microamp: Channel 3 current in uA.
|
||||
Values are rounded to derive 0uA, 10uA, 400uA, 800uA.
|
||||
ti,enable-extended-delay: Enable extended delay.
|
||||
|
||||
Example:
|
||||
|
||||
pmic {
|
||||
compatible = "ti,twl6035-pmic", "ti,palmas-pmic";
|
||||
...
|
||||
gpadc {
|
||||
compatible = "ti,palmas-gpadc";
|
||||
interrupts = <18 0
|
||||
16 0
|
||||
17 0>;
|
||||
#io-channel-cells = <1>;
|
||||
ti,channel0-current-microamp = <5>;
|
||||
ti,channel3-current-microamp = <10>;
|
||||
};
|
||||
};
|
||||
...
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
* Texas Instruments' ADC128S052 and ADC122S021 ADC chip
|
||||
* Texas Instruments' ADC128S052, ADC122S021 and ADC124S021 ADC chip
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "ti,adc128s052" or "ti,adc122s021"
|
||||
- compatible: Should be "ti,adc128s052", "ti,adc122s021" or "ti,adc124s021"
|
||||
- reg: spi chip select number for the device
|
||||
- vref-supply: The regulator supply for ADC reference voltage
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
* Texas Instruments' ADS8684 and ADS8688 ADC chip
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "ti,ads8684" or "ti,ads8688"
|
||||
- reg: spi chip select number for the device
|
||||
|
||||
Recommended properties:
|
||||
- spi-max-frequency: Definition as per
|
||||
Documentation/devicetree/bindings/spi/spi-bus.txt
|
||||
|
||||
Optional properties:
|
||||
- vref-supply: The regulator supply for ADC reference voltage
|
||||
|
||||
Example:
|
||||
adc@0 {
|
||||
compatible = "ti,ads8688";
|
||||
reg = <0>;
|
||||
vref-supply = <&vdd_supply>;
|
||||
spi-max-frequency = <1000000>;
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
Maxim MAX30100 heart rate and pulse oximeter sensor
|
||||
|
||||
* https://datasheets.maximintegrated.com/en/ds/MAX30100.pdf
|
||||
|
||||
Required properties:
|
||||
- compatible: must be "maxim,max30100"
|
||||
- reg: the I2C address of the sensor
|
||||
- interrupt-parent: should be the phandle for the interrupt controller
|
||||
- interrupts: the sole interrupt generated by the device
|
||||
|
||||
Refer to interrupt-controller/interrupts.txt for generic
|
||||
interrupt client node bindings.
|
||||
|
||||
Example:
|
||||
|
||||
max30100@057 {
|
||||
compatible = "maxim,max30100";
|
||||
reg = <57>;
|
||||
interrupt-parent = <&gpio1>;
|
||||
interrupts = <16 2>;
|
||||
};
|
|
@ -7,13 +7,24 @@ Required properties:
|
|||
Optional properties:
|
||||
- upisemi,glass-coef: glass attenuation factor - compensation factor of
|
||||
resolution 1000 for material transmittance.
|
||||
|
||||
- upisemi,dark-ths: array of 8 elements containing 16-bit thresholds (adc
|
||||
counts) corresponding to every scale.
|
||||
|
||||
- upisemi,upper-dark-gain: 8-bit dark gain compensation factor(4 int and 4
|
||||
fractional bits - Q4.4) applied when light > threshold
|
||||
|
||||
- upisemi,lower-dark-gain: 8-bit dark gain compensation factor(4 int and 4
|
||||
fractional bits - Q4.4) applied when light < threshold
|
||||
|
||||
- upisemi,continuous: This chip has two power modes: one-shot (chip takes one
|
||||
measurement and then shuts itself down) and continuous (
|
||||
chip takes continuous measurements). The one-shot mode is
|
||||
more power-friendly but the continuous mode may be more
|
||||
reliable. If this property is specified the continuous
|
||||
mode will be used instead of the default one-shot one for
|
||||
raw reads.
|
||||
|
||||
If the optional properties are not specified these factors will default to the
|
||||
values in the below example.
|
||||
The glass-coef defaults to no compensation for the covering material.
|
||||
|
|
|
@ -36,6 +36,7 @@ Accelerometers:
|
|||
- st,lsm303dlm-accel
|
||||
- st,lsm330-accel
|
||||
- st,lsm303agr-accel
|
||||
- st,lis2dh12-accel
|
||||
|
||||
Gyroscopes:
|
||||
- st,l3g4200d-gyro
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
Hi6220 SoC ION
|
||||
===================================================================
|
||||
Required properties:
|
||||
- compatible : "hisilicon,hi6220-ion"
|
||||
- list of the ION heaps
|
||||
- heap name : maybe heap_sys_user@0
|
||||
- heap id : id should be unique in the system.
|
||||
- heap base : base ddr address of the heap,0 means that
|
||||
it is dynamic.
|
||||
- heap size : memory size and 0 means it is dynamic.
|
||||
- heap type : the heap type of the heap, please also
|
||||
see the define in ion.h(drivers/staging/android/uapi/ion.h)
|
||||
-------------------------------------------------------------------
|
||||
Example:
|
||||
hi6220-ion {
|
||||
compatible = "hisilicon,hi6220-ion";
|
||||
heap_sys_user@0 {
|
||||
heap-name = "sys_user";
|
||||
heap-id = <0x0>;
|
||||
heap-base = <0x0>;
|
||||
heap-size = <0x0>;
|
||||
heap-type = "ion_system";
|
||||
};
|
||||
heap_sys_contig@0 {
|
||||
heap-name = "sys_contig";
|
||||
heap-id = <0x1>;
|
||||
heap-base = <0x0>;
|
||||
heap-size = <0x0>;
|
||||
heap-type = "ion_system_contig";
|
||||
};
|
||||
};
|
|
@ -0,0 +1,93 @@
|
|||
Industrial IIO configfs support
|
||||
|
||||
1. Overview
|
||||
|
||||
Configfs is a filesystem-based manager of kernel objects. IIO uses some
|
||||
objects that could be easily configured using configfs (e.g.: devices,
|
||||
triggers).
|
||||
|
||||
See Documentation/filesystems/configfs/configfs.txt for more information
|
||||
about how configfs works.
|
||||
|
||||
2. Usage
|
||||
|
||||
In order to use configfs support in IIO we need to select it at compile
|
||||
time via CONFIG_IIO_CONFIGFS config option.
|
||||
|
||||
Then, mount the configfs filesystem (usually under /config directory):
|
||||
|
||||
$ mkdir /config
|
||||
$ mount -t configfs none /config
|
||||
|
||||
At this point, all default IIO groups will be created and can be accessed
|
||||
under /config/iio. Next chapters will describe available IIO configuration
|
||||
objects.
|
||||
|
||||
3. Software triggers
|
||||
|
||||
One of the IIO default configfs groups is the "triggers" group. It is
|
||||
automagically accessible when the configfs is mounted and can be found
|
||||
under /config/iio/triggers.
|
||||
|
||||
IIO software triggers implementation offers support for creating multiple
|
||||
trigger types. A new trigger type is usually implemented as a separate
|
||||
kernel module following the interface in include/linux/iio/sw_trigger.h:
|
||||
|
||||
/*
|
||||
* drivers/iio/trigger/iio-trig-sample.c
|
||||
* sample kernel module implementing a new trigger type
|
||||
*/
|
||||
#include <linux/iio/sw_trigger.h>
|
||||
|
||||
|
||||
static struct iio_sw_trigger *iio_trig_sample_probe(const char *name)
|
||||
{
|
||||
/*
|
||||
* This allocates and registers an IIO trigger plus other
|
||||
* trigger type specific initialization.
|
||||
*/
|
||||
}
|
||||
|
||||
static int iio_trig_hrtimer_remove(struct iio_sw_trigger *swt)
|
||||
{
|
||||
/*
|
||||
* This undoes the actions in iio_trig_sample_probe
|
||||
*/
|
||||
}
|
||||
|
||||
static const struct iio_sw_trigger_ops iio_trig_sample_ops = {
|
||||
.probe = iio_trig_sample_probe,
|
||||
.remove = iio_trig_sample_remove,
|
||||
};
|
||||
|
||||
static struct iio_sw_trigger_type iio_trig_sample = {
|
||||
.name = "trig-sample",
|
||||
.owner = THIS_MODULE,
|
||||
.ops = &iio_trig_sample_ops,
|
||||
};
|
||||
|
||||
module_iio_sw_trigger_driver(iio_trig_sample);
|
||||
|
||||
Each trigger type has its own directory under /config/iio/triggers. Loading
|
||||
iio-trig-sample module will create 'trig-sample' trigger type directory
|
||||
/config/iio/triggers/trig-sample.
|
||||
|
||||
We support the following interrupt sources (trigger types):
|
||||
* hrtimer, uses high resolution timers as interrupt source
|
||||
|
||||
3.1 Hrtimer triggers creation and destruction
|
||||
|
||||
Loading iio-trig-hrtimer module will register hrtimer trigger types allowing
|
||||
users to create hrtimer triggers under /config/iio/triggers/hrtimer.
|
||||
|
||||
e.g:
|
||||
|
||||
$ mkdir /config/triggers/hrtimer/instance1
|
||||
$ rmdir /config/triggers/hrtimer/instance1
|
||||
|
||||
Each trigger can have one or more attributes specific to the trigger type.
|
||||
|
||||
3.2 "hrtimer" trigger types attributes
|
||||
|
||||
"hrtimer" trigger type doesn't have any configurable attribute from /config dir.
|
||||
It does introduce the sampling_frequency attribute to trigger directory.
|
|
@ -22,6 +22,14 @@ if IIO_BUFFER
|
|||
source "drivers/iio/buffer/Kconfig"
|
||||
endif # IIO_BUFFER
|
||||
|
||||
config IIO_CONFIGFS
|
||||
tristate "Enable IIO configuration via configfs"
|
||||
select CONFIGFS_FS
|
||||
help
|
||||
This allows configuring various IIO bits through configfs
|
||||
(e.g. software triggers). For more info see
|
||||
Documentation/iio/iio_configfs.txt.
|
||||
|
||||
config IIO_TRIGGER
|
||||
bool "Enable triggered sampling support"
|
||||
help
|
||||
|
@ -38,6 +46,14 @@ config IIO_CONSUMERS_PER_TRIGGER
|
|||
This value controls the maximum number of consumers that a
|
||||
given trigger may handle. Default is 2.
|
||||
|
||||
config IIO_SW_TRIGGER
|
||||
tristate "Enable software triggers support"
|
||||
select IIO_CONFIGFS
|
||||
help
|
||||
Provides IIO core support for software triggers. A software
|
||||
trigger can be created via configfs or directly by a driver
|
||||
using the API provided.
|
||||
|
||||
config IIO_TRIGGERED_EVENT
|
||||
tristate
|
||||
select IIO_TRIGGER
|
||||
|
@ -50,8 +66,10 @@ source "drivers/iio/amplifiers/Kconfig"
|
|||
source "drivers/iio/chemical/Kconfig"
|
||||
source "drivers/iio/common/Kconfig"
|
||||
source "drivers/iio/dac/Kconfig"
|
||||
source "drivers/iio/dummy/Kconfig"
|
||||
source "drivers/iio/frequency/Kconfig"
|
||||
source "drivers/iio/gyro/Kconfig"
|
||||
source "drivers/iio/health/Kconfig"
|
||||
source "drivers/iio/humidity/Kconfig"
|
||||
source "drivers/iio/imu/Kconfig"
|
||||
source "drivers/iio/light/Kconfig"
|
||||
|
|
|
@ -7,6 +7,8 @@ industrialio-y := industrialio-core.o industrialio-event.o inkern.o
|
|||
industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o
|
||||
industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o
|
||||
|
||||
obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o
|
||||
obj-$(CONFIG_IIO_SW_TRIGGER) += industrialio-sw-trigger.o
|
||||
obj-$(CONFIG_IIO_TRIGGERED_EVENT) += industrialio-triggered-event.o
|
||||
|
||||
obj-y += accel/
|
||||
|
@ -16,8 +18,10 @@ obj-y += buffer/
|
|||
obj-y += chemical/
|
||||
obj-y += common/
|
||||
obj-y += dac/
|
||||
obj-y += dummy/
|
||||
obj-y += gyro/
|
||||
obj-y += frequency/
|
||||
obj-y += health/
|
||||
obj-y += humidity/
|
||||
obj-y += imu/
|
||||
obj-y += light/
|
||||
|
|
|
@ -64,7 +64,7 @@ config IIO_ST_ACCEL_3AXIS
|
|||
help
|
||||
Say yes here to build support for STMicroelectronics accelerometers:
|
||||
LSM303DLH, LSM303DLHC, LIS3DH, LSM330D, LSM330DL, LSM330DLC,
|
||||
LIS331DLH, LSM303DL, LSM303DLM, LSM330.
|
||||
LIS331DLH, LSM303DL, LSM303DLM, LSM330, LIS2DH12.
|
||||
|
||||
This driver can also be built as a module. If so, these modules
|
||||
will be created:
|
||||
|
@ -107,6 +107,35 @@ config KXCJK1013
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called kxcjk-1013.
|
||||
|
||||
config MMA7455
|
||||
tristate
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
|
||||
config MMA7455_I2C
|
||||
tristate "Freescale MMA7455L/MMA7456L Accelerometer I2C Driver"
|
||||
depends on I2C
|
||||
select MMA7455
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say yes here to build support for the Freescale MMA7455L and
|
||||
MMA7456L 3-axis accelerometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called mma7455_i2c.
|
||||
|
||||
config MMA7455_SPI
|
||||
tristate "Freescale MMA7455L/MMA7456L Accelerometer SPI Driver"
|
||||
depends on SPI_MASTER
|
||||
select MMA7455
|
||||
select REGMAP_SPI
|
||||
help
|
||||
Say yes here to build support for the Freescale MMA7455L and
|
||||
MMA7456L 3-axis accelerometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called mma7455_spi.
|
||||
|
||||
config MMA8452
|
||||
tristate "Freescale MMA8452Q and similar Accelerometers Driver"
|
||||
depends on I2C
|
||||
|
@ -158,6 +187,17 @@ config MXC4005
|
|||
To compile this driver as a module, choose M. The module will be
|
||||
called mxc4005.
|
||||
|
||||
config MXC6255
|
||||
tristate "Memsic MXC6255 Orientation Sensing Accelerometer Driver"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say yes here to build support for the Memsic MXC6255 Orientation
|
||||
Sensing Accelerometer Driver.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called mxc6255.
|
||||
|
||||
config STK8312
|
||||
tristate "Sensortek STK8312 3-Axis Accelerometer Driver"
|
||||
depends on I2C
|
||||
|
|
|
@ -10,6 +10,11 @@ obj-$(CONFIG_BMC150_ACCEL_SPI) += bmc150-accel-spi.o
|
|||
obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o
|
||||
obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o
|
||||
obj-$(CONFIG_KXSD9) += kxsd9.o
|
||||
|
||||
obj-$(CONFIG_MMA7455) += mma7455_core.o
|
||||
obj-$(CONFIG_MMA7455_I2C) += mma7455_i2c.o
|
||||
obj-$(CONFIG_MMA7455_SPI) += mma7455_spi.o
|
||||
|
||||
obj-$(CONFIG_MMA8452) += mma8452.o
|
||||
|
||||
obj-$(CONFIG_MMA9551_CORE) += mma9551_core.o
|
||||
|
@ -17,6 +22,7 @@ obj-$(CONFIG_MMA9551) += mma9551.o
|
|||
obj-$(CONFIG_MMA9553) += mma9553.o
|
||||
|
||||
obj-$(CONFIG_MXC4005) += mxc4005.o
|
||||
obj-$(CONFIG_MXC6255) += mxc6255.o
|
||||
|
||||
obj-$(CONFIG_STK8312) += stk8312.o
|
||||
obj-$(CONFIG_STK8BA50) += stk8ba50.o
|
||||
|
|
|
@ -1623,24 +1623,22 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq,
|
|||
}
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(dev);
|
||||
if (ret)
|
||||
goto err_trigger_unregister;
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
pm_runtime_set_autosuspend_delay(dev, BMC150_AUTO_SUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Unable to register iio device\n");
|
||||
goto err_trigger_unregister;
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(dev);
|
||||
if (ret)
|
||||
goto err_iio_unregister;
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
pm_runtime_set_autosuspend_delay(dev, BMC150_AUTO_SUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_iio_unregister:
|
||||
iio_device_unregister(indio_dev);
|
||||
err_trigger_unregister:
|
||||
bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1);
|
||||
err_buffer_cleanup:
|
||||
|
@ -1655,12 +1653,12 @@ int bmc150_accel_core_remove(struct device *dev)
|
|||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct bmc150_accel_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
pm_runtime_disable(data->dev);
|
||||
pm_runtime_set_suspended(data->dev);
|
||||
pm_runtime_put_noidle(data->dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1);
|
||||
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
|
|
@ -1264,25 +1264,23 @@ static int kxcjk1013_probe(struct i2c_client *client,
|
|||
goto err_trigger_unregister;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "unable to register iio device\n");
|
||||
goto err_buffer_cleanup;
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret)
|
||||
goto err_iio_unregister;
|
||||
goto err_buffer_cleanup;
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev,
|
||||
KXCJK1013_SLEEP_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "unable to register iio device\n");
|
||||
goto err_buffer_cleanup;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_iio_unregister:
|
||||
iio_device_unregister(indio_dev);
|
||||
err_buffer_cleanup:
|
||||
if (data->dready_trig)
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
@ -1302,12 +1300,12 @@ static int kxcjk1013_remove(struct i2c_client *client)
|
|||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct kxcjk1013_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
pm_runtime_disable(&client->dev);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
pm_runtime_put_noidle(&client->dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
if (data->dready_trig) {
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
iio_trigger_unregister(data->dready_trig);
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* IIO accel driver for Freescale MMA7455L 3-axis 10-bit accelerometer
|
||||
* Copyright 2015 Joachim Eastwood <manabian@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __MMA7455_H
|
||||
#define __MMA7455_H
|
||||
|
||||
extern const struct regmap_config mma7455_core_regmap;
|
||||
|
||||
int mma7455_core_probe(struct device *dev, struct regmap *regmap,
|
||||
const char *name);
|
||||
int mma7455_core_remove(struct device *dev);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* IIO accel core driver for Freescale MMA7455L 3-axis 10-bit accelerometer
|
||||
* Copyright 2015 Joachim Eastwood <manabian@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* UNSUPPORTED hardware features:
|
||||
* - 8-bit mode with different scales
|
||||
* - INT1/INT2 interrupts
|
||||
* - Offset calibration
|
||||
* - Events
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "mma7455.h"
|
||||
|
||||
#define MMA7455_REG_XOUTL 0x00
|
||||
#define MMA7455_REG_XOUTH 0x01
|
||||
#define MMA7455_REG_YOUTL 0x02
|
||||
#define MMA7455_REG_YOUTH 0x03
|
||||
#define MMA7455_REG_ZOUTL 0x04
|
||||
#define MMA7455_REG_ZOUTH 0x05
|
||||
#define MMA7455_REG_STATUS 0x09
|
||||
#define MMA7455_STATUS_DRDY BIT(0)
|
||||
#define MMA7455_REG_WHOAMI 0x0f
|
||||
#define MMA7455_WHOAMI_ID 0x55
|
||||
#define MMA7455_REG_MCTL 0x16
|
||||
#define MMA7455_MCTL_MODE_STANDBY 0x00
|
||||
#define MMA7455_MCTL_MODE_MEASURE 0x01
|
||||
#define MMA7455_REG_CTL1 0x18
|
||||
#define MMA7455_CTL1_DFBW_MASK BIT(7)
|
||||
#define MMA7455_CTL1_DFBW_125HZ BIT(7)
|
||||
#define MMA7455_CTL1_DFBW_62_5HZ 0
|
||||
#define MMA7455_REG_TW 0x1e
|
||||
|
||||
/*
|
||||
* When MMA7455 is used in 10-bit it has a fullscale of -8g
|
||||
* corresponding to raw value -512. The userspace interface
|
||||
* uses m/s^2 and we declare micro units.
|
||||
* So scale factor is given by:
|
||||
* g * 8 * 1e6 / 512 = 153228.90625, with g = 9.80665
|
||||
*/
|
||||
#define MMA7455_10BIT_SCALE 153229
|
||||
|
||||
struct mma7455_data {
|
||||
struct regmap *regmap;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static int mma7455_drdy(struct mma7455_data *mma7455)
|
||||
{
|
||||
unsigned int reg;
|
||||
int tries = 3;
|
||||
int ret;
|
||||
|
||||
while (tries-- > 0) {
|
||||
ret = regmap_read(mma7455->regmap, MMA7455_REG_STATUS, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (reg & MMA7455_STATUS_DRDY)
|
||||
return 0;
|
||||
|
||||
msleep(20);
|
||||
}
|
||||
|
||||
dev_warn(mma7455->dev, "data not ready\n");
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static irqreturn_t mma7455_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct mma7455_data *mma7455 = iio_priv(indio_dev);
|
||||
u8 buf[16]; /* 3 x 16-bit channels + padding + ts */
|
||||
int ret;
|
||||
|
||||
ret = mma7455_drdy(mma7455);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
ret = regmap_bulk_read(mma7455->regmap, MMA7455_REG_XOUTL, buf,
|
||||
sizeof(__le16) * 3);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns());
|
||||
|
||||
done:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int mma7455_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct mma7455_data *mma7455 = iio_priv(indio_dev);
|
||||
unsigned int reg;
|
||||
__le16 data;
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
|
||||
ret = mma7455_drdy(mma7455);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_bulk_read(mma7455->regmap, chan->address, &data,
|
||||
sizeof(data));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = sign_extend32(le16_to_cpu(data), 9);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
*val2 = MMA7455_10BIT_SCALE;
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret = regmap_read(mma7455->regmap, MMA7455_REG_CTL1, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (reg & MMA7455_CTL1_DFBW_MASK)
|
||||
*val = 250;
|
||||
else
|
||||
*val = 125;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int mma7455_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct mma7455_data *mma7455 = iio_priv(indio_dev);
|
||||
int i;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
if (val == 250 && val2 == 0)
|
||||
i = MMA7455_CTL1_DFBW_125HZ;
|
||||
else if (val == 125 && val2 == 0)
|
||||
i = MMA7455_CTL1_DFBW_62_5HZ;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_update_bits(mma7455->regmap, MMA7455_REG_CTL1,
|
||||
MMA7455_CTL1_DFBW_MASK, i);
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
/* In 10-bit mode there is only one scale available */
|
||||
if (val == 0 && val2 == MMA7455_10BIT_SCALE)
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static IIO_CONST_ATTR(sampling_frequency_available, "125 250");
|
||||
|
||||
static struct attribute *mma7455_attributes[] = {
|
||||
&iio_const_attr_sampling_frequency_available.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group mma7455_group = {
|
||||
.attrs = mma7455_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_info mma7455_info = {
|
||||
.attrs = &mma7455_group,
|
||||
.read_raw = mma7455_read_raw,
|
||||
.write_raw = mma7455_write_raw,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
#define MMA7455_CHANNEL(axis, idx) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.modified = 1, \
|
||||
.address = MMA7455_REG_##axis##OUTL,\
|
||||
.channel2 = IIO_MOD_##axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.scan_index = idx, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 10, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec mma7455_channels[] = {
|
||||
MMA7455_CHANNEL(X, 0),
|
||||
MMA7455_CHANNEL(Y, 1),
|
||||
MMA7455_CHANNEL(Z, 2),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
};
|
||||
|
||||
static const unsigned long mma7455_scan_masks[] = {0x7, 0};
|
||||
|
||||
const struct regmap_config mma7455_core_regmap = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = MMA7455_REG_TW,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(mma7455_core_regmap);
|
||||
|
||||
int mma7455_core_probe(struct device *dev, struct regmap *regmap,
|
||||
const char *name)
|
||||
{
|
||||
struct mma7455_data *mma7455;
|
||||
struct iio_dev *indio_dev;
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(regmap, MMA7455_REG_WHOAMI, ®);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to read reg\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (reg != MMA7455_WHOAMI_ID) {
|
||||
dev_err(dev, "device id mismatch\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*mma7455));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, indio_dev);
|
||||
mma7455 = iio_priv(indio_dev);
|
||||
mma7455->regmap = regmap;
|
||||
mma7455->dev = dev;
|
||||
|
||||
indio_dev->info = &mma7455_info;
|
||||
indio_dev->name = name;
|
||||
indio_dev->dev.parent = dev;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = mma7455_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(mma7455_channels);
|
||||
indio_dev->available_scan_masks = mma7455_scan_masks;
|
||||
|
||||
regmap_write(mma7455->regmap, MMA7455_REG_MCTL,
|
||||
MMA7455_MCTL_MODE_MEASURE);
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
mma7455_trigger_handler, NULL);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to setup triggered buffer\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to register device\n");
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mma7455_core_probe);
|
||||
|
||||
int mma7455_core_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct mma7455_data *mma7455 = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
regmap_write(mma7455->regmap, MMA7455_REG_MCTL,
|
||||
MMA7455_MCTL_MODE_STANDBY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mma7455_core_remove);
|
||||
|
||||
MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>");
|
||||
MODULE_DESCRIPTION("Freescale MMA7455L core accelerometer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* IIO accel I2C driver for Freescale MMA7455L 3-axis 10-bit accelerometer
|
||||
* Copyright 2015 Joachim Eastwood <manabian@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "mma7455.h"
|
||||
|
||||
static int mma7455_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
const char *name = NULL;
|
||||
|
||||
regmap = devm_regmap_init_i2c(i2c, &mma7455_core_regmap);
|
||||
if (IS_ERR(regmap))
|
||||
return PTR_ERR(regmap);
|
||||
|
||||
if (id)
|
||||
name = id->name;
|
||||
|
||||
return mma7455_core_probe(&i2c->dev, regmap, name);
|
||||
}
|
||||
|
||||
static int mma7455_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
return mma7455_core_remove(&i2c->dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id mma7455_i2c_ids[] = {
|
||||
{ "mma7455", 0 },
|
||||
{ "mma7456", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mma7455_i2c_ids);
|
||||
|
||||
static struct i2c_driver mma7455_i2c_driver = {
|
||||
.probe = mma7455_i2c_probe,
|
||||
.remove = mma7455_i2c_remove,
|
||||
.id_table = mma7455_i2c_ids,
|
||||
.driver = {
|
||||
.name = "mma7455-i2c",
|
||||
},
|
||||
};
|
||||
module_i2c_driver(mma7455_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>");
|
||||
MODULE_DESCRIPTION("Freescale MMA7455L I2C accelerometer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* IIO accel SPI driver for Freescale MMA7455L 3-axis 10-bit accelerometer
|
||||
* Copyright 2015 Joachim Eastwood <manabian@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include "mma7455.h"
|
||||
|
||||
static int mma7455_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct spi_device_id *id = spi_get_device_id(spi);
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = devm_regmap_init_spi(spi, &mma7455_core_regmap);
|
||||
if (IS_ERR(regmap))
|
||||
return PTR_ERR(regmap);
|
||||
|
||||
return mma7455_core_probe(&spi->dev, regmap, id->name);
|
||||
}
|
||||
|
||||
static int mma7455_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
return mma7455_core_remove(&spi->dev);
|
||||
}
|
||||
|
||||
static const struct spi_device_id mma7455_spi_ids[] = {
|
||||
{ "mma7455", 0 },
|
||||
{ "mma7456", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, mma7455_spi_ids);
|
||||
|
||||
static struct spi_driver mma7455_spi_driver = {
|
||||
.probe = mma7455_spi_probe,
|
||||
.remove = mma7455_spi_remove,
|
||||
.id_table = mma7455_spi_ids,
|
||||
.driver = {
|
||||
.name = "mma7455-spi",
|
||||
},
|
||||
};
|
||||
module_spi_driver(mma7455_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>");
|
||||
MODULE_DESCRIPTION("Freescale MMA7455L SPI accelerometer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -29,6 +29,7 @@
|
|||
#include <linux/iio/events.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_irq.h>
|
||||
|
||||
#define MMA8452_STATUS 0x00
|
||||
#define MMA8452_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0))
|
||||
|
@ -57,7 +58,6 @@
|
|||
#define MMA8452_FF_MT_COUNT 0x18
|
||||
#define MMA8452_TRANSIENT_CFG 0x1d
|
||||
#define MMA8452_TRANSIENT_CFG_HPF_BYP BIT(0)
|
||||
#define MMA8452_TRANSIENT_CFG_CHAN(chan) BIT(chan + 1)
|
||||
#define MMA8452_TRANSIENT_CFG_ELE BIT(4)
|
||||
#define MMA8452_TRANSIENT_SRC 0x1e
|
||||
#define MMA8452_TRANSIENT_SRC_XTRANSE BIT(1)
|
||||
|
@ -143,6 +143,13 @@ struct mma_chip_info {
|
|||
u8 ev_count;
|
||||
};
|
||||
|
||||
enum {
|
||||
idx_x,
|
||||
idx_y,
|
||||
idx_z,
|
||||
idx_ts,
|
||||
};
|
||||
|
||||
static int mma8452_drdy(struct mma8452_data *data)
|
||||
{
|
||||
int tries = 150;
|
||||
|
@ -816,31 +823,31 @@ static struct attribute_group mma8452_event_attribute_group = {
|
|||
}
|
||||
|
||||
static const struct iio_chan_spec mma8452_channels[] = {
|
||||
MMA8452_CHANNEL(X, 0, 12),
|
||||
MMA8452_CHANNEL(Y, 1, 12),
|
||||
MMA8452_CHANNEL(Z, 2, 12),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
MMA8452_CHANNEL(X, idx_x, 12),
|
||||
MMA8452_CHANNEL(Y, idx_y, 12),
|
||||
MMA8452_CHANNEL(Z, idx_z, 12),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(idx_ts),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec mma8453_channels[] = {
|
||||
MMA8452_CHANNEL(X, 0, 10),
|
||||
MMA8452_CHANNEL(Y, 1, 10),
|
||||
MMA8452_CHANNEL(Z, 2, 10),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
MMA8452_CHANNEL(X, idx_x, 10),
|
||||
MMA8452_CHANNEL(Y, idx_y, 10),
|
||||
MMA8452_CHANNEL(Z, idx_z, 10),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(idx_ts),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec mma8652_channels[] = {
|
||||
MMA8652_CHANNEL(X, 0, 12),
|
||||
MMA8652_CHANNEL(Y, 1, 12),
|
||||
MMA8652_CHANNEL(Z, 2, 12),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
MMA8652_CHANNEL(X, idx_x, 12),
|
||||
MMA8652_CHANNEL(Y, idx_y, 12),
|
||||
MMA8652_CHANNEL(Z, idx_z, 12),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(idx_ts),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec mma8653_channels[] = {
|
||||
MMA8652_CHANNEL(X, 0, 10),
|
||||
MMA8652_CHANNEL(Y, 1, 10),
|
||||
MMA8652_CHANNEL(Z, 2, 10),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
MMA8652_CHANNEL(X, idx_x, 10),
|
||||
MMA8652_CHANNEL(Y, idx_y, 10),
|
||||
MMA8652_CHANNEL(Z, idx_z, 10),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(idx_ts),
|
||||
};
|
||||
|
||||
enum {
|
||||
|
@ -1130,13 +1137,21 @@ static int mma8452_probe(struct i2c_client *client,
|
|||
MMA8452_INT_FF_MT;
|
||||
int enabled_interrupts = MMA8452_INT_TRANS |
|
||||
MMA8452_INT_FF_MT;
|
||||
int irq2;
|
||||
|
||||
/* Assume wired to INT1 pin */
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
MMA8452_CTRL_REG5,
|
||||
supported_interrupts);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
irq2 = of_irq_get_byname(client->dev.of_node, "INT2");
|
||||
|
||||
if (irq2 == client->irq) {
|
||||
dev_dbg(&client->dev, "using interrupt line INT2\n");
|
||||
} else {
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
MMA8452_CTRL_REG5,
|
||||
supported_interrupts);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev_dbg(&client->dev, "using interrupt line INT1\n");
|
||||
}
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
MMA8452_CTRL_REG4,
|
||||
|
|
|
@ -495,25 +495,23 @@ static int mma9551_probe(struct i2c_client *client,
|
|||
if (ret < 0)
|
||||
goto out_poweroff;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "unable to register iio device\n");
|
||||
goto out_poweroff;
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret < 0)
|
||||
goto out_iio_unregister;
|
||||
goto out_poweroff;
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev,
|
||||
MMA9551_AUTO_SUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "unable to register iio device\n");
|
||||
goto out_poweroff;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_iio_unregister:
|
||||
iio_device_unregister(indio_dev);
|
||||
out_poweroff:
|
||||
mma9551_set_device_state(client, false);
|
||||
|
||||
|
@ -525,11 +523,12 @@ static int mma9551_remove(struct i2c_client *client)
|
|||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
pm_runtime_disable(&client->dev);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
pm_runtime_put_noidle(&client->dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
mutex_lock(&data->mutex);
|
||||
mma9551_set_device_state(data->client, false);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
|
|
@ -1133,27 +1133,24 @@ static int mma9553_probe(struct i2c_client *client,
|
|||
}
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "unable to register iio device\n");
|
||||
goto out_poweroff;
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret < 0)
|
||||
goto out_iio_unregister;
|
||||
goto out_poweroff;
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev,
|
||||
MMA9551_AUTO_SUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
dev_dbg(&indio_dev->dev, "Registered device %s\n", name);
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "unable to register iio device\n");
|
||||
goto out_poweroff;
|
||||
}
|
||||
|
||||
dev_dbg(&indio_dev->dev, "Registered device %s\n", name);
|
||||
return 0;
|
||||
|
||||
out_iio_unregister:
|
||||
iio_device_unregister(indio_dev);
|
||||
out_poweroff:
|
||||
mma9551_set_device_state(client, false);
|
||||
return ret;
|
||||
|
@ -1164,11 +1161,12 @@ static int mma9553_remove(struct i2c_client *client)
|
|||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct mma9553_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
pm_runtime_disable(&client->dev);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
pm_runtime_put_noidle(&client->dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
mutex_lock(&data->mutex);
|
||||
mma9551_set_device_state(data->client, false);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* MXC6255 - MEMSIC orientation sensing accelerometer
|
||||
*
|
||||
* Copyright (c) 2015, Intel Corporation.
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* IIO driver for MXC6255 (7-bit I2C slave address 0x15).
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
||||
#define MXC6255_DRV_NAME "mxc6255"
|
||||
#define MXC6255_REGMAP_NAME "mxc6255_regmap"
|
||||
|
||||
#define MXC6255_REG_XOUT 0x00
|
||||
#define MXC6255_REG_YOUT 0x01
|
||||
#define MXC6255_REG_CHIP_ID 0x08
|
||||
|
||||
#define MXC6255_CHIP_ID 0x05
|
||||
|
||||
/*
|
||||
* MXC6255 has only one measurement range: +/- 2G.
|
||||
* The acceleration output is an 8-bit value.
|
||||
*
|
||||
* Scale is calculated as follows:
|
||||
* (2 + 2) * 9.80665 / (2^8 - 1) = 0.153829
|
||||
*
|
||||
* Scale value for +/- 2G measurement range
|
||||
*/
|
||||
#define MXC6255_SCALE 153829
|
||||
|
||||
enum mxc6255_axis {
|
||||
AXIS_X,
|
||||
AXIS_Y,
|
||||
};
|
||||
|
||||
struct mxc6255_data {
|
||||
struct i2c_client *client;
|
||||
struct regmap *regmap;
|
||||
};
|
||||
|
||||
static int mxc6255_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct mxc6255_data *data = iio_priv(indio_dev);
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = regmap_read(data->regmap, chan->address, ®);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev,
|
||||
"Error reading reg %lu\n", chan->address);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*val = sign_extend32(reg, 7);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
*val2 = MXC6255_SCALE;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info mxc6255_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = mxc6255_read_raw,
|
||||
};
|
||||
|
||||
#define MXC6255_CHANNEL(_axis, reg) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##_axis, \
|
||||
.address = reg, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec mxc6255_channels[] = {
|
||||
MXC6255_CHANNEL(X, MXC6255_REG_XOUT),
|
||||
MXC6255_CHANNEL(Y, MXC6255_REG_YOUT),
|
||||
};
|
||||
|
||||
static bool mxc6255_is_readable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case MXC6255_REG_XOUT:
|
||||
case MXC6255_REG_YOUT:
|
||||
case MXC6255_REG_CHIP_ID:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct regmap_config mxc6255_regmap_config = {
|
||||
.name = MXC6255_REGMAP_NAME,
|
||||
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.readable_reg = mxc6255_is_readable_reg,
|
||||
};
|
||||
|
||||
static int mxc6255_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct mxc6255_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
struct regmap *regmap;
|
||||
unsigned int chip_id;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &mxc6255_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&client->dev, "Error initializing regmap\n");
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
data->regmap = regmap;
|
||||
|
||||
indio_dev->name = MXC6255_DRV_NAME;
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->channels = mxc6255_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(mxc6255_channels);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->info = &mxc6255_info;
|
||||
|
||||
ret = regmap_read(data->regmap, MXC6255_REG_CHIP_ID, &chip_id);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error reading chip id %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (chip_id != MXC6255_CHIP_ID) {
|
||||
dev_err(&client->dev, "Invalid chip id %x\n", chip_id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_dbg(&client->dev, "Chip id %x\n", chip_id);
|
||||
|
||||
ret = devm_iio_device_register(&client->dev, indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Could not register IIO device\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id mxc6255_acpi_match[] = {
|
||||
{"MXC6255", 0},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, mxc6255_acpi_match);
|
||||
|
||||
static const struct i2c_device_id mxc6255_id[] = {
|
||||
{"mxc6255", 0},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mxc6255_id);
|
||||
|
||||
static struct i2c_driver mxc6255_driver = {
|
||||
.driver = {
|
||||
.name = MXC6255_DRV_NAME,
|
||||
.acpi_match_table = ACPI_PTR(mxc6255_acpi_match),
|
||||
},
|
||||
.probe = mxc6255_probe,
|
||||
.id_table = mxc6255_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(mxc6255_driver);
|
||||
|
||||
MODULE_AUTHOR("Teodora Baluta <teodora.baluta@intel.com>");
|
||||
MODULE_DESCRIPTION("MEMSIC MXC6255 orientation sensing accelerometer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -27,6 +27,7 @@
|
|||
#define LSM303DLM_ACCEL_DEV_NAME "lsm303dlm_accel"
|
||||
#define LSM330_ACCEL_DEV_NAME "lsm330_accel"
|
||||
#define LSM303AGR_ACCEL_DEV_NAME "lsm303agr_accel"
|
||||
#define LIS2DH12_ACCEL_DEV_NAME "lis2dh12_accel"
|
||||
|
||||
/**
|
||||
* struct st_sensors_platform_data - default accel platform data
|
||||
|
|
|
@ -232,6 +232,7 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
[3] = LSM330DL_ACCEL_DEV_NAME,
|
||||
[4] = LSM330DLC_ACCEL_DEV_NAME,
|
||||
[5] = LSM303AGR_ACCEL_DEV_NAME,
|
||||
[6] = LIS2DH12_ACCEL_DEV_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_accel_12bit_channels,
|
||||
.odr = {
|
||||
|
|
|
@ -72,6 +72,10 @@ static const struct of_device_id st_accel_of_match[] = {
|
|||
.compatible = "st,lsm303agr-accel",
|
||||
.data = LSM303AGR_ACCEL_DEV_NAME,
|
||||
},
|
||||
{
|
||||
.compatible = "st,lis2dh12-accel",
|
||||
.data = LIS2DH12_ACCEL_DEV_NAME,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, st_accel_of_match);
|
||||
|
@ -121,6 +125,7 @@ static const struct i2c_device_id st_accel_id_table[] = {
|
|||
{ LSM303DLM_ACCEL_DEV_NAME },
|
||||
{ LSM330_ACCEL_DEV_NAME },
|
||||
{ LSM303AGR_ACCEL_DEV_NAME },
|
||||
{ LIS2DH12_ACCEL_DEV_NAME },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, st_accel_id_table);
|
||||
|
|
|
@ -58,6 +58,7 @@ static const struct spi_device_id st_accel_id_table[] = {
|
|||
{ LSM303DLM_ACCEL_DEV_NAME },
|
||||
{ LSM330_ACCEL_DEV_NAME },
|
||||
{ LSM303AGR_ACCEL_DEV_NAME },
|
||||
{ LIS2DH12_ACCEL_DEV_NAME },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, st_accel_id_table);
|
||||
|
|
|
@ -194,6 +194,25 @@ config HI8435
|
|||
This driver can also be built as a module. If so, the module will be
|
||||
called hi8435.
|
||||
|
||||
config INA2XX_ADC
|
||||
tristate "Texas Instruments INA2xx Power Monitors IIO driver"
|
||||
depends on I2C && !SENSORS_INA2XX
|
||||
select REGMAP_I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_KFIFO_BUF
|
||||
help
|
||||
Say yes here to build support for TI INA2xx family of Power Monitors.
|
||||
This driver is mutually exclusive with the HWMON version.
|
||||
|
||||
config IMX7D_ADC
|
||||
tristate "IMX7D ADC driver"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
help
|
||||
Say yes here to build support for IMX7D ADC.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called imx7d_adc.
|
||||
|
||||
config LP8788_ADC
|
||||
tristate "LP8788 ADC driver"
|
||||
depends on MFD_LP8788
|
||||
|
@ -275,6 +294,14 @@ config NAU7802
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called nau7802.
|
||||
|
||||
config PALMAS_GPADC
|
||||
tristate "TI Palmas General Purpose ADC"
|
||||
depends on MFD_PALMAS
|
||||
help
|
||||
Palmas series pmic chip by Texas Instruments (twl6035/6037)
|
||||
is used in smartphones and tablets and supports a 16 channel
|
||||
general purpose ADC.
|
||||
|
||||
config QCOM_SPMI_IADC
|
||||
tristate "Qualcomm SPMI PMIC current ADC"
|
||||
depends on SPMI
|
||||
|
@ -324,15 +351,25 @@ config TI_ADC081C
|
|||
called ti-adc081c.
|
||||
|
||||
config TI_ADC128S052
|
||||
tristate "Texas Instruments ADC128S052/ADC122S021"
|
||||
tristate "Texas Instruments ADC128S052/ADC122S021/ADC124S021"
|
||||
depends on SPI
|
||||
help
|
||||
If you say yes here you get support for Texas Instruments ADC128S052
|
||||
and ADC122S021 chips.
|
||||
If you say yes here you get support for Texas Instruments ADC128S052,
|
||||
ADC122S021 and ADC124S021 chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called ti-adc128s052.
|
||||
|
||||
config TI_ADS8688
|
||||
tristate "Texas Instruments ADS8688"
|
||||
depends on SPI && OF
|
||||
help
|
||||
If you say yes here you get support for Texas Instruments ADS8684 and
|
||||
and ADS8688 ADC chips
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called ti-ads8688.
|
||||
|
||||
config TI_AM335X_ADC
|
||||
tristate "TI's AM335X ADC driver"
|
||||
depends on MFD_TI_AM335X_TSCADC
|
||||
|
|
|
@ -20,6 +20,8 @@ obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o
|
|||
obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o
|
||||
obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o
|
||||
obj-$(CONFIG_HI8435) += hi8435.o
|
||||
obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o
|
||||
obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o
|
||||
obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
|
||||
obj-$(CONFIG_MAX1027) += max1027.o
|
||||
obj-$(CONFIG_MAX1363) += max1363.o
|
||||
|
@ -27,11 +29,13 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
|
|||
obj-$(CONFIG_MCP3422) += mcp3422.o
|
||||
obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
|
||||
obj-$(CONFIG_NAU7802) += nau7802.o
|
||||
obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
|
||||
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
|
||||
obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
|
||||
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
|
||||
obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
|
||||
obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
|
||||
obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o
|
||||
obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o
|
||||
obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
|
||||
obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
|
||||
|
|
|
@ -478,10 +478,9 @@ static int ad7793_read_raw(struct iio_dev *indio_dev,
|
|||
*val2 = st->
|
||||
scale_avail[(st->conf >> 8) & 0x7][1];
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
} else {
|
||||
/* 1170mV / 2^23 * 6 */
|
||||
scale_uv = (1170ULL * 1000000000ULL * 6ULL);
|
||||
}
|
||||
/* 1170mV / 2^23 * 6 */
|
||||
scale_uv = (1170ULL * 1000000000ULL * 6ULL);
|
||||
break;
|
||||
case IIO_TEMP:
|
||||
/* 1170mV / 0.81 mV/C / 2^23 */
|
||||
|
|
|
@ -742,7 +742,7 @@ static int at91_adc_of_get_resolution(struct at91_adc_state *st,
|
|||
return count;
|
||||
}
|
||||
|
||||
resolutions = kmalloc(count * sizeof(*resolutions), GFP_KERNEL);
|
||||
resolutions = kmalloc_array(count, sizeof(*resolutions), GFP_KERNEL);
|
||||
if (!resolutions)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
|
@ -0,0 +1,609 @@
|
|||
/*
|
||||
* Freescale i.MX7D ADC driver
|
||||
*
|
||||
* Copyright (C) 2015 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/driver.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
||||
/* ADC register */
|
||||
#define IMX7D_REG_ADC_CH_A_CFG1 0x00
|
||||
#define IMX7D_REG_ADC_CH_A_CFG2 0x10
|
||||
#define IMX7D_REG_ADC_CH_B_CFG1 0x20
|
||||
#define IMX7D_REG_ADC_CH_B_CFG2 0x30
|
||||
#define IMX7D_REG_ADC_CH_C_CFG1 0x40
|
||||
#define IMX7D_REG_ADC_CH_C_CFG2 0x50
|
||||
#define IMX7D_REG_ADC_CH_D_CFG1 0x60
|
||||
#define IMX7D_REG_ADC_CH_D_CFG2 0x70
|
||||
#define IMX7D_REG_ADC_CH_SW_CFG 0x80
|
||||
#define IMX7D_REG_ADC_TIMER_UNIT 0x90
|
||||
#define IMX7D_REG_ADC_DMA_FIFO 0xa0
|
||||
#define IMX7D_REG_ADC_FIFO_STATUS 0xb0
|
||||
#define IMX7D_REG_ADC_INT_SIG_EN 0xc0
|
||||
#define IMX7D_REG_ADC_INT_EN 0xd0
|
||||
#define IMX7D_REG_ADC_INT_STATUS 0xe0
|
||||
#define IMX7D_REG_ADC_CHA_B_CNV_RSLT 0xf0
|
||||
#define IMX7D_REG_ADC_CHC_D_CNV_RSLT 0x100
|
||||
#define IMX7D_REG_ADC_CH_SW_CNV_RSLT 0x110
|
||||
#define IMX7D_REG_ADC_DMA_FIFO_DAT 0x120
|
||||
#define IMX7D_REG_ADC_ADC_CFG 0x130
|
||||
|
||||
#define IMX7D_REG_ADC_CHANNEL_CFG2_BASE 0x10
|
||||
#define IMX7D_EACH_CHANNEL_REG_OFFSET 0x20
|
||||
|
||||
#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN (0x1 << 31)
|
||||
#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE BIT(30)
|
||||
#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN BIT(29)
|
||||
#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(x) ((x) << 24)
|
||||
|
||||
#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4 (0x0 << 12)
|
||||
#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8 (0x1 << 12)
|
||||
#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16 (0x2 << 12)
|
||||
#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32 (0x3 << 12)
|
||||
|
||||
#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4 (0x0 << 29)
|
||||
#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8 (0x1 << 29)
|
||||
#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16 (0x2 << 29)
|
||||
#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32 (0x3 << 29)
|
||||
#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64 (0x4 << 29)
|
||||
#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128 (0x5 << 29)
|
||||
|
||||
#define IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN BIT(31)
|
||||
#define IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN BIT(1)
|
||||
#define IMX7D_REG_ADC_ADC_CFG_ADC_EN BIT(0)
|
||||
|
||||
#define IMX7D_REG_ADC_INT_CHA_COV_INT_EN BIT(8)
|
||||
#define IMX7D_REG_ADC_INT_CHB_COV_INT_EN BIT(9)
|
||||
#define IMX7D_REG_ADC_INT_CHC_COV_INT_EN BIT(10)
|
||||
#define IMX7D_REG_ADC_INT_CHD_COV_INT_EN BIT(11)
|
||||
#define IMX7D_REG_ADC_INT_CHANNEL_INT_EN \
|
||||
(IMX7D_REG_ADC_INT_CHA_COV_INT_EN | \
|
||||
IMX7D_REG_ADC_INT_CHB_COV_INT_EN | \
|
||||
IMX7D_REG_ADC_INT_CHC_COV_INT_EN | \
|
||||
IMX7D_REG_ADC_INT_CHD_COV_INT_EN)
|
||||
#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS 0xf00
|
||||
#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT 0xf0000
|
||||
|
||||
#define IMX7D_ADC_TIMEOUT msecs_to_jiffies(100)
|
||||
|
||||
enum imx7d_adc_clk_pre_div {
|
||||
IMX7D_ADC_ANALOG_CLK_PRE_DIV_4,
|
||||
IMX7D_ADC_ANALOG_CLK_PRE_DIV_8,
|
||||
IMX7D_ADC_ANALOG_CLK_PRE_DIV_16,
|
||||
IMX7D_ADC_ANALOG_CLK_PRE_DIV_32,
|
||||
IMX7D_ADC_ANALOG_CLK_PRE_DIV_64,
|
||||
IMX7D_ADC_ANALOG_CLK_PRE_DIV_128,
|
||||
};
|
||||
|
||||
enum imx7d_adc_average_num {
|
||||
IMX7D_ADC_AVERAGE_NUM_4,
|
||||
IMX7D_ADC_AVERAGE_NUM_8,
|
||||
IMX7D_ADC_AVERAGE_NUM_16,
|
||||
IMX7D_ADC_AVERAGE_NUM_32,
|
||||
};
|
||||
|
||||
struct imx7d_adc_feature {
|
||||
enum imx7d_adc_clk_pre_div clk_pre_div;
|
||||
enum imx7d_adc_average_num avg_num;
|
||||
|
||||
u32 core_time_unit; /* impact the sample rate */
|
||||
|
||||
bool average_en;
|
||||
};
|
||||
|
||||
struct imx7d_adc {
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
struct clk *clk;
|
||||
|
||||
u32 vref_uv;
|
||||
u32 value;
|
||||
u32 channel;
|
||||
u32 pre_div_num;
|
||||
|
||||
struct regulator *vref;
|
||||
struct imx7d_adc_feature adc_feature;
|
||||
|
||||
struct completion completion;
|
||||
};
|
||||
|
||||
struct imx7d_adc_analogue_core_clk {
|
||||
u32 pre_div;
|
||||
u32 reg_config;
|
||||
};
|
||||
|
||||
#define IMX7D_ADC_ANALOGUE_CLK_CONFIG(_pre_div, _reg_conf) { \
|
||||
.pre_div = (_pre_div), \
|
||||
.reg_config = (_reg_conf), \
|
||||
}
|
||||
|
||||
static const struct imx7d_adc_analogue_core_clk imx7d_adc_analogue_clk[] = {
|
||||
IMX7D_ADC_ANALOGUE_CLK_CONFIG(4, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4),
|
||||
IMX7D_ADC_ANALOGUE_CLK_CONFIG(8, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8),
|
||||
IMX7D_ADC_ANALOGUE_CLK_CONFIG(16, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16),
|
||||
IMX7D_ADC_ANALOGUE_CLK_CONFIG(32, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32),
|
||||
IMX7D_ADC_ANALOGUE_CLK_CONFIG(64, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64),
|
||||
IMX7D_ADC_ANALOGUE_CLK_CONFIG(128, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128),
|
||||
};
|
||||
|
||||
#define IMX7D_ADC_CHAN(_idx) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.channel = (_idx), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec imx7d_adc_iio_channels[] = {
|
||||
IMX7D_ADC_CHAN(0),
|
||||
IMX7D_ADC_CHAN(1),
|
||||
IMX7D_ADC_CHAN(2),
|
||||
IMX7D_ADC_CHAN(3),
|
||||
IMX7D_ADC_CHAN(4),
|
||||
IMX7D_ADC_CHAN(5),
|
||||
IMX7D_ADC_CHAN(6),
|
||||
IMX7D_ADC_CHAN(7),
|
||||
IMX7D_ADC_CHAN(8),
|
||||
IMX7D_ADC_CHAN(9),
|
||||
IMX7D_ADC_CHAN(10),
|
||||
IMX7D_ADC_CHAN(11),
|
||||
IMX7D_ADC_CHAN(12),
|
||||
IMX7D_ADC_CHAN(13),
|
||||
IMX7D_ADC_CHAN(14),
|
||||
IMX7D_ADC_CHAN(15),
|
||||
};
|
||||
|
||||
static const u32 imx7d_adc_average_num[] = {
|
||||
IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4,
|
||||
IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8,
|
||||
IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16,
|
||||
IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32,
|
||||
};
|
||||
|
||||
static void imx7d_adc_feature_config(struct imx7d_adc *info)
|
||||
{
|
||||
info->adc_feature.clk_pre_div = IMX7D_ADC_ANALOG_CLK_PRE_DIV_4;
|
||||
info->adc_feature.avg_num = IMX7D_ADC_AVERAGE_NUM_32;
|
||||
info->adc_feature.core_time_unit = 1;
|
||||
info->adc_feature.average_en = true;
|
||||
}
|
||||
|
||||
static void imx7d_adc_sample_rate_set(struct imx7d_adc *info)
|
||||
{
|
||||
struct imx7d_adc_feature *adc_feature = &info->adc_feature;
|
||||
struct imx7d_adc_analogue_core_clk adc_analogure_clk;
|
||||
u32 i;
|
||||
u32 tmp_cfg1;
|
||||
u32 sample_rate = 0;
|
||||
|
||||
/*
|
||||
* Before sample set, disable channel A,B,C,D. Here we
|
||||
* clear the bit 31 of register REG_ADC_CH_A\B\C\D_CFG1.
|
||||
*/
|
||||
for (i = 0; i < 4; i++) {
|
||||
tmp_cfg1 =
|
||||
readl(info->regs + i * IMX7D_EACH_CHANNEL_REG_OFFSET);
|
||||
tmp_cfg1 &= ~IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN;
|
||||
writel(tmp_cfg1,
|
||||
info->regs + i * IMX7D_EACH_CHANNEL_REG_OFFSET);
|
||||
}
|
||||
|
||||
adc_analogure_clk = imx7d_adc_analogue_clk[adc_feature->clk_pre_div];
|
||||
sample_rate |= adc_analogure_clk.reg_config;
|
||||
info->pre_div_num = adc_analogure_clk.pre_div;
|
||||
|
||||
sample_rate |= adc_feature->core_time_unit;
|
||||
writel(sample_rate, info->regs + IMX7D_REG_ADC_TIMER_UNIT);
|
||||
}
|
||||
|
||||
static void imx7d_adc_hw_init(struct imx7d_adc *info)
|
||||
{
|
||||
u32 cfg;
|
||||
|
||||
/* power up and enable adc analogue core */
|
||||
cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG);
|
||||
cfg &= ~(IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN |
|
||||
IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN);
|
||||
cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_EN;
|
||||
writel(cfg, info->regs + IMX7D_REG_ADC_ADC_CFG);
|
||||
|
||||
/* enable channel A,B,C,D interrupt */
|
||||
writel(IMX7D_REG_ADC_INT_CHANNEL_INT_EN,
|
||||
info->regs + IMX7D_REG_ADC_INT_SIG_EN);
|
||||
writel(IMX7D_REG_ADC_INT_CHANNEL_INT_EN,
|
||||
info->regs + IMX7D_REG_ADC_INT_EN);
|
||||
|
||||
imx7d_adc_sample_rate_set(info);
|
||||
}
|
||||
|
||||
static void imx7d_adc_channel_set(struct imx7d_adc *info)
|
||||
{
|
||||
u32 cfg1 = 0;
|
||||
u32 cfg2;
|
||||
u32 channel;
|
||||
|
||||
channel = info->channel;
|
||||
|
||||
/* the channel choose single conversion, and enable average mode */
|
||||
cfg1 |= (IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN |
|
||||
IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE);
|
||||
if (info->adc_feature.average_en)
|
||||
cfg1 |= IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN;
|
||||
|
||||
/*
|
||||
* physical channel 0 chose logical channel A
|
||||
* physical channel 1 chose logical channel B
|
||||
* physical channel 2 chose logical channel C
|
||||
* physical channel 3 chose logical channel D
|
||||
*/
|
||||
cfg1 |= IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(channel);
|
||||
|
||||
/*
|
||||
* read register REG_ADC_CH_A\B\C\D_CFG2, according to the
|
||||
* channel chosen
|
||||
*/
|
||||
cfg2 = readl(info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel +
|
||||
IMX7D_REG_ADC_CHANNEL_CFG2_BASE);
|
||||
|
||||
cfg2 |= imx7d_adc_average_num[info->adc_feature.avg_num];
|
||||
|
||||
/*
|
||||
* write the register REG_ADC_CH_A\B\C\D_CFG2, according to
|
||||
* the channel chosen
|
||||
*/
|
||||
writel(cfg2, info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel +
|
||||
IMX7D_REG_ADC_CHANNEL_CFG2_BASE);
|
||||
writel(cfg1, info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel);
|
||||
}
|
||||
|
||||
static u32 imx7d_adc_get_sample_rate(struct imx7d_adc *info)
|
||||
{
|
||||
/* input clock is always 24MHz */
|
||||
u32 input_clk = 24000000;
|
||||
u32 analogue_core_clk;
|
||||
u32 core_time_unit = info->adc_feature.core_time_unit;
|
||||
u32 tmp;
|
||||
|
||||
analogue_core_clk = input_clk / info->pre_div_num;
|
||||
tmp = (core_time_unit + 1) * 6;
|
||||
|
||||
return analogue_core_clk / tmp;
|
||||
}
|
||||
|
||||
static int imx7d_adc_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val,
|
||||
int *val2,
|
||||
long mask)
|
||||
{
|
||||
struct imx7d_adc *info = iio_priv(indio_dev);
|
||||
|
||||
u32 channel;
|
||||
long ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
mutex_lock(&indio_dev->mlock);
|
||||
reinit_completion(&info->completion);
|
||||
|
||||
channel = chan->channel & 0x03;
|
||||
info->channel = channel;
|
||||
imx7d_adc_channel_set(info);
|
||||
|
||||
ret = wait_for_completion_interruptible_timeout
|
||||
(&info->completion, IMX7D_ADC_TIMEOUT);
|
||||
if (ret == 0) {
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
if (ret < 0) {
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*val = info->value;
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
info->vref_uv = regulator_get_voltage(info->vref);
|
||||
*val = info->vref_uv / 1000;
|
||||
*val2 = 12;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*val = imx7d_adc_get_sample_rate(info);
|
||||
return IIO_VAL_INT;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int imx7d_adc_read_data(struct imx7d_adc *info)
|
||||
{
|
||||
u32 channel;
|
||||
u32 value;
|
||||
|
||||
channel = info->channel & 0x03;
|
||||
|
||||
/*
|
||||
* channel A and B conversion result share one register,
|
||||
* bit[27~16] is the channel B conversion result,
|
||||
* bit[11~0] is the channel A conversion result.
|
||||
* channel C and D is the same.
|
||||
*/
|
||||
if (channel < 2)
|
||||
value = readl(info->regs + IMX7D_REG_ADC_CHA_B_CNV_RSLT);
|
||||
else
|
||||
value = readl(info->regs + IMX7D_REG_ADC_CHC_D_CNV_RSLT);
|
||||
if (channel & 0x1) /* channel B or D */
|
||||
value = (value >> 16) & 0xFFF;
|
||||
else /* channel A or C */
|
||||
value &= 0xFFF;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static irqreturn_t imx7d_adc_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct imx7d_adc *info = (struct imx7d_adc *)dev_id;
|
||||
int status;
|
||||
|
||||
status = readl(info->regs + IMX7D_REG_ADC_INT_STATUS);
|
||||
if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS) {
|
||||
info->value = imx7d_adc_read_data(info);
|
||||
complete(&info->completion);
|
||||
|
||||
/*
|
||||
* The register IMX7D_REG_ADC_INT_STATUS can't clear
|
||||
* itself after read operation, need software to write
|
||||
* 0 to the related bit. Here we clear the channel A/B/C/D
|
||||
* conversion finished flag.
|
||||
*/
|
||||
status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS;
|
||||
writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the channel A/B/C/D conversion timeout, report it and clear these
|
||||
* timeout flags.
|
||||
*/
|
||||
if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT) {
|
||||
pr_err("%s: ADC got conversion time out interrupt: 0x%08x\n",
|
||||
dev_name(info->dev), status);
|
||||
status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT;
|
||||
writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int imx7d_adc_reg_access(struct iio_dev *indio_dev,
|
||||
unsigned reg, unsigned writeval,
|
||||
unsigned *readval)
|
||||
{
|
||||
struct imx7d_adc *info = iio_priv(indio_dev);
|
||||
|
||||
if (!readval || reg % 4 || reg > IMX7D_REG_ADC_ADC_CFG)
|
||||
return -EINVAL;
|
||||
|
||||
*readval = readl(info->regs + reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_info imx7d_adc_iio_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &imx7d_adc_read_raw,
|
||||
.debugfs_reg_access = &imx7d_adc_reg_access,
|
||||
};
|
||||
|
||||
static const struct of_device_id imx7d_adc_match[] = {
|
||||
{ .compatible = "fsl,imx7d-adc", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx7d_adc_match);
|
||||
|
||||
static void imx7d_adc_power_down(struct imx7d_adc *info)
|
||||
{
|
||||
u32 adc_cfg;
|
||||
|
||||
adc_cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG);
|
||||
adc_cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN |
|
||||
IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN;
|
||||
adc_cfg &= ~IMX7D_REG_ADC_ADC_CFG_ADC_EN;
|
||||
writel(adc_cfg, info->regs + IMX7D_REG_ADC_ADC_CFG);
|
||||
}
|
||||
|
||||
static int imx7d_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct imx7d_adc *info;
|
||||
struct iio_dev *indio_dev;
|
||||
struct resource *mem;
|
||||
int irq;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
|
||||
if (!indio_dev) {
|
||||
dev_err(&pdev->dev, "Failed allocating iio device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
info = iio_priv(indio_dev);
|
||||
info->dev = &pdev->dev;
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
info->regs = devm_ioremap_resource(&pdev->dev, mem);
|
||||
if (IS_ERR(info->regs)) {
|
||||
ret = PTR_ERR(info->regs);
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to remap adc memory, err = %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "No irq resource?\n");
|
||||
return irq;
|
||||
}
|
||||
|
||||
info->clk = devm_clk_get(&pdev->dev, "adc");
|
||||
if (IS_ERR(info->clk)) {
|
||||
ret = PTR_ERR(info->clk);
|
||||
dev_err(&pdev->dev, "Failed getting clock, err = %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
info->vref = devm_regulator_get(&pdev->dev, "vref");
|
||||
if (IS_ERR(info->vref)) {
|
||||
ret = PTR_ERR(info->vref);
|
||||
dev_err(&pdev->dev,
|
||||
"Failed getting reference voltage, err = %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regulator_enable(info->vref);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"Can't enable adc reference top voltage, err = %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
init_completion(&info->completion);
|
||||
|
||||
indio_dev->name = dev_name(&pdev->dev);
|
||||
indio_dev->dev.parent = &pdev->dev;
|
||||
indio_dev->info = &imx7d_adc_iio_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = imx7d_adc_iio_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(imx7d_adc_iio_channels);
|
||||
|
||||
ret = clk_prepare_enable(info->clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"Could not prepare or enable the clock.\n");
|
||||
goto error_adc_clk_enable;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(info->dev, irq,
|
||||
imx7d_adc_isr, 0,
|
||||
dev_name(&pdev->dev), info);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Failed requesting irq, irq = %d\n", irq);
|
||||
goto error_iio_device_register;
|
||||
}
|
||||
|
||||
imx7d_adc_feature_config(info);
|
||||
imx7d_adc_hw_init(info);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
imx7d_adc_power_down(info);
|
||||
dev_err(&pdev->dev, "Couldn't register the device.\n");
|
||||
goto error_iio_device_register;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error_iio_device_register:
|
||||
clk_disable_unprepare(info->clk);
|
||||
error_adc_clk_enable:
|
||||
regulator_disable(info->vref);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx7d_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct imx7d_adc *info = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
imx7d_adc_power_down(info);
|
||||
|
||||
clk_disable_unprepare(info->clk);
|
||||
regulator_disable(info->vref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused imx7d_adc_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct imx7d_adc *info = iio_priv(indio_dev);
|
||||
|
||||
imx7d_adc_power_down(info);
|
||||
|
||||
clk_disable_unprepare(info->clk);
|
||||
regulator_disable(info->vref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused imx7d_adc_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct imx7d_adc *info = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = regulator_enable(info->vref);
|
||||
if (ret) {
|
||||
dev_err(info->dev,
|
||||
"Can't enable adc reference top voltage, err = %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(info->clk);
|
||||
if (ret) {
|
||||
dev_err(info->dev,
|
||||
"Could not prepare or enable clock.\n");
|
||||
regulator_disable(info->vref);
|
||||
return ret;
|
||||
}
|
||||
|
||||
imx7d_adc_hw_init(info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(imx7d_adc_pm_ops, imx7d_adc_suspend, imx7d_adc_resume);
|
||||
|
||||
static struct platform_driver imx7d_adc_driver = {
|
||||
.probe = imx7d_adc_probe,
|
||||
.remove = imx7d_adc_remove,
|
||||
.driver = {
|
||||
.name = "imx7d_adc",
|
||||
.of_match_table = imx7d_adc_match,
|
||||
.pm = &imx7d_adc_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(imx7d_adc_driver);
|
||||
|
||||
MODULE_AUTHOR("Haibo Chen <haibo.chen@freescale.com>");
|
||||
MODULE_DESCRIPTION("Freeacale IMX7D ADC driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,745 @@
|
|||
/*
|
||||
* INA2XX Current and Power Monitors
|
||||
*
|
||||
* Copyright 2015 Baylibre SAS.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Based on linux/drivers/iio/adc/ad7291.c
|
||||
* Copyright 2010-2011 Analog Devices Inc.
|
||||
*
|
||||
* Based on linux/drivers/hwmon/ina2xx.c
|
||||
* Copyright 2012 Lothar Felten <l-felten@ti.com>
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*
|
||||
* IIO driver for INA219-220-226-230-231
|
||||
*
|
||||
* Configurable 7-bit I2C slave address from 0x40 to 0x4F
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/platform_data/ina2xx.h>
|
||||
|
||||
#include <linux/util_macros.h>
|
||||
|
||||
/* INA2XX registers definition */
|
||||
#define INA2XX_CONFIG 0x00
|
||||
#define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */
|
||||
#define INA2XX_BUS_VOLTAGE 0x02 /* readonly */
|
||||
#define INA2XX_POWER 0x03 /* readonly */
|
||||
#define INA2XX_CURRENT 0x04 /* readonly */
|
||||
#define INA2XX_CALIBRATION 0x05
|
||||
|
||||
#define INA226_ALERT_MASK 0x06
|
||||
#define INA266_CVRF BIT(3)
|
||||
|
||||
#define INA2XX_MAX_REGISTERS 8
|
||||
|
||||
/* settings - depend on use case */
|
||||
#define INA219_CONFIG_DEFAULT 0x399F /* PGA=8 */
|
||||
#define INA226_CONFIG_DEFAULT 0x4327
|
||||
#define INA226_DEFAULT_AVG 4
|
||||
#define INA226_DEFAULT_IT 1110
|
||||
|
||||
#define INA2XX_RSHUNT_DEFAULT 10000
|
||||
|
||||
/*
|
||||
* bit mask for reading the averaging setting in the configuration register
|
||||
* FIXME: use regmap_fields.
|
||||
*/
|
||||
#define INA2XX_MODE_MASK GENMASK(3, 0)
|
||||
|
||||
#define INA226_AVG_MASK GENMASK(11, 9)
|
||||
#define INA226_SHIFT_AVG(val) ((val) << 9)
|
||||
|
||||
/* Integration time for VBus */
|
||||
#define INA226_ITB_MASK GENMASK(8, 6)
|
||||
#define INA226_SHIFT_ITB(val) ((val) << 6)
|
||||
|
||||
/* Integration time for VShunt */
|
||||
#define INA226_ITS_MASK GENMASK(5, 3)
|
||||
#define INA226_SHIFT_ITS(val) ((val) << 3)
|
||||
|
||||
/* Cosmetic macro giving the sampling period for a full P=UxI cycle */
|
||||
#define SAMPLING_PERIOD(c) ((c->int_time_vbus + c->int_time_vshunt) \
|
||||
* c->avg)
|
||||
|
||||
static bool ina2xx_is_writeable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
return (reg == INA2XX_CONFIG) || (reg > INA2XX_CURRENT);
|
||||
}
|
||||
|
||||
static bool ina2xx_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
return (reg != INA2XX_CONFIG);
|
||||
}
|
||||
|
||||
static inline bool is_signed_reg(unsigned int reg)
|
||||
{
|
||||
return (reg == INA2XX_SHUNT_VOLTAGE) || (reg == INA2XX_CURRENT);
|
||||
}
|
||||
|
||||
static const struct regmap_config ina2xx_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 16,
|
||||
.max_register = INA2XX_MAX_REGISTERS,
|
||||
.writeable_reg = ina2xx_is_writeable_reg,
|
||||
.volatile_reg = ina2xx_is_volatile_reg,
|
||||
};
|
||||
|
||||
enum ina2xx_ids { ina219, ina226 };
|
||||
|
||||
struct ina2xx_config {
|
||||
u16 config_default;
|
||||
int calibration_factor;
|
||||
int shunt_div;
|
||||
int bus_voltage_shift;
|
||||
int bus_voltage_lsb; /* uV */
|
||||
int power_lsb; /* uW */
|
||||
};
|
||||
|
||||
struct ina2xx_chip_info {
|
||||
struct regmap *regmap;
|
||||
struct task_struct *task;
|
||||
const struct ina2xx_config *config;
|
||||
struct mutex state_lock;
|
||||
unsigned int shunt_resistor;
|
||||
int avg;
|
||||
s64 prev_ns; /* track buffer capture time, check for underruns*/
|
||||
int int_time_vbus; /* Bus voltage integration time uS */
|
||||
int int_time_vshunt; /* Shunt voltage integration time uS */
|
||||
bool allow_async_readout;
|
||||
};
|
||||
|
||||
static const struct ina2xx_config ina2xx_config[] = {
|
||||
[ina219] = {
|
||||
.config_default = INA219_CONFIG_DEFAULT,
|
||||
.calibration_factor = 40960000,
|
||||
.shunt_div = 100,
|
||||
.bus_voltage_shift = 3,
|
||||
.bus_voltage_lsb = 4000,
|
||||
.power_lsb = 20000,
|
||||
},
|
||||
[ina226] = {
|
||||
.config_default = INA226_CONFIG_DEFAULT,
|
||||
.calibration_factor = 5120000,
|
||||
.shunt_div = 400,
|
||||
.bus_voltage_shift = 0,
|
||||
.bus_voltage_lsb = 1250,
|
||||
.power_lsb = 25000,
|
||||
},
|
||||
};
|
||||
|
||||
static int ina2xx_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
struct ina2xx_chip_info *chip = iio_priv(indio_dev);
|
||||
unsigned int regval;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = regmap_read(chip->regmap, chan->address, ®val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (is_signed_reg(chan->address))
|
||||
*val = (s16) regval;
|
||||
else
|
||||
*val = regval;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
||||
*val = chip->avg;
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
*val = 0;
|
||||
if (chan->address == INA2XX_SHUNT_VOLTAGE)
|
||||
*val2 = chip->int_time_vshunt;
|
||||
else
|
||||
*val2 = chip->int_time_vbus;
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
/*
|
||||
* Sample freq is read only, it is a consequence of
|
||||
* 1/AVG*(CT_bus+CT_shunt).
|
||||
*/
|
||||
*val = DIV_ROUND_CLOSEST(1000000, SAMPLING_PERIOD(chip));
|
||||
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
switch (chan->address) {
|
||||
case INA2XX_SHUNT_VOLTAGE:
|
||||
/* processed (mV) = raw*1000/shunt_div */
|
||||
*val2 = chip->config->shunt_div;
|
||||
*val = 1000;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
|
||||
case INA2XX_BUS_VOLTAGE:
|
||||
/* processed (mV) = raw*lsb (uV) / (1000 << shift) */
|
||||
*val = chip->config->bus_voltage_lsb;
|
||||
*val2 = 1000 << chip->config->bus_voltage_shift;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
|
||||
case INA2XX_POWER:
|
||||
/* processed (mW) = raw*lsb (uW) / 1000 */
|
||||
*val = chip->config->power_lsb;
|
||||
*val2 = 1000;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
|
||||
case INA2XX_CURRENT:
|
||||
/* processed (mA) = raw (mA) */
|
||||
*val = 1;
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Available averaging rates for ina226. The indices correspond with
|
||||
* the bit values expected by the chip (according to the ina226 datasheet,
|
||||
* table 3 AVG bit settings, found at
|
||||
* http://www.ti.com/lit/ds/symlink/ina226.pdf.
|
||||
*/
|
||||
static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 };
|
||||
|
||||
static int ina226_set_average(struct ina2xx_chip_info *chip, unsigned int val,
|
||||
unsigned int *config)
|
||||
{
|
||||
int bits;
|
||||
|
||||
if (val > 1024 || val < 1)
|
||||
return -EINVAL;
|
||||
|
||||
bits = find_closest(val, ina226_avg_tab,
|
||||
ARRAY_SIZE(ina226_avg_tab));
|
||||
|
||||
chip->avg = ina226_avg_tab[bits];
|
||||
|
||||
*config &= ~INA226_AVG_MASK;
|
||||
*config |= INA226_SHIFT_AVG(bits) & INA226_AVG_MASK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Conversion times in uS */
|
||||
static const int ina226_conv_time_tab[] = { 140, 204, 332, 588, 1100,
|
||||
2116, 4156, 8244 };
|
||||
|
||||
static int ina226_set_int_time_vbus(struct ina2xx_chip_info *chip,
|
||||
unsigned int val_us, unsigned int *config)
|
||||
{
|
||||
int bits;
|
||||
|
||||
if (val_us > 8244 || val_us < 140)
|
||||
return -EINVAL;
|
||||
|
||||
bits = find_closest(val_us, ina226_conv_time_tab,
|
||||
ARRAY_SIZE(ina226_conv_time_tab));
|
||||
|
||||
chip->int_time_vbus = ina226_conv_time_tab[bits];
|
||||
|
||||
*config &= ~INA226_ITB_MASK;
|
||||
*config |= INA226_SHIFT_ITB(bits) & INA226_ITB_MASK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina226_set_int_time_vshunt(struct ina2xx_chip_info *chip,
|
||||
unsigned int val_us, unsigned int *config)
|
||||
{
|
||||
int bits;
|
||||
|
||||
if (val_us > 8244 || val_us < 140)
|
||||
return -EINVAL;
|
||||
|
||||
bits = find_closest(val_us, ina226_conv_time_tab,
|
||||
ARRAY_SIZE(ina226_conv_time_tab));
|
||||
|
||||
chip->int_time_vshunt = ina226_conv_time_tab[bits];
|
||||
|
||||
*config &= ~INA226_ITS_MASK;
|
||||
*config |= INA226_SHIFT_ITS(bits) & INA226_ITS_MASK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina2xx_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct ina2xx_chip_info *chip = iio_priv(indio_dev);
|
||||
int ret;
|
||||
unsigned int config, tmp;
|
||||
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
|
||||
mutex_lock(&chip->state_lock);
|
||||
|
||||
ret = regmap_read(chip->regmap, INA2XX_CONFIG, &config);
|
||||
if (ret < 0)
|
||||
goto _err;
|
||||
|
||||
tmp = config;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
||||
ret = ina226_set_average(chip, val, &tmp);
|
||||
break;
|
||||
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
if (chan->address == INA2XX_SHUNT_VOLTAGE)
|
||||
ret = ina226_set_int_time_vshunt(chip, val2, &tmp);
|
||||
else
|
||||
ret = ina226_set_int_time_vbus(chip, val2, &tmp);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
if (!ret && (tmp != config))
|
||||
ret = regmap_write(chip->regmap, INA2XX_CONFIG, tmp);
|
||||
_err:
|
||||
mutex_unlock(&chip->state_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t ina2xx_allow_async_readout_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev));
|
||||
|
||||
return sprintf(buf, "%d\n", chip->allow_async_readout);
|
||||
}
|
||||
|
||||
static ssize_t ina2xx_allow_async_readout_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev));
|
||||
bool val;
|
||||
int ret;
|
||||
|
||||
ret = strtobool((const char *) buf, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
chip->allow_async_readout = val;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int set_shunt_resistor(struct ina2xx_chip_info *chip, unsigned int val)
|
||||
{
|
||||
if (val <= 0 || val > chip->config->calibration_factor)
|
||||
return -EINVAL;
|
||||
|
||||
chip->shunt_resistor = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t ina2xx_shunt_resistor_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev));
|
||||
|
||||
return sprintf(buf, "%d\n", chip->shunt_resistor);
|
||||
}
|
||||
|
||||
static ssize_t ina2xx_shunt_resistor_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev));
|
||||
unsigned long val;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul((const char *) buf, 10, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = set_shunt_resistor(chip, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#define INA2XX_CHAN(_type, _index, _address) { \
|
||||
.type = (_type), \
|
||||
.address = (_address), \
|
||||
.indexed = 1, \
|
||||
.channel = (_index), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
|
||||
| BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
|
||||
.scan_index = (_index), \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_CPU, \
|
||||
} \
|
||||
}
|
||||
|
||||
/*
|
||||
* Sampling Freq is a consequence of the integration times of
|
||||
* the Voltage channels.
|
||||
*/
|
||||
#define INA2XX_CHAN_VOLTAGE(_index, _address) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.address = (_address), \
|
||||
.indexed = 1, \
|
||||
.channel = (_index), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_INT_TIME), \
|
||||
.scan_index = (_index), \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
} \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec ina2xx_channels[] = {
|
||||
INA2XX_CHAN_VOLTAGE(0, INA2XX_SHUNT_VOLTAGE),
|
||||
INA2XX_CHAN_VOLTAGE(1, INA2XX_BUS_VOLTAGE),
|
||||
INA2XX_CHAN(IIO_POWER, 2, INA2XX_POWER),
|
||||
INA2XX_CHAN(IIO_CURRENT, 3, INA2XX_CURRENT),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(4),
|
||||
};
|
||||
|
||||
static int ina2xx_work_buffer(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct ina2xx_chip_info *chip = iio_priv(indio_dev);
|
||||
unsigned short data[8];
|
||||
int bit, ret, i = 0;
|
||||
unsigned long buffer_us, elapsed_us;
|
||||
s64 time_a, time_b;
|
||||
unsigned int alert;
|
||||
|
||||
time_a = iio_get_time_ns();
|
||||
|
||||
/*
|
||||
* Because the timer thread and the chip conversion clock
|
||||
* are asynchronous, the period difference will eventually
|
||||
* result in reading V[k-1] again, or skip V[k] at time Tk.
|
||||
* In order to resync the timer with the conversion process
|
||||
* we check the ConVersionReadyFlag.
|
||||
* On hardware that supports using the ALERT pin to toggle a
|
||||
* GPIO a triggered buffer could be used instead.
|
||||
* For now, we pay for that extra read of the ALERT register
|
||||
*/
|
||||
if (!chip->allow_async_readout)
|
||||
do {
|
||||
ret = regmap_read(chip->regmap, INA226_ALERT_MASK,
|
||||
&alert);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
alert &= INA266_CVRF;
|
||||
trace_printk("Conversion ready: %d\n", !!alert);
|
||||
|
||||
} while (!alert);
|
||||
|
||||
/*
|
||||
* Single register reads: bulk_read will not work with ina226
|
||||
* as there is no auto-increment of the address register for
|
||||
* data length longer than 16bits.
|
||||
*/
|
||||
for_each_set_bit(bit, indio_dev->active_scan_mask,
|
||||
indio_dev->masklength) {
|
||||
unsigned int val;
|
||||
|
||||
ret = regmap_read(chip->regmap,
|
||||
INA2XX_SHUNT_VOLTAGE + bit, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data[i++] = val;
|
||||
}
|
||||
|
||||
time_b = iio_get_time_ns();
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev,
|
||||
(unsigned int *)data, time_a);
|
||||
|
||||
buffer_us = (unsigned long)(time_b - time_a) / 1000;
|
||||
elapsed_us = (unsigned long)(time_a - chip->prev_ns) / 1000;
|
||||
|
||||
trace_printk("uS: elapsed: %lu, buf: %lu\n", elapsed_us, buffer_us);
|
||||
|
||||
chip->prev_ns = time_a;
|
||||
|
||||
return buffer_us;
|
||||
};
|
||||
|
||||
static int ina2xx_capture_thread(void *data)
|
||||
{
|
||||
struct iio_dev *indio_dev = (struct iio_dev *)data;
|
||||
struct ina2xx_chip_info *chip = iio_priv(indio_dev);
|
||||
unsigned int sampling_us = SAMPLING_PERIOD(chip);
|
||||
int buffer_us;
|
||||
|
||||
/*
|
||||
* Poll a bit faster than the chip internal Fs, in case
|
||||
* we wish to sync with the conversion ready flag.
|
||||
*/
|
||||
if (!chip->allow_async_readout)
|
||||
sampling_us -= 200;
|
||||
|
||||
do {
|
||||
buffer_us = ina2xx_work_buffer(indio_dev);
|
||||
if (buffer_us < 0)
|
||||
return buffer_us;
|
||||
|
||||
if (sampling_us > buffer_us)
|
||||
udelay(sampling_us - buffer_us);
|
||||
|
||||
} while (!kthread_should_stop());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ina2xx_buffer_enable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct ina2xx_chip_info *chip = iio_priv(indio_dev);
|
||||
unsigned int sampling_us = SAMPLING_PERIOD(chip);
|
||||
|
||||
trace_printk("Enabling buffer w/ scan_mask %02x, freq = %d, avg =%u\n",
|
||||
(unsigned int)(*indio_dev->active_scan_mask),
|
||||
1000000/sampling_us, chip->avg);
|
||||
|
||||
trace_printk("Expected work period: %u us\n", sampling_us);
|
||||
trace_printk("Async readout mode: %d\n", chip->allow_async_readout);
|
||||
|
||||
chip->prev_ns = iio_get_time_ns();
|
||||
|
||||
chip->task = kthread_run(ina2xx_capture_thread, (void *)indio_dev,
|
||||
"%s:%d-%uus", indio_dev->name, indio_dev->id,
|
||||
sampling_us);
|
||||
|
||||
return PTR_ERR_OR_ZERO(chip->task);
|
||||
}
|
||||
|
||||
static int ina2xx_buffer_disable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct ina2xx_chip_info *chip = iio_priv(indio_dev);
|
||||
|
||||
if (chip->task) {
|
||||
kthread_stop(chip->task);
|
||||
chip->task = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops ina2xx_setup_ops = {
|
||||
.postenable = &ina2xx_buffer_enable,
|
||||
.predisable = &ina2xx_buffer_disable,
|
||||
};
|
||||
|
||||
static int ina2xx_debug_reg(struct iio_dev *indio_dev,
|
||||
unsigned reg, unsigned writeval, unsigned *readval)
|
||||
{
|
||||
struct ina2xx_chip_info *chip = iio_priv(indio_dev);
|
||||
|
||||
if (!readval)
|
||||
return regmap_write(chip->regmap, reg, writeval);
|
||||
|
||||
return regmap_read(chip->regmap, reg, readval);
|
||||
}
|
||||
|
||||
/* Possible integration times for vshunt and vbus */
|
||||
static IIO_CONST_ATTR_INT_TIME_AVAIL \
|
||||
("0.000140 0.000204 0.000332 0.000588 0.001100 0.002116 0.004156 0.008244");
|
||||
|
||||
static IIO_DEVICE_ATTR(in_allow_async_readout, S_IRUGO | S_IWUSR,
|
||||
ina2xx_allow_async_readout_show,
|
||||
ina2xx_allow_async_readout_store, 0);
|
||||
|
||||
static IIO_DEVICE_ATTR(in_shunt_resistor, S_IRUGO | S_IWUSR,
|
||||
ina2xx_shunt_resistor_show,
|
||||
ina2xx_shunt_resistor_store, 0);
|
||||
|
||||
static struct attribute *ina2xx_attributes[] = {
|
||||
&iio_dev_attr_in_allow_async_readout.dev_attr.attr,
|
||||
&iio_const_attr_integration_time_available.dev_attr.attr,
|
||||
&iio_dev_attr_in_shunt_resistor.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group ina2xx_attribute_group = {
|
||||
.attrs = ina2xx_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_info ina2xx_info = {
|
||||
.debugfs_reg_access = &ina2xx_debug_reg,
|
||||
.read_raw = &ina2xx_read_raw,
|
||||
.write_raw = &ina2xx_write_raw,
|
||||
.attrs = &ina2xx_attribute_group,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
/* Initialize the configuration and calibration registers. */
|
||||
static int ina2xx_init(struct ina2xx_chip_info *chip, unsigned int config)
|
||||
{
|
||||
u16 regval;
|
||||
int ret = regmap_write(chip->regmap, INA2XX_CONFIG, config);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/*
|
||||
* Set current LSB to 1mA, shunt is in uOhms
|
||||
* (equation 13 in datasheet). We hardcode a Current_LSB
|
||||
* of 1.0 x10-6. The only remaining parameter is RShunt.
|
||||
* There is no need to expose the CALIBRATION register
|
||||
* to the user for now.
|
||||
*/
|
||||
regval = DIV_ROUND_CLOSEST(chip->config->calibration_factor,
|
||||
chip->shunt_resistor);
|
||||
|
||||
return regmap_write(chip->regmap, INA2XX_CALIBRATION, regval);
|
||||
}
|
||||
|
||||
static int ina2xx_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct ina2xx_chip_info *chip;
|
||||
struct iio_dev *indio_dev;
|
||||
struct iio_buffer *buffer;
|
||||
int ret;
|
||||
unsigned int val;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
chip = iio_priv(indio_dev);
|
||||
|
||||
chip->config = &ina2xx_config[id->driver_data];
|
||||
|
||||
if (of_property_read_u32(client->dev.of_node,
|
||||
"shunt-resistor", &val) < 0) {
|
||||
struct ina2xx_platform_data *pdata =
|
||||
dev_get_platdata(&client->dev);
|
||||
|
||||
if (pdata)
|
||||
val = pdata->shunt_uohms;
|
||||
else
|
||||
val = INA2XX_RSHUNT_DEFAULT;
|
||||
}
|
||||
|
||||
ret = set_shunt_resistor(chip, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_init(&chip->state_lock);
|
||||
|
||||
/* This is only used for device removal purposes. */
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
indio_dev->name = id->name;
|
||||
indio_dev->channels = ina2xx_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(ina2xx_channels);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &ina2xx_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
|
||||
|
||||
chip->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config);
|
||||
if (IS_ERR(chip->regmap)) {
|
||||
dev_err(&client->dev, "failed to allocate register map\n");
|
||||
return PTR_ERR(chip->regmap);
|
||||
}
|
||||
|
||||
/* Patch the current config register with default. */
|
||||
val = chip->config->config_default;
|
||||
|
||||
if (id->driver_data == ina226) {
|
||||
ina226_set_average(chip, INA226_DEFAULT_AVG, &val);
|
||||
ina226_set_int_time_vbus(chip, INA226_DEFAULT_IT, &val);
|
||||
ina226_set_int_time_vshunt(chip, INA226_DEFAULT_IT, &val);
|
||||
}
|
||||
|
||||
ret = ina2xx_init(chip, val);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "error configuring the device: %d\n",
|
||||
ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
buffer = devm_iio_kfifo_allocate(&indio_dev->dev);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
indio_dev->setup_ops = &ina2xx_setup_ops;
|
||||
|
||||
iio_device_attach_buffer(indio_dev, buffer);
|
||||
|
||||
return iio_device_register(indio_dev);
|
||||
}
|
||||
|
||||
|
||||
static int ina2xx_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct ina2xx_chip_info *chip = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
/* Powerdown */
|
||||
return regmap_update_bits(chip->regmap, INA2XX_CONFIG,
|
||||
INA2XX_MODE_MASK, 0);
|
||||
}
|
||||
|
||||
|
||||
static const struct i2c_device_id ina2xx_id[] = {
|
||||
{"ina219", ina219},
|
||||
{"ina220", ina219},
|
||||
{"ina226", ina226},
|
||||
{"ina230", ina226},
|
||||
{"ina231", ina226},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, ina2xx_id);
|
||||
|
||||
static struct i2c_driver ina2xx_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
.probe = ina2xx_probe,
|
||||
.remove = ina2xx_remove,
|
||||
.id_table = ina2xx_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(ina2xx_driver);
|
||||
|
||||
MODULE_AUTHOR("Marc Titinger <marc.titinger@baylibre.com>");
|
||||
MODULE_DESCRIPTION("Texas Instruments INA2XX ADC driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -354,6 +354,7 @@ static int mcp320x_remove(struct spi_device *spi)
|
|||
|
||||
#if defined(CONFIG_OF)
|
||||
static const struct of_device_id mcp320x_dt_ids[] = {
|
||||
/* NOTE: The use of compatibles with no vendor prefix is deprecated. */
|
||||
{
|
||||
.compatible = "mcp3001",
|
||||
.data = &mcp320x_chip_infos[mcp3001],
|
||||
|
@ -381,6 +382,33 @@ static const struct of_device_id mcp320x_dt_ids[] = {
|
|||
}, {
|
||||
.compatible = "mcp3301",
|
||||
.data = &mcp320x_chip_infos[mcp3301],
|
||||
}, {
|
||||
.compatible = "microchip,mcp3001",
|
||||
.data = &mcp320x_chip_infos[mcp3001],
|
||||
}, {
|
||||
.compatible = "microchip,mcp3002",
|
||||
.data = &mcp320x_chip_infos[mcp3002],
|
||||
}, {
|
||||
.compatible = "microchip,mcp3004",
|
||||
.data = &mcp320x_chip_infos[mcp3004],
|
||||
}, {
|
||||
.compatible = "microchip,mcp3008",
|
||||
.data = &mcp320x_chip_infos[mcp3008],
|
||||
}, {
|
||||
.compatible = "microchip,mcp3201",
|
||||
.data = &mcp320x_chip_infos[mcp3201],
|
||||
}, {
|
||||
.compatible = "microchip,mcp3202",
|
||||
.data = &mcp320x_chip_infos[mcp3202],
|
||||
}, {
|
||||
.compatible = "microchip,mcp3204",
|
||||
.data = &mcp320x_chip_infos[mcp3204],
|
||||
}, {
|
||||
.compatible = "microchip,mcp3208",
|
||||
.data = &mcp320x_chip_infos[mcp3208],
|
||||
}, {
|
||||
.compatible = "microchip,mcp3301",
|
||||
.data = &mcp320x_chip_infos[mcp3301],
|
||||
}, {
|
||||
}
|
||||
};
|
||||
|
|
|
@ -305,6 +305,10 @@ static const struct attribute_group mcp3422_attribute_group = {
|
|||
.attrs = mcp3422_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec mcp3421_channels[] = {
|
||||
MCP3422_CHAN(0),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec mcp3422_channels[] = {
|
||||
MCP3422_CHAN(0),
|
||||
MCP3422_CHAN(1),
|
||||
|
@ -352,6 +356,10 @@ static int mcp3422_probe(struct i2c_client *client,
|
|||
indio_dev->info = &mcp3422_info;
|
||||
|
||||
switch (adc->id) {
|
||||
case 1:
|
||||
indio_dev->channels = mcp3421_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(mcp3421_channels);
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
case 6:
|
||||
|
@ -383,6 +391,7 @@ static int mcp3422_probe(struct i2c_client *client,
|
|||
}
|
||||
|
||||
static const struct i2c_device_id mcp3422_id[] = {
|
||||
{ "mcp3421", 1 },
|
||||
{ "mcp3422", 2 },
|
||||
{ "mcp3423", 3 },
|
||||
{ "mcp3424", 4 },
|
||||
|
|
|
@ -0,0 +1,859 @@
|
|||
/*
|
||||
* palmas-adc.c -- TI PALMAS GPADC.
|
||||
*
|
||||
* Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
|
||||
*
|
||||
* Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation version 2.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/mfd/palmas.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/machine.h>
|
||||
#include <linux/iio/driver.h>
|
||||
|
||||
#define MOD_NAME "palmas-gpadc"
|
||||
#define PALMAS_ADC_CONVERSION_TIMEOUT (msecs_to_jiffies(5000))
|
||||
#define PALMAS_TO_BE_CALCULATED 0
|
||||
#define PALMAS_GPADC_TRIMINVALID -1
|
||||
|
||||
struct palmas_gpadc_info {
|
||||
/* calibration codes and regs */
|
||||
int x1; /* lower ideal code */
|
||||
int x2; /* higher ideal code */
|
||||
int v1; /* expected lower volt reading */
|
||||
int v2; /* expected higher volt reading */
|
||||
u8 trim1_reg; /* register number for lower trim */
|
||||
u8 trim2_reg; /* register number for upper trim */
|
||||
int gain; /* calculated from above (after reading trim regs) */
|
||||
int offset; /* calculated from above (after reading trim regs) */
|
||||
int gain_error; /* calculated from above (after reading trim regs) */
|
||||
bool is_uncalibrated; /* if channel has calibration data */
|
||||
};
|
||||
|
||||
#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_uncalibrated) \
|
||||
[PALMAS_ADC_CH_##_chan] = { \
|
||||
.x1 = _x1, \
|
||||
.x2 = _x2, \
|
||||
.v1 = _v1, \
|
||||
.v2 = _v2, \
|
||||
.gain = PALMAS_TO_BE_CALCULATED, \
|
||||
.offset = PALMAS_TO_BE_CALCULATED, \
|
||||
.gain_error = PALMAS_TO_BE_CALCULATED, \
|
||||
.trim1_reg = PALMAS_GPADC_TRIM##_t1, \
|
||||
.trim2_reg = PALMAS_GPADC_TRIM##_t2, \
|
||||
.is_uncalibrated = _is_uncalibrated \
|
||||
}
|
||||
|
||||
static struct palmas_gpadc_info palmas_gpadc_info[] = {
|
||||
PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
|
||||
PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
|
||||
PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
|
||||
PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
|
||||
PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
|
||||
PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
|
||||
PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
|
||||
PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
|
||||
PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
|
||||
PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
|
||||
PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
|
||||
PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
|
||||
PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
|
||||
PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
|
||||
PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
|
||||
PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
|
||||
};
|
||||
|
||||
/**
|
||||
* struct palmas_gpadc - the palmas_gpadc structure
|
||||
* @ch0_current: channel 0 current source setting
|
||||
* 0: 0 uA
|
||||
* 1: 5 uA
|
||||
* 2: 15 uA
|
||||
* 3: 20 uA
|
||||
* @ch3_current: channel 0 current source setting
|
||||
* 0: 0 uA
|
||||
* 1: 10 uA
|
||||
* 2: 400 uA
|
||||
* 3: 800 uA
|
||||
* @extended_delay: enable the gpadc extended delay mode
|
||||
* @auto_conversion_period: define the auto_conversion_period
|
||||
*
|
||||
* This is the palmas_gpadc structure to store run-time information
|
||||
* and pointers for this driver instance.
|
||||
*/
|
||||
|
||||
struct palmas_gpadc {
|
||||
struct device *dev;
|
||||
struct palmas *palmas;
|
||||
u8 ch0_current;
|
||||
u8 ch3_current;
|
||||
bool extended_delay;
|
||||
int irq;
|
||||
int irq_auto_0;
|
||||
int irq_auto_1;
|
||||
struct palmas_gpadc_info *adc_info;
|
||||
struct completion conv_completion;
|
||||
struct palmas_adc_wakeup_property wakeup1_data;
|
||||
struct palmas_adc_wakeup_property wakeup2_data;
|
||||
bool wakeup1_enable;
|
||||
bool wakeup2_enable;
|
||||
int auto_conversion_period;
|
||||
};
|
||||
|
||||
/*
|
||||
* GPADC lock issue in AUTO mode.
|
||||
* Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
|
||||
* mode feature.
|
||||
* Details:
|
||||
* When the AUTO mode is the only conversion mode enabled, if the AUTO
|
||||
* mode feature is disabled with bit GPADC_AUTO_CTRL. AUTO_CONV1_EN = 0
|
||||
* or bit GPADC_AUTO_CTRL. AUTO_CONV0_EN = 0 during a conversion, the
|
||||
* conversion mechanism can be seen as locked meaning that all following
|
||||
* conversion will give 0 as a result. Bit GPADC_STATUS.GPADC_AVAILABLE
|
||||
* will stay at 0 meaning that GPADC is busy. An RT conversion can unlock
|
||||
* the GPADC.
|
||||
*
|
||||
* Workaround(s):
|
||||
* To avoid the lock mechanism, the workaround to follow before any stop
|
||||
* conversion request is:
|
||||
* Force the GPADC state machine to be ON by using the GPADC_CTRL1.
|
||||
* GPADC_FORCE bit = 1
|
||||
* Shutdown the GPADC AUTO conversion using
|
||||
* GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
|
||||
* After 100us, force the GPADC state machine to be OFF by using the
|
||||
* GPADC_CTRL1. GPADC_FORCE bit = 0
|
||||
*/
|
||||
|
||||
static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_CTRL1,
|
||||
PALMAS_GPADC_CTRL1_GPADC_FORCE,
|
||||
PALMAS_GPADC_CTRL1_GPADC_FORCE);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_AUTO_CTRL,
|
||||
PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
|
||||
PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
|
||||
0);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
udelay(100);
|
||||
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_CTRL1,
|
||||
PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
|
||||
if (ret < 0)
|
||||
dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static irqreturn_t palmas_gpadc_irq(int irq, void *data)
|
||||
{
|
||||
struct palmas_gpadc *adc = data;
|
||||
|
||||
complete(&adc->conv_completion);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
|
||||
{
|
||||
struct palmas_gpadc *adc = data;
|
||||
|
||||
dev_dbg(adc->dev, "Threshold interrupt %d occurs\n", irq);
|
||||
palmas_disable_auto_conversion(adc);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc,
|
||||
bool mask)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!mask)
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
|
||||
PALMAS_INT3_MASK,
|
||||
PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
|
||||
else
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
|
||||
PALMAS_INT3_MASK,
|
||||
PALMAS_INT3_MASK_GPADC_EOC_SW,
|
||||
PALMAS_INT3_MASK_GPADC_EOC_SW);
|
||||
if (ret < 0)
|
||||
dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
|
||||
int enable)
|
||||
{
|
||||
unsigned int mask, val;
|
||||
int ret;
|
||||
|
||||
if (enable) {
|
||||
val = (adc->extended_delay
|
||||
<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_RT_CTRL,
|
||||
PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
|
||||
PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
|
||||
PALMAS_GPADC_CTRL1_GPADC_FORCE);
|
||||
val = (adc->ch0_current
|
||||
<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
|
||||
val |= (adc->ch3_current
|
||||
<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
|
||||
val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_CTRL1, mask, val);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev,
|
||||
"Failed to update current setting: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
|
||||
PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
|
||||
val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_SW_SELECT, mask, val);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_SW_SELECT, 0);
|
||||
if (ret < 0)
|
||||
dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
|
||||
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_CTRL1,
|
||||
PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = palmas_gpadc_enable(adc, adc_chan, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return palmas_gpadc_start_mask_interrupt(adc, 0);
|
||||
}
|
||||
|
||||
static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
|
||||
{
|
||||
palmas_gpadc_start_mask_interrupt(adc, 1);
|
||||
palmas_gpadc_enable(adc, adc_chan, false);
|
||||
}
|
||||
|
||||
static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
|
||||
{
|
||||
int k;
|
||||
int d1;
|
||||
int d2;
|
||||
int ret;
|
||||
int gain;
|
||||
int x1 = adc->adc_info[adc_chan].x1;
|
||||
int x2 = adc->adc_info[adc_chan].x2;
|
||||
int v1 = adc->adc_info[adc_chan].v1;
|
||||
int v2 = adc->adc_info[adc_chan].v2;
|
||||
|
||||
ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
|
||||
adc->adc_info[adc_chan].trim1_reg, &d1);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "TRIM read failed: %d\n", ret);
|
||||
goto scrub;
|
||||
}
|
||||
|
||||
ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
|
||||
adc->adc_info[adc_chan].trim2_reg, &d2);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "TRIM read failed: %d\n", ret);
|
||||
goto scrub;
|
||||
}
|
||||
|
||||
/* gain error calculation */
|
||||
k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
|
||||
|
||||
/* gain calculation */
|
||||
gain = ((v2 - v1) * 1000) / (x2 - x1);
|
||||
|
||||
adc->adc_info[adc_chan].gain_error = k;
|
||||
adc->adc_info[adc_chan].gain = gain;
|
||||
/* offset Calculation */
|
||||
adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
|
||||
|
||||
scrub:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
init_completion(&adc->conv_completion);
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_SW_SELECT,
|
||||
PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
|
||||
PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "SELECT_SW_START write failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = wait_for_completion_timeout(&adc->conv_completion,
|
||||
PALMAS_ADC_CONVERSION_TIMEOUT);
|
||||
if (ret == 0) {
|
||||
dev_err(adc->dev, "conversion not completed\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "SW_CONV0_LSB read failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = val & 0xFFF;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
|
||||
int adc_chan, int val)
|
||||
{
|
||||
if (!adc->adc_info[adc_chan].is_uncalibrated)
|
||||
val = (val*1000 - adc->adc_info[adc_chan].offset) /
|
||||
adc->adc_info[adc_chan].gain_error;
|
||||
|
||||
if (val < 0) {
|
||||
dev_err(adc->dev, "Mismatch with calibration\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
val = (val * adc->adc_info[adc_chan].gain) / 1000;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val, int *val2, long mask)
|
||||
{
|
||||
struct palmas_gpadc *adc = iio_priv(indio_dev);
|
||||
int adc_chan = chan->channel;
|
||||
int ret = 0;
|
||||
|
||||
if (adc_chan > PALMAS_ADC_CH_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&indio_dev->mlock);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
case IIO_CHAN_INFO_PROCESSED:
|
||||
ret = palmas_gpadc_read_prepare(adc, adc_chan);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = palmas_gpadc_start_conversion(adc, adc_chan);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev,
|
||||
"ADC start conversion failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (mask == IIO_CHAN_INFO_PROCESSED)
|
||||
ret = palmas_gpadc_get_calibrated_code(
|
||||
adc, adc_chan, ret);
|
||||
|
||||
*val = ret;
|
||||
|
||||
ret = IIO_VAL_INT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
return ret;
|
||||
|
||||
out:
|
||||
palmas_gpadc_read_done(adc, adc_chan);
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info palmas_gpadc_iio_info = {
|
||||
.read_raw = palmas_gpadc_read_raw,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
#define PALMAS_ADC_CHAN_IIO(chan, _type, chan_info) \
|
||||
{ \
|
||||
.datasheet_name = PALMAS_DATASHEET_NAME(chan), \
|
||||
.type = _type, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(chan_info), \
|
||||
.indexed = 1, \
|
||||
.channel = PALMAS_ADC_CH_##chan, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
|
||||
PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN1, IIO_TEMP, IIO_CHAN_INFO_RAW),
|
||||
PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN3, IIO_TEMP, IIO_CHAN_INFO_RAW),
|
||||
PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN12, IIO_TEMP, IIO_CHAN_INFO_RAW),
|
||||
PALMAS_ADC_CHAN_IIO(IN13, IIO_TEMP, IIO_CHAN_INFO_RAW),
|
||||
PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
|
||||
};
|
||||
|
||||
static int palmas_gpadc_get_adc_dt_data(struct platform_device *pdev,
|
||||
struct palmas_gpadc_platform_data **gpadc_pdata)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct palmas_gpadc_platform_data *gp_data;
|
||||
int ret;
|
||||
u32 pval;
|
||||
|
||||
gp_data = devm_kzalloc(&pdev->dev, sizeof(*gp_data), GFP_KERNEL);
|
||||
if (!gp_data)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = of_property_read_u32(np, "ti,channel0-current-microamp", &pval);
|
||||
if (!ret)
|
||||
gp_data->ch0_current = pval;
|
||||
|
||||
ret = of_property_read_u32(np, "ti,channel3-current-microamp", &pval);
|
||||
if (!ret)
|
||||
gp_data->ch3_current = pval;
|
||||
|
||||
gp_data->extended_delay = of_property_read_bool(np,
|
||||
"ti,enable-extended-delay");
|
||||
|
||||
*gpadc_pdata = gp_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int palmas_gpadc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct palmas_gpadc *adc;
|
||||
struct palmas_platform_data *pdata;
|
||||
struct palmas_gpadc_platform_data *gpadc_pdata = NULL;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret, i;
|
||||
|
||||
pdata = dev_get_platdata(pdev->dev.parent);
|
||||
|
||||
if (pdata && pdata->gpadc_pdata)
|
||||
gpadc_pdata = pdata->gpadc_pdata;
|
||||
|
||||
if (!gpadc_pdata && pdev->dev.of_node) {
|
||||
ret = palmas_gpadc_get_adc_dt_data(pdev, &gpadc_pdata);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
if (!gpadc_pdata)
|
||||
return -EINVAL;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
|
||||
if (!indio_dev) {
|
||||
dev_err(&pdev->dev, "iio_device_alloc failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
adc = iio_priv(indio_dev);
|
||||
adc->dev = &pdev->dev;
|
||||
adc->palmas = dev_get_drvdata(pdev->dev.parent);
|
||||
adc->adc_info = palmas_gpadc_info;
|
||||
init_completion(&adc->conv_completion);
|
||||
dev_set_drvdata(&pdev->dev, indio_dev);
|
||||
|
||||
adc->auto_conversion_period = gpadc_pdata->auto_conversion_period_ms;
|
||||
adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
|
||||
if (adc->irq < 0) {
|
||||
dev_err(adc->dev,
|
||||
"get virq failed: %d\n", adc->irq);
|
||||
ret = adc->irq;
|
||||
goto out;
|
||||
}
|
||||
ret = request_threaded_irq(adc->irq, NULL,
|
||||
palmas_gpadc_irq,
|
||||
IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
|
||||
adc);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev,
|
||||
"request irq %d failed: %d\n", adc->irq, ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (gpadc_pdata->adc_wakeup1_data) {
|
||||
memcpy(&adc->wakeup1_data, gpadc_pdata->adc_wakeup1_data,
|
||||
sizeof(adc->wakeup1_data));
|
||||
adc->wakeup1_enable = true;
|
||||
adc->irq_auto_0 = platform_get_irq(pdev, 1);
|
||||
ret = request_threaded_irq(adc->irq_auto_0, NULL,
|
||||
palmas_gpadc_irq_auto,
|
||||
IRQF_ONESHOT | IRQF_EARLY_RESUME,
|
||||
"palmas-adc-auto-0", adc);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "request auto0 irq %d failed: %d\n",
|
||||
adc->irq_auto_0, ret);
|
||||
goto out_irq_free;
|
||||
}
|
||||
}
|
||||
|
||||
if (gpadc_pdata->adc_wakeup2_data) {
|
||||
memcpy(&adc->wakeup2_data, gpadc_pdata->adc_wakeup2_data,
|
||||
sizeof(adc->wakeup2_data));
|
||||
adc->wakeup2_enable = true;
|
||||
adc->irq_auto_1 = platform_get_irq(pdev, 2);
|
||||
ret = request_threaded_irq(adc->irq_auto_1, NULL,
|
||||
palmas_gpadc_irq_auto,
|
||||
IRQF_ONESHOT | IRQF_EARLY_RESUME,
|
||||
"palmas-adc-auto-1", adc);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "request auto1 irq %d failed: %d\n",
|
||||
adc->irq_auto_1, ret);
|
||||
goto out_irq_auto0_free;
|
||||
}
|
||||
}
|
||||
|
||||
/* set the current source 0 (value 0/5/15/20 uA => 0..3) */
|
||||
if (gpadc_pdata->ch0_current <= 1)
|
||||
adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
|
||||
else if (gpadc_pdata->ch0_current <= 5)
|
||||
adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
|
||||
else if (gpadc_pdata->ch0_current <= 15)
|
||||
adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
|
||||
else
|
||||
adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
|
||||
|
||||
/* set the current source 3 (value 0/10/400/800 uA => 0..3) */
|
||||
if (gpadc_pdata->ch3_current <= 1)
|
||||
adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
|
||||
else if (gpadc_pdata->ch3_current <= 10)
|
||||
adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
|
||||
else if (gpadc_pdata->ch3_current <= 400)
|
||||
adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
|
||||
else
|
||||
adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
|
||||
|
||||
adc->extended_delay = gpadc_pdata->extended_delay;
|
||||
|
||||
indio_dev->name = MOD_NAME;
|
||||
indio_dev->dev.parent = &pdev->dev;
|
||||
indio_dev->info = &palmas_gpadc_iio_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = palmas_gpadc_iio_channel;
|
||||
indio_dev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
|
||||
goto out_irq_auto1_free;
|
||||
}
|
||||
|
||||
device_set_wakeup_capable(&pdev->dev, 1);
|
||||
for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
|
||||
if (!(adc->adc_info[i].is_uncalibrated))
|
||||
palmas_gpadc_calibrate(adc, i);
|
||||
}
|
||||
|
||||
if (adc->wakeup1_enable || adc->wakeup2_enable)
|
||||
device_wakeup_enable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
out_irq_auto1_free:
|
||||
if (gpadc_pdata->adc_wakeup2_data)
|
||||
free_irq(adc->irq_auto_1, adc);
|
||||
out_irq_auto0_free:
|
||||
if (gpadc_pdata->adc_wakeup1_data)
|
||||
free_irq(adc->irq_auto_0, adc);
|
||||
out_irq_free:
|
||||
free_irq(adc->irq, adc);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int palmas_gpadc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(&pdev->dev);
|
||||
struct palmas_gpadc *adc = iio_priv(indio_dev);
|
||||
|
||||
if (adc->wakeup1_enable || adc->wakeup2_enable)
|
||||
device_wakeup_disable(&pdev->dev);
|
||||
iio_device_unregister(indio_dev);
|
||||
free_irq(adc->irq, adc);
|
||||
if (adc->wakeup1_enable)
|
||||
free_irq(adc->irq_auto_0, adc);
|
||||
if (adc->wakeup2_enable)
|
||||
free_irq(adc->irq_auto_1, adc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
|
||||
{
|
||||
int adc_period, conv;
|
||||
int i;
|
||||
int ch0 = 0, ch1 = 0;
|
||||
int thres;
|
||||
int ret;
|
||||
|
||||
adc_period = adc->auto_conversion_period;
|
||||
for (i = 0; i < 16; ++i) {
|
||||
if (((1000 * (1 << i)) / 32) < adc_period)
|
||||
continue;
|
||||
}
|
||||
if (i > 0)
|
||||
i--;
|
||||
adc_period = i;
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_AUTO_CTRL,
|
||||
PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
|
||||
adc_period);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
conv = 0;
|
||||
if (adc->wakeup1_enable) {
|
||||
int polarity;
|
||||
|
||||
ch0 = adc->wakeup1_data.adc_channel_number;
|
||||
conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
|
||||
if (adc->wakeup1_data.adc_high_threshold > 0) {
|
||||
thres = adc->wakeup1_data.adc_high_threshold;
|
||||
polarity = 0;
|
||||
} else {
|
||||
thres = adc->wakeup1_data.adc_low_threshold;
|
||||
polarity = PALMAS_GPADC_THRES_CONV0_MSB_THRES_CONV0_POL;
|
||||
}
|
||||
|
||||
ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev,
|
||||
"THRES_CONV0_LSB write failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_THRES_CONV0_MSB,
|
||||
((thres >> 8) & 0xF) | polarity);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev,
|
||||
"THRES_CONV0_MSB write failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (adc->wakeup2_enable) {
|
||||
int polarity;
|
||||
|
||||
ch1 = adc->wakeup2_data.adc_channel_number;
|
||||
conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
|
||||
if (adc->wakeup2_data.adc_high_threshold > 0) {
|
||||
thres = adc->wakeup2_data.adc_high_threshold;
|
||||
polarity = 0;
|
||||
} else {
|
||||
thres = adc->wakeup2_data.adc_low_threshold;
|
||||
polarity = PALMAS_GPADC_THRES_CONV1_MSB_THRES_CONV1_POL;
|
||||
}
|
||||
|
||||
ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev,
|
||||
"THRES_CONV1_LSB write failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_THRES_CONV1_MSB,
|
||||
((thres >> 8) & 0xF) | polarity);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev,
|
||||
"THRES_CONV1_MSB write failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_AUTO_CTRL,
|
||||
PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
|
||||
PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
|
||||
if (ret < 0)
|
||||
dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
|
||||
PALMAS_GPADC_AUTO_SELECT, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = palmas_disable_auto_conversion(adc);
|
||||
if (ret < 0)
|
||||
dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int palmas_gpadc_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct palmas_gpadc *adc = iio_priv(indio_dev);
|
||||
int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
|
||||
int ret;
|
||||
|
||||
if (!device_may_wakeup(dev) || !wakeup)
|
||||
return 0;
|
||||
|
||||
ret = palmas_adc_wakeup_configure(adc);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (adc->wakeup1_enable)
|
||||
enable_irq_wake(adc->irq_auto_0);
|
||||
|
||||
if (adc->wakeup2_enable)
|
||||
enable_irq_wake(adc->irq_auto_1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int palmas_gpadc_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct palmas_gpadc *adc = iio_priv(indio_dev);
|
||||
int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
|
||||
int ret;
|
||||
|
||||
if (!device_may_wakeup(dev) || !wakeup)
|
||||
return 0;
|
||||
|
||||
ret = palmas_adc_wakeup_reset(adc);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (adc->wakeup1_enable)
|
||||
disable_irq_wake(adc->irq_auto_0);
|
||||
|
||||
if (adc->wakeup2_enable)
|
||||
disable_irq_wake(adc->irq_auto_1);
|
||||
|
||||
return 0;
|
||||
};
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops palmas_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
|
||||
palmas_gpadc_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id of_palmas_gpadc_match_tbl[] = {
|
||||
{ .compatible = "ti,palmas-gpadc", },
|
||||
{ /* end */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_palmas_gpadc_match_tbl);
|
||||
|
||||
static struct platform_driver palmas_gpadc_driver = {
|
||||
.probe = palmas_gpadc_probe,
|
||||
.remove = palmas_gpadc_remove,
|
||||
.driver = {
|
||||
.name = MOD_NAME,
|
||||
.pm = &palmas_pm_ops,
|
||||
.of_match_table = of_palmas_gpadc_match_tbl,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init palmas_gpadc_init(void)
|
||||
{
|
||||
return platform_driver_register(&palmas_gpadc_driver);
|
||||
}
|
||||
module_init(palmas_gpadc_init);
|
||||
|
||||
static void __exit palmas_gpadc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&palmas_gpadc_driver);
|
||||
}
|
||||
module_exit(palmas_gpadc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("palmas GPADC driver");
|
||||
MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
|
||||
MODULE_ALIAS("platform:palmas-gpadc");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -1,10 +1,11 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Angelo Compagnucci <angelo.compagnucci@gmail.com>
|
||||
*
|
||||
* Driver for Texas Instruments' ADC128S052 and ADC122S021 ADC chip.
|
||||
* Driver for Texas Instruments' ADC128S052, ADC122S021 and ADC124S021 ADC chip.
|
||||
* Datasheets can be found here:
|
||||
* http://www.ti.com/lit/ds/symlink/adc128s052.pdf
|
||||
* http://www.ti.com/lit/ds/symlink/adc122s021.pdf
|
||||
* http://www.ti.com/lit/ds/symlink/adc124s021.pdf
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
|
@ -114,9 +115,17 @@ static const struct iio_chan_spec adc122s021_channels[] = {
|
|||
ADC128_VOLTAGE_CHANNEL(1),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec adc124s021_channels[] = {
|
||||
ADC128_VOLTAGE_CHANNEL(0),
|
||||
ADC128_VOLTAGE_CHANNEL(1),
|
||||
ADC128_VOLTAGE_CHANNEL(2),
|
||||
ADC128_VOLTAGE_CHANNEL(3),
|
||||
};
|
||||
|
||||
static const struct adc128_configuration adc128_config[] = {
|
||||
{ adc128s052_channels, ARRAY_SIZE(adc128s052_channels) },
|
||||
{ adc122s021_channels, ARRAY_SIZE(adc122s021_channels) },
|
||||
{ adc124s021_channels, ARRAY_SIZE(adc124s021_channels) },
|
||||
};
|
||||
|
||||
static const struct iio_info adc128_info = {
|
||||
|
@ -177,6 +186,7 @@ static int adc128_remove(struct spi_device *spi)
|
|||
static const struct of_device_id adc128_of_match[] = {
|
||||
{ .compatible = "ti,adc128s052", },
|
||||
{ .compatible = "ti,adc122s021", },
|
||||
{ .compatible = "ti,adc124s021", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adc128_of_match);
|
||||
|
@ -184,6 +194,7 @@ MODULE_DEVICE_TABLE(of, adc128_of_match);
|
|||
static const struct spi_device_id adc128_id[] = {
|
||||
{ "adc128s052", 0}, /* index into adc128_config */
|
||||
{ "adc122s021", 1},
|
||||
{ "adc124s021", 2},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, adc128_id);
|
||||
|
|
|
@ -0,0 +1,486 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Prevas A/S
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
||||
#define ADS8688_CMD_REG(x) (x << 8)
|
||||
#define ADS8688_CMD_REG_NOOP 0x00
|
||||
#define ADS8688_CMD_REG_RST 0x85
|
||||
#define ADS8688_CMD_REG_MAN_CH(chan) (0xC0 | (4 * chan))
|
||||
#define ADS8688_CMD_DONT_CARE_BITS 16
|
||||
|
||||
#define ADS8688_PROG_REG(x) (x << 9)
|
||||
#define ADS8688_PROG_REG_RANGE_CH(chan) (0x05 + chan)
|
||||
#define ADS8688_PROG_WR_BIT BIT(8)
|
||||
#define ADS8688_PROG_DONT_CARE_BITS 8
|
||||
|
||||
#define ADS8688_REG_PLUSMINUS25VREF 0
|
||||
#define ADS8688_REG_PLUSMINUS125VREF 1
|
||||
#define ADS8688_REG_PLUSMINUS0625VREF 2
|
||||
#define ADS8688_REG_PLUS25VREF 5
|
||||
#define ADS8688_REG_PLUS125VREF 6
|
||||
|
||||
#define ADS8688_VREF_MV 4096
|
||||
#define ADS8688_REALBITS 16
|
||||
|
||||
/*
|
||||
* enum ads8688_range - ADS8688 reference voltage range
|
||||
* @ADS8688_PLUSMINUS25VREF: Device is configured for input range ±2.5 * VREF
|
||||
* @ADS8688_PLUSMINUS125VREF: Device is configured for input range ±1.25 * VREF
|
||||
* @ADS8688_PLUSMINUS0625VREF: Device is configured for input range ±0.625 * VREF
|
||||
* @ADS8688_PLUS25VREF: Device is configured for input range 0 - 2.5 * VREF
|
||||
* @ADS8688_PLUS125VREF: Device is configured for input range 0 - 1.25 * VREF
|
||||
*/
|
||||
enum ads8688_range {
|
||||
ADS8688_PLUSMINUS25VREF,
|
||||
ADS8688_PLUSMINUS125VREF,
|
||||
ADS8688_PLUSMINUS0625VREF,
|
||||
ADS8688_PLUS25VREF,
|
||||
ADS8688_PLUS125VREF,
|
||||
};
|
||||
|
||||
struct ads8688_chip_info {
|
||||
const struct iio_chan_spec *channels;
|
||||
unsigned int num_channels;
|
||||
};
|
||||
|
||||
struct ads8688_state {
|
||||
struct mutex lock;
|
||||
const struct ads8688_chip_info *chip_info;
|
||||
struct spi_device *spi;
|
||||
struct regulator *reg;
|
||||
unsigned int vref_mv;
|
||||
enum ads8688_range range[8];
|
||||
union {
|
||||
__be32 d32;
|
||||
u8 d8[4];
|
||||
} data[2] ____cacheline_aligned;
|
||||
};
|
||||
|
||||
enum ads8688_id {
|
||||
ID_ADS8684,
|
||||
ID_ADS8688,
|
||||
};
|
||||
|
||||
struct ads8688_ranges {
|
||||
enum ads8688_range range;
|
||||
unsigned int scale;
|
||||
int offset;
|
||||
u8 reg;
|
||||
};
|
||||
|
||||
static const struct ads8688_ranges ads8688_range_def[5] = {
|
||||
{
|
||||
.range = ADS8688_PLUSMINUS25VREF,
|
||||
.scale = 76295,
|
||||
.offset = -(1 << (ADS8688_REALBITS - 1)),
|
||||
.reg = ADS8688_REG_PLUSMINUS25VREF,
|
||||
}, {
|
||||
.range = ADS8688_PLUSMINUS125VREF,
|
||||
.scale = 38148,
|
||||
.offset = -(1 << (ADS8688_REALBITS - 1)),
|
||||
.reg = ADS8688_REG_PLUSMINUS125VREF,
|
||||
}, {
|
||||
.range = ADS8688_PLUSMINUS0625VREF,
|
||||
.scale = 19074,
|
||||
.offset = -(1 << (ADS8688_REALBITS - 1)),
|
||||
.reg = ADS8688_REG_PLUSMINUS0625VREF,
|
||||
}, {
|
||||
.range = ADS8688_PLUS25VREF,
|
||||
.scale = 38148,
|
||||
.offset = 0,
|
||||
.reg = ADS8688_REG_PLUS25VREF,
|
||||
}, {
|
||||
.range = ADS8688_PLUS125VREF,
|
||||
.scale = 19074,
|
||||
.offset = 0,
|
||||
.reg = ADS8688_REG_PLUS125VREF,
|
||||
}
|
||||
};
|
||||
|
||||
static ssize_t ads8688_show_scales(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct ads8688_state *st = iio_priv(dev_to_iio_dev(dev));
|
||||
|
||||
return sprintf(buf, "0.%09u 0.%09u 0.%09u\n",
|
||||
ads8688_range_def[0].scale * st->vref_mv,
|
||||
ads8688_range_def[1].scale * st->vref_mv,
|
||||
ads8688_range_def[2].scale * st->vref_mv);
|
||||
}
|
||||
|
||||
static ssize_t ads8688_show_offsets(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d %d\n", ads8688_range_def[0].offset,
|
||||
ads8688_range_def[3].offset);
|
||||
}
|
||||
|
||||
static IIO_DEVICE_ATTR(in_voltage_scale_available, S_IRUGO,
|
||||
ads8688_show_scales, NULL, 0);
|
||||
static IIO_DEVICE_ATTR(in_voltage_offset_available, S_IRUGO,
|
||||
ads8688_show_offsets, NULL, 0);
|
||||
|
||||
static struct attribute *ads8688_attributes[] = {
|
||||
&iio_dev_attr_in_voltage_scale_available.dev_attr.attr,
|
||||
&iio_dev_attr_in_voltage_offset_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group ads8688_attribute_group = {
|
||||
.attrs = ads8688_attributes,
|
||||
};
|
||||
|
||||
#define ADS8688_CHAN(index) \
|
||||
{ \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.channel = index, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
|
||||
| BIT(IIO_CHAN_INFO_SCALE) \
|
||||
| BIT(IIO_CHAN_INFO_OFFSET), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec ads8684_channels[] = {
|
||||
ADS8688_CHAN(0),
|
||||
ADS8688_CHAN(1),
|
||||
ADS8688_CHAN(2),
|
||||
ADS8688_CHAN(3),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec ads8688_channels[] = {
|
||||
ADS8688_CHAN(0),
|
||||
ADS8688_CHAN(1),
|
||||
ADS8688_CHAN(2),
|
||||
ADS8688_CHAN(3),
|
||||
ADS8688_CHAN(4),
|
||||
ADS8688_CHAN(5),
|
||||
ADS8688_CHAN(6),
|
||||
ADS8688_CHAN(7),
|
||||
};
|
||||
|
||||
static int ads8688_prog_write(struct iio_dev *indio_dev, unsigned int addr,
|
||||
unsigned int val)
|
||||
{
|
||||
struct ads8688_state *st = iio_priv(indio_dev);
|
||||
u32 tmp;
|
||||
|
||||
tmp = ADS8688_PROG_REG(addr) | ADS8688_PROG_WR_BIT | val;
|
||||
tmp <<= ADS8688_PROG_DONT_CARE_BITS;
|
||||
st->data[0].d32 = cpu_to_be32(tmp);
|
||||
|
||||
return spi_write(st->spi, &st->data[0].d8[1], 3);
|
||||
}
|
||||
|
||||
static int ads8688_reset(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct ads8688_state *st = iio_priv(indio_dev);
|
||||
u32 tmp;
|
||||
|
||||
tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_RST);
|
||||
tmp <<= ADS8688_CMD_DONT_CARE_BITS;
|
||||
st->data[0].d32 = cpu_to_be32(tmp);
|
||||
|
||||
return spi_write(st->spi, &st->data[0].d8[0], 4);
|
||||
}
|
||||
|
||||
static int ads8688_read(struct iio_dev *indio_dev, unsigned int chan)
|
||||
{
|
||||
struct ads8688_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
u32 tmp;
|
||||
struct spi_transfer t[] = {
|
||||
{
|
||||
.tx_buf = &st->data[0].d8[0],
|
||||
.len = 4,
|
||||
.cs_change = 1,
|
||||
}, {
|
||||
.tx_buf = &st->data[1].d8[0],
|
||||
.rx_buf = &st->data[1].d8[0],
|
||||
.len = 4,
|
||||
},
|
||||
};
|
||||
|
||||
tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_MAN_CH(chan));
|
||||
tmp <<= ADS8688_CMD_DONT_CARE_BITS;
|
||||
st->data[0].d32 = cpu_to_be32(tmp);
|
||||
|
||||
tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_NOOP);
|
||||
tmp <<= ADS8688_CMD_DONT_CARE_BITS;
|
||||
st->data[1].d32 = cpu_to_be32(tmp);
|
||||
|
||||
ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return be32_to_cpu(st->data[1].d32) & 0xffff;
|
||||
}
|
||||
|
||||
static int ads8688_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long m)
|
||||
{
|
||||
int ret, offset;
|
||||
unsigned long scale_mv;
|
||||
|
||||
struct ads8688_state *st = iio_priv(indio_dev);
|
||||
|
||||
mutex_lock(&st->lock);
|
||||
switch (m) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = ads8688_read(indio_dev, chan->channel);
|
||||
mutex_unlock(&st->lock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
scale_mv = st->vref_mv;
|
||||
scale_mv *= ads8688_range_def[st->range[chan->channel]].scale;
|
||||
*val = 0;
|
||||
*val2 = scale_mv;
|
||||
mutex_unlock(&st->lock);
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
offset = ads8688_range_def[st->range[chan->channel]].offset;
|
||||
*val = offset;
|
||||
mutex_unlock(&st->lock);
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
mutex_unlock(&st->lock);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ads8688_write_reg_range(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
enum ads8688_range range)
|
||||
{
|
||||
unsigned int tmp;
|
||||
int ret;
|
||||
|
||||
tmp = ADS8688_PROG_REG_RANGE_CH(chan->channel);
|
||||
ret = ads8688_prog_write(indio_dev, tmp, range);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ads8688_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct ads8688_state *st = iio_priv(indio_dev);
|
||||
unsigned int scale = 0;
|
||||
int ret = -EINVAL, i, offset = 0;
|
||||
|
||||
mutex_lock(&st->lock);
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
/* If the offset is 0 the ±2.5 * VREF mode is not available */
|
||||
offset = ads8688_range_def[st->range[chan->channel]].offset;
|
||||
if (offset == 0 && val2 == ads8688_range_def[0].scale * st->vref_mv) {
|
||||
mutex_unlock(&st->lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Lookup new mode */
|
||||
for (i = 0; i < ARRAY_SIZE(ads8688_range_def); i++)
|
||||
if (val2 == ads8688_range_def[i].scale * st->vref_mv &&
|
||||
offset == ads8688_range_def[i].offset) {
|
||||
ret = ads8688_write_reg_range(indio_dev, chan,
|
||||
ads8688_range_def[i].reg);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
/*
|
||||
* There are only two available offsets:
|
||||
* 0 and -(1 << (ADS8688_REALBITS - 1))
|
||||
*/
|
||||
if (!(ads8688_range_def[0].offset == val ||
|
||||
ads8688_range_def[3].offset == val)) {
|
||||
mutex_unlock(&st->lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the device are in ±2.5 * VREF mode, it's not allowed to
|
||||
* switch to a mode where the offset is 0
|
||||
*/
|
||||
if (val == 0 &&
|
||||
st->range[chan->channel] == ADS8688_PLUSMINUS25VREF) {
|
||||
mutex_unlock(&st->lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
scale = ads8688_range_def[st->range[chan->channel]].scale;
|
||||
|
||||
/* Lookup new mode */
|
||||
for (i = 0; i < ARRAY_SIZE(ads8688_range_def); i++)
|
||||
if (val == ads8688_range_def[i].offset &&
|
||||
scale == ads8688_range_def[i].scale) {
|
||||
ret = ads8688_write_reg_range(indio_dev, chan,
|
||||
ads8688_range_def[i].reg);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
st->range[chan->channel] = ads8688_range_def[i].range;
|
||||
|
||||
mutex_unlock(&st->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ads8688_write_raw_get_fmt(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
long mask)
|
||||
{
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct iio_info ads8688_info = {
|
||||
.read_raw = &ads8688_read_raw,
|
||||
.write_raw = &ads8688_write_raw,
|
||||
.write_raw_get_fmt = &ads8688_write_raw_get_fmt,
|
||||
.attrs = &ads8688_attribute_group,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct ads8688_chip_info ads8688_chip_info_tbl[] = {
|
||||
[ID_ADS8684] = {
|
||||
.channels = ads8684_channels,
|
||||
.num_channels = ARRAY_SIZE(ads8684_channels),
|
||||
},
|
||||
[ID_ADS8688] = {
|
||||
.channels = ads8688_channels,
|
||||
.num_channels = ARRAY_SIZE(ads8688_channels),
|
||||
},
|
||||
};
|
||||
|
||||
static int ads8688_probe(struct spi_device *spi)
|
||||
{
|
||||
struct ads8688_state *st;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
|
||||
if (indio_dev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
st = iio_priv(indio_dev);
|
||||
|
||||
st->reg = devm_regulator_get_optional(&spi->dev, "vref");
|
||||
if (!IS_ERR(st->reg)) {
|
||||
ret = regulator_enable(st->reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regulator_get_voltage(st->reg);
|
||||
if (ret < 0)
|
||||
goto error_out;
|
||||
|
||||
st->vref_mv = ret / 1000;
|
||||
} else {
|
||||
/* Use internal reference */
|
||||
st->vref_mv = ADS8688_VREF_MV;
|
||||
}
|
||||
|
||||
st->chip_info = &ads8688_chip_info_tbl[spi_get_device_id(spi)->driver_data];
|
||||
|
||||
spi->mode = SPI_MODE_1;
|
||||
|
||||
spi_set_drvdata(spi, indio_dev);
|
||||
|
||||
st->spi = spi;
|
||||
|
||||
indio_dev->name = spi_get_device_id(spi)->name;
|
||||
indio_dev->dev.parent = &spi->dev;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = st->chip_info->channels;
|
||||
indio_dev->num_channels = st->chip_info->num_channels;
|
||||
indio_dev->info = &ads8688_info;
|
||||
|
||||
ads8688_reset(indio_dev);
|
||||
|
||||
mutex_init(&st->lock);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret)
|
||||
goto error_out;
|
||||
|
||||
return 0;
|
||||
|
||||
error_out:
|
||||
if (!IS_ERR_OR_NULL(st->reg))
|
||||
regulator_disable(st->reg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ads8688_remove(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev = spi_get_drvdata(spi);
|
||||
struct ads8688_state *st = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
if (!IS_ERR_OR_NULL(st->reg))
|
||||
regulator_disable(st->reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id ads8688_id[] = {
|
||||
{"ads8684", ID_ADS8684},
|
||||
{"ads8688", ID_ADS8688},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ads8688_id);
|
||||
|
||||
static const struct of_device_id ads8688_of_match[] = {
|
||||
{ .compatible = "ti,ads8684" },
|
||||
{ .compatible = "ti,ads8688" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ads8688_of_match);
|
||||
|
||||
static struct spi_driver ads8688_driver = {
|
||||
.driver = {
|
||||
.name = "ads8688",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ads8688_probe,
|
||||
.remove = ads8688_remove,
|
||||
.id_table = ads8688_id,
|
||||
};
|
||||
module_spi_driver(ads8688_driver);
|
||||
|
||||
MODULE_AUTHOR("Sean Nyekjaer <sean.nyekjaer@prevas.dk>");
|
||||
MODULE_DESCRIPTION("Texas Instruments ADS8688 driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -803,7 +803,7 @@ err:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static struct iio_buffer_setup_ops xadc_buffer_ops = {
|
||||
static const struct iio_buffer_setup_ops xadc_buffer_ops = {
|
||||
.preenable = &xadc_preenable,
|
||||
.postenable = &iio_triggered_buffer_postenable,
|
||||
.predisable = &iio_triggered_buffer_predisable,
|
||||
|
|
|
@ -9,6 +9,26 @@ config IIO_BUFFER_CB
|
|||
Should be selected by any drivers that do in-kernel push
|
||||
usage. That is, those where the data is pushed to the consumer.
|
||||
|
||||
config IIO_BUFFER_DMA
|
||||
tristate
|
||||
help
|
||||
Provides the generic IIO DMA buffer infrastructure that can be used by
|
||||
drivers for devices with DMA support to implement the IIO buffer.
|
||||
|
||||
Should be selected by drivers that want to use the generic DMA buffer
|
||||
infrastructure.
|
||||
|
||||
config IIO_BUFFER_DMAENGINE
|
||||
tristate
|
||||
select IIO_BUFFER_DMA
|
||||
help
|
||||
Provides a bonding of the generic IIO DMA buffer infrastructure with the
|
||||
DMAengine framework. This can be used by converter drivers with a DMA port
|
||||
connected to an external DMA controller which is supported by the
|
||||
DMAengine framework.
|
||||
|
||||
Should be selected by drivers that want to use this functionality.
|
||||
|
||||
config IIO_KFIFO_BUF
|
||||
tristate "Industrial I/O buffering based on kfifo"
|
||||
help
|
||||
|
|
|
@ -4,5 +4,7 @@
|
|||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
obj-$(CONFIG_IIO_BUFFER_CB) += industrialio-buffer-cb.o
|
||||
obj-$(CONFIG_IIO_BUFFER_DMA) += industrialio-buffer-dma.o
|
||||
obj-$(CONFIG_IIO_BUFFER_DMAENGINE) += industrialio-buffer-dmaengine.o
|
||||
obj-$(CONFIG_IIO_TRIGGERED_BUFFER) += industrialio-triggered-buffer.o
|
||||
obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
|
||||
|
|
|
@ -0,0 +1,683 @@
|
|||
/*
|
||||
* Copyright 2013-2015 Analog Devices Inc.
|
||||
* Author: Lars-Peter Clausen <lars@metafoo.de>
|
||||
*
|
||||
* Licensed under the GPL-2.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/buffer-dma.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
/*
|
||||
* For DMA buffers the storage is sub-divided into so called blocks. Each block
|
||||
* has its own memory buffer. The size of the block is the granularity at which
|
||||
* memory is exchanged between the hardware and the application. Increasing the
|
||||
* basic unit of data exchange from one sample to one block decreases the
|
||||
* management overhead that is associated with each sample. E.g. if we say the
|
||||
* management overhead for one exchange is x and the unit of exchange is one
|
||||
* sample the overhead will be x for each sample. Whereas when using a block
|
||||
* which contains n samples the overhead per sample is reduced to x/n. This
|
||||
* allows to achieve much higher samplerates than what can be sustained with
|
||||
* the one sample approach.
|
||||
*
|
||||
* Blocks are exchanged between the DMA controller and the application via the
|
||||
* means of two queues. The incoming queue and the outgoing queue. Blocks on the
|
||||
* incoming queue are waiting for the DMA controller to pick them up and fill
|
||||
* them with data. Block on the outgoing queue have been filled with data and
|
||||
* are waiting for the application to dequeue them and read the data.
|
||||
*
|
||||
* A block can be in one of the following states:
|
||||
* * Owned by the application. In this state the application can read data from
|
||||
* the block.
|
||||
* * On the incoming list: Blocks on the incoming list are queued up to be
|
||||
* processed by the DMA controller.
|
||||
* * Owned by the DMA controller: The DMA controller is processing the block
|
||||
* and filling it with data.
|
||||
* * On the outgoing list: Blocks on the outgoing list have been successfully
|
||||
* processed by the DMA controller and contain data. They can be dequeued by
|
||||
* the application.
|
||||
* * Dead: A block that is dead has been marked as to be freed. It might still
|
||||
* be owned by either the application or the DMA controller at the moment.
|
||||
* But once they are done processing it instead of going to either the
|
||||
* incoming or outgoing queue the block will be freed.
|
||||
*
|
||||
* In addition to this blocks are reference counted and the memory associated
|
||||
* with both the block structure as well as the storage memory for the block
|
||||
* will be freed when the last reference to the block is dropped. This means a
|
||||
* block must not be accessed without holding a reference.
|
||||
*
|
||||
* The iio_dma_buffer implementation provides a generic infrastructure for
|
||||
* managing the blocks.
|
||||
*
|
||||
* A driver for a specific piece of hardware that has DMA capabilities need to
|
||||
* implement the submit() callback from the iio_dma_buffer_ops structure. This
|
||||
* callback is supposed to initiate the DMA transfer copying data from the
|
||||
* converter to the memory region of the block. Once the DMA transfer has been
|
||||
* completed the driver must call iio_dma_buffer_block_done() for the completed
|
||||
* block.
|
||||
*
|
||||
* Prior to this it must set the bytes_used field of the block contains
|
||||
* the actual number of bytes in the buffer. Typically this will be equal to the
|
||||
* size of the block, but if the DMA hardware has certain alignment requirements
|
||||
* for the transfer length it might choose to use less than the full size. In
|
||||
* either case it is expected that bytes_used is a multiple of the bytes per
|
||||
* datum, i.e. the block must not contain partial samples.
|
||||
*
|
||||
* The driver must call iio_dma_buffer_block_done() for each block it has
|
||||
* received through its submit_block() callback, even if it does not actually
|
||||
* perform a DMA transfer for the block, e.g. because the buffer was disabled
|
||||
* before the block transfer was started. In this case it should set bytes_used
|
||||
* to 0.
|
||||
*
|
||||
* In addition it is recommended that a driver implements the abort() callback.
|
||||
* It will be called when the buffer is disabled and can be used to cancel
|
||||
* pending and stop active transfers.
|
||||
*
|
||||
* The specific driver implementation should use the default callback
|
||||
* implementations provided by this module for the iio_buffer_access_funcs
|
||||
* struct. It may overload some callbacks with custom variants if the hardware
|
||||
* has special requirements that are not handled by the generic functions. If a
|
||||
* driver chooses to overload a callback it has to ensure that the generic
|
||||
* callback is called from within the custom callback.
|
||||
*/
|
||||
|
||||
static void iio_buffer_block_release(struct kref *kref)
|
||||
{
|
||||
struct iio_dma_buffer_block *block = container_of(kref,
|
||||
struct iio_dma_buffer_block, kref);
|
||||
|
||||
WARN_ON(block->state != IIO_BLOCK_STATE_DEAD);
|
||||
|
||||
dma_free_coherent(block->queue->dev, PAGE_ALIGN(block->size),
|
||||
block->vaddr, block->phys_addr);
|
||||
|
||||
iio_buffer_put(&block->queue->buffer);
|
||||
kfree(block);
|
||||
}
|
||||
|
||||
static void iio_buffer_block_get(struct iio_dma_buffer_block *block)
|
||||
{
|
||||
kref_get(&block->kref);
|
||||
}
|
||||
|
||||
static void iio_buffer_block_put(struct iio_dma_buffer_block *block)
|
||||
{
|
||||
kref_put(&block->kref, iio_buffer_block_release);
|
||||
}
|
||||
|
||||
/*
|
||||
* dma_free_coherent can sleep, hence we need to take some special care to be
|
||||
* able to drop a reference from an atomic context.
|
||||
*/
|
||||
static LIST_HEAD(iio_dma_buffer_dead_blocks);
|
||||
static DEFINE_SPINLOCK(iio_dma_buffer_dead_blocks_lock);
|
||||
|
||||
static void iio_dma_buffer_cleanup_worker(struct work_struct *work)
|
||||
{
|
||||
struct iio_dma_buffer_block *block, *_block;
|
||||
LIST_HEAD(block_list);
|
||||
|
||||
spin_lock_irq(&iio_dma_buffer_dead_blocks_lock);
|
||||
list_splice_tail_init(&iio_dma_buffer_dead_blocks, &block_list);
|
||||
spin_unlock_irq(&iio_dma_buffer_dead_blocks_lock);
|
||||
|
||||
list_for_each_entry_safe(block, _block, &block_list, head)
|
||||
iio_buffer_block_release(&block->kref);
|
||||
}
|
||||
static DECLARE_WORK(iio_dma_buffer_cleanup_work, iio_dma_buffer_cleanup_worker);
|
||||
|
||||
static void iio_buffer_block_release_atomic(struct kref *kref)
|
||||
{
|
||||
struct iio_dma_buffer_block *block;
|
||||
unsigned long flags;
|
||||
|
||||
block = container_of(kref, struct iio_dma_buffer_block, kref);
|
||||
|
||||
spin_lock_irqsave(&iio_dma_buffer_dead_blocks_lock, flags);
|
||||
list_add_tail(&block->head, &iio_dma_buffer_dead_blocks);
|
||||
spin_unlock_irqrestore(&iio_dma_buffer_dead_blocks_lock, flags);
|
||||
|
||||
schedule_work(&iio_dma_buffer_cleanup_work);
|
||||
}
|
||||
|
||||
/*
|
||||
* Version of iio_buffer_block_put() that can be called from atomic context
|
||||
*/
|
||||
static void iio_buffer_block_put_atomic(struct iio_dma_buffer_block *block)
|
||||
{
|
||||
kref_put(&block->kref, iio_buffer_block_release_atomic);
|
||||
}
|
||||
|
||||
static struct iio_dma_buffer_queue *iio_buffer_to_queue(struct iio_buffer *buf)
|
||||
{
|
||||
return container_of(buf, struct iio_dma_buffer_queue, buffer);
|
||||
}
|
||||
|
||||
static struct iio_dma_buffer_block *iio_dma_buffer_alloc_block(
|
||||
struct iio_dma_buffer_queue *queue, size_t size)
|
||||
{
|
||||
struct iio_dma_buffer_block *block;
|
||||
|
||||
block = kzalloc(sizeof(*block), GFP_KERNEL);
|
||||
if (!block)
|
||||
return NULL;
|
||||
|
||||
block->vaddr = dma_alloc_coherent(queue->dev, PAGE_ALIGN(size),
|
||||
&block->phys_addr, GFP_KERNEL);
|
||||
if (!block->vaddr) {
|
||||
kfree(block);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
block->size = size;
|
||||
block->state = IIO_BLOCK_STATE_DEQUEUED;
|
||||
block->queue = queue;
|
||||
INIT_LIST_HEAD(&block->head);
|
||||
kref_init(&block->kref);
|
||||
|
||||
iio_buffer_get(&queue->buffer);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
static void _iio_dma_buffer_block_done(struct iio_dma_buffer_block *block)
|
||||
{
|
||||
struct iio_dma_buffer_queue *queue = block->queue;
|
||||
|
||||
/*
|
||||
* The buffer has already been freed by the application, just drop the
|
||||
* reference.
|
||||
*/
|
||||
if (block->state != IIO_BLOCK_STATE_DEAD) {
|
||||
block->state = IIO_BLOCK_STATE_DONE;
|
||||
list_add_tail(&block->head, &queue->outgoing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_block_done() - Indicate that a block has been completed
|
||||
* @block: The completed block
|
||||
*
|
||||
* Should be called when the DMA controller has finished handling the block to
|
||||
* pass back ownership of the block to the queue.
|
||||
*/
|
||||
void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block)
|
||||
{
|
||||
struct iio_dma_buffer_queue *queue = block->queue;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&queue->list_lock, flags);
|
||||
_iio_dma_buffer_block_done(block);
|
||||
spin_unlock_irqrestore(&queue->list_lock, flags);
|
||||
|
||||
iio_buffer_block_put_atomic(block);
|
||||
wake_up_interruptible_poll(&queue->buffer.pollq, POLLIN | POLLRDNORM);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_block_done);
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_block_list_abort() - Indicate that a list block has been
|
||||
* aborted
|
||||
* @queue: Queue for which to complete blocks.
|
||||
* @list: List of aborted blocks. All blocks in this list must be from @queue.
|
||||
*
|
||||
* Typically called from the abort() callback after the DMA controller has been
|
||||
* stopped. This will set bytes_used to 0 for each block in the list and then
|
||||
* hand the blocks back to the queue.
|
||||
*/
|
||||
void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue,
|
||||
struct list_head *list)
|
||||
{
|
||||
struct iio_dma_buffer_block *block, *_block;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&queue->list_lock, flags);
|
||||
list_for_each_entry_safe(block, _block, list, head) {
|
||||
list_del(&block->head);
|
||||
block->bytes_used = 0;
|
||||
_iio_dma_buffer_block_done(block);
|
||||
iio_buffer_block_put_atomic(block);
|
||||
}
|
||||
spin_unlock_irqrestore(&queue->list_lock, flags);
|
||||
|
||||
wake_up_interruptible_poll(&queue->buffer.pollq, POLLIN | POLLRDNORM);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_block_list_abort);
|
||||
|
||||
static bool iio_dma_block_reusable(struct iio_dma_buffer_block *block)
|
||||
{
|
||||
/*
|
||||
* If the core owns the block it can be re-used. This should be the
|
||||
* default case when enabling the buffer, unless the DMA controller does
|
||||
* not support abort and has not given back the block yet.
|
||||
*/
|
||||
switch (block->state) {
|
||||
case IIO_BLOCK_STATE_DEQUEUED:
|
||||
case IIO_BLOCK_STATE_QUEUED:
|
||||
case IIO_BLOCK_STATE_DONE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_request_update() - DMA buffer request_update callback
|
||||
* @buffer: The buffer which to request an update
|
||||
*
|
||||
* Should be used as the iio_dma_buffer_request_update() callback for
|
||||
* iio_buffer_access_ops struct for DMA buffers.
|
||||
*/
|
||||
int iio_dma_buffer_request_update(struct iio_buffer *buffer)
|
||||
{
|
||||
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
|
||||
struct iio_dma_buffer_block *block;
|
||||
bool try_reuse = false;
|
||||
size_t size;
|
||||
int ret = 0;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Split the buffer into two even parts. This is used as a double
|
||||
* buffering scheme with usually one block at a time being used by the
|
||||
* DMA and the other one by the application.
|
||||
*/
|
||||
size = DIV_ROUND_UP(queue->buffer.bytes_per_datum *
|
||||
queue->buffer.length, 2);
|
||||
|
||||
mutex_lock(&queue->lock);
|
||||
|
||||
/* Allocations are page aligned */
|
||||
if (PAGE_ALIGN(queue->fileio.block_size) == PAGE_ALIGN(size))
|
||||
try_reuse = true;
|
||||
|
||||
queue->fileio.block_size = size;
|
||||
queue->fileio.active_block = NULL;
|
||||
|
||||
spin_lock_irq(&queue->list_lock);
|
||||
for (i = 0; i < 2; i++) {
|
||||
block = queue->fileio.blocks[i];
|
||||
|
||||
/* If we can't re-use it free it */
|
||||
if (block && (!iio_dma_block_reusable(block) || !try_reuse))
|
||||
block->state = IIO_BLOCK_STATE_DEAD;
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point all blocks are either owned by the core or marked as
|
||||
* dead. This means we can reset the lists without having to fear
|
||||
* corrution.
|
||||
*/
|
||||
INIT_LIST_HEAD(&queue->outgoing);
|
||||
spin_unlock_irq(&queue->list_lock);
|
||||
|
||||
INIT_LIST_HEAD(&queue->incoming);
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (queue->fileio.blocks[i]) {
|
||||
block = queue->fileio.blocks[i];
|
||||
if (block->state == IIO_BLOCK_STATE_DEAD) {
|
||||
/* Could not reuse it */
|
||||
iio_buffer_block_put(block);
|
||||
block = NULL;
|
||||
} else {
|
||||
block->size = size;
|
||||
}
|
||||
} else {
|
||||
block = NULL;
|
||||
}
|
||||
|
||||
if (!block) {
|
||||
block = iio_dma_buffer_alloc_block(queue, size);
|
||||
if (!block) {
|
||||
ret = -ENOMEM;
|
||||
goto out_unlock;
|
||||
}
|
||||
queue->fileio.blocks[i] = block;
|
||||
}
|
||||
|
||||
block->state = IIO_BLOCK_STATE_QUEUED;
|
||||
list_add_tail(&block->head, &queue->incoming);
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&queue->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_request_update);
|
||||
|
||||
static void iio_dma_buffer_submit_block(struct iio_dma_buffer_queue *queue,
|
||||
struct iio_dma_buffer_block *block)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* If the hardware has already been removed we put the block into
|
||||
* limbo. It will neither be on the incoming nor outgoing list, nor will
|
||||
* it ever complete. It will just wait to be freed eventually.
|
||||
*/
|
||||
if (!queue->ops)
|
||||
return;
|
||||
|
||||
block->state = IIO_BLOCK_STATE_ACTIVE;
|
||||
iio_buffer_block_get(block);
|
||||
ret = queue->ops->submit(queue, block);
|
||||
if (ret) {
|
||||
/*
|
||||
* This is a bit of a problem and there is not much we can do
|
||||
* other then wait for the buffer to be disabled and re-enabled
|
||||
* and try again. But it should not really happen unless we run
|
||||
* out of memory or something similar.
|
||||
*
|
||||
* TODO: Implement support in the IIO core to allow buffers to
|
||||
* notify consumers that something went wrong and the buffer
|
||||
* should be disabled.
|
||||
*/
|
||||
iio_buffer_block_put(block);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_enable() - Enable DMA buffer
|
||||
* @buffer: IIO buffer to enable
|
||||
* @indio_dev: IIO device the buffer is attached to
|
||||
*
|
||||
* Needs to be called when the device that the buffer is attached to starts
|
||||
* sampling. Typically should be the iio_buffer_access_ops enable callback.
|
||||
*
|
||||
* This will allocate the DMA buffers and start the DMA transfers.
|
||||
*/
|
||||
int iio_dma_buffer_enable(struct iio_buffer *buffer,
|
||||
struct iio_dev *indio_dev)
|
||||
{
|
||||
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
|
||||
struct iio_dma_buffer_block *block, *_block;
|
||||
|
||||
mutex_lock(&queue->lock);
|
||||
queue->active = true;
|
||||
list_for_each_entry_safe(block, _block, &queue->incoming, head) {
|
||||
list_del(&block->head);
|
||||
iio_dma_buffer_submit_block(queue, block);
|
||||
}
|
||||
mutex_unlock(&queue->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_enable);
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_disable() - Disable DMA buffer
|
||||
* @buffer: IIO DMA buffer to disable
|
||||
* @indio_dev: IIO device the buffer is attached to
|
||||
*
|
||||
* Needs to be called when the device that the buffer is attached to stops
|
||||
* sampling. Typically should be the iio_buffer_access_ops disable callback.
|
||||
*/
|
||||
int iio_dma_buffer_disable(struct iio_buffer *buffer,
|
||||
struct iio_dev *indio_dev)
|
||||
{
|
||||
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
|
||||
|
||||
mutex_lock(&queue->lock);
|
||||
queue->active = false;
|
||||
|
||||
if (queue->ops && queue->ops->abort)
|
||||
queue->ops->abort(queue);
|
||||
mutex_unlock(&queue->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_disable);
|
||||
|
||||
static void iio_dma_buffer_enqueue(struct iio_dma_buffer_queue *queue,
|
||||
struct iio_dma_buffer_block *block)
|
||||
{
|
||||
if (block->state == IIO_BLOCK_STATE_DEAD) {
|
||||
iio_buffer_block_put(block);
|
||||
} else if (queue->active) {
|
||||
iio_dma_buffer_submit_block(queue, block);
|
||||
} else {
|
||||
block->state = IIO_BLOCK_STATE_QUEUED;
|
||||
list_add_tail(&block->head, &queue->incoming);
|
||||
}
|
||||
}
|
||||
|
||||
static struct iio_dma_buffer_block *iio_dma_buffer_dequeue(
|
||||
struct iio_dma_buffer_queue *queue)
|
||||
{
|
||||
struct iio_dma_buffer_block *block;
|
||||
|
||||
spin_lock_irq(&queue->list_lock);
|
||||
block = list_first_entry_or_null(&queue->outgoing, struct
|
||||
iio_dma_buffer_block, head);
|
||||
if (block != NULL) {
|
||||
list_del(&block->head);
|
||||
block->state = IIO_BLOCK_STATE_DEQUEUED;
|
||||
}
|
||||
spin_unlock_irq(&queue->list_lock);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_read() - DMA buffer read callback
|
||||
* @buffer: Buffer to read form
|
||||
* @n: Number of bytes to read
|
||||
* @user_buffer: Userspace buffer to copy the data to
|
||||
*
|
||||
* Should be used as the read_first_n callback for iio_buffer_access_ops
|
||||
* struct for DMA buffers.
|
||||
*/
|
||||
int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n,
|
||||
char __user *user_buffer)
|
||||
{
|
||||
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
|
||||
struct iio_dma_buffer_block *block;
|
||||
int ret;
|
||||
|
||||
if (n < buffer->bytes_per_datum)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&queue->lock);
|
||||
|
||||
if (!queue->fileio.active_block) {
|
||||
block = iio_dma_buffer_dequeue(queue);
|
||||
if (block == NULL) {
|
||||
ret = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
queue->fileio.pos = 0;
|
||||
queue->fileio.active_block = block;
|
||||
} else {
|
||||
block = queue->fileio.active_block;
|
||||
}
|
||||
|
||||
n = rounddown(n, buffer->bytes_per_datum);
|
||||
if (n > block->bytes_used - queue->fileio.pos)
|
||||
n = block->bytes_used - queue->fileio.pos;
|
||||
|
||||
if (copy_to_user(user_buffer, block->vaddr + queue->fileio.pos, n)) {
|
||||
ret = -EFAULT;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
queue->fileio.pos += n;
|
||||
|
||||
if (queue->fileio.pos == block->bytes_used) {
|
||||
queue->fileio.active_block = NULL;
|
||||
iio_dma_buffer_enqueue(queue, block);
|
||||
}
|
||||
|
||||
ret = n;
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&queue->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_read);
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_data_available() - DMA buffer data_available callback
|
||||
* @buf: Buffer to check for data availability
|
||||
*
|
||||
* Should be used as the data_available callback for iio_buffer_access_ops
|
||||
* struct for DMA buffers.
|
||||
*/
|
||||
size_t iio_dma_buffer_data_available(struct iio_buffer *buf)
|
||||
{
|
||||
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buf);
|
||||
struct iio_dma_buffer_block *block;
|
||||
size_t data_available = 0;
|
||||
|
||||
/*
|
||||
* For counting the available bytes we'll use the size of the block not
|
||||
* the number of actual bytes available in the block. Otherwise it is
|
||||
* possible that we end up with a value that is lower than the watermark
|
||||
* but won't increase since all blocks are in use.
|
||||
*/
|
||||
|
||||
mutex_lock(&queue->lock);
|
||||
if (queue->fileio.active_block)
|
||||
data_available += queue->fileio.active_block->size;
|
||||
|
||||
spin_lock_irq(&queue->list_lock);
|
||||
list_for_each_entry(block, &queue->outgoing, head)
|
||||
data_available += block->size;
|
||||
spin_unlock_irq(&queue->list_lock);
|
||||
mutex_unlock(&queue->lock);
|
||||
|
||||
return data_available;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_data_available);
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_set_bytes_per_datum() - DMA buffer set_bytes_per_datum callback
|
||||
* @buffer: Buffer to set the bytes-per-datum for
|
||||
* @bpd: The new bytes-per-datum value
|
||||
*
|
||||
* Should be used as the set_bytes_per_datum callback for iio_buffer_access_ops
|
||||
* struct for DMA buffers.
|
||||
*/
|
||||
int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd)
|
||||
{
|
||||
buffer->bytes_per_datum = bpd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_set_bytes_per_datum);
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_set_length - DMA buffer set_length callback
|
||||
* @buffer: Buffer to set the length for
|
||||
* @length: The new buffer length
|
||||
*
|
||||
* Should be used as the set_length callback for iio_buffer_access_ops
|
||||
* struct for DMA buffers.
|
||||
*/
|
||||
int iio_dma_buffer_set_length(struct iio_buffer *buffer, int length)
|
||||
{
|
||||
/* Avoid an invalid state */
|
||||
if (length < 2)
|
||||
length = 2;
|
||||
buffer->length = length;
|
||||
buffer->watermark = length / 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_set_length);
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_init() - Initialize DMA buffer queue
|
||||
* @queue: Buffer to initialize
|
||||
* @dev: DMA device
|
||||
* @ops: DMA buffer queue callback operations
|
||||
*
|
||||
* The DMA device will be used by the queue to do DMA memory allocations. So it
|
||||
* should refer to the device that will perform the DMA to ensure that
|
||||
* allocations are done from a memory region that can be accessed by the device.
|
||||
*/
|
||||
int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue,
|
||||
struct device *dev, const struct iio_dma_buffer_ops *ops)
|
||||
{
|
||||
iio_buffer_init(&queue->buffer);
|
||||
queue->buffer.length = PAGE_SIZE;
|
||||
queue->buffer.watermark = queue->buffer.length / 2;
|
||||
queue->dev = dev;
|
||||
queue->ops = ops;
|
||||
|
||||
INIT_LIST_HEAD(&queue->incoming);
|
||||
INIT_LIST_HEAD(&queue->outgoing);
|
||||
|
||||
mutex_init(&queue->lock);
|
||||
spin_lock_init(&queue->list_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_init);
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_exit() - Cleanup DMA buffer queue
|
||||
* @queue: Buffer to cleanup
|
||||
*
|
||||
* After this function has completed it is safe to free any resources that are
|
||||
* associated with the buffer and are accessed inside the callback operations.
|
||||
*/
|
||||
void iio_dma_buffer_exit(struct iio_dma_buffer_queue *queue)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
mutex_lock(&queue->lock);
|
||||
|
||||
spin_lock_irq(&queue->list_lock);
|
||||
for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
|
||||
if (!queue->fileio.blocks[i])
|
||||
continue;
|
||||
queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD;
|
||||
}
|
||||
INIT_LIST_HEAD(&queue->outgoing);
|
||||
spin_unlock_irq(&queue->list_lock);
|
||||
|
||||
INIT_LIST_HEAD(&queue->incoming);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
|
||||
if (!queue->fileio.blocks[i])
|
||||
continue;
|
||||
iio_buffer_block_put(queue->fileio.blocks[i]);
|
||||
queue->fileio.blocks[i] = NULL;
|
||||
}
|
||||
queue->fileio.active_block = NULL;
|
||||
queue->ops = NULL;
|
||||
|
||||
mutex_unlock(&queue->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_exit);
|
||||
|
||||
/**
|
||||
* iio_dma_buffer_release() - Release final buffer resources
|
||||
* @queue: Buffer to release
|
||||
*
|
||||
* Frees resources that can't yet be freed in iio_dma_buffer_exit(). Should be
|
||||
* called in the buffers release callback implementation right before freeing
|
||||
* the memory associated with the buffer.
|
||||
*/
|
||||
void iio_dma_buffer_release(struct iio_dma_buffer_queue *queue)
|
||||
{
|
||||
mutex_destroy(&queue->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dma_buffer_release);
|
||||
|
||||
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
|
||||
MODULE_DESCRIPTION("DMA buffer for the IIO framework");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright 2014-2015 Analog Devices Inc.
|
||||
* Author: Lars-Peter Clausen <lars@metafoo.de>
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/buffer-dma.h>
|
||||
#include <linux/iio/buffer-dmaengine.h>
|
||||
|
||||
/*
|
||||
* The IIO DMAengine buffer combines the generic IIO DMA buffer infrastructure
|
||||
* with the DMAengine framework. The generic IIO DMA buffer infrastructure is
|
||||
* used to manage the buffer memory and implement the IIO buffer operations
|
||||
* while the DMAengine framework is used to perform the DMA transfers. Combined
|
||||
* this results in a device independent fully functional DMA buffer
|
||||
* implementation that can be used by device drivers for peripherals which are
|
||||
* connected to a DMA controller which has a DMAengine driver implementation.
|
||||
*/
|
||||
|
||||
struct dmaengine_buffer {
|
||||
struct iio_dma_buffer_queue queue;
|
||||
|
||||
struct dma_chan *chan;
|
||||
struct list_head active;
|
||||
|
||||
size_t align;
|
||||
size_t max_size;
|
||||
};
|
||||
|
||||
static struct dmaengine_buffer *iio_buffer_to_dmaengine_buffer(
|
||||
struct iio_buffer *buffer)
|
||||
{
|
||||
return container_of(buffer, struct dmaengine_buffer, queue.buffer);
|
||||
}
|
||||
|
||||
static void iio_dmaengine_buffer_block_done(void *data)
|
||||
{
|
||||
struct iio_dma_buffer_block *block = data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&block->queue->list_lock, flags);
|
||||
list_del(&block->head);
|
||||
spin_unlock_irqrestore(&block->queue->list_lock, flags);
|
||||
iio_dma_buffer_block_done(block);
|
||||
}
|
||||
|
||||
static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue,
|
||||
struct iio_dma_buffer_block *block)
|
||||
{
|
||||
struct dmaengine_buffer *dmaengine_buffer =
|
||||
iio_buffer_to_dmaengine_buffer(&queue->buffer);
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
dma_cookie_t cookie;
|
||||
|
||||
block->bytes_used = min(block->size, dmaengine_buffer->max_size);
|
||||
block->bytes_used = rounddown(block->bytes_used,
|
||||
dmaengine_buffer->align);
|
||||
|
||||
desc = dmaengine_prep_slave_single(dmaengine_buffer->chan,
|
||||
block->phys_addr, block->bytes_used, DMA_DEV_TO_MEM,
|
||||
DMA_PREP_INTERRUPT);
|
||||
if (!desc)
|
||||
return -ENOMEM;
|
||||
|
||||
desc->callback = iio_dmaengine_buffer_block_done;
|
||||
desc->callback_param = block;
|
||||
|
||||
cookie = dmaengine_submit(desc);
|
||||
if (dma_submit_error(cookie))
|
||||
return dma_submit_error(cookie);
|
||||
|
||||
spin_lock_irq(&dmaengine_buffer->queue.list_lock);
|
||||
list_add_tail(&block->head, &dmaengine_buffer->active);
|
||||
spin_unlock_irq(&dmaengine_buffer->queue.list_lock);
|
||||
|
||||
dma_async_issue_pending(dmaengine_buffer->chan);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void iio_dmaengine_buffer_abort(struct iio_dma_buffer_queue *queue)
|
||||
{
|
||||
struct dmaengine_buffer *dmaengine_buffer =
|
||||
iio_buffer_to_dmaengine_buffer(&queue->buffer);
|
||||
|
||||
dmaengine_terminate_all(dmaengine_buffer->chan);
|
||||
/* FIXME: There is a slight chance of a race condition here.
|
||||
* dmaengine_terminate_all() does not guarantee that all transfer
|
||||
* callbacks have finished running. Need to introduce a
|
||||
* dmaengine_terminate_all_sync().
|
||||
*/
|
||||
iio_dma_buffer_block_list_abort(queue, &dmaengine_buffer->active);
|
||||
}
|
||||
|
||||
static void iio_dmaengine_buffer_release(struct iio_buffer *buf)
|
||||
{
|
||||
struct dmaengine_buffer *dmaengine_buffer =
|
||||
iio_buffer_to_dmaengine_buffer(buf);
|
||||
|
||||
iio_dma_buffer_release(&dmaengine_buffer->queue);
|
||||
kfree(dmaengine_buffer);
|
||||
}
|
||||
|
||||
static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = {
|
||||
.read_first_n = iio_dma_buffer_read,
|
||||
.set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum,
|
||||
.set_length = iio_dma_buffer_set_length,
|
||||
.request_update = iio_dma_buffer_request_update,
|
||||
.enable = iio_dma_buffer_enable,
|
||||
.disable = iio_dma_buffer_disable,
|
||||
.data_available = iio_dma_buffer_data_available,
|
||||
.release = iio_dmaengine_buffer_release,
|
||||
|
||||
.modes = INDIO_BUFFER_HARDWARE,
|
||||
.flags = INDIO_BUFFER_FLAG_FIXED_WATERMARK,
|
||||
};
|
||||
|
||||
static const struct iio_dma_buffer_ops iio_dmaengine_default_ops = {
|
||||
.submit = iio_dmaengine_buffer_submit_block,
|
||||
.abort = iio_dmaengine_buffer_abort,
|
||||
};
|
||||
|
||||
/**
|
||||
* iio_dmaengine_buffer_alloc() - Allocate new buffer which uses DMAengine
|
||||
* @dev: Parent device for the buffer
|
||||
* @channel: DMA channel name, typically "rx".
|
||||
*
|
||||
* This allocates a new IIO buffer which internally uses the DMAengine framework
|
||||
* to perform its transfers. The parent device will be used to request the DMA
|
||||
* channel.
|
||||
*
|
||||
* Once done using the buffer iio_dmaengine_buffer_free() should be used to
|
||||
* release it.
|
||||
*/
|
||||
struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
|
||||
const char *channel)
|
||||
{
|
||||
struct dmaengine_buffer *dmaengine_buffer;
|
||||
unsigned int width, src_width, dest_width;
|
||||
struct dma_slave_caps caps;
|
||||
struct dma_chan *chan;
|
||||
int ret;
|
||||
|
||||
dmaengine_buffer = kzalloc(sizeof(*dmaengine_buffer), GFP_KERNEL);
|
||||
if (!dmaengine_buffer)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
chan = dma_request_slave_channel_reason(dev, channel);
|
||||
if (IS_ERR(chan)) {
|
||||
ret = PTR_ERR(chan);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
ret = dma_get_slave_caps(chan, &caps);
|
||||
if (ret < 0)
|
||||
goto err_free;
|
||||
|
||||
/* Needs to be aligned to the maximum of the minimums */
|
||||
if (caps.src_addr_widths)
|
||||
src_width = __ffs(caps.src_addr_widths);
|
||||
else
|
||||
src_width = 1;
|
||||
if (caps.dst_addr_widths)
|
||||
dest_width = __ffs(caps.dst_addr_widths);
|
||||
else
|
||||
dest_width = 1;
|
||||
width = max(src_width, dest_width);
|
||||
|
||||
INIT_LIST_HEAD(&dmaengine_buffer->active);
|
||||
dmaengine_buffer->chan = chan;
|
||||
dmaengine_buffer->align = width;
|
||||
dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev);
|
||||
|
||||
iio_dma_buffer_init(&dmaengine_buffer->queue, chan->device->dev,
|
||||
&iio_dmaengine_default_ops);
|
||||
|
||||
dmaengine_buffer->queue.buffer.access = &iio_dmaengine_buffer_ops;
|
||||
|
||||
return &dmaengine_buffer->queue.buffer;
|
||||
|
||||
err_free:
|
||||
kfree(dmaengine_buffer);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL(iio_dmaengine_buffer_alloc);
|
||||
|
||||
/**
|
||||
* iio_dmaengine_buffer_free() - Free dmaengine buffer
|
||||
* @buffer: Buffer to free
|
||||
*
|
||||
* Frees a buffer previously allocated with iio_dmaengine_buffer_alloc().
|
||||
*/
|
||||
void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
|
||||
{
|
||||
struct dmaengine_buffer *dmaengine_buffer =
|
||||
iio_buffer_to_dmaengine_buffer(buffer);
|
||||
|
||||
iio_dma_buffer_exit(&dmaengine_buffer->queue);
|
||||
dma_release_channel(dmaengine_buffer->chan);
|
||||
|
||||
iio_buffer_put(buffer);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_dmaengine_buffer_free);
|
|
@ -4,6 +4,14 @@
|
|||
|
||||
menu "Chemical Sensors"
|
||||
|
||||
config IAQCORE
|
||||
tristate "AMS iAQ-Core VOC sensors"
|
||||
depends on I2C
|
||||
help
|
||||
Say Y here to build I2C interface support for the AMS
|
||||
iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds)
|
||||
sensors
|
||||
|
||||
config VZ89X
|
||||
tristate "SGX Sensortech MiCS VZ89X VOC sensor"
|
||||
depends on I2C
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
#
|
||||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
obj-$(CONFIG_IAQCORE) += ams-iaq-core.o
|
||||
obj-$(CONFIG_VZ89X) += vz89x.o
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* ams-iaq-core.c - Support for AMS iAQ-Core VOC sensors
|
||||
*
|
||||
* Copyright (C) 2015 Matt Ranostay <mranostay@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
#define AMS_IAQCORE_DATA_SIZE 9
|
||||
|
||||
#define AMS_IAQCORE_VOC_CO2_IDX 0
|
||||
#define AMS_IAQCORE_VOC_RESISTANCE_IDX 1
|
||||
#define AMS_IAQCORE_VOC_TVOC_IDX 2
|
||||
|
||||
struct ams_iaqcore_reading {
|
||||
__be16 co2_ppm;
|
||||
u8 status;
|
||||
__be32 resistance;
|
||||
__be16 voc_ppb;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct ams_iaqcore_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
unsigned long last_update;
|
||||
|
||||
struct ams_iaqcore_reading buffer;
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec ams_iaqcore_channels[] = {
|
||||
{
|
||||
.type = IIO_CONCENTRATION,
|
||||
.channel2 = IIO_MOD_CO2,
|
||||
.modified = 1,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
|
||||
.address = AMS_IAQCORE_VOC_CO2_IDX,
|
||||
},
|
||||
{
|
||||
.type = IIO_RESISTANCE,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
|
||||
.address = AMS_IAQCORE_VOC_RESISTANCE_IDX,
|
||||
},
|
||||
{
|
||||
.type = IIO_CONCENTRATION,
|
||||
.channel2 = IIO_MOD_VOC,
|
||||
.modified = 1,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
|
||||
.address = AMS_IAQCORE_VOC_TVOC_IDX,
|
||||
},
|
||||
};
|
||||
|
||||
static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
int ret;
|
||||
|
||||
struct i2c_msg msg = {
|
||||
.addr = client->addr,
|
||||
.flags = client->flags | I2C_M_RD,
|
||||
.len = AMS_IAQCORE_DATA_SIZE,
|
||||
.buf = (char *) &data->buffer,
|
||||
};
|
||||
|
||||
ret = i2c_transfer(client->adapter, &msg, 1);
|
||||
|
||||
return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret;
|
||||
}
|
||||
|
||||
static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* sensor can only be polled once a second max per datasheet */
|
||||
if (!time_after(jiffies, data->last_update + HZ))
|
||||
return 0;
|
||||
|
||||
ret = ams_iaqcore_read_measurement(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->last_update = jiffies;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ams_iaqcore_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val,
|
||||
int *val2, long mask)
|
||||
{
|
||||
struct ams_iaqcore_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (mask != IIO_CHAN_INFO_PROCESSED)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
ret = ams_iaqcore_get_measurement(data);
|
||||
|
||||
if (ret)
|
||||
goto err_out;
|
||||
|
||||
switch (chan->address) {
|
||||
case AMS_IAQCORE_VOC_CO2_IDX:
|
||||
*val = 0;
|
||||
*val2 = be16_to_cpu(data->buffer.co2_ppm);
|
||||
ret = IIO_VAL_INT_PLUS_MICRO;
|
||||
break;
|
||||
case AMS_IAQCORE_VOC_RESISTANCE_IDX:
|
||||
*val = be32_to_cpu(data->buffer.resistance);
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
case AMS_IAQCORE_VOC_TVOC_IDX:
|
||||
*val = 0;
|
||||
*val2 = be16_to_cpu(data->buffer.voc_ppb);
|
||||
ret = IIO_VAL_INT_PLUS_NANO;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
err_out:
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info ams_iaqcore_info = {
|
||||
.read_raw = ams_iaqcore_read_raw,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int ams_iaqcore_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
struct ams_iaqcore_data *data;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
|
||||
/* so initial reading will complete */
|
||||
data->last_update = jiffies - HZ;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &ams_iaqcore_info,
|
||||
indio_dev->name = dev_name(&client->dev);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
indio_dev->channels = ams_iaqcore_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels);
|
||||
|
||||
return devm_iio_device_register(&client->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ams_iaqcore_id[] = {
|
||||
{ "ams-iaq-core", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id);
|
||||
|
||||
static const struct of_device_id ams_iaqcore_dt_ids[] = {
|
||||
{ .compatible = "ams,iaq-core" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids);
|
||||
|
||||
static struct i2c_driver ams_iaqcore_driver = {
|
||||
.driver = {
|
||||
.name = "ams-iaq-core",
|
||||
.of_match_table = of_match_ptr(ams_iaqcore_dt_ids),
|
||||
},
|
||||
.probe = ams_iaqcore_probe,
|
||||
.id_table = ams_iaqcore_id,
|
||||
};
|
||||
module_i2c_driver(ams_iaqcore_driver);
|
||||
|
||||
MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
|
||||
MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -34,8 +34,9 @@
|
|||
struct vz89x_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
unsigned long last_update;
|
||||
int (*xfer)(struct vz89x_data *data, u8 cmd);
|
||||
|
||||
unsigned long last_update;
|
||||
u8 buffer[VZ89X_REG_MEASUREMENT_SIZE];
|
||||
};
|
||||
|
||||
|
@ -100,27 +101,60 @@ static int vz89x_measurement_is_valid(struct vz89x_data *data)
|
|||
return !!(data->buffer[VZ89X_REG_MEASUREMENT_SIZE - 1] > 0);
|
||||
}
|
||||
|
||||
static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
struct i2c_msg msg[2];
|
||||
int ret;
|
||||
u8 buf[3] = { cmd, 0, 0};
|
||||
|
||||
msg[0].addr = client->addr;
|
||||
msg[0].flags = client->flags;
|
||||
msg[0].len = 3;
|
||||
msg[0].buf = (char *) &buf;
|
||||
|
||||
msg[1].addr = client->addr;
|
||||
msg[1].flags = client->flags | I2C_M_RD;
|
||||
msg[1].len = VZ89X_REG_MEASUREMENT_SIZE;
|
||||
msg[1].buf = (char *) &data->buffer;
|
||||
|
||||
ret = i2c_transfer(client->adapter, msg, 2);
|
||||
|
||||
return (ret == 2) ? 0 : ret;
|
||||
}
|
||||
|
||||
static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
ret = i2c_smbus_write_word_data(client, cmd, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < VZ89X_REG_MEASUREMENT_SIZE; i++) {
|
||||
ret = i2c_smbus_read_byte(client);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->buffer[i] = ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vz89x_get_measurement(struct vz89x_data *data)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
/* sensor can only be polled once a second max per datasheet */
|
||||
if (!time_after(jiffies, data->last_update + HZ))
|
||||
return 0;
|
||||
|
||||
ret = i2c_smbus_write_word_data(data->client,
|
||||
VZ89X_REG_MEASUREMENT, 0);
|
||||
ret = data->xfer(data, VZ89X_REG_MEASUREMENT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < VZ89X_REG_MEASUREMENT_SIZE; i++) {
|
||||
ret = i2c_smbus_read_byte(data->client);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->buffer[i] = ret;
|
||||
}
|
||||
|
||||
ret = vz89x_measurement_is_valid(data);
|
||||
if (ret)
|
||||
return -EAGAIN;
|
||||
|
@ -204,15 +238,19 @@ static int vz89x_probe(struct i2c_client *client,
|
|||
struct iio_dev *indio_dev;
|
||||
struct vz89x_data *data;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA |
|
||||
I2C_FUNC_SMBUS_BYTE))
|
||||
return -ENODEV;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
|
||||
if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
|
||||
data->xfer = vz89x_i2c_xfer;
|
||||
else if (i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))
|
||||
data->xfer = vz89x_smbus_xfer;
|
||||
else
|
||||
return -ENOTSUPP;
|
||||
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
data->last_update = jiffies - HZ;
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
#include <asm/unaligned.h>
|
||||
#include <linux/iio/common/st_sensors.h>
|
||||
|
||||
|
||||
#define ST_SENSORS_WAI_ADDRESS 0x0f
|
||||
|
||||
static inline u32 st_sensors_get_unaligned_le24(const u8 *p)
|
||||
{
|
||||
return (s32)((p[0] | p[1] << 8 | p[2] << 16) << 8) >> 8;
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# Industrial I/O subsystem Dummy Driver configuration
|
||||
#
|
||||
menu "IIO dummy driver"
|
||||
depends on IIO
|
||||
|
||||
config IIO_DUMMY_EVGEN
|
||||
select IRQ_WORK
|
||||
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
|
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# Makefile for the IIO Dummy Driver
|
||||
#
|
||||
|
||||
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
|
|
@ -435,7 +435,9 @@ static int adis16136_initial_setup(struct iio_dev *indio_dev)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
sscanf(indio_dev->name, "adis%u\n", &device_id);
|
||||
ret = sscanf(indio_dev->name, "adis%u\n", &device_id);
|
||||
if (ret != 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (prod_id != device_id)
|
||||
dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.",
|
||||
|
|
|
@ -1077,25 +1077,23 @@ int bmg160_core_probe(struct device *dev, struct regmap *regmap, int irq,
|
|||
goto err_trigger_unregister;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "unable to register iio device\n");
|
||||
goto err_buffer_cleanup;
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(dev);
|
||||
if (ret)
|
||||
goto err_iio_unregister;
|
||||
goto err_buffer_cleanup;
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
pm_runtime_set_autosuspend_delay(dev,
|
||||
BMG160_AUTO_SUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "unable to register iio device\n");
|
||||
goto err_buffer_cleanup;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_iio_unregister:
|
||||
iio_device_unregister(indio_dev);
|
||||
err_buffer_cleanup:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
err_trigger_unregister:
|
||||
|
@ -1113,11 +1111,12 @@ void bmg160_core_remove(struct device *dev)
|
|||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct bmg160_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_set_suspended(dev);
|
||||
pm_runtime_put_noidle(dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
if (data->dready_trig) {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
#
|
||||
# Health sensors
|
||||
#
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
|
||||
menu "Health sensors"
|
||||
|
||||
config MAX30100
|
||||
tristate "MAX30100 heart rate and pulse oximeter sensor"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_KFIFO_BUF
|
||||
help
|
||||
Say Y here to build I2C interface support for the Maxim
|
||||
MAX30100 heart rate, and pulse oximeter sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called max30100.
|
||||
|
||||
endmenu
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Makefile for IIO Health sensors
|
||||
#
|
||||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
|
||||
obj-$(CONFIG_MAX30100) += max30100.o
|
|
@ -0,0 +1,453 @@
|
|||
/*
|
||||
* max30100.c - Support for MAX30100 heart rate and pulse oximeter sensor
|
||||
*
|
||||
* Copyright (C) 2015 Matt Ranostay <mranostay@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* TODO: allow LED current and pulse length controls via device tree properties
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
|
||||
#define MAX30100_REGMAP_NAME "max30100_regmap"
|
||||
#define MAX30100_DRV_NAME "max30100"
|
||||
|
||||
#define MAX30100_REG_INT_STATUS 0x00
|
||||
#define MAX30100_REG_INT_STATUS_PWR_RDY BIT(0)
|
||||
#define MAX30100_REG_INT_STATUS_SPO2_RDY BIT(4)
|
||||
#define MAX30100_REG_INT_STATUS_HR_RDY BIT(5)
|
||||
#define MAX30100_REG_INT_STATUS_FIFO_RDY BIT(7)
|
||||
|
||||
#define MAX30100_REG_INT_ENABLE 0x01
|
||||
#define MAX30100_REG_INT_ENABLE_SPO2_EN BIT(0)
|
||||
#define MAX30100_REG_INT_ENABLE_HR_EN BIT(1)
|
||||
#define MAX30100_REG_INT_ENABLE_FIFO_EN BIT(3)
|
||||
#define MAX30100_REG_INT_ENABLE_MASK 0xf0
|
||||
#define MAX30100_REG_INT_ENABLE_MASK_SHIFT 4
|
||||
|
||||
#define MAX30100_REG_FIFO_WR_PTR 0x02
|
||||
#define MAX30100_REG_FIFO_OVR_CTR 0x03
|
||||
#define MAX30100_REG_FIFO_RD_PTR 0x04
|
||||
#define MAX30100_REG_FIFO_DATA 0x05
|
||||
#define MAX30100_REG_FIFO_DATA_ENTRY_COUNT 16
|
||||
#define MAX30100_REG_FIFO_DATA_ENTRY_LEN 4
|
||||
|
||||
#define MAX30100_REG_MODE_CONFIG 0x06
|
||||
#define MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN BIT(0)
|
||||
#define MAX30100_REG_MODE_CONFIG_MODE_HR_EN BIT(1)
|
||||
#define MAX30100_REG_MODE_CONFIG_MODE_MASK 0x03
|
||||
#define MAX30100_REG_MODE_CONFIG_TEMP_EN BIT(3)
|
||||
#define MAX30100_REG_MODE_CONFIG_PWR BIT(7)
|
||||
|
||||
#define MAX30100_REG_SPO2_CONFIG 0x07
|
||||
#define MAX30100_REG_SPO2_CONFIG_100HZ BIT(2)
|
||||
#define MAX30100_REG_SPO2_CONFIG_HI_RES_EN BIT(6)
|
||||
#define MAX30100_REG_SPO2_CONFIG_1600US 0x3
|
||||
|
||||
#define MAX30100_REG_LED_CONFIG 0x09
|
||||
#define MAX30100_REG_LED_CONFIG_RED_LED_SHIFT 4
|
||||
|
||||
#define MAX30100_REG_LED_CONFIG_24MA 0x07
|
||||
#define MAX30100_REG_LED_CONFIG_50MA 0x0f
|
||||
|
||||
#define MAX30100_REG_TEMP_INTEGER 0x16
|
||||
#define MAX30100_REG_TEMP_FRACTION 0x17
|
||||
|
||||
struct max30100_data {
|
||||
struct i2c_client *client;
|
||||
struct iio_dev *indio_dev;
|
||||
struct mutex lock;
|
||||
struct regmap *regmap;
|
||||
|
||||
__be16 buffer[2]; /* 2 16-bit channels */
|
||||
};
|
||||
|
||||
static bool max30100_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case MAX30100_REG_INT_STATUS:
|
||||
case MAX30100_REG_MODE_CONFIG:
|
||||
case MAX30100_REG_FIFO_WR_PTR:
|
||||
case MAX30100_REG_FIFO_OVR_CTR:
|
||||
case MAX30100_REG_FIFO_RD_PTR:
|
||||
case MAX30100_REG_FIFO_DATA:
|
||||
case MAX30100_REG_TEMP_INTEGER:
|
||||
case MAX30100_REG_TEMP_FRACTION:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct regmap_config max30100_regmap_config = {
|
||||
.name = MAX30100_REGMAP_NAME,
|
||||
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.max_register = MAX30100_REG_TEMP_FRACTION,
|
||||
.cache_type = REGCACHE_FLAT,
|
||||
|
||||
.volatile_reg = max30100_is_volatile_reg,
|
||||
};
|
||||
|
||||
static const unsigned long max30100_scan_masks[] = {0x3, 0};
|
||||
|
||||
static const struct iio_chan_spec max30100_channels[] = {
|
||||
{
|
||||
.type = IIO_INTENSITY,
|
||||
.channel2 = IIO_MOD_LIGHT_IR,
|
||||
.modified = 1,
|
||||
|
||||
.scan_index = 0,
|
||||
.scan_type = {
|
||||
.sign = 'u',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_BE,
|
||||
},
|
||||
},
|
||||
{
|
||||
.type = IIO_INTENSITY,
|
||||
.channel2 = IIO_MOD_LIGHT_RED,
|
||||
.modified = 1,
|
||||
|
||||
.scan_index = 1,
|
||||
.scan_type = {
|
||||
.sign = 'u',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_BE,
|
||||
},
|
||||
},
|
||||
{
|
||||
.type = IIO_TEMP,
|
||||
.info_mask_separate =
|
||||
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
|
||||
.scan_index = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static int max30100_set_powermode(struct max30100_data *data, bool state)
|
||||
{
|
||||
return regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
|
||||
MAX30100_REG_MODE_CONFIG_PWR,
|
||||
state ? 0 : MAX30100_REG_MODE_CONFIG_PWR);
|
||||
}
|
||||
|
||||
static int max30100_clear_fifo(struct max30100_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(data->regmap, MAX30100_REG_FIFO_WR_PTR, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(data->regmap, MAX30100_REG_FIFO_OVR_CTR, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return regmap_write(data->regmap, MAX30100_REG_FIFO_RD_PTR, 0);
|
||||
}
|
||||
|
||||
static int max30100_buffer_postenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct max30100_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = max30100_set_powermode(data, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return max30100_clear_fifo(data);
|
||||
}
|
||||
|
||||
static int max30100_buffer_predisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct max30100_data *data = iio_priv(indio_dev);
|
||||
|
||||
return max30100_set_powermode(data, false);
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops max30100_buffer_setup_ops = {
|
||||
.postenable = max30100_buffer_postenable,
|
||||
.predisable = max30100_buffer_predisable,
|
||||
};
|
||||
|
||||
static inline int max30100_fifo_count(struct max30100_data *data)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(data->regmap, MAX30100_REG_INT_STATUS, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* FIFO is almost full */
|
||||
if (val & MAX30100_REG_INT_STATUS_FIFO_RDY)
|
||||
return MAX30100_REG_FIFO_DATA_ENTRY_COUNT - 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max30100_read_measurement(struct max30100_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_i2c_block_data(data->client,
|
||||
MAX30100_REG_FIFO_DATA,
|
||||
MAX30100_REG_FIFO_DATA_ENTRY_LEN,
|
||||
(u8 *) &data->buffer);
|
||||
|
||||
return (ret == MAX30100_REG_FIFO_DATA_ENTRY_LEN) ? 0 : ret;
|
||||
}
|
||||
|
||||
static irqreturn_t max30100_interrupt_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_dev *indio_dev = private;
|
||||
struct max30100_data *data = iio_priv(indio_dev);
|
||||
int ret, cnt = 0;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
while (cnt-- || (cnt = max30100_fifo_count(data) > 0)) {
|
||||
ret = max30100_read_measurement(data);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
iio_push_to_buffers(data->indio_dev, data->buffer);
|
||||
}
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int max30100_chip_init(struct max30100_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* RED IR LED = 24mA, IR LED = 50mA */
|
||||
ret = regmap_write(data->regmap, MAX30100_REG_LED_CONFIG,
|
||||
(MAX30100_REG_LED_CONFIG_24MA <<
|
||||
MAX30100_REG_LED_CONFIG_RED_LED_SHIFT) |
|
||||
MAX30100_REG_LED_CONFIG_50MA);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* enable hi-res SPO2 readings at 100Hz */
|
||||
ret = regmap_write(data->regmap, MAX30100_REG_SPO2_CONFIG,
|
||||
MAX30100_REG_SPO2_CONFIG_HI_RES_EN |
|
||||
MAX30100_REG_SPO2_CONFIG_100HZ);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* enable SPO2 mode */
|
||||
ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
|
||||
MAX30100_REG_MODE_CONFIG_MODE_MASK,
|
||||
MAX30100_REG_MODE_CONFIG_MODE_HR_EN |
|
||||
MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* enable FIFO interrupt */
|
||||
return regmap_update_bits(data->regmap, MAX30100_REG_INT_ENABLE,
|
||||
MAX30100_REG_INT_ENABLE_MASK,
|
||||
MAX30100_REG_INT_ENABLE_FIFO_EN
|
||||
<< MAX30100_REG_INT_ENABLE_MASK_SHIFT);
|
||||
}
|
||||
|
||||
static int max30100_read_temp(struct max30100_data *data, int *val)
|
||||
{
|
||||
int ret;
|
||||
unsigned int reg;
|
||||
|
||||
ret = regmap_read(data->regmap, MAX30100_REG_TEMP_INTEGER, ®);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = reg << 4;
|
||||
|
||||
ret = regmap_read(data->regmap, MAX30100_REG_TEMP_FRACTION, ®);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val |= reg & 0xf;
|
||||
*val = sign_extend32(*val, 11);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max30100_get_temp(struct max30100_data *data, int *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* start acquisition */
|
||||
ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG,
|
||||
MAX30100_REG_MODE_CONFIG_TEMP_EN,
|
||||
MAX30100_REG_MODE_CONFIG_TEMP_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
usleep_range(35000, 50000);
|
||||
|
||||
return max30100_read_temp(data, val);
|
||||
}
|
||||
|
||||
static int max30100_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct max30100_data *data = iio_priv(indio_dev);
|
||||
int ret = -EINVAL;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
/*
|
||||
* Temperature reading can only be acquired while engine
|
||||
* is running
|
||||
*/
|
||||
mutex_lock(&indio_dev->mlock);
|
||||
|
||||
if (!iio_buffer_enabled(indio_dev))
|
||||
ret = -EAGAIN;
|
||||
else {
|
||||
ret = max30100_get_temp(data, val);
|
||||
if (!ret)
|
||||
ret = IIO_VAL_INT;
|
||||
|
||||
}
|
||||
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 1; /* 0.0625 */
|
||||
*val2 = 16;
|
||||
ret = IIO_VAL_FRACTIONAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info max30100_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = max30100_read_raw,
|
||||
};
|
||||
|
||||
static int max30100_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct max30100_data *data;
|
||||
struct iio_buffer *buffer;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
buffer = devm_iio_kfifo_allocate(&client->dev);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
iio_device_attach_buffer(indio_dev, buffer);
|
||||
|
||||
indio_dev->name = MAX30100_DRV_NAME;
|
||||
indio_dev->channels = max30100_channels;
|
||||
indio_dev->info = &max30100_info;
|
||||
indio_dev->num_channels = ARRAY_SIZE(max30100_channels);
|
||||
indio_dev->available_scan_masks = max30100_scan_masks;
|
||||
indio_dev->modes = (INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE);
|
||||
indio_dev->setup_ops = &max30100_buffer_setup_ops;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->indio_dev = indio_dev;
|
||||
data->client = client;
|
||||
|
||||
mutex_init(&data->lock);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
data->regmap = devm_regmap_init_i2c(client, &max30100_regmap_config);
|
||||
if (IS_ERR(data->regmap)) {
|
||||
dev_err(&client->dev, "regmap initialization failed.\n");
|
||||
return PTR_ERR(data->regmap);
|
||||
}
|
||||
max30100_set_powermode(data, false);
|
||||
|
||||
ret = max30100_chip_init(data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (client->irq <= 0) {
|
||||
dev_err(&client->dev, "no valid irq defined\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = devm_request_threaded_irq(&client->dev, client->irq,
|
||||
NULL, max30100_interrupt_handler,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
"max30100_irq", indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "request irq (%d) failed\n", client->irq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return iio_device_register(indio_dev);
|
||||
}
|
||||
|
||||
static int max30100_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct max30100_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
max30100_set_powermode(data, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max30100_id[] = {
|
||||
{ "max30100", 0 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max30100_id);
|
||||
|
||||
static const struct of_device_id max30100_dt_ids[] = {
|
||||
{ .compatible = "maxim,max30100" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, max30100_dt_ids);
|
||||
|
||||
static struct i2c_driver max30100_driver = {
|
||||
.driver = {
|
||||
.name = MAX30100_DRV_NAME,
|
||||
.of_match_table = of_match_ptr(max30100_dt_ids),
|
||||
},
|
||||
.probe = max30100_probe,
|
||||
.remove = max30100_remove,
|
||||
.id_table = max30100_id,
|
||||
};
|
||||
module_i2c_driver(max30100_driver);
|
||||
|
||||
MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
|
||||
MODULE_DESCRIPTION("MAX30100 heart rate and pulse oximeter sensor");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -288,7 +288,11 @@ static int adis16400_initial_setup(struct iio_dev *indio_dev)
|
|||
if (ret)
|
||||
goto err_ret;
|
||||
|
||||
sscanf(indio_dev->name, "adis%u\n", &device_id);
|
||||
ret = sscanf(indio_dev->name, "adis%u\n", &device_id);
|
||||
if (ret != 1) {
|
||||
ret = -EINVAL;
|
||||
goto err_ret;
|
||||
}
|
||||
|
||||
if (prod_id != device_id)
|
||||
dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.",
|
||||
|
|
|
@ -765,7 +765,9 @@ static int adis16480_initial_setup(struct iio_dev *indio_dev)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
sscanf(indio_dev->name, "adis%u\n", &device_id);
|
||||
ret = sscanf(indio_dev->name, "adis%u\n", &device_id);
|
||||
if (ret != 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (prod_id != device_id)
|
||||
dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.",
|
||||
|
|
|
@ -1390,6 +1390,14 @@ static int kmx61_probe(struct i2c_client *client,
|
|||
}
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret < 0)
|
||||
goto err_buffer_cleanup_mag;
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev, KMX61_SLEEP_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
ret = iio_device_register(data->acc_indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to register acc iio device\n");
|
||||
|
@ -1402,18 +1410,8 @@ static int kmx61_probe(struct i2c_client *client,
|
|||
goto err_iio_unregister_acc;
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret < 0)
|
||||
goto err_iio_unregister_mag;
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev, KMX61_SLEEP_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_iio_unregister_mag:
|
||||
iio_device_unregister(data->mag_indio_dev);
|
||||
err_iio_unregister_acc:
|
||||
iio_device_unregister(data->acc_indio_dev);
|
||||
err_buffer_cleanup_mag:
|
||||
|
@ -1437,13 +1435,13 @@ static int kmx61_remove(struct i2c_client *client)
|
|||
{
|
||||
struct kmx61_data *data = i2c_get_clientdata(client);
|
||||
|
||||
iio_device_unregister(data->acc_indio_dev);
|
||||
iio_device_unregister(data->mag_indio_dev);
|
||||
|
||||
pm_runtime_disable(&client->dev);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
pm_runtime_put_noidle(&client->dev);
|
||||
|
||||
iio_device_unregister(data->acc_indio_dev);
|
||||
iio_device_unregister(data->mag_indio_dev);
|
||||
|
||||
if (client->irq > 0) {
|
||||
iio_triggered_buffer_cleanup(data->acc_indio_dev);
|
||||
iio_triggered_buffer_cleanup(data->mag_indio_dev);
|
||||
|
|
|
@ -193,7 +193,8 @@ void iio_buffer_init(struct iio_buffer *buffer)
|
|||
INIT_LIST_HEAD(&buffer->buffer_list);
|
||||
init_waitqueue_head(&buffer->pollq);
|
||||
kref_init(&buffer->ref);
|
||||
buffer->watermark = 1;
|
||||
if (!buffer->watermark)
|
||||
buffer->watermark = 1;
|
||||
}
|
||||
EXPORT_SYMBOL(iio_buffer_init);
|
||||
|
||||
|
@ -567,6 +568,22 @@ static void iio_buffer_deactivate_all(struct iio_dev *indio_dev)
|
|||
iio_buffer_deactivate(buffer);
|
||||
}
|
||||
|
||||
static int iio_buffer_enable(struct iio_buffer *buffer,
|
||||
struct iio_dev *indio_dev)
|
||||
{
|
||||
if (!buffer->access->enable)
|
||||
return 0;
|
||||
return buffer->access->enable(buffer, indio_dev);
|
||||
}
|
||||
|
||||
static int iio_buffer_disable(struct iio_buffer *buffer,
|
||||
struct iio_dev *indio_dev)
|
||||
{
|
||||
if (!buffer->access->disable)
|
||||
return 0;
|
||||
return buffer->access->disable(buffer, indio_dev);
|
||||
}
|
||||
|
||||
static void iio_buffer_update_bytes_per_datum(struct iio_dev *indio_dev,
|
||||
struct iio_buffer *buffer)
|
||||
{
|
||||
|
@ -610,6 +627,7 @@ static void iio_free_scan_mask(struct iio_dev *indio_dev,
|
|||
|
||||
struct iio_device_config {
|
||||
unsigned int mode;
|
||||
unsigned int watermark;
|
||||
const unsigned long *scan_mask;
|
||||
unsigned int scan_bytes;
|
||||
bool scan_timestamp;
|
||||
|
@ -642,10 +660,14 @@ static int iio_verify_update(struct iio_dev *indio_dev,
|
|||
if (buffer == remove_buffer)
|
||||
continue;
|
||||
modes &= buffer->access->modes;
|
||||
config->watermark = min(config->watermark, buffer->watermark);
|
||||
}
|
||||
|
||||
if (insert_buffer)
|
||||
if (insert_buffer) {
|
||||
modes &= insert_buffer->access->modes;
|
||||
config->watermark = min(config->watermark,
|
||||
insert_buffer->watermark);
|
||||
}
|
||||
|
||||
/* Definitely possible for devices to support both of these. */
|
||||
if ((modes & INDIO_BUFFER_TRIGGERED) && indio_dev->trig) {
|
||||
|
@ -713,6 +735,7 @@ static int iio_verify_update(struct iio_dev *indio_dev,
|
|||
static int iio_enable_buffers(struct iio_dev *indio_dev,
|
||||
struct iio_device_config *config)
|
||||
{
|
||||
struct iio_buffer *buffer;
|
||||
int ret;
|
||||
|
||||
indio_dev->active_scan_mask = config->scan_mask;
|
||||
|
@ -743,6 +766,16 @@ static int iio_enable_buffers(struct iio_dev *indio_dev,
|
|||
}
|
||||
}
|
||||
|
||||
if (indio_dev->info->hwfifo_set_watermark)
|
||||
indio_dev->info->hwfifo_set_watermark(indio_dev,
|
||||
config->watermark);
|
||||
|
||||
list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) {
|
||||
ret = iio_buffer_enable(buffer, indio_dev);
|
||||
if (ret)
|
||||
goto err_disable_buffers;
|
||||
}
|
||||
|
||||
indio_dev->currentmode = config->mode;
|
||||
|
||||
if (indio_dev->setup_ops->postenable) {
|
||||
|
@ -750,12 +783,16 @@ static int iio_enable_buffers(struct iio_dev *indio_dev,
|
|||
if (ret) {
|
||||
dev_dbg(&indio_dev->dev,
|
||||
"Buffer not started: postenable failed (%d)\n", ret);
|
||||
goto err_run_postdisable;
|
||||
goto err_disable_buffers;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_buffers:
|
||||
list_for_each_entry_continue_reverse(buffer, &indio_dev->buffer_list,
|
||||
buffer_list)
|
||||
iio_buffer_disable(buffer, indio_dev);
|
||||
err_run_postdisable:
|
||||
indio_dev->currentmode = INDIO_DIRECT_MODE;
|
||||
if (indio_dev->setup_ops->postdisable)
|
||||
|
@ -768,6 +805,7 @@ err_undo_config:
|
|||
|
||||
static int iio_disable_buffers(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct iio_buffer *buffer;
|
||||
int ret = 0;
|
||||
int ret2;
|
||||
|
||||
|
@ -788,6 +826,12 @@ static int iio_disable_buffers(struct iio_dev *indio_dev)
|
|||
ret = ret2;
|
||||
}
|
||||
|
||||
list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) {
|
||||
ret2 = iio_buffer_disable(buffer, indio_dev);
|
||||
if (ret2 && !ret)
|
||||
ret = ret2;
|
||||
}
|
||||
|
||||
indio_dev->currentmode = INDIO_DIRECT_MODE;
|
||||
|
||||
if (indio_dev->setup_ops->postdisable) {
|
||||
|
@ -974,9 +1018,6 @@ static ssize_t iio_buffer_store_watermark(struct device *dev,
|
|||
}
|
||||
|
||||
buffer->watermark = val;
|
||||
|
||||
if (indio_dev->info->hwfifo_set_watermark)
|
||||
indio_dev->info->hwfifo_set_watermark(indio_dev, val);
|
||||
out:
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
|
||||
|
@ -991,6 +1032,8 @@ static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR,
|
|||
iio_buffer_show_enable, iio_buffer_store_enable);
|
||||
static DEVICE_ATTR(watermark, S_IRUGO | S_IWUSR,
|
||||
iio_buffer_show_watermark, iio_buffer_store_watermark);
|
||||
static struct device_attribute dev_attr_watermark_ro = __ATTR(watermark,
|
||||
S_IRUGO, iio_buffer_show_watermark, NULL);
|
||||
|
||||
static struct attribute *iio_buffer_attrs[] = {
|
||||
&dev_attr_length.attr,
|
||||
|
@ -1033,6 +1076,9 @@ int iio_buffer_alloc_sysfs_and_mask(struct iio_dev *indio_dev)
|
|||
if (!buffer->access->set_length)
|
||||
attr[0] = &dev_attr_length_ro.attr;
|
||||
|
||||
if (buffer->access->flags & INDIO_BUFFER_FLAG_FIXED_WATERMARK)
|
||||
attr[2] = &dev_attr_watermark_ro.attr;
|
||||
|
||||
if (buffer->attrs)
|
||||
memcpy(&attr[ARRAY_SIZE(iio_buffer_attrs)], buffer->attrs,
|
||||
sizeof(struct attribute *) * attrcount);
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Industrial I/O configfs bits
|
||||
*
|
||||
* Copyright (c) 2015 Intel Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/configfs.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/configfs.h>
|
||||
|
||||
static struct config_item_type iio_root_group_type = {
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
struct configfs_subsystem iio_configfs_subsys = {
|
||||
.su_group = {
|
||||
.cg_item = {
|
||||
.ci_namebuf = "iio",
|
||||
.ci_type = &iio_root_group_type,
|
||||
},
|
||||
},
|
||||
.su_mutex = __MUTEX_INITIALIZER(iio_configfs_subsys.su_mutex),
|
||||
};
|
||||
EXPORT_SYMBOL(iio_configfs_subsys);
|
||||
|
||||
static int __init iio_configfs_init(void)
|
||||
{
|
||||
config_group_init(&iio_configfs_subsys.su_group);
|
||||
|
||||
return configfs_register_subsystem(&iio_configfs_subsys);
|
||||
}
|
||||
module_init(iio_configfs_init);
|
||||
|
||||
static void __exit iio_configfs_exit(void)
|
||||
{
|
||||
configfs_unregister_subsystem(&iio_configfs_subsys);
|
||||
}
|
||||
module_exit(iio_configfs_exit);
|
||||
|
||||
MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>");
|
||||
MODULE_DESCRIPTION("Industrial I/O configfs support");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -470,6 +470,7 @@ ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals)
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_format_value);
|
||||
|
||||
static ssize_t iio_read_channel_info(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
|
@ -512,6 +513,12 @@ int iio_str_to_fixpoint(const char *str, int fract_mult,
|
|||
int i = 0, f = 0;
|
||||
bool integer_part = true, negative = false;
|
||||
|
||||
if (fract_mult == 0) {
|
||||
*fract = 0;
|
||||
|
||||
return kstrtoint(str, 0, integer);
|
||||
}
|
||||
|
||||
if (str[0] == '-') {
|
||||
negative = true;
|
||||
str++;
|
||||
|
@ -571,6 +578,9 @@ static ssize_t iio_write_channel_info(struct device *dev,
|
|||
if (indio_dev->info->write_raw_get_fmt)
|
||||
switch (indio_dev->info->write_raw_get_fmt(indio_dev,
|
||||
this_attr->c, this_attr->address)) {
|
||||
case IIO_VAL_INT:
|
||||
fract_mult = 0;
|
||||
break;
|
||||
case IIO_VAL_INT_PLUS_MICRO:
|
||||
fract_mult = 100000;
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* The Industrial I/O core, software trigger functions
|
||||
*
|
||||
* Copyright (c) 2015 Intel Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/iio/sw_trigger.h>
|
||||
#include <linux/iio/configfs.h>
|
||||
#include <linux/configfs.h>
|
||||
|
||||
static struct config_group *iio_triggers_group;
|
||||
static struct config_item_type iio_trigger_type_group_type;
|
||||
|
||||
static struct config_item_type iio_triggers_group_type = {
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static LIST_HEAD(iio_trigger_types_list);
|
||||
static DEFINE_MUTEX(iio_trigger_types_lock);
|
||||
|
||||
static
|
||||
struct iio_sw_trigger_type *__iio_find_sw_trigger_type(const char *name,
|
||||
unsigned len)
|
||||
{
|
||||
struct iio_sw_trigger_type *t = NULL, *iter;
|
||||
|
||||
list_for_each_entry(iter, &iio_trigger_types_list, list)
|
||||
if (!strcmp(iter->name, name)) {
|
||||
t = iter;
|
||||
break;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
int iio_register_sw_trigger_type(struct iio_sw_trigger_type *t)
|
||||
{
|
||||
struct iio_sw_trigger_type *iter;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&iio_trigger_types_lock);
|
||||
iter = __iio_find_sw_trigger_type(t->name, strlen(t->name));
|
||||
if (iter)
|
||||
ret = -EBUSY;
|
||||
else
|
||||
list_add_tail(&t->list, &iio_trigger_types_list);
|
||||
mutex_unlock(&iio_trigger_types_lock);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
t->group = configfs_register_default_group(iio_triggers_group, t->name,
|
||||
&iio_trigger_type_group_type);
|
||||
if (IS_ERR(t->group))
|
||||
ret = PTR_ERR(t->group);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(iio_register_sw_trigger_type);
|
||||
|
||||
void iio_unregister_sw_trigger_type(struct iio_sw_trigger_type *t)
|
||||
{
|
||||
struct iio_sw_trigger_type *iter;
|
||||
|
||||
mutex_lock(&iio_trigger_types_lock);
|
||||
iter = __iio_find_sw_trigger_type(t->name, strlen(t->name));
|
||||
if (iter)
|
||||
list_del(&t->list);
|
||||
mutex_unlock(&iio_trigger_types_lock);
|
||||
|
||||
configfs_unregister_default_group(t->group);
|
||||
}
|
||||
EXPORT_SYMBOL(iio_unregister_sw_trigger_type);
|
||||
|
||||
static
|
||||
struct iio_sw_trigger_type *iio_get_sw_trigger_type(const char *name)
|
||||
{
|
||||
struct iio_sw_trigger_type *t;
|
||||
|
||||
mutex_lock(&iio_trigger_types_lock);
|
||||
t = __iio_find_sw_trigger_type(name, strlen(name));
|
||||
if (t && !try_module_get(t->owner))
|
||||
t = NULL;
|
||||
mutex_unlock(&iio_trigger_types_lock);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
struct iio_sw_trigger *iio_sw_trigger_create(const char *type, const char *name)
|
||||
{
|
||||
struct iio_sw_trigger *t;
|
||||
struct iio_sw_trigger_type *tt;
|
||||
|
||||
tt = iio_get_sw_trigger_type(type);
|
||||
if (!tt) {
|
||||
pr_err("Invalid trigger type: %s\n", type);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
t = tt->ops->probe(name);
|
||||
if (IS_ERR(t))
|
||||
goto out_module_put;
|
||||
|
||||
t->trigger_type = tt;
|
||||
|
||||
return t;
|
||||
out_module_put:
|
||||
module_put(tt->owner);
|
||||
return t;
|
||||
}
|
||||
EXPORT_SYMBOL(iio_sw_trigger_create);
|
||||
|
||||
void iio_sw_trigger_destroy(struct iio_sw_trigger *t)
|
||||
{
|
||||
struct iio_sw_trigger_type *tt = t->trigger_type;
|
||||
|
||||
tt->ops->remove(t);
|
||||
module_put(tt->owner);
|
||||
}
|
||||
EXPORT_SYMBOL(iio_sw_trigger_destroy);
|
||||
|
||||
static struct config_group *trigger_make_group(struct config_group *group,
|
||||
const char *name)
|
||||
{
|
||||
struct iio_sw_trigger *t;
|
||||
|
||||
t = iio_sw_trigger_create(group->cg_item.ci_name, name);
|
||||
if (IS_ERR(t))
|
||||
return ERR_CAST(t);
|
||||
|
||||
config_item_set_name(&t->group.cg_item, "%s", name);
|
||||
|
||||
return &t->group;
|
||||
}
|
||||
|
||||
static void trigger_drop_group(struct config_group *group,
|
||||
struct config_item *item)
|
||||
{
|
||||
struct iio_sw_trigger *t = to_iio_sw_trigger(item);
|
||||
|
||||
iio_sw_trigger_destroy(t);
|
||||
config_item_put(item);
|
||||
}
|
||||
|
||||
static struct configfs_group_operations trigger_ops = {
|
||||
.make_group = &trigger_make_group,
|
||||
.drop_item = &trigger_drop_group,
|
||||
};
|
||||
|
||||
static struct config_item_type iio_trigger_type_group_type = {
|
||||
.ct_group_ops = &trigger_ops,
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init iio_sw_trigger_init(void)
|
||||
{
|
||||
iio_triggers_group =
|
||||
configfs_register_default_group(&iio_configfs_subsys.su_group,
|
||||
"triggers",
|
||||
&iio_triggers_group_type);
|
||||
if (IS_ERR(iio_triggers_group))
|
||||
return PTR_ERR(iio_triggers_group);
|
||||
return 0;
|
||||
}
|
||||
module_init(iio_sw_trigger_init);
|
||||
|
||||
static void __exit iio_sw_trigger_exit(void)
|
||||
{
|
||||
configfs_unregister_default_group(iio_triggers_group);
|
||||
}
|
||||
module_exit(iio_sw_trigger_exit);
|
||||
|
||||
MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>");
|
||||
MODULE_DESCRIPTION("Industrial I/O software triggers support");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -61,12 +61,10 @@ EXPORT_SYMBOL_GPL(iio_map_array_register);
|
|||
int iio_map_array_unregister(struct iio_dev *indio_dev)
|
||||
{
|
||||
int ret = -ENODEV;
|
||||
struct iio_map_internal *mapi;
|
||||
struct list_head *pos, *tmp;
|
||||
struct iio_map_internal *mapi, *next;
|
||||
|
||||
mutex_lock(&iio_map_list_lock);
|
||||
list_for_each_safe(pos, tmp, &iio_map_list) {
|
||||
mapi = list_entry(pos, struct iio_map_internal, l);
|
||||
list_for_each_entry_safe(mapi, next, &iio_map_list, l) {
|
||||
if (indio_dev == mapi->indio_dev) {
|
||||
list_del(&mapi->l);
|
||||
kfree(mapi);
|
||||
|
|
|
@ -743,8 +743,10 @@ static int lm3533_als_set_resistor(struct lm3533_als *als, u8 val)
|
|||
{
|
||||
int ret;
|
||||
|
||||
if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
|
||||
if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX) {
|
||||
dev_err(&als->pdev->dev, "invalid resistor value\n");
|
||||
return -EINVAL;
|
||||
};
|
||||
|
||||
ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
|
||||
if (ret) {
|
||||
|
|
|
@ -381,17 +381,23 @@ static int pa12203001_probe(struct i2c_client *client,
|
|||
return ret;
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret < 0) {
|
||||
pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE);
|
||||
return ret;
|
||||
}
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev,
|
||||
PA12203001_SLEEP_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
return iio_device_register(indio_dev);
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pa12203001_remove(struct i2c_client *client)
|
||||
|
|
|
@ -507,34 +507,28 @@ static int rpr0521_probe(struct i2c_client *client,
|
|||
dev_err(&client->dev, "rpr0521 chip init failed\n");
|
||||
return ret;
|
||||
}
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret < 0)
|
||||
goto err_iio_unregister;
|
||||
return ret;
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev, RPR0521_SLEEP_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_iio_unregister:
|
||||
iio_device_unregister(indio_dev);
|
||||
return ret;
|
||||
return iio_device_register(indio_dev);
|
||||
}
|
||||
|
||||
static int rpr0521_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
pm_runtime_disable(&client->dev);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
pm_runtime_put_noidle(&client->dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
rpr0521_poweroff(iio_priv(indio_dev));
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -20,14 +20,21 @@
|
|||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iio/events.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#define US5182D_REG_CFG0 0x00
|
||||
#define US5182D_CFG0_ONESHOT_EN BIT(6)
|
||||
#define US5182D_CFG0_SHUTDOWN_EN BIT(7)
|
||||
#define US5182D_CFG0_WORD_ENABLE BIT(0)
|
||||
#define US5182D_CFG0_PROX BIT(3)
|
||||
#define US5182D_CFG0_PX_IRQ BIT(2)
|
||||
|
||||
#define US5182D_REG_CFG1 0x01
|
||||
#define US5182D_CFG1_ALS_RES16 BIT(4)
|
||||
|
@ -39,6 +46,7 @@
|
|||
|
||||
#define US5182D_REG_CFG3 0x03
|
||||
#define US5182D_CFG3_LED_CURRENT100 (BIT(4) | BIT(5))
|
||||
#define US5182D_CFG3_INT_SOURCE_PX BIT(3)
|
||||
|
||||
#define US5182D_REG_CFG4 0x10
|
||||
|
||||
|
@ -53,6 +61,13 @@
|
|||
#define US5182D_REG_AUTO_LDARK_GAIN 0x29
|
||||
#define US5182D_REG_AUTO_HDARK_GAIN 0x2a
|
||||
|
||||
/* Thresholds for events: px low (0x08-l, 0x09-h), px high (0x0a-l 0x0b-h) */
|
||||
#define US5182D_REG_PXL_TH 0x08
|
||||
#define US5182D_REG_PXH_TH 0x0a
|
||||
|
||||
#define US5182D_REG_PXL_TH_DEFAULT 1000
|
||||
#define US5182D_REG_PXH_TH_DEFAULT 30000
|
||||
|
||||
#define US5182D_OPMODE_ALS 0x01
|
||||
#define US5182D_OPMODE_PX 0x02
|
||||
#define US5182D_OPMODE_SHIFT 4
|
||||
|
@ -81,6 +96,9 @@
|
|||
#define US5182D_READ_BYTE 1
|
||||
#define US5182D_READ_WORD 2
|
||||
#define US5182D_OPSTORE_SLEEP_TIME 20 /* ms */
|
||||
#define US5182D_SLEEP_MS 3000 /* ms */
|
||||
#define US5182D_PXH_TH_DISABLE 0xffff
|
||||
#define US5182D_PXL_TH_DISABLE 0x0000
|
||||
|
||||
/* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */
|
||||
static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600,
|
||||
|
@ -99,6 +117,11 @@ enum mode {
|
|||
US5182D_PX_ONLY
|
||||
};
|
||||
|
||||
enum pmode {
|
||||
US5182D_CONTINUOUS,
|
||||
US5182D_ONESHOT
|
||||
};
|
||||
|
||||
struct us5182d_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
|
@ -111,7 +134,19 @@ struct us5182d_data {
|
|||
u8 upper_dark_gain;
|
||||
u16 *us5182d_dark_ths;
|
||||
|
||||
u16 px_low_th;
|
||||
u16 px_high_th;
|
||||
|
||||
int rising_en;
|
||||
int falling_en;
|
||||
|
||||
u8 opmode;
|
||||
u8 power_mode;
|
||||
|
||||
bool als_enabled;
|
||||
bool px_enabled;
|
||||
|
||||
bool default_continuous;
|
||||
};
|
||||
|
||||
static IIO_CONST_ATTR(in_illuminance_scale_available,
|
||||
|
@ -130,16 +165,30 @@ static const struct {
|
|||
u8 reg;
|
||||
u8 val;
|
||||
} us5182d_regvals[] = {
|
||||
{US5182D_REG_CFG0, (US5182D_CFG0_SHUTDOWN_EN |
|
||||
US5182D_CFG0_WORD_ENABLE)},
|
||||
{US5182D_REG_CFG0, US5182D_CFG0_WORD_ENABLE},
|
||||
{US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16},
|
||||
{US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 |
|
||||
US5182D_CFG2_PXGAIN_DEFAULT)},
|
||||
{US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100},
|
||||
{US5182D_REG_MODE_STORE, US5182D_STORE_MODE},
|
||||
{US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100 |
|
||||
US5182D_CFG3_INT_SOURCE_PX},
|
||||
{US5182D_REG_CFG4, 0x00},
|
||||
};
|
||||
|
||||
static const struct iio_event_spec us5182d_events[] = {
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_RISING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
|
||||
BIT(IIO_EV_INFO_ENABLE),
|
||||
},
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_FALLING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
|
||||
BIT(IIO_EV_INFO_ENABLE),
|
||||
},
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec us5182d_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
|
@ -149,27 +198,12 @@ static const struct iio_chan_spec us5182d_channels[] = {
|
|||
{
|
||||
.type = IIO_PROXIMITY,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.event_spec = us5182d_events,
|
||||
.num_event_specs = ARRAY_SIZE(us5182d_events),
|
||||
}
|
||||
};
|
||||
|
||||
static int us5182d_get_als(struct us5182d_data *data)
|
||||
{
|
||||
int ret;
|
||||
unsigned long result;
|
||||
|
||||
ret = i2c_smbus_read_word_data(data->client,
|
||||
US5182D_REG_ADL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
result = ret * data->ga / US5182D_GA_RESOLUTION;
|
||||
if (result > 0xffff)
|
||||
result = 0xffff;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int us5182d_set_opmode(struct us5182d_data *data, u8 mode)
|
||||
static int us5182d_oneshot_en(struct us5182d_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -183,6 +217,20 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode)
|
|||
*/
|
||||
ret = ret | US5182D_CFG0_ONESHOT_EN;
|
||||
|
||||
return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret);
|
||||
}
|
||||
|
||||
static int us5182d_set_opmode(struct us5182d_data *data, u8 mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (mode == data->opmode)
|
||||
return 0;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* update mode */
|
||||
ret = ret & ~US5182D_OPMODE_MASK;
|
||||
ret = ret | (mode << US5182D_OPMODE_SHIFT);
|
||||
|
@ -196,9 +244,6 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (mode == data->opmode)
|
||||
return 0;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_MODE_STORE,
|
||||
US5182D_STORE_MODE);
|
||||
if (ret < 0)
|
||||
|
@ -210,6 +255,177 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int us5182d_als_enable(struct us5182d_data *data)
|
||||
{
|
||||
int ret;
|
||||
u8 mode;
|
||||
|
||||
if (data->power_mode == US5182D_ONESHOT) {
|
||||
ret = us5182d_set_opmode(data, US5182D_ALS_ONLY);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->px_enabled = false;
|
||||
}
|
||||
|
||||
if (data->als_enabled)
|
||||
return 0;
|
||||
|
||||
mode = data->px_enabled ? US5182D_ALS_PX : US5182D_ALS_ONLY;
|
||||
|
||||
ret = us5182d_set_opmode(data, mode);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->als_enabled = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int us5182d_px_enable(struct us5182d_data *data)
|
||||
{
|
||||
int ret;
|
||||
u8 mode;
|
||||
|
||||
if (data->power_mode == US5182D_ONESHOT) {
|
||||
ret = us5182d_set_opmode(data, US5182D_PX_ONLY);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->als_enabled = false;
|
||||
}
|
||||
|
||||
if (data->px_enabled)
|
||||
return 0;
|
||||
|
||||
mode = data->als_enabled ? US5182D_ALS_PX : US5182D_PX_ONLY;
|
||||
|
||||
ret = us5182d_set_opmode(data, mode);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->px_enabled = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int us5182d_get_als(struct us5182d_data *data)
|
||||
{
|
||||
int ret;
|
||||
unsigned long result;
|
||||
|
||||
ret = us5182d_als_enable(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_read_word_data(data->client,
|
||||
US5182D_REG_ADL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
result = ret * data->ga / US5182D_GA_RESOLUTION;
|
||||
if (result > 0xffff)
|
||||
result = 0xffff;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int us5182d_get_px(struct us5182d_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = us5182d_px_enable(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return i2c_smbus_read_word_data(data->client,
|
||||
US5182D_REG_PDL);
|
||||
}
|
||||
|
||||
static int us5182d_shutdown_en(struct us5182d_data *data, u8 state)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (data->power_mode == US5182D_ONESHOT)
|
||||
return 0;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = ret & ~US5182D_CFG0_SHUTDOWN_EN;
|
||||
ret = ret | state;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (state & US5182D_CFG0_SHUTDOWN_EN) {
|
||||
data->als_enabled = false;
|
||||
data->px_enabled = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int us5182d_set_power_state(struct us5182d_data *data, bool on)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (data->power_mode == US5182D_ONESHOT)
|
||||
return 0;
|
||||
|
||||
if (on) {
|
||||
ret = pm_runtime_get_sync(&data->client->dev);
|
||||
if (ret < 0)
|
||||
pm_runtime_put_noidle(&data->client->dev);
|
||||
} else {
|
||||
pm_runtime_mark_last_busy(&data->client->dev);
|
||||
ret = pm_runtime_put_autosuspend(&data->client->dev);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int us5182d_read_value(struct us5182d_data *data,
|
||||
struct iio_chan_spec const *chan)
|
||||
{
|
||||
int ret, value;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
if (data->power_mode == US5182D_ONESHOT) {
|
||||
ret = us5182d_oneshot_en(data);
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
ret = us5182d_set_power_state(data, true);
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
|
||||
if (chan->type == IIO_LIGHT)
|
||||
ret = us5182d_get_als(data);
|
||||
else
|
||||
ret = us5182d_get_px(data);
|
||||
if (ret < 0)
|
||||
goto out_poweroff;
|
||||
|
||||
value = ret;
|
||||
|
||||
ret = us5182d_set_power_state(data, false);
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
return value;
|
||||
|
||||
out_poweroff:
|
||||
us5182d_set_power_state(data, false);
|
||||
out_err:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int us5182d_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val,
|
||||
int *val2, long mask)
|
||||
|
@ -219,53 +435,21 @@ static int us5182d_read_raw(struct iio_dev *indio_dev,
|
|||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
mutex_lock(&data->lock);
|
||||
ret = us5182d_set_opmode(data, US5182D_OPMODE_ALS);
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
|
||||
ret = us5182d_get_als(data);
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
mutex_unlock(&data->lock);
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_PROXIMITY:
|
||||
mutex_lock(&data->lock);
|
||||
ret = us5182d_set_opmode(data, US5182D_OPMODE_PX);
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
|
||||
ret = i2c_smbus_read_word_data(data->client,
|
||||
US5182D_REG_PDL);
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
mutex_unlock(&data->lock);
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = us5182d_read_value(data, chan);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = 0;
|
||||
*val2 = us5182d_scales[ret & US5182D_AGAIN_MASK];
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
out_err:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -343,11 +527,201 @@ static int us5182d_write_raw(struct iio_dev *indio_dev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int us5182d_setup_prox(struct iio_dev *indio_dev,
|
||||
enum iio_event_direction dir, u16 val)
|
||||
{
|
||||
struct us5182d_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (dir == IIO_EV_DIR_FALLING)
|
||||
return i2c_smbus_write_word_data(data->client,
|
||||
US5182D_REG_PXL_TH, val);
|
||||
else if (dir == IIO_EV_DIR_RISING)
|
||||
return i2c_smbus_write_word_data(data->client,
|
||||
US5182D_REG_PXH_TH, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int us5182d_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 us5182d_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_RISING:
|
||||
mutex_lock(&data->lock);
|
||||
*val = data->px_high_th;
|
||||
mutex_unlock(&data->lock);
|
||||
break;
|
||||
case IIO_EV_DIR_FALLING:
|
||||
mutex_lock(&data->lock);
|
||||
*val = data->px_low_th;
|
||||
mutex_unlock(&data->lock);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
static int us5182d_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 us5182d_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (val < 0 || val > USHRT_MAX || val2 != 0)
|
||||
return -EINVAL;
|
||||
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_RISING:
|
||||
mutex_lock(&data->lock);
|
||||
if (data->rising_en) {
|
||||
ret = us5182d_setup_prox(indio_dev, dir, val);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
data->px_high_th = val;
|
||||
mutex_unlock(&data->lock);
|
||||
break;
|
||||
case IIO_EV_DIR_FALLING:
|
||||
mutex_lock(&data->lock);
|
||||
if (data->falling_en) {
|
||||
ret = us5182d_setup_prox(indio_dev, dir, val);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
data->px_low_th = val;
|
||||
mutex_unlock(&data->lock);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int us5182d_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 us5182d_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_RISING:
|
||||
mutex_lock(&data->lock);
|
||||
ret = data->rising_en;
|
||||
mutex_unlock(&data->lock);
|
||||
break;
|
||||
case IIO_EV_DIR_FALLING:
|
||||
mutex_lock(&data->lock);
|
||||
ret = data->falling_en;
|
||||
mutex_unlock(&data->lock);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int us5182d_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 us5182d_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
u16 new_th;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_RISING:
|
||||
if (data->rising_en == state) {
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
}
|
||||
new_th = US5182D_PXH_TH_DISABLE;
|
||||
if (state) {
|
||||
data->power_mode = US5182D_CONTINUOUS;
|
||||
ret = us5182d_set_power_state(data, true);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
ret = us5182d_px_enable(data);
|
||||
if (ret < 0)
|
||||
goto err_poweroff;
|
||||
new_th = data->px_high_th;
|
||||
}
|
||||
ret = us5182d_setup_prox(indio_dev, dir, new_th);
|
||||
if (ret < 0)
|
||||
goto err_poweroff;
|
||||
data->rising_en = state;
|
||||
break;
|
||||
case IIO_EV_DIR_FALLING:
|
||||
if (data->falling_en == state) {
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
}
|
||||
new_th = US5182D_PXL_TH_DISABLE;
|
||||
if (state) {
|
||||
data->power_mode = US5182D_CONTINUOUS;
|
||||
ret = us5182d_set_power_state(data, true);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
ret = us5182d_px_enable(data);
|
||||
if (ret < 0)
|
||||
goto err_poweroff;
|
||||
new_th = data->px_low_th;
|
||||
}
|
||||
ret = us5182d_setup_prox(indio_dev, dir, new_th);
|
||||
if (ret < 0)
|
||||
goto err_poweroff;
|
||||
data->falling_en = state;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
ret = us5182d_set_power_state(data, false);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!data->falling_en && !data->rising_en && !data->default_continuous)
|
||||
data->power_mode = US5182D_ONESHOT;
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
|
||||
err_poweroff:
|
||||
if (state)
|
||||
us5182d_set_power_state(data, false);
|
||||
err:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info us5182d_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = us5182d_read_raw,
|
||||
.write_raw = us5182d_write_raw,
|
||||
.attrs = &us5182d_attr_group,
|
||||
.read_event_value = &us5182d_read_thresh,
|
||||
.write_event_value = &us5182d_write_thresh,
|
||||
.read_event_config = &us5182d_read_event_config,
|
||||
.write_event_config = &us5182d_write_event_config,
|
||||
};
|
||||
|
||||
static int us5182d_reset(struct iio_dev *indio_dev)
|
||||
|
@ -368,6 +742,10 @@ static int us5182d_init(struct iio_dev *indio_dev)
|
|||
return ret;
|
||||
|
||||
data->opmode = 0;
|
||||
data->power_mode = US5182D_CONTINUOUS;
|
||||
data->px_low_th = US5182D_REG_PXL_TH_DEFAULT;
|
||||
data->px_high_th = US5182D_REG_PXH_TH_DEFAULT;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) {
|
||||
ret = i2c_smbus_write_byte_data(data->client,
|
||||
us5182d_regvals[i].reg,
|
||||
|
@ -376,7 +754,17 @@ static int us5182d_init(struct iio_dev *indio_dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
data->als_enabled = true;
|
||||
data->px_enabled = true;
|
||||
|
||||
if (!data->default_continuous) {
|
||||
ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->power_mode = US5182D_ONESHOT;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void us5182d_get_platform_data(struct iio_dev *indio_dev)
|
||||
|
@ -399,6 +787,8 @@ static void us5182d_get_platform_data(struct iio_dev *indio_dev)
|
|||
"upisemi,lower-dark-gain",
|
||||
&data->lower_dark_gain))
|
||||
data->lower_dark_gain = US5182D_REG_AUTO_LDARK_GAIN_DEFAULT;
|
||||
data->default_continuous = device_property_read_bool(&data->client->dev,
|
||||
"upisemi,continuous");
|
||||
}
|
||||
|
||||
static int us5182d_dark_gain_config(struct iio_dev *indio_dev)
|
||||
|
@ -426,6 +816,33 @@ static int us5182d_dark_gain_config(struct iio_dev *indio_dev)
|
|||
US5182D_REG_DARK_AUTO_EN_DEFAULT);
|
||||
}
|
||||
|
||||
static irqreturn_t us5182d_irq_thread_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_dev *indio_dev = private;
|
||||
struct us5182d_data *data = iio_priv(indio_dev);
|
||||
enum iio_event_direction dir;
|
||||
int ret;
|
||||
u64 ev;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev, "i2c transfer error in irq\n");
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
dir = ret & US5182D_CFG0_PROX ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
|
||||
ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, IIO_EV_TYPE_THRESH, dir);
|
||||
|
||||
iio_push_event(indio_dev, ev, iio_get_time_ns());
|
||||
|
||||
ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0,
|
||||
ret & ~US5182D_CFG0_PX_IRQ);
|
||||
if (ret < 0)
|
||||
dev_err(&data->client->dev, "i2c transfer error in irq\n");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int us5182d_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
|
@ -457,6 +874,16 @@ static int us5182d_probe(struct i2c_client *client,
|
|||
return (ret < 0) ? ret : -ENODEV;
|
||||
}
|
||||
|
||||
if (client->irq > 0) {
|
||||
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
|
||||
us5182d_irq_thread_handler,
|
||||
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
||||
"us5182d-irq", indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else
|
||||
dev_warn(&client->dev, "no valid irq found\n");
|
||||
|
||||
us5182d_get_platform_data(indio_dev);
|
||||
ret = us5182d_init(indio_dev);
|
||||
if (ret < 0)
|
||||
|
@ -464,18 +891,73 @@ static int us5182d_probe(struct i2c_client *client,
|
|||
|
||||
ret = us5182d_dark_gain_config(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
goto out_err;
|
||||
|
||||
if (data->default_continuous) {
|
||||
pm_runtime_set_active(&client->dev);
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev,
|
||||
US5182D_SLEEP_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
goto out_err;
|
||||
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN);
|
||||
return ret;
|
||||
|
||||
return iio_device_register(indio_dev);
|
||||
}
|
||||
|
||||
static int us5182d_remove(struct i2c_client *client)
|
||||
{
|
||||
struct us5182d_data *data = iio_priv(i2c_get_clientdata(client));
|
||||
|
||||
iio_device_unregister(i2c_get_clientdata(client));
|
||||
return i2c_smbus_write_byte_data(client, US5182D_REG_CFG0,
|
||||
US5182D_CFG0_SHUTDOWN_EN);
|
||||
|
||||
pm_runtime_disable(&client->dev);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
|
||||
return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM)
|
||||
static int us5182d_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct us5182d_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (data->power_mode == US5182D_CONTINUOUS)
|
||||
return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int us5182d_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct us5182d_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (data->power_mode == US5182D_CONTINUOUS)
|
||||
return us5182d_shutdown_en(data,
|
||||
~US5182D_CFG0_SHUTDOWN_EN & 0xff);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops us5182d_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(us5182d_suspend, us5182d_resume)
|
||||
SET_RUNTIME_PM_OPS(us5182d_suspend, us5182d_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct acpi_device_id us5182d_acpi_match[] = {
|
||||
{ "USD5182", 0},
|
||||
{}
|
||||
|
@ -493,6 +975,7 @@ MODULE_DEVICE_TABLE(i2c, us5182d_id);
|
|||
static struct i2c_driver us5182d_driver = {
|
||||
.driver = {
|
||||
.name = US5182D_DRV_NAME,
|
||||
.pm = &us5182d_pm_ops,
|
||||
.acpi_match_table = ACPI_PTR(us5182d_acpi_match),
|
||||
},
|
||||
.probe = us5182d_probe,
|
||||
|
|
|
@ -928,27 +928,24 @@ static int bmc150_magn_probe(struct i2c_client *client,
|
|||
goto err_free_irq;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "unable to register iio device\n");
|
||||
goto err_buffer_cleanup;
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret)
|
||||
goto err_iio_unregister;
|
||||
goto err_buffer_cleanup;
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev,
|
||||
BMC150_MAGN_AUTO_SUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
dev_dbg(&indio_dev->dev, "Registered device %s\n", name);
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "unable to register iio device\n");
|
||||
goto err_buffer_cleanup;
|
||||
}
|
||||
|
||||
dev_dbg(&indio_dev->dev, "Registered device %s\n", name);
|
||||
return 0;
|
||||
|
||||
err_iio_unregister:
|
||||
iio_device_unregister(indio_dev);
|
||||
err_buffer_cleanup:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
err_free_irq:
|
||||
|
@ -967,11 +964,12 @@ static int bmc150_magn_remove(struct i2c_client *client)
|
|||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct bmc150_magn_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
pm_runtime_disable(&client->dev);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
pm_runtime_put_noidle(&client->dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
if (client->irq > 0)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* TODO: runtime pm, interrupt mode, and signal strength reporting
|
||||
* TODO: interrupt mode, and signal strength reporting
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
|
@ -21,6 +21,7 @@
|
|||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
|
@ -35,8 +36,11 @@
|
|||
#define LIDAR_REG_STATUS_INVALID BIT(3)
|
||||
#define LIDAR_REG_STATUS_READY BIT(0)
|
||||
|
||||
#define LIDAR_REG_DATA_HBYTE 0x0f
|
||||
#define LIDAR_REG_DATA_LBYTE 0x10
|
||||
#define LIDAR_REG_DATA_HBYTE 0x0f
|
||||
#define LIDAR_REG_DATA_LBYTE 0x10
|
||||
#define LIDAR_REG_DATA_WORD_READ BIT(7)
|
||||
|
||||
#define LIDAR_REG_PWR_CONTROL 0x65
|
||||
|
||||
#define LIDAR_DRV_NAME "lidar"
|
||||
|
||||
|
@ -44,6 +48,9 @@ struct lidar_data {
|
|||
struct iio_dev *indio_dev;
|
||||
struct i2c_client *client;
|
||||
|
||||
int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len);
|
||||
int i2c_enabled;
|
||||
|
||||
u16 buffer[8]; /* 2 byte distance + 8 byte timestamp */
|
||||
};
|
||||
|
||||
|
@ -62,7 +69,28 @@ static const struct iio_chan_spec lidar_channels[] = {
|
|||
IIO_CHAN_SOFT_TIMESTAMP(1),
|
||||
};
|
||||
|
||||
static int lidar_read_byte(struct lidar_data *data, int reg)
|
||||
static int lidar_i2c_xfer(struct lidar_data *data, u8 reg, u8 *val, int len)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
struct i2c_msg msg[2];
|
||||
int ret;
|
||||
|
||||
msg[0].addr = client->addr;
|
||||
msg[0].flags = client->flags | I2C_M_STOP;
|
||||
msg[0].len = 1;
|
||||
msg[0].buf = (char *) ®
|
||||
|
||||
msg[1].addr = client->addr;
|
||||
msg[1].flags = client->flags | I2C_M_RD;
|
||||
msg[1].len = len;
|
||||
msg[1].buf = (char *) val;
|
||||
|
||||
ret = i2c_transfer(client->adapter, msg, 2);
|
||||
|
||||
return (ret == 2) ? 0 : ret;
|
||||
}
|
||||
|
||||
static int lidar_smbus_xfer(struct lidar_data *data, u8 reg, u8 *val, int len)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
int ret;
|
||||
|
@ -72,17 +100,35 @@ static int lidar_read_byte(struct lidar_data *data, int reg)
|
|||
* so in turn i2c_smbus_read_byte_data cannot be used
|
||||
*/
|
||||
|
||||
ret = i2c_smbus_write_byte(client, reg);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "cannot write addr value");
|
||||
return ret;
|
||||
while (len--) {
|
||||
ret = i2c_smbus_write_byte(client, reg++);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "cannot write addr value");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = i2c_smbus_read_byte(client);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "cannot read data value");
|
||||
return ret;
|
||||
}
|
||||
|
||||
*(val++) = ret;
|
||||
}
|
||||
|
||||
ret = i2c_smbus_read_byte(client);
|
||||
if (ret < 0)
|
||||
dev_err(&client->dev, "cannot read data value");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
static int lidar_read_byte(struct lidar_data *data, u8 reg)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = data->xfer(data, reg, &val, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline int lidar_write_control(struct lidar_data *data, int val)
|
||||
|
@ -90,24 +136,22 @@ static inline int lidar_write_control(struct lidar_data *data, int val)
|
|||
return i2c_smbus_write_byte_data(data->client, LIDAR_REG_CONTROL, val);
|
||||
}
|
||||
|
||||
static inline int lidar_write_power(struct lidar_data *data, int val)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(data->client,
|
||||
LIDAR_REG_PWR_CONTROL, val);
|
||||
}
|
||||
|
||||
static int lidar_read_measurement(struct lidar_data *data, u16 *reg)
|
||||
{
|
||||
int ret;
|
||||
int val;
|
||||
int ret = data->xfer(data, LIDAR_REG_DATA_HBYTE |
|
||||
(data->i2c_enabled ? LIDAR_REG_DATA_WORD_READ : 0),
|
||||
(u8 *) reg, 2);
|
||||
|
||||
ret = lidar_read_byte(data, LIDAR_REG_DATA_HBYTE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
val = ret << 8;
|
||||
if (!ret)
|
||||
*reg = be16_to_cpu(*reg);
|
||||
|
||||
ret = lidar_read_byte(data, LIDAR_REG_DATA_LBYTE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val |= ret;
|
||||
*reg = val;
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lidar_get_measurement(struct lidar_data *data, u16 *reg)
|
||||
|
@ -116,6 +160,8 @@ static int lidar_get_measurement(struct lidar_data *data, u16 *reg)
|
|||
int tries = 10;
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(&client->dev);
|
||||
|
||||
/* start sample */
|
||||
ret = lidar_write_control(data, LIDAR_REG_CONTROL_ACQUIRE);
|
||||
if (ret < 0) {
|
||||
|
@ -144,6 +190,8 @@ static int lidar_get_measurement(struct lidar_data *data, u16 *reg)
|
|||
}
|
||||
ret = -EIO;
|
||||
}
|
||||
pm_runtime_mark_last_busy(&client->dev);
|
||||
pm_runtime_put_autosuspend(&client->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -221,6 +269,16 @@ static int lidar_probe(struct i2c_client *client,
|
|||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
data = iio_priv(indio_dev);
|
||||
|
||||
if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
||||
data->xfer = lidar_i2c_xfer;
|
||||
data->i2c_enabled = 1;
|
||||
} else if (i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE))
|
||||
data->xfer = lidar_smbus_xfer;
|
||||
else
|
||||
return -ENOTSUPP;
|
||||
|
||||
indio_dev->info = &lidar_info;
|
||||
indio_dev->name = LIDAR_DRV_NAME;
|
||||
|
@ -228,7 +286,6 @@ static int lidar_probe(struct i2c_client *client,
|
|||
indio_dev->num_channels = ARRAY_SIZE(lidar_channels);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
data->client = client;
|
||||
|
@ -243,6 +300,17 @@ static int lidar_probe(struct i2c_client *client,
|
|||
if (ret)
|
||||
goto error_unreg_buffer;
|
||||
|
||||
pm_runtime_set_autosuspend_delay(&client->dev, 1000);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret)
|
||||
goto error_unreg_buffer;
|
||||
pm_runtime_enable(&client->dev);
|
||||
|
||||
pm_runtime_mark_last_busy(&client->dev);
|
||||
pm_runtime_idle(&client->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
error_unreg_buffer:
|
||||
|
@ -258,6 +326,9 @@ static int lidar_remove(struct i2c_client *client)
|
|||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
pm_runtime_disable(&client->dev);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -273,10 +344,38 @@ static const struct of_device_id lidar_dt_ids[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(of, lidar_dt_ids);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int lidar_pm_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lidar_data *data = iio_priv(indio_dev);
|
||||
|
||||
return lidar_write_power(data, 0x0f);
|
||||
}
|
||||
|
||||
static int lidar_pm_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lidar_data *data = iio_priv(indio_dev);
|
||||
int ret = lidar_write_power(data, 0);
|
||||
|
||||
/* regulator and FPGA needs settling time */
|
||||
usleep_range(15000, 20000);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops lidar_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(lidar_pm_runtime_suspend,
|
||||
lidar_pm_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static struct i2c_driver lidar_driver = {
|
||||
.driver = {
|
||||
.name = LIDAR_DRV_NAME,
|
||||
.of_match_table = of_match_ptr(lidar_dt_ids),
|
||||
.pm = &lidar_pm_ops,
|
||||
},
|
||||
.probe = lidar_probe,
|
||||
.remove = lidar_remove,
|
||||
|
|
|
@ -5,6 +5,16 @@
|
|||
|
||||
menu "Triggers - standalone"
|
||||
|
||||
config IIO_HRTIMER_TRIGGER
|
||||
tristate "High resolution timer trigger"
|
||||
depends on IIO_SW_TRIGGER
|
||||
help
|
||||
Provides a frequency based IIO trigger using high resolution
|
||||
timers as interrupt source.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called iio-trig-hrtimer.
|
||||
|
||||
config IIO_INTERRUPT_TRIGGER
|
||||
tristate "Generic interrupt trigger"
|
||||
help
|
||||
|
|
|
@ -3,5 +3,7 @@
|
|||
#
|
||||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
|
||||
obj-$(CONFIG_IIO_HRTIMER_TRIGGER) += iio-trig-hrtimer.o
|
||||
obj-$(CONFIG_IIO_INTERRUPT_TRIGGER) += iio-trig-interrupt.o
|
||||
obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
/**
|
||||
* The industrial I/O periodic hrtimer trigger driver
|
||||
*
|
||||
* Copyright (C) Intuitive Aerial AB
|
||||
* Written by Marten Svanfeldt, marten@intuitiveaerial.com
|
||||
* Copyright (C) 2012, Analog Device Inc.
|
||||
* Author: Lars-Peter Clausen <lars@metafoo.de>
|
||||
* Copyright (C) 2015, Intel Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/hrtimer.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/sw_trigger.h>
|
||||
|
||||
/* default sampling frequency - 100Hz */
|
||||
#define HRTIMER_DEFAULT_SAMPLING_FREQUENCY 100
|
||||
|
||||
struct iio_hrtimer_info {
|
||||
struct iio_sw_trigger swt;
|
||||
struct hrtimer timer;
|
||||
unsigned long sampling_frequency;
|
||||
ktime_t period;
|
||||
};
|
||||
|
||||
static struct config_item_type iio_hrtimer_type = {
|
||||
.ct_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static
|
||||
ssize_t iio_hrtimer_show_sampling_frequency(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_trigger *trig = to_iio_trigger(dev);
|
||||
struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%lu\n", info->sampling_frequency);
|
||||
}
|
||||
|
||||
static
|
||||
ssize_t iio_hrtimer_store_sampling_frequency(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct iio_trigger *trig = to_iio_trigger(dev);
|
||||
struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig);
|
||||
unsigned long val;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buf, 10, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!val || val > NSEC_PER_SEC)
|
||||
return -EINVAL;
|
||||
|
||||
info->sampling_frequency = val;
|
||||
info->period = ktime_set(0, NSEC_PER_SEC / val);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR,
|
||||
iio_hrtimer_show_sampling_frequency,
|
||||
iio_hrtimer_store_sampling_frequency);
|
||||
|
||||
static struct attribute *iio_hrtimer_attrs[] = {
|
||||
&dev_attr_sampling_frequency.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group iio_hrtimer_attr_group = {
|
||||
.attrs = iio_hrtimer_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *iio_hrtimer_attr_groups[] = {
|
||||
&iio_hrtimer_attr_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
static enum hrtimer_restart iio_hrtimer_trig_handler(struct hrtimer *timer)
|
||||
{
|
||||
struct iio_hrtimer_info *info;
|
||||
|
||||
info = container_of(timer, struct iio_hrtimer_info, timer);
|
||||
|
||||
hrtimer_forward_now(timer, info->period);
|
||||
iio_trigger_poll(info->swt.trigger);
|
||||
|
||||
return HRTIMER_RESTART;
|
||||
}
|
||||
|
||||
static int iio_trig_hrtimer_set_state(struct iio_trigger *trig, bool state)
|
||||
{
|
||||
struct iio_hrtimer_info *trig_info;
|
||||
|
||||
trig_info = iio_trigger_get_drvdata(trig);
|
||||
|
||||
if (state)
|
||||
hrtimer_start(&trig_info->timer, trig_info->period,
|
||||
HRTIMER_MODE_REL);
|
||||
else
|
||||
hrtimer_cancel(&trig_info->timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_trigger_ops iio_hrtimer_trigger_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.set_trigger_state = iio_trig_hrtimer_set_state,
|
||||
};
|
||||
|
||||
static struct iio_sw_trigger *iio_trig_hrtimer_probe(const char *name)
|
||||
{
|
||||
struct iio_hrtimer_info *trig_info;
|
||||
int ret;
|
||||
|
||||
trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL);
|
||||
if (!trig_info)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
trig_info->swt.trigger = iio_trigger_alloc("%s", name);
|
||||
if (!trig_info->swt.trigger) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_trig_info;
|
||||
}
|
||||
|
||||
iio_trigger_set_drvdata(trig_info->swt.trigger, trig_info);
|
||||
trig_info->swt.trigger->ops = &iio_hrtimer_trigger_ops;
|
||||
trig_info->swt.trigger->dev.groups = iio_hrtimer_attr_groups;
|
||||
|
||||
hrtimer_init(&trig_info->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
trig_info->timer.function = iio_hrtimer_trig_handler;
|
||||
|
||||
trig_info->sampling_frequency = HRTIMER_DEFAULT_SAMPLING_FREQUENCY;
|
||||
trig_info->period = ktime_set(0, NSEC_PER_SEC /
|
||||
trig_info->sampling_frequency);
|
||||
|
||||
ret = iio_trigger_register(trig_info->swt.trigger);
|
||||
if (ret)
|
||||
goto err_free_trigger;
|
||||
|
||||
iio_swt_group_init_type_name(&trig_info->swt, name, &iio_hrtimer_type);
|
||||
return &trig_info->swt;
|
||||
err_free_trigger:
|
||||
iio_trigger_free(trig_info->swt.trigger);
|
||||
err_free_trig_info:
|
||||
kfree(trig_info);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static int iio_trig_hrtimer_remove(struct iio_sw_trigger *swt)
|
||||
{
|
||||
struct iio_hrtimer_info *trig_info;
|
||||
|
||||
trig_info = iio_trigger_get_drvdata(swt->trigger);
|
||||
|
||||
iio_trigger_unregister(swt->trigger);
|
||||
|
||||
/* cancel the timer after unreg to make sure no one rearms it */
|
||||
hrtimer_cancel(&trig_info->timer);
|
||||
iio_trigger_free(swt->trigger);
|
||||
kfree(trig_info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_sw_trigger_ops iio_trig_hrtimer_ops = {
|
||||
.probe = iio_trig_hrtimer_probe,
|
||||
.remove = iio_trig_hrtimer_remove,
|
||||
};
|
||||
|
||||
static struct iio_sw_trigger_type iio_trig_hrtimer = {
|
||||
.name = "hrtimer",
|
||||
.owner = THIS_MODULE,
|
||||
.ops = &iio_trig_hrtimer_ops,
|
||||
};
|
||||
|
||||
module_iio_sw_trigger_driver(iio_trig_hrtimer);
|
||||
|
||||
MODULE_AUTHOR("Marten Svanfeldt <marten@intuitiveaerial.com>");
|
||||
MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com>");
|
||||
MODULE_DESCRIPTION("Periodic hrtimer trigger for the IIO subsystem");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -25,5 +25,13 @@ ion/
|
|||
exposes existing cma regions and doesn't reserve unecessarily memory when
|
||||
booting a system which doesn't use ion.
|
||||
|
||||
sync framework:
|
||||
- remove CONFIG_SW_SYNC_USER, it is used only for testing/debugging and
|
||||
should not be upstreamed.
|
||||
- port CONFIG_SW_SYNC_USER tests interfaces to use debugfs somehow
|
||||
- port libsync tests to kselftest
|
||||
- clean up and ABI check for security issues
|
||||
- move it to drivers/base/dma-buf
|
||||
|
||||
Please send patches to Greg Kroah-Hartman <greg@kroah.com> and Cc:
|
||||
Arve Hjønnevåg <arve@android.com> and Riley Andrews <riandrews@android.com>
|
||||
|
|
|
@ -831,14 +831,14 @@ static struct miscdevice ashmem_misc = {
|
|||
|
||||
static int __init ashmem_init(void)
|
||||
{
|
||||
int ret;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
|
||||
sizeof(struct ashmem_area),
|
||||
0, 0, NULL);
|
||||
if (unlikely(!ashmem_area_cachep)) {
|
||||
pr_err("failed to create slab cache\n");
|
||||
return -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",
|
||||
|
@ -846,13 +846,13 @@ static int __init ashmem_init(void)
|
|||
0, 0, NULL);
|
||||
if (unlikely(!ashmem_range_cachep)) {
|
||||
pr_err("failed to create slab cache\n");
|
||||
return -ENOMEM;
|
||||
goto out_free1;
|
||||
}
|
||||
|
||||
ret = misc_register(&ashmem_misc);
|
||||
if (unlikely(ret)) {
|
||||
pr_err("failed to register misc device!\n");
|
||||
return ret;
|
||||
goto out_free2;
|
||||
}
|
||||
|
||||
register_shrinker(&ashmem_shrinker);
|
||||
|
@ -860,5 +860,12 @@ static int __init ashmem_init(void)
|
|||
pr_info("initialized\n");
|
||||
|
||||
return 0;
|
||||
|
||||
out_free2:
|
||||
kmem_cache_destroy(ashmem_range_cachep);
|
||||
out_free1:
|
||||
kmem_cache_destroy(ashmem_area_cachep);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
device_initcall(ashmem_init);
|
||||
|
|
|
@ -33,3 +33,10 @@ config ION_TEGRA
|
|||
help
|
||||
Choose this option if you wish to use ion on an nVidia Tegra.
|
||||
|
||||
config ION_HISI
|
||||
tristate "Ion for Hisilicon"
|
||||
depends on ARCH_HISI && ION
|
||||
help
|
||||
Choose this option if you wish to use ion on Hisilicon Platform.
|
||||
|
||||
source "drivers/staging/android/ion/hisilicon/Kconfig"
|
||||
|
|
|
@ -7,4 +7,5 @@ endif
|
|||
|
||||
obj-$(CONFIG_ION_DUMMY) += ion_dummy_driver.o
|
||||
obj-$(CONFIG_ION_TEGRA) += tegra/
|
||||
obj-$(CONFIG_ION_HISI) += hisilicon/
|
||||
|
||||
|
|
|
@ -137,7 +137,7 @@ long compat_ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|||
|
||||
data32 = compat_ptr(arg);
|
||||
data = compat_alloc_user_space(sizeof(*data));
|
||||
if (data == NULL)
|
||||
if (!data)
|
||||
return -EFAULT;
|
||||
|
||||
err = compat_get_ion_allocation_data(data32, data);
|
||||
|
@ -156,7 +156,7 @@ long compat_ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|||
|
||||
data32 = compat_ptr(arg);
|
||||
data = compat_alloc_user_space(sizeof(*data));
|
||||
if (data == NULL)
|
||||
if (!data)
|
||||
return -EFAULT;
|
||||
|
||||
err = compat_get_ion_handle_data(data32, data);
|
||||
|
@ -173,7 +173,7 @@ long compat_ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|||
|
||||
data32 = compat_ptr(arg);
|
||||
data = compat_alloc_user_space(sizeof(*data));
|
||||
if (data == NULL)
|
||||
if (!data)
|
||||
return -EFAULT;
|
||||
|
||||
err = compat_get_ion_custom_data(data32, data);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
config HI6220_ION
|
||||
bool "Hi6220 ION Driver"
|
||||
depends on ARCH_HISI && ION
|
||||
help
|
||||
Build the Hisilicon Hi6220 ion driver.
|
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_HI6220_ION) += hi6220_ion.o
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Hisilicon Hi6220 ION Driver
|
||||
*
|
||||
* Copyright (c) 2015 Hisilicon Limited.
|
||||
*
|
||||
* Author: Chen Feng <puck.chen@hisilicon.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "Ion: " fmt
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/mm.h>
|
||||
#include "../ion_priv.h"
|
||||
#include "../ion.h"
|
||||
|
||||
struct hi6220_ion_type_table {
|
||||
const char *name;
|
||||
enum ion_heap_type type;
|
||||
};
|
||||
|
||||
static struct hi6220_ion_type_table ion_type_table[] = {
|
||||
{"ion_system", ION_HEAP_TYPE_SYSTEM},
|
||||
{"ion_system_contig", ION_HEAP_TYPE_SYSTEM_CONTIG},
|
||||
{"ion_carveout", ION_HEAP_TYPE_CARVEOUT},
|
||||
{"ion_chunk", ION_HEAP_TYPE_CHUNK},
|
||||
{"ion_dma", ION_HEAP_TYPE_DMA},
|
||||
{"ion_custom", ION_HEAP_TYPE_CUSTOM},
|
||||
};
|
||||
|
||||
static struct ion_device *idev;
|
||||
static int num_heaps;
|
||||
static struct ion_heap **heaps;
|
||||
static struct ion_platform_heap **heaps_data;
|
||||
|
||||
static int get_type_by_name(const char *name, enum ion_heap_type *type)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ion_type_table); i++) {
|
||||
if (strncmp(name, ion_type_table[i].name, strlen(name)))
|
||||
continue;
|
||||
|
||||
*type = ion_type_table[i].type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int hi6220_set_platform_data(struct platform_device *pdev)
|
||||
{
|
||||
unsigned int base;
|
||||
unsigned int size;
|
||||
unsigned int id;
|
||||
const char *heap_name;
|
||||
const char *type_name;
|
||||
enum ion_heap_type type;
|
||||
int ret;
|
||||
struct device_node *np;
|
||||
struct ion_platform_heap *p_data;
|
||||
const struct device_node *dt_node = pdev->dev.of_node;
|
||||
int index = 0;
|
||||
|
||||
for_each_child_of_node(dt_node, np)
|
||||
num_heaps++;
|
||||
|
||||
heaps_data = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct ion_platform_heap *) *
|
||||
num_heaps,
|
||||
GFP_KERNEL);
|
||||
if (!heaps_data)
|
||||
return -ENOMEM;
|
||||
|
||||
for_each_child_of_node(dt_node, np) {
|
||||
ret = of_property_read_string(np, "heap-name", &heap_name);
|
||||
if (ret < 0) {
|
||||
pr_err("check the name of node %s\n", np->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "heap-id", &id);
|
||||
if (ret < 0) {
|
||||
pr_err("check the id %s\n", np->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "heap-base", &base);
|
||||
if (ret < 0) {
|
||||
pr_err("check the base of node %s\n", np->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "heap-size", &size);
|
||||
if (ret < 0) {
|
||||
pr_err("check the size of node %s\n", np->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = of_property_read_string(np, "heap-type", &type_name);
|
||||
if (ret < 0) {
|
||||
pr_err("check the type of node %s\n", np->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = get_type_by_name(type_name, &type);
|
||||
if (ret < 0) {
|
||||
pr_err("type name error %s!\n", type_name);
|
||||
continue;
|
||||
}
|
||||
pr_info("heap index %d : name %s base 0x%x size 0x%x id %d type %d\n",
|
||||
index, heap_name, base, size, id, type);
|
||||
|
||||
p_data = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct ion_platform_heap),
|
||||
GFP_KERNEL);
|
||||
if (!p_data)
|
||||
return -ENOMEM;
|
||||
|
||||
p_data->name = heap_name;
|
||||
p_data->base = base;
|
||||
p_data->size = size;
|
||||
p_data->id = id;
|
||||
p_data->type = type;
|
||||
|
||||
heaps_data[index] = p_data;
|
||||
index++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hi6220_ion_probe(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
int err;
|
||||
static struct ion_platform_heap *p_heap;
|
||||
|
||||
idev = ion_device_create(NULL);
|
||||
err = hi6220_set_platform_data(pdev);
|
||||
if (err) {
|
||||
pr_err("ion set platform data error!\n");
|
||||
goto err_free_idev;
|
||||
}
|
||||
heaps = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct ion_heap *) * num_heaps,
|
||||
GFP_KERNEL);
|
||||
if (!heaps) {
|
||||
err = -ENOMEM;
|
||||
goto err_free_idev;
|
||||
}
|
||||
|
||||
/*
|
||||
* create the heaps as specified in the dts file
|
||||
*/
|
||||
for (i = 0; i < num_heaps; i++) {
|
||||
p_heap = heaps_data[i];
|
||||
heaps[i] = ion_heap_create(p_heap);
|
||||
if (IS_ERR_OR_NULL(heaps[i])) {
|
||||
err = PTR_ERR(heaps[i]);
|
||||
goto err_free_heaps;
|
||||
}
|
||||
|
||||
ion_device_add_heap(idev, heaps[i]);
|
||||
|
||||
pr_info("%s: adding heap %s of type %d with %lx@%lx\n",
|
||||
__func__, p_heap->name, p_heap->type,
|
||||
p_heap->base, (unsigned long)p_heap->size);
|
||||
}
|
||||
return err;
|
||||
|
||||
err_free_heaps:
|
||||
for (i = 0; i < num_heaps; ++i) {
|
||||
ion_heap_destroy(heaps[i]);
|
||||
heaps[i] = NULL;
|
||||
}
|
||||
err_free_idev:
|
||||
ion_device_destroy(idev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hi6220_ion_remove(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_heaps; i++) {
|
||||
ion_heap_destroy(heaps[i]);
|
||||
heaps[i] = NULL;
|
||||
}
|
||||
ion_device_destroy(idev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id hi6220_ion_match_table[] = {
|
||||
{.compatible = "hisilicon,hi6220-ion"},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver hi6220_ion_driver = {
|
||||
.probe = hi6220_ion_probe,
|
||||
.remove = hi6220_ion_remove,
|
||||
.driver = {
|
||||
.name = "ion-hi6220",
|
||||
.of_match_table = hi6220_ion_match_table,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init hi6220_ion_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = platform_driver_register(&hi6220_ion_driver);
|
||||
return ret;
|
||||
}
|
||||
|
||||
subsys_initcall(hi6220_ion_init);
|
|
@ -43,7 +43,7 @@
|
|||
#include <linux/profile.h>
|
||||
#include <linux/notifier.h>
|
||||
|
||||
static uint32_t lowmem_debug_level = 1;
|
||||
static u32 lowmem_debug_level = 1;
|
||||
static short lowmem_adj[6] = {
|
||||
0,
|
||||
1,
|
||||
|
@ -105,8 +105,8 @@ static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
|
|||
}
|
||||
|
||||
lowmem_print(3, "lowmem_scan %lu, %x, ofree %d %d, ma %hd\n",
|
||||
sc->nr_to_scan, sc->gfp_mask, other_free,
|
||||
other_file, min_score_adj);
|
||||
sc->nr_to_scan, sc->gfp_mask, other_free,
|
||||
other_file, min_score_adj);
|
||||
|
||||
if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) {
|
||||
lowmem_print(5, "lowmem_scan %lu, %x, return 0\n",
|
||||
|
|
|
@ -43,7 +43,7 @@ struct sync_timeline *sync_timeline_create(const struct sync_timeline_ops *ops,
|
|||
return NULL;
|
||||
|
||||
obj = kzalloc(size, GFP_KERNEL);
|
||||
if (obj == NULL)
|
||||
if (!obj)
|
||||
return NULL;
|
||||
|
||||
kref_init(&obj->kref);
|
||||
|
@ -130,7 +130,7 @@ struct sync_pt *sync_pt_create(struct sync_timeline *obj, int size)
|
|||
return NULL;
|
||||
|
||||
pt = kzalloc(size, GFP_KERNEL);
|
||||
if (pt == NULL)
|
||||
if (!pt)
|
||||
return NULL;
|
||||
|
||||
spin_lock_irqsave(&obj->child_list_lock, flags);
|
||||
|
@ -155,7 +155,7 @@ static struct sync_fence *sync_fence_alloc(int size, const char *name)
|
|||
struct sync_fence *fence;
|
||||
|
||||
fence = kzalloc(size, GFP_KERNEL);
|
||||
if (fence == NULL)
|
||||
if (!fence)
|
||||
return NULL;
|
||||
|
||||
fence->file = anon_inode_getfile("sync_fence", &sync_fence_fops,
|
||||
|
@ -188,34 +188,39 @@ static void fence_check_cb_func(struct fence *f, struct fence_cb *cb)
|
|||
}
|
||||
|
||||
/* TODO: implement a create which takes more that one sync_pt */
|
||||
struct sync_fence *sync_fence_create(const char *name, struct sync_pt *pt)
|
||||
struct sync_fence *sync_fence_create_dma(const char *name, struct fence *pt)
|
||||
{
|
||||
struct sync_fence *fence;
|
||||
|
||||
fence = sync_fence_alloc(offsetof(struct sync_fence, cbs[1]), name);
|
||||
if (fence == NULL)
|
||||
if (!fence)
|
||||
return NULL;
|
||||
|
||||
fence->num_fences = 1;
|
||||
atomic_set(&fence->status, 1);
|
||||
|
||||
fence->cbs[0].sync_pt = &pt->base;
|
||||
fence->cbs[0].sync_pt = pt;
|
||||
fence->cbs[0].fence = fence;
|
||||
if (fence_add_callback(&pt->base, &fence->cbs[0].cb,
|
||||
fence_check_cb_func))
|
||||
if (fence_add_callback(pt, &fence->cbs[0].cb, fence_check_cb_func))
|
||||
atomic_dec(&fence->status);
|
||||
|
||||
sync_fence_debug_add(fence);
|
||||
|
||||
return fence;
|
||||
}
|
||||
EXPORT_SYMBOL(sync_fence_create_dma);
|
||||
|
||||
struct sync_fence *sync_fence_create(const char *name, struct sync_pt *pt)
|
||||
{
|
||||
return sync_fence_create_dma(name, &pt->base);
|
||||
}
|
||||
EXPORT_SYMBOL(sync_fence_create);
|
||||
|
||||
struct sync_fence *sync_fence_fdget(int fd)
|
||||
{
|
||||
struct file *file = fget(fd);
|
||||
|
||||
if (file == NULL)
|
||||
if (!file)
|
||||
return NULL;
|
||||
|
||||
if (file->f_op != &sync_fence_fops)
|
||||
|
@ -262,7 +267,7 @@ struct sync_fence *sync_fence_merge(const char *name,
|
|||
unsigned long size = offsetof(struct sync_fence, cbs[num_fences]);
|
||||
|
||||
fence = sync_fence_alloc(size, name);
|
||||
if (fence == NULL)
|
||||
if (!fence)
|
||||
return NULL;
|
||||
|
||||
atomic_set(&fence->status, num_fences);
|
||||
|
@ -313,7 +318,7 @@ struct sync_fence *sync_fence_merge(const char *name,
|
|||
EXPORT_SYMBOL(sync_fence_merge);
|
||||
|
||||
int sync_fence_wake_up_wq(wait_queue_t *curr, unsigned mode,
|
||||
int wake_flags, void *key)
|
||||
int wake_flags, void *key)
|
||||
{
|
||||
struct sync_fence_waiter *wait;
|
||||
|
||||
|
@ -353,7 +358,7 @@ int sync_fence_wait_async(struct sync_fence *fence,
|
|||
EXPORT_SYMBOL(sync_fence_wait_async);
|
||||
|
||||
int sync_fence_cancel_async(struct sync_fence *fence,
|
||||
struct sync_fence_waiter *waiter)
|
||||
struct sync_fence_waiter *waiter)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
@ -519,12 +524,10 @@ static const struct fence_ops android_fence_ops = {
|
|||
static void sync_fence_free(struct kref *kref)
|
||||
{
|
||||
struct sync_fence *fence = container_of(kref, struct sync_fence, kref);
|
||||
int i, status = atomic_read(&fence->status);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < fence->num_fences; ++i) {
|
||||
if (status)
|
||||
fence_remove_callback(fence->cbs[i].sync_pt,
|
||||
&fence->cbs[i].cb);
|
||||
fence_remove_callback(fence->cbs[i].sync_pt, &fence->cbs[i].cb);
|
||||
fence_put(fence->cbs[i].sync_pt);
|
||||
}
|
||||
|
||||
|
@ -583,14 +586,14 @@ static long sync_fence_ioctl_merge(struct sync_fence *fence, unsigned long arg)
|
|||
}
|
||||
|
||||
fence2 = sync_fence_fdget(data.fd2);
|
||||
if (fence2 == NULL) {
|
||||
if (!fence2) {
|
||||
err = -ENOENT;
|
||||
goto err_put_fd;
|
||||
}
|
||||
|
||||
data.name[sizeof(data.name) - 1] = '\0';
|
||||
fence3 = sync_fence_merge(data.name, fence, fence2);
|
||||
if (fence3 == NULL) {
|
||||
if (!fence3) {
|
||||
err = -ENOMEM;
|
||||
goto err_put_fence2;
|
||||
}
|
||||
|
@ -666,7 +669,7 @@ static long sync_fence_ioctl_fence_info(struct sync_fence *fence,
|
|||
size = 4096;
|
||||
|
||||
data = kzalloc(size, GFP_KERNEL);
|
||||
if (data == NULL)
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
strlcpy(data->name, fence->name, sizeof(data->name));
|
||||
|
|
|
@ -254,6 +254,16 @@ void sync_pt_free(struct sync_pt *pt);
|
|||
*/
|
||||
struct sync_fence *sync_fence_create(const char *name, struct sync_pt *pt);
|
||||
|
||||
/**
|
||||
* sync_fence_create_dma() - creates a sync fence from dma-fence
|
||||
* @name: name of fence to create
|
||||
* @pt: dma-fence to add to the fence
|
||||
*
|
||||
* Creates a fence containg @pt. Once this is called, the fence takes
|
||||
* ownership of @pt.
|
||||
*/
|
||||
struct sync_fence *sync_fence_create_dma(const char *name, struct fence *pt);
|
||||
|
||||
/*
|
||||
* API for sync_fence consumers
|
||||
*/
|
||||
|
|
|
@ -82,36 +82,42 @@ static const char *sync_status_str(int status)
|
|||
return "error";
|
||||
}
|
||||
|
||||
static void sync_print_pt(struct seq_file *s, struct sync_pt *pt, bool fence)
|
||||
static void sync_print_pt(struct seq_file *s, struct fence *pt, bool fence)
|
||||
{
|
||||
int status = 1;
|
||||
struct sync_timeline *parent = sync_pt_parent(pt);
|
||||
|
||||
if (fence_is_signaled_locked(&pt->base))
|
||||
status = pt->base.status;
|
||||
if (fence_is_signaled_locked(pt))
|
||||
status = pt->status;
|
||||
|
||||
seq_printf(s, " %s%spt %s",
|
||||
fence ? parent->name : "",
|
||||
fence && pt->ops->get_timeline_name ?
|
||||
pt->ops->get_timeline_name(pt) : "",
|
||||
fence ? "_" : "",
|
||||
sync_status_str(status));
|
||||
|
||||
if (status <= 0) {
|
||||
struct timespec64 ts64 =
|
||||
ktime_to_timespec64(pt->base.timestamp);
|
||||
ktime_to_timespec64(pt->timestamp);
|
||||
|
||||
seq_printf(s, "@%lld.%09ld", (s64)ts64.tv_sec, ts64.tv_nsec);
|
||||
}
|
||||
|
||||
if (parent->ops->timeline_value_str &&
|
||||
parent->ops->pt_value_str) {
|
||||
if ((!fence || pt->ops->timeline_value_str) &&
|
||||
pt->ops->fence_value_str) {
|
||||
char value[64];
|
||||
bool success;
|
||||
|
||||
parent->ops->pt_value_str(pt, value, sizeof(value));
|
||||
seq_printf(s, ": %s", value);
|
||||
if (fence) {
|
||||
parent->ops->timeline_value_str(parent, value,
|
||||
sizeof(value));
|
||||
seq_printf(s, " / %s", value);
|
||||
pt->ops->fence_value_str(pt, value, sizeof(value));
|
||||
success = strlen(value);
|
||||
|
||||
if (success)
|
||||
seq_printf(s, ": %s", value);
|
||||
|
||||
if (success && fence) {
|
||||
pt->ops->timeline_value_str(pt, value, sizeof(value));
|
||||
|
||||
if (strlen(value))
|
||||
seq_printf(s, " / %s", value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +144,7 @@ static void sync_print_obj(struct seq_file *s, struct sync_timeline *obj)
|
|||
list_for_each(pos, &obj->child_list_head) {
|
||||
struct sync_pt *pt =
|
||||
container_of(pos, struct sync_pt, child_list);
|
||||
sync_print_pt(s, pt, false);
|
||||
sync_print_pt(s, &pt->base, false);
|
||||
}
|
||||
spin_unlock_irqrestore(&obj->child_list_lock, flags);
|
||||
}
|
||||
|
@ -153,11 +159,7 @@ static void sync_print_fence(struct seq_file *s, struct sync_fence *fence)
|
|||
sync_status_str(atomic_read(&fence->status)));
|
||||
|
||||
for (i = 0; i < fence->num_fences; ++i) {
|
||||
struct sync_pt *pt =
|
||||
container_of(fence->cbs[i].sync_pt,
|
||||
struct sync_pt, base);
|
||||
|
||||
sync_print_pt(s, pt, true);
|
||||
sync_print_pt(s, fence->cbs[i].sync_pt, true);
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&fence->wq.lock, flags);
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
#include "timed_output.h"
|
||||
#include "timed_gpio.h"
|
||||
|
||||
|
||||
struct timed_gpio_data {
|
||||
struct timed_output_dev dev;
|
||||
struct hrtimer timer;
|
||||
|
@ -76,8 +75,8 @@ static void gpio_enable(struct timed_output_dev *dev, int value)
|
|||
value = data->max_timeout;
|
||||
|
||||
hrtimer_start(&data->timer,
|
||||
ktime_set(value / 1000, (value % 1000) * 1000000),
|
||||
HRTIMER_MODE_REL);
|
||||
ktime_set(value / 1000, (value % 1000) * 1000000),
|
||||
HRTIMER_MODE_REL);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&data->lock, flags);
|
||||
|
@ -94,8 +93,8 @@ static int timed_gpio_probe(struct platform_device *pdev)
|
|||
return -EBUSY;
|
||||
|
||||
gpio_data = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct timed_gpio_data) * pdata->num_gpios,
|
||||
GFP_KERNEL);
|
||||
sizeof(*gpio_data) * pdata->num_gpios,
|
||||
GFP_KERNEL);
|
||||
if (!gpio_data)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -104,7 +103,7 @@ static int timed_gpio_probe(struct platform_device *pdev)
|
|||
gpio_dat = &gpio_data[i];
|
||||
|
||||
hrtimer_init(&gpio_dat->timer, CLOCK_MONOTONIC,
|
||||
HRTIMER_MODE_REL);
|
||||
HRTIMER_MODE_REL);
|
||||
gpio_dat->timer.function = gpio_timer_func;
|
||||
spin_lock_init(&gpio_dat->lock);
|
||||
|
||||
|
|
|
@ -737,15 +737,23 @@ config COMEDI_ADL_PCI9118
|
|||
called adl_pci9118.
|
||||
|
||||
config COMEDI_ADV_PCI1710
|
||||
tristate "Advantech PCI-171x, PCI-1720 and PCI-1731 support"
|
||||
tristate "Advantech PCI-171x and PCI-1731 support"
|
||||
select COMEDI_8254
|
||||
---help---
|
||||
Enable support for Advantech PCI-1710, PCI-1710HG, PCI-1711,
|
||||
PCI-1713, PCI-1720 and PCI-1731
|
||||
PCI-1713 and PCI-1731
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called adv_pci1710.
|
||||
|
||||
config COMEDI_ADV_PCI1720
|
||||
tristate "Advantech PCI-1720 support"
|
||||
---help---
|
||||
Enable support for Advantech PCI-1720 Analog Output board.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called adv_pci1720.
|
||||
|
||||
config COMEDI_ADV_PCI1723
|
||||
tristate "Advantech PCI-1723 support"
|
||||
---help---
|
||||
|
@ -764,6 +772,14 @@ config COMEDI_ADV_PCI1724
|
|||
To compile this driver as a module, choose M here: the module will be
|
||||
called adv_pci1724.
|
||||
|
||||
config COMEDI_ADV_PCI1760
|
||||
tristate "Advantech PCI-1760 support"
|
||||
---help---
|
||||
Enable support for Advantech PCI-1760 board.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called adv_pci1760.
|
||||
|
||||
config COMEDI_ADV_PCI_DIO
|
||||
tristate "Advantech PCI DIO card support"
|
||||
select COMEDI_8254
|
||||
|
@ -771,8 +787,8 @@ config COMEDI_ADV_PCI_DIO
|
|||
---help---
|
||||
Enable support for Advantech PCI DIO cards
|
||||
PCI-1730, PCI-1733, PCI-1734, PCI-1735U, PCI-1736UP, PCI-1739U,
|
||||
PCI-1750, PCI-1751, PCI-1752, PCI-1753/E, PCI-1754, PCI-1756,
|
||||
PCI-1760 and PCI-1762
|
||||
PCI-1750, PCI-1751, PCI-1752, PCI-1753/E, PCI-1754, PCI-1756 and
|
||||
PCI-1762
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called adv_pci_dio.
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
/*
|
||||
include/comedi.h (installed as /usr/include/comedi.h)
|
||||
header file for comedi
|
||||
|
||||
COMEDI - Linux Control and Measurement Device Interface
|
||||
Copyright (C) 1998-2001 David A. Schleef <ds@schleef.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser 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/comedi.h (installed as /usr/include/comedi.h)
|
||||
* header file for comedi
|
||||
*
|
||||
* COMEDI - Linux Control and Measurement Device Interface
|
||||
* Copyright (C) 1998-2001 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _COMEDI_H
|
||||
#define _COMEDI_H
|
||||
|
@ -28,9 +28,9 @@
|
|||
#define COMEDI_MAJOR 98
|
||||
|
||||
/*
|
||||
maximum number of minor devices. This can be increased, although
|
||||
kernel structures are currently statically allocated, thus you
|
||||
don't want this to be much more than you actually use.
|
||||
* maximum number of minor devices. This can be increased, although
|
||||
* kernel structures are currently statically allocated, thus you
|
||||
* don't want this to be much more than you actually use.
|
||||
*/
|
||||
#define COMEDI_NDEVICES 16
|
||||
|
||||
|
@ -63,21 +63,21 @@
|
|||
/* packs and unpacks a channel/range number */
|
||||
|
||||
#define CR_PACK(chan, rng, aref) \
|
||||
((((aref)&0x3)<<24) | (((rng)&0xff)<<16) | (chan))
|
||||
((((aref) & 0x3) << 24) | (((rng) & 0xff) << 16) | (chan))
|
||||
#define CR_PACK_FLAGS(chan, range, aref, flags) \
|
||||
(CR_PACK(chan, range, aref) | ((flags) & CR_FLAGS_MASK))
|
||||
|
||||
#define CR_CHAN(a) ((a)&0xffff)
|
||||
#define CR_RANGE(a) (((a)>>16)&0xff)
|
||||
#define CR_AREF(a) (((a)>>24)&0x03)
|
||||
#define CR_CHAN(a) ((a) & 0xffff)
|
||||
#define CR_RANGE(a) (((a) >> 16) & 0xff)
|
||||
#define CR_AREF(a) (((a) >> 24) & 0x03)
|
||||
|
||||
#define CR_FLAGS_MASK 0xfc000000
|
||||
#define CR_ALT_FILTER (1<<26)
|
||||
#define CR_ALT_FILTER (1 << 26)
|
||||
#define CR_DITHER CR_ALT_FILTER
|
||||
#define CR_DEGLITCH CR_ALT_FILTER
|
||||
#define CR_ALT_SOURCE (1<<27)
|
||||
#define CR_EDGE (1<<30)
|
||||
#define CR_INVERT (1<<31)
|
||||
#define CR_ALT_SOURCE (1 << 27)
|
||||
#define CR_EDGE (1 << 30)
|
||||
#define CR_INVERT (1 << 31)
|
||||
|
||||
#define AREF_GROUND 0x00 /* analog ref = analog ground */
|
||||
#define AREF_COMMON 0x01 /* analog ref = analog common */
|
||||
|
@ -114,11 +114,11 @@
|
|||
|
||||
#define INSN_READ (0 | INSN_MASK_READ)
|
||||
#define INSN_WRITE (1 | INSN_MASK_WRITE)
|
||||
#define INSN_BITS (2 | INSN_MASK_READ|INSN_MASK_WRITE)
|
||||
#define INSN_CONFIG (3 | INSN_MASK_READ|INSN_MASK_WRITE)
|
||||
#define INSN_GTOD (4 | INSN_MASK_READ|INSN_MASK_SPECIAL)
|
||||
#define INSN_WAIT (5 | INSN_MASK_WRITE|INSN_MASK_SPECIAL)
|
||||
#define INSN_INTTRIG (6 | INSN_MASK_WRITE|INSN_MASK_SPECIAL)
|
||||
#define INSN_BITS (2 | INSN_MASK_READ | INSN_MASK_WRITE)
|
||||
#define INSN_CONFIG (3 | INSN_MASK_READ | INSN_MASK_WRITE)
|
||||
#define INSN_GTOD (4 | INSN_MASK_READ | INSN_MASK_SPECIAL)
|
||||
#define INSN_WAIT (5 | INSN_MASK_WRITE | INSN_MASK_SPECIAL)
|
||||
#define INSN_INTTRIG (6 | INSN_MASK_WRITE | INSN_MASK_SPECIAL)
|
||||
|
||||
/* trigger flags */
|
||||
/* These flags are used in comedi_trig structures */
|
||||
|
@ -279,7 +279,8 @@ enum configuration_ids {
|
|||
INSN_CONFIG_SET_OTHER_SRC = 2005, /* Set other source */
|
||||
/* INSN_CONFIG_GET_OTHER_SRC = 2006,*//* Get other source */
|
||||
/* Get size in bytes of subdevice's on-board fifos used during
|
||||
* streaming input/output */
|
||||
* streaming input/output
|
||||
*/
|
||||
INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE = 2006,
|
||||
INSN_CONFIG_SET_COUNTER_MODE = 4097,
|
||||
/* INSN_CONFIG_8254_SET_MODE is deprecated */
|
||||
|
@ -292,7 +293,8 @@ enum configuration_ids {
|
|||
INSN_CONFIG_PWM_GET_PERIOD = 5001, /* gets frequency */
|
||||
INSN_CONFIG_GET_PWM_STATUS = 5002, /* is it running? */
|
||||
/* sets H bridge: duty cycle and sign bit for a relay at the
|
||||
* same time */
|
||||
* same time
|
||||
*/
|
||||
INSN_CONFIG_PWM_SET_H_BRIDGE = 5003,
|
||||
/* gets H bridge data: duty cycle and the sign bit */
|
||||
INSN_CONFIG_PWM_GET_H_BRIDGE = 5004
|
||||
|
@ -502,13 +504,13 @@ struct comedi_bufinfo {
|
|||
|
||||
/* range stuff */
|
||||
|
||||
#define __RANGE(a, b) ((((a)&0xffff)<<16)|((b)&0xffff))
|
||||
#define __RANGE(a, b) ((((a) & 0xffff) << 16) | ((b) & 0xffff))
|
||||
|
||||
#define RANGE_OFFSET(a) (((a)>>16)&0xffff)
|
||||
#define RANGE_LENGTH(b) ((b)&0xffff)
|
||||
#define RANGE_OFFSET(a) (((a) >> 16) & 0xffff)
|
||||
#define RANGE_LENGTH(b) ((b) & 0xffff)
|
||||
|
||||
#define RF_UNIT(flags) ((flags)&0xff)
|
||||
#define RF_EXTERNAL (1<<8)
|
||||
#define RF_UNIT(flags) ((flags) & 0xff)
|
||||
#define RF_EXTERNAL (1 << 8)
|
||||
|
||||
#define UNIT_volt 0
|
||||
#define UNIT_mA 1
|
||||
|
@ -521,23 +523,22 @@ struct comedi_bufinfo {
|
|||
/**********************************************************/
|
||||
|
||||
/*
|
||||
8254 specific configuration.
|
||||
|
||||
It supports two config commands:
|
||||
|
||||
0 ID: INSN_CONFIG_SET_COUNTER_MODE
|
||||
1 8254 Mode
|
||||
I8254_MODE0, I8254_MODE1, ..., I8254_MODE5
|
||||
OR'ed with:
|
||||
I8254_BCD, I8254_BINARY
|
||||
|
||||
0 ID: INSN_CONFIG_8254_READ_STATUS
|
||||
1 <-- Status byte returned here.
|
||||
B7 = Output
|
||||
B6 = NULL Count
|
||||
B5 - B0 Current mode.
|
||||
|
||||
*/
|
||||
* 8254 specific configuration.
|
||||
*
|
||||
* It supports two config commands:
|
||||
*
|
||||
* 0 ID: INSN_CONFIG_SET_COUNTER_MODE
|
||||
* 1 8254 Mode
|
||||
* I8254_MODE0, I8254_MODE1, ..., I8254_MODE5
|
||||
* OR'ed with:
|
||||
* I8254_BCD, I8254_BINARY
|
||||
*
|
||||
* 0 ID: INSN_CONFIG_8254_READ_STATUS
|
||||
* 1 <-- Status byte returned here.
|
||||
* B7 = Output
|
||||
* B6 = NULL Count
|
||||
* B5 - B0 Current mode.
|
||||
*/
|
||||
|
||||
enum i8254_mode {
|
||||
I8254_MODE0 = (0 << 1), /* Interrupt on terminal count */
|
||||
|
@ -545,18 +546,20 @@ enum i8254_mode {
|
|||
I8254_MODE2 = (2 << 1), /* Rate generator */
|
||||
I8254_MODE3 = (3 << 1), /* Square wave mode */
|
||||
I8254_MODE4 = (4 << 1), /* Software triggered strobe */
|
||||
I8254_MODE5 = (5 << 1), /* Hardware triggered strobe
|
||||
* (retriggerable) */
|
||||
I8254_BCD = 1, /* use binary-coded decimal instead of binary
|
||||
* (pretty useless) */
|
||||
/* Hardware triggered strobe (retriggerable) */
|
||||
I8254_MODE5 = (5 << 1),
|
||||
/* Use binary-coded decimal instead of binary (pretty useless) */
|
||||
I8254_BCD = 1,
|
||||
I8254_BINARY = 0
|
||||
};
|
||||
|
||||
#define NI_USUAL_PFI_SELECT(x) (((x) < 10) ? (0x1 + (x)) : (0xb + (x)))
|
||||
#define NI_USUAL_RTSI_SELECT(x) (((x) < 7) ? (0xb + (x)) : 0x1b)
|
||||
|
||||
/* mode bits for NI general-purpose counters, set with
|
||||
* INSN_CONFIG_SET_COUNTER_MODE */
|
||||
/*
|
||||
* mode bits for NI general-purpose counters, set with
|
||||
* INSN_CONFIG_SET_COUNTER_MODE
|
||||
*/
|
||||
#define NI_GPCT_COUNTING_MODE_SHIFT 16
|
||||
#define NI_GPCT_INDEX_PHASE_BITSHIFT 20
|
||||
#define NI_GPCT_COUNTING_DIRECTION_SHIFT 24
|
||||
|
@ -624,8 +627,10 @@ enum ni_gpct_mode_bits {
|
|||
NI_GPCT_INVERT_OUTPUT_BIT = 0x20000000
|
||||
};
|
||||
|
||||
/* Bits for setting a clock source with
|
||||
* INSN_CONFIG_SET_CLOCK_SRC when using NI general-purpose counters. */
|
||||
/*
|
||||
* Bits for setting a clock source with
|
||||
* INSN_CONFIG_SET_CLOCK_SRC when using NI general-purpose counters.
|
||||
*/
|
||||
enum ni_gpct_clock_source_bits {
|
||||
NI_GPCT_CLOCK_SRC_SELECT_MASK = 0x3f,
|
||||
NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS = 0x0,
|
||||
|
@ -656,9 +661,11 @@ enum ni_gpct_clock_source_bits {
|
|||
/* no pfi on NI 660x */
|
||||
#define NI_GPCT_PFI_CLOCK_SRC_BITS(x) (0x20 + (x))
|
||||
|
||||
/* Possibilities for setting a gate source with
|
||||
INSN_CONFIG_SET_GATE_SRC when using NI general-purpose counters.
|
||||
May be bitwise-or'd with CR_EDGE or CR_INVERT. */
|
||||
/*
|
||||
* Possibilities for setting a gate source with
|
||||
* INSN_CONFIG_SET_GATE_SRC when using NI general-purpose counters.
|
||||
* May be bitwise-or'd with CR_EDGE or CR_INVERT.
|
||||
*/
|
||||
enum ni_gpct_gate_select {
|
||||
/* m-series gates */
|
||||
NI_GPCT_TIMESTAMP_MUX_GATE_SELECT = 0x0,
|
||||
|
@ -675,9 +682,11 @@ enum ni_gpct_gate_select {
|
|||
/* more gates for 660x "second gate" */
|
||||
NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT = 0x201,
|
||||
NI_GPCT_SELECTED_GATE_GATE_SELECT = 0x21e,
|
||||
/* m-series "second gate" sources are unknown,
|
||||
/*
|
||||
* m-series "second gate" sources are unknown,
|
||||
* we should add them here with an offset of 0x300 when
|
||||
* known. */
|
||||
* known.
|
||||
*/
|
||||
NI_GPCT_DISABLED_GATE_SELECT = 0x8000,
|
||||
};
|
||||
|
||||
|
@ -686,8 +695,10 @@ enum ni_gpct_gate_select {
|
|||
#define NI_GPCT_PFI_GATE_SELECT(x) NI_USUAL_PFI_SELECT(x)
|
||||
#define NI_GPCT_UP_DOWN_PIN_GATE_SELECT(x) (0x202 + (x))
|
||||
|
||||
/* Possibilities for setting a source with
|
||||
INSN_CONFIG_SET_OTHER_SRC when using NI general-purpose counters. */
|
||||
/*
|
||||
* Possibilities for setting a source with
|
||||
* INSN_CONFIG_SET_OTHER_SRC when using NI general-purpose counters.
|
||||
*/
|
||||
enum ni_gpct_other_index {
|
||||
NI_GPCT_SOURCE_ENCODER_A,
|
||||
NI_GPCT_SOURCE_ENCODER_B,
|
||||
|
@ -702,18 +713,24 @@ enum ni_gpct_other_select {
|
|||
|
||||
#define NI_GPCT_PFI_OTHER_SELECT(x) NI_USUAL_PFI_SELECT(x)
|
||||
|
||||
/* start sources for ni general-purpose counters for use with
|
||||
INSN_CONFIG_ARM */
|
||||
/*
|
||||
* start sources for ni general-purpose counters for use with
|
||||
* INSN_CONFIG_ARM
|
||||
*/
|
||||
enum ni_gpct_arm_source {
|
||||
NI_GPCT_ARM_IMMEDIATE = 0x0,
|
||||
NI_GPCT_ARM_PAIRED_IMMEDIATE = 0x1, /* Start both the counter
|
||||
* and the adjacent paired
|
||||
* counter simultaneously */
|
||||
/* NI doesn't document bits for selecting hardware arm triggers.
|
||||
/*
|
||||
* Start both the counter and the adjacent pared
|
||||
* counter simultaneously
|
||||
*/
|
||||
NI_GPCT_ARM_PAIRED_IMMEDIATE = 0x1,
|
||||
/*
|
||||
* NI doesn't document bits for selecting hardware arm triggers.
|
||||
* If the NI_GPCT_ARM_UNKNOWN bit is set, we will pass the least
|
||||
* significant bits (3 bits for 660x or 5 bits for m-series)
|
||||
* through to the hardware. This will at least allow someone to
|
||||
* figure out what the bits do later. */
|
||||
* figure out what the bits do later.
|
||||
*/
|
||||
NI_GPCT_ARM_UNKNOWN = 0x1000,
|
||||
};
|
||||
|
||||
|
@ -728,8 +745,10 @@ enum ni_gpct_filter_select {
|
|||
NI_GPCT_FILTER_2x_TIMEBASE_3 = 0x6
|
||||
};
|
||||
|
||||
/* PFI digital filtering options for ni m-series for use with
|
||||
* INSN_CONFIG_FILTER. */
|
||||
/*
|
||||
* PFI digital filtering options for ni m-series for use with
|
||||
* INSN_CONFIG_FILTER.
|
||||
*/
|
||||
enum ni_pfi_filter_select {
|
||||
NI_PFI_FILTER_OFF = 0x0,
|
||||
NI_PFI_FILTER_125ns = 0x1,
|
||||
|
@ -740,9 +759,11 @@ enum ni_pfi_filter_select {
|
|||
/* master clock sources for ni mio boards and INSN_CONFIG_SET_CLOCK_SRC */
|
||||
enum ni_mio_clock_source {
|
||||
NI_MIO_INTERNAL_CLOCK = 0,
|
||||
NI_MIO_RTSI_CLOCK = 1, /* doesn't work for m-series, use
|
||||
NI_MIO_PLL_RTSI_CLOCK() */
|
||||
/* the NI_MIO_PLL_* sources are m-series only */
|
||||
/*
|
||||
* Doesn't work for m-series, use NI_MIO_PLL_RTSI_CLOCK()
|
||||
* the NI_MIO_PLL_* sources are m-series only
|
||||
*/
|
||||
NI_MIO_RTSI_CLOCK = 1,
|
||||
NI_MIO_PLL_PXI_STAR_TRIGGER_CLOCK = 2,
|
||||
NI_MIO_PLL_PXI10_CLOCK = 3,
|
||||
NI_MIO_PLL_RTSI0_CLOCK = 4
|
||||
|
@ -750,9 +771,11 @@ enum ni_mio_clock_source {
|
|||
|
||||
#define NI_MIO_PLL_RTSI_CLOCK(x) (NI_MIO_PLL_RTSI0_CLOCK + (x))
|
||||
|
||||
/* Signals which can be routed to an NI RTSI pin with INSN_CONFIG_SET_ROUTING.
|
||||
The numbers assigned are not arbitrary, they correspond to the bits required
|
||||
to program the board. */
|
||||
/*
|
||||
* Signals which can be routed to an NI RTSI pin with INSN_CONFIG_SET_ROUTING.
|
||||
* The numbers assigned are not arbitrary, they correspond to the bits required
|
||||
* to program the board.
|
||||
*/
|
||||
enum ni_rtsi_routing {
|
||||
NI_RTSI_OUTPUT_ADR_START1 = 0,
|
||||
NI_RTSI_OUTPUT_ADR_START2 = 1,
|
||||
|
@ -763,17 +786,19 @@ enum ni_rtsi_routing {
|
|||
NI_RTSI_OUTPUT_G_GATE0 = 6,
|
||||
NI_RTSI_OUTPUT_RGOUT0 = 7,
|
||||
NI_RTSI_OUTPUT_RTSI_BRD_0 = 8,
|
||||
NI_RTSI_OUTPUT_RTSI_OSC = 12 /* pre-m-series always have RTSI
|
||||
* clock on line 7 */
|
||||
/* Pre-m-series always have RTSI clock on line 7 */
|
||||
NI_RTSI_OUTPUT_RTSI_OSC = 12
|
||||
};
|
||||
|
||||
#define NI_RTSI_OUTPUT_RTSI_BRD(x) (NI_RTSI_OUTPUT_RTSI_BRD_0 + (x))
|
||||
|
||||
/* Signals which can be routed to an NI PFI pin on an m-series board with
|
||||
/*
|
||||
* Signals which can be routed to an NI PFI pin on an m-series board with
|
||||
* INSN_CONFIG_SET_ROUTING. These numbers are also returned by
|
||||
* INSN_CONFIG_GET_ROUTING on pre-m-series boards, even though their routing
|
||||
* cannot be changed. The numbers assigned are not arbitrary, they correspond
|
||||
* to the bits required to program the board. */
|
||||
* to the bits required to program the board.
|
||||
*/
|
||||
enum ni_pfi_routing {
|
||||
NI_PFI_OUTPUT_PFI_DEFAULT = 0,
|
||||
NI_PFI_OUTPUT_AI_START1 = 1,
|
||||
|
@ -803,20 +828,24 @@ enum ni_pfi_routing {
|
|||
|
||||
#define NI_PFI_OUTPUT_RTSI(x) (NI_PFI_OUTPUT_RTSI0 + (x))
|
||||
|
||||
/* Signals which can be routed to output on a NI PFI pin on a 660x board
|
||||
with INSN_CONFIG_SET_ROUTING. The numbers assigned are
|
||||
not arbitrary, they correspond to the bits required
|
||||
to program the board. Lines 0 to 7 can only be set to
|
||||
NI_660X_PFI_OUTPUT_DIO. Lines 32 to 39 can only be set to
|
||||
NI_660X_PFI_OUTPUT_COUNTER. */
|
||||
/*
|
||||
* Signals which can be routed to output on a NI PFI pin on a 660x board
|
||||
* with INSN_CONFIG_SET_ROUTING. The numbers assigned are
|
||||
* not arbitrary, they correspond to the bits required
|
||||
* to program the board. Lines 0 to 7 can only be set to
|
||||
* NI_660X_PFI_OUTPUT_DIO. Lines 32 to 39 can only be set to
|
||||
* NI_660X_PFI_OUTPUT_COUNTER.
|
||||
*/
|
||||
enum ni_660x_pfi_routing {
|
||||
NI_660X_PFI_OUTPUT_COUNTER = 1, /* counter */
|
||||
NI_660X_PFI_OUTPUT_DIO = 2, /* static digital output */
|
||||
};
|
||||
|
||||
/* NI External Trigger lines. These values are not arbitrary, but are related
|
||||
/*
|
||||
* NI External Trigger lines. These values are not arbitrary, but are related
|
||||
* to the bits required to program the board (offset by 1 for historical
|
||||
* reasons). */
|
||||
* reasons).
|
||||
*/
|
||||
#define NI_EXT_PFI(x) (NI_USUAL_PFI_SELECT(x) - 1)
|
||||
#define NI_EXT_RTSI(x) (NI_USUAL_RTSI_SELECT(x) - 1)
|
||||
|
||||
|
@ -827,9 +856,11 @@ enum comedi_counter_status_flags {
|
|||
COMEDI_COUNTER_TERMINAL_COUNT = 0x4,
|
||||
};
|
||||
|
||||
/* Clock sources for CDIO subdevice on NI m-series boards. Used as the
|
||||
/*
|
||||
* Clock sources for CDIO subdevice on NI m-series boards. Used as the
|
||||
* scan_begin_arg for a comedi_command. These sources may also be bitwise-or'd
|
||||
* with CR_INVERT to change polarity. */
|
||||
* with CR_INVERT to change polarity.
|
||||
*/
|
||||
enum ni_m_series_cdio_scan_begin_src {
|
||||
NI_CDIO_SCAN_BEGIN_SRC_GROUND = 0,
|
||||
NI_CDIO_SCAN_BEGIN_SRC_AI_START = 18,
|
||||
|
@ -846,38 +877,50 @@ enum ni_m_series_cdio_scan_begin_src {
|
|||
#define NI_CDIO_SCAN_BEGIN_SRC_PFI(x) NI_USUAL_PFI_SELECT(x)
|
||||
#define NI_CDIO_SCAN_BEGIN_SRC_RTSI(x) NI_USUAL_RTSI_SELECT(x)
|
||||
|
||||
/* scan_begin_src for scan_begin_arg==TRIG_EXT with analog output command on NI
|
||||
/*
|
||||
* scan_begin_src for scan_begin_arg==TRIG_EXT with analog output command on NI
|
||||
* boards. These scan begin sources can also be bitwise-or'd with CR_INVERT to
|
||||
* change polarity. */
|
||||
* change polarity.
|
||||
*/
|
||||
#define NI_AO_SCAN_BEGIN_SRC_PFI(x) NI_USUAL_PFI_SELECT(x)
|
||||
#define NI_AO_SCAN_BEGIN_SRC_RTSI(x) NI_USUAL_RTSI_SELECT(x)
|
||||
|
||||
/* Bits for setting a clock source with
|
||||
* INSN_CONFIG_SET_CLOCK_SRC when using NI frequency output subdevice. */
|
||||
/*
|
||||
* Bits for setting a clock source with
|
||||
* INSN_CONFIG_SET_CLOCK_SRC when using NI frequency output subdevice.
|
||||
*/
|
||||
enum ni_freq_out_clock_source_bits {
|
||||
NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC, /* 10 MHz */
|
||||
NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC /* 100 KHz */
|
||||
};
|
||||
|
||||
/* Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
|
||||
* 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver). */
|
||||
/*
|
||||
* Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
|
||||
* 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver).
|
||||
*/
|
||||
enum amplc_dio_clock_source {
|
||||
AMPLC_DIO_CLK_CLKN, /* per channel external clock
|
||||
input/output pin (pin is only an
|
||||
input when clock source set to this
|
||||
value, otherwise it is an output) */
|
||||
/*
|
||||
* Per channel external clock
|
||||
* input/output pin (pin is only an
|
||||
* input when clock source set to this value,
|
||||
* otherwise it is an output)
|
||||
*/
|
||||
AMPLC_DIO_CLK_CLKN,
|
||||
AMPLC_DIO_CLK_10MHZ, /* 10 MHz internal clock */
|
||||
AMPLC_DIO_CLK_1MHZ, /* 1 MHz internal clock */
|
||||
AMPLC_DIO_CLK_100KHZ, /* 100 kHz internal clock */
|
||||
AMPLC_DIO_CLK_10KHZ, /* 10 kHz internal clock */
|
||||
AMPLC_DIO_CLK_1KHZ, /* 1 kHz internal clock */
|
||||
AMPLC_DIO_CLK_OUTNM1, /* output of preceding counter channel
|
||||
(for channel 0, preceding counter
|
||||
channel is channel 2 on preceding
|
||||
counter subdevice, for first counter
|
||||
subdevice, preceding counter
|
||||
subdevice is the last counter
|
||||
subdevice) */
|
||||
/*
|
||||
* Output of preceding counter channel
|
||||
* (for channel 0, preceding counter
|
||||
* channel is channel 2 on preceding
|
||||
* counter subdevice, for first counter
|
||||
* subdevice, preceding counter
|
||||
* subdevice is the last counter
|
||||
* subdevice)
|
||||
*/
|
||||
AMPLC_DIO_CLK_OUTNM1,
|
||||
AMPLC_DIO_CLK_EXT, /* per chip external input pin */
|
||||
/* the following are "enhanced" clock sources for PCIe models */
|
||||
AMPLC_DIO_CLK_VCC, /* clock input HIGH */
|
||||
|
@ -886,35 +929,39 @@ enum amplc_dio_clock_source {
|
|||
AMPLC_DIO_CLK_20MHZ /* 20 MHz internal clock */
|
||||
};
|
||||
|
||||
/* Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
|
||||
* timer subdevice on some Amplicon DIO PCIe boards (amplc_dio200 driver). */
|
||||
/*
|
||||
* Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for
|
||||
* timer subdevice on some Amplicon DIO PCIe boards (amplc_dio200 driver).
|
||||
*/
|
||||
enum amplc_dio_ts_clock_src {
|
||||
AMPLC_DIO_TS_CLK_1GHZ, /* 1 ns period with 20 ns granularity */
|
||||
AMPLC_DIO_TS_CLK_1MHZ, /* 1 us period */
|
||||
AMPLC_DIO_TS_CLK_1KHZ /* 1 ms period */
|
||||
};
|
||||
|
||||
/* Values for setting a gate source with INSN_CONFIG_SET_GATE_SRC for
|
||||
* 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver). */
|
||||
/*
|
||||
* Values for setting a gate source with INSN_CONFIG_SET_GATE_SRC for
|
||||
* 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver).
|
||||
*/
|
||||
enum amplc_dio_gate_source {
|
||||
AMPLC_DIO_GAT_VCC, /* internal high logic level */
|
||||
AMPLC_DIO_GAT_GND, /* internal low logic level */
|
||||
AMPLC_DIO_GAT_GATN, /* per channel external gate input */
|
||||
AMPLC_DIO_GAT_NOUTNM2, /* negated output of counter channel
|
||||
minus 2 (for channels 0 or 1,
|
||||
channel minus 2 is channel 1 or 2 on
|
||||
the preceding counter subdevice, for
|
||||
the first counter subdevice the
|
||||
preceding counter subdevice is the
|
||||
last counter subdevice) */
|
||||
/*
|
||||
* negated output of counter channel minus 2
|
||||
* (for channels 0 or 1, channel minus 2 is channel 1 or 2 on
|
||||
* the preceding counter subdevice, for the first counter subdevice
|
||||
* the preceding counter subdevice is the last counter subdevice)
|
||||
*/
|
||||
AMPLC_DIO_GAT_NOUTNM2,
|
||||
AMPLC_DIO_GAT_RESERVED4,
|
||||
AMPLC_DIO_GAT_RESERVED5,
|
||||
AMPLC_DIO_GAT_RESERVED6,
|
||||
AMPLC_DIO_GAT_RESERVED7,
|
||||
/* the following are "enhanced" gate sources for PCIe models */
|
||||
AMPLC_DIO_GAT_NGATN = 6, /* negated per channel gate input */
|
||||
AMPLC_DIO_GAT_OUTNM2, /* non-negated output of counter
|
||||
channel minus 2 */
|
||||
/* non-negated output of counter channel minus 2 */
|
||||
AMPLC_DIO_GAT_OUTNM2,
|
||||
AMPLC_DIO_GAT_PAT_PRESENT, /* "pattern present" signal */
|
||||
AMPLC_DIO_GAT_PAT_OCCURRED, /* "pattern occurred" latched */
|
||||
AMPLC_DIO_GAT_PAT_GONE, /* "pattern gone away" latched */
|
||||
|
|
|
@ -2303,11 +2303,13 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
|
|||
{
|
||||
struct comedi_subdevice *s;
|
||||
struct comedi_async *async;
|
||||
int n, m, count = 0, retval = 0;
|
||||
unsigned int n, m;
|
||||
ssize_t count = 0;
|
||||
int retval = 0;
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
struct comedi_file *cfp = file->private_data;
|
||||
struct comedi_device *dev = cfp->dev;
|
||||
bool on_wait_queue = false;
|
||||
bool become_nonbusy = false;
|
||||
bool attach_locked;
|
||||
unsigned int old_detach_count;
|
||||
|
||||
|
@ -2329,74 +2331,33 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
|
|||
}
|
||||
|
||||
async = s->async;
|
||||
|
||||
if (!s->busy || !nbytes)
|
||||
goto out;
|
||||
if (s->busy != file) {
|
||||
retval = -EACCES;
|
||||
goto out;
|
||||
}
|
||||
if (!(async->cmd.flags & CMDF_WRITE)) {
|
||||
if (s->busy != file || !(async->cmd.flags & CMDF_WRITE)) {
|
||||
retval = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
add_wait_queue(&async->wait_head, &wait);
|
||||
on_wait_queue = true;
|
||||
while (nbytes > 0 && !retval) {
|
||||
while (count == 0 && !retval) {
|
||||
unsigned runflags;
|
||||
unsigned int wp, n1, n2;
|
||||
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
runflags = comedi_get_subdevice_runflags(s);
|
||||
if (!comedi_is_runflags_running(runflags)) {
|
||||
if (count == 0) {
|
||||
struct comedi_subdevice *new_s;
|
||||
|
||||
if (comedi_is_runflags_in_error(runflags))
|
||||
retval = -EPIPE;
|
||||
else
|
||||
retval = 0;
|
||||
/*
|
||||
* To avoid deadlock, cannot acquire dev->mutex
|
||||
* while dev->attach_lock is held. Need to
|
||||
* remove task from the async wait queue before
|
||||
* releasing dev->attach_lock, as it might not
|
||||
* be valid afterwards.
|
||||
*/
|
||||
remove_wait_queue(&async->wait_head, &wait);
|
||||
on_wait_queue = false;
|
||||
up_read(&dev->attach_lock);
|
||||
attach_locked = false;
|
||||
mutex_lock(&dev->mutex);
|
||||
/*
|
||||
* Become non-busy unless things have changed
|
||||
* behind our back. Checking dev->detach_count
|
||||
* is unchanged ought to be sufficient (unless
|
||||
* there have been 2**32 detaches in the
|
||||
* meantime!), but check the subdevice pointer
|
||||
* as well just in case.
|
||||
*/
|
||||
new_s = comedi_file_write_subdevice(file);
|
||||
if (dev->attached &&
|
||||
old_detach_count == dev->detach_count &&
|
||||
s == new_s && new_s->async == async)
|
||||
do_become_nonbusy(dev, s);
|
||||
mutex_unlock(&dev->mutex);
|
||||
}
|
||||
if (comedi_is_runflags_in_error(runflags))
|
||||
retval = -EPIPE;
|
||||
if (retval || nbytes)
|
||||
become_nonbusy = true;
|
||||
break;
|
||||
}
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
n = nbytes;
|
||||
|
||||
m = n;
|
||||
if (async->buf_write_ptr + m > async->prealloc_bufsz)
|
||||
m = async->prealloc_bufsz - async->buf_write_ptr;
|
||||
/* Allocate all free buffer space. */
|
||||
comedi_buf_write_alloc(s, async->prealloc_bufsz);
|
||||
if (m > comedi_buf_write_n_allocated(s))
|
||||
m = comedi_buf_write_n_allocated(s);
|
||||
if (m < n)
|
||||
n = m;
|
||||
m = comedi_buf_write_n_allocated(s);
|
||||
n = min_t(size_t, m, nbytes);
|
||||
|
||||
if (n == 0) {
|
||||
if (file->f_flags & O_NONBLOCK) {
|
||||
|
@ -2408,21 +2369,22 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
|
|||
retval = -ERESTARTSYS;
|
||||
break;
|
||||
}
|
||||
if (!s->busy)
|
||||
break;
|
||||
if (s->busy != file) {
|
||||
retval = -EACCES;
|
||||
break;
|
||||
}
|
||||
if (!(async->cmd.flags & CMDF_WRITE)) {
|
||||
if (s->busy != file ||
|
||||
!(async->cmd.flags & CMDF_WRITE)) {
|
||||
retval = -EINVAL;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
m = copy_from_user(async->prealloc_buf + async->buf_write_ptr,
|
||||
buf, n);
|
||||
wp = async->buf_write_ptr;
|
||||
n1 = min(n, async->prealloc_bufsz - wp);
|
||||
n2 = n - n1;
|
||||
m = copy_from_user(async->prealloc_buf + wp, buf, n1);
|
||||
if (m)
|
||||
m += n2;
|
||||
else if (n2)
|
||||
m = copy_from_user(async->prealloc_buf, buf + n1, n2);
|
||||
if (m) {
|
||||
n -= m;
|
||||
retval = -EFAULT;
|
||||
|
@ -2433,12 +2395,38 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
|
|||
nbytes -= n;
|
||||
|
||||
buf += n;
|
||||
break; /* makes device work like a pipe */
|
||||
}
|
||||
remove_wait_queue(&async->wait_head, &wait);
|
||||
set_current_state(TASK_RUNNING);
|
||||
if (become_nonbusy && count == 0) {
|
||||
struct comedi_subdevice *new_s;
|
||||
|
||||
/*
|
||||
* To avoid deadlock, cannot acquire dev->mutex
|
||||
* while dev->attach_lock is held.
|
||||
*/
|
||||
up_read(&dev->attach_lock);
|
||||
attach_locked = false;
|
||||
mutex_lock(&dev->mutex);
|
||||
/*
|
||||
* Check device hasn't become detached behind our back.
|
||||
* Checking dev->detach_count is unchanged ought to be
|
||||
* sufficient (unless there have been 2**32 detaches in the
|
||||
* meantime!), but check the subdevice pointer as well just in
|
||||
* case.
|
||||
*
|
||||
* Also check the subdevice is still in a suitable state to
|
||||
* become non-busy in case it changed behind our back.
|
||||
*/
|
||||
new_s = comedi_file_write_subdevice(file);
|
||||
if (dev->attached && old_detach_count == dev->detach_count &&
|
||||
s == new_s && new_s->async == async && s->busy == file &&
|
||||
(async->cmd.flags & CMDF_WRITE) &&
|
||||
!comedi_is_subdevice_running(s))
|
||||
do_become_nonbusy(dev, s);
|
||||
mutex_unlock(&dev->mutex);
|
||||
}
|
||||
out:
|
||||
if (on_wait_queue)
|
||||
remove_wait_queue(&async->wait_head, &wait);
|
||||
set_current_state(TASK_RUNNING);
|
||||
if (attach_locked)
|
||||
up_read(&dev->attach_lock);
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче