Staging/IIO patches for 4.10-rc1
Here's the "big" staging/iio pull request for 4.10-rc1. Not as big as 4.9 was, but still just over a thousand changes. We almost broke even of lines added vs. removed, as the slicoss driver was removed (got a "clean" driver for the same hardware through the netdev tree), and some iio drivers were also dropped, but I think we ended up adding a few thousand lines to the source tree in the end. Other than that it's a lot of minor fixes all over the place, nothing major stands out at all. All of these have been in linux-next for a while. There will be a merge conflict with Al's vfs tree in the lustre code, but the resolution for that should be pretty simple, that too has been in linux-next. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCWFAk1Q8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ymikACg05b0h/iVTTH18474PXXnzw6jk9IAn0gI2fx9 cqp2MglTvphhrXzddL7V =MeTw -----END PGP SIGNATURE----- Merge tag 'staging-4.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging Pull staging/IIO updates from Greg KH: "Here's the "big" staging/iio pull request for 4.10-rc1. Not as big as 4.9 was, but still just over a thousand changes. We almost broke even of lines added vs. removed, as the slicoss driver was removed (got a "clean" driver for the same hardware through the netdev tree), and some iio drivers were also dropped, but I think we ended up adding a few thousand lines to the source tree in the end. Other than that it's a lot of minor fixes all over the place, nothing major stands out at all. All of these have been in linux-next for a while. There will be a merge conflict with Al's vfs tree in the lustre code, but the resolution for that should be pretty simple, that too has been in linux-next" * tag 'staging-4.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (1002 commits) staging: comedi: comedidev.h: Document usage of 'detach' handler staging: fsl-mc: remove unnecessary info prints from bus driver staging: fsl-mc: add sysfs ABI doc staging/lustre/o2iblnd: Fix misspelled attemps->attempts staging/lustre/o2iblnd: Fix misspelling intialized->intialized staging/lustre: Convert all bare unsigned to unsigned int staging/lustre/socklnd: Fix whitespace problem staging/lustre/o2iblnd: Add missing space staging/lustre/lnetselftest: Fix potential integer overflow staging: greybus: audio_module: remove redundant OOM message staging: dgnc: Fix lines longer than 80 characters staging: dgnc: fix blank line after '{' warnings. staging/android: remove Sync Framework tasks from TODO staging/lustre/osc: Revert erroneous list_for_each_entry_safe use staging: slicoss: remove the staging driver staging: lustre: libcfs: remove lnet upcall code staging: lustre: remove set but unused variables staging: lustre: osc: set lock data for readahead lock staging: lustre: import: don't reconnect during connect interpret staging: lustre: clio: remove mtime check in vvp_io_fault_start() ...
This commit is contained in:
Коммит
72cca7baf4
|
@ -0,0 +1,21 @@
|
|||
What: /sys/bus/fsl-mc/drivers/.../bind
|
||||
Date: December 2016
|
||||
Contact: stuart.yoder@nxp.com
|
||||
Description:
|
||||
Writing a device location to this file will cause
|
||||
the driver to attempt to bind to the device found at
|
||||
this location. The format for the location is Object.Id
|
||||
and is the same as found in /sys/bus/fsl-mc/devices/.
|
||||
For example:
|
||||
# echo dpni.2 > /sys/bus/fsl-mc/drivers/fsl_dpaa2_eth/bind
|
||||
|
||||
What: /sys/bus/fsl-mc/drivers/.../unbind
|
||||
Date: December 2016
|
||||
Contact: stuart.yoder@nxp.com
|
||||
Description:
|
||||
Writing a device location to this file will cause the
|
||||
driver to attempt to unbind from the device found at
|
||||
this location. The format for the location is Object.Id
|
||||
and is the same as found in /sys/bus/fsl-mc/devices/.
|
||||
For example:
|
||||
# echo dpni.2 > /sys/bus/fsl-mc/drivers/fsl_dpaa2_eth/unbind
|
|
@ -329,6 +329,7 @@ What: /sys/bus/iio/devices/iio:deviceX/in_pressure_scale
|
|||
What: /sys/bus/iio/devices/iio:deviceX/in_humidityrelative_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_velocity_sqrt(x^2+y^2+z^2)_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_illuminance_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_countY_scale
|
||||
KernelVersion: 2.6.35
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
|
@ -1579,3 +1580,20 @@ Contact: linux-iio@vger.kernel.org
|
|||
Description:
|
||||
Raw (unscaled no offset etc.) electric conductivity reading that
|
||||
can be processed to siemens per meter.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_countY_raw
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Raw counter device counts from channel Y. For quadrature
|
||||
counters, multiplication by an available [Y]_scale results in
|
||||
the counts of a single quadrature signal phase from channel Y.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_indexY_raw
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Raw counter device index value from channel Y. This attribute
|
||||
provides an absolute positional reference (e.g. a pulse once per
|
||||
revolution) which may be used to home positional systems as
|
||||
required.
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_invert
|
||||
Date: October 2016
|
||||
KernelVersion: 4.9
|
||||
Contact: Peter Rosin <peda@axentia.se>
|
||||
Description:
|
||||
The DAC is used to find the peak level of an alternating
|
||||
voltage input signal by a binary search using the output
|
||||
of a comparator wired to an interrupt pin. Like so:
|
||||
_
|
||||
| \
|
||||
input +------>-------|+ \
|
||||
| \
|
||||
.-------. | }---.
|
||||
| | | / |
|
||||
| dac|-->--|- / |
|
||||
| | |_/ |
|
||||
| | |
|
||||
| | |
|
||||
| irq|------<-------'
|
||||
| |
|
||||
'-------'
|
||||
The boolean invert attribute (0/1) should be set when the
|
||||
input signal is centered around the maximum value of the
|
||||
dac instead of zero. The envelope detector will search
|
||||
from below in this case and will also invert the result.
|
||||
The edge/level of the interrupt is also switched to its
|
||||
opposite value.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_compare_interval
|
||||
Date: October 2016
|
||||
KernelVersion: 4.9
|
||||
Contact: Peter Rosin <peda@axentia.se>
|
||||
Description:
|
||||
Number of milliseconds to wait for the comparator in each
|
||||
step of the binary search for the input peak level. Needs
|
||||
to relate to the frequency of the input signal.
|
|
@ -0,0 +1,125 @@
|
|||
What: /sys/bus/iio/devices/iio:deviceX/in_count_count_direction_available
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_count_count_mode_available
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_count_noise_error_available
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_count_quadrature_mode_available
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_index_index_polarity_available
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_index_synchronous_mode_available
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Discrete set of available values for the respective counter
|
||||
configuration are listed in this file.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_countY_count_direction
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Read-only attribute that indicates whether the counter for
|
||||
channel Y is counting up or down.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_countY_count_mode
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Count mode for channel Y. Four count modes are available:
|
||||
normal, range limit, non-recycle, and modulo-n. The preset value
|
||||
for channel Y is used by the count mode where required.
|
||||
|
||||
Normal:
|
||||
Counting is continuous in either direction.
|
||||
|
||||
Range Limit:
|
||||
An upper or lower limit is set, mimicking limit switches
|
||||
in the mechanical counterpart. The upper limit is set to
|
||||
the preset value, while the lower limit is set to 0. The
|
||||
counter freezes at count = preset when counting up, and
|
||||
at count = 0 when counting down. At either of these
|
||||
limits, the counting is resumed only when the count
|
||||
direction is reversed.
|
||||
|
||||
Non-recycle:
|
||||
Counter is disabled whenever a 24-bit count overflow or
|
||||
underflow takes place. The counter is re-enabled when a
|
||||
new count value is loaded to the counter via a preset
|
||||
operation or write to raw.
|
||||
|
||||
Modulo-N:
|
||||
A count boundary is set between 0 and the preset value.
|
||||
The counter is reset to 0 at count = preset when
|
||||
counting up, while the counter is set to the preset
|
||||
value at count = 0 when counting down; the counter does
|
||||
not freeze at the bundary points, but counts
|
||||
continuously throughout.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_countY_noise_error
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Read-only attribute that indicates whether excessive noise is
|
||||
present at the channel Y count inputs in quadrature clock mode;
|
||||
irrelevant in non-quadrature clock mode.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_countY_preset
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
If the counter device supports preset registers, the preset
|
||||
count for channel Y is provided by this attribute.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_countY_quadrature_mode
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Configure channel Y counter for non-quadrature or quadrature
|
||||
clock mode. Selecting non-quadrature clock mode will disable
|
||||
synchronous load mode. In quadrature clock mode, the channel Y
|
||||
scale attribute selects the encoder phase division (scale of 1
|
||||
selects full-cycle, scale of 0.5 selects half-cycle, scale of
|
||||
0.25 selects quarter-cycle) processed by the channel Y counter.
|
||||
|
||||
Non-quadrature:
|
||||
The filter and decoder circuit are bypassed. Encoder A
|
||||
input serves as the count input and B as the UP/DOWN
|
||||
direction control input, with B = 1 selecting UP Count
|
||||
mode and B = 0 selecting Down Count mode.
|
||||
|
||||
Quadrature:
|
||||
Encoder A and B inputs are digitally filtered and
|
||||
decoded for UP/DN clock.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_countY_set_to_preset_on_index
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Whether to set channel Y counter with channel Y preset value
|
||||
when channel Y index input is active, or continuously count.
|
||||
Valid attribute values are boolean.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_indexY_index_polarity
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Active level of channel Y index input; irrelevant in
|
||||
non-synchronous load mode.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_indexY_synchronous_mode
|
||||
KernelVersion: 4.9
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Configure channel Y counter for non-synchronous or synchronous
|
||||
load mode. Synchronous load mode cannot be selected in
|
||||
non-quadrature clock mode.
|
||||
|
||||
Non-synchronous:
|
||||
A logic low level is the active level at this index
|
||||
input. The index function (as enabled via
|
||||
set_to_preset_on_index) is performed directly on the
|
||||
active level of the index input.
|
||||
|
||||
Synchronous:
|
||||
Intended for interfacing with encoder Index output in
|
||||
quadrature clock mode. The active level is configured
|
||||
via index_polarity. The index function (as enabled via
|
||||
set_to_preset_on_index) is performed synchronously with
|
||||
the quadrature clock on the active level of the index
|
||||
input.
|
|
@ -0,0 +1,18 @@
|
|||
What: /sys/bus/iio/devices/iio:deviceX/calibrate
|
||||
Date: July 2015
|
||||
KernelVersion: 4.7
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Writing '1' will perform a FOC (Fast Online Calibration). The
|
||||
corresponding calibration offsets can be read from *_calibbias
|
||||
entries.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/location
|
||||
Date: July 2015
|
||||
KernelVersion: 4.7
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
This attribute returns a string with the physical location where
|
||||
the motion sensor is placed. For example, in a laptop a motion
|
||||
sensor can be located on the base or on the lid. Current valid
|
||||
values are 'base' and 'lid'.
|
|
@ -0,0 +1,8 @@
|
|||
What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_raw_available
|
||||
Date: October 2016
|
||||
KernelVersion: 4.9
|
||||
Contact: Peter Rosin <peda@axentia.se>
|
||||
Description:
|
||||
The range of available values represented as the minimum value,
|
||||
the step and the maximum value, all enclosed in square brackets.
|
||||
Example: [0 1 256]
|
|
@ -0,0 +1,19 @@
|
|||
What: /sys/bus/iio/devices/iio:deviceX/proximity_on_chip_ambient_infrared_suppression
|
||||
Date: January 2011
|
||||
KernelVersion: 2.6.37
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
From ISL29018 Data Sheet (FN6619.4, Oct 8, 2012) regarding the
|
||||
infrared suppression:
|
||||
|
||||
Scheme 0, makes full n (4, 8, 12, 16) bits (unsigned) proximity
|
||||
detection. The range of Scheme 0 proximity count is from 0 to
|
||||
2^n. Logic 1 of this bit, Scheme 1, makes n-1 (3, 7, 11, 15)
|
||||
bits (2's complementary) proximity_less_ambient detection. The
|
||||
range of Scheme 1 proximity count is from -2^(n-1) to 2^(n-1).
|
||||
The sign bit is extended for resolutions less than 16. While
|
||||
Scheme 0 has wider dynamic range, Scheme 1 proximity detection
|
||||
is less affected by the ambient IR noise variation.
|
||||
|
||||
0 Sensing IR from LED and ambient
|
||||
1 Sensing IR from LED with ambient IR rejection
|
|
@ -1,18 +1,18 @@
|
|||
What: /sys/bus/iio/devices/device[n]/lux_table
|
||||
KernelVersion: 2.6.37
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
This property gets/sets the table of coefficients
|
||||
used in calculating illuminance in lux.
|
||||
|
||||
What: /sys/bus/iio/devices/device[n]/illuminance0_calibrate
|
||||
What: /sys/bus/iio/devices/device[n]/in_illuminance_calibrate
|
||||
KernelVersion: 2.6.37
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
This property causes an internal calibration of the als gain trim
|
||||
value which is later used in calculating illuminance in lux.
|
||||
|
||||
What: /sys/bus/iio/devices/device[n]/illuminance0_input_target
|
||||
What: /sys/bus/iio/devices/device[n]/in_illuminance_lux_table
|
||||
KernelVersion: 2.6.37
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
This property gets/sets the table of coefficients
|
||||
used in calculating illuminance in lux.
|
||||
|
||||
What: /sys/bus/iio/devices/device[n]/in_illuminance_input_target
|
||||
KernelVersion: 2.6.37
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
|
@ -0,0 +1,8 @@
|
|||
What: /sys/bus/iio/devices/iio:deviceX/out_resistance_raw_available
|
||||
Date: October 2016
|
||||
KernelVersion: 4.9
|
||||
Contact: Peter Rosin <peda@axentia.se>
|
||||
Description:
|
||||
The range of available values represented as the minimum value,
|
||||
the step and the maximum value, all enclosed in square brackets.
|
||||
Example: [0 1 256]
|
|
@ -39,11 +39,13 @@ dallas,ds75 Digital Thermometer and Thermostat
|
|||
dlg,da9053 DA9053: flexible system level PMIC with multicore support
|
||||
dlg,da9063 DA9063: system PMIC for quad-core application processors
|
||||
domintech,dmard09 DMARD09: 3-axis Accelerometer
|
||||
domintech,dmard10 DMARD10: 3-axis Accelerometer
|
||||
epson,rx8010 I2C-BUS INTERFACE REAL TIME CLOCK MODULE
|
||||
epson,rx8025 High-Stability. I2C-Bus INTERFACE REAL TIME CLOCK MODULE
|
||||
epson,rx8581 I2C-BUS INTERFACE REAL TIME CLOCK MODULE
|
||||
fsl,mag3110 MAG3110: Xtrinsic High Accuracy, 3D Magnetometer
|
||||
fsl,mc13892 MC13892: Power Management Integrated Circuit (PMIC) for i.MX35/51
|
||||
fsl,mma7660 MMA7660FC: 3-Axis Orientation/Motion Detection Sensor
|
||||
fsl,mma8450 MMA8450Q: Xtrinsic Low-power, 3-axis Xtrinsic Accelerometer
|
||||
fsl,mpl3115 MPL3115: Absolute Digital Pressure Sensor
|
||||
fsl,mpr121 MPR121: Proximity Capacitive Touch Sensor Controller
|
||||
|
@ -57,6 +59,7 @@ maxim,max1237 Low-Power, 4-/12-Channel, 2-Wire Serial, 12-Bit ADCs
|
|||
maxim,max6625 9-Bit/12-Bit Temperature Sensors with I²C-Compatible Serial Interface
|
||||
mc,rv3029c2 Real Time Clock Module with I2C-Bus
|
||||
mcube,mc3230 mCube 3-axis 8-bit digital accelerometer
|
||||
memsic,mxc6225 MEMSIC 2-axis 8-bit digital accelerometer
|
||||
microchip,mcp4531-502 Microchip 7-bit Single I2C Digital Potentiometer (5k)
|
||||
microchip,mcp4531-103 Microchip 7-bit Single I2C Digital Potentiometer (10k)
|
||||
microchip,mcp4531-503 Microchip 7-bit Single I2C Digital Potentiometer (50k)
|
||||
|
@ -121,6 +124,9 @@ microchip,mcp4662-502 Microchip 8-bit Dual I2C Digital Potentiometer with NV Mem
|
|||
microchip,mcp4662-103 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (10k)
|
||||
microchip,mcp4662-503 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (50k)
|
||||
microchip,mcp4662-104 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (100k)
|
||||
miramems,da226 MiraMEMS DA226 2-axis 14-bit digital accelerometer
|
||||
miramems,da280 MiraMEMS DA280 3-axis 14-bit digital accelerometer
|
||||
miramems,da311 MiraMEMS DA311 3-axis 12-bit digital accelerometer
|
||||
national,lm63 Temperature sensor with integrated fan control
|
||||
national,lm75 I2C TEMP SENSOR
|
||||
national,lm80 Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor
|
||||
|
@ -146,6 +152,7 @@ ricoh,rv5c387a I2C bus SERIAL INTERFACE REAL-TIME CLOCK IC
|
|||
samsung,24ad0xd1 S524AD0XF1 (128K/256K-bit Serial EEPROM for Low Power)
|
||||
sgx,vz89x SGX Sensortech VZ89X Sensors
|
||||
sii,s35390a 2-wire CMOS real-time clock
|
||||
silabs,si7020 Relative Humidity and Temperature Sensors
|
||||
skyworks,sky81452 Skyworks SKY81452: Six-Channel White LED Driver with Touch Panel Bias Supply
|
||||
st,24c256 i2c serial eeprom (24cxx)
|
||||
st,m41t00 Serial real-time clock (RTC)
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
Bindings for ADC envelope detector using a DAC and a comparator
|
||||
|
||||
The DAC is used to find the peak level of an alternating voltage input
|
||||
signal by a binary search using the output of a comparator wired to
|
||||
an interrupt pin. Like so:
|
||||
_
|
||||
| \
|
||||
input +------>-------|+ \
|
||||
| \
|
||||
.-------. | }---.
|
||||
| | | / |
|
||||
| dac|-->--|- / |
|
||||
| | |_/ |
|
||||
| | |
|
||||
| | |
|
||||
| irq|------<-------'
|
||||
| |
|
||||
'-------'
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "axentia,tse850-envelope-detector"
|
||||
- io-channels: Channel node of the dac to be used for comparator input.
|
||||
- io-channel-names: Should be "dac".
|
||||
- interrupt specification for one client interrupt,
|
||||
see ../../interrupt-controller/interrupts.txt for details.
|
||||
- interrupt-names: Should be "comp".
|
||||
|
||||
Example:
|
||||
|
||||
&i2c {
|
||||
dpot: mcp4651-104@28 {
|
||||
compatible = "microchip,mcp4651-104";
|
||||
reg = <0x28>;
|
||||
#io-channel-cells = <1>;
|
||||
};
|
||||
};
|
||||
|
||||
dac: dac {
|
||||
compatible = "dpot-dac";
|
||||
vref-supply = <®_3v3>;
|
||||
io-channels = <&dpot 0>;
|
||||
io-channel-names = "dpot";
|
||||
#io-channel-cells = <1>;
|
||||
};
|
||||
|
||||
envelope-detector {
|
||||
compatible = "axentia,tse850-envelope-detector";
|
||||
io-channels = <&dac 0>;
|
||||
io-channel-names = "dac";
|
||||
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
|
||||
interrupt-names = "comp";
|
||||
};
|
|
@ -0,0 +1,83 @@
|
|||
STMicroelectronics STM32 ADC device driver
|
||||
|
||||
STM32 ADC is a successive approximation analog-to-digital converter.
|
||||
It has several multiplexed input channels. Conversions can be performed
|
||||
in single, continuous, scan or discontinuous mode. Result of the ADC is
|
||||
stored in a left-aligned or right-aligned 32-bit data register.
|
||||
Conversions can be launched in software or using hardware triggers.
|
||||
|
||||
The analog watchdog feature allows the application to detect if the input
|
||||
voltage goes beyond the user-defined, higher or lower thresholds.
|
||||
|
||||
Each STM32 ADC block can have up to 3 ADC instances.
|
||||
|
||||
Each instance supports two contexts to manage conversions, each one has its
|
||||
own configurable sequence and trigger:
|
||||
- regular conversion can be done in sequence, running in background
|
||||
- injected conversions have higher priority, and so have the ability to
|
||||
interrupt regular conversion sequence (either triggered in SW or HW).
|
||||
Regular sequence is resumed, in case it has been interrupted.
|
||||
|
||||
Contents of a stm32 adc root node:
|
||||
-----------------------------------
|
||||
Required properties:
|
||||
- compatible: Should be "st,stm32f4-adc-core".
|
||||
- reg: Offset and length of the ADC block register set.
|
||||
- interrupts: Must contain the interrupt for ADC block.
|
||||
- clocks: Clock for the analog circuitry (common to all ADCs).
|
||||
- clock-names: Must be "adc".
|
||||
- interrupt-controller: Identifies the controller node as interrupt-parent
|
||||
- vref-supply: Phandle to the vref input analog reference voltage.
|
||||
- #interrupt-cells = <1>;
|
||||
- #address-cells = <1>;
|
||||
- #size-cells = <0>;
|
||||
|
||||
Optional properties:
|
||||
- A pinctrl state named "default" for each ADC channel may be defined to set
|
||||
inX ADC pins in mode of operation for analog input on external pin.
|
||||
|
||||
Contents of a stm32 adc child node:
|
||||
-----------------------------------
|
||||
An ADC block node should contain at least one subnode, representing an
|
||||
ADC instance available on the machine.
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "st,stm32f4-adc".
|
||||
- reg: Offset of ADC instance in ADC block (e.g. may be 0x0, 0x100, 0x200).
|
||||
- clocks: Input clock private to this ADC instance.
|
||||
- interrupt-parent: Phandle to the parent interrupt controller.
|
||||
- interrupts: IRQ Line for the ADC (e.g. may be 0 for adc@0, 1 for adc@100 or
|
||||
2 for adc@200).
|
||||
- st,adc-channels: List of single-ended channels muxed for this ADC.
|
||||
It can have up to 16 channels, numbered from 0 to 15 (resp. for in0..in15).
|
||||
- #io-channel-cells = <1>: See the IIO bindings section "IIO consumers" in
|
||||
Documentation/devicetree/bindings/iio/iio-bindings.txt
|
||||
|
||||
Example:
|
||||
adc: adc@40012000 {
|
||||
compatible = "st,stm32f4-adc-core";
|
||||
reg = <0x40012000 0x400>;
|
||||
interrupts = <18>;
|
||||
clocks = <&rcc 0 168>;
|
||||
clock-names = "adc";
|
||||
vref-supply = <®_vref>;
|
||||
interrupt-controller;
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&adc3_in8_pin>;
|
||||
|
||||
#interrupt-cells = <1>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
adc@0 {
|
||||
compatible = "st,stm32f4-adc";
|
||||
#io-channel-cells = <1>;
|
||||
reg = <0x0>;
|
||||
clocks = <&rcc 0 168>;
|
||||
interrupt-parent = <&adc>;
|
||||
interrupts = <0>;
|
||||
st,adc-channels = <8>;
|
||||
};
|
||||
...
|
||||
other adc child nodes follow...
|
||||
};
|
|
@ -3,6 +3,7 @@
|
|||
Required properties:
|
||||
- compatible: Should be "ti,adc141s626" or "ti,adc161s626"
|
||||
- reg: spi chip select number for the device
|
||||
- vdda-supply: supply voltage to VDDA pin
|
||||
|
||||
Recommended properties:
|
||||
- spi-max-frequency: Definition as per
|
||||
|
@ -11,6 +12,7 @@ Recommended properties:
|
|||
Example:
|
||||
adc@0 {
|
||||
compatible = "ti,adc161s626";
|
||||
vdda-supply = <&vdda_fixed>;
|
||||
reg = <0>;
|
||||
spi-max-frequency = <4300000>;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
Bindings for DAC emulation using a digital potentiometer
|
||||
|
||||
It is assumed that the dpot is used as a voltage divider between the
|
||||
current dpot wiper setting and the maximum resistance of the dpot. The
|
||||
divided voltage is provided by a vref regulator.
|
||||
|
||||
.------.
|
||||
.-----------. | |
|
||||
| vref |--' .---.
|
||||
| regulator |--. | |
|
||||
'-----------' | | d |
|
||||
| | p |
|
||||
| | o | wiper
|
||||
| | t |<---------+
|
||||
| | |
|
||||
| '---' dac output voltage
|
||||
| |
|
||||
'------+------------+
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "dpot-dac"
|
||||
- vref-supply: The regulator supplying the voltage divider.
|
||||
- io-channels: Channel node of the dpot to be used for the voltage division.
|
||||
- io-channel-names: Should be "dpot".
|
||||
|
||||
Example:
|
||||
|
||||
&i2c {
|
||||
dpot: mcp4651-503@28 {
|
||||
compatible = "microchip,mcp4651-503";
|
||||
reg = <0x28>;
|
||||
#io-channel-cells = <1>;
|
||||
};
|
||||
};
|
||||
|
||||
dac {
|
||||
compatible = "dpot-dac";
|
||||
vref-supply = <®_3v3>;
|
||||
io-channels = <&dpot 0>;
|
||||
io-channel-names = "dpot";
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
Microchip mcp4725 and mcp4726 DAC device driver
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "microchip,mcp4725" or "microchip,mcp4726"
|
||||
- reg: Should contain the DAC I2C address
|
||||
- vdd-supply: Phandle to the Vdd power supply. This supply is used as a
|
||||
voltage reference on mcp4725. It is used as a voltage reference on
|
||||
mcp4726 if there is no vref-supply specified.
|
||||
|
||||
Optional properties (valid only for mcp4726):
|
||||
- vref-supply: Optional phandle to the Vref power supply. Vref pin is
|
||||
used as a voltage reference when this supply is specified.
|
||||
- microchip,vref-buffered: Boolean to enable buffering of the external
|
||||
Vref pin. This boolean is not valid without the vref-supply. Quoting
|
||||
the datasheet: This is offered in cases where the reference voltage
|
||||
does not have the current capability not to drop its voltage when
|
||||
connected to the internal resistor ladder circuit.
|
||||
|
||||
Examples:
|
||||
|
||||
/* simple mcp4725 */
|
||||
mcp4725@60 {
|
||||
compatible = "microchip,mcp4725";
|
||||
reg = <0x60>;
|
||||
vdd-supply = <&vdac_vdd>;
|
||||
};
|
||||
|
||||
/* mcp4726 with the buffered external reference voltage */
|
||||
mcp4726@60 {
|
||||
compatible = "microchip,mcp4726";
|
||||
reg = <0x60>;
|
||||
vdd-supply = <&vdac_vdd>;
|
||||
vref-supply = <&vdac_vref>;
|
||||
microchip,vref-buffered;
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
Invensense MPU-3050 Gyroscope device tree bindings
|
||||
|
||||
Required properties:
|
||||
- compatible : should be "invensense,mpu3050"
|
||||
- reg : the I2C address of the sensor
|
||||
|
||||
Optional properties:
|
||||
- interrupt-parent : should be the phandle for the interrupt controller
|
||||
- interrupts : interrupt mapping for the trigger interrupt from the
|
||||
internal oscillator. The following IRQ modes are supported:
|
||||
IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_HIGH and
|
||||
IRQ_TYPE_LEVEL_LOW. The driver should detect and configure the hardware
|
||||
for the desired interrupt type.
|
||||
- vdd-supply : supply regulator for the main power voltage.
|
||||
- vlogic-supply : supply regulator for the signal voltage.
|
||||
- mount-matrix : see iio/mount-matrix.txt
|
||||
|
||||
Optional subnodes:
|
||||
- The MPU-3050 will pass through and forward the I2C signals from the
|
||||
incoming I2C bus, alternatively drive traffic to a slave device (usually
|
||||
an accelerometer) on its own initiative. Therefore is supports a subnode
|
||||
i2c gate node. For details see: i2c/i2c-gate.txt
|
||||
|
||||
Example:
|
||||
|
||||
mpu3050@68 {
|
||||
compatible = "invensense,mpu3050";
|
||||
reg = <0x68>;
|
||||
interrupt-parent = <&foo>;
|
||||
interrupts = <12 IRQ_TYPE_EDGE_FALLING>;
|
||||
vdd-supply = <&bar>;
|
||||
vlogic-supply = <&baz>;
|
||||
|
||||
/* External I2C interface */
|
||||
i2c-gate {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
fnord@18 {
|
||||
compatible = "fnord";
|
||||
reg = <0x18>;
|
||||
interrupt-parent = <&foo>;
|
||||
interrupts = <13 IRQ_TYPE_EDGE_FALLING>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
* HTS221 STM humidity + temperature sensor
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "st,hts221"
|
||||
- reg: i2c address of the sensor / spi cs line
|
||||
|
||||
Optional properties:
|
||||
- interrupt-parent: should be the phandle for the interrupt controller
|
||||
- interrupts: interrupt mapping for IRQ. It should be configured with
|
||||
flags IRQ_TYPE_LEVEL_HIGH or IRQ_TYPE_EDGE_RISING.
|
||||
|
||||
Refer to interrupt-controller/interrupts.txt for generic interrupt
|
||||
client node bindings.
|
||||
|
||||
Example:
|
||||
|
||||
hts221@5f {
|
||||
compatible = "st,hts221";
|
||||
reg = <0x5f>;
|
||||
interrupt-parent = <&gpio0>;
|
||||
interrupts = <0 IRQ_TYPE_EDGE_RISING>;
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
* ISL 29018/29023/29035 I2C ALS, Proximity, and Infrared sensor
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: Should be one of
|
||||
"isil,isl29018"
|
||||
"isil,isl29023"
|
||||
"isil,isl29035"
|
||||
- reg: the I2C address of the device
|
||||
|
||||
Optional properties:
|
||||
|
||||
- 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.
|
||||
|
||||
- vcc-supply: phandle to the regulator that provides power to the sensor.
|
||||
|
||||
Example:
|
||||
|
||||
isl29018@44 {
|
||||
compatible = "isil,isl29018";
|
||||
reg = <0x44>;
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <TEGRA_GPIO(Z, 2) IRQ_TYPE_LEVEL_HIGH>;
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
* TAOS TSL 2580/2581/2583 ALS sensor
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: Should be one of
|
||||
"amstaos,tsl2580"
|
||||
"amstaos,tsl2581"
|
||||
"amstaos,tsl2583"
|
||||
- reg: the I2C address of the device
|
||||
|
||||
Optional properties:
|
||||
|
||||
- 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.
|
||||
|
||||
- vcc-supply: phandle to the regulator that provides power to the sensor.
|
||||
|
||||
Example:
|
||||
|
||||
tsl2581@29 {
|
||||
compatible = "amstaos,tsl2581";
|
||||
reg = <0x29>;
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
* Texas Instruments LMP91000 potentiostat
|
||||
|
||||
http://www.ti.com/lit/ds/symlink/lmp91000.pdf
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: should be "ti,lmp91000"
|
||||
- reg: the I2C address of the device
|
||||
- io-channels: the phandle of the iio provider
|
||||
|
||||
- ti,external-tia-resistor: if the property ti,tia-gain-ohm is not defined this
|
||||
needs to be set to signal that an external resistor value is being used.
|
||||
|
||||
Optional properties:
|
||||
|
||||
- ti,tia-gain-ohm: ohm value of the internal resistor for the transimpedance
|
||||
amplifier. Must be 2750, 3500, 7000, 14000, 35000, 120000, or 350000 ohms.
|
||||
|
||||
- ti,rload-ohm: ohm value of the internal resistor load applied to the gas
|
||||
sensor. Must be 10, 33, 50, or 100 (default) ohms.
|
||||
|
||||
Example:
|
||||
|
||||
lmp91000@48 {
|
||||
compatible = "ti,lmp91000";
|
||||
reg = <0x48>;
|
||||
ti,tia-gain-ohm = <7500>;
|
||||
ti,rload = <100>;
|
||||
io-channels = <&adc>;
|
||||
};
|
|
@ -42,6 +42,7 @@ Accelerometers:
|
|||
- st,lsm303agr-accel
|
||||
- st,lis2dh12-accel
|
||||
- st,h3lis331dl-accel
|
||||
- st,lng2dm-accel
|
||||
|
||||
Gyroscopes:
|
||||
- st,l3g4200d-gyro
|
||||
|
|
|
@ -39,6 +39,7 @@ auo AU Optronics Corporation
|
|||
auvidea Auvidea GmbH
|
||||
avago Avago Technologies
|
||||
avic Shanghai AVIC Optoelectronics Co., Ltd.
|
||||
axentia Axentia Technologies AB
|
||||
axis Axis Communications AB
|
||||
boe BOE Technology Group Co., Ltd.
|
||||
bosch Bosch Sensortec GmbH
|
||||
|
@ -160,16 +161,19 @@ lltc Linear Technology Corporation
|
|||
lsi LSI Corp. (LSI Logic)
|
||||
marvell Marvell Technology Group Ltd.
|
||||
maxim Maxim Integrated Products
|
||||
mcube mCube
|
||||
meas Measurement Specialties
|
||||
mediatek MediaTek Inc.
|
||||
melexis Melexis N.V.
|
||||
melfas MELFAS Inc.
|
||||
memsic MEMSIC Inc.
|
||||
merrii Merrii Technology Co., Ltd.
|
||||
micrel Micrel Inc.
|
||||
microchip Microchip Technology Inc.
|
||||
microcrystal Micro Crystal AG
|
||||
micron Micron Technology Inc.
|
||||
minix MINIX Technology Ltd.
|
||||
miramems MiraMEMS Sensing Technology Co., Ltd.
|
||||
mitsubishi Mitsubishi Electric Corporation
|
||||
mosaixtech Mosaix Technologies, Inc.
|
||||
moxa Moxa
|
||||
|
|
52
MAINTAINERS
52
MAINTAINERS
|
@ -260,6 +260,12 @@ L: linux-gpio@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/gpio/gpio-104-idio-16.c
|
||||
|
||||
ACCES 104-QUAD-8 IIO DRIVER
|
||||
M: William Breathitt Gray <vilhelm.gray@gmail.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/iio/counter/104-quad-8.c
|
||||
|
||||
ACENIC DRIVER
|
||||
M: Jes Sorensen <jes@trained-monkey.org>
|
||||
L: linux-acenic@sunsite.dk
|
||||
|
@ -803,7 +809,7 @@ S: Supported
|
|||
F: drivers/iio/*/ad*
|
||||
X: drivers/iio/*/adjd*
|
||||
F: drivers/staging/iio/*/ad*
|
||||
F: staging/iio/trigger/iio-trig-bfin-timer.c
|
||||
F: drivers/staging/iio/trigger/iio-trig-bfin-timer.c
|
||||
|
||||
ANALOG DEVICES INC DMA DRIVERS
|
||||
M: Lars-Peter Clausen <lars@metafoo.de>
|
||||
|
@ -2612,6 +2618,7 @@ L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
|
|||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/rpi/linux-rpi.git
|
||||
S: Maintained
|
||||
N: bcm2835
|
||||
F: drivers/staging/vc04_services
|
||||
|
||||
BROADCOM BCM47XX MIPS ARCHITECTURE
|
||||
M: Hauke Mehrtens <hauke@hauke-m.de>
|
||||
|
@ -5192,13 +5199,6 @@ F: sound/soc/fsl/fsl*
|
|||
F: sound/soc/fsl/imx*
|
||||
F: sound/soc/fsl/mpc8610_hpcd.c
|
||||
|
||||
FREESCALE QORIQ MANAGEMENT COMPLEX DRIVER
|
||||
M: "J. German Rivera" <German.Rivera@freescale.com>
|
||||
M: Stuart Yoder <stuart.yoder@nxp.com>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/staging/fsl-mc/
|
||||
|
||||
FREEVXFS FILESYSTEM
|
||||
M: Christoph Hellwig <hch@infradead.org>
|
||||
W: ftp://ftp.openlinux.org/pub/people/hch/vxfs
|
||||
|
@ -6215,6 +6215,22 @@ L: linux-media@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/media/rc/iguanair.c
|
||||
|
||||
IIO DIGITAL POTENTIOMETER DAC
|
||||
M: Peter Rosin <peda@axentia.se>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-bus-iio-dac-dpot-dac
|
||||
F: Documentation/devicetree/bindings/iio/dac/dpot-dac.txt
|
||||
F: drivers/iio/dac/dpot-dac.c
|
||||
|
||||
IIO ENVELOPE DETECTOR
|
||||
M: Peter Rosin <peda@axentia.se>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector
|
||||
F: Documentation/devicetree/bindings/iio/adc/envelope-detector.txt
|
||||
F: drivers/iio/adc/envelope-detector.c
|
||||
|
||||
IIO SUBSYSTEM AND DRIVERS
|
||||
M: Jonathan Cameron <jic23@kernel.org>
|
||||
R: Hartmut Knaack <knaack.h@gmx.de>
|
||||
|
@ -6596,6 +6612,13 @@ S: Maintained
|
|||
F: arch/x86/include/asm/pmc_core.h
|
||||
F: drivers/platform/x86/intel_pmc_core*
|
||||
|
||||
INVENSENSE MPU-3050 GYROSCOPE DRIVER
|
||||
M: Linus Walleij <linus.walleij@linaro.org>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/iio/gyro/mpu3050*
|
||||
F: Documentation/devicetree/bindings/iio/gyroscope/inv,mpu3050.txt
|
||||
|
||||
IOC3 ETHERNET DRIVER
|
||||
M: Ralf Baechle <ralf@linux-mips.org>
|
||||
L: linux-mips@linux-mips.org
|
||||
|
@ -7803,6 +7826,7 @@ MCP4531 MICROCHIP DIGITAL POTENTIOMETER DRIVER
|
|||
M: Peter Rosin <peda@axentia.se>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-bus-iio-potentiometer-mcp4531
|
||||
F: drivers/iio/potentiometer/mcp4531.c
|
||||
|
||||
MEASUREMENT COMPUTING CIO-DAC IIO DRIVER
|
||||
|
@ -10056,6 +10080,12 @@ F: fs/qnx4/
|
|||
F: include/uapi/linux/qnx4_fs.h
|
||||
F: include/uapi/linux/qnxtypes.h
|
||||
|
||||
QORIQ DPAA2 FSL-MC BUS DRIVER
|
||||
M: Stuart Yoder <stuart.yoder@nxp.com>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/staging/fsl-mc/
|
||||
|
||||
QT1010 MEDIA DRIVER
|
||||
M: Antti Palosaari <crope@iki.fi>
|
||||
L: linux-media@vger.kernel.org
|
||||
|
@ -11647,12 +11677,6 @@ L: linux-fbdev@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/staging/sm750fb/
|
||||
|
||||
STAGING - SLICOSS
|
||||
M: Lior Dotan <liodot@gmail.com>
|
||||
M: Christopher Harrer <charrer@alacritech.com>
|
||||
S: Odd Fixes
|
||||
F: drivers/staging/slicoss/
|
||||
|
||||
STAGING - SPEAKUP CONSOLE SPEECH DRIVER
|
||||
M: William Hubbs <w.d.hubbs@gmail.com>
|
||||
M: Chris Brannon <chris@the-brannons.com>
|
||||
|
|
|
@ -73,6 +73,7 @@ source "drivers/iio/adc/Kconfig"
|
|||
source "drivers/iio/amplifiers/Kconfig"
|
||||
source "drivers/iio/chemical/Kconfig"
|
||||
source "drivers/iio/common/Kconfig"
|
||||
source "drivers/iio/counter/Kconfig"
|
||||
source "drivers/iio/dac/Kconfig"
|
||||
source "drivers/iio/dummy/Kconfig"
|
||||
source "drivers/iio/frequency/Kconfig"
|
||||
|
@ -87,6 +88,7 @@ if IIO_TRIGGER
|
|||
source "drivers/iio/trigger/Kconfig"
|
||||
endif #IIO_TRIGGER
|
||||
source "drivers/iio/potentiometer/Kconfig"
|
||||
source "drivers/iio/potentiostat/Kconfig"
|
||||
source "drivers/iio/pressure/Kconfig"
|
||||
source "drivers/iio/proximity/Kconfig"
|
||||
source "drivers/iio/temperature/Kconfig"
|
||||
|
|
|
@ -18,6 +18,7 @@ obj-y += amplifiers/
|
|||
obj-y += buffer/
|
||||
obj-y += chemical/
|
||||
obj-y += common/
|
||||
obj-y += counter/
|
||||
obj-y += dac/
|
||||
obj-y += dummy/
|
||||
obj-y += gyro/
|
||||
|
@ -29,6 +30,7 @@ obj-y += light/
|
|||
obj-y += magnetometer/
|
||||
obj-y += orientation/
|
||||
obj-y += potentiometer/
|
||||
obj-y += potentiostat/
|
||||
obj-y += pressure/
|
||||
obj-y += proximity/
|
||||
obj-y += temperature/
|
||||
|
|
|
@ -52,6 +52,26 @@ config BMC150_ACCEL_SPI
|
|||
tristate
|
||||
select REGMAP_SPI
|
||||
|
||||
config DA280
|
||||
tristate "MiraMEMS DA280 3-axis 14-bit digital accelerometer driver"
|
||||
depends on I2C
|
||||
help
|
||||
Say yes here to build support for the MiraMEMS DA280 3-axis 14-bit
|
||||
digital accelerometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called da280.
|
||||
|
||||
config DA311
|
||||
tristate "MiraMEMS DA311 3-axis 12-bit digital accelerometer driver"
|
||||
depends on I2C
|
||||
help
|
||||
Say yes here to build support for the MiraMEMS DA311 3-axis 12-bit
|
||||
digital accelerometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called da311.
|
||||
|
||||
config DMARD06
|
||||
tristate "Domintech DMARD06 Digital Accelerometer Driver"
|
||||
depends on OF || COMPILE_TEST
|
||||
|
@ -73,6 +93,16 @@ config DMARD09
|
|||
Choosing M will build the driver as a module. If so, the module
|
||||
will be called dmard09.
|
||||
|
||||
config DMARD10
|
||||
tristate "Domintech DMARD10 3-axis Accelerometer Driver"
|
||||
depends on I2C
|
||||
help
|
||||
Say yes here to get support for the Domintech DMARD10 3-axis
|
||||
accelerometer.
|
||||
|
||||
Choosing M will build the driver as a module. If so, the module
|
||||
will be called dmard10.
|
||||
|
||||
config HID_SENSOR_ACCEL_3D
|
||||
depends on HID_SENSOR_HUB
|
||||
select IIO_BUFFER
|
||||
|
@ -97,7 +127,8 @@ 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, LIS2DH12, H3LIS331DL.
|
||||
LIS331DLH, LSM303DL, LSM303DLM, LSM330, LIS2DH12, H3LIS331DL,
|
||||
LNG2DM
|
||||
|
||||
This driver can also be built as a module. If so, these modules
|
||||
will be created:
|
||||
|
@ -273,6 +304,18 @@ config MXC6255
|
|||
To compile this driver as a module, choose M here: the module will be
|
||||
called mxc6255.
|
||||
|
||||
config SCA3000
|
||||
select IIO_BUFFER
|
||||
select IIO_KFIFO_BUF
|
||||
depends on SPI
|
||||
tristate "VTI SCA3000 series accelerometers"
|
||||
help
|
||||
Say Y here to build support for the VTI SCA3000 series of SPI
|
||||
accelerometers. These devices use a hardware ring buffer.
|
||||
|
||||
To compile this driver as a module, say M here: the module will be
|
||||
called sca3000.
|
||||
|
||||
config STK8312
|
||||
tristate "Sensortek STK8312 3-Axis Accelerometer Driver"
|
||||
depends on I2C
|
||||
|
|
|
@ -8,8 +8,11 @@ obj-$(CONFIG_BMA220) += bma220_spi.o
|
|||
obj-$(CONFIG_BMC150_ACCEL) += bmc150-accel-core.o
|
||||
obj-$(CONFIG_BMC150_ACCEL_I2C) += bmc150-accel-i2c.o
|
||||
obj-$(CONFIG_BMC150_ACCEL_SPI) += bmc150-accel-spi.o
|
||||
obj-$(CONFIG_DA280) += da280.o
|
||||
obj-$(CONFIG_DA311) += da311.o
|
||||
obj-$(CONFIG_DMARD06) += dmard06.o
|
||||
obj-$(CONFIG_DMARD09) += dmard09.o
|
||||
obj-$(CONFIG_DMARD10) += dmard10.o
|
||||
obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o
|
||||
obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o
|
||||
obj-$(CONFIG_KXSD9) += kxsd9.o
|
||||
|
@ -32,6 +35,8 @@ obj-$(CONFIG_MMA9553) += mma9553.o
|
|||
obj-$(CONFIG_MXC4005) += mxc4005.o
|
||||
obj-$(CONFIG_MXC6255) += mxc6255.o
|
||||
|
||||
obj-$(CONFIG_SCA3000) += sca3000.o
|
||||
|
||||
obj-$(CONFIG_STK8312) += stk8312.o
|
||||
obj-$(CONFIG_STK8BA50) += stk8ba50.o
|
||||
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
/**
|
||||
* IIO driver for the MiraMEMS DA280 3-axis accelerometer and
|
||||
* IIO driver for the MiraMEMS DA226 2-axis accelerometer
|
||||
*
|
||||
* Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/byteorder/generic.h>
|
||||
|
||||
#define DA280_REG_CHIP_ID 0x01
|
||||
#define DA280_REG_ACC_X_LSB 0x02
|
||||
#define DA280_REG_ACC_Y_LSB 0x04
|
||||
#define DA280_REG_ACC_Z_LSB 0x06
|
||||
#define DA280_REG_MODE_BW 0x11
|
||||
|
||||
#define DA280_CHIP_ID 0x13
|
||||
#define DA280_MODE_ENABLE 0x1e
|
||||
#define DA280_MODE_DISABLE 0x9e
|
||||
|
||||
enum { da226, da280 };
|
||||
|
||||
/*
|
||||
* a value of + or -4096 corresponds to + or - 1G
|
||||
* scale = 9.81 / 4096 = 0.002395019
|
||||
*/
|
||||
|
||||
static const int da280_nscale = 2395019;
|
||||
|
||||
#define DA280_CHANNEL(reg, axis) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.address = reg, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec da280_channels[] = {
|
||||
DA280_CHANNEL(DA280_REG_ACC_X_LSB, X),
|
||||
DA280_CHANNEL(DA280_REG_ACC_Y_LSB, Y),
|
||||
DA280_CHANNEL(DA280_REG_ACC_Z_LSB, Z),
|
||||
};
|
||||
|
||||
struct da280_data {
|
||||
struct i2c_client *client;
|
||||
};
|
||||
|
||||
static int da280_enable(struct i2c_client *client, bool enable)
|
||||
{
|
||||
u8 data = enable ? DA280_MODE_ENABLE : DA280_MODE_DISABLE;
|
||||
|
||||
return i2c_smbus_write_byte_data(client, DA280_REG_MODE_BW, data);
|
||||
}
|
||||
|
||||
static int da280_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct da280_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = i2c_smbus_read_word_data(data->client, chan->address);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/*
|
||||
* Values are 14 bits, stored as 16 bits with the 2
|
||||
* least significant bits always 0.
|
||||
*/
|
||||
*val = (short)ret >> 2;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
*val2 = da280_nscale;
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info da280_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = da280_read_raw,
|
||||
};
|
||||
|
||||
static int da280_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct iio_dev *indio_dev;
|
||||
struct da280_data *data;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, DA280_REG_CHIP_ID);
|
||||
if (ret != DA280_CHIP_ID)
|
||||
return (ret < 0) ? ret : -ENODEV;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->client = client;
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &da280_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = da280_channels;
|
||||
if (id->driver_data == da226) {
|
||||
indio_dev->name = "da226";
|
||||
indio_dev->num_channels = 2;
|
||||
} else {
|
||||
indio_dev->name = "da280";
|
||||
indio_dev->num_channels = 3;
|
||||
}
|
||||
|
||||
ret = da280_enable(client, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "device_register failed\n");
|
||||
da280_enable(client, false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da280_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
return da280_enable(client, false);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int da280_suspend(struct device *dev)
|
||||
{
|
||||
return da280_enable(to_i2c_client(dev), false);
|
||||
}
|
||||
|
||||
static int da280_resume(struct device *dev)
|
||||
{
|
||||
return da280_enable(to_i2c_client(dev), true);
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(da280_pm_ops, da280_suspend, da280_resume);
|
||||
|
||||
static const struct i2c_device_id da280_i2c_id[] = {
|
||||
{ "da226", da226 },
|
||||
{ "da280", da280 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, da280_i2c_id);
|
||||
|
||||
static struct i2c_driver da280_driver = {
|
||||
.driver = {
|
||||
.name = "da280",
|
||||
.pm = &da280_pm_ops,
|
||||
},
|
||||
.probe = da280_probe,
|
||||
.remove = da280_remove,
|
||||
.id_table = da280_i2c_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(da280_driver);
|
||||
|
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
||||
MODULE_DESCRIPTION("MiraMEMS DA280 3-Axis Accelerometer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,305 @@
|
|||
/**
|
||||
* IIO driver for the MiraMEMS DA311 3-axis accelerometer
|
||||
*
|
||||
* Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com>
|
||||
* Copyright (c) 2011-2013 MiraMEMS Sensing Technology Co., Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/byteorder/generic.h>
|
||||
|
||||
#define DA311_CHIP_ID 0x13
|
||||
|
||||
/*
|
||||
* Note register addressed go from 0 - 0x3f and then wrap.
|
||||
* For some reason there are 2 banks with 0 - 0x3f addresses,
|
||||
* rather then a single 0-0x7f bank.
|
||||
*/
|
||||
|
||||
/* Bank 0 regs */
|
||||
#define DA311_REG_BANK 0x0000
|
||||
#define DA311_REG_LDO_REG 0x0006
|
||||
#define DA311_REG_CHIP_ID 0x000f
|
||||
#define DA311_REG_TEMP_CFG_REG 0x001f
|
||||
#define DA311_REG_CTRL_REG1 0x0020
|
||||
#define DA311_REG_CTRL_REG3 0x0022
|
||||
#define DA311_REG_CTRL_REG4 0x0023
|
||||
#define DA311_REG_CTRL_REG5 0x0024
|
||||
#define DA311_REG_CTRL_REG6 0x0025
|
||||
#define DA311_REG_STATUS_REG 0x0027
|
||||
#define DA311_REG_OUT_X_L 0x0028
|
||||
#define DA311_REG_OUT_X_H 0x0029
|
||||
#define DA311_REG_OUT_Y_L 0x002a
|
||||
#define DA311_REG_OUT_Y_H 0x002b
|
||||
#define DA311_REG_OUT_Z_L 0x002c
|
||||
#define DA311_REG_OUT_Z_H 0x002d
|
||||
#define DA311_REG_INT1_CFG 0x0030
|
||||
#define DA311_REG_INT1_SRC 0x0031
|
||||
#define DA311_REG_INT1_THS 0x0032
|
||||
#define DA311_REG_INT1_DURATION 0x0033
|
||||
#define DA311_REG_INT2_CFG 0x0034
|
||||
#define DA311_REG_INT2_SRC 0x0035
|
||||
#define DA311_REG_INT2_THS 0x0036
|
||||
#define DA311_REG_INT2_DURATION 0x0037
|
||||
#define DA311_REG_CLICK_CFG 0x0038
|
||||
#define DA311_REG_CLICK_SRC 0x0039
|
||||
#define DA311_REG_CLICK_THS 0x003a
|
||||
#define DA311_REG_TIME_LIMIT 0x003b
|
||||
#define DA311_REG_TIME_LATENCY 0x003c
|
||||
#define DA311_REG_TIME_WINDOW 0x003d
|
||||
|
||||
/* Bank 1 regs */
|
||||
#define DA311_REG_SOFT_RESET 0x0105
|
||||
#define DA311_REG_OTP_XOFF_L 0x0110
|
||||
#define DA311_REG_OTP_XOFF_H 0x0111
|
||||
#define DA311_REG_OTP_YOFF_L 0x0112
|
||||
#define DA311_REG_OTP_YOFF_H 0x0113
|
||||
#define DA311_REG_OTP_ZOFF_L 0x0114
|
||||
#define DA311_REG_OTP_ZOFF_H 0x0115
|
||||
#define DA311_REG_OTP_XSO 0x0116
|
||||
#define DA311_REG_OTP_YSO 0x0117
|
||||
#define DA311_REG_OTP_ZSO 0x0118
|
||||
#define DA311_REG_OTP_TRIM_OSC 0x011b
|
||||
#define DA311_REG_LPF_ABSOLUTE 0x011c
|
||||
#define DA311_REG_TEMP_OFF1 0x0127
|
||||
#define DA311_REG_TEMP_OFF2 0x0128
|
||||
#define DA311_REG_TEMP_OFF3 0x0129
|
||||
#define DA311_REG_OTP_TRIM_THERM_H 0x011a
|
||||
|
||||
/*
|
||||
* a value of + or -1024 corresponds to + or - 1G
|
||||
* scale = 9.81 / 1024 = 0.009580078
|
||||
*/
|
||||
|
||||
static const int da311_nscale = 9580078;
|
||||
|
||||
#define DA311_CHANNEL(reg, axis) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.address = reg, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec da311_channels[] = {
|
||||
/* | 0x80 comes from the android driver */
|
||||
DA311_CHANNEL(DA311_REG_OUT_X_L | 0x80, X),
|
||||
DA311_CHANNEL(DA311_REG_OUT_Y_L | 0x80, Y),
|
||||
DA311_CHANNEL(DA311_REG_OUT_Z_L | 0x80, Z),
|
||||
};
|
||||
|
||||
struct da311_data {
|
||||
struct i2c_client *client;
|
||||
};
|
||||
|
||||
static int da311_register_mask_write(struct i2c_client *client, u16 addr,
|
||||
u8 mask, u8 data)
|
||||
{
|
||||
int ret;
|
||||
u8 tmp_data = 0;
|
||||
|
||||
if (addr & 0xff00) {
|
||||
/* Select bank 1 */
|
||||
ret = i2c_smbus_write_byte_data(client, DA311_REG_BANK, 0x01);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (mask != 0xff) {
|
||||
ret = i2c_smbus_read_byte_data(client, addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
tmp_data = ret;
|
||||
}
|
||||
|
||||
tmp_data &= ~mask;
|
||||
tmp_data |= data & mask;
|
||||
ret = i2c_smbus_write_byte_data(client, addr & 0xff, tmp_data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (addr & 0xff00) {
|
||||
/* Back to bank 0 */
|
||||
ret = i2c_smbus_write_byte_data(client, DA311_REG_BANK, 0x00);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Init sequence taken from the android driver */
|
||||
static int da311_reset(struct i2c_client *client)
|
||||
{
|
||||
const struct {
|
||||
u16 addr;
|
||||
u8 mask;
|
||||
u8 data;
|
||||
} init_data[] = {
|
||||
{ DA311_REG_TEMP_CFG_REG, 0xff, 0x08 },
|
||||
{ DA311_REG_CTRL_REG5, 0xff, 0x80 },
|
||||
{ DA311_REG_CTRL_REG4, 0x30, 0x00 },
|
||||
{ DA311_REG_CTRL_REG1, 0xff, 0x6f },
|
||||
{ DA311_REG_TEMP_CFG_REG, 0xff, 0x88 },
|
||||
{ DA311_REG_LDO_REG, 0xff, 0x02 },
|
||||
{ DA311_REG_OTP_TRIM_OSC, 0xff, 0x27 },
|
||||
{ DA311_REG_LPF_ABSOLUTE, 0xff, 0x30 },
|
||||
{ DA311_REG_TEMP_OFF1, 0xff, 0x3f },
|
||||
{ DA311_REG_TEMP_OFF2, 0xff, 0xff },
|
||||
{ DA311_REG_TEMP_OFF3, 0xff, 0x0f },
|
||||
};
|
||||
int i, ret;
|
||||
|
||||
/* Reset */
|
||||
ret = da311_register_mask_write(client, DA311_REG_SOFT_RESET,
|
||||
0xff, 0xaa);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(init_data); i++) {
|
||||
ret = da311_register_mask_write(client,
|
||||
init_data[i].addr,
|
||||
init_data[i].mask,
|
||||
init_data[i].data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int da311_enable(struct i2c_client *client, bool enable)
|
||||
{
|
||||
u8 data = enable ? 0x00 : 0x20;
|
||||
|
||||
return da311_register_mask_write(client, DA311_REG_TEMP_CFG_REG,
|
||||
0x20, data);
|
||||
}
|
||||
|
||||
static int da311_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct da311_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = i2c_smbus_read_word_data(data->client, chan->address);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/*
|
||||
* Values are 12 bits, stored as 16 bits with the 4
|
||||
* least significant bits always 0.
|
||||
*/
|
||||
*val = (short)ret >> 4;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
*val2 = da311_nscale;
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info da311_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = da311_read_raw,
|
||||
};
|
||||
|
||||
static int da311_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct iio_dev *indio_dev;
|
||||
struct da311_data *data;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, DA311_REG_CHIP_ID);
|
||||
if (ret != DA311_CHIP_ID)
|
||||
return (ret < 0) ? ret : -ENODEV;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->client = client;
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &da311_info;
|
||||
indio_dev->name = "da311";
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = da311_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(da311_channels);
|
||||
|
||||
ret = da311_reset(client);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = da311_enable(client, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "device_register failed\n");
|
||||
da311_enable(client, false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da311_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
return da311_enable(client, false);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int da311_suspend(struct device *dev)
|
||||
{
|
||||
return da311_enable(to_i2c_client(dev), false);
|
||||
}
|
||||
|
||||
static int da311_resume(struct device *dev)
|
||||
{
|
||||
return da311_enable(to_i2c_client(dev), true);
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(da311_pm_ops, da311_suspend, da311_resume);
|
||||
|
||||
static const struct i2c_device_id da311_i2c_id[] = {
|
||||
{"da311", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, da311_i2c_id);
|
||||
|
||||
static struct i2c_driver da311_driver = {
|
||||
.driver = {
|
||||
.name = "da311",
|
||||
.pm = &da311_pm_ops,
|
||||
},
|
||||
.probe = da311_probe,
|
||||
.remove = da311_remove,
|
||||
.id_table = da311_i2c_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(da311_driver);
|
||||
|
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
||||
MODULE_DESCRIPTION("MiraMEMS DA311 3-Axis Accelerometer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,266 @@
|
|||
/**
|
||||
* IIO driver for the 3-axis accelerometer Domintech ARD10.
|
||||
*
|
||||
* Copyright (c) 2016 Hans de Goede <hdegoede@redhat.com>
|
||||
* Copyright (c) 2012 Domintech Technology Co., Ltd
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/byteorder/generic.h>
|
||||
|
||||
#define DMARD10_REG_ACTR 0x00
|
||||
#define DMARD10_REG_AFEM 0x0c
|
||||
#define DMARD10_REG_STADR 0x12
|
||||
#define DMARD10_REG_STAINT 0x1c
|
||||
#define DMARD10_REG_MISC2 0x1f
|
||||
#define DMARD10_REG_PD 0x21
|
||||
|
||||
#define DMARD10_MODE_OFF 0x00
|
||||
#define DMARD10_MODE_STANDBY 0x02
|
||||
#define DMARD10_MODE_ACTIVE 0x06
|
||||
#define DMARD10_MODE_READ_OTP 0x12
|
||||
#define DMARD10_MODE_RESET_DATA_PATH 0x82
|
||||
|
||||
/* AFEN set 1, ATM[2:0]=b'000 (normal), EN_Z/Y/X/T=1 */
|
||||
#define DMARD10_VALUE_AFEM_AFEN_NORMAL 0x8f
|
||||
/* ODR[3:0]=b'0111 (100Hz), CCK[3:0]=b'0100 (204.8kHZ) */
|
||||
#define DMARD10_VALUE_CKSEL_ODR_100_204 0x74
|
||||
/* INTC[6:5]=b'00 */
|
||||
#define DMARD10_VALUE_INTC 0x00
|
||||
/* TAP1/TAP2 Average 2 */
|
||||
#define DMARD10_VALUE_TAPNS_AVE_2 0x11
|
||||
|
||||
#define DMARD10_VALUE_STADR 0x55
|
||||
#define DMARD10_VALUE_STAINT 0xaa
|
||||
#define DMARD10_VALUE_MISC2_OSCA_EN 0x08
|
||||
#define DMARD10_VALUE_PD_RST 0x52
|
||||
|
||||
/* Offsets into the buffer read in dmard10_read_raw() */
|
||||
#define DMARD10_X_OFFSET 1
|
||||
#define DMARD10_Y_OFFSET 2
|
||||
#define DMARD10_Z_OFFSET 3
|
||||
|
||||
/*
|
||||
* a value of + or -128 corresponds to + or - 1G
|
||||
* scale = 9.81 / 128 = 0.076640625
|
||||
*/
|
||||
|
||||
static const int dmard10_nscale = 76640625;
|
||||
|
||||
#define DMARD10_CHANNEL(reg, axis) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.address = reg, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec dmard10_channels[] = {
|
||||
DMARD10_CHANNEL(DMARD10_X_OFFSET, X),
|
||||
DMARD10_CHANNEL(DMARD10_Y_OFFSET, Y),
|
||||
DMARD10_CHANNEL(DMARD10_Z_OFFSET, Z),
|
||||
};
|
||||
|
||||
struct dmard10_data {
|
||||
struct i2c_client *client;
|
||||
};
|
||||
|
||||
/* Init sequence taken from the android driver */
|
||||
static int dmard10_reset(struct i2c_client *client)
|
||||
{
|
||||
unsigned char buffer[7];
|
||||
int ret;
|
||||
|
||||
/* 1. Powerdown reset */
|
||||
ret = i2c_smbus_write_byte_data(client, DMARD10_REG_PD,
|
||||
DMARD10_VALUE_PD_RST);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* 2. ACTR => Standby mode => Download OTP to parameter reg =>
|
||||
* Standby mode => Reset data path => Standby mode
|
||||
*/
|
||||
buffer[0] = DMARD10_REG_ACTR;
|
||||
buffer[1] = DMARD10_MODE_STANDBY;
|
||||
buffer[2] = DMARD10_MODE_READ_OTP;
|
||||
buffer[3] = DMARD10_MODE_STANDBY;
|
||||
buffer[4] = DMARD10_MODE_RESET_DATA_PATH;
|
||||
buffer[5] = DMARD10_MODE_STANDBY;
|
||||
ret = i2c_master_send(client, buffer, 6);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* 3. OSCA_EN = 1, TSTO = b'000 (INT1 = normal, TEST0 = normal) */
|
||||
ret = i2c_smbus_write_byte_data(client, DMARD10_REG_MISC2,
|
||||
DMARD10_VALUE_MISC2_OSCA_EN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* 4. AFEN = 1 (AFE will powerdown after ADC) */
|
||||
buffer[0] = DMARD10_REG_AFEM;
|
||||
buffer[1] = DMARD10_VALUE_AFEM_AFEN_NORMAL;
|
||||
buffer[2] = DMARD10_VALUE_CKSEL_ODR_100_204;
|
||||
buffer[3] = DMARD10_VALUE_INTC;
|
||||
buffer[4] = DMARD10_VALUE_TAPNS_AVE_2;
|
||||
buffer[5] = 0x00; /* DLYC, no delay timing */
|
||||
buffer[6] = 0x07; /* INTD=1 push-pull, INTA=1 active high, AUTOT=1 */
|
||||
ret = i2c_master_send(client, buffer, 7);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* 5. Activation mode */
|
||||
ret = i2c_smbus_write_byte_data(client, DMARD10_REG_ACTR,
|
||||
DMARD10_MODE_ACTIVE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Shutdown sequence taken from the android driver */
|
||||
static int dmard10_shutdown(struct i2c_client *client)
|
||||
{
|
||||
unsigned char buffer[3];
|
||||
|
||||
buffer[0] = DMARD10_REG_ACTR;
|
||||
buffer[1] = DMARD10_MODE_STANDBY;
|
||||
buffer[2] = DMARD10_MODE_OFF;
|
||||
|
||||
return i2c_master_send(client, buffer, 3);
|
||||
}
|
||||
|
||||
static int dmard10_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct dmard10_data *data = iio_priv(indio_dev);
|
||||
__le16 buf[4];
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
/*
|
||||
* Read 8 bytes starting at the REG_STADR register, trying to
|
||||
* read the individual X, Y, Z registers will always read 0.
|
||||
*/
|
||||
ret = i2c_smbus_read_i2c_block_data(data->client,
|
||||
DMARD10_REG_STADR,
|
||||
sizeof(buf), (u8 *)buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = le16_to_cpu(buf[chan->address]);
|
||||
*val = sign_extend32(ret, 12);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
*val2 = dmard10_nscale;
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info dmard10_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = dmard10_read_raw,
|
||||
};
|
||||
|
||||
static int dmard10_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct iio_dev *indio_dev;
|
||||
struct dmard10_data *data;
|
||||
|
||||
/* These 2 registers have special POR reset values used for id */
|
||||
ret = i2c_smbus_read_byte_data(client, DMARD10_REG_STADR);
|
||||
if (ret != DMARD10_VALUE_STADR)
|
||||
return (ret < 0) ? ret : -ENODEV;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, DMARD10_REG_STAINT);
|
||||
if (ret != DMARD10_VALUE_STAINT)
|
||||
return (ret < 0) ? ret : -ENODEV;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev) {
|
||||
dev_err(&client->dev, "iio allocation failed!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->client = client;
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &dmard10_info;
|
||||
indio_dev->name = "dmard10";
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = dmard10_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(dmard10_channels);
|
||||
|
||||
ret = dmard10_reset(client);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "device_register failed\n");
|
||||
dmard10_shutdown(client);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dmard10_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
return dmard10_shutdown(client);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int dmard10_suspend(struct device *dev)
|
||||
{
|
||||
return dmard10_shutdown(to_i2c_client(dev));
|
||||
}
|
||||
|
||||
static int dmard10_resume(struct device *dev)
|
||||
{
|
||||
return dmard10_reset(to_i2c_client(dev));
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(dmard10_pm_ops, dmard10_suspend, dmard10_resume);
|
||||
|
||||
static const struct i2c_device_id dmard10_i2c_id[] = {
|
||||
{"dmard10", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, dmard10_i2c_id);
|
||||
|
||||
static struct i2c_driver dmard10_driver = {
|
||||
.driver = {
|
||||
.name = "dmard10",
|
||||
.pm = &dmard10_pm_ops,
|
||||
},
|
||||
.probe = dmard10_probe,
|
||||
.remove = dmard10_remove,
|
||||
.id_table = dmard10_i2c_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(dmard10_driver);
|
||||
|
||||
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
|
||||
MODULE_DESCRIPTION("Domintech ARD10 3-Axis Accelerometer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -39,7 +39,7 @@
|
|||
|
||||
#define MMA7660_SCALE_AVAIL "0.467142857"
|
||||
|
||||
const int mma7660_nscale = 467142857;
|
||||
static const int mma7660_nscale = 467142857;
|
||||
|
||||
#define MMA7660_CHANNEL(reg, axis) { \
|
||||
.type = IIO_ACCEL, \
|
||||
|
|
|
@ -459,12 +459,14 @@ static int mma8452_read_raw(struct iio_dev *indio_dev,
|
|||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
ret = mma8452_read(data, buffer);
|
||||
mutex_unlock(&data->lock);
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -664,37 +666,46 @@ static int mma8452_write_raw(struct iio_dev *indio_dev,
|
|||
struct mma8452_data *data = iio_priv(indio_dev);
|
||||
int i, ret;
|
||||
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
i = mma8452_get_samp_freq_index(data, val, val2);
|
||||
if (i < 0)
|
||||
return i;
|
||||
|
||||
if (i < 0) {
|
||||
ret = i;
|
||||
break;
|
||||
}
|
||||
data->ctrl_reg1 &= ~MMA8452_CTRL_DR_MASK;
|
||||
data->ctrl_reg1 |= i << MMA8452_CTRL_DR_SHIFT;
|
||||
|
||||
return mma8452_change_config(data, MMA8452_CTRL_REG1,
|
||||
ret = mma8452_change_config(data, MMA8452_CTRL_REG1,
|
||||
data->ctrl_reg1);
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
i = mma8452_get_scale_index(data, val, val2);
|
||||
if (i < 0)
|
||||
return i;
|
||||
if (i < 0) {
|
||||
ret = i;
|
||||
break;
|
||||
}
|
||||
|
||||
data->data_cfg &= ~MMA8452_DATA_CFG_FS_MASK;
|
||||
data->data_cfg |= i;
|
||||
|
||||
return mma8452_change_config(data, MMA8452_DATA_CFG,
|
||||
ret = mma8452_change_config(data, MMA8452_DATA_CFG,
|
||||
data->data_cfg);
|
||||
break;
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
if (val < -128 || val > 127)
|
||||
return -EINVAL;
|
||||
if (val < -128 || val > 127) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return mma8452_change_config(data,
|
||||
ret = mma8452_change_config(data,
|
||||
MMA8452_OFF_X + chan->scan_index,
|
||||
val);
|
||||
break;
|
||||
|
||||
case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
|
||||
if (val == 0 && val2 == 0) {
|
||||
|
@ -703,23 +714,30 @@ static int mma8452_write_raw(struct iio_dev *indio_dev,
|
|||
data->data_cfg |= MMA8452_DATA_CFG_HPF_MASK;
|
||||
ret = mma8452_set_hp_filter_frequency(data, val, val2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
}
|
||||
|
||||
return mma8452_change_config(data, MMA8452_DATA_CFG,
|
||||
ret = mma8452_change_config(data, MMA8452_DATA_CFG,
|
||||
data->data_cfg);
|
||||
break;
|
||||
|
||||
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
||||
ret = mma8452_get_odr_index(data);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mma8452_os_ratio); i++) {
|
||||
if (mma8452_os_ratio[i][ret] == val)
|
||||
return mma8452_set_power_mode(data, i);
|
||||
if (mma8452_os_ratio[i][ret] == val) {
|
||||
ret = mma8452_set_power_mode(data, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mma8452_read_thresh(struct iio_dev *indio_dev,
|
||||
|
@ -1347,20 +1365,9 @@ static int mma8452_data_rdy_trigger_set_state(struct iio_trigger *trig,
|
|||
return mma8452_change_config(data, MMA8452_CTRL_REG4, reg);
|
||||
}
|
||||
|
||||
static int mma8452_validate_device(struct iio_trigger *trig,
|
||||
struct iio_dev *indio_dev)
|
||||
{
|
||||
struct iio_dev *indio = iio_trigger_get_drvdata(trig);
|
||||
|
||||
if (indio != indio_dev)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_trigger_ops mma8452_trigger_ops = {
|
||||
.set_trigger_state = mma8452_data_rdy_trigger_set_state,
|
||||
.validate_device = mma8452_validate_device,
|
||||
.validate_device = iio_trigger_validate_own_device,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -30,6 +30,7 @@
|
|||
#define LSM303AGR_ACCEL_DEV_NAME "lsm303agr_accel"
|
||||
#define LIS2DH12_ACCEL_DEV_NAME "lis2dh12_accel"
|
||||
#define LIS3L02DQ_ACCEL_DEV_NAME "lis3l02dq"
|
||||
#define LNG2DM_ACCEL_DEV_NAME "lng2dm"
|
||||
|
||||
/**
|
||||
* struct st_sensors_platform_data - default accel platform data
|
||||
|
|
|
@ -43,194 +43,6 @@
|
|||
#define ST_ACCEL_FS_AVL_200G 200
|
||||
#define ST_ACCEL_FS_AVL_400G 400
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 1 */
|
||||
#define ST_ACCEL_1_WAI_EXP 0x33
|
||||
#define ST_ACCEL_1_ODR_ADDR 0x20
|
||||
#define ST_ACCEL_1_ODR_MASK 0xf0
|
||||
#define ST_ACCEL_1_ODR_AVL_1HZ_VAL 0x01
|
||||
#define ST_ACCEL_1_ODR_AVL_10HZ_VAL 0x02
|
||||
#define ST_ACCEL_1_ODR_AVL_25HZ_VAL 0x03
|
||||
#define ST_ACCEL_1_ODR_AVL_50HZ_VAL 0x04
|
||||
#define ST_ACCEL_1_ODR_AVL_100HZ_VAL 0x05
|
||||
#define ST_ACCEL_1_ODR_AVL_200HZ_VAL 0x06
|
||||
#define ST_ACCEL_1_ODR_AVL_400HZ_VAL 0x07
|
||||
#define ST_ACCEL_1_ODR_AVL_1600HZ_VAL 0x08
|
||||
#define ST_ACCEL_1_FS_ADDR 0x23
|
||||
#define ST_ACCEL_1_FS_MASK 0x30
|
||||
#define ST_ACCEL_1_FS_AVL_2_VAL 0x00
|
||||
#define ST_ACCEL_1_FS_AVL_4_VAL 0x01
|
||||
#define ST_ACCEL_1_FS_AVL_8_VAL 0x02
|
||||
#define ST_ACCEL_1_FS_AVL_16_VAL 0x03
|
||||
#define ST_ACCEL_1_FS_AVL_2_GAIN IIO_G_TO_M_S_2(1000)
|
||||
#define ST_ACCEL_1_FS_AVL_4_GAIN IIO_G_TO_M_S_2(2000)
|
||||
#define ST_ACCEL_1_FS_AVL_8_GAIN IIO_G_TO_M_S_2(4000)
|
||||
#define ST_ACCEL_1_FS_AVL_16_GAIN IIO_G_TO_M_S_2(12000)
|
||||
#define ST_ACCEL_1_BDU_ADDR 0x23
|
||||
#define ST_ACCEL_1_BDU_MASK 0x80
|
||||
#define ST_ACCEL_1_DRDY_IRQ_ADDR 0x22
|
||||
#define ST_ACCEL_1_DRDY_IRQ_INT1_MASK 0x10
|
||||
#define ST_ACCEL_1_DRDY_IRQ_INT2_MASK 0x08
|
||||
#define ST_ACCEL_1_IHL_IRQ_ADDR 0x25
|
||||
#define ST_ACCEL_1_IHL_IRQ_MASK 0x02
|
||||
#define ST_ACCEL_1_MULTIREAD_BIT true
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 2 */
|
||||
#define ST_ACCEL_2_WAI_EXP 0x32
|
||||
#define ST_ACCEL_2_ODR_ADDR 0x20
|
||||
#define ST_ACCEL_2_ODR_MASK 0x18
|
||||
#define ST_ACCEL_2_ODR_AVL_50HZ_VAL 0x00
|
||||
#define ST_ACCEL_2_ODR_AVL_100HZ_VAL 0x01
|
||||
#define ST_ACCEL_2_ODR_AVL_400HZ_VAL 0x02
|
||||
#define ST_ACCEL_2_ODR_AVL_1000HZ_VAL 0x03
|
||||
#define ST_ACCEL_2_PW_ADDR 0x20
|
||||
#define ST_ACCEL_2_PW_MASK 0xe0
|
||||
#define ST_ACCEL_2_FS_ADDR 0x23
|
||||
#define ST_ACCEL_2_FS_MASK 0x30
|
||||
#define ST_ACCEL_2_FS_AVL_2_VAL 0X00
|
||||
#define ST_ACCEL_2_FS_AVL_4_VAL 0X01
|
||||
#define ST_ACCEL_2_FS_AVL_8_VAL 0x03
|
||||
#define ST_ACCEL_2_FS_AVL_2_GAIN IIO_G_TO_M_S_2(1000)
|
||||
#define ST_ACCEL_2_FS_AVL_4_GAIN IIO_G_TO_M_S_2(2000)
|
||||
#define ST_ACCEL_2_FS_AVL_8_GAIN IIO_G_TO_M_S_2(3900)
|
||||
#define ST_ACCEL_2_BDU_ADDR 0x23
|
||||
#define ST_ACCEL_2_BDU_MASK 0x80
|
||||
#define ST_ACCEL_2_DRDY_IRQ_ADDR 0x22
|
||||
#define ST_ACCEL_2_DRDY_IRQ_INT1_MASK 0x02
|
||||
#define ST_ACCEL_2_DRDY_IRQ_INT2_MASK 0x10
|
||||
#define ST_ACCEL_2_IHL_IRQ_ADDR 0x22
|
||||
#define ST_ACCEL_2_IHL_IRQ_MASK 0x80
|
||||
#define ST_ACCEL_2_OD_IRQ_ADDR 0x22
|
||||
#define ST_ACCEL_2_OD_IRQ_MASK 0x40
|
||||
#define ST_ACCEL_2_MULTIREAD_BIT true
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 3 */
|
||||
#define ST_ACCEL_3_WAI_EXP 0x40
|
||||
#define ST_ACCEL_3_ODR_ADDR 0x20
|
||||
#define ST_ACCEL_3_ODR_MASK 0xf0
|
||||
#define ST_ACCEL_3_ODR_AVL_3HZ_VAL 0x01
|
||||
#define ST_ACCEL_3_ODR_AVL_6HZ_VAL 0x02
|
||||
#define ST_ACCEL_3_ODR_AVL_12HZ_VAL 0x03
|
||||
#define ST_ACCEL_3_ODR_AVL_25HZ_VAL 0x04
|
||||
#define ST_ACCEL_3_ODR_AVL_50HZ_VAL 0x05
|
||||
#define ST_ACCEL_3_ODR_AVL_100HZ_VAL 0x06
|
||||
#define ST_ACCEL_3_ODR_AVL_200HZ_VAL 0x07
|
||||
#define ST_ACCEL_3_ODR_AVL_400HZ_VAL 0x08
|
||||
#define ST_ACCEL_3_ODR_AVL_800HZ_VAL 0x09
|
||||
#define ST_ACCEL_3_ODR_AVL_1600HZ_VAL 0x0a
|
||||
#define ST_ACCEL_3_FS_ADDR 0x24
|
||||
#define ST_ACCEL_3_FS_MASK 0x38
|
||||
#define ST_ACCEL_3_FS_AVL_2_VAL 0X00
|
||||
#define ST_ACCEL_3_FS_AVL_4_VAL 0X01
|
||||
#define ST_ACCEL_3_FS_AVL_6_VAL 0x02
|
||||
#define ST_ACCEL_3_FS_AVL_8_VAL 0x03
|
||||
#define ST_ACCEL_3_FS_AVL_16_VAL 0x04
|
||||
#define ST_ACCEL_3_FS_AVL_2_GAIN IIO_G_TO_M_S_2(61)
|
||||
#define ST_ACCEL_3_FS_AVL_4_GAIN IIO_G_TO_M_S_2(122)
|
||||
#define ST_ACCEL_3_FS_AVL_6_GAIN IIO_G_TO_M_S_2(183)
|
||||
#define ST_ACCEL_3_FS_AVL_8_GAIN IIO_G_TO_M_S_2(244)
|
||||
#define ST_ACCEL_3_FS_AVL_16_GAIN IIO_G_TO_M_S_2(732)
|
||||
#define ST_ACCEL_3_BDU_ADDR 0x20
|
||||
#define ST_ACCEL_3_BDU_MASK 0x08
|
||||
#define ST_ACCEL_3_DRDY_IRQ_ADDR 0x23
|
||||
#define ST_ACCEL_3_DRDY_IRQ_INT1_MASK 0x80
|
||||
#define ST_ACCEL_3_DRDY_IRQ_INT2_MASK 0x00
|
||||
#define ST_ACCEL_3_IHL_IRQ_ADDR 0x23
|
||||
#define ST_ACCEL_3_IHL_IRQ_MASK 0x40
|
||||
#define ST_ACCEL_3_IG1_EN_ADDR 0x23
|
||||
#define ST_ACCEL_3_IG1_EN_MASK 0x08
|
||||
#define ST_ACCEL_3_MULTIREAD_BIT false
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 4 */
|
||||
#define ST_ACCEL_4_WAI_EXP 0x3a
|
||||
#define ST_ACCEL_4_ODR_ADDR 0x20
|
||||
#define ST_ACCEL_4_ODR_MASK 0x30 /* DF1 and DF0 */
|
||||
#define ST_ACCEL_4_ODR_AVL_40HZ_VAL 0x00
|
||||
#define ST_ACCEL_4_ODR_AVL_160HZ_VAL 0x01
|
||||
#define ST_ACCEL_4_ODR_AVL_640HZ_VAL 0x02
|
||||
#define ST_ACCEL_4_ODR_AVL_2560HZ_VAL 0x03
|
||||
#define ST_ACCEL_4_PW_ADDR 0x20
|
||||
#define ST_ACCEL_4_PW_MASK 0xc0
|
||||
#define ST_ACCEL_4_FS_ADDR 0x21
|
||||
#define ST_ACCEL_4_FS_MASK 0x80
|
||||
#define ST_ACCEL_4_FS_AVL_2_VAL 0X00
|
||||
#define ST_ACCEL_4_FS_AVL_6_VAL 0X01
|
||||
#define ST_ACCEL_4_FS_AVL_2_GAIN IIO_G_TO_M_S_2(1024)
|
||||
#define ST_ACCEL_4_FS_AVL_6_GAIN IIO_G_TO_M_S_2(340)
|
||||
#define ST_ACCEL_4_BDU_ADDR 0x21
|
||||
#define ST_ACCEL_4_BDU_MASK 0x40
|
||||
#define ST_ACCEL_4_DRDY_IRQ_ADDR 0x21
|
||||
#define ST_ACCEL_4_DRDY_IRQ_INT1_MASK 0x04
|
||||
#define ST_ACCEL_4_MULTIREAD_BIT true
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 5 */
|
||||
#define ST_ACCEL_5_WAI_EXP 0x3b
|
||||
#define ST_ACCEL_5_ODR_ADDR 0x20
|
||||
#define ST_ACCEL_5_ODR_MASK 0x80
|
||||
#define ST_ACCEL_5_ODR_AVL_100HZ_VAL 0x00
|
||||
#define ST_ACCEL_5_ODR_AVL_400HZ_VAL 0x01
|
||||
#define ST_ACCEL_5_PW_ADDR 0x20
|
||||
#define ST_ACCEL_5_PW_MASK 0x40
|
||||
#define ST_ACCEL_5_FS_ADDR 0x20
|
||||
#define ST_ACCEL_5_FS_MASK 0x20
|
||||
#define ST_ACCEL_5_FS_AVL_2_VAL 0X00
|
||||
#define ST_ACCEL_5_FS_AVL_8_VAL 0X01
|
||||
/* TODO: check these resulting gain settings, these are not in the datsheet */
|
||||
#define ST_ACCEL_5_FS_AVL_2_GAIN IIO_G_TO_M_S_2(18000)
|
||||
#define ST_ACCEL_5_FS_AVL_8_GAIN IIO_G_TO_M_S_2(72000)
|
||||
#define ST_ACCEL_5_DRDY_IRQ_ADDR 0x22
|
||||
#define ST_ACCEL_5_DRDY_IRQ_INT1_MASK 0x04
|
||||
#define ST_ACCEL_5_DRDY_IRQ_INT2_MASK 0x20
|
||||
#define ST_ACCEL_5_IHL_IRQ_ADDR 0x22
|
||||
#define ST_ACCEL_5_IHL_IRQ_MASK 0x80
|
||||
#define ST_ACCEL_5_OD_IRQ_ADDR 0x22
|
||||
#define ST_ACCEL_5_OD_IRQ_MASK 0x40
|
||||
#define ST_ACCEL_5_IG1_EN_ADDR 0x21
|
||||
#define ST_ACCEL_5_IG1_EN_MASK 0x08
|
||||
#define ST_ACCEL_5_MULTIREAD_BIT false
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 6 */
|
||||
#define ST_ACCEL_6_WAI_EXP 0x32
|
||||
#define ST_ACCEL_6_ODR_ADDR 0x20
|
||||
#define ST_ACCEL_6_ODR_MASK 0x18
|
||||
#define ST_ACCEL_6_ODR_AVL_50HZ_VAL 0x00
|
||||
#define ST_ACCEL_6_ODR_AVL_100HZ_VAL 0x01
|
||||
#define ST_ACCEL_6_ODR_AVL_400HZ_VAL 0x02
|
||||
#define ST_ACCEL_6_ODR_AVL_1000HZ_VAL 0x03
|
||||
#define ST_ACCEL_6_PW_ADDR 0x20
|
||||
#define ST_ACCEL_6_PW_MASK 0x20
|
||||
#define ST_ACCEL_6_FS_ADDR 0x23
|
||||
#define ST_ACCEL_6_FS_MASK 0x30
|
||||
#define ST_ACCEL_6_FS_AVL_100_VAL 0x00
|
||||
#define ST_ACCEL_6_FS_AVL_200_VAL 0x01
|
||||
#define ST_ACCEL_6_FS_AVL_400_VAL 0x03
|
||||
#define ST_ACCEL_6_FS_AVL_100_GAIN IIO_G_TO_M_S_2(49000)
|
||||
#define ST_ACCEL_6_FS_AVL_200_GAIN IIO_G_TO_M_S_2(98000)
|
||||
#define ST_ACCEL_6_FS_AVL_400_GAIN IIO_G_TO_M_S_2(195000)
|
||||
#define ST_ACCEL_6_BDU_ADDR 0x23
|
||||
#define ST_ACCEL_6_BDU_MASK 0x80
|
||||
#define ST_ACCEL_6_DRDY_IRQ_ADDR 0x22
|
||||
#define ST_ACCEL_6_DRDY_IRQ_INT1_MASK 0x02
|
||||
#define ST_ACCEL_6_DRDY_IRQ_INT2_MASK 0x10
|
||||
#define ST_ACCEL_6_IHL_IRQ_ADDR 0x22
|
||||
#define ST_ACCEL_6_IHL_IRQ_MASK 0x80
|
||||
#define ST_ACCEL_6_MULTIREAD_BIT true
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 7 */
|
||||
#define ST_ACCEL_7_ODR_ADDR 0x20
|
||||
#define ST_ACCEL_7_ODR_MASK 0x30
|
||||
#define ST_ACCEL_7_ODR_AVL_280HZ_VAL 0x00
|
||||
#define ST_ACCEL_7_ODR_AVL_560HZ_VAL 0x01
|
||||
#define ST_ACCEL_7_ODR_AVL_1120HZ_VAL 0x02
|
||||
#define ST_ACCEL_7_ODR_AVL_4480HZ_VAL 0x03
|
||||
#define ST_ACCEL_7_PW_ADDR 0x20
|
||||
#define ST_ACCEL_7_PW_MASK 0xc0
|
||||
#define ST_ACCEL_7_FS_AVL_2_GAIN IIO_G_TO_M_S_2(488)
|
||||
#define ST_ACCEL_7_BDU_ADDR 0x21
|
||||
#define ST_ACCEL_7_BDU_MASK 0x40
|
||||
#define ST_ACCEL_7_DRDY_IRQ_ADDR 0x21
|
||||
#define ST_ACCEL_7_DRDY_IRQ_INT1_MASK 0x04
|
||||
#define ST_ACCEL_7_MULTIREAD_BIT false
|
||||
|
||||
static const struct iio_chan_spec st_accel_8bit_channels[] = {
|
||||
ST_SENSORS_LSM_CHANNELS(IIO_ACCEL,
|
||||
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
|
||||
|
@ -281,7 +93,7 @@ static const struct iio_chan_spec st_accel_16bit_channels[] = {
|
|||
|
||||
static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
||||
{
|
||||
.wai = ST_ACCEL_1_WAI_EXP,
|
||||
.wai = 0x33,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = LIS3DH_ACCEL_DEV_NAME,
|
||||
|
@ -294,22 +106,22 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
},
|
||||
.ch = (struct iio_chan_spec *)st_accel_12bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_ACCEL_1_ODR_ADDR,
|
||||
.mask = ST_ACCEL_1_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0xf0,
|
||||
.odr_avl = {
|
||||
{ 1, ST_ACCEL_1_ODR_AVL_1HZ_VAL, },
|
||||
{ 10, ST_ACCEL_1_ODR_AVL_10HZ_VAL, },
|
||||
{ 25, ST_ACCEL_1_ODR_AVL_25HZ_VAL, },
|
||||
{ 50, ST_ACCEL_1_ODR_AVL_50HZ_VAL, },
|
||||
{ 100, ST_ACCEL_1_ODR_AVL_100HZ_VAL, },
|
||||
{ 200, ST_ACCEL_1_ODR_AVL_200HZ_VAL, },
|
||||
{ 400, ST_ACCEL_1_ODR_AVL_400HZ_VAL, },
|
||||
{ 1600, ST_ACCEL_1_ODR_AVL_1600HZ_VAL, },
|
||||
{ .hz = 1, .value = 0x01, },
|
||||
{ .hz = 10, .value = 0x02, },
|
||||
{ .hz = 25, .value = 0x03, },
|
||||
{ .hz = 50, .value = 0x04, },
|
||||
{ .hz = 100, .value = 0x05, },
|
||||
{ .hz = 200, .value = 0x06, },
|
||||
{ .hz = 400, .value = 0x07, },
|
||||
{ .hz = 1600, .value = 0x08, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_ACCEL_1_ODR_ADDR,
|
||||
.mask = ST_ACCEL_1_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0xf0,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
.enable_axis = {
|
||||
|
@ -317,48 +129,48 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
.mask = ST_SENSORS_DEFAULT_AXIS_MASK,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_ACCEL_1_FS_ADDR,
|
||||
.mask = ST_ACCEL_1_FS_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x30,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_ACCEL_FS_AVL_2G,
|
||||
.value = ST_ACCEL_1_FS_AVL_2_VAL,
|
||||
.gain = ST_ACCEL_1_FS_AVL_2_GAIN,
|
||||
.value = 0x00,
|
||||
.gain = IIO_G_TO_M_S_2(1000),
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_ACCEL_FS_AVL_4G,
|
||||
.value = ST_ACCEL_1_FS_AVL_4_VAL,
|
||||
.gain = ST_ACCEL_1_FS_AVL_4_GAIN,
|
||||
.value = 0x01,
|
||||
.gain = IIO_G_TO_M_S_2(2000),
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_ACCEL_FS_AVL_8G,
|
||||
.value = ST_ACCEL_1_FS_AVL_8_VAL,
|
||||
.gain = ST_ACCEL_1_FS_AVL_8_GAIN,
|
||||
.value = 0x02,
|
||||
.gain = IIO_G_TO_M_S_2(4000),
|
||||
},
|
||||
[3] = {
|
||||
.num = ST_ACCEL_FS_AVL_16G,
|
||||
.value = ST_ACCEL_1_FS_AVL_16_VAL,
|
||||
.gain = ST_ACCEL_1_FS_AVL_16_GAIN,
|
||||
.value = 0x03,
|
||||
.gain = IIO_G_TO_M_S_2(12000),
|
||||
},
|
||||
},
|
||||
},
|
||||
.bdu = {
|
||||
.addr = ST_ACCEL_1_BDU_ADDR,
|
||||
.mask = ST_ACCEL_1_BDU_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x80,
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_ACCEL_1_DRDY_IRQ_ADDR,
|
||||
.mask_int1 = ST_ACCEL_1_DRDY_IRQ_INT1_MASK,
|
||||
.mask_int2 = ST_ACCEL_1_DRDY_IRQ_INT2_MASK,
|
||||
.addr_ihl = ST_ACCEL_1_IHL_IRQ_ADDR,
|
||||
.mask_ihl = ST_ACCEL_1_IHL_IRQ_MASK,
|
||||
.addr = 0x22,
|
||||
.mask_int1 = 0x10,
|
||||
.mask_int2 = 0x08,
|
||||
.addr_ihl = 0x25,
|
||||
.mask_ihl = 0x02,
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
},
|
||||
.multi_read_bit = ST_ACCEL_1_MULTIREAD_BIT,
|
||||
.multi_read_bit = true,
|
||||
.bootime = 2,
|
||||
},
|
||||
{
|
||||
.wai = ST_ACCEL_2_WAI_EXP,
|
||||
.wai = 0x32,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = LIS331DLH_ACCEL_DEV_NAME,
|
||||
|
@ -368,18 +180,18 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
},
|
||||
.ch = (struct iio_chan_spec *)st_accel_12bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_ACCEL_2_ODR_ADDR,
|
||||
.mask = ST_ACCEL_2_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x18,
|
||||
.odr_avl = {
|
||||
{ 50, ST_ACCEL_2_ODR_AVL_50HZ_VAL, },
|
||||
{ 100, ST_ACCEL_2_ODR_AVL_100HZ_VAL, },
|
||||
{ 400, ST_ACCEL_2_ODR_AVL_400HZ_VAL, },
|
||||
{ 1000, ST_ACCEL_2_ODR_AVL_1000HZ_VAL, },
|
||||
{ .hz = 50, .value = 0x00, },
|
||||
{ .hz = 100, .value = 0x01, },
|
||||
{ .hz = 400, .value = 0x02, },
|
||||
{ .hz = 1000, .value = 0x03, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_ACCEL_2_PW_ADDR,
|
||||
.mask = ST_ACCEL_2_PW_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0xe0,
|
||||
.value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
|
@ -388,69 +200,69 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
.mask = ST_SENSORS_DEFAULT_AXIS_MASK,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_ACCEL_2_FS_ADDR,
|
||||
.mask = ST_ACCEL_2_FS_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x30,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_ACCEL_FS_AVL_2G,
|
||||
.value = ST_ACCEL_2_FS_AVL_2_VAL,
|
||||
.gain = ST_ACCEL_2_FS_AVL_2_GAIN,
|
||||
.value = 0x00,
|
||||
.gain = IIO_G_TO_M_S_2(1000),
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_ACCEL_FS_AVL_4G,
|
||||
.value = ST_ACCEL_2_FS_AVL_4_VAL,
|
||||
.gain = ST_ACCEL_2_FS_AVL_4_GAIN,
|
||||
.value = 0x01,
|
||||
.gain = IIO_G_TO_M_S_2(2000),
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_ACCEL_FS_AVL_8G,
|
||||
.value = ST_ACCEL_2_FS_AVL_8_VAL,
|
||||
.gain = ST_ACCEL_2_FS_AVL_8_GAIN,
|
||||
.value = 0x03,
|
||||
.gain = IIO_G_TO_M_S_2(3900),
|
||||
},
|
||||
},
|
||||
},
|
||||
.bdu = {
|
||||
.addr = ST_ACCEL_2_BDU_ADDR,
|
||||
.mask = ST_ACCEL_2_BDU_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x80,
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_ACCEL_2_DRDY_IRQ_ADDR,
|
||||
.mask_int1 = ST_ACCEL_2_DRDY_IRQ_INT1_MASK,
|
||||
.mask_int2 = ST_ACCEL_2_DRDY_IRQ_INT2_MASK,
|
||||
.addr_ihl = ST_ACCEL_2_IHL_IRQ_ADDR,
|
||||
.mask_ihl = ST_ACCEL_2_IHL_IRQ_MASK,
|
||||
.addr_od = ST_ACCEL_2_OD_IRQ_ADDR,
|
||||
.mask_od = ST_ACCEL_2_OD_IRQ_MASK,
|
||||
.addr = 0x22,
|
||||
.mask_int1 = 0x02,
|
||||
.mask_int2 = 0x10,
|
||||
.addr_ihl = 0x22,
|
||||
.mask_ihl = 0x80,
|
||||
.addr_od = 0x22,
|
||||
.mask_od = 0x40,
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
},
|
||||
.multi_read_bit = ST_ACCEL_2_MULTIREAD_BIT,
|
||||
.multi_read_bit = true,
|
||||
.bootime = 2,
|
||||
},
|
||||
{
|
||||
.wai = ST_ACCEL_3_WAI_EXP,
|
||||
.wai = 0x40,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = LSM330_ACCEL_DEV_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_accel_16bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_ACCEL_3_ODR_ADDR,
|
||||
.mask = ST_ACCEL_3_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0xf0,
|
||||
.odr_avl = {
|
||||
{ 3, ST_ACCEL_3_ODR_AVL_3HZ_VAL },
|
||||
{ 6, ST_ACCEL_3_ODR_AVL_6HZ_VAL, },
|
||||
{ 12, ST_ACCEL_3_ODR_AVL_12HZ_VAL, },
|
||||
{ 25, ST_ACCEL_3_ODR_AVL_25HZ_VAL, },
|
||||
{ 50, ST_ACCEL_3_ODR_AVL_50HZ_VAL, },
|
||||
{ 100, ST_ACCEL_3_ODR_AVL_100HZ_VAL, },
|
||||
{ 200, ST_ACCEL_3_ODR_AVL_200HZ_VAL, },
|
||||
{ 400, ST_ACCEL_3_ODR_AVL_400HZ_VAL, },
|
||||
{ 800, ST_ACCEL_3_ODR_AVL_800HZ_VAL, },
|
||||
{ 1600, ST_ACCEL_3_ODR_AVL_1600HZ_VAL, },
|
||||
{ .hz = 3, .value = 0x01, },
|
||||
{ .hz = 6, .value = 0x02, },
|
||||
{ .hz = 12, .value = 0x03, },
|
||||
{ .hz = 25, .value = 0x04, },
|
||||
{ .hz = 50, .value = 0x05, },
|
||||
{ .hz = 100, .value = 0x06, },
|
||||
{ .hz = 200, .value = 0x07, },
|
||||
{ .hz = 400, .value = 0x08, },
|
||||
{ .hz = 800, .value = 0x09, },
|
||||
{ .hz = 1600, .value = 0x0a, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_ACCEL_3_ODR_ADDR,
|
||||
.mask = ST_ACCEL_3_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0xf0,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
.enable_axis = {
|
||||
|
@ -458,75 +270,75 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
.mask = ST_SENSORS_DEFAULT_AXIS_MASK,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_ACCEL_3_FS_ADDR,
|
||||
.mask = ST_ACCEL_3_FS_MASK,
|
||||
.addr = 0x24,
|
||||
.mask = 0x38,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_ACCEL_FS_AVL_2G,
|
||||
.value = ST_ACCEL_3_FS_AVL_2_VAL,
|
||||
.gain = ST_ACCEL_3_FS_AVL_2_GAIN,
|
||||
.value = 0x00,
|
||||
.gain = IIO_G_TO_M_S_2(61),
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_ACCEL_FS_AVL_4G,
|
||||
.value = ST_ACCEL_3_FS_AVL_4_VAL,
|
||||
.gain = ST_ACCEL_3_FS_AVL_4_GAIN,
|
||||
.value = 0x01,
|
||||
.gain = IIO_G_TO_M_S_2(122),
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_ACCEL_FS_AVL_6G,
|
||||
.value = ST_ACCEL_3_FS_AVL_6_VAL,
|
||||
.gain = ST_ACCEL_3_FS_AVL_6_GAIN,
|
||||
.value = 0x02,
|
||||
.gain = IIO_G_TO_M_S_2(183),
|
||||
},
|
||||
[3] = {
|
||||
.num = ST_ACCEL_FS_AVL_8G,
|
||||
.value = ST_ACCEL_3_FS_AVL_8_VAL,
|
||||
.gain = ST_ACCEL_3_FS_AVL_8_GAIN,
|
||||
.value = 0x03,
|
||||
.gain = IIO_G_TO_M_S_2(244),
|
||||
},
|
||||
[4] = {
|
||||
.num = ST_ACCEL_FS_AVL_16G,
|
||||
.value = ST_ACCEL_3_FS_AVL_16_VAL,
|
||||
.gain = ST_ACCEL_3_FS_AVL_16_GAIN,
|
||||
.value = 0x04,
|
||||
.gain = IIO_G_TO_M_S_2(732),
|
||||
},
|
||||
},
|
||||
},
|
||||
.bdu = {
|
||||
.addr = ST_ACCEL_3_BDU_ADDR,
|
||||
.mask = ST_ACCEL_3_BDU_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x08,
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_ACCEL_3_DRDY_IRQ_ADDR,
|
||||
.mask_int1 = ST_ACCEL_3_DRDY_IRQ_INT1_MASK,
|
||||
.mask_int2 = ST_ACCEL_3_DRDY_IRQ_INT2_MASK,
|
||||
.addr_ihl = ST_ACCEL_3_IHL_IRQ_ADDR,
|
||||
.mask_ihl = ST_ACCEL_3_IHL_IRQ_MASK,
|
||||
.addr = 0x23,
|
||||
.mask_int1 = 0x80,
|
||||
.mask_int2 = 0x00,
|
||||
.addr_ihl = 0x23,
|
||||
.mask_ihl = 0x40,
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
.ig1 = {
|
||||
.en_addr = ST_ACCEL_3_IG1_EN_ADDR,
|
||||
.en_mask = ST_ACCEL_3_IG1_EN_MASK,
|
||||
.en_addr = 0x23,
|
||||
.en_mask = 0x08,
|
||||
},
|
||||
},
|
||||
.multi_read_bit = ST_ACCEL_3_MULTIREAD_BIT,
|
||||
.multi_read_bit = false,
|
||||
.bootime = 2,
|
||||
},
|
||||
{
|
||||
.wai = ST_ACCEL_4_WAI_EXP,
|
||||
.wai = 0x3a,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = LIS3LV02DL_ACCEL_DEV_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_accel_12bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_ACCEL_4_ODR_ADDR,
|
||||
.mask = ST_ACCEL_4_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x30, /* DF1 and DF0 */
|
||||
.odr_avl = {
|
||||
{ 40, ST_ACCEL_4_ODR_AVL_40HZ_VAL },
|
||||
{ 160, ST_ACCEL_4_ODR_AVL_160HZ_VAL, },
|
||||
{ 640, ST_ACCEL_4_ODR_AVL_640HZ_VAL, },
|
||||
{ 2560, ST_ACCEL_4_ODR_AVL_2560HZ_VAL, },
|
||||
{ .hz = 40, .value = 0x00, },
|
||||
{ .hz = 160, .value = 0x01, },
|
||||
{ .hz = 640, .value = 0x02, },
|
||||
{ .hz = 2560, .value = 0x03, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_ACCEL_4_PW_ADDR,
|
||||
.mask = ST_ACCEL_4_PW_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0xc0,
|
||||
.value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
|
@ -535,51 +347,51 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
.mask = ST_SENSORS_DEFAULT_AXIS_MASK,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_ACCEL_4_FS_ADDR,
|
||||
.mask = ST_ACCEL_4_FS_MASK,
|
||||
.addr = 0x21,
|
||||
.mask = 0x80,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_ACCEL_FS_AVL_2G,
|
||||
.value = ST_ACCEL_4_FS_AVL_2_VAL,
|
||||
.gain = ST_ACCEL_4_FS_AVL_2_GAIN,
|
||||
.value = 0x00,
|
||||
.gain = IIO_G_TO_M_S_2(1024),
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_ACCEL_FS_AVL_6G,
|
||||
.value = ST_ACCEL_4_FS_AVL_6_VAL,
|
||||
.gain = ST_ACCEL_4_FS_AVL_6_GAIN,
|
||||
.value = 0x01,
|
||||
.gain = IIO_G_TO_M_S_2(340),
|
||||
},
|
||||
},
|
||||
},
|
||||
.bdu = {
|
||||
.addr = ST_ACCEL_4_BDU_ADDR,
|
||||
.mask = ST_ACCEL_4_BDU_MASK,
|
||||
.addr = 0x21,
|
||||
.mask = 0x40,
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_ACCEL_4_DRDY_IRQ_ADDR,
|
||||
.mask_int1 = ST_ACCEL_4_DRDY_IRQ_INT1_MASK,
|
||||
.addr = 0x21,
|
||||
.mask_int1 = 0x04,
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
},
|
||||
.multi_read_bit = ST_ACCEL_4_MULTIREAD_BIT,
|
||||
.multi_read_bit = true,
|
||||
.bootime = 2, /* guess */
|
||||
},
|
||||
{
|
||||
.wai = ST_ACCEL_5_WAI_EXP,
|
||||
.wai = 0x3b,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = LIS331DL_ACCEL_DEV_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_accel_8bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_ACCEL_5_ODR_ADDR,
|
||||
.mask = ST_ACCEL_5_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x80,
|
||||
.odr_avl = {
|
||||
{ 100, ST_ACCEL_5_ODR_AVL_100HZ_VAL },
|
||||
{ 400, ST_ACCEL_5_ODR_AVL_400HZ_VAL, },
|
||||
{ .hz = 100, .value = 0x00, },
|
||||
{ .hz = 400, .value = 0x01, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_ACCEL_5_PW_ADDR,
|
||||
.mask = ST_ACCEL_5_PW_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x40,
|
||||
.value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
|
@ -588,54 +400,58 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
.mask = ST_SENSORS_DEFAULT_AXIS_MASK,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_ACCEL_5_FS_ADDR,
|
||||
.mask = ST_ACCEL_5_FS_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x20,
|
||||
/*
|
||||
* TODO: check these resulting gain settings, these are
|
||||
* not in the datsheet
|
||||
*/
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_ACCEL_FS_AVL_2G,
|
||||
.value = ST_ACCEL_5_FS_AVL_2_VAL,
|
||||
.gain = ST_ACCEL_5_FS_AVL_2_GAIN,
|
||||
.value = 0x00,
|
||||
.gain = IIO_G_TO_M_S_2(18000),
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_ACCEL_FS_AVL_8G,
|
||||
.value = ST_ACCEL_5_FS_AVL_8_VAL,
|
||||
.gain = ST_ACCEL_5_FS_AVL_8_GAIN,
|
||||
.value = 0x01,
|
||||
.gain = IIO_G_TO_M_S_2(72000),
|
||||
},
|
||||
},
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_ACCEL_5_DRDY_IRQ_ADDR,
|
||||
.mask_int1 = ST_ACCEL_5_DRDY_IRQ_INT1_MASK,
|
||||
.mask_int2 = ST_ACCEL_5_DRDY_IRQ_INT2_MASK,
|
||||
.addr_ihl = ST_ACCEL_5_IHL_IRQ_ADDR,
|
||||
.mask_ihl = ST_ACCEL_5_IHL_IRQ_MASK,
|
||||
.addr_od = ST_ACCEL_5_OD_IRQ_ADDR,
|
||||
.mask_od = ST_ACCEL_5_OD_IRQ_MASK,
|
||||
.addr = 0x22,
|
||||
.mask_int1 = 0x04,
|
||||
.mask_int2 = 0x20,
|
||||
.addr_ihl = 0x22,
|
||||
.mask_ihl = 0x80,
|
||||
.addr_od = 0x22,
|
||||
.mask_od = 0x40,
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
},
|
||||
.multi_read_bit = ST_ACCEL_5_MULTIREAD_BIT,
|
||||
.multi_read_bit = false,
|
||||
.bootime = 2, /* guess */
|
||||
},
|
||||
{
|
||||
.wai = ST_ACCEL_6_WAI_EXP,
|
||||
.wai = 0x32,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = H3LIS331DL_DRIVER_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_accel_12bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_ACCEL_6_ODR_ADDR,
|
||||
.mask = ST_ACCEL_6_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x18,
|
||||
.odr_avl = {
|
||||
{ 50, ST_ACCEL_6_ODR_AVL_50HZ_VAL },
|
||||
{ 100, ST_ACCEL_6_ODR_AVL_100HZ_VAL, },
|
||||
{ 400, ST_ACCEL_6_ODR_AVL_400HZ_VAL, },
|
||||
{ 1000, ST_ACCEL_6_ODR_AVL_1000HZ_VAL, },
|
||||
{ .hz = 50, .value = 0x00, },
|
||||
{ .hz = 100, .value = 0x01, },
|
||||
{ .hz = 400, .value = 0x02, },
|
||||
{ .hz = 1000, .value = 0x03, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_ACCEL_6_PW_ADDR,
|
||||
.mask = ST_ACCEL_6_PW_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x20,
|
||||
.value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
|
@ -644,38 +460,38 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
.mask = ST_SENSORS_DEFAULT_AXIS_MASK,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_ACCEL_6_FS_ADDR,
|
||||
.mask = ST_ACCEL_6_FS_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x30,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_ACCEL_FS_AVL_100G,
|
||||
.value = ST_ACCEL_6_FS_AVL_100_VAL,
|
||||
.gain = ST_ACCEL_6_FS_AVL_100_GAIN,
|
||||
.value = 0x00,
|
||||
.gain = IIO_G_TO_M_S_2(49000),
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_ACCEL_FS_AVL_200G,
|
||||
.value = ST_ACCEL_6_FS_AVL_200_VAL,
|
||||
.gain = ST_ACCEL_6_FS_AVL_200_GAIN,
|
||||
.value = 0x01,
|
||||
.gain = IIO_G_TO_M_S_2(98000),
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_ACCEL_FS_AVL_400G,
|
||||
.value = ST_ACCEL_6_FS_AVL_400_VAL,
|
||||
.gain = ST_ACCEL_6_FS_AVL_400_GAIN,
|
||||
.value = 0x03,
|
||||
.gain = IIO_G_TO_M_S_2(195000),
|
||||
},
|
||||
},
|
||||
},
|
||||
.bdu = {
|
||||
.addr = ST_ACCEL_6_BDU_ADDR,
|
||||
.mask = ST_ACCEL_6_BDU_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x80,
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_ACCEL_6_DRDY_IRQ_ADDR,
|
||||
.mask_int1 = ST_ACCEL_6_DRDY_IRQ_INT1_MASK,
|
||||
.mask_int2 = ST_ACCEL_6_DRDY_IRQ_INT2_MASK,
|
||||
.addr_ihl = ST_ACCEL_6_IHL_IRQ_ADDR,
|
||||
.mask_ihl = ST_ACCEL_6_IHL_IRQ_MASK,
|
||||
.addr = 0x22,
|
||||
.mask_int1 = 0x02,
|
||||
.mask_int2 = 0x10,
|
||||
.addr_ihl = 0x22,
|
||||
.mask_ihl = 0x80,
|
||||
},
|
||||
.multi_read_bit = ST_ACCEL_6_MULTIREAD_BIT,
|
||||
.multi_read_bit = true,
|
||||
.bootime = 2,
|
||||
},
|
||||
{
|
||||
|
@ -685,18 +501,18 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
},
|
||||
.ch = (struct iio_chan_spec *)st_accel_12bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_ACCEL_7_ODR_ADDR,
|
||||
.mask = ST_ACCEL_7_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x30,
|
||||
.odr_avl = {
|
||||
{ 280, ST_ACCEL_7_ODR_AVL_280HZ_VAL, },
|
||||
{ 560, ST_ACCEL_7_ODR_AVL_560HZ_VAL, },
|
||||
{ 1120, ST_ACCEL_7_ODR_AVL_1120HZ_VAL, },
|
||||
{ 4480, ST_ACCEL_7_ODR_AVL_4480HZ_VAL, },
|
||||
{ .hz = 280, .value = 0x00, },
|
||||
{ .hz = 560, .value = 0x01, },
|
||||
{ .hz = 1120, .value = 0x02, },
|
||||
{ .hz = 4480, .value = 0x03, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_ACCEL_7_PW_ADDR,
|
||||
.mask = ST_ACCEL_7_PW_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0xc0,
|
||||
.value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
|
@ -708,7 +524,7 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_ACCEL_FS_AVL_2G,
|
||||
.gain = ST_ACCEL_7_FS_AVL_2_GAIN,
|
||||
.gain = IIO_G_TO_M_S_2(488),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -719,11 +535,78 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
|||
.bdu = {
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_ACCEL_7_DRDY_IRQ_ADDR,
|
||||
.mask_int1 = ST_ACCEL_7_DRDY_IRQ_INT1_MASK,
|
||||
.addr = 0x21,
|
||||
.mask_int1 = 0x04,
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
},
|
||||
.multi_read_bit = ST_ACCEL_7_MULTIREAD_BIT,
|
||||
.multi_read_bit = false,
|
||||
.bootime = 2,
|
||||
},
|
||||
{
|
||||
.wai = 0x33,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = LNG2DM_ACCEL_DEV_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_accel_8bit_channels,
|
||||
.odr = {
|
||||
.addr = 0x20,
|
||||
.mask = 0xf0,
|
||||
.odr_avl = {
|
||||
{ .hz = 1, .value = 0x01, },
|
||||
{ .hz = 10, .value = 0x02, },
|
||||
{ .hz = 25, .value = 0x03, },
|
||||
{ .hz = 50, .value = 0x04, },
|
||||
{ .hz = 100, .value = 0x05, },
|
||||
{ .hz = 200, .value = 0x06, },
|
||||
{ .hz = 400, .value = 0x07, },
|
||||
{ .hz = 1600, .value = 0x08, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = 0x20,
|
||||
.mask = 0xf0,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
.enable_axis = {
|
||||
.addr = ST_SENSORS_DEFAULT_AXIS_ADDR,
|
||||
.mask = ST_SENSORS_DEFAULT_AXIS_MASK,
|
||||
},
|
||||
.fs = {
|
||||
.addr = 0x23,
|
||||
.mask = 0x30,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_ACCEL_FS_AVL_2G,
|
||||
.value = 0x00,
|
||||
.gain = IIO_G_TO_M_S_2(15600),
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_ACCEL_FS_AVL_4G,
|
||||
.value = 0x01,
|
||||
.gain = IIO_G_TO_M_S_2(31200),
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_ACCEL_FS_AVL_8G,
|
||||
.value = 0x02,
|
||||
.gain = IIO_G_TO_M_S_2(62500),
|
||||
},
|
||||
[3] = {
|
||||
.num = ST_ACCEL_FS_AVL_16G,
|
||||
.value = 0x03,
|
||||
.gain = IIO_G_TO_M_S_2(187500),
|
||||
},
|
||||
},
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = 0x22,
|
||||
.mask_int1 = 0x10,
|
||||
.mask_int2 = 0x08,
|
||||
.addr_ihl = 0x25,
|
||||
.mask_ihl = 0x02,
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
},
|
||||
.multi_read_bit = true,
|
||||
.bootime = 2,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -84,6 +84,10 @@ static const struct of_device_id st_accel_of_match[] = {
|
|||
.compatible = "st,lis3l02dq",
|
||||
.data = LIS3L02DQ_ACCEL_DEV_NAME,
|
||||
},
|
||||
{
|
||||
.compatible = "st,lng2dm-accel",
|
||||
.data = LNG2DM_ACCEL_DEV_NAME,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, st_accel_of_match);
|
||||
|
@ -135,6 +139,7 @@ static const struct i2c_device_id st_accel_id_table[] = {
|
|||
{ LSM303AGR_ACCEL_DEV_NAME },
|
||||
{ LIS2DH12_ACCEL_DEV_NAME },
|
||||
{ LIS3L02DQ_ACCEL_DEV_NAME },
|
||||
{ LNG2DM_ACCEL_DEV_NAME },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, st_accel_id_table);
|
||||
|
|
|
@ -60,6 +60,7 @@ static const struct spi_device_id st_accel_id_table[] = {
|
|||
{ LSM303AGR_ACCEL_DEV_NAME },
|
||||
{ LIS2DH12_ACCEL_DEV_NAME },
|
||||
{ LIS3L02DQ_ACCEL_DEV_NAME },
|
||||
{ LNG2DM_ACCEL_DEV_NAME },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, st_accel_id_table);
|
||||
|
|
|
@ -58,6 +58,18 @@ config AD7476
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called ad7476.
|
||||
|
||||
config AD7766
|
||||
tristate "Analog Devices AD7766/AD7767 ADC driver"
|
||||
depends on SPI_MASTER
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
Say yes here to build support for Analog Devices AD7766, AD7766-1,
|
||||
AD7766-2, AD7767, AD7767-1, AD7767-2 SPI analog to digital converters.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called ad7766.
|
||||
|
||||
config AD7791
|
||||
tristate "Analog Devices AD7791 ADC driver"
|
||||
depends on SPI
|
||||
|
@ -195,6 +207,16 @@ config DA9150_GPADC
|
|||
To compile this driver as a module, choose M here: the module will be
|
||||
called berlin2-adc.
|
||||
|
||||
config ENVELOPE_DETECTOR
|
||||
tristate "Envelope detector using a DAC and a comparator"
|
||||
depends on OF
|
||||
help
|
||||
Say yes here to build support for an envelope detector using a DAC
|
||||
and a comparator.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called envelope-detector.
|
||||
|
||||
config EXYNOS_ADC
|
||||
tristate "Exynos ADC driver support"
|
||||
depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST)
|
||||
|
@ -419,6 +441,28 @@ config ROCKCHIP_SARADC
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called rockchip_saradc.
|
||||
|
||||
config STM32_ADC_CORE
|
||||
tristate "STMicroelectronics STM32 adc core"
|
||||
depends on ARCH_STM32 || COMPILE_TEST
|
||||
depends on OF
|
||||
depends on REGULATOR
|
||||
help
|
||||
Select this option to enable the core driver for STMicroelectronics
|
||||
STM32 analog-to-digital converter (ADC).
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called stm32-adc-core.
|
||||
|
||||
config STM32_ADC
|
||||
tristate "STMicroelectronics STM32 adc"
|
||||
depends on STM32_ADC_CORE
|
||||
help
|
||||
Say yes here to build support for STMicroelectronics stm32 Analog
|
||||
to Digital Converter (ADC).
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called stm32-adc.
|
||||
|
||||
config STX104
|
||||
tristate "Apex Embedded Systems STX104 driver"
|
||||
depends on X86 && ISA_BUS_API
|
||||
|
@ -449,6 +493,8 @@ config TI_ADC081C
|
|||
config TI_ADC0832
|
||||
tristate "Texas Instruments ADC0831/ADC0832/ADC0834/ADC0838"
|
||||
depends on SPI
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
If you say yes here you get support for Texas Instruments ADC0831,
|
||||
ADC0832, ADC0834, ADC0838 ADC chips.
|
||||
|
|
|
@ -9,6 +9,7 @@ obj-$(CONFIG_AD7291) += ad7291.o
|
|||
obj-$(CONFIG_AD7298) += ad7298.o
|
||||
obj-$(CONFIG_AD7923) += ad7923.o
|
||||
obj-$(CONFIG_AD7476) += ad7476.o
|
||||
obj-$(CONFIG_AD7766) += ad7766.o
|
||||
obj-$(CONFIG_AD7791) += ad7791.o
|
||||
obj-$(CONFIG_AD7793) += ad7793.o
|
||||
obj-$(CONFIG_AD7887) += ad7887.o
|
||||
|
@ -20,6 +21,7 @@ obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o
|
|||
obj-$(CONFIG_BERLIN2_ADC) += berlin2-adc.o
|
||||
obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o
|
||||
obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o
|
||||
obj-$(CONFIG_ENVELOPE_DETECTOR) += envelope-detector.o
|
||||
obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o
|
||||
obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o
|
||||
obj-$(CONFIG_HI8435) += hi8435.o
|
||||
|
@ -41,6 +43,8 @@ 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_STX104) += stx104.o
|
||||
obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
|
||||
obj-$(CONFIG_STM32_ADC) += stm32-adc.o
|
||||
obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
|
||||
obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
|
||||
obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
|
||||
|
|
|
@ -0,0 +1,330 @@
|
|||
/*
|
||||
* AD7766/AD7767 SPI ADC driver
|
||||
*
|
||||
* Copyright 2016 Analog Devices Inc.
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
struct ad7766_chip_info {
|
||||
unsigned int decimation_factor;
|
||||
};
|
||||
|
||||
enum {
|
||||
AD7766_SUPPLY_AVDD = 0,
|
||||
AD7766_SUPPLY_DVDD = 1,
|
||||
AD7766_SUPPLY_VREF = 2,
|
||||
AD7766_NUM_SUPPLIES = 3
|
||||
};
|
||||
|
||||
struct ad7766 {
|
||||
const struct ad7766_chip_info *chip_info;
|
||||
struct spi_device *spi;
|
||||
struct clk *mclk;
|
||||
struct gpio_desc *pd_gpio;
|
||||
struct regulator_bulk_data reg[AD7766_NUM_SUPPLIES];
|
||||
|
||||
struct iio_trigger *trig;
|
||||
|
||||
struct spi_transfer xfer;
|
||||
struct spi_message msg;
|
||||
|
||||
/*
|
||||
* DMA (thus cache coherency maintenance) requires the
|
||||
* transfer buffers to live in their own cache lines.
|
||||
* Make the buffer large enough for one 24 bit sample and one 64 bit
|
||||
* aligned 64 bit timestamp.
|
||||
*/
|
||||
unsigned char data[ALIGN(3, sizeof(s64)) + sizeof(s64)]
|
||||
____cacheline_aligned;
|
||||
};
|
||||
|
||||
/*
|
||||
* AD7766 and AD7767 variations are interface compatible, the main difference is
|
||||
* analog performance. Both parts will use the same ID.
|
||||
*/
|
||||
enum ad7766_device_ids {
|
||||
ID_AD7766,
|
||||
ID_AD7766_1,
|
||||
ID_AD7766_2,
|
||||
};
|
||||
|
||||
static irqreturn_t ad7766_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct ad7766 *ad7766 = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = spi_sync(ad7766->spi, &ad7766->msg);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, ad7766->data,
|
||||
pf->timestamp);
|
||||
done:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ad7766_preenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct ad7766 *ad7766 = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(ad7766->reg), ad7766->reg);
|
||||
if (ret < 0) {
|
||||
dev_err(&ad7766->spi->dev, "Failed to enable supplies: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(ad7766->mclk);
|
||||
if (ret < 0) {
|
||||
dev_err(&ad7766->spi->dev, "Failed to enable MCLK: %d\n", ret);
|
||||
regulator_bulk_disable(ARRAY_SIZE(ad7766->reg), ad7766->reg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ad7766->pd_gpio)
|
||||
gpiod_set_value(ad7766->pd_gpio, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad7766_postdisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct ad7766 *ad7766 = iio_priv(indio_dev);
|
||||
|
||||
if (ad7766->pd_gpio)
|
||||
gpiod_set_value(ad7766->pd_gpio, 1);
|
||||
|
||||
/*
|
||||
* The PD pin is synchronous to the clock, so give it some time to
|
||||
* notice the change before we disable the clock.
|
||||
*/
|
||||
msleep(20);
|
||||
|
||||
clk_disable_unprepare(ad7766->mclk);
|
||||
regulator_bulk_disable(ARRAY_SIZE(ad7766->reg), ad7766->reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad7766_read_raw(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, int *val, int *val2, long info)
|
||||
{
|
||||
struct ad7766 *ad7766 = iio_priv(indio_dev);
|
||||
struct regulator *vref = ad7766->reg[AD7766_SUPPLY_VREF].consumer;
|
||||
int scale_uv;
|
||||
|
||||
switch (info) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
scale_uv = regulator_get_voltage(vref);
|
||||
if (scale_uv < 0)
|
||||
return scale_uv;
|
||||
*val = scale_uv / 1000;
|
||||
*val2 = chan->scan_type.realbits;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*val = clk_get_rate(ad7766->mclk) /
|
||||
ad7766->chip_info->decimation_factor;
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec ad7766_channels[] = {
|
||||
{
|
||||
.type = IIO_VOLTAGE,
|
||||
.indexed = 1,
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
|
||||
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
|
||||
.scan_type = {
|
||||
.sign = 's',
|
||||
.realbits = 24,
|
||||
.storagebits = 32,
|
||||
.endianness = IIO_BE,
|
||||
},
|
||||
},
|
||||
IIO_CHAN_SOFT_TIMESTAMP(1),
|
||||
};
|
||||
|
||||
static const struct ad7766_chip_info ad7766_chip_info[] = {
|
||||
[ID_AD7766] = {
|
||||
.decimation_factor = 8,
|
||||
},
|
||||
[ID_AD7766_1] = {
|
||||
.decimation_factor = 16,
|
||||
},
|
||||
[ID_AD7766_2] = {
|
||||
.decimation_factor = 32,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct iio_buffer_setup_ops ad7766_buffer_setup_ops = {
|
||||
.preenable = &ad7766_preenable,
|
||||
.postenable = &iio_triggered_buffer_postenable,
|
||||
.predisable = &iio_triggered_buffer_predisable,
|
||||
.postdisable = &ad7766_postdisable,
|
||||
};
|
||||
|
||||
static const struct iio_info ad7766_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &ad7766_read_raw,
|
||||
};
|
||||
|
||||
static irqreturn_t ad7766_irq(int irq, void *private)
|
||||
{
|
||||
iio_trigger_poll(private);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ad7766_set_trigger_state(struct iio_trigger *trig, bool enable)
|
||||
{
|
||||
struct ad7766 *ad7766 = iio_trigger_get_drvdata(trig);
|
||||
|
||||
if (enable)
|
||||
enable_irq(ad7766->spi->irq);
|
||||
else
|
||||
disable_irq(ad7766->spi->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_trigger_ops ad7766_trigger_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.set_trigger_state = ad7766_set_trigger_state,
|
||||
.validate_device = iio_trigger_validate_own_device,
|
||||
};
|
||||
|
||||
static int ad7766_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct spi_device_id *id = spi_get_device_id(spi);
|
||||
struct iio_dev *indio_dev;
|
||||
struct ad7766 *ad7766;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*ad7766));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
ad7766 = iio_priv(indio_dev);
|
||||
ad7766->chip_info = &ad7766_chip_info[id->driver_data];
|
||||
|
||||
ad7766->mclk = devm_clk_get(&spi->dev, "mclk");
|
||||
if (IS_ERR(ad7766->mclk))
|
||||
return PTR_ERR(ad7766->mclk);
|
||||
|
||||
ad7766->reg[AD7766_SUPPLY_AVDD].supply = "avdd";
|
||||
ad7766->reg[AD7766_SUPPLY_DVDD].supply = "dvdd";
|
||||
ad7766->reg[AD7766_SUPPLY_VREF].supply = "vref";
|
||||
|
||||
ret = devm_regulator_bulk_get(&spi->dev, ARRAY_SIZE(ad7766->reg),
|
||||
ad7766->reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ad7766->pd_gpio = devm_gpiod_get_optional(&spi->dev, "powerdown",
|
||||
GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(ad7766->pd_gpio))
|
||||
return PTR_ERR(ad7766->pd_gpio);
|
||||
|
||||
indio_dev->dev.parent = &spi->dev;
|
||||
indio_dev->name = spi_get_device_id(spi)->name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = ad7766_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(ad7766_channels);
|
||||
indio_dev->info = &ad7766_info;
|
||||
|
||||
if (spi->irq > 0) {
|
||||
ad7766->trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d",
|
||||
indio_dev->name, indio_dev->id);
|
||||
if (!ad7766->trig)
|
||||
return -ENOMEM;
|
||||
|
||||
ad7766->trig->ops = &ad7766_trigger_ops;
|
||||
ad7766->trig->dev.parent = &spi->dev;
|
||||
iio_trigger_set_drvdata(ad7766->trig, ad7766);
|
||||
|
||||
ret = devm_request_irq(&spi->dev, spi->irq, ad7766_irq,
|
||||
IRQF_TRIGGER_FALLING, dev_name(&spi->dev),
|
||||
ad7766->trig);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The device generates interrupts as long as it is powered up.
|
||||
* Some platforms might not allow the option to power it down so
|
||||
* disable the interrupt to avoid extra load on the system
|
||||
*/
|
||||
disable_irq(spi->irq);
|
||||
|
||||
ret = devm_iio_trigger_register(&spi->dev, ad7766->trig);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
spi_set_drvdata(spi, indio_dev);
|
||||
|
||||
ad7766->spi = spi;
|
||||
|
||||
/* First byte always 0 */
|
||||
ad7766->xfer.rx_buf = &ad7766->data[1];
|
||||
ad7766->xfer.len = 3;
|
||||
|
||||
spi_message_init(&ad7766->msg);
|
||||
spi_message_add_tail(&ad7766->xfer, &ad7766->msg);
|
||||
|
||||
ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
|
||||
&iio_pollfunc_store_time, &ad7766_trigger_handler,
|
||||
&ad7766_buffer_setup_ops);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_iio_device_register(&spi->dev, indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id ad7766_id[] = {
|
||||
{"ad7766", ID_AD7766},
|
||||
{"ad7766-1", ID_AD7766_1},
|
||||
{"ad7766-2", ID_AD7766_2},
|
||||
{"ad7767", ID_AD7766},
|
||||
{"ad7767-1", ID_AD7766_1},
|
||||
{"ad7767-2", ID_AD7766_2},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ad7766_id);
|
||||
|
||||
static struct spi_driver ad7766_driver = {
|
||||
.driver = {
|
||||
.name = "ad7766",
|
||||
},
|
||||
.probe = ad7766_probe,
|
||||
.id_table = ad7766_id,
|
||||
};
|
||||
module_spi_driver(ad7766_driver);
|
||||
|
||||
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
|
||||
MODULE_DESCRIPTION("Analog Devices AD7766 and AD7767 ADCs driver support");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -30,6 +30,7 @@
|
|||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
|
||||
/* Registers */
|
||||
#define AT91_ADC_CR 0x00 /* Control Register */
|
||||
|
@ -1347,6 +1348,32 @@ static int at91_adc_remove(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int at91_adc_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *idev = platform_get_drvdata(to_platform_device(dev));
|
||||
struct at91_adc_state *st = iio_priv(idev);
|
||||
|
||||
pinctrl_pm_select_sleep_state(dev);
|
||||
clk_disable_unprepare(st->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int at91_adc_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *idev = platform_get_drvdata(to_platform_device(dev));
|
||||
struct at91_adc_state *st = iio_priv(idev);
|
||||
|
||||
clk_prepare_enable(st->clk);
|
||||
pinctrl_pm_select_default_state(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(at91_adc_pm_ops, at91_adc_suspend, at91_adc_resume);
|
||||
|
||||
static struct at91_adc_caps at91sam9260_caps = {
|
||||
.calc_startup_ticks = calc_startup_ticks_9260,
|
||||
.num_channels = 4,
|
||||
|
@ -1441,6 +1468,7 @@ static struct platform_driver at91_adc_driver = {
|
|||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.of_match_table = of_match_ptr(at91_adc_dt_ids),
|
||||
.pm = &at91_adc_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,422 @@
|
|||
/*
|
||||
* Driver for an envelope detector using a DAC and a comparator
|
||||
*
|
||||
* Copyright (C) 2016 Axentia Technologies AB
|
||||
*
|
||||
* Author: Peter Rosin <peda@axentia.se>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The DAC is used to find the peak level of an alternating voltage input
|
||||
* signal by a binary search using the output of a comparator wired to
|
||||
* an interrupt pin. Like so:
|
||||
* _
|
||||
* | \
|
||||
* input +------>-------|+ \
|
||||
* | \
|
||||
* .-------. | }---.
|
||||
* | | | / |
|
||||
* | dac|-->--|- / |
|
||||
* | | |_/ |
|
||||
* | | |
|
||||
* | | |
|
||||
* | irq|------<-------'
|
||||
* | |
|
||||
* '-------'
|
||||
*/
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/iio/consumer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
struct envelope {
|
||||
spinlock_t comp_lock; /* protects comp */
|
||||
int comp;
|
||||
|
||||
struct mutex read_lock; /* protects everything else */
|
||||
|
||||
int comp_irq;
|
||||
u32 comp_irq_trigger;
|
||||
u32 comp_irq_trigger_inv;
|
||||
|
||||
struct iio_channel *dac;
|
||||
struct delayed_work comp_timeout;
|
||||
|
||||
unsigned int comp_interval;
|
||||
bool invert;
|
||||
u32 dac_max;
|
||||
|
||||
int high;
|
||||
int level;
|
||||
int low;
|
||||
|
||||
struct completion done;
|
||||
};
|
||||
|
||||
/*
|
||||
* The envelope_detector_comp_latch function works together with the compare
|
||||
* interrupt service routine below (envelope_detector_comp_isr) as a latch
|
||||
* (one-bit memory) for if the interrupt has triggered since last calling
|
||||
* this function.
|
||||
* The ..._comp_isr function disables the interrupt so that the cpu does not
|
||||
* need to service a possible interrupt flood from the comparator when no-one
|
||||
* cares anyway, and this ..._comp_latch function reenables them again if
|
||||
* needed.
|
||||
*/
|
||||
static int envelope_detector_comp_latch(struct envelope *env)
|
||||
{
|
||||
int comp;
|
||||
|
||||
spin_lock_irq(&env->comp_lock);
|
||||
comp = env->comp;
|
||||
env->comp = 0;
|
||||
spin_unlock_irq(&env->comp_lock);
|
||||
|
||||
if (!comp)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* The irq was disabled, and is reenabled just now.
|
||||
* But there might have been a pending irq that
|
||||
* happened while the irq was disabled that fires
|
||||
* just as the irq is reenabled. That is not what
|
||||
* is desired.
|
||||
*/
|
||||
enable_irq(env->comp_irq);
|
||||
|
||||
/* So, synchronize this possibly pending irq... */
|
||||
synchronize_irq(env->comp_irq);
|
||||
|
||||
/* ...and redo the whole dance. */
|
||||
spin_lock_irq(&env->comp_lock);
|
||||
comp = env->comp;
|
||||
env->comp = 0;
|
||||
spin_unlock_irq(&env->comp_lock);
|
||||
|
||||
if (comp)
|
||||
enable_irq(env->comp_irq);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static irqreturn_t envelope_detector_comp_isr(int irq, void *ctx)
|
||||
{
|
||||
struct envelope *env = ctx;
|
||||
|
||||
spin_lock(&env->comp_lock);
|
||||
env->comp = 1;
|
||||
disable_irq_nosync(env->comp_irq);
|
||||
spin_unlock(&env->comp_lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void envelope_detector_setup_compare(struct envelope *env)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Do a binary search for the peak input level, and stop
|
||||
* when that level is "trapped" between two adjacent DAC
|
||||
* values.
|
||||
* When invert is active, use the midpoint floor so that
|
||||
* env->level ends up as env->low when the termination
|
||||
* criteria below is fulfilled, and use the midpoint
|
||||
* ceiling when invert is not active so that env->level
|
||||
* ends up as env->high in that case.
|
||||
*/
|
||||
env->level = (env->high + env->low + !env->invert) / 2;
|
||||
|
||||
if (env->high == env->low + 1) {
|
||||
complete(&env->done);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set a "safe" DAC level (if there is such a thing)... */
|
||||
ret = iio_write_channel_raw(env->dac, env->invert ? 0 : env->dac_max);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
/* ...clear the comparison result... */
|
||||
envelope_detector_comp_latch(env);
|
||||
|
||||
/* ...set the real DAC level... */
|
||||
ret = iio_write_channel_raw(env->dac, env->level);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
/* ...and wait for a bit to see if the latch catches anything. */
|
||||
schedule_delayed_work(&env->comp_timeout,
|
||||
msecs_to_jiffies(env->comp_interval));
|
||||
return;
|
||||
|
||||
err:
|
||||
env->level = ret;
|
||||
complete(&env->done);
|
||||
}
|
||||
|
||||
static void envelope_detector_timeout(struct work_struct *work)
|
||||
{
|
||||
struct envelope *env = container_of(work, struct envelope,
|
||||
comp_timeout.work);
|
||||
|
||||
/* Adjust low/high depending on the latch content... */
|
||||
if (!envelope_detector_comp_latch(env) ^ !env->invert)
|
||||
env->low = env->level;
|
||||
else
|
||||
env->high = env->level;
|
||||
|
||||
/* ...and continue the search. */
|
||||
envelope_detector_setup_compare(env);
|
||||
}
|
||||
|
||||
static int envelope_detector_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct envelope *env = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
/*
|
||||
* When invert is active, start with high=max+1 and low=0
|
||||
* since we will end up with the low value when the
|
||||
* termination criteria is fulfilled (rounding down). And
|
||||
* start with high=max and low=-1 when invert is not active
|
||||
* since we will end up with the high value in that case.
|
||||
* This ensures that the returned value in both cases are
|
||||
* in the same range as the DAC and is a value that has not
|
||||
* triggered the comparator.
|
||||
*/
|
||||
mutex_lock(&env->read_lock);
|
||||
env->high = env->dac_max + env->invert;
|
||||
env->low = -1 + env->invert;
|
||||
envelope_detector_setup_compare(env);
|
||||
wait_for_completion(&env->done);
|
||||
if (env->level < 0) {
|
||||
ret = env->level;
|
||||
goto err_unlock;
|
||||
}
|
||||
*val = env->invert ? env->dac_max - env->level : env->level;
|
||||
mutex_unlock(&env->read_lock);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
return iio_read_channel_scale(env->dac, val, val2);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
|
||||
err_unlock:
|
||||
mutex_unlock(&env->read_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t envelope_show_invert(struct iio_dev *indio_dev,
|
||||
uintptr_t private,
|
||||
struct iio_chan_spec const *ch, char *buf)
|
||||
{
|
||||
struct envelope *env = iio_priv(indio_dev);
|
||||
|
||||
return sprintf(buf, "%u\n", env->invert);
|
||||
}
|
||||
|
||||
static ssize_t envelope_store_invert(struct iio_dev *indio_dev,
|
||||
uintptr_t private,
|
||||
struct iio_chan_spec const *ch,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct envelope *env = iio_priv(indio_dev);
|
||||
unsigned long invert;
|
||||
int ret;
|
||||
u32 trigger;
|
||||
|
||||
ret = kstrtoul(buf, 0, &invert);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (invert > 1)
|
||||
return -EINVAL;
|
||||
|
||||
trigger = invert ? env->comp_irq_trigger_inv : env->comp_irq_trigger;
|
||||
|
||||
mutex_lock(&env->read_lock);
|
||||
if (invert != env->invert)
|
||||
ret = irq_set_irq_type(env->comp_irq, trigger);
|
||||
if (!ret) {
|
||||
env->invert = invert;
|
||||
ret = len;
|
||||
}
|
||||
mutex_unlock(&env->read_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t envelope_show_comp_interval(struct iio_dev *indio_dev,
|
||||
uintptr_t private,
|
||||
struct iio_chan_spec const *ch,
|
||||
char *buf)
|
||||
{
|
||||
struct envelope *env = iio_priv(indio_dev);
|
||||
|
||||
return sprintf(buf, "%u\n", env->comp_interval);
|
||||
}
|
||||
|
||||
static ssize_t envelope_store_comp_interval(struct iio_dev *indio_dev,
|
||||
uintptr_t private,
|
||||
struct iio_chan_spec const *ch,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct envelope *env = iio_priv(indio_dev);
|
||||
unsigned long interval;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buf, 0, &interval);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (interval > 1000)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&env->read_lock);
|
||||
env->comp_interval = interval;
|
||||
mutex_unlock(&env->read_lock);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec_ext_info envelope_detector_ext_info[] = {
|
||||
{ .name = "invert",
|
||||
.read = envelope_show_invert,
|
||||
.write = envelope_store_invert, },
|
||||
{ .name = "compare_interval",
|
||||
.read = envelope_show_comp_interval,
|
||||
.write = envelope_store_comp_interval, },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec envelope_detector_iio_channel = {
|
||||
.type = IIO_ALTVOLTAGE,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
|
||||
| BIT(IIO_CHAN_INFO_SCALE),
|
||||
.ext_info = envelope_detector_ext_info,
|
||||
.indexed = 1,
|
||||
};
|
||||
|
||||
static const struct iio_info envelope_detector_info = {
|
||||
.read_raw = &envelope_detector_read_raw,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int envelope_detector_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct iio_dev *indio_dev;
|
||||
struct envelope *env;
|
||||
enum iio_chan_type type;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*env));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
env = iio_priv(indio_dev);
|
||||
env->comp_interval = 50; /* some sensible default? */
|
||||
|
||||
spin_lock_init(&env->comp_lock);
|
||||
mutex_init(&env->read_lock);
|
||||
init_completion(&env->done);
|
||||
INIT_DELAYED_WORK(&env->comp_timeout, envelope_detector_timeout);
|
||||
|
||||
indio_dev->name = dev_name(dev);
|
||||
indio_dev->dev.parent = dev;
|
||||
indio_dev->dev.of_node = dev->of_node;
|
||||
indio_dev->info = &envelope_detector_info;
|
||||
indio_dev->channels = &envelope_detector_iio_channel;
|
||||
indio_dev->num_channels = 1;
|
||||
|
||||
env->dac = devm_iio_channel_get(dev, "dac");
|
||||
if (IS_ERR(env->dac)) {
|
||||
if (PTR_ERR(env->dac) != -EPROBE_DEFER)
|
||||
dev_err(dev, "failed to get dac input channel\n");
|
||||
return PTR_ERR(env->dac);
|
||||
}
|
||||
|
||||
env->comp_irq = platform_get_irq_byname(pdev, "comp");
|
||||
if (env->comp_irq < 0) {
|
||||
if (env->comp_irq != -EPROBE_DEFER)
|
||||
dev_err(dev, "failed to get compare interrupt\n");
|
||||
return env->comp_irq;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, env->comp_irq, envelope_detector_comp_isr,
|
||||
0, "envelope-detector", env);
|
||||
if (ret) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(dev, "failed to request interrupt\n");
|
||||
return ret;
|
||||
}
|
||||
env->comp_irq_trigger = irq_get_trigger_type(env->comp_irq);
|
||||
if (env->comp_irq_trigger & IRQF_TRIGGER_RISING)
|
||||
env->comp_irq_trigger_inv |= IRQF_TRIGGER_FALLING;
|
||||
if (env->comp_irq_trigger & IRQF_TRIGGER_FALLING)
|
||||
env->comp_irq_trigger_inv |= IRQF_TRIGGER_RISING;
|
||||
if (env->comp_irq_trigger & IRQF_TRIGGER_HIGH)
|
||||
env->comp_irq_trigger_inv |= IRQF_TRIGGER_LOW;
|
||||
if (env->comp_irq_trigger & IRQF_TRIGGER_LOW)
|
||||
env->comp_irq_trigger_inv |= IRQF_TRIGGER_HIGH;
|
||||
|
||||
ret = iio_get_channel_type(env->dac, &type);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (type != IIO_VOLTAGE) {
|
||||
dev_err(dev, "dac is of the wrong type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = iio_read_max_channel_raw(env->dac, &env->dac_max);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "dac does not indicate its raw maximum value\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id envelope_detector_match[] = {
|
||||
{ .compatible = "axentia,tse850-envelope-detector", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, envelope_detector_match);
|
||||
|
||||
static struct platform_driver envelope_detector_driver = {
|
||||
.probe = envelope_detector_probe,
|
||||
.driver = {
|
||||
.name = "iio-envelope-detector",
|
||||
.of_match_table = envelope_detector_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(envelope_detector_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator");
|
||||
MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -238,7 +238,9 @@ static int max1027_read_single_value(struct iio_dev *indio_dev,
|
|||
|
||||
/* Configure conversion register with the requested chan */
|
||||
st->reg = MAX1027_CONV_REG | MAX1027_CHAN(chan->channel) |
|
||||
MAX1027_NOSCAN | !!(chan->type == IIO_TEMP);
|
||||
MAX1027_NOSCAN;
|
||||
if (chan->type == IIO_TEMP)
|
||||
st->reg |= MAX1027_TEMP;
|
||||
ret = spi_write(st->spi, &st->reg, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(&indio_dev->dev,
|
||||
|
@ -360,17 +362,6 @@ static int max1027_set_trigger_state(struct iio_trigger *trig, bool state)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int max1027_validate_device(struct iio_trigger *trig,
|
||||
struct iio_dev *indio_dev)
|
||||
{
|
||||
struct iio_dev *indio = iio_trigger_get_drvdata(trig);
|
||||
|
||||
if (indio != indio_dev)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t max1027_trigger_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_poll_func *pf = (struct iio_poll_func *)private;
|
||||
|
@ -391,7 +382,7 @@ static irqreturn_t max1027_trigger_handler(int irq, void *private)
|
|||
|
||||
static const struct iio_trigger_ops max1027_trigger_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.validate_device = &max1027_validate_device,
|
||||
.validate_device = &iio_trigger_validate_own_device,
|
||||
.set_trigger_state = &max1027_set_trigger_state,
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
* This file is part of STM32 ADC driver
|
||||
*
|
||||
* Copyright (C) 2016, STMicroelectronics - All Rights Reserved
|
||||
* Author: Fabrice Gasnier <fabrice.gasnier@st.com>.
|
||||
*
|
||||
* Inspired from: fsl-imx25-tsadc
|
||||
*
|
||||
* License type: GPLv2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/irqdesc.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "stm32-adc-core.h"
|
||||
|
||||
/* STM32F4 - common registers for all ADC instances: 1, 2 & 3 */
|
||||
#define STM32F4_ADC_CSR (STM32_ADCX_COMN_OFFSET + 0x00)
|
||||
#define STM32F4_ADC_CCR (STM32_ADCX_COMN_OFFSET + 0x04)
|
||||
|
||||
/* STM32F4_ADC_CSR - bit fields */
|
||||
#define STM32F4_EOC3 BIT(17)
|
||||
#define STM32F4_EOC2 BIT(9)
|
||||
#define STM32F4_EOC1 BIT(1)
|
||||
|
||||
/* STM32F4_ADC_CCR - bit fields */
|
||||
#define STM32F4_ADC_ADCPRE_SHIFT 16
|
||||
#define STM32F4_ADC_ADCPRE_MASK GENMASK(17, 16)
|
||||
|
||||
/* STM32 F4 maximum analog clock rate (from datasheet) */
|
||||
#define STM32F4_ADC_MAX_CLK_RATE 36000000
|
||||
|
||||
/**
|
||||
* struct stm32_adc_priv - stm32 ADC core private data
|
||||
* @irq: irq for ADC block
|
||||
* @domain: irq domain reference
|
||||
* @aclk: clock reference for the analog circuitry
|
||||
* @vref: regulator reference
|
||||
* @common: common data for all ADC instances
|
||||
*/
|
||||
struct stm32_adc_priv {
|
||||
int irq;
|
||||
struct irq_domain *domain;
|
||||
struct clk *aclk;
|
||||
struct regulator *vref;
|
||||
struct stm32_adc_common common;
|
||||
};
|
||||
|
||||
static struct stm32_adc_priv *to_stm32_adc_priv(struct stm32_adc_common *com)
|
||||
{
|
||||
return container_of(com, struct stm32_adc_priv, common);
|
||||
}
|
||||
|
||||
/* STM32F4 ADC internal common clock prescaler division ratios */
|
||||
static int stm32f4_pclk_div[] = {2, 4, 6, 8};
|
||||
|
||||
/**
|
||||
* stm32f4_adc_clk_sel() - Select stm32f4 ADC common clock prescaler
|
||||
* @priv: stm32 ADC core private data
|
||||
* Select clock prescaler used for analog conversions, before using ADC.
|
||||
*/
|
||||
static int stm32f4_adc_clk_sel(struct platform_device *pdev,
|
||||
struct stm32_adc_priv *priv)
|
||||
{
|
||||
unsigned long rate;
|
||||
u32 val;
|
||||
int i;
|
||||
|
||||
rate = clk_get_rate(priv->aclk);
|
||||
for (i = 0; i < ARRAY_SIZE(stm32f4_pclk_div); i++) {
|
||||
if ((rate / stm32f4_pclk_div[i]) <= STM32F4_ADC_MAX_CLK_RATE)
|
||||
break;
|
||||
}
|
||||
if (i >= ARRAY_SIZE(stm32f4_pclk_div))
|
||||
return -EINVAL;
|
||||
|
||||
val = readl_relaxed(priv->common.base + STM32F4_ADC_CCR);
|
||||
val &= ~STM32F4_ADC_ADCPRE_MASK;
|
||||
val |= i << STM32F4_ADC_ADCPRE_SHIFT;
|
||||
writel_relaxed(val, priv->common.base + STM32F4_ADC_CCR);
|
||||
|
||||
dev_dbg(&pdev->dev, "Using analog clock source at %ld kHz\n",
|
||||
rate / (stm32f4_pclk_div[i] * 1000));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ADC common interrupt for all instances */
|
||||
static void stm32_adc_irq_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct stm32_adc_priv *priv = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
u32 status;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
status = readl_relaxed(priv->common.base + STM32F4_ADC_CSR);
|
||||
|
||||
if (status & STM32F4_EOC1)
|
||||
generic_handle_irq(irq_find_mapping(priv->domain, 0));
|
||||
|
||||
if (status & STM32F4_EOC2)
|
||||
generic_handle_irq(irq_find_mapping(priv->domain, 1));
|
||||
|
||||
if (status & STM32F4_EOC3)
|
||||
generic_handle_irq(irq_find_mapping(priv->domain, 2));
|
||||
|
||||
chained_irq_exit(chip, desc);
|
||||
};
|
||||
|
||||
static int stm32_adc_domain_map(struct irq_domain *d, unsigned int irq,
|
||||
irq_hw_number_t hwirq)
|
||||
{
|
||||
irq_set_chip_data(irq, d->host_data);
|
||||
irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_level_irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm32_adc_domain_unmap(struct irq_domain *d, unsigned int irq)
|
||||
{
|
||||
irq_set_chip_and_handler(irq, NULL, NULL);
|
||||
irq_set_chip_data(irq, NULL);
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops stm32_adc_domain_ops = {
|
||||
.map = stm32_adc_domain_map,
|
||||
.unmap = stm32_adc_domain_unmap,
|
||||
.xlate = irq_domain_xlate_onecell,
|
||||
};
|
||||
|
||||
static int stm32_adc_irq_probe(struct platform_device *pdev,
|
||||
struct stm32_adc_priv *priv)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
|
||||
priv->irq = platform_get_irq(pdev, 0);
|
||||
if (priv->irq < 0) {
|
||||
dev_err(&pdev->dev, "failed to get irq\n");
|
||||
return priv->irq;
|
||||
}
|
||||
|
||||
priv->domain = irq_domain_add_simple(np, STM32_ADC_MAX_ADCS, 0,
|
||||
&stm32_adc_domain_ops,
|
||||
priv);
|
||||
if (!priv->domain) {
|
||||
dev_err(&pdev->dev, "Failed to add irq domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
irq_set_chained_handler(priv->irq, stm32_adc_irq_handler);
|
||||
irq_set_handler_data(priv->irq, priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm32_adc_irq_remove(struct platform_device *pdev,
|
||||
struct stm32_adc_priv *priv)
|
||||
{
|
||||
int hwirq;
|
||||
|
||||
for (hwirq = 0; hwirq < STM32_ADC_MAX_ADCS; hwirq++)
|
||||
irq_dispose_mapping(irq_find_mapping(priv->domain, hwirq));
|
||||
irq_domain_remove(priv->domain);
|
||||
irq_set_chained_handler(priv->irq, NULL);
|
||||
}
|
||||
|
||||
static int stm32_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct stm32_adc_priv *priv;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
if (!pdev->dev.of_node)
|
||||
return -ENODEV;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
priv->common.base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(priv->common.base))
|
||||
return PTR_ERR(priv->common.base);
|
||||
|
||||
priv->vref = devm_regulator_get(&pdev->dev, "vref");
|
||||
if (IS_ERR(priv->vref)) {
|
||||
ret = PTR_ERR(priv->vref);
|
||||
dev_err(&pdev->dev, "vref get failed, %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regulator_enable(priv->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "vref enable failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regulator_get_voltage(priv->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "vref get voltage failed, %d\n", ret);
|
||||
goto err_regulator_disable;
|
||||
}
|
||||
priv->common.vref_mv = ret / 1000;
|
||||
dev_dbg(&pdev->dev, "vref+=%dmV\n", priv->common.vref_mv);
|
||||
|
||||
priv->aclk = devm_clk_get(&pdev->dev, "adc");
|
||||
if (IS_ERR(priv->aclk)) {
|
||||
ret = PTR_ERR(priv->aclk);
|
||||
dev_err(&pdev->dev, "Can't get 'adc' clock\n");
|
||||
goto err_regulator_disable;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(priv->aclk);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "adc clk enable failed\n");
|
||||
goto err_regulator_disable;
|
||||
}
|
||||
|
||||
ret = stm32f4_adc_clk_sel(pdev, priv);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "adc clk selection failed\n");
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
ret = stm32_adc_irq_probe(pdev, priv);
|
||||
if (ret < 0)
|
||||
goto err_clk_disable;
|
||||
|
||||
platform_set_drvdata(pdev, &priv->common);
|
||||
|
||||
ret = of_platform_populate(np, NULL, NULL, &pdev->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to populate DT children\n");
|
||||
goto err_irq_remove;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_irq_remove:
|
||||
stm32_adc_irq_remove(pdev, priv);
|
||||
|
||||
err_clk_disable:
|
||||
clk_disable_unprepare(priv->aclk);
|
||||
|
||||
err_regulator_disable:
|
||||
regulator_disable(priv->vref);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct stm32_adc_common *common = platform_get_drvdata(pdev);
|
||||
struct stm32_adc_priv *priv = to_stm32_adc_priv(common);
|
||||
|
||||
of_platform_depopulate(&pdev->dev);
|
||||
stm32_adc_irq_remove(pdev, priv);
|
||||
clk_disable_unprepare(priv->aclk);
|
||||
regulator_disable(priv->vref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id stm32_adc_of_match[] = {
|
||||
{ .compatible = "st,stm32f4-adc-core" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, stm32_adc_of_match);
|
||||
|
||||
static struct platform_driver stm32_adc_driver = {
|
||||
.probe = stm32_adc_probe,
|
||||
.remove = stm32_adc_remove,
|
||||
.driver = {
|
||||
.name = "stm32-adc-core",
|
||||
.of_match_table = stm32_adc_of_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(stm32_adc_driver);
|
||||
|
||||
MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics STM32 ADC core driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:stm32-adc-core");
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* This file is part of STM32 ADC driver
|
||||
*
|
||||
* Copyright (C) 2016, STMicroelectronics - All Rights Reserved
|
||||
* Author: Fabrice Gasnier <fabrice.gasnier@st.com>.
|
||||
*
|
||||
* License type: GPLv2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __STM32_ADC_H
|
||||
#define __STM32_ADC_H
|
||||
|
||||
/*
|
||||
* STM32 - ADC global register map
|
||||
* ________________________________________________________
|
||||
* | Offset | Register |
|
||||
* --------------------------------------------------------
|
||||
* | 0x000 | Master ADC1 |
|
||||
* --------------------------------------------------------
|
||||
* | 0x100 | Slave ADC2 |
|
||||
* --------------------------------------------------------
|
||||
* | 0x200 | Slave ADC3 |
|
||||
* --------------------------------------------------------
|
||||
* | 0x300 | Master & Slave common regs |
|
||||
* --------------------------------------------------------
|
||||
*/
|
||||
#define STM32_ADC_MAX_ADCS 3
|
||||
#define STM32_ADCX_COMN_OFFSET 0x300
|
||||
|
||||
/**
|
||||
* struct stm32_adc_common - stm32 ADC driver common data (for all instances)
|
||||
* @base: control registers base cpu addr
|
||||
* @vref_mv: vref voltage (mv)
|
||||
*/
|
||||
struct stm32_adc_common {
|
||||
void __iomem *base;
|
||||
int vref_mv;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,518 @@
|
|||
/*
|
||||
* This file is part of STM32 ADC driver
|
||||
*
|
||||
* Copyright (C) 2016, STMicroelectronics - All Rights Reserved
|
||||
* Author: Fabrice Gasnier <fabrice.gasnier@st.com>.
|
||||
*
|
||||
* License type: GPLv2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include "stm32-adc-core.h"
|
||||
|
||||
/* STM32F4 - Registers for each ADC instance */
|
||||
#define STM32F4_ADC_SR 0x00
|
||||
#define STM32F4_ADC_CR1 0x04
|
||||
#define STM32F4_ADC_CR2 0x08
|
||||
#define STM32F4_ADC_SMPR1 0x0C
|
||||
#define STM32F4_ADC_SMPR2 0x10
|
||||
#define STM32F4_ADC_HTR 0x24
|
||||
#define STM32F4_ADC_LTR 0x28
|
||||
#define STM32F4_ADC_SQR1 0x2C
|
||||
#define STM32F4_ADC_SQR2 0x30
|
||||
#define STM32F4_ADC_SQR3 0x34
|
||||
#define STM32F4_ADC_JSQR 0x38
|
||||
#define STM32F4_ADC_JDR1 0x3C
|
||||
#define STM32F4_ADC_JDR2 0x40
|
||||
#define STM32F4_ADC_JDR3 0x44
|
||||
#define STM32F4_ADC_JDR4 0x48
|
||||
#define STM32F4_ADC_DR 0x4C
|
||||
|
||||
/* STM32F4_ADC_SR - bit fields */
|
||||
#define STM32F4_STRT BIT(4)
|
||||
#define STM32F4_EOC BIT(1)
|
||||
|
||||
/* STM32F4_ADC_CR1 - bit fields */
|
||||
#define STM32F4_SCAN BIT(8)
|
||||
#define STM32F4_EOCIE BIT(5)
|
||||
|
||||
/* STM32F4_ADC_CR2 - bit fields */
|
||||
#define STM32F4_SWSTART BIT(30)
|
||||
#define STM32F4_EXTEN_MASK GENMASK(29, 28)
|
||||
#define STM32F4_EOCS BIT(10)
|
||||
#define STM32F4_ADON BIT(0)
|
||||
|
||||
/* STM32F4_ADC_SQR1 - bit fields */
|
||||
#define STM32F4_L_SHIFT 20
|
||||
#define STM32F4_L_MASK GENMASK(23, 20)
|
||||
|
||||
/* STM32F4_ADC_SQR3 - bit fields */
|
||||
#define STM32F4_SQ1_SHIFT 0
|
||||
#define STM32F4_SQ1_MASK GENMASK(4, 0)
|
||||
|
||||
#define STM32_ADC_TIMEOUT_US 100000
|
||||
#define STM32_ADC_TIMEOUT (msecs_to_jiffies(STM32_ADC_TIMEOUT_US / 1000))
|
||||
|
||||
/**
|
||||
* struct stm32_adc - private data of each ADC IIO instance
|
||||
* @common: reference to ADC block common data
|
||||
* @offset: ADC instance register offset in ADC block
|
||||
* @completion: end of single conversion completion
|
||||
* @buffer: data buffer
|
||||
* @clk: clock for this adc instance
|
||||
* @irq: interrupt for this adc instance
|
||||
* @lock: spinlock
|
||||
*/
|
||||
struct stm32_adc {
|
||||
struct stm32_adc_common *common;
|
||||
u32 offset;
|
||||
struct completion completion;
|
||||
u16 *buffer;
|
||||
struct clk *clk;
|
||||
int irq;
|
||||
spinlock_t lock; /* interrupt lock */
|
||||
};
|
||||
|
||||
/**
|
||||
* struct stm32_adc_chan_spec - specification of stm32 adc channel
|
||||
* @type: IIO channel type
|
||||
* @channel: channel number (single ended)
|
||||
* @name: channel name (single ended)
|
||||
*/
|
||||
struct stm32_adc_chan_spec {
|
||||
enum iio_chan_type type;
|
||||
int channel;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
/* Input definitions common for all STM32F4 instances */
|
||||
static const struct stm32_adc_chan_spec stm32f4_adc123_channels[] = {
|
||||
{ IIO_VOLTAGE, 0, "in0" },
|
||||
{ IIO_VOLTAGE, 1, "in1" },
|
||||
{ IIO_VOLTAGE, 2, "in2" },
|
||||
{ IIO_VOLTAGE, 3, "in3" },
|
||||
{ IIO_VOLTAGE, 4, "in4" },
|
||||
{ IIO_VOLTAGE, 5, "in5" },
|
||||
{ IIO_VOLTAGE, 6, "in6" },
|
||||
{ IIO_VOLTAGE, 7, "in7" },
|
||||
{ IIO_VOLTAGE, 8, "in8" },
|
||||
{ IIO_VOLTAGE, 9, "in9" },
|
||||
{ IIO_VOLTAGE, 10, "in10" },
|
||||
{ IIO_VOLTAGE, 11, "in11" },
|
||||
{ IIO_VOLTAGE, 12, "in12" },
|
||||
{ IIO_VOLTAGE, 13, "in13" },
|
||||
{ IIO_VOLTAGE, 14, "in14" },
|
||||
{ IIO_VOLTAGE, 15, "in15" },
|
||||
};
|
||||
|
||||
/**
|
||||
* STM32 ADC registers access routines
|
||||
* @adc: stm32 adc instance
|
||||
* @reg: reg offset in adc instance
|
||||
*
|
||||
* Note: All instances share same base, with 0x0, 0x100 or 0x200 offset resp.
|
||||
* for adc1, adc2 and adc3.
|
||||
*/
|
||||
static u32 stm32_adc_readl(struct stm32_adc *adc, u32 reg)
|
||||
{
|
||||
return readl_relaxed(adc->common->base + adc->offset + reg);
|
||||
}
|
||||
|
||||
static u16 stm32_adc_readw(struct stm32_adc *adc, u32 reg)
|
||||
{
|
||||
return readw_relaxed(adc->common->base + adc->offset + reg);
|
||||
}
|
||||
|
||||
static void stm32_adc_writel(struct stm32_adc *adc, u32 reg, u32 val)
|
||||
{
|
||||
writel_relaxed(val, adc->common->base + adc->offset + reg);
|
||||
}
|
||||
|
||||
static void stm32_adc_set_bits(struct stm32_adc *adc, u32 reg, u32 bits)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&adc->lock, flags);
|
||||
stm32_adc_writel(adc, reg, stm32_adc_readl(adc, reg) | bits);
|
||||
spin_unlock_irqrestore(&adc->lock, flags);
|
||||
}
|
||||
|
||||
static void stm32_adc_clr_bits(struct stm32_adc *adc, u32 reg, u32 bits)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&adc->lock, flags);
|
||||
stm32_adc_writel(adc, reg, stm32_adc_readl(adc, reg) & ~bits);
|
||||
spin_unlock_irqrestore(&adc->lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* stm32_adc_conv_irq_enable() - Enable end of conversion interrupt
|
||||
* @adc: stm32 adc instance
|
||||
*/
|
||||
static void stm32_adc_conv_irq_enable(struct stm32_adc *adc)
|
||||
{
|
||||
stm32_adc_set_bits(adc, STM32F4_ADC_CR1, STM32F4_EOCIE);
|
||||
};
|
||||
|
||||
/**
|
||||
* stm32_adc_conv_irq_disable() - Disable end of conversion interrupt
|
||||
* @adc: stm32 adc instance
|
||||
*/
|
||||
static void stm32_adc_conv_irq_disable(struct stm32_adc *adc)
|
||||
{
|
||||
stm32_adc_clr_bits(adc, STM32F4_ADC_CR1, STM32F4_EOCIE);
|
||||
}
|
||||
|
||||
/**
|
||||
* stm32_adc_start_conv() - Start conversions for regular channels.
|
||||
* @adc: stm32 adc instance
|
||||
*/
|
||||
static void stm32_adc_start_conv(struct stm32_adc *adc)
|
||||
{
|
||||
stm32_adc_set_bits(adc, STM32F4_ADC_CR1, STM32F4_SCAN);
|
||||
stm32_adc_set_bits(adc, STM32F4_ADC_CR2, STM32F4_EOCS | STM32F4_ADON);
|
||||
|
||||
/* Wait for Power-up time (tSTAB from datasheet) */
|
||||
usleep_range(2, 3);
|
||||
|
||||
/* Software start ? (e.g. trigger detection disabled ?) */
|
||||
if (!(stm32_adc_readl(adc, STM32F4_ADC_CR2) & STM32F4_EXTEN_MASK))
|
||||
stm32_adc_set_bits(adc, STM32F4_ADC_CR2, STM32F4_SWSTART);
|
||||
}
|
||||
|
||||
static void stm32_adc_stop_conv(struct stm32_adc *adc)
|
||||
{
|
||||
stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_EXTEN_MASK);
|
||||
stm32_adc_clr_bits(adc, STM32F4_ADC_SR, STM32F4_STRT);
|
||||
|
||||
stm32_adc_clr_bits(adc, STM32F4_ADC_CR1, STM32F4_SCAN);
|
||||
stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_ADON);
|
||||
}
|
||||
|
||||
/**
|
||||
* stm32_adc_single_conv() - Performs a single conversion
|
||||
* @indio_dev: IIO device
|
||||
* @chan: IIO channel
|
||||
* @res: conversion result
|
||||
*
|
||||
* The function performs a single conversion on a given channel:
|
||||
* - Program sequencer with one channel (e.g. in SQ1 with len = 1)
|
||||
* - Use SW trigger
|
||||
* - Start conversion, then wait for interrupt completion.
|
||||
*/
|
||||
static int stm32_adc_single_conv(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
int *res)
|
||||
{
|
||||
struct stm32_adc *adc = iio_priv(indio_dev);
|
||||
long timeout;
|
||||
u32 val;
|
||||
u16 result;
|
||||
int ret;
|
||||
|
||||
reinit_completion(&adc->completion);
|
||||
|
||||
adc->buffer = &result;
|
||||
|
||||
/* Program chan number in regular sequence */
|
||||
val = stm32_adc_readl(adc, STM32F4_ADC_SQR3);
|
||||
val &= ~STM32F4_SQ1_MASK;
|
||||
val |= chan->channel << STM32F4_SQ1_SHIFT;
|
||||
stm32_adc_writel(adc, STM32F4_ADC_SQR3, val);
|
||||
|
||||
/* Set regular sequence len (0 for 1 conversion) */
|
||||
stm32_adc_clr_bits(adc, STM32F4_ADC_SQR1, STM32F4_L_MASK);
|
||||
|
||||
/* Trigger detection disabled (conversion can be launched in SW) */
|
||||
stm32_adc_clr_bits(adc, STM32F4_ADC_CR2, STM32F4_EXTEN_MASK);
|
||||
|
||||
stm32_adc_conv_irq_enable(adc);
|
||||
|
||||
stm32_adc_start_conv(adc);
|
||||
|
||||
timeout = wait_for_completion_interruptible_timeout(
|
||||
&adc->completion, STM32_ADC_TIMEOUT);
|
||||
if (timeout == 0) {
|
||||
ret = -ETIMEDOUT;
|
||||
} else if (timeout < 0) {
|
||||
ret = timeout;
|
||||
} else {
|
||||
*res = result;
|
||||
ret = IIO_VAL_INT;
|
||||
}
|
||||
|
||||
stm32_adc_stop_conv(adc);
|
||||
|
||||
stm32_adc_conv_irq_disable(adc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_adc_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct stm32_adc *adc = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (chan->type == IIO_VOLTAGE)
|
||||
ret = stm32_adc_single_conv(indio_dev, chan, val);
|
||||
else
|
||||
ret = -EINVAL;
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
return ret;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = adc->common->vref_mv;
|
||||
*val2 = chan->scan_type.realbits;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t stm32_adc_isr(int irq, void *data)
|
||||
{
|
||||
struct stm32_adc *adc = data;
|
||||
u32 status = stm32_adc_readl(adc, STM32F4_ADC_SR);
|
||||
|
||||
if (status & STM32F4_EOC) {
|
||||
*adc->buffer = stm32_adc_readw(adc, STM32F4_ADC_DR);
|
||||
complete(&adc->completion);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
static int stm32_adc_of_xlate(struct iio_dev *indio_dev,
|
||||
const struct of_phandle_args *iiospec)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < indio_dev->num_channels; i++)
|
||||
if (indio_dev->channels[i].channel == iiospec->args[0])
|
||||
return i;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* stm32_adc_debugfs_reg_access - read or write register value
|
||||
*
|
||||
* To read a value from an ADC register:
|
||||
* echo [ADC reg offset] > direct_reg_access
|
||||
* cat direct_reg_access
|
||||
*
|
||||
* To write a value in a ADC register:
|
||||
* echo [ADC_reg_offset] [value] > direct_reg_access
|
||||
*/
|
||||
static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev,
|
||||
unsigned reg, unsigned writeval,
|
||||
unsigned *readval)
|
||||
{
|
||||
struct stm32_adc *adc = iio_priv(indio_dev);
|
||||
|
||||
if (!readval)
|
||||
stm32_adc_writel(adc, reg, writeval);
|
||||
else
|
||||
*readval = stm32_adc_readl(adc, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_info stm32_adc_iio_info = {
|
||||
.read_raw = stm32_adc_read_raw,
|
||||
.debugfs_reg_access = stm32_adc_debugfs_reg_access,
|
||||
.of_xlate = stm32_adc_of_xlate,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void stm32_adc_chan_init_one(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec *chan,
|
||||
const struct stm32_adc_chan_spec *channel,
|
||||
int scan_index)
|
||||
{
|
||||
chan->type = channel->type;
|
||||
chan->channel = channel->channel;
|
||||
chan->datasheet_name = channel->name;
|
||||
chan->scan_index = scan_index;
|
||||
chan->indexed = 1;
|
||||
chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
|
||||
chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
|
||||
chan->scan_type.sign = 'u';
|
||||
chan->scan_type.realbits = 12;
|
||||
chan->scan_type.storagebits = 16;
|
||||
}
|
||||
|
||||
static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct device_node *node = indio_dev->dev.of_node;
|
||||
struct property *prop;
|
||||
const __be32 *cur;
|
||||
struct iio_chan_spec *channels;
|
||||
int scan_index = 0, num_channels;
|
||||
u32 val;
|
||||
|
||||
num_channels = of_property_count_u32_elems(node, "st,adc-channels");
|
||||
if (num_channels < 0 ||
|
||||
num_channels >= ARRAY_SIZE(stm32f4_adc123_channels)) {
|
||||
dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
|
||||
return num_channels < 0 ? num_channels : -EINVAL;
|
||||
}
|
||||
|
||||
channels = devm_kcalloc(&indio_dev->dev, num_channels,
|
||||
sizeof(struct iio_chan_spec), GFP_KERNEL);
|
||||
if (!channels)
|
||||
return -ENOMEM;
|
||||
|
||||
of_property_for_each_u32(node, "st,adc-channels", prop, cur, val) {
|
||||
if (val >= ARRAY_SIZE(stm32f4_adc123_channels)) {
|
||||
dev_err(&indio_dev->dev, "Invalid channel %d\n", val);
|
||||
return -EINVAL;
|
||||
}
|
||||
stm32_adc_chan_init_one(indio_dev, &channels[scan_index],
|
||||
&stm32f4_adc123_channels[val],
|
||||
scan_index);
|
||||
scan_index++;
|
||||
}
|
||||
|
||||
indio_dev->num_channels = scan_index;
|
||||
indio_dev->channels = channels;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
struct stm32_adc *adc;
|
||||
int ret;
|
||||
|
||||
if (!pdev->dev.of_node)
|
||||
return -ENODEV;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
adc = iio_priv(indio_dev);
|
||||
adc->common = dev_get_drvdata(pdev->dev.parent);
|
||||
spin_lock_init(&adc->lock);
|
||||
init_completion(&adc->completion);
|
||||
|
||||
indio_dev->name = dev_name(&pdev->dev);
|
||||
indio_dev->dev.parent = &pdev->dev;
|
||||
indio_dev->dev.of_node = pdev->dev.of_node;
|
||||
indio_dev->info = &stm32_adc_iio_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
platform_set_drvdata(pdev, adc);
|
||||
|
||||
ret = of_property_read_u32(pdev->dev.of_node, "reg", &adc->offset);
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev, "missing reg property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
adc->irq = platform_get_irq(pdev, 0);
|
||||
if (adc->irq < 0) {
|
||||
dev_err(&pdev->dev, "failed to get irq\n");
|
||||
return adc->irq;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, adc->irq, stm32_adc_isr,
|
||||
0, pdev->name, adc);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to request IRQ\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
adc->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(adc->clk)) {
|
||||
dev_err(&pdev->dev, "Can't get clock\n");
|
||||
return PTR_ERR(adc->clk);
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(adc->clk);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "clk enable failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = stm32_adc_chan_of_init(indio_dev);
|
||||
if (ret < 0)
|
||||
goto err_clk_disable;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "iio dev register failed\n");
|
||||
goto err_clk_disable;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_clk_disable:
|
||||
clk_disable_unprepare(adc->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct stm32_adc *adc = platform_get_drvdata(pdev);
|
||||
struct iio_dev *indio_dev = iio_priv_to_dev(adc);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
clk_disable_unprepare(adc->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id stm32_adc_of_match[] = {
|
||||
{ .compatible = "st,stm32f4-adc" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, stm32_adc_of_match);
|
||||
|
||||
static struct platform_driver stm32_adc_driver = {
|
||||
.probe = stm32_adc_probe,
|
||||
.remove = stm32_adc_remove,
|
||||
.driver = {
|
||||
.name = "stm32-adc",
|
||||
.of_match_table = stm32_adc_of_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(stm32_adc_driver);
|
||||
|
||||
MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics STM32 ADC IIO driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:stm32-adc");
|
|
@ -14,6 +14,10 @@
|
|||
#include <linux/spi/spi.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
|
||||
enum {
|
||||
adc0831,
|
||||
|
@ -38,10 +42,16 @@ struct adc0832 {
|
|||
.indexed = 1, \
|
||||
.channel = chan, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.scan_index = chan, \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = 8, \
|
||||
.storagebits = 8, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define ADC0832_VOLTAGE_CHANNEL_DIFF(chan1, chan2) \
|
||||
#define ADC0832_VOLTAGE_CHANNEL_DIFF(chan1, chan2, si) \
|
||||
{ \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
|
@ -49,18 +59,26 @@ struct adc0832 {
|
|||
.channel2 = (chan2), \
|
||||
.differential = 1, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.scan_index = si, \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = 8, \
|
||||
.storagebits = 8, \
|
||||
}, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec adc0831_channels[] = {
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1, 0),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(1),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec adc0832_channels[] = {
|
||||
ADC0832_VOLTAGE_CHANNEL(0),
|
||||
ADC0832_VOLTAGE_CHANNEL(1),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1, 2),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0, 3),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(4),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec adc0834_channels[] = {
|
||||
|
@ -68,10 +86,11 @@ static const struct iio_chan_spec adc0834_channels[] = {
|
|||
ADC0832_VOLTAGE_CHANNEL(1),
|
||||
ADC0832_VOLTAGE_CHANNEL(2),
|
||||
ADC0832_VOLTAGE_CHANNEL(3),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(2, 3),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(3, 2),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1, 4),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0, 5),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(2, 3, 6),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(3, 2, 7),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(8),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec adc0838_channels[] = {
|
||||
|
@ -83,14 +102,15 @@ static const struct iio_chan_spec adc0838_channels[] = {
|
|||
ADC0832_VOLTAGE_CHANNEL(5),
|
||||
ADC0832_VOLTAGE_CHANNEL(6),
|
||||
ADC0832_VOLTAGE_CHANNEL(7),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(2, 3),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(3, 2),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(4, 5),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(5, 4),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(6, 7),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(7, 6),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1, 8),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0, 9),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(2, 3, 10),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(3, 2, 11),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(4, 5, 12),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(5, 4, 13),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(6, 7, 14),
|
||||
ADC0832_VOLTAGE_CHANNEL_DIFF(7, 6, 15),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(16),
|
||||
};
|
||||
|
||||
static int adc0831_adc_conversion(struct adc0832 *adc)
|
||||
|
@ -178,6 +198,42 @@ static const struct iio_info adc0832_info = {
|
|||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static irqreturn_t adc0832_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct adc0832 *adc = iio_priv(indio_dev);
|
||||
u8 data[24] = { }; /* 16x 1 byte ADC data + 8 bytes timestamp */
|
||||
int scan_index;
|
||||
int i = 0;
|
||||
|
||||
mutex_lock(&adc->lock);
|
||||
|
||||
for_each_set_bit(scan_index, indio_dev->active_scan_mask,
|
||||
indio_dev->masklength) {
|
||||
const struct iio_chan_spec *scan_chan =
|
||||
&indio_dev->channels[scan_index];
|
||||
int ret = adc0832_adc_conversion(adc, scan_chan->channel,
|
||||
scan_chan->differential);
|
||||
if (ret < 0) {
|
||||
dev_warn(&adc->spi->dev,
|
||||
"failed to get conversion data\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
data[i] = ret;
|
||||
i++;
|
||||
}
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, data,
|
||||
iio_get_time_ns(indio_dev));
|
||||
out:
|
||||
mutex_unlock(&adc->lock);
|
||||
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int adc0832_probe(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
|
@ -233,8 +289,19 @@ static int adc0832_probe(struct spi_device *spi)
|
|||
|
||||
spi_set_drvdata(spi, indio_dev);
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
adc0832_trigger_handler, NULL);
|
||||
if (ret)
|
||||
goto err_reg_disable;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret)
|
||||
goto err_buffer_cleanup;
|
||||
|
||||
return 0;
|
||||
err_buffer_cleanup:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
err_reg_disable:
|
||||
regulator_disable(adc->reg);
|
||||
|
||||
return ret;
|
||||
|
@ -246,6 +313,7 @@ static int adc0832_remove(struct spi_device *spi)
|
|||
struct adc0832 *adc = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
regulator_disable(adc->reg);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#define TI_ADC_DRV_NAME "ti-adc161s626"
|
||||
|
||||
|
@ -39,7 +40,9 @@ static const struct iio_chan_spec ti_adc141s626_channels[] = {
|
|||
{
|
||||
.type = IIO_VOLTAGE,
|
||||
.channel = 0,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_OFFSET),
|
||||
.scan_index = 0,
|
||||
.scan_type = {
|
||||
.sign = 's',
|
||||
|
@ -54,7 +57,9 @@ static const struct iio_chan_spec ti_adc161s626_channels[] = {
|
|||
{
|
||||
.type = IIO_VOLTAGE,
|
||||
.channel = 0,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_OFFSET),
|
||||
.scan_index = 0,
|
||||
.scan_type = {
|
||||
.sign = 's',
|
||||
|
@ -68,6 +73,8 @@ static const struct iio_chan_spec ti_adc161s626_channels[] = {
|
|||
struct ti_adc_data {
|
||||
struct iio_dev *indio_dev;
|
||||
struct spi_device *spi;
|
||||
struct regulator *ref;
|
||||
|
||||
u8 read_size;
|
||||
u8 shift;
|
||||
|
||||
|
@ -135,9 +142,8 @@ static int ti_adc_read_raw(struct iio_dev *indio_dev,
|
|||
struct ti_adc_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (mask != IIO_CHAN_INFO_RAW)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -145,8 +151,23 @@ static int ti_adc_read_raw(struct iio_dev *indio_dev,
|
|||
ret = ti_adc_read_measurement(data, chan, val);
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
|
||||
if (!ret)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = regulator_get_voltage(data->ref);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret / 1000;
|
||||
*val2 = chan->scan_type.realbits;
|
||||
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
*val = 1 << (chan->scan_type.realbits - 1);
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -191,10 +212,17 @@ static int ti_adc_probe(struct spi_device *spi)
|
|||
break;
|
||||
}
|
||||
|
||||
data->ref = devm_regulator_get(&spi->dev, "vdda");
|
||||
if (!IS_ERR(data->ref)) {
|
||||
ret = regulator_enable(data->ref);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
ti_adc_trigger_handler, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto error_regulator_disable;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret)
|
||||
|
@ -205,15 +233,20 @@ static int ti_adc_probe(struct spi_device *spi)
|
|||
error_unreg_buffer:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
error_regulator_disable:
|
||||
regulator_disable(data->ref);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ti_adc_remove(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev = spi_get_drvdata(spi);
|
||||
struct ti_adc_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
regulator_disable(data->ref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -30,10 +30,28 @@
|
|||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#define DMA_BUFFER_SIZE SZ_2K
|
||||
|
||||
struct tiadc_dma {
|
||||
struct dma_slave_config conf;
|
||||
struct dma_chan *chan;
|
||||
dma_addr_t addr;
|
||||
dma_cookie_t cookie;
|
||||
u8 *buf;
|
||||
int current_period;
|
||||
int period_size;
|
||||
u8 fifo_thresh;
|
||||
};
|
||||
|
||||
struct tiadc_device {
|
||||
struct ti_tscadc_dev *mfd_tscadc;
|
||||
struct tiadc_dma dma;
|
||||
struct mutex fifo1_lock; /* to protect fifo access */
|
||||
int channels;
|
||||
int total_ch_enabled;
|
||||
u8 channel_line[8];
|
||||
u8 channel_step[8];
|
||||
int buffer_en_ch_steps;
|
||||
|
@ -198,6 +216,67 @@ static irqreturn_t tiadc_worker_h(int irq, void *private)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void tiadc_dma_rx_complete(void *param)
|
||||
{
|
||||
struct iio_dev *indio_dev = param;
|
||||
struct tiadc_device *adc_dev = iio_priv(indio_dev);
|
||||
struct tiadc_dma *dma = &adc_dev->dma;
|
||||
u8 *data;
|
||||
int i;
|
||||
|
||||
data = dma->buf + dma->current_period * dma->period_size;
|
||||
dma->current_period = 1 - dma->current_period; /* swap the buffer ID */
|
||||
|
||||
for (i = 0; i < dma->period_size; i += indio_dev->scan_bytes) {
|
||||
iio_push_to_buffers(indio_dev, data);
|
||||
data += indio_dev->scan_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
static int tiadc_start_dma(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct tiadc_device *adc_dev = iio_priv(indio_dev);
|
||||
struct tiadc_dma *dma = &adc_dev->dma;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
|
||||
dma->current_period = 0; /* We start to fill period 0 */
|
||||
/*
|
||||
* Make the fifo thresh as the multiple of total number of
|
||||
* channels enabled, so make sure that cyclic DMA period
|
||||
* length is also a multiple of total number of channels
|
||||
* enabled. This ensures that no invalid data is reported
|
||||
* to the stack via iio_push_to_buffers().
|
||||
*/
|
||||
dma->fifo_thresh = rounddown(FIFO1_THRESHOLD + 1,
|
||||
adc_dev->total_ch_enabled) - 1;
|
||||
/* Make sure that period length is multiple of fifo thresh level */
|
||||
dma->period_size = rounddown(DMA_BUFFER_SIZE / 2,
|
||||
(dma->fifo_thresh + 1) * sizeof(u16));
|
||||
|
||||
dma->conf.src_maxburst = dma->fifo_thresh + 1;
|
||||
dmaengine_slave_config(dma->chan, &dma->conf);
|
||||
|
||||
desc = dmaengine_prep_dma_cyclic(dma->chan, dma->addr,
|
||||
dma->period_size * 2,
|
||||
dma->period_size, DMA_DEV_TO_MEM,
|
||||
DMA_PREP_INTERRUPT);
|
||||
if (!desc)
|
||||
return -EBUSY;
|
||||
|
||||
desc->callback = tiadc_dma_rx_complete;
|
||||
desc->callback_param = indio_dev;
|
||||
|
||||
dma->cookie = dmaengine_submit(desc);
|
||||
|
||||
dma_async_issue_pending(dma->chan);
|
||||
|
||||
tiadc_writel(adc_dev, REG_FIFO1THR, dma->fifo_thresh);
|
||||
tiadc_writel(adc_dev, REG_DMA1REQ, dma->fifo_thresh);
|
||||
tiadc_writel(adc_dev, REG_DMAENABLE_SET, DMA_FIFO1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tiadc_buffer_preenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct tiadc_device *adc_dev = iio_priv(indio_dev);
|
||||
|
@ -218,20 +297,30 @@ static int tiadc_buffer_preenable(struct iio_dev *indio_dev)
|
|||
static int tiadc_buffer_postenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct tiadc_device *adc_dev = iio_priv(indio_dev);
|
||||
struct tiadc_dma *dma = &adc_dev->dma;
|
||||
unsigned int irq_enable;
|
||||
unsigned int enb = 0;
|
||||
u8 bit;
|
||||
|
||||
tiadc_step_config(indio_dev);
|
||||
for_each_set_bit(bit, indio_dev->active_scan_mask, adc_dev->channels)
|
||||
for_each_set_bit(bit, indio_dev->active_scan_mask, adc_dev->channels) {
|
||||
enb |= (get_adc_step_bit(adc_dev, bit) << 1);
|
||||
adc_dev->total_ch_enabled++;
|
||||
}
|
||||
adc_dev->buffer_en_ch_steps = enb;
|
||||
|
||||
if (dma->chan)
|
||||
tiadc_start_dma(indio_dev);
|
||||
|
||||
am335x_tsc_se_set_cache(adc_dev->mfd_tscadc, enb);
|
||||
|
||||
tiadc_writel(adc_dev, REG_IRQSTATUS, IRQENB_FIFO1THRES
|
||||
| IRQENB_FIFO1OVRRUN | IRQENB_FIFO1UNDRFLW);
|
||||
tiadc_writel(adc_dev, REG_IRQENABLE, IRQENB_FIFO1THRES
|
||||
| IRQENB_FIFO1OVRRUN);
|
||||
|
||||
irq_enable = IRQENB_FIFO1OVRRUN;
|
||||
if (!dma->chan)
|
||||
irq_enable |= IRQENB_FIFO1THRES;
|
||||
tiadc_writel(adc_dev, REG_IRQENABLE, irq_enable);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -239,12 +328,18 @@ static int tiadc_buffer_postenable(struct iio_dev *indio_dev)
|
|||
static int tiadc_buffer_predisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct tiadc_device *adc_dev = iio_priv(indio_dev);
|
||||
struct tiadc_dma *dma = &adc_dev->dma;
|
||||
int fifo1count, i, read;
|
||||
|
||||
tiadc_writel(adc_dev, REG_IRQCLR, (IRQENB_FIFO1THRES |
|
||||
IRQENB_FIFO1OVRRUN | IRQENB_FIFO1UNDRFLW));
|
||||
am335x_tsc_se_clr(adc_dev->mfd_tscadc, adc_dev->buffer_en_ch_steps);
|
||||
adc_dev->buffer_en_ch_steps = 0;
|
||||
adc_dev->total_ch_enabled = 0;
|
||||
if (dma->chan) {
|
||||
tiadc_writel(adc_dev, REG_DMAENABLE_CLEAR, 0x2);
|
||||
dmaengine_terminate_async(dma->chan);
|
||||
}
|
||||
|
||||
/* Flush FIFO of leftover data in the time it takes to disable adc */
|
||||
fifo1count = tiadc_readl(adc_dev, REG_FIFO1CNT);
|
||||
|
@ -430,6 +525,41 @@ static const struct iio_info tiadc_info = {
|
|||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int tiadc_request_dma(struct platform_device *pdev,
|
||||
struct tiadc_device *adc_dev)
|
||||
{
|
||||
struct tiadc_dma *dma = &adc_dev->dma;
|
||||
dma_cap_mask_t mask;
|
||||
|
||||
/* Default slave configuration parameters */
|
||||
dma->conf.direction = DMA_DEV_TO_MEM;
|
||||
dma->conf.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
||||
dma->conf.src_addr = adc_dev->mfd_tscadc->tscadc_phys_base + REG_FIFO1;
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_CYCLIC, mask);
|
||||
|
||||
/* Get a channel for RX */
|
||||
dma->chan = dma_request_chan(adc_dev->mfd_tscadc->dev, "fifo1");
|
||||
if (IS_ERR(dma->chan)) {
|
||||
int ret = PTR_ERR(dma->chan);
|
||||
|
||||
dma->chan = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* RX buffer */
|
||||
dma->buf = dma_alloc_coherent(dma->chan->device->dev, DMA_BUFFER_SIZE,
|
||||
&dma->addr, GFP_KERNEL);
|
||||
if (!dma->buf)
|
||||
goto err;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
dma_release_channel(dma->chan);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static int tiadc_parse_dt(struct platform_device *pdev,
|
||||
struct tiadc_device *adc_dev)
|
||||
{
|
||||
|
@ -512,8 +642,14 @@ static int tiadc_probe(struct platform_device *pdev)
|
|||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
err = tiadc_request_dma(pdev, adc_dev);
|
||||
if (err && err == -EPROBE_DEFER)
|
||||
goto err_dma;
|
||||
|
||||
return 0;
|
||||
|
||||
err_dma:
|
||||
iio_device_unregister(indio_dev);
|
||||
err_buffer_unregister:
|
||||
tiadc_iio_buffered_hardware_remove(indio_dev);
|
||||
err_free_channels:
|
||||
|
@ -525,8 +661,14 @@ static int tiadc_remove(struct platform_device *pdev)
|
|||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct tiadc_device *adc_dev = iio_priv(indio_dev);
|
||||
struct tiadc_dma *dma = &adc_dev->dma;
|
||||
u32 step_en;
|
||||
|
||||
if (dma->chan) {
|
||||
dma_free_coherent(dma->chan->device->dev, DMA_BUFFER_SIZE,
|
||||
dma->buf, dma->addr);
|
||||
dma_release_channel(dma->chan);
|
||||
}
|
||||
iio_device_unregister(indio_dev);
|
||||
tiadc_iio_buffered_hardware_remove(indio_dev);
|
||||
tiadc_channels_remove(indio_dev);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# IIO common modules
|
||||
#
|
||||
|
||||
source "drivers/iio/common/cros_ec_sensors/Kconfig"
|
||||
source "drivers/iio/common/hid-sensors/Kconfig"
|
||||
source "drivers/iio/common/ms_sensors/Kconfig"
|
||||
source "drivers/iio/common/ssp_sensors/Kconfig"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#
|
||||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
obj-y += cros_ec_sensors/
|
||||
obj-y += hid-sensors/
|
||||
obj-y += ms_sensors/
|
||||
obj-y += ssp_sensors/
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
#
|
||||
# Chrome OS Embedded Controller managed sensors library
|
||||
#
|
||||
config IIO_CROS_EC_SENSORS_CORE
|
||||
tristate "ChromeOS EC Sensors Core"
|
||||
depends on SYSFS && MFD_CROS_EC
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
Base module for the ChromeOS EC Sensors module.
|
||||
Contains core functions used by other IIO CrosEC sensor
|
||||
drivers.
|
||||
Define common attributes and sysfs interrupt handler.
|
||||
|
||||
config IIO_CROS_EC_SENSORS
|
||||
tristate "ChromeOS EC Contiguous Sensors"
|
||||
depends on IIO_CROS_EC_SENSORS_CORE
|
||||
help
|
||||
Module to handle 3d contiguous sensors like
|
||||
Accelerometers, Gyroscope and Magnetometer that are
|
||||
presented by the ChromeOS EC Sensor hub.
|
||||
Creates an IIO device for each functions.
|
|
@ -0,0 +1,6 @@
|
|||
#
|
||||
# Makefile for sensors seen through the ChromeOS EC sensor hub.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_IIO_CROS_EC_SENSORS_CORE) += cros_ec_sensors_core.o
|
||||
obj-$(CONFIG_IIO_CROS_EC_SENSORS) += cros_ec_sensors.o
|
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
* cros_ec_sensors - Driver for Chrome OS Embedded Controller sensors.
|
||||
*
|
||||
* Copyright (C) 2016 Google, Inc
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This driver uses the cros-ec interface to communicate with the Chrome OS
|
||||
* EC about sensors data. Data access is presented through iio sysfs.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/cros_ec.h>
|
||||
#include <linux/mfd/cros_ec_commands.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#include "cros_ec_sensors_core.h"
|
||||
|
||||
#define CROS_EC_SENSORS_MAX_CHANNELS 4
|
||||
|
||||
/* State data for ec_sensors iio driver. */
|
||||
struct cros_ec_sensors_state {
|
||||
/* Shared by all sensors */
|
||||
struct cros_ec_sensors_core_state core;
|
||||
|
||||
struct iio_chan_spec channels[CROS_EC_SENSORS_MAX_CHANNELS];
|
||||
};
|
||||
|
||||
static int cros_ec_sensors_read(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct cros_ec_sensors_state *st = iio_priv(indio_dev);
|
||||
s16 data = 0;
|
||||
s64 val64;
|
||||
int i;
|
||||
int ret;
|
||||
int idx = chan->scan_index;
|
||||
|
||||
mutex_lock(&st->core.cmd_lock);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = st->core.read_ec_sensors_data(indio_dev, 1 << idx, &data);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
*val = data;
|
||||
break;
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET;
|
||||
st->core.param.sensor_offset.flags = 0;
|
||||
|
||||
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
/* Save values */
|
||||
for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++)
|
||||
st->core.calib[i] =
|
||||
st->core.resp->sensor_offset.offset[i];
|
||||
|
||||
*val = st->core.calib[idx];
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE;
|
||||
st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE;
|
||||
|
||||
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
val64 = st->core.resp->sensor_range.ret;
|
||||
switch (st->core.type) {
|
||||
case MOTIONSENSE_TYPE_ACCEL:
|
||||
/*
|
||||
* EC returns data in g, iio exepects m/s^2.
|
||||
* Do not use IIO_G_TO_M_S_2 to avoid precision loss.
|
||||
*/
|
||||
*val = div_s64(val64 * 980665, 10);
|
||||
*val2 = 10000 << (CROS_EC_SENSOR_BITS - 1);
|
||||
ret = IIO_VAL_FRACTIONAL;
|
||||
break;
|
||||
case MOTIONSENSE_TYPE_GYRO:
|
||||
/*
|
||||
* EC returns data in dps, iio expects rad/s.
|
||||
* Do not use IIO_DEGREE_TO_RAD to avoid precision
|
||||
* loss. Round to the nearest integer.
|
||||
*/
|
||||
*val = div_s64(val64 * 314159 + 9000000ULL, 1000);
|
||||
*val2 = 18000 << (CROS_EC_SENSOR_BITS - 1);
|
||||
ret = IIO_VAL_FRACTIONAL;
|
||||
break;
|
||||
case MOTIONSENSE_TYPE_MAG:
|
||||
/*
|
||||
* EC returns data in 16LSB / uT,
|
||||
* iio expects Gauss
|
||||
*/
|
||||
*val = val64;
|
||||
*val2 = 100 << (CROS_EC_SENSOR_BITS - 1);
|
||||
ret = IIO_VAL_FRACTIONAL;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = cros_ec_sensors_core_read(&st->core, chan, val, val2,
|
||||
mask);
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&st->core.cmd_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cros_ec_sensors_write(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct cros_ec_sensors_state *st = iio_priv(indio_dev);
|
||||
int i;
|
||||
int ret;
|
||||
int idx = chan->scan_index;
|
||||
|
||||
mutex_lock(&st->core.cmd_lock);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
st->core.calib[idx] = val;
|
||||
|
||||
/* Send to EC for each axis, even if not complete */
|
||||
st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET;
|
||||
st->core.param.sensor_offset.flags =
|
||||
MOTION_SENSE_SET_OFFSET;
|
||||
for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++)
|
||||
st->core.param.sensor_offset.offset[i] =
|
||||
st->core.calib[i];
|
||||
st->core.param.sensor_offset.temp =
|
||||
EC_MOTION_SENSE_INVALID_CALIB_TEMP;
|
||||
|
||||
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
if (st->core.type == MOTIONSENSE_TYPE_MAG) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE;
|
||||
st->core.param.sensor_range.data = val;
|
||||
|
||||
/* Always roundup, so caller gets at least what it asks for. */
|
||||
st->core.param.sensor_range.roundup = 1;
|
||||
|
||||
ret = cros_ec_motion_send_host_cmd(&st->core, 0);
|
||||
break;
|
||||
default:
|
||||
ret = cros_ec_sensors_core_write(
|
||||
&st->core, chan, val, val2, mask);
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&st->core.cmd_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info ec_sensors_info = {
|
||||
.read_raw = &cros_ec_sensors_read,
|
||||
.write_raw = &cros_ec_sensors_write,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int cros_ec_sensors_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
|
||||
struct cros_ec_device *ec_device;
|
||||
struct iio_dev *indio_dev;
|
||||
struct cros_ec_sensors_state *state;
|
||||
struct iio_chan_spec *channel;
|
||||
int ret, i;
|
||||
|
||||
if (!ec_dev || !ec_dev->ec_dev) {
|
||||
dev_warn(&pdev->dev, "No CROS EC device found.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
ec_device = ec_dev->ec_dev;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = cros_ec_sensors_core_init(pdev, indio_dev, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
indio_dev->info = &ec_sensors_info;
|
||||
state = iio_priv(indio_dev);
|
||||
for (channel = state->channels, i = CROS_EC_SENSOR_X;
|
||||
i < CROS_EC_SENSOR_MAX_AXIS; i++, channel++) {
|
||||
/* Common part */
|
||||
channel->info_mask_separate =
|
||||
BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_CALIBBIAS);
|
||||
channel->info_mask_shared_by_all =
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_FREQUENCY) |
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ);
|
||||
channel->scan_type.realbits = CROS_EC_SENSOR_BITS;
|
||||
channel->scan_type.storagebits = CROS_EC_SENSOR_BITS;
|
||||
channel->scan_index = i;
|
||||
channel->ext_info = cros_ec_sensors_ext_info;
|
||||
channel->modified = 1;
|
||||
channel->channel2 = IIO_MOD_X + i;
|
||||
channel->scan_type.sign = 's';
|
||||
|
||||
/* Sensor specific */
|
||||
switch (state->core.type) {
|
||||
case MOTIONSENSE_TYPE_ACCEL:
|
||||
channel->type = IIO_ACCEL;
|
||||
break;
|
||||
case MOTIONSENSE_TYPE_GYRO:
|
||||
channel->type = IIO_ANGL_VEL;
|
||||
break;
|
||||
case MOTIONSENSE_TYPE_MAG:
|
||||
channel->type = IIO_MAGN;
|
||||
break;
|
||||
default:
|
||||
dev_err(&pdev->dev, "Unknown motion sensor\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Timestamp */
|
||||
channel->type = IIO_TIMESTAMP;
|
||||
channel->channel = -1;
|
||||
channel->scan_index = CROS_EC_SENSOR_MAX_AXIS;
|
||||
channel->scan_type.sign = 's';
|
||||
channel->scan_type.realbits = 64;
|
||||
channel->scan_type.storagebits = 64;
|
||||
|
||||
indio_dev->channels = state->channels;
|
||||
indio_dev->num_channels = CROS_EC_SENSORS_MAX_CHANNELS;
|
||||
|
||||
/* There is only enough room for accel and gyro in the io space */
|
||||
if ((state->core.ec->cmd_readmem != NULL) &&
|
||||
(state->core.type != MOTIONSENSE_TYPE_MAG))
|
||||
state->core.read_ec_sensors_data = cros_ec_sensors_read_lpc;
|
||||
else
|
||||
state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd;
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
cros_ec_sensors_capture, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret)
|
||||
goto error_uninit_buffer;
|
||||
|
||||
return 0;
|
||||
|
||||
error_uninit_buffer:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cros_ec_sensors_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id cros_ec_sensors_ids[] = {
|
||||
{
|
||||
.name = "cros-ec-accel",
|
||||
},
|
||||
{
|
||||
.name = "cros-ec-gyro",
|
||||
},
|
||||
{
|
||||
.name = "cros-ec-mag",
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, cros_ec_sensors_ids);
|
||||
|
||||
static struct platform_driver cros_ec_sensors_platform_driver = {
|
||||
.driver = {
|
||||
.name = "cros-ec-sensors",
|
||||
},
|
||||
.probe = cros_ec_sensors_probe,
|
||||
.remove = cros_ec_sensors_remove,
|
||||
.id_table = cros_ec_sensors_ids,
|
||||
};
|
||||
module_platform_driver(cros_ec_sensors_platform_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ChromeOS EC 3-axis sensors driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,450 @@
|
|||
/*
|
||||
* cros_ec_sensors_core - Common function for Chrome OS EC sensor driver.
|
||||
*
|
||||
* Copyright (C) 2016 Google, Inc
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/cros_ec.h>
|
||||
#include <linux/mfd/cros_ec_commands.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "cros_ec_sensors_core.h"
|
||||
|
||||
static char *cros_ec_loc[] = {
|
||||
[MOTIONSENSE_LOC_BASE] = "base",
|
||||
[MOTIONSENSE_LOC_LID] = "lid",
|
||||
[MOTIONSENSE_LOC_MAX] = "unknown",
|
||||
};
|
||||
|
||||
int cros_ec_sensors_core_init(struct platform_device *pdev,
|
||||
struct iio_dev *indio_dev,
|
||||
bool physical_device)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cros_ec_sensors_core_state *state = iio_priv(indio_dev);
|
||||
struct cros_ec_dev *ec = dev_get_drvdata(pdev->dev.parent);
|
||||
struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev);
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
state->ec = ec->ec_dev;
|
||||
state->msg = devm_kzalloc(&pdev->dev,
|
||||
max((u16)sizeof(struct ec_params_motion_sense),
|
||||
state->ec->max_response), GFP_KERNEL);
|
||||
if (!state->msg)
|
||||
return -ENOMEM;
|
||||
|
||||
state->resp = (struct ec_response_motion_sense *)state->msg->data;
|
||||
|
||||
mutex_init(&state->cmd_lock);
|
||||
|
||||
/* Set up the host command structure. */
|
||||
state->msg->version = 2;
|
||||
state->msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
|
||||
state->msg->outsize = sizeof(struct ec_params_motion_sense);
|
||||
|
||||
indio_dev->dev.parent = &pdev->dev;
|
||||
indio_dev->name = pdev->name;
|
||||
|
||||
if (physical_device) {
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
state->param.cmd = MOTIONSENSE_CMD_INFO;
|
||||
state->param.info.sensor_num = sensor_platform->sensor_num;
|
||||
if (cros_ec_motion_send_host_cmd(state, 0)) {
|
||||
dev_warn(dev, "Can not access sensor info\n");
|
||||
return -EIO;
|
||||
}
|
||||
state->type = state->resp->info.type;
|
||||
state->loc = state->resp->info.location;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cros_ec_sensors_core_init);
|
||||
|
||||
int cros_ec_motion_send_host_cmd(struct cros_ec_sensors_core_state *state,
|
||||
u16 opt_length)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (opt_length)
|
||||
state->msg->insize = min(opt_length, state->ec->max_response);
|
||||
else
|
||||
state->msg->insize = state->ec->max_response;
|
||||
|
||||
memcpy(state->msg->data, &state->param, sizeof(state->param));
|
||||
|
||||
ret = cros_ec_cmd_xfer_status(state->ec, state->msg);
|
||||
if (ret < 0)
|
||||
return -EIO;
|
||||
|
||||
if (ret &&
|
||||
state->resp != (struct ec_response_motion_sense *)state->msg->data)
|
||||
memcpy(state->resp, state->msg->data, ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cros_ec_motion_send_host_cmd);
|
||||
|
||||
static ssize_t cros_ec_sensors_calibrate(struct iio_dev *indio_dev,
|
||||
uintptr_t private, const struct iio_chan_spec *chan,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
|
||||
int ret, i;
|
||||
bool calibrate;
|
||||
|
||||
ret = strtobool(buf, &calibrate);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!calibrate)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&st->cmd_lock);
|
||||
st->param.cmd = MOTIONSENSE_CMD_PERFORM_CALIB;
|
||||
ret = cros_ec_motion_send_host_cmd(st, 0);
|
||||
if (ret != 0) {
|
||||
dev_warn(&indio_dev->dev, "Unable to calibrate sensor\n");
|
||||
} else {
|
||||
/* Save values */
|
||||
for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++)
|
||||
st->calib[i] = st->resp->perform_calib.offset[i];
|
||||
}
|
||||
mutex_unlock(&st->cmd_lock);
|
||||
|
||||
return ret ? ret : len;
|
||||
}
|
||||
|
||||
static ssize_t cros_ec_sensors_loc(struct iio_dev *indio_dev,
|
||||
uintptr_t private, const struct iio_chan_spec *chan,
|
||||
char *buf)
|
||||
{
|
||||
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%s\n", cros_ec_loc[st->loc]);
|
||||
}
|
||||
|
||||
const struct iio_chan_spec_ext_info cros_ec_sensors_ext_info[] = {
|
||||
{
|
||||
.name = "calibrate",
|
||||
.shared = IIO_SHARED_BY_ALL,
|
||||
.write = cros_ec_sensors_calibrate
|
||||
},
|
||||
{
|
||||
.name = "location",
|
||||
.shared = IIO_SHARED_BY_ALL,
|
||||
.read = cros_ec_sensors_loc
|
||||
},
|
||||
{ },
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(cros_ec_sensors_ext_info);
|
||||
|
||||
/**
|
||||
* cros_ec_sensors_idx_to_reg - convert index into offset in shared memory
|
||||
* @st: pointer to state information for device
|
||||
* @idx: sensor index (should be element of enum sensor_index)
|
||||
*
|
||||
* Return: address to read at
|
||||
*/
|
||||
static unsigned int cros_ec_sensors_idx_to_reg(
|
||||
struct cros_ec_sensors_core_state *st,
|
||||
unsigned int idx)
|
||||
{
|
||||
/*
|
||||
* When using LPC interface, only space for 2 Accel and one Gyro.
|
||||
* First halfword of MOTIONSENSE_TYPE_ACCEL is used by angle.
|
||||
*/
|
||||
if (st->type == MOTIONSENSE_TYPE_ACCEL)
|
||||
return EC_MEMMAP_ACC_DATA + sizeof(u16) *
|
||||
(1 + idx + st->param.info.sensor_num *
|
||||
CROS_EC_SENSOR_MAX_AXIS);
|
||||
|
||||
return EC_MEMMAP_GYRO_DATA + sizeof(u16) * idx;
|
||||
}
|
||||
|
||||
static int cros_ec_sensors_cmd_read_u8(struct cros_ec_device *ec,
|
||||
unsigned int offset, u8 *dest)
|
||||
{
|
||||
return ec->cmd_readmem(ec, offset, 1, dest);
|
||||
}
|
||||
|
||||
static int cros_ec_sensors_cmd_read_u16(struct cros_ec_device *ec,
|
||||
unsigned int offset, u16 *dest)
|
||||
{
|
||||
__le16 tmp;
|
||||
int ret = ec->cmd_readmem(ec, offset, 2, &tmp);
|
||||
|
||||
if (ret >= 0)
|
||||
*dest = le16_to_cpu(tmp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cros_ec_sensors_read_until_not_busy() - read until is not busy
|
||||
*
|
||||
* @st: pointer to state information for device
|
||||
*
|
||||
* Read from EC status byte until it reads not busy.
|
||||
* Return: 8-bit status if ok, -errno on failure.
|
||||
*/
|
||||
static int cros_ec_sensors_read_until_not_busy(
|
||||
struct cros_ec_sensors_core_state *st)
|
||||
{
|
||||
struct cros_ec_device *ec = st->ec;
|
||||
u8 status;
|
||||
int ret, attempts = 0;
|
||||
|
||||
ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, &status);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
while (status & EC_MEMMAP_ACC_STATUS_BUSY_BIT) {
|
||||
/* Give up after enough attempts, return error. */
|
||||
if (attempts++ >= 50)
|
||||
return -EIO;
|
||||
|
||||
/* Small delay every so often. */
|
||||
if (attempts % 5 == 0)
|
||||
msleep(25);
|
||||
|
||||
ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS,
|
||||
&status);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* read_ec_sensors_data_unsafe() - read acceleration data from EC shared memory
|
||||
* @indio_dev: pointer to IIO device
|
||||
* @scan_mask: bitmap of the sensor indices to scan
|
||||
* @data: location to store data
|
||||
*
|
||||
* This is the unsafe function for reading the EC data. It does not guarantee
|
||||
* that the EC will not modify the data as it is being read in.
|
||||
*
|
||||
* Return: 0 on success, -errno on failure.
|
||||
*/
|
||||
static int cros_ec_sensors_read_data_unsafe(struct iio_dev *indio_dev,
|
||||
unsigned long scan_mask, s16 *data)
|
||||
{
|
||||
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
|
||||
struct cros_ec_device *ec = st->ec;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
/* Read all sensors enabled in scan_mask. Each value is 2 bytes. */
|
||||
for_each_set_bit(i, &scan_mask, indio_dev->masklength) {
|
||||
ret = cros_ec_sensors_cmd_read_u16(ec,
|
||||
cros_ec_sensors_idx_to_reg(st, i),
|
||||
data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cros_ec_sensors_read_lpc(struct iio_dev *indio_dev,
|
||||
unsigned long scan_mask, s16 *data)
|
||||
{
|
||||
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
|
||||
struct cros_ec_device *ec = st->ec;
|
||||
u8 samp_id = 0xff, status = 0;
|
||||
int ret, attempts = 0;
|
||||
|
||||
/*
|
||||
* Continually read all data from EC until the status byte after
|
||||
* all reads reflects that the EC is not busy and the sample id
|
||||
* matches the sample id from before all reads. This guarantees
|
||||
* that data read in was not modified by the EC while reading.
|
||||
*/
|
||||
while ((status & (EC_MEMMAP_ACC_STATUS_BUSY_BIT |
|
||||
EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK)) != samp_id) {
|
||||
/* If we have tried to read too many times, return error. */
|
||||
if (attempts++ >= 5)
|
||||
return -EIO;
|
||||
|
||||
/* Read status byte until EC is not busy. */
|
||||
ret = cros_ec_sensors_read_until_not_busy(st);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Store the current sample id so that we can compare to the
|
||||
* sample id after reading the data.
|
||||
*/
|
||||
samp_id = ret & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK;
|
||||
|
||||
/* Read all EC data, format it, and store it into data. */
|
||||
ret = cros_ec_sensors_read_data_unsafe(indio_dev, scan_mask,
|
||||
data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Read status byte. */
|
||||
ret = cros_ec_sensors_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS,
|
||||
&status);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cros_ec_sensors_read_lpc);
|
||||
|
||||
int cros_ec_sensors_read_cmd(struct iio_dev *indio_dev,
|
||||
unsigned long scan_mask, s16 *data)
|
||||
{
|
||||
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
unsigned int i;
|
||||
|
||||
/* Read all sensor data through a command. */
|
||||
st->param.cmd = MOTIONSENSE_CMD_DATA;
|
||||
ret = cros_ec_motion_send_host_cmd(st, sizeof(st->resp->data));
|
||||
if (ret != 0) {
|
||||
dev_warn(&indio_dev->dev, "Unable to read sensor data\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for_each_set_bit(i, &scan_mask, indio_dev->masklength) {
|
||||
*data = st->resp->data.data[i];
|
||||
data++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cros_ec_sensors_read_cmd);
|
||||
|
||||
irqreturn_t cros_ec_sensors_capture(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&st->cmd_lock);
|
||||
|
||||
/* Clear capture data. */
|
||||
memset(st->samples, 0, indio_dev->scan_bytes);
|
||||
|
||||
/* Read data based on which channels are enabled in scan mask. */
|
||||
ret = st->read_ec_sensors_data(indio_dev,
|
||||
*(indio_dev->active_scan_mask),
|
||||
(s16 *)st->samples);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, st->samples,
|
||||
iio_get_time_ns(indio_dev));
|
||||
|
||||
done:
|
||||
/*
|
||||
* Tell the core we are done with this trigger and ready for the
|
||||
* next one.
|
||||
*/
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
mutex_unlock(&st->cmd_lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cros_ec_sensors_capture);
|
||||
|
||||
int cros_ec_sensors_core_read(struct cros_ec_sensors_core_state *st,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
int ret = IIO_VAL_INT;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
st->param.cmd = MOTIONSENSE_CMD_EC_RATE;
|
||||
st->param.ec_rate.data =
|
||||
EC_MOTION_SENSE_NO_VALUE;
|
||||
|
||||
if (cros_ec_motion_send_host_cmd(st, 0))
|
||||
ret = -EIO;
|
||||
else
|
||||
*val = st->resp->ec_rate.ret;
|
||||
break;
|
||||
case IIO_CHAN_INFO_FREQUENCY:
|
||||
st->param.cmd = MOTIONSENSE_CMD_SENSOR_ODR;
|
||||
st->param.sensor_odr.data =
|
||||
EC_MOTION_SENSE_NO_VALUE;
|
||||
|
||||
if (cros_ec_motion_send_host_cmd(st, 0))
|
||||
ret = -EIO;
|
||||
else
|
||||
*val = st->resp->sensor_odr.ret;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cros_ec_sensors_core_read);
|
||||
|
||||
int cros_ec_sensors_core_write(struct cros_ec_sensors_core_state *st,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_FREQUENCY:
|
||||
st->param.cmd = MOTIONSENSE_CMD_SENSOR_ODR;
|
||||
st->param.sensor_odr.data = val;
|
||||
|
||||
/* Always roundup, so caller gets at least what it asks for. */
|
||||
st->param.sensor_odr.roundup = 1;
|
||||
|
||||
if (cros_ec_motion_send_host_cmd(st, 0))
|
||||
ret = -EIO;
|
||||
break;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
st->param.cmd = MOTIONSENSE_CMD_EC_RATE;
|
||||
st->param.ec_rate.data = val;
|
||||
|
||||
if (cros_ec_motion_send_host_cmd(st, 0))
|
||||
ret = -EIO;
|
||||
else
|
||||
st->curr_sampl_freq = val;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cros_ec_sensors_core_write);
|
||||
|
||||
MODULE_DESCRIPTION("ChromeOS EC sensor hub core functions");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* ChromeOS EC sensor hub
|
||||
*
|
||||
* Copyright (C) 2016 Google, Inc
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef __CROS_EC_SENSORS_CORE_H
|
||||
#define __CROS_EC_SENSORS_CORE_H
|
||||
|
||||
#include <linux/irqreturn.h>
|
||||
|
||||
enum {
|
||||
CROS_EC_SENSOR_X,
|
||||
CROS_EC_SENSOR_Y,
|
||||
CROS_EC_SENSOR_Z,
|
||||
CROS_EC_SENSOR_MAX_AXIS,
|
||||
};
|
||||
|
||||
/* EC returns sensor values using signed 16 bit registers */
|
||||
#define CROS_EC_SENSOR_BITS 16
|
||||
|
||||
/*
|
||||
* 4 16 bit channels are allowed.
|
||||
* Good enough for current sensors, they use up to 3 16 bit vectors.
|
||||
*/
|
||||
#define CROS_EC_SAMPLE_SIZE (sizeof(s64) * 2)
|
||||
|
||||
/* Minimum sampling period to use when device is suspending */
|
||||
#define CROS_EC_MIN_SUSPEND_SAMPLING_FREQUENCY 1000 /* 1 second */
|
||||
|
||||
/**
|
||||
* struct cros_ec_sensors_core_state - state data for EC sensors IIO driver
|
||||
* @ec: cros EC device structure
|
||||
* @cmd_lock: lock used to prevent simultaneous access to the
|
||||
* commands.
|
||||
* @msg: cros EC command structure
|
||||
* @param: motion sensor parameters structure
|
||||
* @resp: motion sensor response structure
|
||||
* @type: type of motion sensor
|
||||
* @loc: location where the motion sensor is placed
|
||||
* @calib: calibration parameters. Note that trigger
|
||||
* captured data will always provide the calibrated
|
||||
* data
|
||||
* @samples: static array to hold data from a single capture.
|
||||
* For each channel we need 2 bytes, except for
|
||||
* the timestamp. The timestamp is always last and
|
||||
* is always 8-byte aligned.
|
||||
* @read_ec_sensors_data: function used for accessing sensors values
|
||||
* @cuur_sampl_freq: current sampling period
|
||||
*/
|
||||
struct cros_ec_sensors_core_state {
|
||||
struct cros_ec_device *ec;
|
||||
struct mutex cmd_lock;
|
||||
|
||||
struct cros_ec_command *msg;
|
||||
struct ec_params_motion_sense param;
|
||||
struct ec_response_motion_sense *resp;
|
||||
|
||||
enum motionsensor_type type;
|
||||
enum motionsensor_location loc;
|
||||
|
||||
s16 calib[CROS_EC_SENSOR_MAX_AXIS];
|
||||
|
||||
u8 samples[CROS_EC_SAMPLE_SIZE];
|
||||
|
||||
int (*read_ec_sensors_data)(struct iio_dev *indio_dev,
|
||||
unsigned long scan_mask, s16 *data);
|
||||
|
||||
int curr_sampl_freq;
|
||||
};
|
||||
|
||||
/**
|
||||
* cros_ec_sensors_read_lpc() - retrieve data from EC shared memory
|
||||
* @indio_dev: pointer to IIO device
|
||||
* @scan_mask: bitmap of the sensor indices to scan
|
||||
* @data: location to store data
|
||||
*
|
||||
* This is the safe function for reading the EC data. It guarantees that the
|
||||
* data sampled was not modified by the EC while being read.
|
||||
*
|
||||
* Return: 0 on success, -errno on failure.
|
||||
*/
|
||||
int cros_ec_sensors_read_lpc(struct iio_dev *indio_dev, unsigned long scan_mask,
|
||||
s16 *data);
|
||||
|
||||
/**
|
||||
* cros_ec_sensors_read_cmd() - retrieve data using the EC command protocol
|
||||
* @indio_dev: pointer to IIO device
|
||||
* @scan_mask: bitmap of the sensor indices to scan
|
||||
* @data: location to store data
|
||||
*
|
||||
* Return: 0 on success, -errno on failure.
|
||||
*/
|
||||
int cros_ec_sensors_read_cmd(struct iio_dev *indio_dev, unsigned long scan_mask,
|
||||
s16 *data);
|
||||
|
||||
/**
|
||||
* cros_ec_sensors_core_init() - basic initialization of the core structure
|
||||
* @pdev: platform device created for the sensors
|
||||
* @indio_dev: iio device structure of the device
|
||||
* @physical_device: true if the device refers to a physical device
|
||||
*
|
||||
* Return: 0 on success, -errno on failure.
|
||||
*/
|
||||
int cros_ec_sensors_core_init(struct platform_device *pdev,
|
||||
struct iio_dev *indio_dev, bool physical_device);
|
||||
|
||||
/**
|
||||
* cros_ec_sensors_capture() - the trigger handler function
|
||||
* @irq: the interrupt number.
|
||||
* @p: a pointer to the poll function.
|
||||
*
|
||||
* On a trigger event occurring, if the pollfunc is attached then this
|
||||
* handler is called as a threaded interrupt (and hence may sleep). It
|
||||
* is responsible for grabbing data from the device and pushing it into
|
||||
* the associated buffer.
|
||||
*
|
||||
* Return: IRQ_HANDLED
|
||||
*/
|
||||
irqreturn_t cros_ec_sensors_capture(int irq, void *p);
|
||||
|
||||
/**
|
||||
* cros_ec_motion_send_host_cmd() - send motion sense host command
|
||||
* @st: pointer to state information for device
|
||||
* @opt_length: optional length to reduce the response size, useful on the data
|
||||
* path. Otherwise, the maximal allowed response size is used
|
||||
*
|
||||
* When called, the sub-command is assumed to be set in param->cmd.
|
||||
*
|
||||
* Return: 0 on success, -errno on failure.
|
||||
*/
|
||||
int cros_ec_motion_send_host_cmd(struct cros_ec_sensors_core_state *st,
|
||||
u16 opt_length);
|
||||
|
||||
/**
|
||||
* cros_ec_sensors_core_read() - function to request a value from the sensor
|
||||
* @st: pointer to state information for device
|
||||
* @chan: channel specification structure table
|
||||
* @val: will contain one element making up the returned value
|
||||
* @val2: will contain another element making up the returned value
|
||||
* @mask: specifies which values to be requested
|
||||
*
|
||||
* Return: the type of value returned by the device
|
||||
*/
|
||||
int cros_ec_sensors_core_read(struct cros_ec_sensors_core_state *st,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask);
|
||||
|
||||
/**
|
||||
* cros_ec_sensors_core_write() - function to write a value to the sensor
|
||||
* @st: pointer to state information for device
|
||||
* @chan: channel specification structure table
|
||||
* @val: first part of value to write
|
||||
* @val2: second part of value to write
|
||||
* @mask: specifies which values to write
|
||||
*
|
||||
* Return: the type of value returned by the device
|
||||
*/
|
||||
int cros_ec_sensors_core_write(struct cros_ec_sensors_core_state *st,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask);
|
||||
|
||||
/* List of extended channel specification for all sensors */
|
||||
extern const struct iio_chan_spec_ext_info cros_ec_sensors_ext_info[];
|
||||
|
||||
#endif /* __CROS_EC_SENSORS_CORE_H */
|
|
@ -201,7 +201,7 @@ int hid_sensor_write_samp_freq_value(struct hid_sensor_common *st,
|
|||
int ret;
|
||||
|
||||
if (val1 < 0 || val2 < 0)
|
||||
ret = -EINVAL;
|
||||
return -EINVAL;
|
||||
|
||||
value = val1 * pow_10(6) + val2;
|
||||
if (value) {
|
||||
|
@ -250,6 +250,9 @@ int hid_sensor_write_raw_hyst_value(struct hid_sensor_common *st,
|
|||
s32 value;
|
||||
int ret;
|
||||
|
||||
if (val1 < 0 || val2 < 0)
|
||||
return -EINVAL;
|
||||
|
||||
value = convert_to_vtf_format(st->sensitivity.size,
|
||||
st->sensitivity.unit_expo,
|
||||
val1, val2);
|
||||
|
|
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
* IIO driver for the ACCES 104-QUAD-8
|
||||
* Copyright (C) 2016 William Breathitt Gray
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* This driver supports the ACCES 104-QUAD-8 and ACCES 104-QUAD-4.
|
||||
*/
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/types.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/isa.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define QUAD8_EXTENT 32
|
||||
|
||||
static unsigned int base[max_num_isa_dev(QUAD8_EXTENT)];
|
||||
static unsigned int num_quad8;
|
||||
module_param_array(base, uint, &num_quad8, 0);
|
||||
MODULE_PARM_DESC(base, "ACCES 104-QUAD-8 base addresses");
|
||||
|
||||
#define QUAD8_NUM_COUNTERS 8
|
||||
|
||||
/**
|
||||
* struct quad8_iio - IIO device private data structure
|
||||
* @preset: array of preset values
|
||||
* @count_mode: array of count mode configurations
|
||||
* @quadrature_mode: array of quadrature mode configurations
|
||||
* @quadrature_scale: array of quadrature mode scale configurations
|
||||
* @ab_enable: array of A and B inputs enable configurations
|
||||
* @preset_enable: array of set_to_preset_on_index attribute configurations
|
||||
* @synchronous_mode: array of index function synchronous mode configurations
|
||||
* @index_polarity: array of index function polarity configurations
|
||||
* @base: base port address of the IIO device
|
||||
*/
|
||||
struct quad8_iio {
|
||||
unsigned int preset[QUAD8_NUM_COUNTERS];
|
||||
unsigned int count_mode[QUAD8_NUM_COUNTERS];
|
||||
unsigned int quadrature_mode[QUAD8_NUM_COUNTERS];
|
||||
unsigned int quadrature_scale[QUAD8_NUM_COUNTERS];
|
||||
unsigned int ab_enable[QUAD8_NUM_COUNTERS];
|
||||
unsigned int preset_enable[QUAD8_NUM_COUNTERS];
|
||||
unsigned int synchronous_mode[QUAD8_NUM_COUNTERS];
|
||||
unsigned int index_polarity[QUAD8_NUM_COUNTERS];
|
||||
unsigned int base;
|
||||
};
|
||||
|
||||
static int quad8_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val, int *val2, long mask)
|
||||
{
|
||||
struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
const int base_offset = priv->base + 2 * chan->channel;
|
||||
unsigned int flags;
|
||||
unsigned int borrow;
|
||||
unsigned int carry;
|
||||
int i;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (chan->type == IIO_INDEX) {
|
||||
*val = !!(inb(priv->base + 0x16) & BIT(chan->channel));
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
flags = inb(base_offset);
|
||||
borrow = flags & BIT(0);
|
||||
carry = !!(flags & BIT(1));
|
||||
|
||||
/* Borrow XOR Carry effectively doubles count range */
|
||||
*val = (borrow ^ carry) << 24;
|
||||
|
||||
/* Reset Byte Pointer; transfer Counter to Output Latch */
|
||||
outb(0x11, base_offset + 1);
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
*val |= (unsigned int)inb(base_offset) << (8 * i);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_ENABLE:
|
||||
*val = priv->ab_enable[chan->channel];
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 1;
|
||||
*val2 = priv->quadrature_scale[chan->channel];
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int quad8_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int val, int val2, long mask)
|
||||
{
|
||||
struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
const int base_offset = priv->base + 2 * chan->channel;
|
||||
int i;
|
||||
unsigned int ior_cfg;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (chan->type == IIO_INDEX)
|
||||
return -EINVAL;
|
||||
|
||||
/* Only 24-bit values are supported */
|
||||
if ((unsigned int)val > 0xFFFFFF)
|
||||
return -EINVAL;
|
||||
|
||||
/* Reset Byte Pointer */
|
||||
outb(0x01, base_offset + 1);
|
||||
|
||||
/* Counter can only be set via Preset Register */
|
||||
for (i = 0; i < 3; i++)
|
||||
outb(val >> (8 * i), base_offset);
|
||||
|
||||
/* Transfer Preset Register to Counter */
|
||||
outb(0x08, base_offset + 1);
|
||||
|
||||
/* Reset Byte Pointer */
|
||||
outb(0x01, base_offset + 1);
|
||||
|
||||
/* Set Preset Register back to original value */
|
||||
val = priv->preset[chan->channel];
|
||||
for (i = 0; i < 3; i++)
|
||||
outb(val >> (8 * i), base_offset);
|
||||
|
||||
/* Reset Borrow, Carry, Compare, and Sign flags */
|
||||
outb(0x02, base_offset + 1);
|
||||
/* Reset Error flag */
|
||||
outb(0x06, base_offset + 1);
|
||||
|
||||
return 0;
|
||||
case IIO_CHAN_INFO_ENABLE:
|
||||
/* only boolean values accepted */
|
||||
if (val < 0 || val > 1)
|
||||
return -EINVAL;
|
||||
|
||||
priv->ab_enable[chan->channel] = val;
|
||||
|
||||
ior_cfg = val | priv->preset_enable[chan->channel] << 1;
|
||||
|
||||
/* Load I/O control configuration */
|
||||
outb(0x40 | ior_cfg, base_offset);
|
||||
|
||||
return 0;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
/* Quadrature scaling only available in quadrature mode */
|
||||
if (!priv->quadrature_mode[chan->channel] && (val2 || val != 1))
|
||||
return -EINVAL;
|
||||
|
||||
/* Only three gain states (1, 0.5, 0.25) */
|
||||
if (val == 1 && !val2)
|
||||
priv->quadrature_scale[chan->channel] = 0;
|
||||
else if (!val)
|
||||
switch (val2) {
|
||||
case 500000:
|
||||
priv->quadrature_scale[chan->channel] = 1;
|
||||
break;
|
||||
case 250000:
|
||||
priv->quadrature_scale[chan->channel] = 2;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct iio_info quad8_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = quad8_read_raw,
|
||||
.write_raw = quad8_write_raw
|
||||
};
|
||||
|
||||
static ssize_t quad8_read_preset(struct iio_dev *indio_dev, uintptr_t private,
|
||||
const struct iio_chan_spec *chan, char *buf)
|
||||
{
|
||||
const struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", priv->preset[chan->channel]);
|
||||
}
|
||||
|
||||
static ssize_t quad8_write_preset(struct iio_dev *indio_dev, uintptr_t private,
|
||||
const struct iio_chan_spec *chan, const char *buf, size_t len)
|
||||
{
|
||||
struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
const int base_offset = priv->base + 2 * chan->channel;
|
||||
unsigned int preset;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
ret = kstrtouint(buf, 0, &preset);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Only 24-bit values are supported */
|
||||
if (preset > 0xFFFFFF)
|
||||
return -EINVAL;
|
||||
|
||||
priv->preset[chan->channel] = preset;
|
||||
|
||||
/* Reset Byte Pointer */
|
||||
outb(0x01, base_offset + 1);
|
||||
|
||||
/* Set Preset Register */
|
||||
for (i = 0; i < 3; i++)
|
||||
outb(preset >> (8 * i), base_offset);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t quad8_read_set_to_preset_on_index(struct iio_dev *indio_dev,
|
||||
uintptr_t private, const struct iio_chan_spec *chan, char *buf)
|
||||
{
|
||||
const struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
priv->preset_enable[chan->channel]);
|
||||
}
|
||||
|
||||
static ssize_t quad8_write_set_to_preset_on_index(struct iio_dev *indio_dev,
|
||||
uintptr_t private, const struct iio_chan_spec *chan, const char *buf,
|
||||
size_t len)
|
||||
{
|
||||
struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
const int base_offset = priv->base + 2 * chan->channel;
|
||||
bool preset_enable;
|
||||
int ret;
|
||||
unsigned int ior_cfg;
|
||||
|
||||
ret = kstrtobool(buf, &preset_enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv->preset_enable[chan->channel] = preset_enable;
|
||||
|
||||
ior_cfg = priv->ab_enable[chan->channel] |
|
||||
(unsigned int)preset_enable << 1;
|
||||
|
||||
/* Load I/O control configuration to Input / Output Control Register */
|
||||
outb(0x40 | ior_cfg, base_offset);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static const char *const quad8_noise_error_states[] = {
|
||||
"No excessive noise is present at the count inputs",
|
||||
"Excessive noise is present at the count inputs"
|
||||
};
|
||||
|
||||
static int quad8_get_noise_error(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan)
|
||||
{
|
||||
struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
const int base_offset = priv->base + 2 * chan->channel + 1;
|
||||
|
||||
return !!(inb(base_offset) & BIT(4));
|
||||
}
|
||||
|
||||
static const struct iio_enum quad8_noise_error_enum = {
|
||||
.items = quad8_noise_error_states,
|
||||
.num_items = ARRAY_SIZE(quad8_noise_error_states),
|
||||
.get = quad8_get_noise_error
|
||||
};
|
||||
|
||||
static const char *const quad8_count_direction_states[] = {
|
||||
"down",
|
||||
"up"
|
||||
};
|
||||
|
||||
static int quad8_get_count_direction(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan)
|
||||
{
|
||||
struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
const int base_offset = priv->base + 2 * chan->channel + 1;
|
||||
|
||||
return !!(inb(base_offset) & BIT(5));
|
||||
}
|
||||
|
||||
static const struct iio_enum quad8_count_direction_enum = {
|
||||
.items = quad8_count_direction_states,
|
||||
.num_items = ARRAY_SIZE(quad8_count_direction_states),
|
||||
.get = quad8_get_count_direction
|
||||
};
|
||||
|
||||
static const char *const quad8_count_modes[] = {
|
||||
"normal",
|
||||
"range limit",
|
||||
"non-recycle",
|
||||
"modulo-n"
|
||||
};
|
||||
|
||||
static int quad8_set_count_mode(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, unsigned int count_mode)
|
||||
{
|
||||
struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
unsigned int mode_cfg = count_mode << 1;
|
||||
const int base_offset = priv->base + 2 * chan->channel + 1;
|
||||
|
||||
priv->count_mode[chan->channel] = count_mode;
|
||||
|
||||
/* Add quadrature mode configuration */
|
||||
if (priv->quadrature_mode[chan->channel])
|
||||
mode_cfg |= (priv->quadrature_scale[chan->channel] + 1) << 3;
|
||||
|
||||
/* Load mode configuration to Counter Mode Register */
|
||||
outb(0x20 | mode_cfg, base_offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int quad8_get_count_mode(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan)
|
||||
{
|
||||
const struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
|
||||
return priv->count_mode[chan->channel];
|
||||
}
|
||||
|
||||
static const struct iio_enum quad8_count_mode_enum = {
|
||||
.items = quad8_count_modes,
|
||||
.num_items = ARRAY_SIZE(quad8_count_modes),
|
||||
.set = quad8_set_count_mode,
|
||||
.get = quad8_get_count_mode
|
||||
};
|
||||
|
||||
static const char *const quad8_synchronous_modes[] = {
|
||||
"non-synchronous",
|
||||
"synchronous"
|
||||
};
|
||||
|
||||
static int quad8_set_synchronous_mode(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, unsigned int synchronous_mode)
|
||||
{
|
||||
struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
const unsigned int idr_cfg = synchronous_mode |
|
||||
priv->index_polarity[chan->channel] << 1;
|
||||
const int base_offset = priv->base + 2 * chan->channel + 1;
|
||||
|
||||
/* Index function must be non-synchronous in non-quadrature mode */
|
||||
if (synchronous_mode && !priv->quadrature_mode[chan->channel])
|
||||
return -EINVAL;
|
||||
|
||||
priv->synchronous_mode[chan->channel] = synchronous_mode;
|
||||
|
||||
/* Load Index Control configuration to Index Control Register */
|
||||
outb(0x40 | idr_cfg, base_offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int quad8_get_synchronous_mode(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan)
|
||||
{
|
||||
const struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
|
||||
return priv->synchronous_mode[chan->channel];
|
||||
}
|
||||
|
||||
static const struct iio_enum quad8_synchronous_mode_enum = {
|
||||
.items = quad8_synchronous_modes,
|
||||
.num_items = ARRAY_SIZE(quad8_synchronous_modes),
|
||||
.set = quad8_set_synchronous_mode,
|
||||
.get = quad8_get_synchronous_mode
|
||||
};
|
||||
|
||||
static const char *const quad8_quadrature_modes[] = {
|
||||
"non-quadrature",
|
||||
"quadrature"
|
||||
};
|
||||
|
||||
static int quad8_set_quadrature_mode(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, unsigned int quadrature_mode)
|
||||
{
|
||||
struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
unsigned int mode_cfg = priv->count_mode[chan->channel] << 1;
|
||||
const int base_offset = priv->base + 2 * chan->channel + 1;
|
||||
|
||||
if (quadrature_mode)
|
||||
mode_cfg |= (priv->quadrature_scale[chan->channel] + 1) << 3;
|
||||
else {
|
||||
/* Quadrature scaling only available in quadrature mode */
|
||||
priv->quadrature_scale[chan->channel] = 0;
|
||||
|
||||
/* Synchronous function not supported in non-quadrature mode */
|
||||
if (priv->synchronous_mode[chan->channel])
|
||||
quad8_set_synchronous_mode(indio_dev, chan, 0);
|
||||
}
|
||||
|
||||
priv->quadrature_mode[chan->channel] = quadrature_mode;
|
||||
|
||||
/* Load mode configuration to Counter Mode Register */
|
||||
outb(0x20 | mode_cfg, base_offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int quad8_get_quadrature_mode(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan)
|
||||
{
|
||||
const struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
|
||||
return priv->quadrature_mode[chan->channel];
|
||||
}
|
||||
|
||||
static const struct iio_enum quad8_quadrature_mode_enum = {
|
||||
.items = quad8_quadrature_modes,
|
||||
.num_items = ARRAY_SIZE(quad8_quadrature_modes),
|
||||
.set = quad8_set_quadrature_mode,
|
||||
.get = quad8_get_quadrature_mode
|
||||
};
|
||||
|
||||
static const char *const quad8_index_polarity_modes[] = {
|
||||
"negative",
|
||||
"positive"
|
||||
};
|
||||
|
||||
static int quad8_set_index_polarity(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, unsigned int index_polarity)
|
||||
{
|
||||
struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
const unsigned int idr_cfg = priv->synchronous_mode[chan->channel] |
|
||||
index_polarity << 1;
|
||||
const int base_offset = priv->base + 2 * chan->channel + 1;
|
||||
|
||||
priv->index_polarity[chan->channel] = index_polarity;
|
||||
|
||||
/* Load Index Control configuration to Index Control Register */
|
||||
outb(0x40 | idr_cfg, base_offset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int quad8_get_index_polarity(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan)
|
||||
{
|
||||
const struct quad8_iio *const priv = iio_priv(indio_dev);
|
||||
|
||||
return priv->index_polarity[chan->channel];
|
||||
}
|
||||
|
||||
static const struct iio_enum quad8_index_polarity_enum = {
|
||||
.items = quad8_index_polarity_modes,
|
||||
.num_items = ARRAY_SIZE(quad8_index_polarity_modes),
|
||||
.set = quad8_set_index_polarity,
|
||||
.get = quad8_get_index_polarity
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec_ext_info quad8_count_ext_info[] = {
|
||||
{
|
||||
.name = "preset",
|
||||
.shared = IIO_SEPARATE,
|
||||
.read = quad8_read_preset,
|
||||
.write = quad8_write_preset
|
||||
},
|
||||
{
|
||||
.name = "set_to_preset_on_index",
|
||||
.shared = IIO_SEPARATE,
|
||||
.read = quad8_read_set_to_preset_on_index,
|
||||
.write = quad8_write_set_to_preset_on_index
|
||||
},
|
||||
IIO_ENUM("noise_error", IIO_SEPARATE, &quad8_noise_error_enum),
|
||||
IIO_ENUM_AVAILABLE("noise_error", &quad8_noise_error_enum),
|
||||
IIO_ENUM("count_direction", IIO_SEPARATE, &quad8_count_direction_enum),
|
||||
IIO_ENUM_AVAILABLE("count_direction", &quad8_count_direction_enum),
|
||||
IIO_ENUM("count_mode", IIO_SEPARATE, &quad8_count_mode_enum),
|
||||
IIO_ENUM_AVAILABLE("count_mode", &quad8_count_mode_enum),
|
||||
IIO_ENUM("quadrature_mode", IIO_SEPARATE, &quad8_quadrature_mode_enum),
|
||||
IIO_ENUM_AVAILABLE("quadrature_mode", &quad8_quadrature_mode_enum),
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec_ext_info quad8_index_ext_info[] = {
|
||||
IIO_ENUM("synchronous_mode", IIO_SEPARATE,
|
||||
&quad8_synchronous_mode_enum),
|
||||
IIO_ENUM_AVAILABLE("synchronous_mode", &quad8_synchronous_mode_enum),
|
||||
IIO_ENUM("index_polarity", IIO_SEPARATE, &quad8_index_polarity_enum),
|
||||
IIO_ENUM_AVAILABLE("index_polarity", &quad8_index_polarity_enum),
|
||||
{}
|
||||
};
|
||||
|
||||
#define QUAD8_COUNT_CHAN(_chan) { \
|
||||
.type = IIO_COUNT, \
|
||||
.channel = (_chan), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_ENABLE) | BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.ext_info = quad8_count_ext_info, \
|
||||
.indexed = 1 \
|
||||
}
|
||||
|
||||
#define QUAD8_INDEX_CHAN(_chan) { \
|
||||
.type = IIO_INDEX, \
|
||||
.channel = (_chan), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.ext_info = quad8_index_ext_info, \
|
||||
.indexed = 1 \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec quad8_channels[] = {
|
||||
QUAD8_COUNT_CHAN(0), QUAD8_INDEX_CHAN(0),
|
||||
QUAD8_COUNT_CHAN(1), QUAD8_INDEX_CHAN(1),
|
||||
QUAD8_COUNT_CHAN(2), QUAD8_INDEX_CHAN(2),
|
||||
QUAD8_COUNT_CHAN(3), QUAD8_INDEX_CHAN(3),
|
||||
QUAD8_COUNT_CHAN(4), QUAD8_INDEX_CHAN(4),
|
||||
QUAD8_COUNT_CHAN(5), QUAD8_INDEX_CHAN(5),
|
||||
QUAD8_COUNT_CHAN(6), QUAD8_INDEX_CHAN(6),
|
||||
QUAD8_COUNT_CHAN(7), QUAD8_INDEX_CHAN(7)
|
||||
};
|
||||
|
||||
static int quad8_probe(struct device *dev, unsigned int id)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
struct quad8_iio *priv;
|
||||
int i, j;
|
||||
unsigned int base_offset;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*priv));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!devm_request_region(dev, base[id], QUAD8_EXTENT,
|
||||
dev_name(dev))) {
|
||||
dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
|
||||
base[id], base[id] + QUAD8_EXTENT);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
indio_dev->info = &quad8_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->num_channels = ARRAY_SIZE(quad8_channels);
|
||||
indio_dev->channels = quad8_channels;
|
||||
indio_dev->name = dev_name(dev);
|
||||
|
||||
priv = iio_priv(indio_dev);
|
||||
priv->base = base[id];
|
||||
|
||||
/* Reset all counters and disable interrupt function */
|
||||
outb(0x01, base[id] + 0x11);
|
||||
/* Set initial configuration for all counters */
|
||||
for (i = 0; i < QUAD8_NUM_COUNTERS; i++) {
|
||||
base_offset = base[id] + 2 * i;
|
||||
/* Reset Byte Pointer */
|
||||
outb(0x01, base_offset + 1);
|
||||
/* Reset Preset Register */
|
||||
for (j = 0; j < 3; j++)
|
||||
outb(0x00, base_offset);
|
||||
/* Reset Borrow, Carry, Compare, and Sign flags */
|
||||
outb(0x04, base_offset + 1);
|
||||
/* Reset Error flag */
|
||||
outb(0x06, base_offset + 1);
|
||||
/* Binary encoding; Normal count; non-quadrature mode */
|
||||
outb(0x20, base_offset + 1);
|
||||
/* Disable A and B inputs; preset on index; FLG1 as Carry */
|
||||
outb(0x40, base_offset + 1);
|
||||
/* Disable index function; negative index polarity */
|
||||
outb(0x60, base_offset + 1);
|
||||
}
|
||||
/* Enable all counters */
|
||||
outb(0x00, base[id] + 0x11);
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
static struct isa_driver quad8_driver = {
|
||||
.probe = quad8_probe,
|
||||
.driver = {
|
||||
.name = "104-quad-8"
|
||||
}
|
||||
};
|
||||
|
||||
module_isa_driver(quad8_driver, num_quad8);
|
||||
|
||||
MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
|
||||
MODULE_DESCRIPTION("ACCES 104-QUAD-8 IIO driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,24 @@
|
|||
#
|
||||
# Counter devices
|
||||
#
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
|
||||
menu "Counters"
|
||||
|
||||
config 104_QUAD_8
|
||||
tristate "ACCES 104-QUAD-8 driver"
|
||||
depends on X86 && ISA_BUS_API
|
||||
help
|
||||
Say yes here to build support for the ACCES 104-QUAD-8 quadrature
|
||||
encoder counter/interface device family (104-QUAD-8, 104-QUAD-4).
|
||||
|
||||
Performing a write to a counter's IIO_CHAN_INFO_RAW sets the counter and
|
||||
also clears the counter's respective error flag. Although the counters
|
||||
have a 25-bit range, only the lower 24 bits may be set, either directly
|
||||
or via a counter's preset attribute. Interrupts are not supported by
|
||||
this driver.
|
||||
|
||||
The base port addresses for the devices may be configured via the base
|
||||
array module parameter.
|
||||
|
||||
endmenu
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Makefile for IIO counter devices
|
||||
#
|
||||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
|
||||
obj-$(CONFIG_104_QUAD_8) += 104-quad-8.o
|
|
@ -200,6 +200,16 @@ config AD8801
|
|||
To compile this driver as a module choose M here: the module will be called
|
||||
ad8801.
|
||||
|
||||
config DPOT_DAC
|
||||
tristate "DAC emulation using a DPOT"
|
||||
depends on OF
|
||||
help
|
||||
Say yes here to build support for DAC emulation using a digital
|
||||
potentiometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called dpot-dac.
|
||||
|
||||
config LPC18XX_DAC
|
||||
tristate "NXP LPC18xx DAC driver"
|
||||
depends on ARCH_LPC18XX || COMPILE_TEST
|
||||
|
|
|
@ -22,6 +22,7 @@ obj-$(CONFIG_AD5686) += ad5686.o
|
|||
obj-$(CONFIG_AD7303) += ad7303.o
|
||||
obj-$(CONFIG_AD8801) += ad8801.o
|
||||
obj-$(CONFIG_CIO_DAC) += cio-dac.o
|
||||
obj-$(CONFIG_DPOT_DAC) += dpot-dac.o
|
||||
obj-$(CONFIG_LPC18XX_DAC) += lpc18xx_dac.o
|
||||
obj-$(CONFIG_M62332) += m62332.o
|
||||
obj-$(CONFIG_MAX517) += max517.o
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#define AD5592R_GPIO_READBACK_EN BIT(10)
|
||||
#define AD5592R_LDAC_READBACK_EN BIT(6)
|
||||
|
||||
static int ad5592r_spi_wnop_r16(struct ad5592r_state *st, u16 *buf)
|
||||
static int ad5592r_spi_wnop_r16(struct ad5592r_state *st, __be16 *buf)
|
||||
{
|
||||
struct spi_device *spi = container_of(st->dev, struct spi_device, dev);
|
||||
struct spi_transfer t = {
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* IIO DAC emulation driver using a digital potentiometer
|
||||
*
|
||||
* Copyright (C) 2016 Axentia Technologies AB
|
||||
*
|
||||
* Author: Peter Rosin <peda@axentia.se>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* It is assumed that the dpot is used as a voltage divider between the
|
||||
* current dpot wiper setting and the maximum resistance of the dpot. The
|
||||
* divided voltage is provided by a vref regulator.
|
||||
*
|
||||
* .------.
|
||||
* .-----------. | |
|
||||
* | vref |--' .---.
|
||||
* | regulator |--. | |
|
||||
* '-----------' | | d |
|
||||
* | | p |
|
||||
* | | o | wiper
|
||||
* | | t |<---------+
|
||||
* | | |
|
||||
* | '---' dac output voltage
|
||||
* | |
|
||||
* '------+------------+
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/iio/consumer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
struct dpot_dac {
|
||||
struct regulator *vref;
|
||||
struct iio_channel *dpot;
|
||||
u32 max_ohms;
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec dpot_dac_iio_channel = {
|
||||
.type = IIO_VOLTAGE,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
|
||||
| BIT(IIO_CHAN_INFO_SCALE),
|
||||
.info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW),
|
||||
.output = 1,
|
||||
.indexed = 1,
|
||||
};
|
||||
|
||||
static int dpot_dac_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct dpot_dac *dac = iio_priv(indio_dev);
|
||||
int ret;
|
||||
unsigned long long tmp;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
return iio_read_channel_raw(dac->dpot, val);
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = iio_read_channel_scale(dac->dpot, val, val2);
|
||||
switch (ret) {
|
||||
case IIO_VAL_FRACTIONAL_LOG2:
|
||||
tmp = *val * 1000000000LL;
|
||||
do_div(tmp, dac->max_ohms);
|
||||
tmp *= regulator_get_voltage(dac->vref) / 1000;
|
||||
do_div(tmp, 1000000000LL);
|
||||
*val = tmp;
|
||||
return ret;
|
||||
case IIO_VAL_INT:
|
||||
/*
|
||||
* Convert integer scale to fractional scale by
|
||||
* setting the denominator (val2) to one...
|
||||
*/
|
||||
*val2 = 1;
|
||||
ret = IIO_VAL_FRACTIONAL;
|
||||
/* ...and fall through. */
|
||||
case IIO_VAL_FRACTIONAL:
|
||||
*val *= regulator_get_voltage(dac->vref) / 1000;
|
||||
*val2 *= dac->max_ohms;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int dpot_dac_read_avail(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
const int **vals, int *type, int *length,
|
||||
long mask)
|
||||
{
|
||||
struct dpot_dac *dac = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
*type = IIO_VAL_INT;
|
||||
return iio_read_avail_channel_raw(dac->dpot, vals, length);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int dpot_dac_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct dpot_dac *dac = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
return iio_write_channel_raw(dac->dpot, val);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct iio_info dpot_dac_info = {
|
||||
.read_raw = dpot_dac_read_raw,
|
||||
.read_avail = dpot_dac_read_avail,
|
||||
.write_raw = dpot_dac_write_raw,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int dpot_dac_channel_max_ohms(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct device *dev = &indio_dev->dev;
|
||||
struct dpot_dac *dac = iio_priv(indio_dev);
|
||||
unsigned long long tmp;
|
||||
int ret;
|
||||
int val;
|
||||
int val2;
|
||||
int max;
|
||||
|
||||
ret = iio_read_max_channel_raw(dac->dpot, &max);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "dpot does not indicate its raw maximum value\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (iio_read_channel_scale(dac->dpot, &val, &val2)) {
|
||||
case IIO_VAL_INT:
|
||||
return max * val;
|
||||
case IIO_VAL_FRACTIONAL:
|
||||
tmp = (unsigned long long)max * val;
|
||||
do_div(tmp, val2);
|
||||
return tmp;
|
||||
case IIO_VAL_FRACTIONAL_LOG2:
|
||||
tmp = val * 1000000000LL * max >> val2;
|
||||
do_div(tmp, 1000000000LL);
|
||||
return tmp;
|
||||
default:
|
||||
dev_err(dev, "dpot has a scale that is too weird\n");
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int dpot_dac_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct iio_dev *indio_dev;
|
||||
struct dpot_dac *dac;
|
||||
enum iio_chan_type type;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*dac));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
dac = iio_priv(indio_dev);
|
||||
|
||||
indio_dev->name = dev_name(dev);
|
||||
indio_dev->dev.parent = dev;
|
||||
indio_dev->info = &dpot_dac_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = &dpot_dac_iio_channel;
|
||||
indio_dev->num_channels = 1;
|
||||
|
||||
dac->vref = devm_regulator_get(dev, "vref");
|
||||
if (IS_ERR(dac->vref)) {
|
||||
if (PTR_ERR(dac->vref) != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "failed to get vref regulator\n");
|
||||
return PTR_ERR(dac->vref);
|
||||
}
|
||||
|
||||
dac->dpot = devm_iio_channel_get(dev, "dpot");
|
||||
if (IS_ERR(dac->dpot)) {
|
||||
if (PTR_ERR(dac->dpot) != -EPROBE_DEFER)
|
||||
dev_err(dev, "failed to get dpot input channel\n");
|
||||
return PTR_ERR(dac->dpot);
|
||||
}
|
||||
|
||||
ret = iio_get_channel_type(dac->dpot, &type);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (type != IIO_RESISTANCE) {
|
||||
dev_err(dev, "dpot is of the wrong type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = dpot_dac_channel_max_ohms(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
dac->max_ohms = ret;
|
||||
|
||||
ret = regulator_enable(dac->vref);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable the vref regulator\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register iio device\n");
|
||||
goto disable_reg;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
disable_reg:
|
||||
regulator_disable(dac->vref);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dpot_dac_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct dpot_dac *dac = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
regulator_disable(dac->vref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id dpot_dac_match[] = {
|
||||
{ .compatible = "dpot-dac" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, dpot_dac_match);
|
||||
|
||||
static struct platform_driver dpot_dac_driver = {
|
||||
.probe = dpot_dac_probe,
|
||||
.remove = dpot_dac_remove,
|
||||
.driver = {
|
||||
.name = "iio-dpot-dac",
|
||||
.of_match_table = dpot_dac_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(dpot_dac_driver);
|
||||
|
||||
MODULE_DESCRIPTION("DAC emulation driver using a digital potentiometer");
|
||||
MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -18,6 +18,8 @@
|
|||
#include <linux/i2c.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
@ -26,12 +28,20 @@
|
|||
|
||||
#define MCP4725_DRV_NAME "mcp4725"
|
||||
|
||||
#define MCP472X_REF_VDD 0x00
|
||||
#define MCP472X_REF_VREF_UNBUFFERED 0x02
|
||||
#define MCP472X_REF_VREF_BUFFERED 0x03
|
||||
|
||||
struct mcp4725_data {
|
||||
struct i2c_client *client;
|
||||
u16 vref_mv;
|
||||
int id;
|
||||
unsigned ref_mode;
|
||||
bool vref_buffered;
|
||||
u16 dac_value;
|
||||
bool powerdown;
|
||||
unsigned powerdown_mode;
|
||||
struct regulator *vdd_reg;
|
||||
struct regulator *vref_reg;
|
||||
};
|
||||
|
||||
static int mcp4725_suspend(struct device *dev)
|
||||
|
@ -86,6 +96,7 @@ static ssize_t mcp4725_store_eeprom(struct device *dev,
|
|||
return 0;
|
||||
|
||||
inoutbuf[0] = 0x60; /* write EEPROM */
|
||||
inoutbuf[0] |= data->ref_mode << 3;
|
||||
inoutbuf[1] = data->dac_value >> 4;
|
||||
inoutbuf[2] = (data->dac_value & 0xf) << 4;
|
||||
|
||||
|
@ -278,18 +289,49 @@ static int mcp4725_set_value(struct iio_dev *indio_dev, int val)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int mcp4726_set_cfg(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct mcp4725_data *data = iio_priv(indio_dev);
|
||||
u8 outbuf[3];
|
||||
int ret;
|
||||
|
||||
outbuf[0] = 0x40;
|
||||
outbuf[0] |= data->ref_mode << 3;
|
||||
if (data->powerdown)
|
||||
outbuf[0] |= data->powerdown << 1;
|
||||
outbuf[1] = data->dac_value >> 4;
|
||||
outbuf[2] = (data->dac_value & 0xf) << 4;
|
||||
|
||||
ret = i2c_master_send(data->client, outbuf, 3);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret != 3)
|
||||
return -EIO;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mcp4725_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct mcp4725_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
*val = data->dac_value;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = data->vref_mv;
|
||||
if (data->ref_mode == MCP472X_REF_VDD)
|
||||
ret = regulator_get_voltage(data->vdd_reg);
|
||||
else
|
||||
ret = regulator_get_voltage(data->vref_reg);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret / 1000;
|
||||
*val2 = 12;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
}
|
||||
|
@ -323,27 +365,98 @@ static const struct iio_info mcp4725_info = {
|
|||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int mcp4725_probe_dt(struct device *dev,
|
||||
struct mcp4725_platform_data *pdata)
|
||||
{
|
||||
struct device_node *np = dev->of_node;
|
||||
|
||||
if (!np)
|
||||
return -ENODEV;
|
||||
|
||||
/* check if is the vref-supply defined */
|
||||
pdata->use_vref = of_property_read_bool(np, "vref-supply");
|
||||
pdata->vref_buffered =
|
||||
of_property_read_bool(np, "microchip,vref-buffered");
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int mcp4725_probe_dt(struct device *dev,
|
||||
struct mcp4725_platform_data *platform_data)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int mcp4725_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct mcp4725_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
struct mcp4725_platform_data *platform_data = client->dev.platform_data;
|
||||
u8 inbuf[3];
|
||||
struct mcp4725_platform_data *pdata, pdata_dt;
|
||||
u8 inbuf[4];
|
||||
u8 pd;
|
||||
u8 ref;
|
||||
int err;
|
||||
|
||||
if (!platform_data || !platform_data->vref_mv) {
|
||||
dev_err(&client->dev, "invalid platform data");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (indio_dev == NULL)
|
||||
return -ENOMEM;
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
data->id = id->driver_data;
|
||||
pdata = dev_get_platdata(&client->dev);
|
||||
|
||||
if (!pdata) {
|
||||
err = mcp4725_probe_dt(&client->dev, &pdata_dt);
|
||||
if (err) {
|
||||
dev_err(&client->dev,
|
||||
"invalid platform or devicetree data");
|
||||
return err;
|
||||
}
|
||||
pdata = &pdata_dt;
|
||||
}
|
||||
|
||||
if (data->id == MCP4725 && pdata->use_vref) {
|
||||
dev_err(&client->dev,
|
||||
"external reference is unavailable on MCP4725");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!pdata->use_vref && pdata->vref_buffered) {
|
||||
dev_err(&client->dev,
|
||||
"buffering is unavailable on the internal reference");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!pdata->use_vref)
|
||||
data->ref_mode = MCP472X_REF_VDD;
|
||||
else
|
||||
data->ref_mode = pdata->vref_buffered ?
|
||||
MCP472X_REF_VREF_BUFFERED :
|
||||
MCP472X_REF_VREF_UNBUFFERED;
|
||||
|
||||
data->vdd_reg = devm_regulator_get(&client->dev, "vdd");
|
||||
if (IS_ERR(data->vdd_reg))
|
||||
return PTR_ERR(data->vdd_reg);
|
||||
|
||||
err = regulator_enable(data->vdd_reg);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (pdata->use_vref) {
|
||||
data->vref_reg = devm_regulator_get(&client->dev, "vref");
|
||||
if (IS_ERR(data->vref_reg)) {
|
||||
err = PTR_ERR(data->vref_reg);
|
||||
goto err_disable_vdd_reg;
|
||||
}
|
||||
|
||||
err = regulator_enable(data->vref_reg);
|
||||
if (err)
|
||||
goto err_disable_vdd_reg;
|
||||
}
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->name = id->name;
|
||||
|
@ -352,25 +465,56 @@ static int mcp4725_probe(struct i2c_client *client,
|
|||
indio_dev->num_channels = 1;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
data->vref_mv = platform_data->vref_mv;
|
||||
/* read current DAC value and settings */
|
||||
err = i2c_master_recv(client, inbuf, data->id == MCP4725 ? 3 : 4);
|
||||
|
||||
/* read current DAC value */
|
||||
err = i2c_master_recv(client, inbuf, 3);
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev, "failed to read DAC value");
|
||||
return err;
|
||||
goto err_disable_vref_reg;
|
||||
}
|
||||
pd = (inbuf[0] >> 1) & 0x3;
|
||||
data->powerdown = pd > 0 ? true : false;
|
||||
data->powerdown_mode = pd ? pd - 1 : 2; /* largest register to gnd */
|
||||
data->powerdown_mode = pd ? pd - 1 : 2; /* largest resistor to gnd */
|
||||
data->dac_value = (inbuf[1] << 4) | (inbuf[2] >> 4);
|
||||
if (data->id == MCP4726)
|
||||
ref = (inbuf[3] >> 3) & 0x3;
|
||||
|
||||
return iio_device_register(indio_dev);
|
||||
if (data->id == MCP4726 && ref != data->ref_mode) {
|
||||
dev_info(&client->dev,
|
||||
"voltage reference mode differs (conf: %u, eeprom: %u), setting %u",
|
||||
data->ref_mode, ref, data->ref_mode);
|
||||
err = mcp4726_set_cfg(indio_dev);
|
||||
if (err < 0)
|
||||
goto err_disable_vref_reg;
|
||||
}
|
||||
|
||||
err = iio_device_register(indio_dev);
|
||||
if (err)
|
||||
goto err_disable_vref_reg;
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_vref_reg:
|
||||
if (data->vref_reg)
|
||||
regulator_disable(data->vref_reg);
|
||||
|
||||
err_disable_vdd_reg:
|
||||
regulator_disable(data->vdd_reg);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int mcp4725_remove(struct i2c_client *client)
|
||||
{
|
||||
iio_device_unregister(i2c_get_clientdata(client));
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct mcp4725_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
if (data->vref_reg)
|
||||
regulator_disable(data->vref_reg);
|
||||
regulator_disable(data->vdd_reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,24 @@ config HID_SENSOR_GYRO_3D
|
|||
Say yes here to build support for the HID SENSOR
|
||||
Gyroscope 3D.
|
||||
|
||||
config MPU3050
|
||||
tristate
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
select REGMAP
|
||||
|
||||
config MPU3050_I2C
|
||||
tristate "Invensense MPU3050 devices on I2C"
|
||||
depends on !(INPUT_MPU3050=y || INPUT_MPU3050=m)
|
||||
depends on I2C
|
||||
select MPU3050
|
||||
select REGMAP_I2C
|
||||
select I2C_MUX
|
||||
help
|
||||
This driver supports the Invensense MPU3050 gyroscope over I2C.
|
||||
This driver can be built as a module. The module will be called
|
||||
inv-mpu3050-i2c.
|
||||
|
||||
config IIO_ST_GYRO_3AXIS
|
||||
tristate "STMicroelectronics gyroscopes 3-Axis Driver"
|
||||
depends on (I2C || SPI_MASTER) && SYSFS
|
||||
|
|
|
@ -14,6 +14,11 @@ obj-$(CONFIG_BMG160_SPI) += bmg160_spi.o
|
|||
|
||||
obj-$(CONFIG_HID_SENSOR_GYRO_3D) += hid-sensor-gyro-3d.o
|
||||
|
||||
# Currently this is rolled into one module, split it if
|
||||
# we ever create a separate SPI interface for MPU-3050
|
||||
obj-$(CONFIG_MPU3050) += mpu3050.o
|
||||
mpu3050-objs := mpu3050-core.o mpu3050-i2c.o
|
||||
|
||||
itg3200-y := itg3200_core.o
|
||||
itg3200-$(CONFIG_IIO_BUFFER) += itg3200_buffer.o
|
||||
obj-$(CONFIG_ITG3200) += itg3200.o
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,124 @@
|
|||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c-mux.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "mpu3050.h"
|
||||
|
||||
static const struct regmap_config mpu3050_i2c_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static int mpu3050_i2c_bypass_select(struct i2c_mux_core *mux, u32 chan_id)
|
||||
{
|
||||
struct mpu3050 *mpu3050 = i2c_mux_priv(mux);
|
||||
|
||||
/* Just power up the device, that is all that is needed */
|
||||
pm_runtime_get_sync(mpu3050->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpu3050_i2c_bypass_deselect(struct i2c_mux_core *mux, u32 chan_id)
|
||||
{
|
||||
struct mpu3050 *mpu3050 = i2c_mux_priv(mux);
|
||||
|
||||
pm_runtime_mark_last_busy(mpu3050->dev);
|
||||
pm_runtime_put_autosuspend(mpu3050->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpu3050_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
const char *name;
|
||||
struct mpu3050 *mpu3050;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_I2C_BLOCK))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (id)
|
||||
name = id->name;
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &mpu3050_i2c_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&client->dev, "Failed to register i2c regmap %d\n",
|
||||
(int)PTR_ERR(regmap));
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
ret = mpu3050_common_probe(&client->dev, regmap, client->irq, name);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* The main driver is up, now register the I2C mux */
|
||||
mpu3050 = iio_priv(dev_get_drvdata(&client->dev));
|
||||
mpu3050->i2cmux = i2c_mux_alloc(client->adapter, &client->dev,
|
||||
1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
|
||||
mpu3050_i2c_bypass_select,
|
||||
mpu3050_i2c_bypass_deselect);
|
||||
/* Just fail the mux, there is no point in killing the driver */
|
||||
if (!mpu3050->i2cmux)
|
||||
dev_err(&client->dev, "failed to allocate I2C mux\n");
|
||||
else {
|
||||
mpu3050->i2cmux->priv = mpu3050;
|
||||
ret = i2c_mux_add_adapter(mpu3050->i2cmux, 0, 0, 0);
|
||||
if (ret)
|
||||
dev_err(&client->dev, "failed to add I2C mux\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpu3050_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(&client->dev);
|
||||
struct mpu3050 *mpu3050 = iio_priv(indio_dev);
|
||||
|
||||
if (mpu3050->i2cmux)
|
||||
i2c_mux_del_adapters(mpu3050->i2cmux);
|
||||
|
||||
return mpu3050_common_remove(&client->dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* device id table is used to identify what device can be
|
||||
* supported by this driver
|
||||
*/
|
||||
static const struct i2c_device_id mpu3050_i2c_id[] = {
|
||||
{ "mpu3050" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mpu3050_i2c_id);
|
||||
|
||||
static const struct of_device_id mpu3050_i2c_of_match[] = {
|
||||
{ .compatible = "invensense,mpu3050", .data = "mpu3050" },
|
||||
/* Deprecated vendor ID from the Input driver */
|
||||
{ .compatible = "invn,mpu3050", .data = "mpu3050" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mpu3050_i2c_of_match);
|
||||
|
||||
static struct i2c_driver mpu3050_i2c_driver = {
|
||||
.probe = mpu3050_i2c_probe,
|
||||
.remove = mpu3050_i2c_remove,
|
||||
.id_table = mpu3050_i2c_id,
|
||||
.driver = {
|
||||
.of_match_table = mpu3050_i2c_of_match,
|
||||
.name = "mpu3050-i2c",
|
||||
.pm = &mpu3050_dev_pm_ops,
|
||||
},
|
||||
};
|
||||
module_i2c_driver(mpu3050_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Linus Walleij");
|
||||
MODULE_DESCRIPTION("Invensense MPU3050 gyroscope driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,96 @@
|
|||
#include <linux/iio/iio.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
/**
|
||||
* enum mpu3050_fullscale - indicates the full range of the sensor in deg/sec
|
||||
*/
|
||||
enum mpu3050_fullscale {
|
||||
FS_250_DPS = 0,
|
||||
FS_500_DPS,
|
||||
FS_1000_DPS,
|
||||
FS_2000_DPS,
|
||||
};
|
||||
|
||||
/**
|
||||
* enum mpu3050_lpf - indicates the low pass filter width
|
||||
*/
|
||||
enum mpu3050_lpf {
|
||||
/* This implicity sets sample frequency to 8 kHz */
|
||||
LPF_256_HZ_NOLPF = 0,
|
||||
/* All others sets the sample frequency to 1 kHz */
|
||||
LPF_188_HZ,
|
||||
LPF_98_HZ,
|
||||
LPF_42_HZ,
|
||||
LPF_20_HZ,
|
||||
LPF_10_HZ,
|
||||
LPF_5_HZ,
|
||||
LPF_2100_HZ_NOLPF,
|
||||
};
|
||||
|
||||
enum mpu3050_axis {
|
||||
AXIS_X = 0,
|
||||
AXIS_Y,
|
||||
AXIS_Z,
|
||||
AXIS_MAX,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mpu3050 - instance state container for the device
|
||||
* @dev: parent device for this instance
|
||||
* @orientation: mounting matrix, flipped axis etc
|
||||
* @map: regmap to reach the registers
|
||||
* @lock: serialization lock to marshal all requests
|
||||
* @irq: the IRQ used for this device
|
||||
* @regs: the regulators to power this device
|
||||
* @fullscale: the current fullscale setting for the device
|
||||
* @lpf: digital low pass filter setting for the device
|
||||
* @divisor: base frequency divider: divides 8 or 1 kHz
|
||||
* @calibration: the three signed 16-bit calibration settings that
|
||||
* get written into the offset registers for each axis to compensate
|
||||
* for DC offsets
|
||||
* @trig: trigger for the MPU-3050 interrupt, if present
|
||||
* @hw_irq_trigger: hardware interrupt trigger is in use
|
||||
* @irq_actl: interrupt is active low
|
||||
* @irq_latch: latched IRQ, this means that it is a level IRQ
|
||||
* @irq_opendrain: the interrupt line shall be configured open drain
|
||||
* @pending_fifo_footer: tells us if there is a pending footer in the FIFO
|
||||
* that we have to read out first when handling the FIFO
|
||||
* @hw_timestamp: latest hardware timestamp from the trigger IRQ, when in
|
||||
* use
|
||||
* @i2cmux: an I2C mux reflecting the fact that this sensor is a hub with
|
||||
* a pass-through I2C interface coming out of it: this device needs to be
|
||||
* powered up in order to reach devices on the other side of this mux
|
||||
*/
|
||||
struct mpu3050 {
|
||||
struct device *dev;
|
||||
struct iio_mount_matrix orientation;
|
||||
struct regmap *map;
|
||||
struct mutex lock;
|
||||
int irq;
|
||||
struct regulator_bulk_data regs[2];
|
||||
enum mpu3050_fullscale fullscale;
|
||||
enum mpu3050_lpf lpf;
|
||||
u8 divisor;
|
||||
s16 calibration[3];
|
||||
struct iio_trigger *trig;
|
||||
bool hw_irq_trigger;
|
||||
bool irq_actl;
|
||||
bool irq_latch;
|
||||
bool irq_opendrain;
|
||||
bool pending_fifo_footer;
|
||||
s64 hw_timestamp;
|
||||
struct i2c_mux_core *i2cmux;
|
||||
};
|
||||
|
||||
/* Probe called from different transports */
|
||||
int mpu3050_common_probe(struct device *dev,
|
||||
struct regmap *map,
|
||||
int irq,
|
||||
const char *name);
|
||||
int mpu3050_common_remove(struct device *dev);
|
||||
|
||||
/* PM ops */
|
||||
extern const struct dev_pm_ops mpu3050_dev_pm_ops;
|
|
@ -39,79 +39,6 @@
|
|||
#define ST_GYRO_FS_AVL_500DPS 500
|
||||
#define ST_GYRO_FS_AVL_2000DPS 2000
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 1 */
|
||||
#define ST_GYRO_1_WAI_EXP 0xd3
|
||||
#define ST_GYRO_1_ODR_ADDR 0x20
|
||||
#define ST_GYRO_1_ODR_MASK 0xc0
|
||||
#define ST_GYRO_1_ODR_AVL_100HZ_VAL 0x00
|
||||
#define ST_GYRO_1_ODR_AVL_200HZ_VAL 0x01
|
||||
#define ST_GYRO_1_ODR_AVL_400HZ_VAL 0x02
|
||||
#define ST_GYRO_1_ODR_AVL_800HZ_VAL 0x03
|
||||
#define ST_GYRO_1_PW_ADDR 0x20
|
||||
#define ST_GYRO_1_PW_MASK 0x08
|
||||
#define ST_GYRO_1_FS_ADDR 0x23
|
||||
#define ST_GYRO_1_FS_MASK 0x30
|
||||
#define ST_GYRO_1_FS_AVL_250_VAL 0x00
|
||||
#define ST_GYRO_1_FS_AVL_500_VAL 0x01
|
||||
#define ST_GYRO_1_FS_AVL_2000_VAL 0x02
|
||||
#define ST_GYRO_1_FS_AVL_250_GAIN IIO_DEGREE_TO_RAD(8750)
|
||||
#define ST_GYRO_1_FS_AVL_500_GAIN IIO_DEGREE_TO_RAD(17500)
|
||||
#define ST_GYRO_1_FS_AVL_2000_GAIN IIO_DEGREE_TO_RAD(70000)
|
||||
#define ST_GYRO_1_BDU_ADDR 0x23
|
||||
#define ST_GYRO_1_BDU_MASK 0x80
|
||||
#define ST_GYRO_1_DRDY_IRQ_ADDR 0x22
|
||||
#define ST_GYRO_1_DRDY_IRQ_INT2_MASK 0x08
|
||||
#define ST_GYRO_1_MULTIREAD_BIT true
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 2 */
|
||||
#define ST_GYRO_2_WAI_EXP 0xd4
|
||||
#define ST_GYRO_2_ODR_ADDR 0x20
|
||||
#define ST_GYRO_2_ODR_MASK 0xc0
|
||||
#define ST_GYRO_2_ODR_AVL_95HZ_VAL 0x00
|
||||
#define ST_GYRO_2_ODR_AVL_190HZ_VAL 0x01
|
||||
#define ST_GYRO_2_ODR_AVL_380HZ_VAL 0x02
|
||||
#define ST_GYRO_2_ODR_AVL_760HZ_VAL 0x03
|
||||
#define ST_GYRO_2_PW_ADDR 0x20
|
||||
#define ST_GYRO_2_PW_MASK 0x08
|
||||
#define ST_GYRO_2_FS_ADDR 0x23
|
||||
#define ST_GYRO_2_FS_MASK 0x30
|
||||
#define ST_GYRO_2_FS_AVL_250_VAL 0x00
|
||||
#define ST_GYRO_2_FS_AVL_500_VAL 0x01
|
||||
#define ST_GYRO_2_FS_AVL_2000_VAL 0x02
|
||||
#define ST_GYRO_2_FS_AVL_250_GAIN IIO_DEGREE_TO_RAD(8750)
|
||||
#define ST_GYRO_2_FS_AVL_500_GAIN IIO_DEGREE_TO_RAD(17500)
|
||||
#define ST_GYRO_2_FS_AVL_2000_GAIN IIO_DEGREE_TO_RAD(70000)
|
||||
#define ST_GYRO_2_BDU_ADDR 0x23
|
||||
#define ST_GYRO_2_BDU_MASK 0x80
|
||||
#define ST_GYRO_2_DRDY_IRQ_ADDR 0x22
|
||||
#define ST_GYRO_2_DRDY_IRQ_INT2_MASK 0x08
|
||||
#define ST_GYRO_2_MULTIREAD_BIT true
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 3 */
|
||||
#define ST_GYRO_3_WAI_EXP 0xd7
|
||||
#define ST_GYRO_3_ODR_ADDR 0x20
|
||||
#define ST_GYRO_3_ODR_MASK 0xc0
|
||||
#define ST_GYRO_3_ODR_AVL_95HZ_VAL 0x00
|
||||
#define ST_GYRO_3_ODR_AVL_190HZ_VAL 0x01
|
||||
#define ST_GYRO_3_ODR_AVL_380HZ_VAL 0x02
|
||||
#define ST_GYRO_3_ODR_AVL_760HZ_VAL 0x03
|
||||
#define ST_GYRO_3_PW_ADDR 0x20
|
||||
#define ST_GYRO_3_PW_MASK 0x08
|
||||
#define ST_GYRO_3_FS_ADDR 0x23
|
||||
#define ST_GYRO_3_FS_MASK 0x30
|
||||
#define ST_GYRO_3_FS_AVL_250_VAL 0x00
|
||||
#define ST_GYRO_3_FS_AVL_500_VAL 0x01
|
||||
#define ST_GYRO_3_FS_AVL_2000_VAL 0x02
|
||||
#define ST_GYRO_3_FS_AVL_250_GAIN IIO_DEGREE_TO_RAD(8750)
|
||||
#define ST_GYRO_3_FS_AVL_500_GAIN IIO_DEGREE_TO_RAD(17500)
|
||||
#define ST_GYRO_3_FS_AVL_2000_GAIN IIO_DEGREE_TO_RAD(70000)
|
||||
#define ST_GYRO_3_BDU_ADDR 0x23
|
||||
#define ST_GYRO_3_BDU_MASK 0x80
|
||||
#define ST_GYRO_3_DRDY_IRQ_ADDR 0x22
|
||||
#define ST_GYRO_3_DRDY_IRQ_INT2_MASK 0x08
|
||||
#define ST_GYRO_3_MULTIREAD_BIT true
|
||||
|
||||
|
||||
static const struct iio_chan_spec st_gyro_16bit_channels[] = {
|
||||
ST_SENSORS_LSM_CHANNELS(IIO_ANGL_VEL,
|
||||
BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
|
||||
|
@ -130,7 +57,7 @@ static const struct iio_chan_spec st_gyro_16bit_channels[] = {
|
|||
|
||||
static const struct st_sensor_settings st_gyro_sensors_settings[] = {
|
||||
{
|
||||
.wai = ST_GYRO_1_WAI_EXP,
|
||||
.wai = 0xd3,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = L3G4200D_GYRO_DEV_NAME,
|
||||
|
@ -138,18 +65,18 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = {
|
|||
},
|
||||
.ch = (struct iio_chan_spec *)st_gyro_16bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_GYRO_1_ODR_ADDR,
|
||||
.mask = ST_GYRO_1_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0xc0,
|
||||
.odr_avl = {
|
||||
{ 100, ST_GYRO_1_ODR_AVL_100HZ_VAL, },
|
||||
{ 200, ST_GYRO_1_ODR_AVL_200HZ_VAL, },
|
||||
{ 400, ST_GYRO_1_ODR_AVL_400HZ_VAL, },
|
||||
{ 800, ST_GYRO_1_ODR_AVL_800HZ_VAL, },
|
||||
{ .hz = 100, .value = 0x00, },
|
||||
{ .hz = 200, .value = 0x01, },
|
||||
{ .hz = 400, .value = 0x02, },
|
||||
{ .hz = 800, .value = 0x03, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_GYRO_1_PW_ADDR,
|
||||
.mask = ST_GYRO_1_PW_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x08,
|
||||
.value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
|
@ -158,33 +85,33 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = {
|
|||
.mask = ST_SENSORS_DEFAULT_AXIS_MASK,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_GYRO_1_FS_ADDR,
|
||||
.mask = ST_GYRO_1_FS_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x30,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_GYRO_FS_AVL_250DPS,
|
||||
.value = ST_GYRO_1_FS_AVL_250_VAL,
|
||||
.gain = ST_GYRO_1_FS_AVL_250_GAIN,
|
||||
.value = 0x00,
|
||||
.gain = IIO_DEGREE_TO_RAD(8750),
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_GYRO_FS_AVL_500DPS,
|
||||
.value = ST_GYRO_1_FS_AVL_500_VAL,
|
||||
.gain = ST_GYRO_1_FS_AVL_500_GAIN,
|
||||
.value = 0x01,
|
||||
.gain = IIO_DEGREE_TO_RAD(17500),
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_GYRO_FS_AVL_2000DPS,
|
||||
.value = ST_GYRO_1_FS_AVL_2000_VAL,
|
||||
.gain = ST_GYRO_1_FS_AVL_2000_GAIN,
|
||||
.value = 0x02,
|
||||
.gain = IIO_DEGREE_TO_RAD(70000),
|
||||
},
|
||||
},
|
||||
},
|
||||
.bdu = {
|
||||
.addr = ST_GYRO_1_BDU_ADDR,
|
||||
.mask = ST_GYRO_1_BDU_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x80,
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_GYRO_1_DRDY_IRQ_ADDR,
|
||||
.mask_int2 = ST_GYRO_1_DRDY_IRQ_INT2_MASK,
|
||||
.addr = 0x22,
|
||||
.mask_int2 = 0x08,
|
||||
/*
|
||||
* The sensor has IHL (active low) and open
|
||||
* drain settings, but only for INT1 and not
|
||||
|
@ -192,11 +119,11 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = {
|
|||
*/
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
},
|
||||
.multi_read_bit = ST_GYRO_1_MULTIREAD_BIT,
|
||||
.multi_read_bit = true,
|
||||
.bootime = 2,
|
||||
},
|
||||
{
|
||||
.wai = ST_GYRO_2_WAI_EXP,
|
||||
.wai = 0xd4,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = L3GD20_GYRO_DEV_NAME,
|
||||
|
@ -208,18 +135,18 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = {
|
|||
},
|
||||
.ch = (struct iio_chan_spec *)st_gyro_16bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_GYRO_2_ODR_ADDR,
|
||||
.mask = ST_GYRO_2_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0xc0,
|
||||
.odr_avl = {
|
||||
{ 95, ST_GYRO_2_ODR_AVL_95HZ_VAL, },
|
||||
{ 190, ST_GYRO_2_ODR_AVL_190HZ_VAL, },
|
||||
{ 380, ST_GYRO_2_ODR_AVL_380HZ_VAL, },
|
||||
{ 760, ST_GYRO_2_ODR_AVL_760HZ_VAL, },
|
||||
{ .hz = 95, .value = 0x00, },
|
||||
{ .hz = 190, .value = 0x01, },
|
||||
{ .hz = 380, .value = 0x02, },
|
||||
{ .hz = 760, .value = 0x03, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_GYRO_2_PW_ADDR,
|
||||
.mask = ST_GYRO_2_PW_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x08,
|
||||
.value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
|
@ -228,33 +155,33 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = {
|
|||
.mask = ST_SENSORS_DEFAULT_AXIS_MASK,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_GYRO_2_FS_ADDR,
|
||||
.mask = ST_GYRO_2_FS_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x30,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_GYRO_FS_AVL_250DPS,
|
||||
.value = ST_GYRO_2_FS_AVL_250_VAL,
|
||||
.gain = ST_GYRO_2_FS_AVL_250_GAIN,
|
||||
.value = 0x00,
|
||||
.gain = IIO_DEGREE_TO_RAD(8750),
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_GYRO_FS_AVL_500DPS,
|
||||
.value = ST_GYRO_2_FS_AVL_500_VAL,
|
||||
.gain = ST_GYRO_2_FS_AVL_500_GAIN,
|
||||
.value = 0x01,
|
||||
.gain = IIO_DEGREE_TO_RAD(17500),
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_GYRO_FS_AVL_2000DPS,
|
||||
.value = ST_GYRO_2_FS_AVL_2000_VAL,
|
||||
.gain = ST_GYRO_2_FS_AVL_2000_GAIN,
|
||||
.value = 0x02,
|
||||
.gain = IIO_DEGREE_TO_RAD(70000),
|
||||
},
|
||||
},
|
||||
},
|
||||
.bdu = {
|
||||
.addr = ST_GYRO_2_BDU_ADDR,
|
||||
.mask = ST_GYRO_2_BDU_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x80,
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_GYRO_2_DRDY_IRQ_ADDR,
|
||||
.mask_int2 = ST_GYRO_2_DRDY_IRQ_INT2_MASK,
|
||||
.addr = 0x22,
|
||||
.mask_int2 = 0x08,
|
||||
/*
|
||||
* The sensor has IHL (active low) and open
|
||||
* drain settings, but only for INT1 and not
|
||||
|
@ -262,29 +189,29 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = {
|
|||
*/
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
},
|
||||
.multi_read_bit = ST_GYRO_2_MULTIREAD_BIT,
|
||||
.multi_read_bit = true,
|
||||
.bootime = 2,
|
||||
},
|
||||
{
|
||||
.wai = ST_GYRO_3_WAI_EXP,
|
||||
.wai = 0xd7,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = L3GD20_GYRO_DEV_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_gyro_16bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_GYRO_3_ODR_ADDR,
|
||||
.mask = ST_GYRO_3_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0xc0,
|
||||
.odr_avl = {
|
||||
{ 95, ST_GYRO_3_ODR_AVL_95HZ_VAL, },
|
||||
{ 190, ST_GYRO_3_ODR_AVL_190HZ_VAL, },
|
||||
{ 380, ST_GYRO_3_ODR_AVL_380HZ_VAL, },
|
||||
{ 760, ST_GYRO_3_ODR_AVL_760HZ_VAL, },
|
||||
{ .hz = 95, .value = 0x00, },
|
||||
{ .hz = 190, .value = 0x01, },
|
||||
{ .hz = 380, .value = 0x02, },
|
||||
{ .hz = 760, .value = 0x03, },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_GYRO_3_PW_ADDR,
|
||||
.mask = ST_GYRO_3_PW_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x08,
|
||||
.value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE,
|
||||
.value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE,
|
||||
},
|
||||
|
@ -293,33 +220,33 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = {
|
|||
.mask = ST_SENSORS_DEFAULT_AXIS_MASK,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_GYRO_3_FS_ADDR,
|
||||
.mask = ST_GYRO_3_FS_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x30,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_GYRO_FS_AVL_250DPS,
|
||||
.value = ST_GYRO_3_FS_AVL_250_VAL,
|
||||
.gain = ST_GYRO_3_FS_AVL_250_GAIN,
|
||||
.value = 0x00,
|
||||
.gain = IIO_DEGREE_TO_RAD(8750),
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_GYRO_FS_AVL_500DPS,
|
||||
.value = ST_GYRO_3_FS_AVL_500_VAL,
|
||||
.gain = ST_GYRO_3_FS_AVL_500_GAIN,
|
||||
.value = 0x01,
|
||||
.gain = IIO_DEGREE_TO_RAD(17500),
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_GYRO_FS_AVL_2000DPS,
|
||||
.value = ST_GYRO_3_FS_AVL_2000_VAL,
|
||||
.gain = ST_GYRO_3_FS_AVL_2000_GAIN,
|
||||
.value = 0x02,
|
||||
.gain = IIO_DEGREE_TO_RAD(70000),
|
||||
},
|
||||
},
|
||||
},
|
||||
.bdu = {
|
||||
.addr = ST_GYRO_3_BDU_ADDR,
|
||||
.mask = ST_GYRO_3_BDU_MASK,
|
||||
.addr = 0x23,
|
||||
.mask = 0x80,
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_GYRO_3_DRDY_IRQ_ADDR,
|
||||
.mask_int2 = ST_GYRO_3_DRDY_IRQ_INT2_MASK,
|
||||
.addr = 0x22,
|
||||
.mask_int2 = 0x08,
|
||||
/*
|
||||
* The sensor has IHL (active low) and open
|
||||
* drain settings, but only for INT1 and not
|
||||
|
@ -327,7 +254,7 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = {
|
|||
*/
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
},
|
||||
.multi_read_bit = ST_GYRO_3_MULTIREAD_BIT,
|
||||
.multi_read_bit = true,
|
||||
.bootime = 2,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -27,6 +27,8 @@ config DHT11
|
|||
config HDC100X
|
||||
tristate "TI HDC100x relative humidity and temperature sensor"
|
||||
depends on I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
Say yes here to build support for the Texas Instruments
|
||||
HDC1000 and HDC1008 relative humidity and temperature sensors.
|
||||
|
@ -34,6 +36,28 @@ config HDC100X
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called hdc100x.
|
||||
|
||||
config HTS221
|
||||
tristate "STMicroelectronics HTS221 sensor Driver"
|
||||
depends on (I2C || SPI)
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
select HTS221_I2C if (I2C)
|
||||
select HTS221_SPI if (SPI_MASTER)
|
||||
help
|
||||
Say yes here to build support for STMicroelectronics HTS221
|
||||
temperature-humidity sensor
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called hts221.
|
||||
|
||||
config HTS221_I2C
|
||||
tristate
|
||||
depends on HTS221
|
||||
|
||||
config HTS221_SPI
|
||||
tristate
|
||||
depends on HTS221
|
||||
|
||||
config HTU21
|
||||
tristate "Measurement Specialties HTU21 humidity & temperature sensor"
|
||||
depends on I2C
|
||||
|
|
|
@ -5,6 +5,13 @@
|
|||
obj-$(CONFIG_AM2315) += am2315.o
|
||||
obj-$(CONFIG_DHT11) += dht11.o
|
||||
obj-$(CONFIG_HDC100X) += hdc100x.o
|
||||
|
||||
hts221-y := hts221_core.o \
|
||||
hts221_buffer.o
|
||||
obj-$(CONFIG_HTS221) += hts221.o
|
||||
obj-$(CONFIG_HTS221_I2C) += hts221_i2c.o
|
||||
obj-$(CONFIG_HTS221_SPI) += hts221_spi.o
|
||||
|
||||
obj-$(CONFIG_HTU21) += htu21.o
|
||||
obj-$(CONFIG_SI7005) += si7005.o
|
||||
obj-$(CONFIG_SI7020) += si7020.o
|
||||
|
|
|
@ -22,11 +22,15 @@
|
|||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
#define HDC100X_REG_TEMP 0x00
|
||||
#define HDC100X_REG_HUMIDITY 0x01
|
||||
|
||||
#define HDC100X_REG_CONFIG 0x02
|
||||
#define HDC100X_REG_CONFIG_ACQ_MODE BIT(12)
|
||||
#define HDC100X_REG_CONFIG_HEATER_EN BIT(13)
|
||||
|
||||
struct hdc100x_data {
|
||||
|
@ -87,22 +91,40 @@ static const struct iio_chan_spec hdc100x_channels[] = {
|
|||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_INT_TIME) |
|
||||
BIT(IIO_CHAN_INFO_OFFSET),
|
||||
.scan_index = 0,
|
||||
.scan_type = {
|
||||
.sign = 's',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_BE,
|
||||
},
|
||||
},
|
||||
{
|
||||
.type = IIO_HUMIDITYRELATIVE,
|
||||
.address = HDC100X_REG_HUMIDITY,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_INT_TIME)
|
||||
BIT(IIO_CHAN_INFO_INT_TIME),
|
||||
.scan_index = 1,
|
||||
.scan_type = {
|
||||
.sign = 'u',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_BE,
|
||||
},
|
||||
},
|
||||
{
|
||||
.type = IIO_CURRENT,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.extend_name = "heater",
|
||||
.output = 1,
|
||||
.scan_index = -1,
|
||||
},
|
||||
IIO_CHAN_SOFT_TIMESTAMP(2),
|
||||
};
|
||||
|
||||
static const unsigned long hdc100x_scan_masks[] = {0x3, 0};
|
||||
|
||||
static int hdc100x_update_config(struct hdc100x_data *data, int mask, int val)
|
||||
{
|
||||
int tmp = (~mask & data->config) | val;
|
||||
|
@ -183,7 +205,14 @@ static int hdc100x_read_raw(struct iio_dev *indio_dev,
|
|||
*val = hdc100x_get_heater_status(data);
|
||||
ret = IIO_VAL_INT;
|
||||
} else {
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret) {
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hdc100x_get_measurement(data, chan);
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
if (ret >= 0) {
|
||||
*val = ret;
|
||||
ret = IIO_VAL_INT;
|
||||
|
@ -246,6 +275,78 @@ static int hdc100x_write_raw(struct iio_dev *indio_dev,
|
|||
}
|
||||
}
|
||||
|
||||
static int hdc100x_buffer_postenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct hdc100x_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
/* Buffer is enabled. First set ACQ Mode, then attach poll func */
|
||||
mutex_lock(&data->lock);
|
||||
ret = hdc100x_update_config(data, HDC100X_REG_CONFIG_ACQ_MODE,
|
||||
HDC100X_REG_CONFIG_ACQ_MODE);
|
||||
mutex_unlock(&data->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return iio_triggered_buffer_postenable(indio_dev);
|
||||
}
|
||||
|
||||
static int hdc100x_buffer_predisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct hdc100x_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
/* First detach poll func, then reset ACQ mode. OK to disable buffer */
|
||||
ret = iio_triggered_buffer_predisable(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
ret = hdc100x_update_config(data, HDC100X_REG_CONFIG_ACQ_MODE, 0);
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops hdc_buffer_setup_ops = {
|
||||
.postenable = hdc100x_buffer_postenable,
|
||||
.predisable = hdc100x_buffer_predisable,
|
||||
};
|
||||
|
||||
static irqreturn_t hdc100x_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct hdc100x_data *data = iio_priv(indio_dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int delay = data->adc_int_us[0] + data->adc_int_us[1];
|
||||
int ret;
|
||||
s16 buf[8]; /* 2x s16 + padding + 8 byte timestamp */
|
||||
|
||||
/* dual read starts at temp register */
|
||||
mutex_lock(&data->lock);
|
||||
ret = i2c_smbus_write_byte(client, HDC100X_REG_TEMP);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "cannot start measurement\n");
|
||||
goto err;
|
||||
}
|
||||
usleep_range(delay, delay + 1000);
|
||||
|
||||
ret = i2c_master_recv(client, (u8 *)buf, 4);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "cannot read sensor data\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, buf,
|
||||
iio_get_time_ns(indio_dev));
|
||||
err:
|
||||
mutex_unlock(&data->lock);
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct iio_info hdc100x_info = {
|
||||
.read_raw = hdc100x_read_raw,
|
||||
.write_raw = hdc100x_write_raw,
|
||||
|
@ -258,6 +359,7 @@ static int hdc100x_probe(struct i2c_client *client,
|
|||
{
|
||||
struct iio_dev *indio_dev;
|
||||
struct hdc100x_data *data;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA |
|
||||
I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C))
|
||||
|
@ -279,12 +381,35 @@ static int hdc100x_probe(struct i2c_client *client,
|
|||
|
||||
indio_dev->channels = hdc100x_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(hdc100x_channels);
|
||||
indio_dev->available_scan_masks = hdc100x_scan_masks;
|
||||
|
||||
/* be sure we are in a known state */
|
||||
hdc100x_set_it_time(data, 0, hdc100x_int_time[0][0]);
|
||||
hdc100x_set_it_time(data, 1, hdc100x_int_time[1][0]);
|
||||
hdc100x_update_config(data, HDC100X_REG_CONFIG_ACQ_MODE, 0);
|
||||
|
||||
return devm_iio_device_register(&client->dev, indio_dev);
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
hdc100x_trigger_handler,
|
||||
&hdc_buffer_setup_ops);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "iio triggered buffer setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hdc100x_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id hdc100x_id[] = {
|
||||
|
@ -298,6 +423,7 @@ static struct i2c_driver hdc100x_driver = {
|
|||
.name = "hdc100x",
|
||||
},
|
||||
.probe = hdc100x_probe,
|
||||
.remove = hdc100x_remove,
|
||||
.id_table = hdc100x_id,
|
||||
};
|
||||
module_i2c_driver(hdc100x_driver);
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* STMicroelectronics hts221 sensor driver
|
||||
*
|
||||
* Copyright 2016 STMicroelectronics Inc.
|
||||
*
|
||||
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
|
||||
*
|
||||
* Licensed under the GPL-2.
|
||||
*/
|
||||
|
||||
#ifndef HTS221_H
|
||||
#define HTS221_H
|
||||
|
||||
#define HTS221_DEV_NAME "hts221"
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
#define HTS221_RX_MAX_LENGTH 8
|
||||
#define HTS221_TX_MAX_LENGTH 8
|
||||
|
||||
#define HTS221_DATA_SIZE 2
|
||||
|
||||
struct hts221_transfer_buffer {
|
||||
u8 rx_buf[HTS221_RX_MAX_LENGTH];
|
||||
u8 tx_buf[HTS221_TX_MAX_LENGTH] ____cacheline_aligned;
|
||||
};
|
||||
|
||||
struct hts221_transfer_function {
|
||||
int (*read)(struct device *dev, u8 addr, int len, u8 *data);
|
||||
int (*write)(struct device *dev, u8 addr, int len, u8 *data);
|
||||
};
|
||||
|
||||
#define HTS221_AVG_DEPTH 8
|
||||
struct hts221_avg_avl {
|
||||
u16 avg;
|
||||
u8 val;
|
||||
};
|
||||
|
||||
enum hts221_sensor_type {
|
||||
HTS221_SENSOR_H,
|
||||
HTS221_SENSOR_T,
|
||||
HTS221_SENSOR_MAX,
|
||||
};
|
||||
|
||||
struct hts221_sensor {
|
||||
u8 cur_avg_idx;
|
||||
int slope, b_gen;
|
||||
};
|
||||
|
||||
struct hts221_hw {
|
||||
const char *name;
|
||||
struct device *dev;
|
||||
|
||||
struct mutex lock;
|
||||
struct iio_trigger *trig;
|
||||
int irq;
|
||||
|
||||
struct hts221_sensor sensors[HTS221_SENSOR_MAX];
|
||||
|
||||
u8 odr;
|
||||
|
||||
const struct hts221_transfer_function *tf;
|
||||
struct hts221_transfer_buffer tb;
|
||||
};
|
||||
|
||||
int hts221_config_drdy(struct hts221_hw *hw, bool enable);
|
||||
int hts221_probe(struct iio_dev *iio_dev);
|
||||
int hts221_power_on(struct hts221_hw *hw);
|
||||
int hts221_power_off(struct hts221_hw *hw);
|
||||
int hts221_allocate_buffers(struct hts221_hw *hw);
|
||||
int hts221_allocate_trigger(struct hts221_hw *hw);
|
||||
|
||||
#endif /* HTS221_H */
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* STMicroelectronics hts221 sensor driver
|
||||
*
|
||||
* Copyright 2016 STMicroelectronics Inc.
|
||||
*
|
||||
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
|
||||
*
|
||||
* Licensed under the GPL-2.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqreturn.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/events.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
|
||||
#include "hts221.h"
|
||||
|
||||
#define HTS221_REG_STATUS_ADDR 0x27
|
||||
#define HTS221_RH_DRDY_MASK BIT(1)
|
||||
#define HTS221_TEMP_DRDY_MASK BIT(0)
|
||||
|
||||
static int hts221_trig_set_state(struct iio_trigger *trig, bool state)
|
||||
{
|
||||
struct iio_dev *iio_dev = iio_trigger_get_drvdata(trig);
|
||||
struct hts221_hw *hw = iio_priv(iio_dev);
|
||||
|
||||
return hts221_config_drdy(hw, state);
|
||||
}
|
||||
|
||||
static const struct iio_trigger_ops hts221_trigger_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.set_trigger_state = hts221_trig_set_state,
|
||||
};
|
||||
|
||||
static irqreturn_t hts221_trigger_handler_thread(int irq, void *private)
|
||||
{
|
||||
struct hts221_hw *hw = (struct hts221_hw *)private;
|
||||
u8 status;
|
||||
int err;
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_STATUS_ADDR, sizeof(status),
|
||||
&status);
|
||||
if (err < 0)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
/*
|
||||
* H_DA bit (humidity data available) is routed to DRDY line.
|
||||
* Humidity sample is computed after temperature one.
|
||||
* Here we can assume data channels are both available if H_DA bit
|
||||
* is set in status register
|
||||
*/
|
||||
if (!(status & HTS221_RH_DRDY_MASK))
|
||||
return IRQ_NONE;
|
||||
|
||||
iio_trigger_poll_chained(hw->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int hts221_allocate_trigger(struct hts221_hw *hw)
|
||||
{
|
||||
struct iio_dev *iio_dev = iio_priv_to_dev(hw);
|
||||
unsigned long irq_type;
|
||||
int err;
|
||||
|
||||
irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq));
|
||||
|
||||
switch (irq_type) {
|
||||
case IRQF_TRIGGER_HIGH:
|
||||
case IRQF_TRIGGER_RISING:
|
||||
break;
|
||||
default:
|
||||
dev_info(hw->dev,
|
||||
"mode %lx unsupported, using IRQF_TRIGGER_RISING\n",
|
||||
irq_type);
|
||||
irq_type = IRQF_TRIGGER_RISING;
|
||||
break;
|
||||
}
|
||||
|
||||
err = devm_request_threaded_irq(hw->dev, hw->irq, NULL,
|
||||
hts221_trigger_handler_thread,
|
||||
irq_type | IRQF_ONESHOT,
|
||||
hw->name, hw);
|
||||
if (err) {
|
||||
dev_err(hw->dev, "failed to request trigger irq %d\n",
|
||||
hw->irq);
|
||||
return err;
|
||||
}
|
||||
|
||||
hw->trig = devm_iio_trigger_alloc(hw->dev, "%s-trigger",
|
||||
iio_dev->name);
|
||||
if (!hw->trig)
|
||||
return -ENOMEM;
|
||||
|
||||
iio_trigger_set_drvdata(hw->trig, iio_dev);
|
||||
hw->trig->ops = &hts221_trigger_ops;
|
||||
hw->trig->dev.parent = hw->dev;
|
||||
iio_dev->trig = iio_trigger_get(hw->trig);
|
||||
|
||||
return devm_iio_trigger_register(hw->dev, hw->trig);
|
||||
}
|
||||
|
||||
static int hts221_buffer_preenable(struct iio_dev *iio_dev)
|
||||
{
|
||||
return hts221_power_on(iio_priv(iio_dev));
|
||||
}
|
||||
|
||||
static int hts221_buffer_postdisable(struct iio_dev *iio_dev)
|
||||
{
|
||||
return hts221_power_off(iio_priv(iio_dev));
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops hts221_buffer_ops = {
|
||||
.preenable = hts221_buffer_preenable,
|
||||
.postenable = iio_triggered_buffer_postenable,
|
||||
.predisable = iio_triggered_buffer_predisable,
|
||||
.postdisable = hts221_buffer_postdisable,
|
||||
};
|
||||
|
||||
static irqreturn_t hts221_buffer_handler_thread(int irq, void *p)
|
||||
{
|
||||
u8 buffer[ALIGN(2 * HTS221_DATA_SIZE, sizeof(s64)) + sizeof(s64)];
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *iio_dev = pf->indio_dev;
|
||||
struct hts221_hw *hw = iio_priv(iio_dev);
|
||||
struct iio_chan_spec const *ch;
|
||||
int err;
|
||||
|
||||
/* humidity data */
|
||||
ch = &iio_dev->channels[HTS221_SENSOR_H];
|
||||
err = hw->tf->read(hw->dev, ch->address, HTS221_DATA_SIZE,
|
||||
buffer);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
|
||||
/* temperature data */
|
||||
ch = &iio_dev->channels[HTS221_SENSOR_T];
|
||||
err = hw->tf->read(hw->dev, ch->address, HTS221_DATA_SIZE,
|
||||
buffer + HTS221_DATA_SIZE);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
|
||||
iio_push_to_buffers_with_timestamp(iio_dev, buffer,
|
||||
iio_get_time_ns(iio_dev));
|
||||
|
||||
out:
|
||||
iio_trigger_notify_done(hw->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int hts221_allocate_buffers(struct hts221_hw *hw)
|
||||
{
|
||||
return devm_iio_triggered_buffer_setup(hw->dev, iio_priv_to_dev(hw),
|
||||
NULL, hts221_buffer_handler_thread,
|
||||
&hts221_buffer_ops);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics hts221 buffer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,687 @@
|
|||
/*
|
||||
* STMicroelectronics hts221 sensor driver
|
||||
*
|
||||
* Copyright 2016 STMicroelectronics Inc.
|
||||
*
|
||||
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
|
||||
*
|
||||
* Licensed under the GPL-2.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#include "hts221.h"
|
||||
|
||||
#define HTS221_REG_WHOAMI_ADDR 0x0f
|
||||
#define HTS221_REG_WHOAMI_VAL 0xbc
|
||||
|
||||
#define HTS221_REG_CNTRL1_ADDR 0x20
|
||||
#define HTS221_REG_CNTRL2_ADDR 0x21
|
||||
#define HTS221_REG_CNTRL3_ADDR 0x22
|
||||
|
||||
#define HTS221_REG_AVG_ADDR 0x10
|
||||
#define HTS221_REG_H_OUT_L 0x28
|
||||
#define HTS221_REG_T_OUT_L 0x2a
|
||||
|
||||
#define HTS221_HUMIDITY_AVG_MASK 0x07
|
||||
#define HTS221_TEMP_AVG_MASK 0x38
|
||||
|
||||
#define HTS221_ODR_MASK 0x87
|
||||
#define HTS221_BDU_MASK BIT(2)
|
||||
|
||||
#define HTS221_DRDY_MASK BIT(2)
|
||||
|
||||
#define HTS221_ENABLE_SENSOR BIT(7)
|
||||
|
||||
#define HTS221_HUMIDITY_AVG_4 0x00 /* 0.4 %RH */
|
||||
#define HTS221_HUMIDITY_AVG_8 0x01 /* 0.3 %RH */
|
||||
#define HTS221_HUMIDITY_AVG_16 0x02 /* 0.2 %RH */
|
||||
#define HTS221_HUMIDITY_AVG_32 0x03 /* 0.15 %RH */
|
||||
#define HTS221_HUMIDITY_AVG_64 0x04 /* 0.1 %RH */
|
||||
#define HTS221_HUMIDITY_AVG_128 0x05 /* 0.07 %RH */
|
||||
#define HTS221_HUMIDITY_AVG_256 0x06 /* 0.05 %RH */
|
||||
#define HTS221_HUMIDITY_AVG_512 0x07 /* 0.03 %RH */
|
||||
|
||||
#define HTS221_TEMP_AVG_2 0x00 /* 0.08 degC */
|
||||
#define HTS221_TEMP_AVG_4 0x08 /* 0.05 degC */
|
||||
#define HTS221_TEMP_AVG_8 0x10 /* 0.04 degC */
|
||||
#define HTS221_TEMP_AVG_16 0x18 /* 0.03 degC */
|
||||
#define HTS221_TEMP_AVG_32 0x20 /* 0.02 degC */
|
||||
#define HTS221_TEMP_AVG_64 0x28 /* 0.015 degC */
|
||||
#define HTS221_TEMP_AVG_128 0x30 /* 0.01 degC */
|
||||
#define HTS221_TEMP_AVG_256 0x38 /* 0.007 degC */
|
||||
|
||||
/* calibration registers */
|
||||
#define HTS221_REG_0RH_CAL_X_H 0x36
|
||||
#define HTS221_REG_1RH_CAL_X_H 0x3a
|
||||
#define HTS221_REG_0RH_CAL_Y_H 0x30
|
||||
#define HTS221_REG_1RH_CAL_Y_H 0x31
|
||||
#define HTS221_REG_0T_CAL_X_L 0x3c
|
||||
#define HTS221_REG_1T_CAL_X_L 0x3e
|
||||
#define HTS221_REG_0T_CAL_Y_H 0x32
|
||||
#define HTS221_REG_1T_CAL_Y_H 0x33
|
||||
#define HTS221_REG_T1_T0_CAL_Y_H 0x35
|
||||
|
||||
struct hts221_odr {
|
||||
u8 hz;
|
||||
u8 val;
|
||||
};
|
||||
|
||||
struct hts221_avg {
|
||||
u8 addr;
|
||||
u8 mask;
|
||||
struct hts221_avg_avl avg_avl[HTS221_AVG_DEPTH];
|
||||
};
|
||||
|
||||
static const struct hts221_odr hts221_odr_table[] = {
|
||||
{ 1, 0x01 }, /* 1Hz */
|
||||
{ 7, 0x02 }, /* 7Hz */
|
||||
{ 13, 0x03 }, /* 12.5Hz */
|
||||
};
|
||||
|
||||
static const struct hts221_avg hts221_avg_list[] = {
|
||||
{
|
||||
.addr = HTS221_REG_AVG_ADDR,
|
||||
.mask = HTS221_HUMIDITY_AVG_MASK,
|
||||
.avg_avl = {
|
||||
{ 4, HTS221_HUMIDITY_AVG_4 },
|
||||
{ 8, HTS221_HUMIDITY_AVG_8 },
|
||||
{ 16, HTS221_HUMIDITY_AVG_16 },
|
||||
{ 32, HTS221_HUMIDITY_AVG_32 },
|
||||
{ 64, HTS221_HUMIDITY_AVG_64 },
|
||||
{ 128, HTS221_HUMIDITY_AVG_128 },
|
||||
{ 256, HTS221_HUMIDITY_AVG_256 },
|
||||
{ 512, HTS221_HUMIDITY_AVG_512 },
|
||||
},
|
||||
},
|
||||
{
|
||||
.addr = HTS221_REG_AVG_ADDR,
|
||||
.mask = HTS221_TEMP_AVG_MASK,
|
||||
.avg_avl = {
|
||||
{ 2, HTS221_TEMP_AVG_2 },
|
||||
{ 4, HTS221_TEMP_AVG_4 },
|
||||
{ 8, HTS221_TEMP_AVG_8 },
|
||||
{ 16, HTS221_TEMP_AVG_16 },
|
||||
{ 32, HTS221_TEMP_AVG_32 },
|
||||
{ 64, HTS221_TEMP_AVG_64 },
|
||||
{ 128, HTS221_TEMP_AVG_128 },
|
||||
{ 256, HTS221_TEMP_AVG_256 },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec hts221_channels[] = {
|
||||
{
|
||||
.type = IIO_HUMIDITYRELATIVE,
|
||||
.address = HTS221_REG_H_OUT_L,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_OFFSET) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
|
||||
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
|
||||
.scan_index = 0,
|
||||
.scan_type = {
|
||||
.sign = 's',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_LE,
|
||||
},
|
||||
},
|
||||
{
|
||||
.type = IIO_TEMP,
|
||||
.address = HTS221_REG_T_OUT_L,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_OFFSET) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
|
||||
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
|
||||
.scan_index = 1,
|
||||
.scan_type = {
|
||||
.sign = 's',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_LE,
|
||||
},
|
||||
},
|
||||
IIO_CHAN_SOFT_TIMESTAMP(2),
|
||||
};
|
||||
|
||||
static int hts221_write_with_mask(struct hts221_hw *hw, u8 addr, u8 mask,
|
||||
u8 val)
|
||||
{
|
||||
u8 data;
|
||||
int err;
|
||||
|
||||
mutex_lock(&hw->lock);
|
||||
|
||||
err = hw->tf->read(hw->dev, addr, sizeof(data), &data);
|
||||
if (err < 0) {
|
||||
dev_err(hw->dev, "failed to read %02x register\n", addr);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
data = (data & ~mask) | (val & mask);
|
||||
|
||||
err = hw->tf->write(hw->dev, addr, sizeof(data), &data);
|
||||
if (err < 0)
|
||||
dev_err(hw->dev, "failed to write %02x register\n", addr);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&hw->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hts221_check_whoami(struct hts221_hw *hw)
|
||||
{
|
||||
u8 data;
|
||||
int err;
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_WHOAMI_ADDR, sizeof(data),
|
||||
&data);
|
||||
if (err < 0) {
|
||||
dev_err(hw->dev, "failed to read whoami register\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (data != HTS221_REG_WHOAMI_VAL) {
|
||||
dev_err(hw->dev, "wrong whoami {%02x vs %02x}\n",
|
||||
data, HTS221_REG_WHOAMI_VAL);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hts221_config_drdy(struct hts221_hw *hw, bool enable)
|
||||
{
|
||||
u8 val = enable ? BIT(2) : 0;
|
||||
int err;
|
||||
|
||||
err = hts221_write_with_mask(hw, HTS221_REG_CNTRL3_ADDR,
|
||||
HTS221_DRDY_MASK, val);
|
||||
|
||||
return err < 0 ? err : 0;
|
||||
}
|
||||
|
||||
static int hts221_update_odr(struct hts221_hw *hw, u8 odr)
|
||||
{
|
||||
int i, err;
|
||||
u8 val;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(hts221_odr_table); i++)
|
||||
if (hts221_odr_table[i].hz == odr)
|
||||
break;
|
||||
|
||||
if (i == ARRAY_SIZE(hts221_odr_table))
|
||||
return -EINVAL;
|
||||
|
||||
val = HTS221_ENABLE_SENSOR | HTS221_BDU_MASK | hts221_odr_table[i].val;
|
||||
err = hts221_write_with_mask(hw, HTS221_REG_CNTRL1_ADDR,
|
||||
HTS221_ODR_MASK, val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
hw->odr = odr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hts221_update_avg(struct hts221_hw *hw,
|
||||
enum hts221_sensor_type type,
|
||||
u16 val)
|
||||
{
|
||||
int i, err;
|
||||
const struct hts221_avg *avg = &hts221_avg_list[type];
|
||||
|
||||
for (i = 0; i < HTS221_AVG_DEPTH; i++)
|
||||
if (avg->avg_avl[i].avg == val)
|
||||
break;
|
||||
|
||||
if (i == HTS221_AVG_DEPTH)
|
||||
return -EINVAL;
|
||||
|
||||
err = hts221_write_with_mask(hw, avg->addr, avg->mask,
|
||||
avg->avg_avl[i].val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
hw->sensors[type].cur_avg_idx = i;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t hts221_sysfs_sampling_freq(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int i;
|
||||
ssize_t len = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(hts221_odr_table); i++)
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len, "%d ",
|
||||
hts221_odr_table[i].hz);
|
||||
buf[len - 1] = '\n';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
hts221_sysfs_rh_oversampling_avail(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
const struct hts221_avg *avg = &hts221_avg_list[HTS221_SENSOR_H];
|
||||
ssize_t len = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(avg->avg_avl); i++)
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len, "%d ",
|
||||
avg->avg_avl[i].avg);
|
||||
buf[len - 1] = '\n';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
hts221_sysfs_temp_oversampling_avail(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
const struct hts221_avg *avg = &hts221_avg_list[HTS221_SENSOR_T];
|
||||
ssize_t len = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(avg->avg_avl); i++)
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len, "%d ",
|
||||
avg->avg_avl[i].avg);
|
||||
buf[len - 1] = '\n';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int hts221_power_on(struct hts221_hw *hw)
|
||||
{
|
||||
return hts221_update_odr(hw, hw->odr);
|
||||
}
|
||||
|
||||
int hts221_power_off(struct hts221_hw *hw)
|
||||
{
|
||||
u8 data[] = {0x00, 0x00};
|
||||
|
||||
return hw->tf->write(hw->dev, HTS221_REG_CNTRL1_ADDR, sizeof(data),
|
||||
data);
|
||||
}
|
||||
|
||||
static int hts221_parse_temp_caldata(struct hts221_hw *hw)
|
||||
{
|
||||
int err, *slope, *b_gen;
|
||||
s16 cal_x0, cal_x1, cal_y0, cal_y1;
|
||||
u8 cal0, cal1;
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_0T_CAL_Y_H,
|
||||
sizeof(cal0), &cal0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_T1_T0_CAL_Y_H,
|
||||
sizeof(cal1), &cal1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
cal_y0 = (le16_to_cpu(cal1 & 0x3) << 8) | cal0;
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_1T_CAL_Y_H,
|
||||
sizeof(cal0), &cal0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
cal_y1 = (((cal1 & 0xc) >> 2) << 8) | cal0;
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_0T_CAL_X_L, sizeof(cal_x0),
|
||||
(u8 *)&cal_x0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
cal_x0 = le16_to_cpu(cal_x0);
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_1T_CAL_X_L, sizeof(cal_x1),
|
||||
(u8 *)&cal_x1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
cal_x1 = le16_to_cpu(cal_x1);
|
||||
|
||||
slope = &hw->sensors[HTS221_SENSOR_T].slope;
|
||||
b_gen = &hw->sensors[HTS221_SENSOR_T].b_gen;
|
||||
|
||||
*slope = ((cal_y1 - cal_y0) * 8000) / (cal_x1 - cal_x0);
|
||||
*b_gen = (((s32)cal_x1 * cal_y0 - (s32)cal_x0 * cal_y1) * 1000) /
|
||||
(cal_x1 - cal_x0);
|
||||
*b_gen *= 8;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hts221_parse_rh_caldata(struct hts221_hw *hw)
|
||||
{
|
||||
int err, *slope, *b_gen;
|
||||
s16 cal_x0, cal_x1, cal_y0, cal_y1;
|
||||
u8 data;
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_0RH_CAL_Y_H, sizeof(data),
|
||||
&data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
cal_y0 = data;
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_1RH_CAL_Y_H, sizeof(data),
|
||||
&data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
cal_y1 = data;
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_0RH_CAL_X_H, sizeof(cal_x0),
|
||||
(u8 *)&cal_x0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
cal_x0 = le16_to_cpu(cal_x0);
|
||||
|
||||
err = hw->tf->read(hw->dev, HTS221_REG_1RH_CAL_X_H, sizeof(cal_x1),
|
||||
(u8 *)&cal_x1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
cal_x1 = le16_to_cpu(cal_x1);
|
||||
|
||||
slope = &hw->sensors[HTS221_SENSOR_H].slope;
|
||||
b_gen = &hw->sensors[HTS221_SENSOR_H].b_gen;
|
||||
|
||||
*slope = ((cal_y1 - cal_y0) * 8000) / (cal_x1 - cal_x0);
|
||||
*b_gen = (((s32)cal_x1 * cal_y0 - (s32)cal_x0 * cal_y1) * 1000) /
|
||||
(cal_x1 - cal_x0);
|
||||
*b_gen *= 8;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hts221_get_sensor_scale(struct hts221_hw *hw,
|
||||
enum iio_chan_type ch_type,
|
||||
int *val, int *val2)
|
||||
{
|
||||
s64 tmp;
|
||||
s32 rem, div, data;
|
||||
|
||||
switch (ch_type) {
|
||||
case IIO_HUMIDITYRELATIVE:
|
||||
data = hw->sensors[HTS221_SENSOR_H].slope;
|
||||
div = (1 << 4) * 1000;
|
||||
break;
|
||||
case IIO_TEMP:
|
||||
data = hw->sensors[HTS221_SENSOR_T].slope;
|
||||
div = (1 << 6) * 1000;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tmp = div_s64(data * 1000000000LL, div);
|
||||
tmp = div_s64_rem(tmp, 1000000000LL, &rem);
|
||||
|
||||
*val = tmp;
|
||||
*val2 = rem;
|
||||
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
}
|
||||
|
||||
static int hts221_get_sensor_offset(struct hts221_hw *hw,
|
||||
enum iio_chan_type ch_type,
|
||||
int *val, int *val2)
|
||||
{
|
||||
s64 tmp;
|
||||
s32 rem, div, data;
|
||||
|
||||
switch (ch_type) {
|
||||
case IIO_HUMIDITYRELATIVE:
|
||||
data = hw->sensors[HTS221_SENSOR_H].b_gen;
|
||||
div = hw->sensors[HTS221_SENSOR_H].slope;
|
||||
break;
|
||||
case IIO_TEMP:
|
||||
data = hw->sensors[HTS221_SENSOR_T].b_gen;
|
||||
div = hw->sensors[HTS221_SENSOR_T].slope;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tmp = div_s64(data * 1000000000LL, div);
|
||||
tmp = div_s64_rem(tmp, 1000000000LL, &rem);
|
||||
|
||||
*val = tmp;
|
||||
*val2 = rem;
|
||||
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
}
|
||||
|
||||
static int hts221_read_oneshot(struct hts221_hw *hw, u8 addr, int *val)
|
||||
{
|
||||
u8 data[HTS221_DATA_SIZE];
|
||||
int err;
|
||||
|
||||
err = hts221_power_on(hw);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
msleep(50);
|
||||
|
||||
err = hw->tf->read(hw->dev, addr, sizeof(data), data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
hts221_power_off(hw);
|
||||
|
||||
*val = (s16)get_unaligned_le16(data);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
static int hts221_read_raw(struct iio_dev *iio_dev,
|
||||
struct iio_chan_spec const *ch,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct hts221_hw *hw = iio_priv(iio_dev);
|
||||
int ret;
|
||||
|
||||
ret = iio_device_claim_direct_mode(iio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = hts221_read_oneshot(hw, ch->address, val);
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = hts221_get_sensor_scale(hw, ch->type, val, val2);
|
||||
break;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
ret = hts221_get_sensor_offset(hw, ch->type, val, val2);
|
||||
break;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*val = hw->odr;
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_CHAN_INFO_OVERSAMPLING_RATIO: {
|
||||
u8 idx;
|
||||
const struct hts221_avg *avg;
|
||||
|
||||
switch (ch->type) {
|
||||
case IIO_HUMIDITYRELATIVE:
|
||||
avg = &hts221_avg_list[HTS221_SENSOR_H];
|
||||
idx = hw->sensors[HTS221_SENSOR_H].cur_avg_idx;
|
||||
*val = avg->avg_avl[idx].avg;
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_TEMP:
|
||||
avg = &hts221_avg_list[HTS221_SENSOR_T];
|
||||
idx = hw->sensors[HTS221_SENSOR_T].cur_avg_idx;
|
||||
*val = avg->avg_avl[idx].avg;
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
iio_device_release_direct_mode(iio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hts221_write_raw(struct iio_dev *iio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct hts221_hw *hw = iio_priv(iio_dev);
|
||||
int ret;
|
||||
|
||||
ret = iio_device_claim_direct_mode(iio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret = hts221_update_odr(hw, val);
|
||||
break;
|
||||
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
||||
switch (chan->type) {
|
||||
case IIO_HUMIDITYRELATIVE:
|
||||
ret = hts221_update_avg(hw, HTS221_SENSOR_H, val);
|
||||
break;
|
||||
case IIO_TEMP:
|
||||
ret = hts221_update_avg(hw, HTS221_SENSOR_T, val);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
iio_device_release_direct_mode(iio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hts221_validate_trigger(struct iio_dev *iio_dev,
|
||||
struct iio_trigger *trig)
|
||||
{
|
||||
struct hts221_hw *hw = iio_priv(iio_dev);
|
||||
|
||||
return hw->trig == trig ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
static IIO_DEVICE_ATTR(in_humidity_oversampling_ratio_available, S_IRUGO,
|
||||
hts221_sysfs_rh_oversampling_avail, NULL, 0);
|
||||
static IIO_DEVICE_ATTR(in_temp_oversampling_ratio_available, S_IRUGO,
|
||||
hts221_sysfs_temp_oversampling_avail, NULL, 0);
|
||||
static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(hts221_sysfs_sampling_freq);
|
||||
|
||||
static struct attribute *hts221_attributes[] = {
|
||||
&iio_dev_attr_sampling_frequency_available.dev_attr.attr,
|
||||
&iio_dev_attr_in_humidity_oversampling_ratio_available.dev_attr.attr,
|
||||
&iio_dev_attr_in_temp_oversampling_ratio_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group hts221_attribute_group = {
|
||||
.attrs = hts221_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_info hts221_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.attrs = &hts221_attribute_group,
|
||||
.read_raw = hts221_read_raw,
|
||||
.write_raw = hts221_write_raw,
|
||||
.validate_trigger = hts221_validate_trigger,
|
||||
};
|
||||
|
||||
static const unsigned long hts221_scan_masks[] = {0x3, 0x0};
|
||||
|
||||
int hts221_probe(struct iio_dev *iio_dev)
|
||||
{
|
||||
struct hts221_hw *hw = iio_priv(iio_dev);
|
||||
int err;
|
||||
u8 data;
|
||||
|
||||
mutex_init(&hw->lock);
|
||||
|
||||
err = hts221_check_whoami(hw);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
hw->odr = hts221_odr_table[0].hz;
|
||||
|
||||
iio_dev->modes = INDIO_DIRECT_MODE;
|
||||
iio_dev->dev.parent = hw->dev;
|
||||
iio_dev->available_scan_masks = hts221_scan_masks;
|
||||
iio_dev->channels = hts221_channels;
|
||||
iio_dev->num_channels = ARRAY_SIZE(hts221_channels);
|
||||
iio_dev->name = HTS221_DEV_NAME;
|
||||
iio_dev->info = &hts221_info;
|
||||
|
||||
/* configure humidity sensor */
|
||||
err = hts221_parse_rh_caldata(hw);
|
||||
if (err < 0) {
|
||||
dev_err(hw->dev, "failed to get rh calibration data\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
data = hts221_avg_list[HTS221_SENSOR_H].avg_avl[3].avg;
|
||||
err = hts221_update_avg(hw, HTS221_SENSOR_H, data);
|
||||
if (err < 0) {
|
||||
dev_err(hw->dev, "failed to set rh oversampling ratio\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* configure temperature sensor */
|
||||
err = hts221_parse_temp_caldata(hw);
|
||||
if (err < 0) {
|
||||
dev_err(hw->dev,
|
||||
"failed to get temperature calibration data\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
data = hts221_avg_list[HTS221_SENSOR_T].avg_avl[3].avg;
|
||||
err = hts221_update_avg(hw, HTS221_SENSOR_T, data);
|
||||
if (err < 0) {
|
||||
dev_err(hw->dev,
|
||||
"failed to set temperature oversampling ratio\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (hw->irq > 0) {
|
||||
err = hts221_allocate_buffers(hw);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = hts221_allocate_trigger(hw);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return devm_iio_device_register(hw->dev, iio_dev);
|
||||
}
|
||||
EXPORT_SYMBOL(hts221_probe);
|
||||
|
||||
MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics hts221 sensor driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* STMicroelectronics hts221 i2c driver
|
||||
*
|
||||
* Copyright 2016 STMicroelectronics Inc.
|
||||
*
|
||||
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
|
||||
*
|
||||
* Licensed under the GPL-2.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include "hts221.h"
|
||||
|
||||
#define I2C_AUTO_INCREMENT 0x80
|
||||
|
||||
static int hts221_i2c_read(struct device *dev, u8 addr, int len, u8 *data)
|
||||
{
|
||||
struct i2c_msg msg[2];
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
|
||||
if (len > 1)
|
||||
addr |= I2C_AUTO_INCREMENT;
|
||||
|
||||
msg[0].addr = client->addr;
|
||||
msg[0].flags = client->flags;
|
||||
msg[0].len = 1;
|
||||
msg[0].buf = &addr;
|
||||
|
||||
msg[1].addr = client->addr;
|
||||
msg[1].flags = client->flags | I2C_M_RD;
|
||||
msg[1].len = len;
|
||||
msg[1].buf = data;
|
||||
|
||||
return i2c_transfer(client->adapter, msg, 2);
|
||||
}
|
||||
|
||||
static int hts221_i2c_write(struct device *dev, u8 addr, int len, u8 *data)
|
||||
{
|
||||
u8 send[len + 1];
|
||||
struct i2c_msg msg;
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
|
||||
if (len > 1)
|
||||
addr |= I2C_AUTO_INCREMENT;
|
||||
|
||||
send[0] = addr;
|
||||
memcpy(&send[1], data, len * sizeof(u8));
|
||||
|
||||
msg.addr = client->addr;
|
||||
msg.flags = client->flags;
|
||||
msg.len = len + 1;
|
||||
msg.buf = send;
|
||||
|
||||
return i2c_transfer(client->adapter, &msg, 1);
|
||||
}
|
||||
|
||||
static const struct hts221_transfer_function hts221_transfer_fn = {
|
||||
.read = hts221_i2c_read,
|
||||
.write = hts221_i2c_write,
|
||||
};
|
||||
|
||||
static int hts221_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct hts221_hw *hw;
|
||||
struct iio_dev *iio_dev;
|
||||
|
||||
iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*hw));
|
||||
if (!iio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, iio_dev);
|
||||
|
||||
hw = iio_priv(iio_dev);
|
||||
hw->name = client->name;
|
||||
hw->dev = &client->dev;
|
||||
hw->irq = client->irq;
|
||||
hw->tf = &hts221_transfer_fn;
|
||||
|
||||
return hts221_probe(iio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id hts221_i2c_of_match[] = {
|
||||
{ .compatible = "st,hts221", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, hts221_i2c_of_match);
|
||||
|
||||
static const struct i2c_device_id hts221_i2c_id_table[] = {
|
||||
{ HTS221_DEV_NAME },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, hts221_i2c_id_table);
|
||||
|
||||
static struct i2c_driver hts221_driver = {
|
||||
.driver = {
|
||||
.name = "hts221_i2c",
|
||||
.of_match_table = of_match_ptr(hts221_i2c_of_match),
|
||||
},
|
||||
.probe = hts221_i2c_probe,
|
||||
.id_table = hts221_i2c_id_table,
|
||||
};
|
||||
module_i2c_driver(hts221_driver);
|
||||
|
||||
MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics hts221 i2c driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* STMicroelectronics hts221 spi driver
|
||||
*
|
||||
* Copyright 2016 STMicroelectronics Inc.
|
||||
*
|
||||
* Lorenzo Bianconi <lorenzo.bianconi@st.com>
|
||||
*
|
||||
* Licensed under the GPL-2.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/slab.h>
|
||||
#include "hts221.h"
|
||||
|
||||
#define SENSORS_SPI_READ 0x80
|
||||
#define SPI_AUTO_INCREMENT 0x40
|
||||
|
||||
static int hts221_spi_read(struct device *dev, u8 addr, int len, u8 *data)
|
||||
{
|
||||
int err;
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
struct iio_dev *iio_dev = spi_get_drvdata(spi);
|
||||
struct hts221_hw *hw = iio_priv(iio_dev);
|
||||
|
||||
struct spi_transfer xfers[] = {
|
||||
{
|
||||
.tx_buf = hw->tb.tx_buf,
|
||||
.bits_per_word = 8,
|
||||
.len = 1,
|
||||
},
|
||||
{
|
||||
.rx_buf = hw->tb.rx_buf,
|
||||
.bits_per_word = 8,
|
||||
.len = len,
|
||||
}
|
||||
};
|
||||
|
||||
if (len > 1)
|
||||
addr |= SPI_AUTO_INCREMENT;
|
||||
hw->tb.tx_buf[0] = addr | SENSORS_SPI_READ;
|
||||
|
||||
err = spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
memcpy(data, hw->tb.rx_buf, len * sizeof(u8));
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int hts221_spi_write(struct device *dev, u8 addr, int len, u8 *data)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
struct iio_dev *iio_dev = spi_get_drvdata(spi);
|
||||
struct hts221_hw *hw = iio_priv(iio_dev);
|
||||
|
||||
struct spi_transfer xfers = {
|
||||
.tx_buf = hw->tb.tx_buf,
|
||||
.bits_per_word = 8,
|
||||
.len = len + 1,
|
||||
};
|
||||
|
||||
if (len >= HTS221_TX_MAX_LENGTH)
|
||||
return -ENOMEM;
|
||||
|
||||
if (len > 1)
|
||||
addr |= SPI_AUTO_INCREMENT;
|
||||
hw->tb.tx_buf[0] = addr;
|
||||
memcpy(&hw->tb.tx_buf[1], data, len);
|
||||
|
||||
return spi_sync_transfer(spi, &xfers, 1);
|
||||
}
|
||||
|
||||
static const struct hts221_transfer_function hts221_transfer_fn = {
|
||||
.read = hts221_spi_read,
|
||||
.write = hts221_spi_write,
|
||||
};
|
||||
|
||||
static int hts221_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct hts221_hw *hw;
|
||||
struct iio_dev *iio_dev;
|
||||
|
||||
iio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*hw));
|
||||
if (!iio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
spi_set_drvdata(spi, iio_dev);
|
||||
|
||||
hw = iio_priv(iio_dev);
|
||||
hw->name = spi->modalias;
|
||||
hw->dev = &spi->dev;
|
||||
hw->irq = spi->irq;
|
||||
hw->tf = &hts221_transfer_fn;
|
||||
|
||||
return hts221_probe(iio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id hts221_spi_of_match[] = {
|
||||
{ .compatible = "st,hts221", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, hts221_spi_of_match);
|
||||
|
||||
static const struct spi_device_id hts221_spi_id_table[] = {
|
||||
{ HTS221_DEV_NAME },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, hts221_spi_id_table);
|
||||
|
||||
static struct spi_driver hts221_driver = {
|
||||
.driver = {
|
||||
.name = "hts221_spi",
|
||||
.of_match_table = of_match_ptr(hts221_spi_of_match),
|
||||
},
|
||||
.probe = hts221_spi_probe,
|
||||
.id_table = hts221_spi_id_table,
|
||||
};
|
||||
module_spi_driver(hts221_driver);
|
||||
|
||||
MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics hts221 spi driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -154,8 +154,17 @@ static const struct i2c_device_id si7020_id[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(i2c, si7020_id);
|
||||
|
||||
static const struct of_device_id si7020_dt_ids[] = {
|
||||
{ .compatible = "silabs,si7020" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, si7020_dt_ids);
|
||||
|
||||
static struct i2c_driver si7020_driver = {
|
||||
.driver.name = "si7020",
|
||||
.driver = {
|
||||
.name = "si7020",
|
||||
.of_match_table = of_match_ptr(si7020_dt_ids),
|
||||
},
|
||||
.probe = si7020_probe,
|
||||
.id_table = si7020_id,
|
||||
};
|
||||
|
|
|
@ -398,7 +398,8 @@ static irqreturn_t bmi160_trigger_handler(int irq, void *p)
|
|||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct bmi160_data *data = iio_priv(indio_dev);
|
||||
s16 buf[16]; /* 3 sens x 3 axis x s16 + 3 x s16 pad + 4 x s16 tstamp */
|
||||
__le16 buf[16];
|
||||
/* 3 sens x 3 axis x __le16 + 3 x __le16 pad + 4 x __le16 tstamp */
|
||||
int i, ret, j = 0, base = BMI160_REG_DATA_MAGN_XOUT_L;
|
||||
__le16 sample;
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ static int inv_mpu_probe(struct i2c_client *client,
|
|||
|
||||
st = iio_priv(dev_get_drvdata(&client->dev));
|
||||
st->muxc = i2c_mux_alloc(client->adapter, &client->dev,
|
||||
1, 0, I2C_MUX_LOCKED,
|
||||
1, 0, I2C_MUX_LOCKED | I2C_MUX_GATE,
|
||||
inv_mpu6050_select_bypass,
|
||||
inv_mpu6050_deselect_bypass);
|
||||
if (!st->muxc) {
|
||||
|
|
|
@ -307,10 +307,9 @@ static int iio_scan_mask_set(struct iio_dev *indio_dev,
|
|||
const unsigned long *mask;
|
||||
unsigned long *trialmask;
|
||||
|
||||
trialmask = kmalloc(sizeof(*trialmask)*
|
||||
BITS_TO_LONGS(indio_dev->masklength),
|
||||
trialmask = kmalloc_array(BITS_TO_LONGS(indio_dev->masklength),
|
||||
sizeof(*trialmask),
|
||||
GFP_KERNEL);
|
||||
|
||||
if (trialmask == NULL)
|
||||
return -ENOMEM;
|
||||
if (!indio_dev->masklength) {
|
||||
|
|
|
@ -81,6 +81,8 @@ static const char * const iio_chan_type_name_spec[] = {
|
|||
[IIO_PH] = "ph",
|
||||
[IIO_UVINDEX] = "uvindex",
|
||||
[IIO_ELECTRICALCONDUCTIVITY] = "electricalconductivity",
|
||||
[IIO_COUNT] = "count",
|
||||
[IIO_INDEX] = "index",
|
||||
};
|
||||
|
||||
static const char * const iio_modifier_names[] = {
|
||||
|
@ -575,9 +577,62 @@ int of_iio_read_mount_matrix(const struct device *dev,
|
|||
#endif
|
||||
EXPORT_SYMBOL(of_iio_read_mount_matrix);
|
||||
|
||||
static ssize_t __iio_format_value(char *buf, size_t len, unsigned int type,
|
||||
int size, const int *vals)
|
||||
{
|
||||
unsigned long long tmp;
|
||||
int tmp0, tmp1;
|
||||
bool scale_db = false;
|
||||
|
||||
switch (type) {
|
||||
case IIO_VAL_INT:
|
||||
return snprintf(buf, len, "%d", vals[0]);
|
||||
case IIO_VAL_INT_PLUS_MICRO_DB:
|
||||
scale_db = true;
|
||||
case IIO_VAL_INT_PLUS_MICRO:
|
||||
if (vals[1] < 0)
|
||||
return snprintf(buf, len, "-%d.%06u%s", abs(vals[0]),
|
||||
-vals[1], scale_db ? " dB" : "");
|
||||
else
|
||||
return snprintf(buf, len, "%d.%06u%s", vals[0], vals[1],
|
||||
scale_db ? " dB" : "");
|
||||
case IIO_VAL_INT_PLUS_NANO:
|
||||
if (vals[1] < 0)
|
||||
return snprintf(buf, len, "-%d.%09u", abs(vals[0]),
|
||||
-vals[1]);
|
||||
else
|
||||
return snprintf(buf, len, "%d.%09u", vals[0], vals[1]);
|
||||
case IIO_VAL_FRACTIONAL:
|
||||
tmp = div_s64((s64)vals[0] * 1000000000LL, vals[1]);
|
||||
tmp1 = vals[1];
|
||||
tmp0 = (int)div_s64_rem(tmp, 1000000000, &tmp1);
|
||||
return snprintf(buf, len, "%d.%09u", tmp0, abs(tmp1));
|
||||
case IIO_VAL_FRACTIONAL_LOG2:
|
||||
tmp = (s64)vals[0] * 1000000000LL >> vals[1];
|
||||
tmp1 = do_div(tmp, 1000000000LL);
|
||||
tmp0 = tmp;
|
||||
return snprintf(buf, len, "%d.%09u", tmp0, tmp1);
|
||||
case IIO_VAL_INT_MULTIPLE:
|
||||
{
|
||||
int i;
|
||||
int l = 0;
|
||||
|
||||
for (i = 0; i < size; ++i) {
|
||||
l += snprintf(&buf[l], len - l, "%d ", vals[i]);
|
||||
if (l >= len)
|
||||
break;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* iio_format_value() - Formats a IIO value into its string representation
|
||||
* @buf: The buffer to which the formatted value gets written
|
||||
* which is assumed to be big enough (i.e. PAGE_SIZE).
|
||||
* @type: One of the IIO_VAL_... constants. This decides how the val
|
||||
* and val2 parameters are formatted.
|
||||
* @size: Number of IIO value entries contained in vals
|
||||
|
@ -590,50 +645,13 @@ EXPORT_SYMBOL(of_iio_read_mount_matrix);
|
|||
*/
|
||||
ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals)
|
||||
{
|
||||
unsigned long long tmp;
|
||||
bool scale_db = false;
|
||||
ssize_t len;
|
||||
|
||||
switch (type) {
|
||||
case IIO_VAL_INT:
|
||||
return sprintf(buf, "%d\n", vals[0]);
|
||||
case IIO_VAL_INT_PLUS_MICRO_DB:
|
||||
scale_db = true;
|
||||
case IIO_VAL_INT_PLUS_MICRO:
|
||||
if (vals[1] < 0)
|
||||
return sprintf(buf, "-%d.%06u%s\n", abs(vals[0]),
|
||||
-vals[1], scale_db ? " dB" : "");
|
||||
else
|
||||
return sprintf(buf, "%d.%06u%s\n", vals[0], vals[1],
|
||||
scale_db ? " dB" : "");
|
||||
case IIO_VAL_INT_PLUS_NANO:
|
||||
if (vals[1] < 0)
|
||||
return sprintf(buf, "-%d.%09u\n", abs(vals[0]),
|
||||
-vals[1]);
|
||||
else
|
||||
return sprintf(buf, "%d.%09u\n", vals[0], vals[1]);
|
||||
case IIO_VAL_FRACTIONAL:
|
||||
tmp = div_s64((s64)vals[0] * 1000000000LL, vals[1]);
|
||||
vals[0] = (int)div_s64_rem(tmp, 1000000000, &vals[1]);
|
||||
return sprintf(buf, "%d.%09u\n", vals[0], abs(vals[1]));
|
||||
case IIO_VAL_FRACTIONAL_LOG2:
|
||||
tmp = (s64)vals[0] * 1000000000LL >> vals[1];
|
||||
vals[1] = do_div(tmp, 1000000000LL);
|
||||
vals[0] = tmp;
|
||||
return sprintf(buf, "%d.%09u\n", vals[0], vals[1]);
|
||||
case IIO_VAL_INT_MULTIPLE:
|
||||
{
|
||||
int i;
|
||||
int len = 0;
|
||||
len = __iio_format_value(buf, PAGE_SIZE, type, size, vals);
|
||||
if (len >= PAGE_SIZE - 1)
|
||||
return -EFBIG;
|
||||
|
||||
for (i = 0; i < size; ++i)
|
||||
len += snprintf(&buf[len], PAGE_SIZE - len, "%d ",
|
||||
vals[i]);
|
||||
len += snprintf(&buf[len], PAGE_SIZE - len, "\n");
|
||||
return len;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return len + sprintf(buf + len, "\n");
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_format_value);
|
||||
|
||||
|
@ -662,6 +680,119 @@ static ssize_t iio_read_channel_info(struct device *dev,
|
|||
return iio_format_value(buf, ret, val_len, vals);
|
||||
}
|
||||
|
||||
static ssize_t iio_format_avail_list(char *buf, const int *vals,
|
||||
int type, int length)
|
||||
{
|
||||
int i;
|
||||
ssize_t len = 0;
|
||||
|
||||
switch (type) {
|
||||
case IIO_VAL_INT:
|
||||
for (i = 0; i < length; i++) {
|
||||
len += __iio_format_value(buf + len, PAGE_SIZE - len,
|
||||
type, 1, &vals[i]);
|
||||
if (len >= PAGE_SIZE)
|
||||
return -EFBIG;
|
||||
if (i < length - 1)
|
||||
len += snprintf(buf + len, PAGE_SIZE - len,
|
||||
" ");
|
||||
else
|
||||
len += snprintf(buf + len, PAGE_SIZE - len,
|
||||
"\n");
|
||||
if (len >= PAGE_SIZE)
|
||||
return -EFBIG;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
for (i = 0; i < length / 2; i++) {
|
||||
len += __iio_format_value(buf + len, PAGE_SIZE - len,
|
||||
type, 2, &vals[i * 2]);
|
||||
if (len >= PAGE_SIZE)
|
||||
return -EFBIG;
|
||||
if (i < length / 2 - 1)
|
||||
len += snprintf(buf + len, PAGE_SIZE - len,
|
||||
" ");
|
||||
else
|
||||
len += snprintf(buf + len, PAGE_SIZE - len,
|
||||
"\n");
|
||||
if (len >= PAGE_SIZE)
|
||||
return -EFBIG;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t iio_format_avail_range(char *buf, const int *vals, int type)
|
||||
{
|
||||
int i;
|
||||
ssize_t len;
|
||||
|
||||
len = snprintf(buf, PAGE_SIZE, "[");
|
||||
switch (type) {
|
||||
case IIO_VAL_INT:
|
||||
for (i = 0; i < 3; i++) {
|
||||
len += __iio_format_value(buf + len, PAGE_SIZE - len,
|
||||
type, 1, &vals[i]);
|
||||
if (len >= PAGE_SIZE)
|
||||
return -EFBIG;
|
||||
if (i < 2)
|
||||
len += snprintf(buf + len, PAGE_SIZE - len,
|
||||
" ");
|
||||
else
|
||||
len += snprintf(buf + len, PAGE_SIZE - len,
|
||||
"]\n");
|
||||
if (len >= PAGE_SIZE)
|
||||
return -EFBIG;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
for (i = 0; i < 3; i++) {
|
||||
len += __iio_format_value(buf + len, PAGE_SIZE - len,
|
||||
type, 2, &vals[i * 2]);
|
||||
if (len >= PAGE_SIZE)
|
||||
return -EFBIG;
|
||||
if (i < 2)
|
||||
len += snprintf(buf + len, PAGE_SIZE - len,
|
||||
" ");
|
||||
else
|
||||
len += snprintf(buf + len, PAGE_SIZE - len,
|
||||
"]\n");
|
||||
if (len >= PAGE_SIZE)
|
||||
return -EFBIG;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t iio_read_channel_info_avail(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
|
||||
const int *vals;
|
||||
int ret;
|
||||
int length;
|
||||
int type;
|
||||
|
||||
ret = indio_dev->info->read_avail(indio_dev, this_attr->c,
|
||||
&vals, &type, &length,
|
||||
this_attr->address);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
switch (ret) {
|
||||
case IIO_AVAIL_LIST:
|
||||
return iio_format_avail_list(buf, vals, type, length);
|
||||
case IIO_AVAIL_RANGE:
|
||||
return iio_format_avail_range(buf, vals, type);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* iio_str_to_fixpoint() - Parse a fixed-point number from a string
|
||||
* @str: The string to parse
|
||||
|
@ -978,6 +1109,40 @@ static int iio_device_add_info_mask_type(struct iio_dev *indio_dev,
|
|||
return attrcount;
|
||||
}
|
||||
|
||||
static int iio_device_add_info_mask_type_avail(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
enum iio_shared_by shared_by,
|
||||
const long *infomask)
|
||||
{
|
||||
int i, ret, attrcount = 0;
|
||||
char *avail_postfix;
|
||||
|
||||
for_each_set_bit(i, infomask, sizeof(infomask) * 8) {
|
||||
avail_postfix = kasprintf(GFP_KERNEL,
|
||||
"%s_available",
|
||||
iio_chan_info_postfix[i]);
|
||||
if (!avail_postfix)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = __iio_add_chan_devattr(avail_postfix,
|
||||
chan,
|
||||
&iio_read_channel_info_avail,
|
||||
NULL,
|
||||
i,
|
||||
shared_by,
|
||||
&indio_dev->dev,
|
||||
&indio_dev->channel_attr_list);
|
||||
kfree(avail_postfix);
|
||||
if ((ret == -EBUSY) && (shared_by != IIO_SEPARATE))
|
||||
continue;
|
||||
else if (ret < 0)
|
||||
return ret;
|
||||
attrcount++;
|
||||
}
|
||||
|
||||
return attrcount;
|
||||
}
|
||||
|
||||
static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan)
|
||||
{
|
||||
|
@ -993,6 +1158,14 @@ static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev,
|
|||
return ret;
|
||||
attrcount += ret;
|
||||
|
||||
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
|
||||
IIO_SEPARATE,
|
||||
&chan->
|
||||
info_mask_separate_available);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
attrcount += ret;
|
||||
|
||||
ret = iio_device_add_info_mask_type(indio_dev, chan,
|
||||
IIO_SHARED_BY_TYPE,
|
||||
&chan->info_mask_shared_by_type);
|
||||
|
@ -1000,6 +1173,14 @@ static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev,
|
|||
return ret;
|
||||
attrcount += ret;
|
||||
|
||||
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
|
||||
IIO_SHARED_BY_TYPE,
|
||||
&chan->
|
||||
info_mask_shared_by_type_available);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
attrcount += ret;
|
||||
|
||||
ret = iio_device_add_info_mask_type(indio_dev, chan,
|
||||
IIO_SHARED_BY_DIR,
|
||||
&chan->info_mask_shared_by_dir);
|
||||
|
@ -1007,6 +1188,13 @@ static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev,
|
|||
return ret;
|
||||
attrcount += ret;
|
||||
|
||||
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
|
||||
IIO_SHARED_BY_DIR,
|
||||
&chan->info_mask_shared_by_dir_available);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
attrcount += ret;
|
||||
|
||||
ret = iio_device_add_info_mask_type(indio_dev, chan,
|
||||
IIO_SHARED_BY_ALL,
|
||||
&chan->info_mask_shared_by_all);
|
||||
|
@ -1014,6 +1202,13 @@ static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev,
|
|||
return ret;
|
||||
attrcount += ret;
|
||||
|
||||
ret = iio_device_add_info_mask_type_avail(indio_dev, chan,
|
||||
IIO_SHARED_BY_ALL,
|
||||
&chan->info_mask_shared_by_all_available);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
attrcount += ret;
|
||||
|
||||
if (chan->ext_info) {
|
||||
unsigned int i = 0;
|
||||
for (ext_info = chan->ext_info; ext_info->name; ext_info++) {
|
||||
|
|
|
@ -717,6 +717,27 @@ bool iio_trigger_using_own(struct iio_dev *indio_dev)
|
|||
}
|
||||
EXPORT_SYMBOL(iio_trigger_using_own);
|
||||
|
||||
/**
|
||||
* iio_trigger_validate_own_device - Check if a trigger and IIO device belong to
|
||||
* the same device
|
||||
* @trig: The IIO trigger to check
|
||||
* @indio_dev: the IIO device to check
|
||||
*
|
||||
* This function can be used as the validate_device callback for triggers that
|
||||
* can only be attached to their own device.
|
||||
*
|
||||
* Return: 0 if both the trigger and the IIO device belong to the same
|
||||
* device, -EINVAL otherwise.
|
||||
*/
|
||||
int iio_trigger_validate_own_device(struct iio_trigger *trig,
|
||||
struct iio_dev *indio_dev)
|
||||
{
|
||||
if (indio_dev->dev.parent != trig->dev.parent)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(iio_trigger_validate_own_device);
|
||||
|
||||
void iio_device_register_trigger_consumer(struct iio_dev *indio_dev)
|
||||
{
|
||||
indio_dev->groups[indio_dev->groupcounter++] =
|
||||
|
|
|
@ -658,6 +658,31 @@ err_unlock:
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(iio_convert_raw_to_processed);
|
||||
|
||||
static int iio_read_channel_attribute(struct iio_channel *chan,
|
||||
int *val, int *val2,
|
||||
enum iio_chan_info_enum attribute)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chan->indio_dev->info_exist_lock);
|
||||
if (chan->indio_dev->info == NULL) {
|
||||
ret = -ENODEV;
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
ret = iio_channel_read(chan, val, val2, attribute);
|
||||
err_unlock:
|
||||
mutex_unlock(&chan->indio_dev->info_exist_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int iio_read_channel_offset(struct iio_channel *chan, int *val, int *val2)
|
||||
{
|
||||
return iio_read_channel_attribute(chan, val, val2, IIO_CHAN_INFO_OFFSET);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_read_channel_offset);
|
||||
|
||||
int iio_read_channel_processed(struct iio_channel *chan, int *val)
|
||||
{
|
||||
int ret;
|
||||
|
@ -686,22 +711,114 @@ err_unlock:
|
|||
EXPORT_SYMBOL_GPL(iio_read_channel_processed);
|
||||
|
||||
int iio_read_channel_scale(struct iio_channel *chan, int *val, int *val2)
|
||||
{
|
||||
return iio_read_channel_attribute(chan, val, val2, IIO_CHAN_INFO_SCALE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_read_channel_scale);
|
||||
|
||||
static int iio_channel_read_avail(struct iio_channel *chan,
|
||||
const int **vals, int *type, int *length,
|
||||
enum iio_chan_info_enum info)
|
||||
{
|
||||
if (!iio_channel_has_available(chan->channel, info))
|
||||
return -EINVAL;
|
||||
|
||||
return chan->indio_dev->info->read_avail(chan->indio_dev, chan->channel,
|
||||
vals, type, length, info);
|
||||
}
|
||||
|
||||
int iio_read_avail_channel_raw(struct iio_channel *chan,
|
||||
const int **vals, int *length)
|
||||
{
|
||||
int ret;
|
||||
int type;
|
||||
|
||||
mutex_lock(&chan->indio_dev->info_exist_lock);
|
||||
if (chan->indio_dev->info == NULL) {
|
||||
if (!chan->indio_dev->info) {
|
||||
ret = -ENODEV;
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
ret = iio_channel_read(chan, val, val2, IIO_CHAN_INFO_SCALE);
|
||||
ret = iio_channel_read_avail(chan,
|
||||
vals, &type, length, IIO_CHAN_INFO_RAW);
|
||||
err_unlock:
|
||||
mutex_unlock(&chan->indio_dev->info_exist_lock);
|
||||
|
||||
if (ret >= 0 && type != IIO_VAL_INT) {
|
||||
/* raw values are assumed to be IIO_VAL_INT */
|
||||
ret = -EINVAL;
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_read_avail_channel_raw);
|
||||
|
||||
static int iio_channel_read_max(struct iio_channel *chan,
|
||||
int *val, int *val2, int *type,
|
||||
enum iio_chan_info_enum info)
|
||||
{
|
||||
int unused;
|
||||
const int *vals;
|
||||
int length;
|
||||
int ret;
|
||||
|
||||
if (!val2)
|
||||
val2 = &unused;
|
||||
|
||||
ret = iio_channel_read_avail(chan, &vals, type, &length, info);
|
||||
switch (ret) {
|
||||
case IIO_AVAIL_RANGE:
|
||||
switch (*type) {
|
||||
case IIO_VAL_INT:
|
||||
*val = vals[2];
|
||||
break;
|
||||
default:
|
||||
*val = vals[4];
|
||||
*val2 = vals[5];
|
||||
}
|
||||
return 0;
|
||||
|
||||
case IIO_AVAIL_LIST:
|
||||
if (length <= 0)
|
||||
return -EINVAL;
|
||||
switch (*type) {
|
||||
case IIO_VAL_INT:
|
||||
*val = vals[--length];
|
||||
while (length) {
|
||||
if (vals[--length] > *val)
|
||||
*val = vals[length];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* FIXME: learn about max for other iio values */
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
int iio_read_max_channel_raw(struct iio_channel *chan, int *val)
|
||||
{
|
||||
int ret;
|
||||
int type;
|
||||
|
||||
mutex_lock(&chan->indio_dev->info_exist_lock);
|
||||
if (!chan->indio_dev->info) {
|
||||
ret = -ENODEV;
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
ret = iio_channel_read_max(chan, val, NULL, &type, IIO_CHAN_INFO_RAW);
|
||||
err_unlock:
|
||||
mutex_unlock(&chan->indio_dev->info_exist_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_read_channel_scale);
|
||||
EXPORT_SYMBOL_GPL(iio_read_max_channel_raw);
|
||||
|
||||
int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type)
|
||||
{
|
||||
|
|
|
@ -140,6 +140,18 @@ config GP2AP020A00F
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called gp2ap020a00f.
|
||||
|
||||
config SENSORS_ISL29018
|
||||
tristate "Intersil 29018 light and proximity sensor"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for ambient light sensing and
|
||||
proximity infrared sensing from Intersil ISL29018.
|
||||
This driver will provide the measurements of ambient light intensity
|
||||
in lux, proximity infrared sensing and normal infrared sensing.
|
||||
Data from sensor is accessible via sysfs.
|
||||
|
||||
config ISL29125
|
||||
tristate "Intersil ISL29125 digital color light sensor"
|
||||
depends on I2C
|
||||
|
@ -326,6 +338,13 @@ config SENSORS_TSL2563
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called tsl2563.
|
||||
|
||||
config TSL2583
|
||||
tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters"
|
||||
depends on I2C
|
||||
help
|
||||
Provides support for the TAOS tsl2580, tsl2581 and tsl2583 devices.
|
||||
Access ALS data via iio, sysfs.
|
||||
|
||||
config TSL4531
|
||||
tristate "TAOS TSL4531 ambient light sensors"
|
||||
depends on I2C
|
||||
|
|
|
@ -17,6 +17,7 @@ obj-$(CONFIG_CM36651) += cm36651.o
|
|||
obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o
|
||||
obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o
|
||||
obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o
|
||||
obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o
|
||||
obj-$(CONFIG_ISL29125) += isl29125.o
|
||||
obj-$(CONFIG_JSA1212) += jsa1212.o
|
||||
obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
|
||||
|
@ -30,6 +31,7 @@ obj-$(CONFIG_SI1145) += si1145.o
|
|||
obj-$(CONFIG_STK3310) += stk3310.o
|
||||
obj-$(CONFIG_TCS3414) += tcs3414.o
|
||||
obj-$(CONFIG_TCS3472) += tcs3472.o
|
||||
obj-$(CONFIG_TSL2583) += tsl2583.o
|
||||
obj-$(CONFIG_TSL4531) += tsl4531.o
|
||||
obj-$(CONFIG_US5182D) += us5182d.o
|
||||
obj-$(CONFIG_VCNL4000) += vcnl4000.o
|
||||
|
|
|
@ -62,16 +62,6 @@
|
|||
#define ISL29035_BOUT_SHIFT 0x07
|
||||
#define ISL29035_BOUT_MASK (0x01 << ISL29035_BOUT_SHIFT)
|
||||
|
||||
#define ISL29018_INT_TIME_AVAIL "0.090000 0.005630 0.000351 0.000021"
|
||||
#define ISL29023_INT_TIME_AVAIL "0.090000 0.005600 0.000352 0.000022"
|
||||
#define ISL29035_INT_TIME_AVAIL "0.105000 0.006500 0.000410 0.000025"
|
||||
|
||||
static const char * const int_time_avail[] = {
|
||||
ISL29018_INT_TIME_AVAIL,
|
||||
ISL29023_INT_TIME_AVAIL,
|
||||
ISL29035_INT_TIME_AVAIL,
|
||||
};
|
||||
|
||||
enum isl29018_int_time {
|
||||
ISL29018_INT_TIME_16,
|
||||
ISL29018_INT_TIME_12,
|
||||
|
@ -110,7 +100,8 @@ struct isl29018_chip {
|
|||
static int isl29018_set_integration_time(struct isl29018_chip *chip,
|
||||
unsigned int utime)
|
||||
{
|
||||
int i, ret;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
unsigned int int_time, new_int_time;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i) {
|
||||
|
@ -145,7 +136,8 @@ static int isl29018_set_integration_time(struct isl29018_chip *chip,
|
|||
|
||||
static int isl29018_set_scale(struct isl29018_chip *chip, int scale, int uscale)
|
||||
{
|
||||
int i, ret;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
struct isl29018_scale new_scale;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i) {
|
||||
|
@ -276,29 +268,35 @@ static int isl29018_read_proximity_ir(struct isl29018_chip *chip, int scheme,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t isl29018_show_scale_available(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
static ssize_t in_illuminance_scale_available_show
|
||||
(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct isl29018_chip *chip = iio_priv(indio_dev);
|
||||
int i, len = 0;
|
||||
unsigned int i;
|
||||
int len = 0;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i)
|
||||
len += sprintf(buf + len, "%d.%06d ",
|
||||
isl29018_scales[chip->int_time][i].scale,
|
||||
isl29018_scales[chip->int_time][i].uscale);
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
buf[len - 1] = '\n';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t isl29018_show_int_time_available(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
static ssize_t in_illuminance_integration_time_available_show
|
||||
(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct isl29018_chip *chip = iio_priv(indio_dev);
|
||||
int i, len = 0;
|
||||
unsigned int i;
|
||||
int len = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i)
|
||||
len += sprintf(buf + len, "0.%06d ",
|
||||
|
@ -309,8 +307,26 @@ static ssize_t isl29018_show_int_time_available(struct device *dev,
|
|||
return len;
|
||||
}
|
||||
|
||||
static ssize_t isl29018_show_prox_infrared_suppression(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
/*
|
||||
* From ISL29018 Data Sheet (FN6619.4, Oct 8, 2012) regarding the
|
||||
* infrared suppression:
|
||||
*
|
||||
* Proximity Sensing Scheme: Bit 7. This bit programs the function
|
||||
* of the proximity detection. Logic 0 of this bit, Scheme 0, makes
|
||||
* full n (4, 8, 12, 16) bits (unsigned) proximity detection. The range
|
||||
* of Scheme 0 proximity count is from 0 to 2^n. Logic 1 of this bit,
|
||||
* Scheme 1, makes n-1 (3, 7, 11, 15) bits (2's complementary)
|
||||
* proximity_less_ambient detection. The range of Scheme 1
|
||||
* proximity count is from -2^(n-1) to 2^(n-1) . The sign bit is extended
|
||||
* for resolutions less than 16. While Scheme 0 has wider dynamic
|
||||
* range, Scheme 1 proximity detection is less affected by the
|
||||
* ambient IR noise variation.
|
||||
*
|
||||
* 0 Sensing IR from LED and ambient
|
||||
* 1 Sensing IR from LED with ambient IR rejection
|
||||
*/
|
||||
static ssize_t proximity_on_chip_ambient_infrared_suppression_show
|
||||
(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
|
@ -323,8 +339,8 @@ static ssize_t isl29018_show_prox_infrared_suppression(struct device *dev,
|
|||
return sprintf(buf, "%d\n", chip->prox_scheme);
|
||||
}
|
||||
|
||||
static ssize_t isl29018_store_prox_infrared_suppression(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
static ssize_t proximity_on_chip_ambient_infrared_suppression_store
|
||||
(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
|
@ -357,6 +373,10 @@ static int isl29018_write_raw(struct iio_dev *indio_dev,
|
|||
int ret = -EINVAL;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
if (chip->suspended) {
|
||||
ret = -EBUSY;
|
||||
goto write_done;
|
||||
}
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_CALIBSCALE:
|
||||
if (chan->type == IIO_LIGHT) {
|
||||
|
@ -366,13 +386,8 @@ static int isl29018_write_raw(struct iio_dev *indio_dev,
|
|||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
if (chan->type == IIO_LIGHT) {
|
||||
if (val) {
|
||||
mutex_unlock(&chip->lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (chan->type == IIO_LIGHT && !val)
|
||||
ret = isl29018_set_integration_time(chip, val2);
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
if (chan->type == IIO_LIGHT)
|
||||
|
@ -381,6 +396,8 @@ static int isl29018_write_raw(struct iio_dev *indio_dev,
|
|||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
write_done:
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return ret;
|
||||
|
@ -397,8 +414,8 @@ static int isl29018_read_raw(struct iio_dev *indio_dev,
|
|||
|
||||
mutex_lock(&chip->lock);
|
||||
if (chip->suspended) {
|
||||
mutex_unlock(&chip->lock);
|
||||
return -EBUSY;
|
||||
ret = -EBUSY;
|
||||
goto read_done;
|
||||
}
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
|
@ -445,7 +462,10 @@ static int isl29018_read_raw(struct iio_dev *indio_dev,
|
|||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
read_done:
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -482,14 +502,9 @@ static const struct iio_chan_spec isl29023_channels[] = {
|
|||
ISL29018_IR_CHANNEL,
|
||||
};
|
||||
|
||||
static IIO_DEVICE_ATTR(in_illuminance_integration_time_available, S_IRUGO,
|
||||
isl29018_show_int_time_available, NULL, 0);
|
||||
static IIO_DEVICE_ATTR(in_illuminance_scale_available, S_IRUGO,
|
||||
isl29018_show_scale_available, NULL, 0);
|
||||
static IIO_DEVICE_ATTR(proximity_on_chip_ambient_infrared_suppression,
|
||||
S_IRUGO | S_IWUSR,
|
||||
isl29018_show_prox_infrared_suppression,
|
||||
isl29018_store_prox_infrared_suppression, 0);
|
||||
static IIO_DEVICE_ATTR_RO(in_illuminance_integration_time_available, 0);
|
||||
static IIO_DEVICE_ATTR_RO(in_illuminance_scale_available, 0);
|
||||
static IIO_DEVICE_ATTR_RW(proximity_on_chip_ambient_infrared_suppression, 0);
|
||||
|
||||
#define ISL29018_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr)
|
||||
|
||||
|
@ -514,12 +529,20 @@ static const struct attribute_group isl29023_group = {
|
|||
.attrs = isl29023_attributes,
|
||||
};
|
||||
|
||||
static int isl29035_detect(struct isl29018_chip *chip)
|
||||
enum {
|
||||
isl29018,
|
||||
isl29023,
|
||||
isl29035,
|
||||
};
|
||||
|
||||
static int isl29018_chip_init(struct isl29018_chip *chip)
|
||||
{
|
||||
int status;
|
||||
unsigned int id;
|
||||
struct device *dev = regmap_get_device(chip->regmap);
|
||||
|
||||
if (chip->type == isl29035) {
|
||||
unsigned int id;
|
||||
|
||||
status = regmap_read(chip->regmap, ISL29035_REG_DEVICE_ID, &id);
|
||||
if (status < 0) {
|
||||
dev_err(dev,
|
||||
|
@ -534,28 +557,15 @@ static int isl29035_detect(struct isl29018_chip *chip)
|
|||
return -ENODEV;
|
||||
|
||||
/* Clear brownout bit */
|
||||
return regmap_update_bits(chip->regmap, ISL29035_REG_DEVICE_ID,
|
||||
status = regmap_update_bits(chip->regmap,
|
||||
ISL29035_REG_DEVICE_ID,
|
||||
ISL29035_BOUT_MASK, 0);
|
||||
}
|
||||
|
||||
enum {
|
||||
isl29018,
|
||||
isl29023,
|
||||
isl29035,
|
||||
};
|
||||
|
||||
static int isl29018_chip_init(struct isl29018_chip *chip)
|
||||
{
|
||||
int status;
|
||||
struct device *dev = regmap_get_device(chip->regmap);
|
||||
|
||||
if (chip->type == isl29035) {
|
||||
status = isl29035_detect(chip);
|
||||
if (status < 0)
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Code added per Intersil Application Note 1534:
|
||||
/*
|
||||
* Code added per Intersil Application Note 1534:
|
||||
* When VDD sinks to approximately 1.8V or below, some of
|
||||
* the part's registers may change their state. When VDD
|
||||
* recovers to 2.25V (or greater), the part may thus be in an
|
||||
|
@ -582,7 +592,8 @@ static int isl29018_chip_init(struct isl29018_chip *chip)
|
|||
return status;
|
||||
}
|
||||
|
||||
/* See Intersil AN1534 comments above.
|
||||
/*
|
||||
* See Intersil AN1534 comments above.
|
||||
* "Operating Mode" (COMMAND1) register is reprogrammed when
|
||||
* data is read from the device.
|
||||
*/
|
||||
|
@ -605,12 +616,10 @@ static int isl29018_chip_init(struct isl29018_chip *chip)
|
|||
|
||||
status = isl29018_set_integration_time(chip,
|
||||
isl29018_int_utimes[chip->type][chip->int_time]);
|
||||
if (status < 0) {
|
||||
if (status < 0)
|
||||
dev_err(dev, "Init of isl29018 fails\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
static const struct iio_info isl29018_info = {
|
||||
|
@ -713,6 +722,7 @@ static int isl29018_probe(struct i2c_client *client,
|
|||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
chip = iio_priv(indio_dev);
|
||||
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
@ -752,6 +762,7 @@ static int isl29018_probe(struct i2c_client *client,
|
|||
indio_dev->name = name;
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
return devm_iio_device_register(&client->dev, indio_dev);
|
||||
}
|
||||
|
||||
|
@ -762,13 +773,15 @@ static int isl29018_suspend(struct device *dev)
|
|||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
/* Since this driver uses only polling commands, we are by default in
|
||||
/*
|
||||
* Since this driver uses only polling commands, we are by default in
|
||||
* auto shutdown (ie, power-down) mode.
|
||||
* So we do not have much to do here.
|
||||
*/
|
||||
chip->suspended = true;
|
||||
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -784,6 +797,7 @@ static int isl29018_resume(struct device *dev)
|
|||
chip->suspended = false;
|
||||
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -807,7 +821,6 @@ static const struct i2c_device_id isl29018_id[] = {
|
|||
{"isl29035", isl29035},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, isl29018_id);
|
||||
|
||||
static const struct of_device_id isl29018_of_match[] = {
|
|
@ -631,14 +631,16 @@ static int ltr501_read_raw(struct iio_dev *indio_dev,
|
|||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_PROCESSED:
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&data->lock_als);
|
||||
ret = ltr501_read_als(data, buf);
|
||||
mutex_unlock(&data->lock_als);
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ltr501_calculate_lux(le16_to_cpu(buf[1]),
|
||||
|
@ -648,8 +650,9 @@ static int ltr501_read_raw(struct iio_dev *indio_dev,
|
|||
return -EINVAL;
|
||||
}
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_INTENSITY:
|
||||
|
@ -657,21 +660,28 @@ static int ltr501_read_raw(struct iio_dev *indio_dev,
|
|||
ret = ltr501_read_als(data, buf);
|
||||
mutex_unlock(&data->lock_als);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
*val = le16_to_cpu(chan->address == LTR501_ALS_DATA1 ?
|
||||
buf[0] : buf[1]);
|
||||
return IIO_VAL_INT;
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_PROXIMITY:
|
||||
mutex_lock(&data->lock_ps);
|
||||
ret = ltr501_read_ps(data);
|
||||
mutex_unlock(&data->lock_ps);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
*val = ret & LTR501_PS_DATA_MASK;
|
||||
return IIO_VAL_INT;
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
return ret;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
switch (chan->type) {
|
||||
case IIO_INTENSITY:
|
||||
|
@ -729,8 +739,9 @@ static int ltr501_write_raw(struct iio_dev *indio_dev,
|
|||
int i, ret, freq_val, freq_val2;
|
||||
struct ltr501_chip_info *info = data->chip_info;
|
||||
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
|
@ -739,85 +750,105 @@ static int ltr501_write_raw(struct iio_dev *indio_dev,
|
|||
i = ltr501_get_gain_index(info->als_gain,
|
||||
info->als_gain_tbl_size,
|
||||
val, val2);
|
||||
if (i < 0)
|
||||
return -EINVAL;
|
||||
if (i < 0) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
data->als_contr &= ~info->als_gain_mask;
|
||||
data->als_contr |= i << info->als_gain_shift;
|
||||
|
||||
return regmap_write(data->regmap, LTR501_ALS_CONTR,
|
||||
ret = regmap_write(data->regmap, LTR501_ALS_CONTR,
|
||||
data->als_contr);
|
||||
break;
|
||||
case IIO_PROXIMITY:
|
||||
i = ltr501_get_gain_index(info->ps_gain,
|
||||
info->ps_gain_tbl_size,
|
||||
val, val2);
|
||||
if (i < 0)
|
||||
return -EINVAL;
|
||||
if (i < 0) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
data->ps_contr &= ~LTR501_CONTR_PS_GAIN_MASK;
|
||||
data->ps_contr |= i << LTR501_CONTR_PS_GAIN_SHIFT;
|
||||
|
||||
return regmap_write(data->regmap, LTR501_PS_CONTR,
|
||||
ret = regmap_write(data->regmap, LTR501_PS_CONTR,
|
||||
data->ps_contr);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
switch (chan->type) {
|
||||
case IIO_INTENSITY:
|
||||
if (val != 0)
|
||||
return -EINVAL;
|
||||
mutex_lock(&data->lock_als);
|
||||
i = ltr501_set_it_time(data, val2);
|
||||
mutex_unlock(&data->lock_als);
|
||||
return i;
|
||||
default:
|
||||
return -EINVAL;
|
||||
if (val != 0) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
mutex_lock(&data->lock_als);
|
||||
ret = ltr501_set_it_time(data, val2);
|
||||
mutex_unlock(&data->lock_als);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
switch (chan->type) {
|
||||
case IIO_INTENSITY:
|
||||
ret = ltr501_als_read_samp_freq(data, &freq_val,
|
||||
&freq_val2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
|
||||
ret = ltr501_als_write_samp_freq(data, val, val2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
|
||||
/* update persistence count when changing frequency */
|
||||
ret = ltr501_write_intr_prst(data, chan->type,
|
||||
0, data->als_period);
|
||||
|
||||
if (ret < 0)
|
||||
return ltr501_als_write_samp_freq(data,
|
||||
freq_val,
|
||||
ret = ltr501_als_write_samp_freq(data, freq_val,
|
||||
freq_val2);
|
||||
return ret;
|
||||
break;
|
||||
case IIO_PROXIMITY:
|
||||
ret = ltr501_ps_read_samp_freq(data, &freq_val,
|
||||
&freq_val2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
|
||||
ret = ltr501_ps_write_samp_freq(data, val, val2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
|
||||
/* update persistence count when changing frequency */
|
||||
ret = ltr501_write_intr_prst(data, chan->type,
|
||||
0, data->ps_period);
|
||||
|
||||
if (ret < 0)
|
||||
return ltr501_ps_write_samp_freq(data,
|
||||
freq_val,
|
||||
ret = ltr501_ps_write_samp_freq(data, freq_val,
|
||||
freq_val2);
|
||||
return ret;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return -EINVAL;
|
||||
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltr501_read_thresh(struct iio_dev *indio_dev,
|
||||
|
|
|
@ -204,17 +204,18 @@ static int max44000_write_alspga(struct max44000_data *data, int val)
|
|||
static int max44000_read_alsval(struct max44000_data *data)
|
||||
{
|
||||
u16 regval;
|
||||
__be16 val;
|
||||
int alstim, ret;
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, MAX44000_REG_ALS_DATA_HI,
|
||||
®val, sizeof(regval));
|
||||
&val, sizeof(val));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
alstim = ret = max44000_read_alstim(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
regval = be16_to_cpu(regval);
|
||||
regval = be16_to_cpu(val);
|
||||
|
||||
/*
|
||||
* Overflow is explained on datasheet page 17.
|
||||
|
|
|
@ -0,0 +1,913 @@
|
|||
/*
|
||||
* Device driver for monitoring ambient light intensity (lux)
|
||||
* within the TAOS tsl258x family of devices (tsl2580, tsl2581, tsl2583).
|
||||
*
|
||||
* Copyright (c) 2011, TAOS Corporation.
|
||||
* Copyright (c) 2016 Brian Masney <masneyb@onstation.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/unistd.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
||||
/* Device Registers and Masks */
|
||||
#define TSL2583_CNTRL 0x00
|
||||
#define TSL2583_ALS_TIME 0X01
|
||||
#define TSL2583_INTERRUPT 0x02
|
||||
#define TSL2583_GAIN 0x07
|
||||
#define TSL2583_REVID 0x11
|
||||
#define TSL2583_CHIPID 0x12
|
||||
#define TSL2583_ALS_CHAN0LO 0x14
|
||||
#define TSL2583_ALS_CHAN0HI 0x15
|
||||
#define TSL2583_ALS_CHAN1LO 0x16
|
||||
#define TSL2583_ALS_CHAN1HI 0x17
|
||||
#define TSL2583_TMR_LO 0x18
|
||||
#define TSL2583_TMR_HI 0x19
|
||||
|
||||
/* tsl2583 cmd reg masks */
|
||||
#define TSL2583_CMD_REG 0x80
|
||||
#define TSL2583_CMD_SPL_FN 0x60
|
||||
#define TSL2583_CMD_ALS_INT_CLR 0x01
|
||||
|
||||
/* tsl2583 cntrl reg masks */
|
||||
#define TSL2583_CNTL_ADC_ENBL 0x02
|
||||
#define TSL2583_CNTL_PWR_OFF 0x00
|
||||
#define TSL2583_CNTL_PWR_ON 0x01
|
||||
|
||||
/* tsl2583 status reg masks */
|
||||
#define TSL2583_STA_ADC_VALID 0x01
|
||||
#define TSL2583_STA_ADC_INTR 0x10
|
||||
|
||||
/* Lux calculation constants */
|
||||
#define TSL2583_LUX_CALC_OVER_FLOW 65535
|
||||
|
||||
#define TSL2583_INTERRUPT_DISABLED 0x00
|
||||
|
||||
#define TSL2583_CHIP_ID 0x90
|
||||
#define TSL2583_CHIP_ID_MASK 0xf0
|
||||
|
||||
/* Per-device data */
|
||||
struct tsl2583_als_info {
|
||||
u16 als_ch0;
|
||||
u16 als_ch1;
|
||||
u16 lux;
|
||||
};
|
||||
|
||||
struct tsl2583_lux {
|
||||
unsigned int ratio;
|
||||
unsigned int ch0;
|
||||
unsigned int ch1;
|
||||
};
|
||||
|
||||
static const struct tsl2583_lux tsl2583_default_lux[] = {
|
||||
{ 9830, 8520, 15729 },
|
||||
{ 12452, 10807, 23344 },
|
||||
{ 14746, 6383, 11705 },
|
||||
{ 17695, 4063, 6554 },
|
||||
{ 0, 0, 0 } /* Termination segment */
|
||||
};
|
||||
|
||||
#define TSL2583_MAX_LUX_TABLE_ENTRIES 11
|
||||
|
||||
struct tsl2583_settings {
|
||||
int als_time;
|
||||
int als_gain;
|
||||
int als_gain_trim;
|
||||
int als_cal_target;
|
||||
|
||||
/*
|
||||
* This structure is intentionally large to accommodate updates via
|
||||
* sysfs. Sized to 11 = max 10 segments + 1 termination segment.
|
||||
* Assumption is that one and only one type of glass used.
|
||||
*/
|
||||
struct tsl2583_lux als_device_lux[TSL2583_MAX_LUX_TABLE_ENTRIES];
|
||||
};
|
||||
|
||||
struct tsl2583_chip {
|
||||
struct mutex als_mutex;
|
||||
struct i2c_client *client;
|
||||
struct tsl2583_als_info als_cur_info;
|
||||
struct tsl2583_settings als_settings;
|
||||
int als_time_scale;
|
||||
int als_saturation;
|
||||
bool suspended;
|
||||
};
|
||||
|
||||
struct gainadj {
|
||||
s16 ch0;
|
||||
s16 ch1;
|
||||
s16 mean;
|
||||
};
|
||||
|
||||
/* Index = (0 - 3) Used to validate the gain selection index */
|
||||
static const struct gainadj gainadj[] = {
|
||||
{ 1, 1, 1 },
|
||||
{ 8, 8, 8 },
|
||||
{ 16, 16, 16 },
|
||||
{ 107, 115, 111 }
|
||||
};
|
||||
|
||||
/*
|
||||
* Provides initial operational parameter defaults.
|
||||
* These defaults may be changed through the device's sysfs files.
|
||||
*/
|
||||
static void tsl2583_defaults(struct tsl2583_chip *chip)
|
||||
{
|
||||
/*
|
||||
* The integration time must be a multiple of 50ms and within the
|
||||
* range [50, 600] ms.
|
||||
*/
|
||||
chip->als_settings.als_time = 100;
|
||||
|
||||
/*
|
||||
* This is an index into the gainadj table. Assume clear glass as the
|
||||
* default.
|
||||
*/
|
||||
chip->als_settings.als_gain = 0;
|
||||
|
||||
/* Default gain trim to account for aperture effects */
|
||||
chip->als_settings.als_gain_trim = 1000;
|
||||
|
||||
/* Known external ALS reading used for calibration */
|
||||
chip->als_settings.als_cal_target = 130;
|
||||
|
||||
/* Default lux table. */
|
||||
memcpy(chip->als_settings.als_device_lux, tsl2583_default_lux,
|
||||
sizeof(tsl2583_default_lux));
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads and calculates current lux value.
|
||||
* The raw ch0 and ch1 values of the ambient light sensed in the last
|
||||
* integration cycle are read from the device.
|
||||
* Time scale factor array values are adjusted based on the integration time.
|
||||
* The raw values are multiplied by a scale factor, and device gain is obtained
|
||||
* using gain index. Limit checks are done next, then the ratio of a multiple
|
||||
* of ch1 value, to the ch0 value, is calculated. The array als_device_lux[]
|
||||
* declared above is then scanned to find the first ratio value that is just
|
||||
* above the ratio we just calculated. The ch0 and ch1 multiplier constants in
|
||||
* the array are then used along with the time scale factor array values, to
|
||||
* calculate the lux.
|
||||
*/
|
||||
static int tsl2583_get_lux(struct iio_dev *indio_dev)
|
||||
{
|
||||
u16 ch0, ch1; /* separated ch0/ch1 data from device */
|
||||
u32 lux; /* raw lux calculated from device data */
|
||||
u64 lux64;
|
||||
u32 ratio;
|
||||
u8 buf[5];
|
||||
struct tsl2583_lux *p;
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
int i, ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(chip->client, TSL2583_CMD_REG);
|
||||
if (ret < 0) {
|
||||
dev_err(&chip->client->dev, "%s: failed to read CMD_REG register\n",
|
||||
__func__);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* is data new & valid */
|
||||
if (!(ret & TSL2583_STA_ADC_INTR)) {
|
||||
dev_err(&chip->client->dev, "%s: data not valid; returning last value\n",
|
||||
__func__);
|
||||
ret = chip->als_cur_info.lux; /* return LAST VALUE */
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
int reg = TSL2583_CMD_REG | (TSL2583_ALS_CHAN0LO + i);
|
||||
|
||||
ret = i2c_smbus_read_byte_data(chip->client, reg);
|
||||
if (ret < 0) {
|
||||
dev_err(&chip->client->dev, "%s: failed to read register %x\n",
|
||||
__func__, reg);
|
||||
goto done;
|
||||
}
|
||||
buf[i] = ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the pending interrupt status bit on the chip to allow the next
|
||||
* integration cycle to start. This has to be done even though this
|
||||
* driver currently does not support interrupts.
|
||||
*/
|
||||
ret = i2c_smbus_write_byte(chip->client,
|
||||
(TSL2583_CMD_REG | TSL2583_CMD_SPL_FN |
|
||||
TSL2583_CMD_ALS_INT_CLR));
|
||||
if (ret < 0) {
|
||||
dev_err(&chip->client->dev, "%s: failed to clear the interrupt bit\n",
|
||||
__func__);
|
||||
goto done; /* have no data, so return failure */
|
||||
}
|
||||
|
||||
/* extract ALS/lux data */
|
||||
ch0 = le16_to_cpup((const __le16 *)&buf[0]);
|
||||
ch1 = le16_to_cpup((const __le16 *)&buf[2]);
|
||||
|
||||
chip->als_cur_info.als_ch0 = ch0;
|
||||
chip->als_cur_info.als_ch1 = ch1;
|
||||
|
||||
if ((ch0 >= chip->als_saturation) || (ch1 >= chip->als_saturation))
|
||||
goto return_max;
|
||||
|
||||
if (!ch0) {
|
||||
/*
|
||||
* The sensor appears to be in total darkness so set the
|
||||
* calculated lux to 0 and return early to avoid a division by
|
||||
* zero below when calculating the ratio.
|
||||
*/
|
||||
ret = 0;
|
||||
chip->als_cur_info.lux = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* calculate ratio */
|
||||
ratio = (ch1 << 15) / ch0;
|
||||
|
||||
/* convert to unscaled lux using the pointer to the table */
|
||||
for (p = (struct tsl2583_lux *)chip->als_settings.als_device_lux;
|
||||
p->ratio != 0 && p->ratio < ratio; p++)
|
||||
;
|
||||
|
||||
if (p->ratio == 0) {
|
||||
lux = 0;
|
||||
} else {
|
||||
u32 ch0lux, ch1lux;
|
||||
|
||||
ch0lux = ((ch0 * p->ch0) +
|
||||
(gainadj[chip->als_settings.als_gain].ch0 >> 1))
|
||||
/ gainadj[chip->als_settings.als_gain].ch0;
|
||||
ch1lux = ((ch1 * p->ch1) +
|
||||
(gainadj[chip->als_settings.als_gain].ch1 >> 1))
|
||||
/ gainadj[chip->als_settings.als_gain].ch1;
|
||||
|
||||
/* note: lux is 31 bit max at this point */
|
||||
if (ch1lux > ch0lux) {
|
||||
dev_dbg(&chip->client->dev, "%s: No Data - Returning 0\n",
|
||||
__func__);
|
||||
ret = 0;
|
||||
chip->als_cur_info.lux = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
lux = ch0lux - ch1lux;
|
||||
}
|
||||
|
||||
/* adjust for active time scale */
|
||||
if (chip->als_time_scale == 0)
|
||||
lux = 0;
|
||||
else
|
||||
lux = (lux + (chip->als_time_scale >> 1)) /
|
||||
chip->als_time_scale;
|
||||
|
||||
/*
|
||||
* Adjust for active gain scale.
|
||||
* The tsl2583_default_lux tables above have a factor of 8192 built in,
|
||||
* so we need to shift right.
|
||||
* User-specified gain provides a multiplier.
|
||||
* Apply user-specified gain before shifting right to retain precision.
|
||||
* Use 64 bits to avoid overflow on multiplication.
|
||||
* Then go back to 32 bits before division to avoid using div_u64().
|
||||
*/
|
||||
lux64 = lux;
|
||||
lux64 = lux64 * chip->als_settings.als_gain_trim;
|
||||
lux64 >>= 13;
|
||||
lux = lux64;
|
||||
lux = (lux + 500) / 1000;
|
||||
|
||||
if (lux > TSL2583_LUX_CALC_OVER_FLOW) { /* check for overflow */
|
||||
return_max:
|
||||
lux = TSL2583_LUX_CALC_OVER_FLOW;
|
||||
}
|
||||
|
||||
/* Update the structure with the latest VALID lux. */
|
||||
chip->als_cur_info.lux = lux;
|
||||
ret = lux;
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Obtain single reading and calculate the als_gain_trim (later used
|
||||
* to derive actual lux).
|
||||
* Return updated gain_trim value.
|
||||
*/
|
||||
static int tsl2583_als_calibrate(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
unsigned int gain_trim_val;
|
||||
int ret;
|
||||
int lux_val;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(chip->client,
|
||||
TSL2583_CMD_REG | TSL2583_CNTRL);
|
||||
if (ret < 0) {
|
||||
dev_err(&chip->client->dev,
|
||||
"%s: failed to read from the CNTRL register\n",
|
||||
__func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret & (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON))
|
||||
!= (TSL2583_CNTL_ADC_ENBL | TSL2583_CNTL_PWR_ON)) {
|
||||
dev_err(&chip->client->dev,
|
||||
"%s: Device is not powered on and/or ADC is not enabled\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
} else if ((ret & TSL2583_STA_ADC_VALID) != TSL2583_STA_ADC_VALID) {
|
||||
dev_err(&chip->client->dev,
|
||||
"%s: The two ADC channels have not completed an integration cycle\n",
|
||||
__func__);
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
lux_val = tsl2583_get_lux(indio_dev);
|
||||
if (lux_val < 0) {
|
||||
dev_err(&chip->client->dev, "%s: failed to get lux\n",
|
||||
__func__);
|
||||
return lux_val;
|
||||
}
|
||||
|
||||
gain_trim_val = (unsigned int)(((chip->als_settings.als_cal_target)
|
||||
* chip->als_settings.als_gain_trim) / lux_val);
|
||||
if ((gain_trim_val < 250) || (gain_trim_val > 4000)) {
|
||||
dev_err(&chip->client->dev,
|
||||
"%s: trim_val of %d is not within the range [250, 4000]\n",
|
||||
__func__, gain_trim_val);
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
chip->als_settings.als_gain_trim = (int)gain_trim_val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tsl2583_set_als_time(struct tsl2583_chip *chip)
|
||||
{
|
||||
int als_count, als_time, ret;
|
||||
u8 val;
|
||||
|
||||
/* determine als integration register */
|
||||
als_count = (chip->als_settings.als_time * 100 + 135) / 270;
|
||||
if (!als_count)
|
||||
als_count = 1; /* ensure at least one cycle */
|
||||
|
||||
/* convert back to time (encompasses overrides) */
|
||||
als_time = (als_count * 27 + 5) / 10;
|
||||
|
||||
val = 256 - als_count;
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2583_CMD_REG | TSL2583_ALS_TIME,
|
||||
val);
|
||||
if (ret < 0) {
|
||||
dev_err(&chip->client->dev, "%s: failed to set the als time to %d\n",
|
||||
__func__, val);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set chip struct re scaling and saturation */
|
||||
chip->als_saturation = als_count * 922; /* 90% of full scale */
|
||||
chip->als_time_scale = (als_time + 25) / 50;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tsl2583_set_als_gain(struct tsl2583_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Set the gain based on als_settings struct */
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2583_CMD_REG | TSL2583_GAIN,
|
||||
chip->als_settings.als_gain);
|
||||
if (ret < 0)
|
||||
dev_err(&chip->client->dev,
|
||||
"%s: failed to set the gain to %d\n", __func__,
|
||||
chip->als_settings.als_gain);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tsl2583_set_power_state(struct tsl2583_chip *chip, u8 state)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2583_CMD_REG | TSL2583_CNTRL, state);
|
||||
if (ret < 0)
|
||||
dev_err(&chip->client->dev,
|
||||
"%s: failed to set the power state to %d\n", __func__,
|
||||
state);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Turn the device on.
|
||||
* Configuration must be set before calling this function.
|
||||
*/
|
||||
static int tsl2583_chip_init_and_power_on(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
/* Power on the device; ADC off. */
|
||||
ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(chip->client,
|
||||
TSL2583_CMD_REG | TSL2583_INTERRUPT,
|
||||
TSL2583_INTERRUPT_DISABLED);
|
||||
if (ret < 0) {
|
||||
dev_err(&chip->client->dev,
|
||||
"%s: failed to disable interrupts\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = tsl2583_set_als_time(chip);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = tsl2583_set_als_gain(chip);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
usleep_range(3000, 3500);
|
||||
|
||||
ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_ON |
|
||||
TSL2583_CNTL_ADC_ENBL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
chip->suspended = false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Sysfs Interface Functions */
|
||||
|
||||
static ssize_t in_illuminance_input_target_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->als_mutex);
|
||||
ret = sprintf(buf, "%d\n", chip->als_settings.als_cal_target);
|
||||
mutex_unlock(&chip->als_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t in_illuminance_input_target_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
int value;
|
||||
|
||||
if (kstrtoint(buf, 0, &value) || !value)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&chip->als_mutex);
|
||||
chip->als_settings.als_cal_target = value;
|
||||
mutex_unlock(&chip->als_mutex);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t in_illuminance_calibrate_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
int value, ret;
|
||||
|
||||
if (kstrtoint(buf, 0, &value) || value != 1)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&chip->als_mutex);
|
||||
|
||||
if (chip->suspended) {
|
||||
ret = -EBUSY;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = tsl2583_als_calibrate(indio_dev);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
ret = len;
|
||||
done:
|
||||
mutex_unlock(&chip->als_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t in_illuminance_lux_table_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
unsigned int i;
|
||||
int offset = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(chip->als_settings.als_device_lux); i++) {
|
||||
offset += sprintf(buf + offset, "%u,%u,%u,",
|
||||
chip->als_settings.als_device_lux[i].ratio,
|
||||
chip->als_settings.als_device_lux[i].ch0,
|
||||
chip->als_settings.als_device_lux[i].ch1);
|
||||
if (chip->als_settings.als_device_lux[i].ratio == 0) {
|
||||
/*
|
||||
* We just printed the first "0" entry.
|
||||
* Now get rid of the extra "," and break.
|
||||
*/
|
||||
offset--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
offset += sprintf(buf + offset, "\n");
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static ssize_t in_illuminance_lux_table_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
const unsigned int max_ints = TSL2583_MAX_LUX_TABLE_ENTRIES * 3;
|
||||
int value[TSL2583_MAX_LUX_TABLE_ENTRIES * 3 + 1];
|
||||
int ret = -EINVAL;
|
||||
unsigned int n;
|
||||
|
||||
mutex_lock(&chip->als_mutex);
|
||||
|
||||
get_options(buf, ARRAY_SIZE(value), value);
|
||||
|
||||
/*
|
||||
* We now have an array of ints starting at value[1], and
|
||||
* enumerated by value[0].
|
||||
* We expect each group of three ints is one table entry,
|
||||
* and the last table entry is all 0.
|
||||
*/
|
||||
n = value[0];
|
||||
if ((n % 3) || n < 6 || n > max_ints) {
|
||||
dev_err(dev,
|
||||
"%s: The number of entries in the lux table must be a multiple of 3 and within the range [6, %d]\n",
|
||||
__func__, max_ints);
|
||||
goto done;
|
||||
}
|
||||
if ((value[n - 2] | value[n - 1] | value[n]) != 0) {
|
||||
dev_err(dev, "%s: The last 3 entries in the lux table must be zeros.\n",
|
||||
__func__);
|
||||
goto done;
|
||||
}
|
||||
|
||||
memcpy(chip->als_settings.als_device_lux, &value[1],
|
||||
value[0] * sizeof(value[1]));
|
||||
|
||||
ret = len;
|
||||
|
||||
done:
|
||||
mutex_unlock(&chip->als_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static IIO_CONST_ATTR(in_illuminance_calibscale_available, "1 8 16 111");
|
||||
static IIO_CONST_ATTR(in_illuminance_integration_time_available,
|
||||
"0.000050 0.000100 0.000150 0.000200 0.000250 0.000300 0.000350 0.000400 0.000450 0.000500 0.000550 0.000600 0.000650");
|
||||
static IIO_DEVICE_ATTR_RW(in_illuminance_input_target, 0);
|
||||
static IIO_DEVICE_ATTR_WO(in_illuminance_calibrate, 0);
|
||||
static IIO_DEVICE_ATTR_RW(in_illuminance_lux_table, 0);
|
||||
|
||||
static struct attribute *sysfs_attrs_ctrl[] = {
|
||||
&iio_const_attr_in_illuminance_calibscale_available.dev_attr.attr,
|
||||
&iio_const_attr_in_illuminance_integration_time_available.dev_attr.attr,
|
||||
&iio_dev_attr_in_illuminance_input_target.dev_attr.attr,
|
||||
&iio_dev_attr_in_illuminance_calibrate.dev_attr.attr,
|
||||
&iio_dev_attr_in_illuminance_lux_table.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group tsl2583_attribute_group = {
|
||||
.attrs = sysfs_attrs_ctrl,
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec tsl2583_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.modified = 1,
|
||||
.channel2 = IIO_MOD_LIGHT_IR,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
},
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.modified = 1,
|
||||
.channel2 = IIO_MOD_LIGHT_BOTH,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
},
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
|
||||
BIT(IIO_CHAN_INFO_CALIBBIAS) |
|
||||
BIT(IIO_CHAN_INFO_CALIBSCALE) |
|
||||
BIT(IIO_CHAN_INFO_INT_TIME),
|
||||
},
|
||||
};
|
||||
|
||||
static int tsl2583_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
int ret = -EINVAL;
|
||||
|
||||
mutex_lock(&chip->als_mutex);
|
||||
|
||||
if (chip->suspended) {
|
||||
ret = -EBUSY;
|
||||
goto read_done;
|
||||
}
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (chan->type == IIO_LIGHT) {
|
||||
ret = tsl2583_get_lux(indio_dev);
|
||||
if (ret < 0)
|
||||
goto read_done;
|
||||
|
||||
/*
|
||||
* From page 20 of the TSL2581, TSL2583 data
|
||||
* sheet (TAOS134 − MARCH 2011):
|
||||
*
|
||||
* One of the photodiodes (channel 0) is
|
||||
* sensitive to both visible and infrared light,
|
||||
* while the second photodiode (channel 1) is
|
||||
* sensitive primarily to infrared light.
|
||||
*/
|
||||
if (chan->channel2 == IIO_MOD_LIGHT_BOTH)
|
||||
*val = chip->als_cur_info.als_ch0;
|
||||
else
|
||||
*val = chip->als_cur_info.als_ch1;
|
||||
|
||||
ret = IIO_VAL_INT;
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_PROCESSED:
|
||||
if (chan->type == IIO_LIGHT) {
|
||||
ret = tsl2583_get_lux(indio_dev);
|
||||
if (ret < 0)
|
||||
goto read_done;
|
||||
|
||||
*val = ret;
|
||||
ret = IIO_VAL_INT;
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
if (chan->type == IIO_LIGHT) {
|
||||
*val = chip->als_settings.als_gain_trim;
|
||||
ret = IIO_VAL_INT;
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_CALIBSCALE:
|
||||
if (chan->type == IIO_LIGHT) {
|
||||
*val = gainadj[chip->als_settings.als_gain].mean;
|
||||
ret = IIO_VAL_INT;
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
if (chan->type == IIO_LIGHT) {
|
||||
*val = 0;
|
||||
*val2 = chip->als_settings.als_time;
|
||||
ret = IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
read_done:
|
||||
mutex_unlock(&chip->als_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tsl2583_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
int ret = -EINVAL;
|
||||
|
||||
mutex_lock(&chip->als_mutex);
|
||||
|
||||
if (chip->suspended) {
|
||||
ret = -EBUSY;
|
||||
goto write_done;
|
||||
}
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
if (chan->type == IIO_LIGHT) {
|
||||
chip->als_settings.als_gain_trim = val;
|
||||
ret = 0;
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_CALIBSCALE:
|
||||
if (chan->type == IIO_LIGHT) {
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(gainadj); i++) {
|
||||
if (gainadj[i].mean == val) {
|
||||
chip->als_settings.als_gain = i;
|
||||
ret = tsl2583_set_als_gain(chip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
if (chan->type == IIO_LIGHT && !val && val2 >= 50 &&
|
||||
val2 <= 650 && !(val2 % 50)) {
|
||||
chip->als_settings.als_time = val2;
|
||||
ret = tsl2583_set_als_time(chip);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
write_done:
|
||||
mutex_unlock(&chip->als_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info tsl2583_info = {
|
||||
.attrs = &tsl2583_attribute_group,
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = tsl2583_read_raw,
|
||||
.write_raw = tsl2583_write_raw,
|
||||
};
|
||||
|
||||
static int tsl2583_probe(struct i2c_client *clientp,
|
||||
const struct i2c_device_id *idp)
|
||||
{
|
||||
int ret;
|
||||
struct tsl2583_chip *chip;
|
||||
struct iio_dev *indio_dev;
|
||||
|
||||
if (!i2c_check_functionality(clientp->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
dev_err(&clientp->dev, "%s: i2c smbus byte data functionality is unsupported\n",
|
||||
__func__);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&clientp->dev, sizeof(*chip));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
chip = iio_priv(indio_dev);
|
||||
chip->client = clientp;
|
||||
i2c_set_clientdata(clientp, indio_dev);
|
||||
|
||||
mutex_init(&chip->als_mutex);
|
||||
chip->suspended = true;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(clientp,
|
||||
TSL2583_CMD_REG | TSL2583_CHIPID);
|
||||
if (ret < 0) {
|
||||
dev_err(&clientp->dev,
|
||||
"%s: failed to read the chip ID register\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret & TSL2583_CHIP_ID_MASK) != TSL2583_CHIP_ID) {
|
||||
dev_err(&clientp->dev, "%s: received an unknown chip ID %x\n",
|
||||
__func__, ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
indio_dev->info = &tsl2583_info;
|
||||
indio_dev->channels = tsl2583_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(tsl2583_channels);
|
||||
indio_dev->dev.parent = &clientp->dev;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->name = chip->client->name;
|
||||
|
||||
ret = devm_iio_device_register(indio_dev->dev.parent, indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&clientp->dev, "%s: iio registration failed\n",
|
||||
__func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Load up the V2 defaults (these are hard coded defaults for now) */
|
||||
tsl2583_defaults(chip);
|
||||
|
||||
/* Make sure the chip is on */
|
||||
ret = tsl2583_chip_init_and_power_on(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev_info(&clientp->dev, "Light sensor found.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused tsl2583_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->als_mutex);
|
||||
|
||||
ret = tsl2583_set_power_state(chip, TSL2583_CNTL_PWR_OFF);
|
||||
chip->suspended = true;
|
||||
|
||||
mutex_unlock(&chip->als_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused tsl2583_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct tsl2583_chip *chip = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->als_mutex);
|
||||
|
||||
ret = tsl2583_chip_init_and_power_on(indio_dev);
|
||||
|
||||
mutex_unlock(&chip->als_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(tsl2583_pm_ops, tsl2583_suspend, tsl2583_resume);
|
||||
|
||||
static struct i2c_device_id tsl2583_idtable[] = {
|
||||
{ "tsl2580", 0 },
|
||||
{ "tsl2581", 1 },
|
||||
{ "tsl2583", 2 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tsl2583_idtable);
|
||||
|
||||
static const struct of_device_id tsl2583_of_match[] = {
|
||||
{ .compatible = "amstaos,tsl2580", },
|
||||
{ .compatible = "amstaos,tsl2581", },
|
||||
{ .compatible = "amstaos,tsl2583", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tsl2583_of_match);
|
||||
|
||||
/* Driver definition */
|
||||
static struct i2c_driver tsl2583_driver = {
|
||||
.driver = {
|
||||
.name = "tsl2583",
|
||||
.pm = &tsl2583_pm_ops,
|
||||
.of_match_table = tsl2583_of_match,
|
||||
},
|
||||
.id_table = tsl2583_idtable,
|
||||
.probe = tsl2583_probe,
|
||||
};
|
||||
module_i2c_driver(tsl2583_driver);
|
||||
|
||||
MODULE_AUTHOR("J. August Brenner <jbrenner@taosinc.com>");
|
||||
MODULE_AUTHOR("Brian Masney <masneyb@onstation.org>");
|
||||
MODULE_DESCRIPTION("TAOS tsl2583 ambient light sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -287,7 +287,7 @@ static int ak8974_await_drdy(struct ak8974 *ak8974)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int ak8974_getresult(struct ak8974 *ak8974, s16 *result)
|
||||
static int ak8974_getresult(struct ak8974 *ak8974, __le16 *result)
|
||||
{
|
||||
unsigned int src;
|
||||
int ret;
|
||||
|
@ -395,7 +395,7 @@ static int ak8974_selftest(struct ak8974 *ak8974)
|
|||
static int ak8974_get_u16_val(struct ak8974 *ak8974, u8 reg, u16 *val)
|
||||
{
|
||||
int ret;
|
||||
u16 bulk;
|
||||
__le16 bulk;
|
||||
|
||||
ret = regmap_bulk_read(ak8974->map, reg, &bulk, 2);
|
||||
if (ret)
|
||||
|
@ -453,7 +453,7 @@ static int ak8974_read_raw(struct iio_dev *indio_dev,
|
|||
long mask)
|
||||
{
|
||||
struct ak8974 *ak8974 = iio_priv(indio_dev);
|
||||
s16 hw_values[3];
|
||||
__le16 hw_values[3];
|
||||
int ret = -EINVAL;
|
||||
|
||||
pm_runtime_get_sync(&ak8974->i2c->dev);
|
||||
|
@ -494,7 +494,7 @@ static void ak8974_fill_buffer(struct iio_dev *indio_dev)
|
|||
{
|
||||
struct ak8974 *ak8974 = iio_priv(indio_dev);
|
||||
int ret;
|
||||
s16 hw_values[8]; /* Three axes + 64bit padding */
|
||||
__le16 hw_values[8]; /* Three axes + 64bit padding */
|
||||
|
||||
pm_runtime_get_sync(&ak8974->i2c->dev);
|
||||
mutex_lock(&ak8974->lock);
|
||||
|
|
|
@ -690,6 +690,7 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
|
|||
struct ak8975_data *data = iio_priv(indio_dev);
|
||||
const struct i2c_client *client = data->client;
|
||||
const struct ak_def *def = data->def;
|
||||
__le16 rval;
|
||||
u16 buff;
|
||||
int ret;
|
||||
|
||||
|
@ -703,7 +704,7 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
|
|||
|
||||
ret = i2c_smbus_read_i2c_block_data_or_emulated(
|
||||
client, def->data_regs[index],
|
||||
sizeof(buff), (u8*)&buff);
|
||||
sizeof(rval), (u8*)&rval);
|
||||
if (ret < 0)
|
||||
goto exit;
|
||||
|
||||
|
@ -713,7 +714,7 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
|
|||
pm_runtime_put_autosuspend(&data->client->dev);
|
||||
|
||||
/* Swap bytes and convert to valid range. */
|
||||
buff = le16_to_cpu(buff);
|
||||
buff = le16_to_cpu(rval);
|
||||
*val = clamp_t(s16, buff, -def->range, def->range);
|
||||
return IIO_VAL_INT;
|
||||
|
||||
|
@ -813,6 +814,7 @@ static void ak8975_fill_buffer(struct iio_dev *indio_dev)
|
|||
const struct ak_def *def = data->def;
|
||||
int ret;
|
||||
s16 buff[8]; /* 3 x 16 bits axis values + 1 aligned 64 bits timestamp */
|
||||
__le16 fval[3];
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
|
@ -826,17 +828,17 @@ static void ak8975_fill_buffer(struct iio_dev *indio_dev)
|
|||
*/
|
||||
ret = i2c_smbus_read_i2c_block_data_or_emulated(client,
|
||||
def->data_regs[0],
|
||||
3 * sizeof(buff[0]),
|
||||
(u8 *)buff);
|
||||
3 * sizeof(fval[0]),
|
||||
(u8 *)fval);
|
||||
if (ret < 0)
|
||||
goto unlock;
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
/* Clamp to valid range. */
|
||||
buff[0] = clamp_t(s16, le16_to_cpu(buff[0]), -def->range, def->range);
|
||||
buff[1] = clamp_t(s16, le16_to_cpu(buff[1]), -def->range, def->range);
|
||||
buff[2] = clamp_t(s16, le16_to_cpu(buff[2]), -def->range, def->range);
|
||||
buff[0] = clamp_t(s16, le16_to_cpu(fval[0]), -def->range, def->range);
|
||||
buff[1] = clamp_t(s16, le16_to_cpu(fval[1]), -def->range, def->range);
|
||||
buff[2] = clamp_t(s16, le16_to_cpu(fval[2]), -def->range, def->range);
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, buff,
|
||||
iio_get_time_ns(indio_dev));
|
||||
|
|
|
@ -42,9 +42,17 @@ enum magn_3d_channel {
|
|||
MAGN_3D_CHANNEL_MAX,
|
||||
};
|
||||
|
||||
struct common_attributes {
|
||||
int scale_pre_decml;
|
||||
int scale_post_decml;
|
||||
int scale_precision;
|
||||
int value_offset;
|
||||
};
|
||||
|
||||
struct magn_3d_state {
|
||||
struct hid_sensor_hub_callbacks callbacks;
|
||||
struct hid_sensor_common common_attributes;
|
||||
struct hid_sensor_common magn_flux_attributes;
|
||||
struct hid_sensor_common rot_attributes;
|
||||
struct hid_sensor_hub_attribute_info magn[MAGN_3D_CHANNEL_MAX];
|
||||
|
||||
/* dynamically sized array to hold sensor values */
|
||||
|
@ -52,10 +60,8 @@ struct magn_3d_state {
|
|||
/* array of pointers to sensor value */
|
||||
u32 *magn_val_addr[MAGN_3D_CHANNEL_MAX];
|
||||
|
||||
int scale_pre_decml;
|
||||
int scale_post_decml;
|
||||
int scale_precision;
|
||||
int value_offset;
|
||||
struct common_attributes magn_flux_attr;
|
||||
struct common_attributes rot_attr;
|
||||
};
|
||||
|
||||
static const u32 magn_3d_addresses[MAGN_3D_CHANNEL_MAX] = {
|
||||
|
@ -162,41 +168,74 @@ static int magn_3d_read_raw(struct iio_dev *indio_dev,
|
|||
*val2 = 0;
|
||||
switch (mask) {
|
||||
case 0:
|
||||
hid_sensor_power_state(&magn_state->common_attributes, true);
|
||||
hid_sensor_power_state(&magn_state->magn_flux_attributes, true);
|
||||
report_id =
|
||||
magn_state->magn[chan->address].report_id;
|
||||
address = magn_3d_addresses[chan->address];
|
||||
if (report_id >= 0)
|
||||
*val = sensor_hub_input_attr_get_raw_value(
|
||||
magn_state->common_attributes.hsdev,
|
||||
magn_state->magn_flux_attributes.hsdev,
|
||||
HID_USAGE_SENSOR_COMPASS_3D, address,
|
||||
report_id,
|
||||
SENSOR_HUB_SYNC);
|
||||
else {
|
||||
*val = 0;
|
||||
hid_sensor_power_state(&magn_state->common_attributes,
|
||||
hid_sensor_power_state(
|
||||
&magn_state->magn_flux_attributes,
|
||||
false);
|
||||
return -EINVAL;
|
||||
}
|
||||
hid_sensor_power_state(&magn_state->common_attributes, false);
|
||||
hid_sensor_power_state(&magn_state->magn_flux_attributes,
|
||||
false);
|
||||
ret_type = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = magn_state->scale_pre_decml;
|
||||
*val2 = magn_state->scale_post_decml;
|
||||
ret_type = magn_state->scale_precision;
|
||||
switch (chan->type) {
|
||||
case IIO_MAGN:
|
||||
*val = magn_state->magn_flux_attr.scale_pre_decml;
|
||||
*val2 = magn_state->magn_flux_attr.scale_post_decml;
|
||||
ret_type = magn_state->magn_flux_attr.scale_precision;
|
||||
break;
|
||||
case IIO_ROT:
|
||||
*val = magn_state->rot_attr.scale_pre_decml;
|
||||
*val2 = magn_state->rot_attr.scale_post_decml;
|
||||
ret_type = magn_state->rot_attr.scale_precision;
|
||||
break;
|
||||
default:
|
||||
ret_type = -EINVAL;
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
*val = magn_state->value_offset;
|
||||
switch (chan->type) {
|
||||
case IIO_MAGN:
|
||||
*val = magn_state->magn_flux_attr.value_offset;
|
||||
ret_type = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_ROT:
|
||||
*val = magn_state->rot_attr.value_offset;
|
||||
ret_type = IIO_VAL_INT;
|
||||
break;
|
||||
default:
|
||||
ret_type = -EINVAL;
|
||||
}
|
||||
break;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret_type = hid_sensor_read_samp_freq_value(
|
||||
&magn_state->common_attributes, val, val2);
|
||||
&magn_state->magn_flux_attributes, val, val2);
|
||||
break;
|
||||
case IIO_CHAN_INFO_HYSTERESIS:
|
||||
switch (chan->type) {
|
||||
case IIO_MAGN:
|
||||
ret_type = hid_sensor_read_raw_hyst_value(
|
||||
&magn_state->common_attributes, val, val2);
|
||||
&magn_state->magn_flux_attributes, val, val2);
|
||||
break;
|
||||
case IIO_ROT:
|
||||
ret_type = hid_sensor_read_raw_hyst_value(
|
||||
&magn_state->rot_attributes, val, val2);
|
||||
break;
|
||||
default:
|
||||
ret_type = -EINVAL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret_type = -EINVAL;
|
||||
|
@ -219,11 +258,21 @@ static int magn_3d_write_raw(struct iio_dev *indio_dev,
|
|||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret = hid_sensor_write_samp_freq_value(
|
||||
&magn_state->common_attributes, val, val2);
|
||||
&magn_state->magn_flux_attributes, val, val2);
|
||||
break;
|
||||
case IIO_CHAN_INFO_HYSTERESIS:
|
||||
switch (chan->type) {
|
||||
case IIO_MAGN:
|
||||
ret = hid_sensor_write_raw_hyst_value(
|
||||
&magn_state->common_attributes, val, val2);
|
||||
&magn_state->magn_flux_attributes, val, val2);
|
||||
break;
|
||||
case IIO_ROT:
|
||||
ret = hid_sensor_write_raw_hyst_value(
|
||||
&magn_state->rot_attributes, val, val2);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
|
@ -254,7 +303,7 @@ static int magn_3d_proc_event(struct hid_sensor_hub_device *hsdev,
|
|||
struct magn_3d_state *magn_state = iio_priv(indio_dev);
|
||||
|
||||
dev_dbg(&indio_dev->dev, "magn_3d_proc_event\n");
|
||||
if (atomic_read(&magn_state->common_attributes.data_ready))
|
||||
if (atomic_read(&magn_state->magn_flux_attributes.data_ready))
|
||||
hid_sensor_push_data(indio_dev, magn_state->iio_vals);
|
||||
|
||||
return 0;
|
||||
|
@ -389,21 +438,48 @@ static int magn_3d_parse_report(struct platform_device *pdev,
|
|||
dev_dbg(&pdev->dev, "magn_3d Setup %d IIO channels\n",
|
||||
*chan_count);
|
||||
|
||||
st->scale_precision = hid_sensor_format_scale(
|
||||
st->magn_flux_attr.scale_precision = hid_sensor_format_scale(
|
||||
HID_USAGE_SENSOR_COMPASS_3D,
|
||||
&st->magn[CHANNEL_SCAN_INDEX_X],
|
||||
&st->scale_pre_decml, &st->scale_post_decml);
|
||||
&st->magn_flux_attr.scale_pre_decml,
|
||||
&st->magn_flux_attr.scale_post_decml);
|
||||
st->rot_attr.scale_precision
|
||||
= hid_sensor_format_scale(
|
||||
HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH,
|
||||
&st->magn[CHANNEL_SCAN_INDEX_NORTH_MAGN_TILT_COMP],
|
||||
&st->rot_attr.scale_pre_decml,
|
||||
&st->rot_attr.scale_post_decml);
|
||||
|
||||
/* Set Sensitivity field ids, when there is no individual modifier */
|
||||
if (st->common_attributes.sensitivity.index < 0) {
|
||||
if (st->magn_flux_attributes.sensitivity.index < 0) {
|
||||
sensor_hub_input_get_attribute_info(hsdev,
|
||||
HID_FEATURE_REPORT, usage_id,
|
||||
HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
|
||||
HID_USAGE_SENSOR_DATA_ORIENTATION,
|
||||
&st->common_attributes.sensitivity);
|
||||
&st->magn_flux_attributes.sensitivity);
|
||||
dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n",
|
||||
st->common_attributes.sensitivity.index,
|
||||
st->common_attributes.sensitivity.report_id);
|
||||
st->magn_flux_attributes.sensitivity.index,
|
||||
st->magn_flux_attributes.sensitivity.report_id);
|
||||
}
|
||||
if (st->magn_flux_attributes.sensitivity.index < 0) {
|
||||
sensor_hub_input_get_attribute_info(hsdev,
|
||||
HID_FEATURE_REPORT, usage_id,
|
||||
HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
|
||||
HID_USAGE_SENSOR_ORIENT_MAGN_FLUX,
|
||||
&st->magn_flux_attributes.sensitivity);
|
||||
dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n",
|
||||
st->magn_flux_attributes.sensitivity.index,
|
||||
st->magn_flux_attributes.sensitivity.report_id);
|
||||
}
|
||||
if (st->rot_attributes.sensitivity.index < 0) {
|
||||
sensor_hub_input_get_attribute_info(hsdev,
|
||||
HID_FEATURE_REPORT, usage_id,
|
||||
HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS |
|
||||
HID_USAGE_SENSOR_ORIENT_COMP_MAGN_NORTH,
|
||||
&st->rot_attributes.sensitivity);
|
||||
dev_dbg(&pdev->dev, "Sensitivity index:report %d:%d\n",
|
||||
st->rot_attributes.sensitivity.index,
|
||||
st->rot_attributes.sensitivity.report_id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -428,16 +504,17 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
|
|||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
magn_state = iio_priv(indio_dev);
|
||||
magn_state->common_attributes.hsdev = hsdev;
|
||||
magn_state->common_attributes.pdev = pdev;
|
||||
magn_state->magn_flux_attributes.hsdev = hsdev;
|
||||
magn_state->magn_flux_attributes.pdev = pdev;
|
||||
|
||||
ret = hid_sensor_parse_common_attributes(hsdev,
|
||||
HID_USAGE_SENSOR_COMPASS_3D,
|
||||
&magn_state->common_attributes);
|
||||
&magn_state->magn_flux_attributes);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to setup common attributes\n");
|
||||
return ret;
|
||||
}
|
||||
magn_state->rot_attributes = magn_state->magn_flux_attributes;
|
||||
|
||||
ret = magn_3d_parse_report(pdev, hsdev,
|
||||
&channels, &chan_count,
|
||||
|
@ -460,9 +537,9 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
|
|||
dev_err(&pdev->dev, "failed to initialize trigger buffer\n");
|
||||
return ret;
|
||||
}
|
||||
atomic_set(&magn_state->common_attributes.data_ready, 0);
|
||||
atomic_set(&magn_state->magn_flux_attributes.data_ready, 0);
|
||||
ret = hid_sensor_setup_trigger(indio_dev, name,
|
||||
&magn_state->common_attributes);
|
||||
&magn_state->magn_flux_attributes);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "trigger setup failed\n");
|
||||
goto error_unreg_buffer_funcs;
|
||||
|
@ -489,7 +566,7 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
|
|||
error_iio_unreg:
|
||||
iio_device_unregister(indio_dev);
|
||||
error_remove_trigger:
|
||||
hid_sensor_remove_trigger(&magn_state->common_attributes);
|
||||
hid_sensor_remove_trigger(&magn_state->magn_flux_attributes);
|
||||
error_unreg_buffer_funcs:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
return ret;
|
||||
|
@ -504,7 +581,7 @@ static int hid_magn_3d_remove(struct platform_device *pdev)
|
|||
|
||||
sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_COMPASS_3D);
|
||||
iio_device_unregister(indio_dev);
|
||||
hid_sensor_remove_trigger(&magn_state->common_attributes);
|
||||
hid_sensor_remove_trigger(&magn_state->magn_flux_attributes);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -46,139 +46,12 @@
|
|||
#define ST_MAGN_FS_AVL_15000MG 15000
|
||||
#define ST_MAGN_FS_AVL_16000MG 16000
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 0 */
|
||||
#define ST_MAGN_0_ODR_ADDR 0x00
|
||||
#define ST_MAGN_0_ODR_MASK 0x1c
|
||||
#define ST_MAGN_0_ODR_AVL_1HZ_VAL 0x00
|
||||
#define ST_MAGN_0_ODR_AVL_2HZ_VAL 0x01
|
||||
#define ST_MAGN_0_ODR_AVL_3HZ_VAL 0x02
|
||||
#define ST_MAGN_0_ODR_AVL_8HZ_VAL 0x03
|
||||
#define ST_MAGN_0_ODR_AVL_15HZ_VAL 0x04
|
||||
#define ST_MAGN_0_ODR_AVL_30HZ_VAL 0x05
|
||||
#define ST_MAGN_0_ODR_AVL_75HZ_VAL 0x06
|
||||
#define ST_MAGN_0_ODR_AVL_220HZ_VAL 0x07
|
||||
#define ST_MAGN_0_PW_ADDR 0x02
|
||||
#define ST_MAGN_0_PW_MASK 0x03
|
||||
#define ST_MAGN_0_PW_ON 0x00
|
||||
#define ST_MAGN_0_PW_OFF 0x03
|
||||
#define ST_MAGN_0_FS_ADDR 0x01
|
||||
#define ST_MAGN_0_FS_MASK 0xe0
|
||||
#define ST_MAGN_0_FS_AVL_1300_VAL 0x01
|
||||
#define ST_MAGN_0_FS_AVL_1900_VAL 0x02
|
||||
#define ST_MAGN_0_FS_AVL_2500_VAL 0x03
|
||||
#define ST_MAGN_0_FS_AVL_4000_VAL 0x04
|
||||
#define ST_MAGN_0_FS_AVL_4700_VAL 0x05
|
||||
#define ST_MAGN_0_FS_AVL_5600_VAL 0x06
|
||||
#define ST_MAGN_0_FS_AVL_8100_VAL 0x07
|
||||
#define ST_MAGN_0_FS_AVL_1300_GAIN_XY 1100
|
||||
#define ST_MAGN_0_FS_AVL_1900_GAIN_XY 855
|
||||
#define ST_MAGN_0_FS_AVL_2500_GAIN_XY 670
|
||||
#define ST_MAGN_0_FS_AVL_4000_GAIN_XY 450
|
||||
#define ST_MAGN_0_FS_AVL_4700_GAIN_XY 400
|
||||
#define ST_MAGN_0_FS_AVL_5600_GAIN_XY 330
|
||||
#define ST_MAGN_0_FS_AVL_8100_GAIN_XY 230
|
||||
#define ST_MAGN_0_FS_AVL_1300_GAIN_Z 980
|
||||
#define ST_MAGN_0_FS_AVL_1900_GAIN_Z 760
|
||||
#define ST_MAGN_0_FS_AVL_2500_GAIN_Z 600
|
||||
#define ST_MAGN_0_FS_AVL_4000_GAIN_Z 400
|
||||
#define ST_MAGN_0_FS_AVL_4700_GAIN_Z 355
|
||||
#define ST_MAGN_0_FS_AVL_5600_GAIN_Z 295
|
||||
#define ST_MAGN_0_FS_AVL_8100_GAIN_Z 205
|
||||
#define ST_MAGN_0_MULTIREAD_BIT false
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 1 */
|
||||
#define ST_MAGN_1_WAI_EXP 0x3c
|
||||
#define ST_MAGN_1_ODR_ADDR 0x00
|
||||
#define ST_MAGN_1_ODR_MASK 0x1c
|
||||
#define ST_MAGN_1_ODR_AVL_1HZ_VAL 0x00
|
||||
#define ST_MAGN_1_ODR_AVL_2HZ_VAL 0x01
|
||||
#define ST_MAGN_1_ODR_AVL_3HZ_VAL 0x02
|
||||
#define ST_MAGN_1_ODR_AVL_8HZ_VAL 0x03
|
||||
#define ST_MAGN_1_ODR_AVL_15HZ_VAL 0x04
|
||||
#define ST_MAGN_1_ODR_AVL_30HZ_VAL 0x05
|
||||
#define ST_MAGN_1_ODR_AVL_75HZ_VAL 0x06
|
||||
#define ST_MAGN_1_ODR_AVL_220HZ_VAL 0x07
|
||||
#define ST_MAGN_1_PW_ADDR 0x02
|
||||
#define ST_MAGN_1_PW_MASK 0x03
|
||||
#define ST_MAGN_1_PW_ON 0x00
|
||||
#define ST_MAGN_1_PW_OFF 0x03
|
||||
#define ST_MAGN_1_FS_ADDR 0x01
|
||||
#define ST_MAGN_1_FS_MASK 0xe0
|
||||
#define ST_MAGN_1_FS_AVL_1300_VAL 0x01
|
||||
#define ST_MAGN_1_FS_AVL_1900_VAL 0x02
|
||||
#define ST_MAGN_1_FS_AVL_2500_VAL 0x03
|
||||
#define ST_MAGN_1_FS_AVL_4000_VAL 0x04
|
||||
#define ST_MAGN_1_FS_AVL_4700_VAL 0x05
|
||||
#define ST_MAGN_1_FS_AVL_5600_VAL 0x06
|
||||
#define ST_MAGN_1_FS_AVL_8100_VAL 0x07
|
||||
#define ST_MAGN_1_FS_AVL_1300_GAIN_XY 909
|
||||
#define ST_MAGN_1_FS_AVL_1900_GAIN_XY 1169
|
||||
#define ST_MAGN_1_FS_AVL_2500_GAIN_XY 1492
|
||||
#define ST_MAGN_1_FS_AVL_4000_GAIN_XY 2222
|
||||
#define ST_MAGN_1_FS_AVL_4700_GAIN_XY 2500
|
||||
#define ST_MAGN_1_FS_AVL_5600_GAIN_XY 3030
|
||||
#define ST_MAGN_1_FS_AVL_8100_GAIN_XY 4347
|
||||
#define ST_MAGN_1_FS_AVL_1300_GAIN_Z 1020
|
||||
#define ST_MAGN_1_FS_AVL_1900_GAIN_Z 1315
|
||||
#define ST_MAGN_1_FS_AVL_2500_GAIN_Z 1666
|
||||
#define ST_MAGN_1_FS_AVL_4000_GAIN_Z 2500
|
||||
#define ST_MAGN_1_FS_AVL_4700_GAIN_Z 2816
|
||||
#define ST_MAGN_1_FS_AVL_5600_GAIN_Z 3389
|
||||
#define ST_MAGN_1_FS_AVL_8100_GAIN_Z 4878
|
||||
#define ST_MAGN_1_MULTIREAD_BIT false
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 2 */
|
||||
#define ST_MAGN_2_WAI_EXP 0x3d
|
||||
#define ST_MAGN_2_ODR_ADDR 0x20
|
||||
#define ST_MAGN_2_ODR_MASK 0x1c
|
||||
#define ST_MAGN_2_ODR_AVL_1HZ_VAL 0x00
|
||||
#define ST_MAGN_2_ODR_AVL_2HZ_VAL 0x01
|
||||
#define ST_MAGN_2_ODR_AVL_3HZ_VAL 0x02
|
||||
#define ST_MAGN_2_ODR_AVL_5HZ_VAL 0x03
|
||||
#define ST_MAGN_2_ODR_AVL_10HZ_VAL 0x04
|
||||
#define ST_MAGN_2_ODR_AVL_20HZ_VAL 0x05
|
||||
#define ST_MAGN_2_ODR_AVL_40HZ_VAL 0x06
|
||||
#define ST_MAGN_2_ODR_AVL_80HZ_VAL 0x07
|
||||
#define ST_MAGN_2_PW_ADDR 0x22
|
||||
#define ST_MAGN_2_PW_MASK 0x03
|
||||
#define ST_MAGN_2_PW_ON 0x00
|
||||
#define ST_MAGN_2_PW_OFF 0x03
|
||||
#define ST_MAGN_2_FS_ADDR 0x21
|
||||
#define ST_MAGN_2_FS_MASK 0x60
|
||||
#define ST_MAGN_2_FS_AVL_4000_VAL 0x00
|
||||
#define ST_MAGN_2_FS_AVL_8000_VAL 0x01
|
||||
#define ST_MAGN_2_FS_AVL_12000_VAL 0x02
|
||||
#define ST_MAGN_2_FS_AVL_16000_VAL 0x03
|
||||
#define ST_MAGN_2_FS_AVL_4000_GAIN 146
|
||||
#define ST_MAGN_2_FS_AVL_8000_GAIN 292
|
||||
#define ST_MAGN_2_FS_AVL_12000_GAIN 438
|
||||
#define ST_MAGN_2_FS_AVL_16000_GAIN 584
|
||||
#define ST_MAGN_2_MULTIREAD_BIT false
|
||||
/* Special L addresses for Sensor 2 */
|
||||
#define ST_MAGN_2_OUT_X_L_ADDR 0x28
|
||||
#define ST_MAGN_2_OUT_Y_L_ADDR 0x2a
|
||||
#define ST_MAGN_2_OUT_Z_L_ADDR 0x2c
|
||||
|
||||
/* CUSTOM VALUES FOR SENSOR 3 */
|
||||
#define ST_MAGN_3_WAI_ADDR 0x4f
|
||||
#define ST_MAGN_3_WAI_EXP 0x40
|
||||
#define ST_MAGN_3_ODR_ADDR 0x60
|
||||
#define ST_MAGN_3_ODR_MASK 0x0c
|
||||
#define ST_MAGN_3_ODR_AVL_10HZ_VAL 0x00
|
||||
#define ST_MAGN_3_ODR_AVL_20HZ_VAL 0x01
|
||||
#define ST_MAGN_3_ODR_AVL_50HZ_VAL 0x02
|
||||
#define ST_MAGN_3_ODR_AVL_100HZ_VAL 0x03
|
||||
#define ST_MAGN_3_PW_ADDR 0x60
|
||||
#define ST_MAGN_3_PW_MASK 0x03
|
||||
#define ST_MAGN_3_PW_ON 0x00
|
||||
#define ST_MAGN_3_PW_OFF 0x03
|
||||
#define ST_MAGN_3_BDU_ADDR 0x62
|
||||
#define ST_MAGN_3_BDU_MASK 0x10
|
||||
#define ST_MAGN_3_DRDY_IRQ_ADDR 0x62
|
||||
#define ST_MAGN_3_DRDY_INT_MASK 0x01
|
||||
#define ST_MAGN_3_IHL_IRQ_ADDR 0x63
|
||||
#define ST_MAGN_3_IHL_IRQ_MASK 0x04
|
||||
#define ST_MAGN_3_FS_AVL_15000_GAIN 1500
|
||||
#define ST_MAGN_3_MULTIREAD_BIT false
|
||||
/* Special L addresses for sensor 3 */
|
||||
#define ST_MAGN_3_OUT_X_L_ADDR 0x68
|
||||
#define ST_MAGN_3_OUT_Y_L_ADDR 0x6a
|
||||
#define ST_MAGN_3_OUT_Z_L_ADDR 0x6c
|
||||
|
@ -240,77 +113,78 @@ static const struct st_sensor_settings st_magn_sensors_settings[] = {
|
|||
},
|
||||
.ch = (struct iio_chan_spec *)st_magn_16bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_MAGN_0_ODR_ADDR,
|
||||
.mask = ST_MAGN_0_ODR_MASK,
|
||||
.addr = 0x00,
|
||||
.mask = 0x1c,
|
||||
.odr_avl = {
|
||||
{ 1, ST_MAGN_0_ODR_AVL_1HZ_VAL, },
|
||||
{ 2, ST_MAGN_0_ODR_AVL_2HZ_VAL, },
|
||||
{ 3, ST_MAGN_0_ODR_AVL_3HZ_VAL, },
|
||||
{ 8, ST_MAGN_0_ODR_AVL_8HZ_VAL, },
|
||||
{ 15, ST_MAGN_0_ODR_AVL_15HZ_VAL, },
|
||||
{ 30, ST_MAGN_0_ODR_AVL_30HZ_VAL, },
|
||||
{ 75, ST_MAGN_0_ODR_AVL_75HZ_VAL, },
|
||||
{ .hz = 1, .value = 0x00 },
|
||||
{ .hz = 2, .value = 0x01 },
|
||||
{ .hz = 3, .value = 0x02 },
|
||||
{ .hz = 8, .value = 0x03 },
|
||||
{ .hz = 15, .value = 0x04 },
|
||||
{ .hz = 30, .value = 0x05 },
|
||||
{ .hz = 75, .value = 0x06 },
|
||||
/* 220 Hz, 0x07 reportedly exist */
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_MAGN_0_PW_ADDR,
|
||||
.mask = ST_MAGN_0_PW_MASK,
|
||||
.value_on = ST_MAGN_0_PW_ON,
|
||||
.value_off = ST_MAGN_0_PW_OFF,
|
||||
.addr = 0x02,
|
||||
.mask = 0x03,
|
||||
.value_on = 0x00,
|
||||
.value_off = 0x03,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_MAGN_0_FS_ADDR,
|
||||
.mask = ST_MAGN_0_FS_MASK,
|
||||
.addr = 0x01,
|
||||
.mask = 0xe0,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_MAGN_FS_AVL_1300MG,
|
||||
.value = ST_MAGN_0_FS_AVL_1300_VAL,
|
||||
.gain = ST_MAGN_0_FS_AVL_1300_GAIN_XY,
|
||||
.gain2 = ST_MAGN_0_FS_AVL_1300_GAIN_Z,
|
||||
.value = 0x01,
|
||||
.gain = 1100,
|
||||
.gain2 = 980,
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_MAGN_FS_AVL_1900MG,
|
||||
.value = ST_MAGN_0_FS_AVL_1900_VAL,
|
||||
.gain = ST_MAGN_0_FS_AVL_1900_GAIN_XY,
|
||||
.gain2 = ST_MAGN_0_FS_AVL_1900_GAIN_Z,
|
||||
.value = 0x02,
|
||||
.gain = 855,
|
||||
.gain2 = 760,
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_MAGN_FS_AVL_2500MG,
|
||||
.value = ST_MAGN_0_FS_AVL_2500_VAL,
|
||||
.gain = ST_MAGN_0_FS_AVL_2500_GAIN_XY,
|
||||
.gain2 = ST_MAGN_0_FS_AVL_2500_GAIN_Z,
|
||||
.value = 0x03,
|
||||
.gain = 670,
|
||||
.gain2 = 600,
|
||||
},
|
||||
[3] = {
|
||||
.num = ST_MAGN_FS_AVL_4000MG,
|
||||
.value = ST_MAGN_0_FS_AVL_4000_VAL,
|
||||
.gain = ST_MAGN_0_FS_AVL_4000_GAIN_XY,
|
||||
.gain2 = ST_MAGN_0_FS_AVL_4000_GAIN_Z,
|
||||
.value = 0x04,
|
||||
.gain = 450,
|
||||
.gain2 = 400,
|
||||
},
|
||||
[4] = {
|
||||
.num = ST_MAGN_FS_AVL_4700MG,
|
||||
.value = ST_MAGN_0_FS_AVL_4700_VAL,
|
||||
.gain = ST_MAGN_0_FS_AVL_4700_GAIN_XY,
|
||||
.gain2 = ST_MAGN_0_FS_AVL_4700_GAIN_Z,
|
||||
.value = 0x05,
|
||||
.gain = 400,
|
||||
.gain2 = 355,
|
||||
},
|
||||
[5] = {
|
||||
.num = ST_MAGN_FS_AVL_5600MG,
|
||||
.value = ST_MAGN_0_FS_AVL_5600_VAL,
|
||||
.gain = ST_MAGN_0_FS_AVL_5600_GAIN_XY,
|
||||
.gain2 = ST_MAGN_0_FS_AVL_5600_GAIN_Z,
|
||||
.value = 0x06,
|
||||
.gain = 330,
|
||||
.gain2 = 295,
|
||||
},
|
||||
[6] = {
|
||||
.num = ST_MAGN_FS_AVL_8100MG,
|
||||
.value = ST_MAGN_0_FS_AVL_8100_VAL,
|
||||
.gain = ST_MAGN_0_FS_AVL_8100_GAIN_XY,
|
||||
.gain2 = ST_MAGN_0_FS_AVL_8100_GAIN_Z,
|
||||
.value = 0x07,
|
||||
.gain = 230,
|
||||
.gain2 = 205,
|
||||
},
|
||||
},
|
||||
},
|
||||
.multi_read_bit = ST_MAGN_0_MULTIREAD_BIT,
|
||||
.multi_read_bit = false,
|
||||
.bootime = 2,
|
||||
},
|
||||
{
|
||||
.wai = ST_MAGN_1_WAI_EXP,
|
||||
.wai = 0x3c,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = LSM303DLHC_MAGN_DEV_NAME,
|
||||
|
@ -318,175 +192,175 @@ static const struct st_sensor_settings st_magn_sensors_settings[] = {
|
|||
},
|
||||
.ch = (struct iio_chan_spec *)st_magn_16bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_MAGN_1_ODR_ADDR,
|
||||
.mask = ST_MAGN_1_ODR_MASK,
|
||||
.addr = 0x00,
|
||||
.mask = 0x1c,
|
||||
.odr_avl = {
|
||||
{ 1, ST_MAGN_1_ODR_AVL_1HZ_VAL, },
|
||||
{ 2, ST_MAGN_1_ODR_AVL_2HZ_VAL, },
|
||||
{ 3, ST_MAGN_1_ODR_AVL_3HZ_VAL, },
|
||||
{ 8, ST_MAGN_1_ODR_AVL_8HZ_VAL, },
|
||||
{ 15, ST_MAGN_1_ODR_AVL_15HZ_VAL, },
|
||||
{ 30, ST_MAGN_1_ODR_AVL_30HZ_VAL, },
|
||||
{ 75, ST_MAGN_1_ODR_AVL_75HZ_VAL, },
|
||||
{ 220, ST_MAGN_1_ODR_AVL_220HZ_VAL, },
|
||||
{ .hz = 1, .value = 0x00 },
|
||||
{ .hz = 2, .value = 0x01 },
|
||||
{ .hz = 3, .value = 0x02 },
|
||||
{ .hz = 8, .value = 0x03 },
|
||||
{ .hz = 15, .value = 0x04 },
|
||||
{ .hz = 30, .value = 0x05 },
|
||||
{ .hz = 75, .value = 0x06 },
|
||||
{ .hz = 220, .value = 0x07 },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_MAGN_1_PW_ADDR,
|
||||
.mask = ST_MAGN_1_PW_MASK,
|
||||
.value_on = ST_MAGN_1_PW_ON,
|
||||
.value_off = ST_MAGN_1_PW_OFF,
|
||||
.addr = 0x02,
|
||||
.mask = 0x03,
|
||||
.value_on = 0x00,
|
||||
.value_off = 0x03,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_MAGN_1_FS_ADDR,
|
||||
.mask = ST_MAGN_1_FS_MASK,
|
||||
.addr = 0x01,
|
||||
.mask = 0xe0,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_MAGN_FS_AVL_1300MG,
|
||||
.value = ST_MAGN_1_FS_AVL_1300_VAL,
|
||||
.gain = ST_MAGN_1_FS_AVL_1300_GAIN_XY,
|
||||
.gain2 = ST_MAGN_1_FS_AVL_1300_GAIN_Z,
|
||||
.value = 0x01,
|
||||
.gain = 909,
|
||||
.gain2 = 1020,
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_MAGN_FS_AVL_1900MG,
|
||||
.value = ST_MAGN_1_FS_AVL_1900_VAL,
|
||||
.gain = ST_MAGN_1_FS_AVL_1900_GAIN_XY,
|
||||
.gain2 = ST_MAGN_1_FS_AVL_1900_GAIN_Z,
|
||||
.value = 0x02,
|
||||
.gain = 1169,
|
||||
.gain2 = 1315,
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_MAGN_FS_AVL_2500MG,
|
||||
.value = ST_MAGN_1_FS_AVL_2500_VAL,
|
||||
.gain = ST_MAGN_1_FS_AVL_2500_GAIN_XY,
|
||||
.gain2 = ST_MAGN_1_FS_AVL_2500_GAIN_Z,
|
||||
.value = 0x03,
|
||||
.gain = 1492,
|
||||
.gain2 = 1666,
|
||||
},
|
||||
[3] = {
|
||||
.num = ST_MAGN_FS_AVL_4000MG,
|
||||
.value = ST_MAGN_1_FS_AVL_4000_VAL,
|
||||
.gain = ST_MAGN_1_FS_AVL_4000_GAIN_XY,
|
||||
.gain2 = ST_MAGN_1_FS_AVL_4000_GAIN_Z,
|
||||
.value = 0x04,
|
||||
.gain = 2222,
|
||||
.gain2 = 2500,
|
||||
},
|
||||
[4] = {
|
||||
.num = ST_MAGN_FS_AVL_4700MG,
|
||||
.value = ST_MAGN_1_FS_AVL_4700_VAL,
|
||||
.gain = ST_MAGN_1_FS_AVL_4700_GAIN_XY,
|
||||
.gain2 = ST_MAGN_1_FS_AVL_4700_GAIN_Z,
|
||||
.value = 0x05,
|
||||
.gain = 2500,
|
||||
.gain2 = 2816,
|
||||
},
|
||||
[5] = {
|
||||
.num = ST_MAGN_FS_AVL_5600MG,
|
||||
.value = ST_MAGN_1_FS_AVL_5600_VAL,
|
||||
.gain = ST_MAGN_1_FS_AVL_5600_GAIN_XY,
|
||||
.gain2 = ST_MAGN_1_FS_AVL_5600_GAIN_Z,
|
||||
.value = 0x06,
|
||||
.gain = 3030,
|
||||
.gain2 = 3389,
|
||||
},
|
||||
[6] = {
|
||||
.num = ST_MAGN_FS_AVL_8100MG,
|
||||
.value = ST_MAGN_1_FS_AVL_8100_VAL,
|
||||
.gain = ST_MAGN_1_FS_AVL_8100_GAIN_XY,
|
||||
.gain2 = ST_MAGN_1_FS_AVL_8100_GAIN_Z,
|
||||
.value = 0x07,
|
||||
.gain = 4347,
|
||||
.gain2 = 4878,
|
||||
},
|
||||
},
|
||||
},
|
||||
.multi_read_bit = ST_MAGN_1_MULTIREAD_BIT,
|
||||
.multi_read_bit = false,
|
||||
.bootime = 2,
|
||||
},
|
||||
{
|
||||
.wai = ST_MAGN_2_WAI_EXP,
|
||||
.wai = 0x3d,
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = LIS3MDL_MAGN_DEV_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_magn_2_16bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_MAGN_2_ODR_ADDR,
|
||||
.mask = ST_MAGN_2_ODR_MASK,
|
||||
.addr = 0x20,
|
||||
.mask = 0x1c,
|
||||
.odr_avl = {
|
||||
{ 1, ST_MAGN_2_ODR_AVL_1HZ_VAL, },
|
||||
{ 2, ST_MAGN_2_ODR_AVL_2HZ_VAL, },
|
||||
{ 3, ST_MAGN_2_ODR_AVL_3HZ_VAL, },
|
||||
{ 5, ST_MAGN_2_ODR_AVL_5HZ_VAL, },
|
||||
{ 10, ST_MAGN_2_ODR_AVL_10HZ_VAL, },
|
||||
{ 20, ST_MAGN_2_ODR_AVL_20HZ_VAL, },
|
||||
{ 40, ST_MAGN_2_ODR_AVL_40HZ_VAL, },
|
||||
{ 80, ST_MAGN_2_ODR_AVL_80HZ_VAL, },
|
||||
{ .hz = 1, .value = 0x00 },
|
||||
{ .hz = 2, .value = 0x01 },
|
||||
{ .hz = 3, .value = 0x02 },
|
||||
{ .hz = 5, .value = 0x03 },
|
||||
{ .hz = 10, .value = 0x04 },
|
||||
{ .hz = 20, .value = 0x05 },
|
||||
{ .hz = 40, .value = 0x06 },
|
||||
{ .hz = 80, .value = 0x07 },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_MAGN_2_PW_ADDR,
|
||||
.mask = ST_MAGN_2_PW_MASK,
|
||||
.value_on = ST_MAGN_2_PW_ON,
|
||||
.value_off = ST_MAGN_2_PW_OFF,
|
||||
.addr = 0x22,
|
||||
.mask = 0x03,
|
||||
.value_on = 0x00,
|
||||
.value_off = 0x03,
|
||||
},
|
||||
.fs = {
|
||||
.addr = ST_MAGN_2_FS_ADDR,
|
||||
.mask = ST_MAGN_2_FS_MASK,
|
||||
.addr = 0x21,
|
||||
.mask = 0x60,
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_MAGN_FS_AVL_4000MG,
|
||||
.value = ST_MAGN_2_FS_AVL_4000_VAL,
|
||||
.gain = ST_MAGN_2_FS_AVL_4000_GAIN,
|
||||
.value = 0x00,
|
||||
.gain = 146,
|
||||
},
|
||||
[1] = {
|
||||
.num = ST_MAGN_FS_AVL_8000MG,
|
||||
.value = ST_MAGN_2_FS_AVL_8000_VAL,
|
||||
.gain = ST_MAGN_2_FS_AVL_8000_GAIN,
|
||||
.value = 0x01,
|
||||
.gain = 292,
|
||||
},
|
||||
[2] = {
|
||||
.num = ST_MAGN_FS_AVL_12000MG,
|
||||
.value = ST_MAGN_2_FS_AVL_12000_VAL,
|
||||
.gain = ST_MAGN_2_FS_AVL_12000_GAIN,
|
||||
.value = 0x02,
|
||||
.gain = 438,
|
||||
},
|
||||
[3] = {
|
||||
.num = ST_MAGN_FS_AVL_16000MG,
|
||||
.value = ST_MAGN_2_FS_AVL_16000_VAL,
|
||||
.gain = ST_MAGN_2_FS_AVL_16000_GAIN,
|
||||
.value = 0x03,
|
||||
.gain = 584,
|
||||
},
|
||||
},
|
||||
},
|
||||
.multi_read_bit = ST_MAGN_2_MULTIREAD_BIT,
|
||||
.multi_read_bit = false,
|
||||
.bootime = 2,
|
||||
},
|
||||
{
|
||||
.wai = ST_MAGN_3_WAI_EXP,
|
||||
.wai_addr = ST_MAGN_3_WAI_ADDR,
|
||||
.wai = 0x40,
|
||||
.wai_addr = 0x4f,
|
||||
.sensors_supported = {
|
||||
[0] = LSM303AGR_MAGN_DEV_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_magn_3_16bit_channels,
|
||||
.odr = {
|
||||
.addr = ST_MAGN_3_ODR_ADDR,
|
||||
.mask = ST_MAGN_3_ODR_MASK,
|
||||
.addr = 0x60,
|
||||
.mask = 0x0c,
|
||||
.odr_avl = {
|
||||
{ 10, ST_MAGN_3_ODR_AVL_10HZ_VAL, },
|
||||
{ 20, ST_MAGN_3_ODR_AVL_20HZ_VAL, },
|
||||
{ 50, ST_MAGN_3_ODR_AVL_50HZ_VAL, },
|
||||
{ 100, ST_MAGN_3_ODR_AVL_100HZ_VAL, },
|
||||
{ .hz = 10, .value = 0x00 },
|
||||
{ .hz = 20, .value = 0x01 },
|
||||
{ .hz = 50, .value = 0x02 },
|
||||
{ .hz = 100, .value = 0x03 },
|
||||
},
|
||||
},
|
||||
.pw = {
|
||||
.addr = ST_MAGN_3_PW_ADDR,
|
||||
.mask = ST_MAGN_3_PW_MASK,
|
||||
.value_on = ST_MAGN_3_PW_ON,
|
||||
.value_off = ST_MAGN_3_PW_OFF,
|
||||
.addr = 0x60,
|
||||
.mask = 0x03,
|
||||
.value_on = 0x00,
|
||||
.value_off = 0x03,
|
||||
},
|
||||
.fs = {
|
||||
.fs_avl = {
|
||||
[0] = {
|
||||
.num = ST_MAGN_FS_AVL_15000MG,
|
||||
.gain = ST_MAGN_3_FS_AVL_15000_GAIN,
|
||||
.gain = 1500,
|
||||
},
|
||||
},
|
||||
},
|
||||
.bdu = {
|
||||
.addr = ST_MAGN_3_BDU_ADDR,
|
||||
.mask = ST_MAGN_3_BDU_MASK,
|
||||
.addr = 0x62,
|
||||
.mask = 0x10,
|
||||
},
|
||||
.drdy_irq = {
|
||||
.addr = ST_MAGN_3_DRDY_IRQ_ADDR,
|
||||
.mask_int1 = ST_MAGN_3_DRDY_INT_MASK,
|
||||
.addr_ihl = ST_MAGN_3_IHL_IRQ_ADDR,
|
||||
.mask_ihl = ST_MAGN_3_IHL_IRQ_MASK,
|
||||
.addr = 0x62,
|
||||
.mask_int1 = 0x01,
|
||||
.addr_ihl = 0x63,
|
||||
.mask_ihl = 0x04,
|
||||
.addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR,
|
||||
},
|
||||
.multi_read_bit = ST_MAGN_3_MULTIREAD_BIT,
|
||||
.multi_read_bit = false,
|
||||
.bootime = 2,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
struct mcp4531_cfg {
|
||||
int wipers;
|
||||
int max_pos;
|
||||
int avail[3];
|
||||
int kohms;
|
||||
};
|
||||
|
||||
|
@ -78,38 +78,38 @@ enum mcp4531_type {
|
|||
};
|
||||
|
||||
static const struct mcp4531_cfg mcp4531_cfg[] = {
|
||||
[MCP453x_502] = { .wipers = 1, .max_pos = 128, .kohms = 5, },
|
||||
[MCP453x_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, },
|
||||
[MCP453x_503] = { .wipers = 1, .max_pos = 128, .kohms = 50, },
|
||||
[MCP453x_104] = { .wipers = 1, .max_pos = 128, .kohms = 100, },
|
||||
[MCP454x_502] = { .wipers = 1, .max_pos = 128, .kohms = 5, },
|
||||
[MCP454x_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, },
|
||||
[MCP454x_503] = { .wipers = 1, .max_pos = 128, .kohms = 50, },
|
||||
[MCP454x_104] = { .wipers = 1, .max_pos = 128, .kohms = 100, },
|
||||
[MCP455x_502] = { .wipers = 1, .max_pos = 256, .kohms = 5, },
|
||||
[MCP455x_103] = { .wipers = 1, .max_pos = 256, .kohms = 10, },
|
||||
[MCP455x_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, },
|
||||
[MCP455x_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, },
|
||||
[MCP456x_502] = { .wipers = 1, .max_pos = 256, .kohms = 5, },
|
||||
[MCP456x_103] = { .wipers = 1, .max_pos = 256, .kohms = 10, },
|
||||
[MCP456x_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, },
|
||||
[MCP456x_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, },
|
||||
[MCP463x_502] = { .wipers = 2, .max_pos = 128, .kohms = 5, },
|
||||
[MCP463x_103] = { .wipers = 2, .max_pos = 128, .kohms = 10, },
|
||||
[MCP463x_503] = { .wipers = 2, .max_pos = 128, .kohms = 50, },
|
||||
[MCP463x_104] = { .wipers = 2, .max_pos = 128, .kohms = 100, },
|
||||
[MCP464x_502] = { .wipers = 2, .max_pos = 128, .kohms = 5, },
|
||||
[MCP464x_103] = { .wipers = 2, .max_pos = 128, .kohms = 10, },
|
||||
[MCP464x_503] = { .wipers = 2, .max_pos = 128, .kohms = 50, },
|
||||
[MCP464x_104] = { .wipers = 2, .max_pos = 128, .kohms = 100, },
|
||||
[MCP465x_502] = { .wipers = 2, .max_pos = 256, .kohms = 5, },
|
||||
[MCP465x_103] = { .wipers = 2, .max_pos = 256, .kohms = 10, },
|
||||
[MCP465x_503] = { .wipers = 2, .max_pos = 256, .kohms = 50, },
|
||||
[MCP465x_104] = { .wipers = 2, .max_pos = 256, .kohms = 100, },
|
||||
[MCP466x_502] = { .wipers = 2, .max_pos = 256, .kohms = 5, },
|
||||
[MCP466x_103] = { .wipers = 2, .max_pos = 256, .kohms = 10, },
|
||||
[MCP466x_503] = { .wipers = 2, .max_pos = 256, .kohms = 50, },
|
||||
[MCP466x_104] = { .wipers = 2, .max_pos = 256, .kohms = 100, },
|
||||
[MCP453x_502] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 5, },
|
||||
[MCP453x_103] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 10, },
|
||||
[MCP453x_503] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 50, },
|
||||
[MCP453x_104] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 100, },
|
||||
[MCP454x_502] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 5, },
|
||||
[MCP454x_103] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 10, },
|
||||
[MCP454x_503] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 50, },
|
||||
[MCP454x_104] = { .wipers = 1, .avail = { 0, 1, 128 }, .kohms = 100, },
|
||||
[MCP455x_502] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 5, },
|
||||
[MCP455x_103] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 10, },
|
||||
[MCP455x_503] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 50, },
|
||||
[MCP455x_104] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 100, },
|
||||
[MCP456x_502] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 5, },
|
||||
[MCP456x_103] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 10, },
|
||||
[MCP456x_503] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 50, },
|
||||
[MCP456x_104] = { .wipers = 1, .avail = { 0, 1, 256 }, .kohms = 100, },
|
||||
[MCP463x_502] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 5, },
|
||||
[MCP463x_103] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 10, },
|
||||
[MCP463x_503] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 50, },
|
||||
[MCP463x_104] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 100, },
|
||||
[MCP464x_502] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 5, },
|
||||
[MCP464x_103] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 10, },
|
||||
[MCP464x_503] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 50, },
|
||||
[MCP464x_104] = { .wipers = 2, .avail = { 0, 1, 128 }, .kohms = 100, },
|
||||
[MCP465x_502] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 5, },
|
||||
[MCP465x_103] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 10, },
|
||||
[MCP465x_503] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 50, },
|
||||
[MCP465x_104] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 100, },
|
||||
[MCP466x_502] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 5, },
|
||||
[MCP466x_103] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 10, },
|
||||
[MCP466x_503] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 50, },
|
||||
[MCP466x_104] = { .wipers = 2, .avail = { 0, 1, 256 }, .kohms = 100, },
|
||||
};
|
||||
|
||||
#define MCP4531_WRITE (0 << 2)
|
||||
|
@ -131,6 +131,7 @@ struct mcp4531_data {
|
|||
.channel = (ch), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_RAW), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec mcp4531_channels[] = {
|
||||
|
@ -156,13 +157,31 @@ static int mcp4531_read_raw(struct iio_dev *indio_dev,
|
|||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 1000 * data->cfg->kohms;
|
||||
*val2 = data->cfg->max_pos;
|
||||
*val2 = data->cfg->avail[2];
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int mcp4531_read_avail(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
const int **vals, int *type, int *length,
|
||||
long mask)
|
||||
{
|
||||
struct mcp4531_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
*length = ARRAY_SIZE(data->cfg->avail);
|
||||
*vals = data->cfg->avail;
|
||||
*type = IIO_VAL_INT;
|
||||
return IIO_AVAIL_RANGE;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int mcp4531_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
|
@ -172,7 +191,7 @@ static int mcp4531_write_raw(struct iio_dev *indio_dev,
|
|||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (val > data->cfg->max_pos || val < 0)
|
||||
if (val > data->cfg->avail[2] || val < 0)
|
||||
return -EINVAL;
|
||||
break;
|
||||
default:
|
||||
|
@ -186,6 +205,7 @@ static int mcp4531_write_raw(struct iio_dev *indio_dev,
|
|||
|
||||
static const struct iio_info mcp4531_info = {
|
||||
.read_raw = mcp4531_read_raw,
|
||||
.read_avail = mcp4531_read_avail,
|
||||
.write_raw = mcp4531_write_raw,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
#
|
||||
# Potentiostat drivers
|
||||
#
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
|
||||
menu "Digital potentiostats"
|
||||
|
||||
config LMP91000
|
||||
tristate "Texas Instruments LMP91000 potentiostat driver"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_BUFFER_CB
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
Say yes here to build support for the Texas Instruments
|
||||
LMP91000 digital potentiostat chip.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called lmp91000
|
||||
|
||||
endmenu
|
|
@ -0,0 +1,6 @@
|
|||
#
|
||||
# Makefile for industrial I/O potentiostat drivers
|
||||
#
|
||||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
obj-$(CONFIG_LMP91000) += lmp91000.o
|
|
@ -0,0 +1,446 @@
|
|||
/*
|
||||
* lmp91000.c - Support for Texas Instruments digital potentiostats
|
||||
*
|
||||
* Copyright (C) 2016 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: bias voltage + polarity control, and multiple chip support
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/consumer.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
#define LMP91000_REG_LOCK 0x01
|
||||
#define LMP91000_REG_TIACN 0x10
|
||||
#define LMP91000_REG_TIACN_GAIN_SHIFT 2
|
||||
|
||||
#define LMP91000_REG_REFCN 0x11
|
||||
#define LMP91000_REG_REFCN_EXT_REF 0x20
|
||||
#define LMP91000_REG_REFCN_50_ZERO 0x80
|
||||
|
||||
#define LMP91000_REG_MODECN 0x12
|
||||
#define LMP91000_REG_MODECN_3LEAD 0x03
|
||||
#define LMP91000_REG_MODECN_TEMP 0x07
|
||||
|
||||
#define LMP91000_DRV_NAME "lmp91000"
|
||||
|
||||
static const int lmp91000_tia_gain[] = { 0, 2750, 3500, 7000, 14000, 35000,
|
||||
120000, 350000 };
|
||||
|
||||
static const int lmp91000_rload[] = { 10, 33, 50, 100 };
|
||||
|
||||
#define LMP91000_TEMP_BASE -40
|
||||
|
||||
static const u16 lmp91000_temp_lut[] = {
|
||||
1875, 1867, 1860, 1852, 1844, 1836, 1828, 1821, 1813, 1805,
|
||||
1797, 1789, 1782, 1774, 1766, 1758, 1750, 1742, 1734, 1727,
|
||||
1719, 1711, 1703, 1695, 1687, 1679, 1671, 1663, 1656, 1648,
|
||||
1640, 1632, 1624, 1616, 1608, 1600, 1592, 1584, 1576, 1568,
|
||||
1560, 1552, 1544, 1536, 1528, 1520, 1512, 1504, 1496, 1488,
|
||||
1480, 1472, 1464, 1456, 1448, 1440, 1432, 1424, 1415, 1407,
|
||||
1399, 1391, 1383, 1375, 1367, 1359, 1351, 1342, 1334, 1326,
|
||||
1318, 1310, 1302, 1293, 1285, 1277, 1269, 1261, 1253, 1244,
|
||||
1236, 1228, 1220, 1212, 1203, 1195, 1187, 1179, 1170, 1162,
|
||||
1154, 1146, 1137, 1129, 1121, 1112, 1104, 1096, 1087, 1079,
|
||||
1071, 1063, 1054, 1046, 1038, 1029, 1021, 1012, 1004, 996,
|
||||
987, 979, 971, 962, 954, 945, 937, 929, 920, 912,
|
||||
903, 895, 886, 878, 870, 861 };
|
||||
|
||||
static const struct regmap_config lmp91000_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
struct lmp91000_data {
|
||||
struct regmap *regmap;
|
||||
struct device *dev;
|
||||
|
||||
struct iio_trigger *trig;
|
||||
struct iio_cb_buffer *cb_buffer;
|
||||
struct iio_channel *adc_chan;
|
||||
|
||||
struct completion completion;
|
||||
u8 chan_select;
|
||||
|
||||
u32 buffer[4]; /* 64-bit data + 64-bit timestamp */
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec lmp91000_channels[] = {
|
||||
{ /* chemical channel mV */
|
||||
.type = IIO_VOLTAGE,
|
||||
.channel = 0,
|
||||
.address = LMP91000_REG_MODECN_3LEAD,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_OFFSET) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
.scan_index = 0,
|
||||
.scan_type = {
|
||||
.sign = 's',
|
||||
.realbits = 32,
|
||||
.storagebits = 32,
|
||||
},
|
||||
},
|
||||
IIO_CHAN_SOFT_TIMESTAMP(1),
|
||||
{ /* temperature channel mV */
|
||||
.type = IIO_TEMP,
|
||||
.channel = 1,
|
||||
.address = LMP91000_REG_MODECN_TEMP,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
|
||||
.scan_index = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static int lmp91000_read(struct lmp91000_data *data, int channel, int *val)
|
||||
{
|
||||
int state, ret;
|
||||
|
||||
ret = regmap_read(data->regmap, LMP91000_REG_MODECN, &state);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
ret = regmap_write(data->regmap, LMP91000_REG_MODECN, channel);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
/* delay till first temperature reading is complete */
|
||||
if ((state != channel) && (channel == LMP91000_REG_MODECN_TEMP))
|
||||
usleep_range(3000, 4000);
|
||||
|
||||
data->chan_select = channel != LMP91000_REG_MODECN_3LEAD;
|
||||
|
||||
iio_trigger_poll_chained(data->trig);
|
||||
|
||||
ret = wait_for_completion_timeout(&data->completion, HZ);
|
||||
reinit_completion(&data->completion);
|
||||
|
||||
if (!ret)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
*val = data->buffer[data->chan_select];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t lmp91000_buffer_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_poll_func *pf = private;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct lmp91000_data *data = iio_priv(indio_dev);
|
||||
int ret, val;
|
||||
|
||||
memset(data->buffer, 0, sizeof(data->buffer));
|
||||
|
||||
ret = lmp91000_read(data, LMP91000_REG_MODECN_3LEAD, &val);
|
||||
if (!ret) {
|
||||
data->buffer[0] = val;
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
|
||||
iio_get_time_ns(indio_dev));
|
||||
}
|
||||
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int lmp91000_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct lmp91000_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
case IIO_CHAN_INFO_PROCESSED: {
|
||||
int ret = iio_channel_start_all_cb(data->cb_buffer);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = lmp91000_read(data, chan->address, val);
|
||||
|
||||
iio_channel_stop_all_cb(data->cb_buffer);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (mask == IIO_CHAN_INFO_PROCESSED) {
|
||||
int tmp, i;
|
||||
|
||||
ret = iio_convert_raw_to_processed(data->adc_chan,
|
||||
*val, &tmp, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(lmp91000_temp_lut); i++)
|
||||
if (lmp91000_temp_lut[i] < tmp)
|
||||
break;
|
||||
|
||||
*val = (LMP91000_TEMP_BASE + i) * 1000;
|
||||
}
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
return iio_read_channel_offset(data->adc_chan, val, val2);
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
return iio_read_channel_scale(data->adc_chan, val, val2);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct iio_info lmp91000_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = lmp91000_read_raw,
|
||||
};
|
||||
|
||||
static int lmp91000_read_config(struct lmp91000_data *data)
|
||||
{
|
||||
struct device *dev = data->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
unsigned int reg, val;
|
||||
int i, ret;
|
||||
|
||||
ret = of_property_read_u32(np, "ti,tia-gain-ohm", &val);
|
||||
if (ret) {
|
||||
if (of_property_read_bool(np, "ti,external-tia-resistor"))
|
||||
val = 0;
|
||||
else {
|
||||
dev_err(dev, "no ti,tia-gain-ohm defined");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
for (i = 0; i < ARRAY_SIZE(lmp91000_tia_gain); i++) {
|
||||
if (lmp91000_tia_gain[i] == val) {
|
||||
reg = i << LMP91000_REG_TIACN_GAIN_SHIFT;
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev, "invalid ti,tia-gain-ohm %d\n", val);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "ti,rload-ohm", &val);
|
||||
if (ret) {
|
||||
val = 100;
|
||||
dev_info(dev, "no ti,rload-ohm defined, default to %d\n", val);
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
for (i = 0; i < ARRAY_SIZE(lmp91000_rload); i++) {
|
||||
if (lmp91000_rload[i] == val) {
|
||||
reg |= i;
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev, "invalid ti,rload-ohm %d\n", val);
|
||||
return ret;
|
||||
}
|
||||
|
||||
regmap_write(data->regmap, LMP91000_REG_LOCK, 0);
|
||||
regmap_write(data->regmap, LMP91000_REG_TIACN, reg);
|
||||
regmap_write(data->regmap, LMP91000_REG_REFCN, LMP91000_REG_REFCN_EXT_REF
|
||||
| LMP91000_REG_REFCN_50_ZERO);
|
||||
regmap_write(data->regmap, LMP91000_REG_LOCK, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lmp91000_buffer_cb(const void *val, void *private)
|
||||
{
|
||||
struct iio_dev *indio_dev = private;
|
||||
struct lmp91000_data *data = iio_priv(indio_dev);
|
||||
|
||||
data->buffer[data->chan_select] = *((int *)val);
|
||||
complete_all(&data->completion);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_trigger_ops lmp91000_trigger_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
|
||||
static int lmp91000_buffer_preenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct lmp91000_data *data = iio_priv(indio_dev);
|
||||
|
||||
return iio_channel_start_all_cb(data->cb_buffer);
|
||||
}
|
||||
|
||||
static int lmp91000_buffer_predisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct lmp91000_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_channel_stop_all_cb(data->cb_buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops lmp91000_buffer_setup_ops = {
|
||||
.preenable = lmp91000_buffer_preenable,
|
||||
.postenable = iio_triggered_buffer_postenable,
|
||||
.predisable = lmp91000_buffer_predisable,
|
||||
};
|
||||
|
||||
static int lmp91000_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct lmp91000_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
indio_dev->info = &lmp91000_info;
|
||||
indio_dev->channels = lmp91000_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(lmp91000_channels);
|
||||
indio_dev->name = LMP91000_DRV_NAME;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->dev = dev;
|
||||
data->regmap = devm_regmap_init_i2c(client, &lmp91000_regmap_config);
|
||||
if (IS_ERR(data->regmap)) {
|
||||
dev_err(dev, "regmap initialization failed.\n");
|
||||
return PTR_ERR(data->regmap);
|
||||
}
|
||||
|
||||
data->trig = devm_iio_trigger_alloc(data->dev, "%s-mux%d",
|
||||
indio_dev->name, indio_dev->id);
|
||||
if (!data->trig) {
|
||||
dev_err(dev, "cannot allocate iio trigger.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
data->trig->ops = &lmp91000_trigger_ops;
|
||||
data->trig->dev.parent = dev;
|
||||
init_completion(&data->completion);
|
||||
|
||||
ret = lmp91000_read_config(data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = iio_trigger_set_immutable(iio_channel_cb_get_iio_dev(data->cb_buffer),
|
||||
data->trig);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot set immutable trigger.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_trigger_register(data->trig);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot register iio trigger.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
&lmp91000_buffer_handler,
|
||||
&lmp91000_buffer_setup_ops);
|
||||
if (ret)
|
||||
goto error_unreg_trigger;
|
||||
|
||||
data->cb_buffer = iio_channel_get_all_cb(dev, &lmp91000_buffer_cb,
|
||||
indio_dev);
|
||||
|
||||
if (IS_ERR(data->cb_buffer)) {
|
||||
if (PTR_ERR(data->cb_buffer) == -ENODEV)
|
||||
ret = -EPROBE_DEFER;
|
||||
else
|
||||
ret = PTR_ERR(data->cb_buffer);
|
||||
|
||||
goto error_unreg_buffer;
|
||||
}
|
||||
|
||||
data->adc_chan = iio_channel_cb_get_channels(data->cb_buffer);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret)
|
||||
goto error_unreg_cb_buffer;
|
||||
|
||||
return 0;
|
||||
|
||||
error_unreg_cb_buffer:
|
||||
iio_channel_release_all_cb(data->cb_buffer);
|
||||
|
||||
error_unreg_buffer:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
error_unreg_trigger:
|
||||
iio_trigger_unregister(data->trig);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lmp91000_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct lmp91000_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
iio_channel_stop_all_cb(data->cb_buffer);
|
||||
iio_channel_release_all_cb(data->cb_buffer);
|
||||
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
iio_trigger_unregister(data->trig);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id lmp91000_of_match[] = {
|
||||
{ .compatible = "ti,lmp91000", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, lmp91000_of_match);
|
||||
|
||||
static const struct i2c_device_id lmp91000_id[] = {
|
||||
{ "lmp91000", 0 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, lmp91000_id);
|
||||
|
||||
static struct i2c_driver lmp91000_driver = {
|
||||
.driver = {
|
||||
.name = LMP91000_DRV_NAME,
|
||||
.of_match_table = of_match_ptr(lmp91000_of_match),
|
||||
},
|
||||
.probe = lmp91000_probe,
|
||||
.remove = lmp91000_remove,
|
||||
.id_table = lmp91000_id,
|
||||
};
|
||||
module_i2c_driver(lmp91000_driver);
|
||||
|
||||
MODULE_AUTHOR("Matt Ranostay <mranostay@gmail.com>");
|
||||
MODULE_DESCRIPTION("LMP91000 digital potentiostat");
|
||||
MODULE_LICENSE("GPL");
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче