mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 04:31:50 +00:00
Staging/IIO driver patches for 4.21-rc1
Here is the big staging and iio driver pull request for 4.21-rc1. Lots and lots of tiny patches here, nothing major at all. Which is good, tiny cleanups is nice to see. No new huge driver removal or addition, this release cycle, although there are lots of good IIO driver changes, addtions, and movement from staging into the "real" part of the kernel, which is always great. Full details are in the shortlog, and all of these have been in linux-next for a while with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCXCY93w8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+ymJRwCfeV0x/+IMS0UGh9ltHnUfCfR9bcgAmgPWMPnY bauRCLkceTjzODOR4SzH =o6Fs -----END PGP SIGNATURE----- Merge tag 'staging-4.21-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging Pull staging/IIO driver updates from Greg KH: "Here is the big staging and iio driver pull request for 4.21-rc1. Lots and lots of tiny patches here, nothing major at all. Which is good, tiny cleanups is nice to see. No new huge driver removal or addition, this release cycle, although there are lots of good IIO driver changes, addtions, and movement from staging into the "real" part of the kernel, which is always great. Full details are in the shortlog, and all of these have been in linux-next for a while with no reported issues" * tag 'staging-4.21-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (553 commits) staging: mt7621-mmc: Correct spelling mistakes in comments staging: wilc1000: fix missing read_write setting when reading data mt7621-mmc: char * array declaration might be better as static const mt7621-mmc: return statement in void function unnecessary mt7621-mmc: Alignment should match open parenthesis mt7621-mmc: Removed unnecessary blank lines mt7621-mmc: Fix some coding style issues staging: android: ashmem: doc: Fix spelling staging: rtl8188eu: cleanup brace coding style issues staging: rtl8188eu: add spaces around '&' in rtw_mlme_ext.c staging: rtl8188eu: change return type of is_basicrate() to bool staging: rtl8188eu: simplify null array initializations staging: rtl8188eu: change order of declarations to improve readability staging: rtl8188eu: make some arrays static in rtw_mlme_ext.c staging: rtl8188eu: constify some arrays staging: rtl8188eu: convert unsigned char arrays to u8 staging: rtl8188eu: remove redundant declaration in rtw_mlme_ext.c staging: rtl8188eu: remove unused arrays WFD_OUI and WMM_INFO_OUI staging: rtl8188eu: remove unnecessary parentheses in rtw_mlme_ext.c staging: rtl8188eu: remove unnecessary comments in rtw_mlme_ext.c ...
This commit is contained in:
commit
02061181d3
@ -64,7 +64,7 @@ Optional properties for all bus drivers:
|
||||
|
||||
Example for a SPI device node:
|
||||
|
||||
lis302@0 {
|
||||
accelerometer@0 {
|
||||
compatible = "st,lis302dl-spi";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <1000000>;
|
||||
@ -89,7 +89,7 @@ Example for a SPI device node:
|
||||
|
||||
Example for a I2C device node:
|
||||
|
||||
lis331dlh: lis331dlh@18 {
|
||||
lis331dlh: accelerometer@18 {
|
||||
compatible = "st,lis331dlh", "st,lis3lv02d";
|
||||
reg = <0x18>;
|
||||
Vdd-supply = <&lis3_reg>;
|
||||
|
16
Documentation/devicetree/bindings/iio/adc/ad7949.txt
Normal file
16
Documentation/devicetree/bindings/iio/adc/ad7949.txt
Normal file
@ -0,0 +1,16 @@
|
||||
* Analog Devices AD7949/AD7682/AD7689
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be one of
|
||||
* "adi,ad7949"
|
||||
* "adi,ad7682"
|
||||
* "adi,ad7689"
|
||||
- reg: spi chip select number for the device
|
||||
- vref-supply: The regulator supply for ADC reference voltage
|
||||
|
||||
Example:
|
||||
adc@0 {
|
||||
compatible = "adi,ad7949";
|
||||
reg = <0>;
|
||||
vref-supply = <&vdd_supply>;
|
||||
};
|
23
Documentation/devicetree/bindings/iio/adc/adc.txt
Normal file
23
Documentation/devicetree/bindings/iio/adc/adc.txt
Normal file
@ -0,0 +1,23 @@
|
||||
Common ADCs properties
|
||||
|
||||
Optional properties for child nodes:
|
||||
- bipolar : Boolean, if set the channel is used in bipolar mode.
|
||||
- diff-channels : Differential channels muxed for this ADC. The first value
|
||||
specifies the positive input pin, the second value the negative
|
||||
input pin.
|
||||
|
||||
Example:
|
||||
adc@0 {
|
||||
compatible = "some,adc";
|
||||
...
|
||||
channel@0 {
|
||||
bipolar;
|
||||
diff-channels = <0 1>;
|
||||
...
|
||||
};
|
||||
|
||||
channel@1 {
|
||||
diff-channels = <2 3>;
|
||||
...
|
||||
};
|
||||
};
|
75
Documentation/devicetree/bindings/iio/adc/adi,ad7124.txt
Normal file
75
Documentation/devicetree/bindings/iio/adc/adi,ad7124.txt
Normal file
@ -0,0 +1,75 @@
|
||||
Analog Devices AD7124 ADC device driver
|
||||
|
||||
Required properties for the AD7124:
|
||||
- compatible: Must be one of "adi,ad7124-4" or "adi,ad7124-8"
|
||||
- reg: SPI chip select number for the device
|
||||
- spi-max-frequency: Max SPI frequency to use
|
||||
see: Documentation/devicetree/bindings/spi/spi-bus.txt
|
||||
- clocks: phandle to the master clock (mclk)
|
||||
see: Documentation/devicetree/bindings/clock/clock-bindings.txt
|
||||
- clock-names: Must be "mclk".
|
||||
- interrupts: IRQ line for the ADC
|
||||
see: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
|
||||
|
||||
Required properties:
|
||||
* #address-cells: Must be 1.
|
||||
* #size-cells: Must be 0.
|
||||
|
||||
Subnode(s) represent the external channels which are connected to the ADC.
|
||||
Each subnode represents one channel and has the following properties:
|
||||
Required properties:
|
||||
* reg: The channel number. It can have up to 4 channels on ad7124-4
|
||||
and 8 channels on ad7124-8, numbered from 0 to 15.
|
||||
* diff-channels: see: Documentation/devicetree/bindings/iio/adc/adc.txt
|
||||
|
||||
Optional properties:
|
||||
* bipolar: see: Documentation/devicetree/bindings/iio/adc/adc.txt
|
||||
* adi,reference-select: Select the reference source to use when
|
||||
converting on the the specific channel. Valid values are:
|
||||
0: REFIN1(+)/REFIN1(−).
|
||||
1: REFIN2(+)/REFIN2(−).
|
||||
3: AVDD
|
||||
If this field is left empty, internal reference is selected.
|
||||
|
||||
Optional properties:
|
||||
- refin1-supply: refin1 supply can be used as reference for conversion.
|
||||
- refin2-supply: refin2 supply can be used as reference for conversion.
|
||||
- avdd-supply: avdd supply can be used as reference for conversion.
|
||||
|
||||
Example:
|
||||
adc@0 {
|
||||
compatible = "adi,ad7124-4";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <5000000>;
|
||||
interrupts = <25 2>;
|
||||
interrupt-parent = <&gpio>;
|
||||
refin1-supply = <&adc_vref>;
|
||||
clocks = <&ad7124_mclk>;
|
||||
clock-names = "mclk";
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
channel@0 {
|
||||
reg = <0>;
|
||||
diff-channels = <0 1>;
|
||||
adi,reference-select = <0>;
|
||||
};
|
||||
|
||||
channel@1 {
|
||||
reg = <1>;
|
||||
bipolar;
|
||||
diff-channels = <2 3>;
|
||||
adi,reference-select = <0>;
|
||||
};
|
||||
|
||||
channel@2 {
|
||||
reg = <2>;
|
||||
diff-channels = <4 5>;
|
||||
};
|
||||
|
||||
channel@3 {
|
||||
reg = <3>;
|
||||
diff-channels = <6 7>;
|
||||
};
|
||||
};
|
@ -22,6 +22,12 @@ Required properties:
|
||||
- vref-supply: the regulator supply for the ADC reference voltage
|
||||
- #io-channel-cells: must be 1, see ../iio-bindings.txt
|
||||
|
||||
Optional properties:
|
||||
- nvmem-cells: phandle to the temperature_calib eFuse cells
|
||||
- nvmem-cell-names: if present (to enable the temperature sensor
|
||||
calibration) this must contain "temperature_calib"
|
||||
|
||||
|
||||
Example:
|
||||
saradc: adc@8680 {
|
||||
compatible = "amlogic,meson-gxl-saradc", "amlogic,meson-saradc";
|
||||
|
@ -11,7 +11,7 @@ New driver handles the following
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "samsung,exynos-adc-v1"
|
||||
for exynos4412/5250 and s5pv210 controllers.
|
||||
for exynos4412/5250 controllers.
|
||||
Must be "samsung,exynos-adc-v2" for
|
||||
future controllers.
|
||||
Must be "samsung,exynos3250-adc" for
|
||||
@ -28,6 +28,8 @@ Required properties:
|
||||
the ADC in s3c2443 and compatibles
|
||||
Must be "samsung,s3c6410-adc" for
|
||||
the ADC in s3c6410 and compatibles
|
||||
Must be "samsung,s5pv210-adc" for
|
||||
the ADC in s5pv210 and compatibles
|
||||
- reg: List of ADC register address range
|
||||
- The base address and range of ADC register
|
||||
- The base address and range of ADC_PHY register (every
|
||||
|
@ -1,7 +1,14 @@
|
||||
* Texas Instruments' ADC128S052, ADC122S021 and ADC124S021 ADC chip
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "ti,adc128s052", "ti,adc122s021" or "ti,adc124s021"
|
||||
- compatible: Should be one of:
|
||||
- "ti,adc128s052"
|
||||
- "ti,adc122s021"
|
||||
- "ti,adc122s051"
|
||||
- "ti,adc122s101"
|
||||
- "ti,adc124s021"
|
||||
- "ti,adc124s051"
|
||||
- "ti,adc124s101"
|
||||
- reg: spi chip select number for the device
|
||||
- vref-supply: The regulator supply for ADC reference voltage
|
||||
|
||||
|
23
Documentation/devicetree/bindings/iio/dac/ti,dac7311.txt
Normal file
23
Documentation/devicetree/bindings/iio/dac/ti,dac7311.txt
Normal file
@ -0,0 +1,23 @@
|
||||
TI DAC7311 device tree bindings
|
||||
|
||||
Required properties:
|
||||
- compatible: must be set to:
|
||||
* "ti,dac7311"
|
||||
* "ti,dac6311"
|
||||
* "ti,dac5311"
|
||||
- reg: spi chip select number for the device
|
||||
- vref-supply: The regulator supply for ADC reference voltage
|
||||
|
||||
Optional properties:
|
||||
- spi-max-frequency: Max SPI frequency to use
|
||||
|
||||
Example:
|
||||
|
||||
spi_master {
|
||||
dac@0 {
|
||||
compatible = "ti,dac7311";
|
||||
reg = <0>; /* CS0 */
|
||||
spi-max-frequency = <1000000>;
|
||||
vref-supply = <&vdd_supply>;
|
||||
};
|
||||
};
|
@ -13,6 +13,7 @@ Required properties:
|
||||
Optional properties:
|
||||
- st,drdy-int-pin: the pin on the package that will be used to signal
|
||||
"data ready" (valid values: 1 or 2).
|
||||
- st,pullups : enable/disable internal i2c controller pullup resistors.
|
||||
- drive-open-drain: the interrupt/data ready line will be configured
|
||||
as open drain, which is useful if several sensors share the same
|
||||
interrupt line. This is a boolean property.
|
||||
|
18
Documentation/devicetree/bindings/iio/light/vcnl4035.txt
Normal file
18
Documentation/devicetree/bindings/iio/light/vcnl4035.txt
Normal file
@ -0,0 +1,18 @@
|
||||
VISHAY VCNL4035 - Ambient Light and proximity sensor
|
||||
|
||||
Link to datasheet: https://www.vishay.com/docs/84251/vcnl4035x01.pdf
|
||||
|
||||
Required properties:
|
||||
|
||||
-compatible: should be "vishay,vcnl4035"
|
||||
-reg: I2C address of the sensor, should be 0x60
|
||||
-interrupts: interrupt mapping for GPIO IRQ (level active low)
|
||||
|
||||
Example:
|
||||
|
||||
light-sensor@60 {
|
||||
compatible = "vishay,vcnl4035";
|
||||
reg = <0x60>;
|
||||
interrupt-parent = <&gpio4>;
|
||||
interrupts = <11 IRQ_TYPE_LEVEL_LOW>;
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
* PNI RM3100 3-axis magnetometer sensor
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : should be "pni,rm3100"
|
||||
- reg : the I2C address or SPI chip select number of the sensor.
|
||||
|
||||
Optional properties:
|
||||
|
||||
- interrupts: data ready (DRDY) from the chip.
|
||||
The interrupts can be triggered on level high.
|
||||
|
||||
Example:
|
||||
|
||||
rm3100: rm3100@20 {
|
||||
compatible = "pni,rm3100";
|
||||
reg = <0x20>;
|
||||
interrupt-parent = <&gpio0>;
|
||||
interrupts = <4 IRQ_TYPE_LEVEL_HIGH>;
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
* Microchip MCP41010/41050/41100/42010/42050/42100 Digital Potentiometer
|
||||
|
||||
Datasheet publicly available at:
|
||||
http://ww1.microchip.com/downloads/en/devicedoc/11195c.pdf
|
||||
|
||||
The node for this driver must be a child node of a SPI controller, hence
|
||||
all mandatory properties described in
|
||||
|
||||
Documentation/devicetree/bindings/spi/spi-bus.txt
|
||||
|
||||
must be specified.
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be one of the following, depending on the
|
||||
model:
|
||||
"microchip,mcp41010"
|
||||
"microchip,mcp41050"
|
||||
"microchip,mcp41100"
|
||||
"microchip,mcp42010"
|
||||
"microchip,mcp42050"
|
||||
"microchip,mcp42100"
|
||||
|
||||
Example:
|
||||
potentiometer@0 {
|
||||
compatible = "microchip,mcp41010";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <500000>;
|
||||
};
|
31
Documentation/devicetree/bindings/iio/resolver/ad2s90.txt
Normal file
31
Documentation/devicetree/bindings/iio/resolver/ad2s90.txt
Normal file
@ -0,0 +1,31 @@
|
||||
Analog Devices AD2S90 Resolver-to-Digital Converter
|
||||
|
||||
https://www.analog.com/en/products/ad2s90.html
|
||||
|
||||
Required properties:
|
||||
- compatible: should be "adi,ad2s90"
|
||||
- reg: SPI chip select number for the device
|
||||
- spi-max-frequency: set maximum clock frequency, must be 830000
|
||||
- spi-cpol and spi-cpha:
|
||||
Either SPI mode (0,0) or (1,1) must be used, so specify none or both of
|
||||
spi-cpha, spi-cpol.
|
||||
|
||||
See for more details:
|
||||
Documentation/devicetree/bindings/spi/spi-bus.txt
|
||||
|
||||
Note about max frequency:
|
||||
Chip's max frequency, as specified in its datasheet, is 2Mhz. But a 600ns
|
||||
delay is expected between the application of a logic LO to CS and the
|
||||
application of SCLK, as also specified. And since the delay is not
|
||||
implemented in the spi code, to satisfy it, SCLK's period should be at most
|
||||
2 * 600ns, so the max frequency should be 1 / (2 * 6e-7), which gives
|
||||
roughly 830000Hz.
|
||||
|
||||
Example:
|
||||
resolver@0 {
|
||||
compatible = "adi,ad2s90";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <830000>;
|
||||
spi-cpol;
|
||||
spi-cpha;
|
||||
};
|
@ -48,6 +48,7 @@ Accelerometers:
|
||||
- st,lis3l02dq
|
||||
- st,lis2dw12
|
||||
- st,lis3dhh
|
||||
- st,lis3de
|
||||
|
||||
Gyroscopes:
|
||||
- st,l3g4200d-gyro
|
||||
@ -67,6 +68,7 @@ Magnetometers:
|
||||
- st,lsm303dlm-magn
|
||||
- st,lis3mdl-magn
|
||||
- st,lis2mdl
|
||||
- st,lsm9ds1-magn
|
||||
|
||||
Pressure sensors:
|
||||
- st,lps001wp-press
|
||||
|
@ -305,6 +305,7 @@ pixcir PIXCIR MICROELECTRONICS Co., Ltd
|
||||
plathome Plat'Home Co., Ltd.
|
||||
plda PLDA
|
||||
plx Broadcom Corporation (formerly PLX Technology)
|
||||
pni PNI Sensor Corporation
|
||||
portwell Portwell Inc.
|
||||
poslab Poslab Technology Co., Ltd.
|
||||
powervr PowerVR (deprecated, use img)
|
||||
@ -417,6 +418,7 @@ vamrs Vamrs Ltd.
|
||||
variscite Variscite Ltd.
|
||||
via VIA Technologies, Inc.
|
||||
virtio Virtual I/O Device Specification, developed by the OASIS consortium
|
||||
vishay Vishay Intertechnology, Inc
|
||||
vitesse Vitesse Semiconductor Corporation
|
||||
vivante Vivante Corporation
|
||||
vocore VoCore Studio
|
||||
|
38
MAINTAINERS
38
MAINTAINERS
@ -846,6 +846,14 @@ S: Supported
|
||||
F: drivers/iio/dac/ad5758.c
|
||||
F: Documentation/devicetree/bindings/iio/dac/ad5758.txt
|
||||
|
||||
ANALOG DEVICES INC AD7124 DRIVER
|
||||
M: Stefan Popa <stefan.popa@analog.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
W: http://ez.analog.com/community/linux-device-drivers
|
||||
S: Supported
|
||||
F: drivers/iio/adc/ad7124.c
|
||||
F: Documentation/devicetree/bindings/iio/adc/adi,ad7124.txt
|
||||
|
||||
ANALOG DEVICES INC AD9389B DRIVER
|
||||
M: Hans Verkuil <hans.verkuil@cisco.com>
|
||||
L: linux-media@vger.kernel.org
|
||||
@ -6906,6 +6914,14 @@ L: linux-input@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/input/touchscreen/htcpen.c
|
||||
|
||||
HTS221 TEMPERATURE-HUMIDITY IIO DRIVER
|
||||
M: Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
W: http://www.st.com/
|
||||
S: Maintained
|
||||
F: drivers/iio/humidity/hts221*
|
||||
F: Documentation/devicetree/bindings/iio/humidity/hts221.txt
|
||||
|
||||
HUAWEI ETHERNET DRIVER
|
||||
M: Aviad Krawczyk <aviad.krawczyk@huawei.com>
|
||||
L: netdev@vger.kernel.org
|
||||
@ -12055,6 +12071,13 @@ M: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
|
||||
S: Maintained
|
||||
F: drivers/pnp/
|
||||
|
||||
PNI RM3100 IIO DRIVER
|
||||
M: Song Qiang <songqiang1304521@gmail.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/iio/magnetometer/rm3100*
|
||||
F: Documentation/devicetree/bindings/iio/magnetometer/pni,rm3100.txt
|
||||
|
||||
POSIX CLOCKS and TIMERS
|
||||
M: Thomas Gleixner <tglx@linutronix.de>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
@ -12840,7 +12863,8 @@ RENESAS R-CAR GYROADC DRIVER
|
||||
M: Marek Vasut <marek.vasut@gmail.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/iio/adc/rcar_gyro_adc.c
|
||||
F: Documentation/devicetree/bindings/iio/adc/renesas,gyroadc.txt
|
||||
F: drivers/iio/adc/rcar-gyroadc.c
|
||||
|
||||
RENESAS R-CAR I2C DRIVERS
|
||||
M: Wolfram Sang <wsa+renesas@sang-engineering.com>
|
||||
@ -14275,6 +14299,14 @@ M: Jan-Benedict Glaw <jbglaw@lug-owl.de>
|
||||
S: Maintained
|
||||
F: arch/alpha/kernel/srm_env.c
|
||||
|
||||
ST LSM6DSx IMU IIO DRIVER
|
||||
M: Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
W: http://www.st.com/
|
||||
S: Maintained
|
||||
F: drivers/iio/imu/st_lsm6dsx/
|
||||
F: Documentation/devicetree/bindings/iio/imu/st_lsm6dsx.txt
|
||||
|
||||
ST STM32 I2C/SMBUS DRIVER
|
||||
M: Pierre-Yves MORDRET <pierre-yves.mordret@st.com>
|
||||
L: linux-i2c@vger.kernel.org
|
||||
@ -14360,8 +14392,8 @@ S: Odd Fixes
|
||||
F: drivers/staging/vt665?/
|
||||
|
||||
STAGING - WILC1000 WIFI DRIVER
|
||||
M: Aditya Shankar <aditya.shankar@microchip.com>
|
||||
M: Ganesh Krishna <ganesh.krishna@microchip.com>
|
||||
M: Adham Abozaeid <adham.abozaeid@microchip.com>
|
||||
M: Ajay Singh <ajay.kathat@microchip.com>
|
||||
L: linux-wireless@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/staging/wilc1000/
|
||||
|
@ -223,7 +223,7 @@ config IIO_ST_ACCEL_3AXIS
|
||||
Say yes here to build support for STMicroelectronics accelerometers:
|
||||
LSM303DLH, LSM303DLHC, LIS3DH, LSM330D, LSM330DL, LSM330DLC,
|
||||
LIS331DLH, LSM303DL, LSM303DLM, LSM330, LIS2DH12, H3LIS331DL,
|
||||
LNG2DM
|
||||
LNG2DM, LIS3DE
|
||||
|
||||
This driver can also be built as a module. If so, these modules
|
||||
will be created:
|
||||
|
@ -1489,8 +1489,11 @@ static const struct acpi_device_id kx_acpi_match[] = {
|
||||
{"KXCJ1013", KXCJK1013},
|
||||
{"KXCJ1008", KXCJ91008},
|
||||
{"KXCJ9000", KXCJ91008},
|
||||
{"KIOX0009", KXTJ21009},
|
||||
{"KIOX000A", KXCJ91008},
|
||||
{"KIOX010A", KXCJ91008}, /* KXCJ91008 inside the display of a 2-in-1 */
|
||||
{"KXTJ1009", KXTJ21009},
|
||||
{"KXJ2109", KXTJ21009},
|
||||
{"SMO8500", KXCJ91008},
|
||||
{ },
|
||||
};
|
||||
|
@ -56,6 +56,7 @@ enum st_accel_type {
|
||||
#define LNG2DM_ACCEL_DEV_NAME "lng2dm"
|
||||
#define LIS2DW12_ACCEL_DEV_NAME "lis2dw12"
|
||||
#define LIS3DHH_ACCEL_DEV_NAME "lis3dhh"
|
||||
#define LIS3DE_ACCEL_DEV_NAME "lis3de"
|
||||
|
||||
/**
|
||||
* struct st_sensors_platform_data - default accel platform data
|
||||
|
@ -103,6 +103,7 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = {
|
||||
[4] = LSM330DLC_ACCEL_DEV_NAME,
|
||||
[5] = LSM303AGR_ACCEL_DEV_NAME,
|
||||
[6] = LIS2DH12_ACCEL_DEV_NAME,
|
||||
[7] = LIS3DE_ACCEL_DEV_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_accel_12bit_channels,
|
||||
.odr = {
|
||||
|
@ -98,6 +98,10 @@ static const struct of_device_id st_accel_of_match[] = {
|
||||
.compatible = "st,lis2dw12",
|
||||
.data = LIS2DW12_ACCEL_DEV_NAME,
|
||||
},
|
||||
{
|
||||
.compatible = "st,lis3de",
|
||||
.data = LIS3DE_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[] = {
|
||||
{ LIS331DL_ACCEL_DEV_NAME },
|
||||
{ LIS3LV02DL_ACCEL_DEV_NAME },
|
||||
{ LIS2DW12_ACCEL_DEV_NAME },
|
||||
{ LIS3DE_ACCEL_DEV_NAME },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, st_accel_id_table);
|
||||
|
@ -90,6 +90,10 @@ static const struct of_device_id st_accel_of_match[] = {
|
||||
.compatible = "st,lis3dhh",
|
||||
.data = LIS3DHH_ACCEL_DEV_NAME,
|
||||
},
|
||||
{
|
||||
.compatible = "st,lis3de",
|
||||
.data = LIS3DE_ACCEL_DEV_NAME,
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, st_accel_of_match);
|
||||
@ -143,6 +147,7 @@ static const struct spi_device_id st_accel_id_table[] = {
|
||||
{ LIS3LV02DL_ACCEL_DEV_NAME },
|
||||
{ LIS2DW12_ACCEL_DEV_NAME },
|
||||
{ LIS3DHH_ACCEL_DEV_NAME },
|
||||
{ LIS3DE_ACCEL_DEV_NAME },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, st_accel_id_table);
|
||||
|
@ -10,6 +10,17 @@ config AD_SIGMA_DELTA
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
|
||||
config AD7124
|
||||
tristate "Analog Devices AD7124 and similar sigma-delta ADCs driver"
|
||||
depends on SPI_MASTER
|
||||
select AD_SIGMA_DELTA
|
||||
help
|
||||
Say yes here to build support for Analog Devices AD7124-4 and AD7124-8
|
||||
SPI analog to digital converters (ADC).
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called ad7124.
|
||||
|
||||
config AD7266
|
||||
tristate "Analog Devices AD7265/AD7266 ADC driver"
|
||||
depends on SPI_MASTER
|
||||
@ -116,6 +127,16 @@ config AD7923
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called ad7923.
|
||||
|
||||
config AD7949
|
||||
tristate "Analog Devices AD7949 and similar ADCs driver"
|
||||
depends on SPI
|
||||
help
|
||||
Say yes here to build support for Analog Devices
|
||||
AD7949, AD7682, AD7689 8 Channel ADCs.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called ad7949.
|
||||
|
||||
config AD799X
|
||||
tristate "Analog Devices AD799x ADC driver"
|
||||
depends on I2C
|
||||
@ -274,7 +295,7 @@ config EP93XX_ADC
|
||||
|
||||
config EXYNOS_ADC
|
||||
tristate "Exynos ADC driver support"
|
||||
depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST)
|
||||
depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || ARCH_S5PV210 || (OF && COMPILE_TEST)
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Core support for the ADC block found in the Samsung EXYNOS series
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o
|
||||
obj-$(CONFIG_AD7124) += ad7124.o
|
||||
obj-$(CONFIG_AD7266) += ad7266.o
|
||||
obj-$(CONFIG_AD7291) += ad7291.o
|
||||
obj-$(CONFIG_AD7298) += ad7298.o
|
||||
@ -14,6 +15,7 @@ obj-$(CONFIG_AD7766) += ad7766.o
|
||||
obj-$(CONFIG_AD7791) += ad7791.o
|
||||
obj-$(CONFIG_AD7793) += ad7793.o
|
||||
obj-$(CONFIG_AD7887) += ad7887.o
|
||||
obj-$(CONFIG_AD7949) += ad7949.o
|
||||
obj-$(CONFIG_AD799X) += ad799x.o
|
||||
obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o
|
||||
obj-$(CONFIG_AT91_ADC) += at91_adc.o
|
||||
|
684
drivers/iio/adc/ad7124.c
Normal file
684
drivers/iio/adc/ad7124.c
Normal file
@ -0,0 +1,684 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* AD7124 SPI ADC driver
|
||||
*
|
||||
* Copyright 2018 Analog Devices Inc.
|
||||
*/
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/adc/ad_sigma_delta.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
||||
/* AD7124 registers */
|
||||
#define AD7124_COMMS 0x00
|
||||
#define AD7124_STATUS 0x00
|
||||
#define AD7124_ADC_CONTROL 0x01
|
||||
#define AD7124_DATA 0x02
|
||||
#define AD7124_IO_CONTROL_1 0x03
|
||||
#define AD7124_IO_CONTROL_2 0x04
|
||||
#define AD7124_ID 0x05
|
||||
#define AD7124_ERROR 0x06
|
||||
#define AD7124_ERROR_EN 0x07
|
||||
#define AD7124_MCLK_COUNT 0x08
|
||||
#define AD7124_CHANNEL(x) (0x09 + (x))
|
||||
#define AD7124_CONFIG(x) (0x19 + (x))
|
||||
#define AD7124_FILTER(x) (0x21 + (x))
|
||||
#define AD7124_OFFSET(x) (0x29 + (x))
|
||||
#define AD7124_GAIN(x) (0x31 + (x))
|
||||
|
||||
/* AD7124_STATUS */
|
||||
#define AD7124_STATUS_POR_FLAG_MSK BIT(4)
|
||||
|
||||
/* AD7124_ADC_CONTROL */
|
||||
#define AD7124_ADC_CTRL_PWR_MSK GENMASK(7, 6)
|
||||
#define AD7124_ADC_CTRL_PWR(x) FIELD_PREP(AD7124_ADC_CTRL_PWR_MSK, x)
|
||||
#define AD7124_ADC_CTRL_MODE_MSK GENMASK(5, 2)
|
||||
#define AD7124_ADC_CTRL_MODE(x) FIELD_PREP(AD7124_ADC_CTRL_MODE_MSK, x)
|
||||
|
||||
/* AD7124_CHANNEL_X */
|
||||
#define AD7124_CHANNEL_EN_MSK BIT(15)
|
||||
#define AD7124_CHANNEL_EN(x) FIELD_PREP(AD7124_CHANNEL_EN_MSK, x)
|
||||
#define AD7124_CHANNEL_SETUP_MSK GENMASK(14, 12)
|
||||
#define AD7124_CHANNEL_SETUP(x) FIELD_PREP(AD7124_CHANNEL_SETUP_MSK, x)
|
||||
#define AD7124_CHANNEL_AINP_MSK GENMASK(9, 5)
|
||||
#define AD7124_CHANNEL_AINP(x) FIELD_PREP(AD7124_CHANNEL_AINP_MSK, x)
|
||||
#define AD7124_CHANNEL_AINM_MSK GENMASK(4, 0)
|
||||
#define AD7124_CHANNEL_AINM(x) FIELD_PREP(AD7124_CHANNEL_AINM_MSK, x)
|
||||
|
||||
/* AD7124_CONFIG_X */
|
||||
#define AD7124_CONFIG_BIPOLAR_MSK BIT(11)
|
||||
#define AD7124_CONFIG_BIPOLAR(x) FIELD_PREP(AD7124_CONFIG_BIPOLAR_MSK, x)
|
||||
#define AD7124_CONFIG_REF_SEL_MSK GENMASK(4, 3)
|
||||
#define AD7124_CONFIG_REF_SEL(x) FIELD_PREP(AD7124_CONFIG_REF_SEL_MSK, x)
|
||||
#define AD7124_CONFIG_PGA_MSK GENMASK(2, 0)
|
||||
#define AD7124_CONFIG_PGA(x) FIELD_PREP(AD7124_CONFIG_PGA_MSK, x)
|
||||
|
||||
/* AD7124_FILTER_X */
|
||||
#define AD7124_FILTER_FS_MSK GENMASK(10, 0)
|
||||
#define AD7124_FILTER_FS(x) FIELD_PREP(AD7124_FILTER_FS_MSK, x)
|
||||
|
||||
enum ad7124_ids {
|
||||
ID_AD7124_4,
|
||||
ID_AD7124_8,
|
||||
};
|
||||
|
||||
enum ad7124_ref_sel {
|
||||
AD7124_REFIN1,
|
||||
AD7124_REFIN2,
|
||||
AD7124_INT_REF,
|
||||
AD7124_AVDD_REF,
|
||||
};
|
||||
|
||||
enum ad7124_power_mode {
|
||||
AD7124_LOW_POWER,
|
||||
AD7124_MID_POWER,
|
||||
AD7124_FULL_POWER,
|
||||
};
|
||||
|
||||
static const unsigned int ad7124_gain[8] = {
|
||||
1, 2, 4, 8, 16, 32, 64, 128
|
||||
};
|
||||
|
||||
static const int ad7124_master_clk_freq_hz[3] = {
|
||||
[AD7124_LOW_POWER] = 76800,
|
||||
[AD7124_MID_POWER] = 153600,
|
||||
[AD7124_FULL_POWER] = 614400,
|
||||
};
|
||||
|
||||
static const char * const ad7124_ref_names[] = {
|
||||
[AD7124_REFIN1] = "refin1",
|
||||
[AD7124_REFIN2] = "refin2",
|
||||
[AD7124_INT_REF] = "int",
|
||||
[AD7124_AVDD_REF] = "avdd",
|
||||
};
|
||||
|
||||
struct ad7124_chip_info {
|
||||
unsigned int num_inputs;
|
||||
};
|
||||
|
||||
struct ad7124_channel_config {
|
||||
enum ad7124_ref_sel refsel;
|
||||
bool bipolar;
|
||||
unsigned int ain;
|
||||
unsigned int vref_mv;
|
||||
unsigned int pga_bits;
|
||||
unsigned int odr;
|
||||
};
|
||||
|
||||
struct ad7124_state {
|
||||
const struct ad7124_chip_info *chip_info;
|
||||
struct ad_sigma_delta sd;
|
||||
struct ad7124_channel_config channel_config[4];
|
||||
struct regulator *vref[4];
|
||||
struct clk *mclk;
|
||||
unsigned int adc_control;
|
||||
unsigned int num_channels;
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec ad7124_channel_template = {
|
||||
.type = IIO_VOLTAGE,
|
||||
.indexed = 1,
|
||||
.differential = 1,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_OFFSET) |
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ),
|
||||
.scan_type = {
|
||||
.sign = 'u',
|
||||
.realbits = 24,
|
||||
.storagebits = 32,
|
||||
.shift = 8,
|
||||
.endianness = IIO_BE,
|
||||
},
|
||||
};
|
||||
|
||||
static struct ad7124_chip_info ad7124_chip_info_tbl[] = {
|
||||
[ID_AD7124_4] = {
|
||||
.num_inputs = 8,
|
||||
},
|
||||
[ID_AD7124_8] = {
|
||||
.num_inputs = 16,
|
||||
},
|
||||
};
|
||||
|
||||
static int ad7124_find_closest_match(const int *array,
|
||||
unsigned int size, int val)
|
||||
{
|
||||
int i, idx;
|
||||
unsigned int diff_new, diff_old;
|
||||
|
||||
diff_old = U32_MAX;
|
||||
idx = 0;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
diff_new = abs(val - array[i]);
|
||||
if (diff_new < diff_old) {
|
||||
diff_old = diff_new;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
static int ad7124_spi_write_mask(struct ad7124_state *st,
|
||||
unsigned int addr,
|
||||
unsigned long mask,
|
||||
unsigned int val,
|
||||
unsigned int bytes)
|
||||
{
|
||||
unsigned int readval;
|
||||
int ret;
|
||||
|
||||
ret = ad_sd_read_reg(&st->sd, addr, bytes, &readval);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
readval &= ~mask;
|
||||
readval |= val;
|
||||
|
||||
return ad_sd_write_reg(&st->sd, addr, bytes, readval);
|
||||
}
|
||||
|
||||
static int ad7124_set_mode(struct ad_sigma_delta *sd,
|
||||
enum ad_sigma_delta_mode mode)
|
||||
{
|
||||
struct ad7124_state *st = container_of(sd, struct ad7124_state, sd);
|
||||
|
||||
st->adc_control &= ~AD7124_ADC_CTRL_MODE_MSK;
|
||||
st->adc_control |= AD7124_ADC_CTRL_MODE(mode);
|
||||
|
||||
return ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, st->adc_control);
|
||||
}
|
||||
|
||||
static int ad7124_set_channel(struct ad_sigma_delta *sd, unsigned int channel)
|
||||
{
|
||||
struct ad7124_state *st = container_of(sd, struct ad7124_state, sd);
|
||||
unsigned int val;
|
||||
|
||||
val = st->channel_config[channel].ain | AD7124_CHANNEL_EN(1) |
|
||||
AD7124_CHANNEL_SETUP(channel);
|
||||
|
||||
return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(channel), 2, val);
|
||||
}
|
||||
|
||||
static const struct ad_sigma_delta_info ad7124_sigma_delta_info = {
|
||||
.set_channel = ad7124_set_channel,
|
||||
.set_mode = ad7124_set_mode,
|
||||
.has_registers = true,
|
||||
.addr_shift = 0,
|
||||
.read_mask = BIT(6),
|
||||
.data_reg = AD7124_DATA,
|
||||
};
|
||||
|
||||
static int ad7124_set_channel_odr(struct ad7124_state *st,
|
||||
unsigned int channel,
|
||||
unsigned int odr)
|
||||
{
|
||||
unsigned int fclk, odr_sel_bits;
|
||||
int ret;
|
||||
|
||||
fclk = clk_get_rate(st->mclk);
|
||||
/*
|
||||
* FS[10:0] = fCLK / (fADC x 32) where:
|
||||
* fADC is the output data rate
|
||||
* fCLK is the master clock frequency
|
||||
* FS[10:0] are the bits in the filter register
|
||||
* FS[10:0] can have a value from 1 to 2047
|
||||
*/
|
||||
odr_sel_bits = DIV_ROUND_CLOSEST(fclk, odr * 32);
|
||||
if (odr_sel_bits < 1)
|
||||
odr_sel_bits = 1;
|
||||
else if (odr_sel_bits > 2047)
|
||||
odr_sel_bits = 2047;
|
||||
|
||||
ret = ad7124_spi_write_mask(st, AD7124_FILTER(channel),
|
||||
AD7124_FILTER_FS_MSK,
|
||||
AD7124_FILTER_FS(odr_sel_bits), 3);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/* fADC = fCLK / (FS[10:0] x 32) */
|
||||
st->channel_config[channel].odr =
|
||||
DIV_ROUND_CLOSEST(fclk, odr_sel_bits * 32);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad7124_set_channel_gain(struct ad7124_state *st,
|
||||
unsigned int channel,
|
||||
unsigned int gain)
|
||||
{
|
||||
unsigned int res;
|
||||
int ret;
|
||||
|
||||
res = ad7124_find_closest_match(ad7124_gain,
|
||||
ARRAY_SIZE(ad7124_gain), gain);
|
||||
ret = ad7124_spi_write_mask(st, AD7124_CONFIG(channel),
|
||||
AD7124_CONFIG_PGA_MSK,
|
||||
AD7124_CONFIG_PGA(res), 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
st->channel_config[channel].pga_bits = res;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad7124_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long info)
|
||||
{
|
||||
struct ad7124_state *st = iio_priv(indio_dev);
|
||||
int idx, ret;
|
||||
|
||||
switch (info) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = ad_sigma_delta_single_conversion(indio_dev, chan, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* After the conversion is performed, disable the channel */
|
||||
ret = ad_sd_write_reg(&st->sd,
|
||||
AD7124_CHANNEL(chan->address), 2,
|
||||
st->channel_config[chan->address].ain |
|
||||
AD7124_CHANNEL_EN(0));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
idx = st->channel_config[chan->address].pga_bits;
|
||||
*val = st->channel_config[chan->address].vref_mv;
|
||||
if (st->channel_config[chan->address].bipolar)
|
||||
*val2 = chan->scan_type.realbits - 1 + idx;
|
||||
else
|
||||
*val2 = chan->scan_type.realbits + idx;
|
||||
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
if (st->channel_config[chan->address].bipolar)
|
||||
*val = -(1 << (chan->scan_type.realbits - 1));
|
||||
else
|
||||
*val = 0;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*val = st->channel_config[chan->address].odr;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ad7124_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long info)
|
||||
{
|
||||
struct ad7124_state *st = iio_priv(indio_dev);
|
||||
unsigned int res, gain, full_scale, vref;
|
||||
|
||||
switch (info) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
if (val2 != 0)
|
||||
return -EINVAL;
|
||||
|
||||
return ad7124_set_channel_odr(st, chan->address, val);
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
if (val != 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (st->channel_config[chan->address].bipolar)
|
||||
full_scale = 1 << (chan->scan_type.realbits - 1);
|
||||
else
|
||||
full_scale = 1 << chan->scan_type.realbits;
|
||||
|
||||
vref = st->channel_config[chan->address].vref_mv * 1000000LL;
|
||||
res = DIV_ROUND_CLOSEST(vref, full_scale);
|
||||
gain = DIV_ROUND_CLOSEST(res, val2);
|
||||
|
||||
return ad7124_set_channel_gain(st, chan->address, gain);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static IIO_CONST_ATTR(in_voltage_scale_available,
|
||||
"0.000001164 0.000002328 0.000004656 0.000009313 0.000018626 0.000037252 0.000074505 0.000149011 0.000298023");
|
||||
|
||||
static struct attribute *ad7124_attributes[] = {
|
||||
&iio_const_attr_in_voltage_scale_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group ad7124_attrs_group = {
|
||||
.attrs = ad7124_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_info ad7124_info = {
|
||||
.read_raw = ad7124_read_raw,
|
||||
.write_raw = ad7124_write_raw,
|
||||
.validate_trigger = ad_sd_validate_trigger,
|
||||
.attrs = &ad7124_attrs_group,
|
||||
};
|
||||
|
||||
static int ad7124_soft_reset(struct ad7124_state *st)
|
||||
{
|
||||
unsigned int readval, timeout;
|
||||
int ret;
|
||||
|
||||
ret = ad_sd_reset(&st->sd, 64);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
timeout = 100;
|
||||
do {
|
||||
ret = ad_sd_read_reg(&st->sd, AD7124_STATUS, 1, &readval);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!(readval & AD7124_STATUS_POR_FLAG_MSK))
|
||||
return 0;
|
||||
|
||||
/* The AD7124 requires typically 2ms to power up and settle */
|
||||
usleep_range(100, 2000);
|
||||
} while (--timeout);
|
||||
|
||||
dev_err(&st->sd.spi->dev, "Soft reset failed\n");
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int ad7124_init_channel_vref(struct ad7124_state *st,
|
||||
unsigned int channel_number)
|
||||
{
|
||||
unsigned int refsel = st->channel_config[channel_number].refsel;
|
||||
|
||||
switch (refsel) {
|
||||
case AD7124_REFIN1:
|
||||
case AD7124_REFIN2:
|
||||
case AD7124_AVDD_REF:
|
||||
if (IS_ERR(st->vref[refsel])) {
|
||||
dev_err(&st->sd.spi->dev,
|
||||
"Error, trying to use external voltage reference without a %s regulator.\n",
|
||||
ad7124_ref_names[refsel]);
|
||||
return PTR_ERR(st->vref[refsel]);
|
||||
}
|
||||
st->channel_config[channel_number].vref_mv =
|
||||
regulator_get_voltage(st->vref[refsel]);
|
||||
/* Conversion from uV to mV */
|
||||
st->channel_config[channel_number].vref_mv /= 1000;
|
||||
break;
|
||||
case AD7124_INT_REF:
|
||||
st->channel_config[channel_number].vref_mv = 2500;
|
||||
break;
|
||||
default:
|
||||
dev_err(&st->sd.spi->dev, "Invalid reference %d\n", refsel);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad7124_of_parse_channel_config(struct iio_dev *indio_dev,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct ad7124_state *st = iio_priv(indio_dev);
|
||||
struct device_node *child;
|
||||
struct iio_chan_spec *chan;
|
||||
unsigned int ain[2], channel = 0, tmp;
|
||||
int ret;
|
||||
|
||||
st->num_channels = of_get_available_child_count(np);
|
||||
if (!st->num_channels) {
|
||||
dev_err(indio_dev->dev.parent, "no channel children\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
chan = devm_kcalloc(indio_dev->dev.parent, st->num_channels,
|
||||
sizeof(*chan), GFP_KERNEL);
|
||||
if (!chan)
|
||||
return -ENOMEM;
|
||||
|
||||
indio_dev->channels = chan;
|
||||
indio_dev->num_channels = st->num_channels;
|
||||
|
||||
for_each_available_child_of_node(np, child) {
|
||||
ret = of_property_read_u32(child, "reg", &channel);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = of_property_read_u32_array(child, "diff-channels",
|
||||
ain, 2);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
if (ain[0] >= st->chip_info->num_inputs ||
|
||||
ain[1] >= st->chip_info->num_inputs) {
|
||||
dev_err(indio_dev->dev.parent,
|
||||
"Input pin number out of range.\n");
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
st->channel_config[channel].ain = AD7124_CHANNEL_AINP(ain[0]) |
|
||||
AD7124_CHANNEL_AINM(ain[1]);
|
||||
st->channel_config[channel].bipolar =
|
||||
of_property_read_bool(child, "bipolar");
|
||||
|
||||
ret = of_property_read_u32(child, "adi,reference-select", &tmp);
|
||||
if (ret)
|
||||
st->channel_config[channel].refsel = AD7124_INT_REF;
|
||||
else
|
||||
st->channel_config[channel].refsel = tmp;
|
||||
|
||||
*chan = ad7124_channel_template;
|
||||
chan->address = channel;
|
||||
chan->scan_index = channel;
|
||||
chan->channel = ain[0];
|
||||
chan->channel2 = ain[1];
|
||||
|
||||
chan++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
of_node_put(child);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7124_setup(struct ad7124_state *st)
|
||||
{
|
||||
unsigned int val, fclk, power_mode;
|
||||
int i, ret;
|
||||
|
||||
fclk = clk_get_rate(st->mclk);
|
||||
if (!fclk)
|
||||
return -EINVAL;
|
||||
|
||||
/* The power mode changes the master clock frequency */
|
||||
power_mode = ad7124_find_closest_match(ad7124_master_clk_freq_hz,
|
||||
ARRAY_SIZE(ad7124_master_clk_freq_hz),
|
||||
fclk);
|
||||
if (fclk != ad7124_master_clk_freq_hz[power_mode]) {
|
||||
ret = clk_set_rate(st->mclk, fclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set the power mode */
|
||||
st->adc_control &= ~AD7124_ADC_CTRL_PWR_MSK;
|
||||
st->adc_control |= AD7124_ADC_CTRL_PWR(power_mode);
|
||||
ret = ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, st->adc_control);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < st->num_channels; i++) {
|
||||
val = st->channel_config[i].ain | AD7124_CHANNEL_SETUP(i);
|
||||
ret = ad_sd_write_reg(&st->sd, AD7124_CHANNEL(i), 2, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = ad7124_init_channel_vref(st, i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
val = AD7124_CONFIG_BIPOLAR(st->channel_config[i].bipolar) |
|
||||
AD7124_CONFIG_REF_SEL(st->channel_config[i].refsel);
|
||||
ret = ad_sd_write_reg(&st->sd, AD7124_CONFIG(i), 2, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/*
|
||||
* 9.38 SPS is the minimum output data rate supported
|
||||
* regardless of the selected power mode. Round it up to 10 and
|
||||
* set all the enabled channels to this default value.
|
||||
*/
|
||||
ret = ad7124_set_channel_odr(st, i, 10);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7124_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct spi_device_id *id;
|
||||
struct ad7124_state *st;
|
||||
struct iio_dev *indio_dev;
|
||||
int i, ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
st = iio_priv(indio_dev);
|
||||
|
||||
id = spi_get_device_id(spi);
|
||||
st->chip_info = &ad7124_chip_info_tbl[id->driver_data];
|
||||
|
||||
ad_sd_init(&st->sd, indio_dev, spi, &ad7124_sigma_delta_info);
|
||||
|
||||
spi_set_drvdata(spi, indio_dev);
|
||||
|
||||
indio_dev->dev.parent = &spi->dev;
|
||||
indio_dev->name = spi_get_device_id(spi)->name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->info = &ad7124_info;
|
||||
|
||||
ret = ad7124_of_parse_channel_config(indio_dev, spi->dev.of_node);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(st->vref); i++) {
|
||||
if (i == AD7124_INT_REF)
|
||||
continue;
|
||||
|
||||
st->vref[i] = devm_regulator_get_optional(&spi->dev,
|
||||
ad7124_ref_names[i]);
|
||||
if (PTR_ERR(st->vref[i]) == -ENODEV)
|
||||
continue;
|
||||
else if (IS_ERR(st->vref[i]))
|
||||
return PTR_ERR(st->vref[i]);
|
||||
|
||||
ret = regulator_enable(st->vref[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
st->mclk = devm_clk_get(&spi->dev, "mclk");
|
||||
if (IS_ERR(st->mclk)) {
|
||||
ret = PTR_ERR(st->mclk);
|
||||
goto error_regulator_disable;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(st->mclk);
|
||||
if (ret < 0)
|
||||
goto error_regulator_disable;
|
||||
|
||||
ret = ad7124_soft_reset(st);
|
||||
if (ret < 0)
|
||||
goto error_clk_disable_unprepare;
|
||||
|
||||
ret = ad7124_setup(st);
|
||||
if (ret < 0)
|
||||
goto error_clk_disable_unprepare;
|
||||
|
||||
ret = ad_sd_setup_buffer_and_trigger(indio_dev);
|
||||
if (ret < 0)
|
||||
goto error_clk_disable_unprepare;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&spi->dev, "Failed to register iio device\n");
|
||||
goto error_remove_trigger;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error_remove_trigger:
|
||||
ad_sd_cleanup_buffer_and_trigger(indio_dev);
|
||||
error_clk_disable_unprepare:
|
||||
clk_disable_unprepare(st->mclk);
|
||||
error_regulator_disable:
|
||||
for (i = ARRAY_SIZE(st->vref) - 1; i >= 0; i--) {
|
||||
if (!IS_ERR_OR_NULL(st->vref[i]))
|
||||
regulator_disable(st->vref[i]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7124_remove(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev = spi_get_drvdata(spi);
|
||||
struct ad7124_state *st = iio_priv(indio_dev);
|
||||
int i;
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
ad_sd_cleanup_buffer_and_trigger(indio_dev);
|
||||
clk_disable_unprepare(st->mclk);
|
||||
|
||||
for (i = ARRAY_SIZE(st->vref) - 1; i >= 0; i--) {
|
||||
if (!IS_ERR_OR_NULL(st->vref[i]))
|
||||
regulator_disable(st->vref[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id ad7124_id_table[] = {
|
||||
{ "ad7124-4", ID_AD7124_4 },
|
||||
{ "ad7124-8", ID_AD7124_8 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ad7124_id_table);
|
||||
|
||||
static const struct of_device_id ad7124_of_match[] = {
|
||||
{ .compatible = "adi,ad7124-4" },
|
||||
{ .compatible = "adi,ad7124-8" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ad7124_of_match);
|
||||
|
||||
static struct spi_driver ad71124_driver = {
|
||||
.driver = {
|
||||
.name = "ad7124",
|
||||
.of_match_table = ad7124_of_match,
|
||||
},
|
||||
.probe = ad7124_probe,
|
||||
.remove = ad7124_remove,
|
||||
.id_table = ad7124_id_table,
|
||||
};
|
||||
module_spi_driver(ad71124_driver);
|
||||
|
||||
MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices AD7124 SPI driver");
|
||||
MODULE_LICENSE("GPL");
|
347
drivers/iio/adc/ad7949.c
Normal file
347
drivers/iio/adc/ad7949.c
Normal file
@ -0,0 +1,347 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* ad7949.c - Analog Devices ADC driver 14/16 bits 4/8 channels
|
||||
*
|
||||
* Copyright (C) 2018 CMC NV
|
||||
*
|
||||
* http://www.analog.com/media/en/technical-documentation/data-sheets/AD7949.pdf
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define AD7949_MASK_CHANNEL_SEL GENMASK(9, 7)
|
||||
#define AD7949_MASK_TOTAL GENMASK(13, 0)
|
||||
#define AD7949_OFFSET_CHANNEL_SEL 7
|
||||
#define AD7949_CFG_READ_BACK 0x1
|
||||
#define AD7949_CFG_REG_SIZE_BITS 14
|
||||
|
||||
enum {
|
||||
ID_AD7949 = 0,
|
||||
ID_AD7682,
|
||||
ID_AD7689,
|
||||
};
|
||||
|
||||
struct ad7949_adc_spec {
|
||||
u8 num_channels;
|
||||
u8 resolution;
|
||||
};
|
||||
|
||||
static const struct ad7949_adc_spec ad7949_adc_spec[] = {
|
||||
[ID_AD7949] = { .num_channels = 8, .resolution = 14 },
|
||||
[ID_AD7682] = { .num_channels = 4, .resolution = 16 },
|
||||
[ID_AD7689] = { .num_channels = 8, .resolution = 16 },
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ad7949_adc_chip - AD ADC chip
|
||||
* @lock: protects write sequences
|
||||
* @vref: regulator generating Vref
|
||||
* @iio_dev: reference to iio structure
|
||||
* @spi: reference to spi structure
|
||||
* @resolution: resolution of the chip
|
||||
* @cfg: copy of the configuration register
|
||||
* @current_channel: current channel in use
|
||||
* @buffer: buffer to send / receive data to / from device
|
||||
*/
|
||||
struct ad7949_adc_chip {
|
||||
struct mutex lock;
|
||||
struct regulator *vref;
|
||||
struct iio_dev *indio_dev;
|
||||
struct spi_device *spi;
|
||||
u8 resolution;
|
||||
u16 cfg;
|
||||
unsigned int current_channel;
|
||||
u32 buffer ____cacheline_aligned;
|
||||
};
|
||||
|
||||
static bool ad7949_spi_cfg_is_read_back(struct ad7949_adc_chip *ad7949_adc)
|
||||
{
|
||||
if (!(ad7949_adc->cfg & AD7949_CFG_READ_BACK))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int ad7949_spi_bits_per_word(struct ad7949_adc_chip *ad7949_adc)
|
||||
{
|
||||
int ret = ad7949_adc->resolution;
|
||||
|
||||
if (ad7949_spi_cfg_is_read_back(ad7949_adc))
|
||||
ret += AD7949_CFG_REG_SIZE_BITS;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7949_spi_write_cfg(struct ad7949_adc_chip *ad7949_adc, u16 val,
|
||||
u16 mask)
|
||||
{
|
||||
int ret;
|
||||
int bits_per_word = ad7949_spi_bits_per_word(ad7949_adc);
|
||||
int shift = bits_per_word - AD7949_CFG_REG_SIZE_BITS;
|
||||
struct spi_message msg;
|
||||
struct spi_transfer tx[] = {
|
||||
{
|
||||
.tx_buf = &ad7949_adc->buffer,
|
||||
.len = 4,
|
||||
.bits_per_word = bits_per_word,
|
||||
},
|
||||
};
|
||||
|
||||
ad7949_adc->cfg = (val & mask) | (ad7949_adc->cfg & ~mask);
|
||||
ad7949_adc->buffer = ad7949_adc->cfg << shift;
|
||||
spi_message_init_with_transfers(&msg, tx, 1);
|
||||
ret = spi_sync(ad7949_adc->spi, &msg);
|
||||
|
||||
/*
|
||||
* This delay is to avoid a new request before the required time to
|
||||
* send a new command to the device
|
||||
*/
|
||||
udelay(2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7949_spi_read_channel(struct ad7949_adc_chip *ad7949_adc, int *val,
|
||||
unsigned int channel)
|
||||
{
|
||||
int ret;
|
||||
int bits_per_word = ad7949_spi_bits_per_word(ad7949_adc);
|
||||
int mask = GENMASK(ad7949_adc->resolution, 0);
|
||||
struct spi_message msg;
|
||||
struct spi_transfer tx[] = {
|
||||
{
|
||||
.rx_buf = &ad7949_adc->buffer,
|
||||
.len = 4,
|
||||
.bits_per_word = bits_per_word,
|
||||
},
|
||||
};
|
||||
|
||||
ret = ad7949_spi_write_cfg(ad7949_adc,
|
||||
channel << AD7949_OFFSET_CHANNEL_SEL,
|
||||
AD7949_MASK_CHANNEL_SEL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ad7949_adc->buffer = 0;
|
||||
spi_message_init_with_transfers(&msg, tx, 1);
|
||||
ret = spi_sync(ad7949_adc->spi, &msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* This delay is to avoid a new request before the required time to
|
||||
* send a new command to the device
|
||||
*/
|
||||
udelay(2);
|
||||
|
||||
ad7949_adc->current_channel = channel;
|
||||
|
||||
if (ad7949_spi_cfg_is_read_back(ad7949_adc))
|
||||
*val = (ad7949_adc->buffer >> AD7949_CFG_REG_SIZE_BITS) & mask;
|
||||
else
|
||||
*val = ad7949_adc->buffer & mask;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define AD7949_ADC_CHANNEL(chan) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.channel = (chan), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec ad7949_adc_channels[] = {
|
||||
AD7949_ADC_CHANNEL(0),
|
||||
AD7949_ADC_CHANNEL(1),
|
||||
AD7949_ADC_CHANNEL(2),
|
||||
AD7949_ADC_CHANNEL(3),
|
||||
AD7949_ADC_CHANNEL(4),
|
||||
AD7949_ADC_CHANNEL(5),
|
||||
AD7949_ADC_CHANNEL(6),
|
||||
AD7949_ADC_CHANNEL(7),
|
||||
};
|
||||
|
||||
static int ad7949_spi_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct ad7949_adc_chip *ad7949_adc = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
mutex_lock(&ad7949_adc->lock);
|
||||
ret = ad7949_spi_read_channel(ad7949_adc, val, chan->channel);
|
||||
mutex_unlock(&ad7949_adc->lock);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = regulator_get_voltage(ad7949_adc->vref);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret / 5000;
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ad7949_spi_reg_access(struct iio_dev *indio_dev,
|
||||
unsigned int reg, unsigned int writeval,
|
||||
unsigned int *readval)
|
||||
{
|
||||
struct ad7949_adc_chip *ad7949_adc = iio_priv(indio_dev);
|
||||
int ret = 0;
|
||||
|
||||
if (readval)
|
||||
*readval = ad7949_adc->cfg;
|
||||
else
|
||||
ret = ad7949_spi_write_cfg(ad7949_adc,
|
||||
writeval & AD7949_MASK_TOTAL, AD7949_MASK_TOTAL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info ad7949_spi_info = {
|
||||
.read_raw = ad7949_spi_read_raw,
|
||||
.debugfs_reg_access = ad7949_spi_reg_access,
|
||||
};
|
||||
|
||||
static int ad7949_spi_init(struct ad7949_adc_chip *ad7949_adc)
|
||||
{
|
||||
int ret;
|
||||
int val;
|
||||
|
||||
/* Sequencer disabled, CFG readback disabled, IN0 as default channel */
|
||||
ad7949_adc->current_channel = 0;
|
||||
ret = ad7949_spi_write_cfg(ad7949_adc, 0x3C79, AD7949_MASK_TOTAL);
|
||||
|
||||
/*
|
||||
* Do two dummy conversions to apply the first configuration setting.
|
||||
* Required only after the start up of the device.
|
||||
*/
|
||||
ad7949_spi_read_channel(ad7949_adc, &val, ad7949_adc->current_channel);
|
||||
ad7949_spi_read_channel(ad7949_adc, &val, ad7949_adc->current_channel);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7949_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
const struct ad7949_adc_spec *spec;
|
||||
struct ad7949_adc_chip *ad7949_adc;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*ad7949_adc));
|
||||
if (!indio_dev) {
|
||||
dev_err(dev, "can not allocate iio device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
indio_dev->dev.parent = dev;
|
||||
indio_dev->dev.of_node = dev->of_node;
|
||||
indio_dev->info = &ad7949_spi_info;
|
||||
indio_dev->name = spi_get_device_id(spi)->name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = ad7949_adc_channels;
|
||||
spi_set_drvdata(spi, indio_dev);
|
||||
|
||||
ad7949_adc = iio_priv(indio_dev);
|
||||
ad7949_adc->indio_dev = indio_dev;
|
||||
ad7949_adc->spi = spi;
|
||||
|
||||
spec = &ad7949_adc_spec[spi_get_device_id(spi)->driver_data];
|
||||
indio_dev->num_channels = spec->num_channels;
|
||||
ad7949_adc->resolution = spec->resolution;
|
||||
|
||||
ad7949_adc->vref = devm_regulator_get(dev, "vref");
|
||||
if (IS_ERR(ad7949_adc->vref)) {
|
||||
dev_err(dev, "fail to request regulator\n");
|
||||
return PTR_ERR(ad7949_adc->vref);
|
||||
}
|
||||
|
||||
ret = regulator_enable(ad7949_adc->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "fail to enable regulator\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
mutex_init(&ad7949_adc->lock);
|
||||
|
||||
ret = ad7949_spi_init(ad7949_adc);
|
||||
if (ret) {
|
||||
dev_err(dev, "enable to init this device: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "fail to register iio device: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
mutex_destroy(&ad7949_adc->lock);
|
||||
regulator_disable(ad7949_adc->vref);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7949_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev = spi_get_drvdata(spi);
|
||||
struct ad7949_adc_chip *ad7949_adc = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
mutex_destroy(&ad7949_adc->lock);
|
||||
regulator_disable(ad7949_adc->vref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ad7949_spi_of_id[] = {
|
||||
{ .compatible = "adi,ad7949" },
|
||||
{ .compatible = "adi,ad7682" },
|
||||
{ .compatible = "adi,ad7689" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ad7949_spi_of_id);
|
||||
|
||||
static const struct spi_device_id ad7949_spi_id[] = {
|
||||
{ "ad7949", ID_AD7949 },
|
||||
{ "ad7682", ID_AD7682 },
|
||||
{ "ad7689", ID_AD7689 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ad7949_spi_id);
|
||||
|
||||
static struct spi_driver ad7949_spi_driver = {
|
||||
.driver = {
|
||||
.name = "ad7949",
|
||||
.of_match_table = ad7949_spi_of_id,
|
||||
},
|
||||
.probe = ad7949_spi_probe,
|
||||
.remove = ad7949_spi_remove,
|
||||
.id_table = ad7949_spi_id,
|
||||
};
|
||||
module_spi_driver(ad7949_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Charles-Antoine Couret <charles-antoine.couret@essensium.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices 14/16-bit 8-channel ADC driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -278,6 +278,7 @@ int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev,
|
||||
{
|
||||
struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev);
|
||||
unsigned int sample, raw_sample;
|
||||
unsigned int data_reg;
|
||||
int ret = 0;
|
||||
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
@ -305,7 +306,12 @@ int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev,
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = ad_sd_read_reg(sigma_delta, AD_SD_REG_DATA,
|
||||
if (sigma_delta->info->data_reg != 0)
|
||||
data_reg = sigma_delta->info->data_reg;
|
||||
else
|
||||
data_reg = AD_SD_REG_DATA;
|
||||
|
||||
ret = ad_sd_read_reg(sigma_delta, data_reg,
|
||||
DIV_ROUND_UP(chan->scan_type.realbits + chan->scan_type.shift, 8),
|
||||
&raw_sample);
|
||||
|
||||
@ -392,6 +398,7 @@ static irqreturn_t ad_sd_trigger_handler(int irq, void *p)
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct ad_sigma_delta *sigma_delta = iio_device_get_drvdata(indio_dev);
|
||||
unsigned int reg_size;
|
||||
unsigned int data_reg;
|
||||
uint8_t data[16];
|
||||
int ret;
|
||||
|
||||
@ -401,18 +408,23 @@ static irqreturn_t ad_sd_trigger_handler(int irq, void *p)
|
||||
indio_dev->channels[0].scan_type.shift;
|
||||
reg_size = DIV_ROUND_UP(reg_size, 8);
|
||||
|
||||
if (sigma_delta->info->data_reg != 0)
|
||||
data_reg = sigma_delta->info->data_reg;
|
||||
else
|
||||
data_reg = AD_SD_REG_DATA;
|
||||
|
||||
switch (reg_size) {
|
||||
case 4:
|
||||
case 2:
|
||||
case 1:
|
||||
ret = ad_sd_read_reg_raw(sigma_delta, AD_SD_REG_DATA,
|
||||
reg_size, &data[0]);
|
||||
ret = ad_sd_read_reg_raw(sigma_delta, data_reg, reg_size,
|
||||
&data[0]);
|
||||
break;
|
||||
case 3:
|
||||
/* We store 24 bit samples in a 32 bit word. Keep the upper
|
||||
* byte set to zero. */
|
||||
ret = ad_sd_read_reg_raw(sigma_delta, AD_SD_REG_DATA,
|
||||
reg_size, &data[1]);
|
||||
ret = ad_sd_read_reg_raw(sigma_delta, data_reg, reg_size,
|
||||
&data[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -115,6 +115,7 @@
|
||||
#define MAX_ADC_V2_CHANNELS 10
|
||||
#define MAX_ADC_V1_CHANNELS 8
|
||||
#define MAX_EXYNOS3250_ADC_CHANNELS 2
|
||||
#define MAX_S5PV210_ADC_CHANNELS 10
|
||||
|
||||
/* Bit definitions common for ADC_V1 and ADC_V2 */
|
||||
#define ADC_CON_EN_START (1u << 0)
|
||||
@ -282,6 +283,16 @@ static const struct exynos_adc_data exynos_adc_v1_data = {
|
||||
.start_conv = exynos_adc_v1_start_conv,
|
||||
};
|
||||
|
||||
static const struct exynos_adc_data exynos_adc_s5pv210_data = {
|
||||
.num_channels = MAX_S5PV210_ADC_CHANNELS,
|
||||
.mask = ADC_DATX_MASK, /* 12 bit ADC resolution */
|
||||
|
||||
.init_hw = exynos_adc_v1_init_hw,
|
||||
.exit_hw = exynos_adc_v1_exit_hw,
|
||||
.clear_irq = exynos_adc_v1_clear_irq,
|
||||
.start_conv = exynos_adc_v1_start_conv,
|
||||
};
|
||||
|
||||
static void exynos_adc_s3c2416_start_conv(struct exynos_adc *info,
|
||||
unsigned long addr)
|
||||
{
|
||||
@ -478,6 +489,9 @@ static const struct of_device_id exynos_adc_match[] = {
|
||||
}, {
|
||||
.compatible = "samsung,s3c6410-adc",
|
||||
.data = &exynos_adc_s3c64xx_data,
|
||||
}, {
|
||||
.compatible = "samsung,s5pv210-adc",
|
||||
.data = &exynos_adc_s5pv210_data,
|
||||
}, {
|
||||
.compatible = "samsung,exynos-adc-v1",
|
||||
.data = &exynos_adc_v1_data,
|
||||
|
@ -250,6 +250,7 @@ static int ina2xx_read_raw(struct iio_dev *indio_dev,
|
||||
*val2 = chip->shunt_resistor_uohm;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
}
|
||||
return -EINVAL;
|
||||
|
||||
case IIO_CHAN_INFO_HARDWAREGAIN:
|
||||
switch (chan->address) {
|
||||
@ -262,6 +263,7 @@ static int ina2xx_read_raw(struct iio_dev *indio_dev,
|
||||
*val = chip->range_vbus == 32 ? 1 : 2;
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
|
@ -1,13 +1,10 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* iio/adc/max11100.c
|
||||
* Maxim max11100 ADC Driver with IIO interface
|
||||
*
|
||||
* Copyright (C) 2016-17 Renesas Electronics Corporation
|
||||
* Copyright (C) 2016-17 Jacopo Mondi
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
|
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* iio/adc/max9611.c
|
||||
*
|
||||
@ -5,10 +6,6 @@
|
||||
* 12-bit ADC interface.
|
||||
*
|
||||
* Copyright (C) 2017 Jacopo Mondi
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <linux/io.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/nvmem-consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
@ -165,6 +166,14 @@
|
||||
|
||||
#define MESON_SAR_ADC_MAX_FIFO_SIZE 32
|
||||
#define MESON_SAR_ADC_TIMEOUT 100 /* ms */
|
||||
#define MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL 6
|
||||
#define MESON_SAR_ADC_TEMP_OFFSET 27
|
||||
|
||||
/* temperature sensor calibration information in eFuse */
|
||||
#define MESON_SAR_ADC_EFUSE_BYTES 4
|
||||
#define MESON_SAR_ADC_EFUSE_BYTE3_UPPER_ADC_VAL GENMASK(6, 0)
|
||||
#define MESON_SAR_ADC_EFUSE_BYTE3_IS_CALIBRATED BIT(7)
|
||||
|
||||
/* for use with IIO_VAL_INT_PLUS_MICRO */
|
||||
#define MILLION 1000000
|
||||
|
||||
@ -175,16 +184,25 @@
|
||||
.address = _chan, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_AVERAGE_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_CALIBBIAS) | \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBBIAS) | \
|
||||
BIT(IIO_CHAN_INFO_CALIBSCALE), \
|
||||
.datasheet_name = "SAR_ADC_CH"#_chan, \
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: the hardware supports IIO_TEMP for channel 6 as well which is
|
||||
* currently not supported by this driver.
|
||||
*/
|
||||
#define MESON_SAR_ADC_TEMP_CHAN(_chan) { \
|
||||
.type = IIO_TEMP, \
|
||||
.channel = _chan, \
|
||||
.address = MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_AVERAGE_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBBIAS) | \
|
||||
BIT(IIO_CHAN_INFO_CALIBSCALE), \
|
||||
.datasheet_name = "TEMP_SENSOR", \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec meson_sar_adc_iio_channels[] = {
|
||||
MESON_SAR_ADC_CHAN(0),
|
||||
MESON_SAR_ADC_CHAN(1),
|
||||
@ -197,6 +215,19 @@ static const struct iio_chan_spec meson_sar_adc_iio_channels[] = {
|
||||
IIO_CHAN_SOFT_TIMESTAMP(8),
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec meson_sar_adc_and_temp_iio_channels[] = {
|
||||
MESON_SAR_ADC_CHAN(0),
|
||||
MESON_SAR_ADC_CHAN(1),
|
||||
MESON_SAR_ADC_CHAN(2),
|
||||
MESON_SAR_ADC_CHAN(3),
|
||||
MESON_SAR_ADC_CHAN(4),
|
||||
MESON_SAR_ADC_CHAN(5),
|
||||
MESON_SAR_ADC_CHAN(6),
|
||||
MESON_SAR_ADC_CHAN(7),
|
||||
MESON_SAR_ADC_TEMP_CHAN(8),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(9),
|
||||
};
|
||||
|
||||
enum meson_sar_adc_avg_mode {
|
||||
NO_AVERAGING = 0x0,
|
||||
MEAN_AVERAGING = 0x1,
|
||||
@ -225,6 +256,9 @@ struct meson_sar_adc_param {
|
||||
u32 bandgap_reg;
|
||||
unsigned int resolution;
|
||||
const struct regmap_config *regmap_config;
|
||||
u8 temperature_trimming_bits;
|
||||
unsigned int temperature_multiplier;
|
||||
unsigned int temperature_divider;
|
||||
};
|
||||
|
||||
struct meson_sar_adc_data {
|
||||
@ -246,6 +280,9 @@ struct meson_sar_adc_priv {
|
||||
struct completion done;
|
||||
int calibbias;
|
||||
int calibscale;
|
||||
bool temperature_sensor_calibrated;
|
||||
u8 temperature_sensor_coefficient;
|
||||
u16 temperature_sensor_adc_val;
|
||||
};
|
||||
|
||||
static const struct regmap_config meson_sar_adc_regmap_config_gxbb = {
|
||||
@ -389,9 +426,16 @@ static void meson_sar_adc_enable_channel(struct iio_dev *indio_dev,
|
||||
MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK,
|
||||
regval);
|
||||
|
||||
if (chan->address == 6)
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
|
||||
MESON_SAR_ADC_DELTA_10_TEMP_SEL, 0);
|
||||
if (chan->address == MESON_SAR_ADC_VOLTAGE_AND_TEMP_CHANNEL) {
|
||||
if (chan->type == IIO_TEMP)
|
||||
regval = MESON_SAR_ADC_DELTA_10_TEMP_SEL;
|
||||
else
|
||||
regval = 0;
|
||||
|
||||
regmap_update_bits(priv->regmap,
|
||||
MESON_SAR_ADC_DELTA_10,
|
||||
MESON_SAR_ADC_DELTA_10_TEMP_SEL, regval);
|
||||
}
|
||||
}
|
||||
|
||||
static void meson_sar_adc_set_chan7_mux(struct iio_dev *indio_dev,
|
||||
@ -506,8 +550,12 @@ static int meson_sar_adc_get_sample(struct iio_dev *indio_dev,
|
||||
enum meson_sar_adc_num_samples avg_samples,
|
||||
int *val)
|
||||
{
|
||||
struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (chan->type == IIO_TEMP && !priv->temperature_sensor_calibrated)
|
||||
return -ENOTSUPP;
|
||||
|
||||
ret = meson_sar_adc_lock(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -555,16 +603,30 @@ static int meson_sar_adc_iio_info_read_raw(struct iio_dev *indio_dev,
|
||||
break;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = regulator_get_voltage(priv->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(indio_dev->dev.parent,
|
||||
"failed to get vref voltage: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
if (chan->type == IIO_VOLTAGE) {
|
||||
ret = regulator_get_voltage(priv->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(indio_dev->dev.parent,
|
||||
"failed to get vref voltage: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*val = ret / 1000;
|
||||
*val2 = priv->param->resolution;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
*val = ret / 1000;
|
||||
*val2 = priv->param->resolution;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
} else if (chan->type == IIO_TEMP) {
|
||||
/* SoC specific multiplier and divider */
|
||||
*val = priv->param->temperature_multiplier;
|
||||
*val2 = priv->param->temperature_divider;
|
||||
|
||||
/* celsius to millicelsius */
|
||||
*val *= 1000;
|
||||
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
*val = priv->calibbias;
|
||||
@ -575,6 +637,13 @@ static int meson_sar_adc_iio_info_read_raw(struct iio_dev *indio_dev,
|
||||
*val2 = priv->calibscale % MILLION;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
*val = DIV_ROUND_CLOSEST(MESON_SAR_ADC_TEMP_OFFSET *
|
||||
priv->param->temperature_divider,
|
||||
priv->param->temperature_multiplier);
|
||||
*val -= priv->temperature_sensor_adc_val;
|
||||
return IIO_VAL_INT;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -587,8 +656,11 @@ static int meson_sar_adc_clk_init(struct iio_dev *indio_dev,
|
||||
struct clk_init_data init;
|
||||
const char *clk_parents[1];
|
||||
|
||||
init.name = devm_kasprintf(&indio_dev->dev, GFP_KERNEL, "%pOF#adc_div",
|
||||
indio_dev->dev.of_node);
|
||||
init.name = devm_kasprintf(&indio_dev->dev, GFP_KERNEL, "%s#adc_div",
|
||||
dev_name(indio_dev->dev.parent));
|
||||
if (!init.name)
|
||||
return -ENOMEM;
|
||||
|
||||
init.flags = 0;
|
||||
init.ops = &clk_divider_ops;
|
||||
clk_parents[0] = __clk_get_name(priv->clkin);
|
||||
@ -606,8 +678,11 @@ static int meson_sar_adc_clk_init(struct iio_dev *indio_dev,
|
||||
if (WARN_ON(IS_ERR(priv->adc_div_clk)))
|
||||
return PTR_ERR(priv->adc_div_clk);
|
||||
|
||||
init.name = devm_kasprintf(&indio_dev->dev, GFP_KERNEL, "%pOF#adc_en",
|
||||
indio_dev->dev.of_node);
|
||||
init.name = devm_kasprintf(&indio_dev->dev, GFP_KERNEL, "%s#adc_en",
|
||||
dev_name(indio_dev->dev.parent));
|
||||
if (!init.name)
|
||||
return -ENOMEM;
|
||||
|
||||
init.flags = CLK_SET_RATE_PARENT;
|
||||
init.ops = &clk_gate_ops;
|
||||
clk_parents[0] = __clk_get_name(priv->adc_div_clk);
|
||||
@ -625,6 +700,65 @@ static int meson_sar_adc_clk_init(struct iio_dev *indio_dev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_sar_adc_temp_sensor_init(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
|
||||
u8 *buf, trimming_bits, trimming_mask, upper_adc_val;
|
||||
struct nvmem_cell *temperature_calib;
|
||||
size_t read_len;
|
||||
int ret;
|
||||
|
||||
temperature_calib = devm_nvmem_cell_get(&indio_dev->dev,
|
||||
"temperature_calib");
|
||||
if (IS_ERR(temperature_calib)) {
|
||||
ret = PTR_ERR(temperature_calib);
|
||||
|
||||
/*
|
||||
* leave the temperature sensor disabled if no calibration data
|
||||
* was passed via nvmem-cells.
|
||||
*/
|
||||
if (ret == -ENODEV)
|
||||
return 0;
|
||||
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(indio_dev->dev.parent,
|
||||
"failed to get temperature_calib cell\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
read_len = MESON_SAR_ADC_EFUSE_BYTES;
|
||||
buf = nvmem_cell_read(temperature_calib, &read_len);
|
||||
if (IS_ERR(buf)) {
|
||||
dev_err(indio_dev->dev.parent,
|
||||
"failed to read temperature_calib cell\n");
|
||||
return PTR_ERR(buf);
|
||||
} else if (read_len != MESON_SAR_ADC_EFUSE_BYTES) {
|
||||
kfree(buf);
|
||||
dev_err(indio_dev->dev.parent,
|
||||
"invalid read size of temperature_calib cell\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
trimming_bits = priv->param->temperature_trimming_bits;
|
||||
trimming_mask = BIT(trimming_bits) - 1;
|
||||
|
||||
priv->temperature_sensor_calibrated =
|
||||
buf[3] & MESON_SAR_ADC_EFUSE_BYTE3_IS_CALIBRATED;
|
||||
priv->temperature_sensor_coefficient = buf[2] & trimming_mask;
|
||||
|
||||
upper_adc_val = FIELD_GET(MESON_SAR_ADC_EFUSE_BYTE3_UPPER_ADC_VAL,
|
||||
buf[3]);
|
||||
|
||||
priv->temperature_sensor_adc_val = buf[2];
|
||||
priv->temperature_sensor_adc_val |= upper_adc_val << BITS_PER_BYTE;
|
||||
priv->temperature_sensor_adc_val >>= trimming_bits;
|
||||
|
||||
kfree(buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_sar_adc_init(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct meson_sar_adc_priv *priv = iio_priv(indio_dev);
|
||||
@ -649,10 +783,12 @@ static int meson_sar_adc_init(struct iio_dev *indio_dev)
|
||||
|
||||
meson_sar_adc_stop_sample_engine(indio_dev);
|
||||
|
||||
/* update the channel 6 MUX to select the temperature sensor */
|
||||
/*
|
||||
* disable this bit as seems to be only relevant for Meson6 (based
|
||||
* on the vendor driver), which we don't support at the moment.
|
||||
*/
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
|
||||
MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL,
|
||||
MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL);
|
||||
MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL, 0);
|
||||
|
||||
/* disable all channels by default */
|
||||
regmap_write(priv->regmap, MESON_SAR_ADC_CHAN_LIST, 0x0);
|
||||
@ -709,6 +845,29 @@ static int meson_sar_adc_init(struct iio_dev *indio_dev)
|
||||
regval |= MESON_SAR_ADC_AUX_SW_XP_DRIVE_SW;
|
||||
regmap_write(priv->regmap, MESON_SAR_ADC_AUX_SW, regval);
|
||||
|
||||
if (priv->temperature_sensor_calibrated) {
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
|
||||
MESON_SAR_ADC_DELTA_10_TS_REVE1,
|
||||
MESON_SAR_ADC_DELTA_10_TS_REVE1);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
|
||||
MESON_SAR_ADC_DELTA_10_TS_REVE0,
|
||||
MESON_SAR_ADC_DELTA_10_TS_REVE0);
|
||||
|
||||
/*
|
||||
* set bits [3:0] of the TSC (temperature sensor coefficient)
|
||||
* to get the correct values when reading the temperature.
|
||||
*/
|
||||
regval = FIELD_PREP(MESON_SAR_ADC_DELTA_10_TS_C_MASK,
|
||||
priv->temperature_sensor_coefficient);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
|
||||
MESON_SAR_ADC_DELTA_10_TS_C_MASK, regval);
|
||||
} else {
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
|
||||
MESON_SAR_ADC_DELTA_10_TS_REVE1, 0);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
|
||||
MESON_SAR_ADC_DELTA_10_TS_REVE0, 0);
|
||||
}
|
||||
|
||||
ret = clk_set_parent(priv->adc_sel_clk, priv->clkin);
|
||||
if (ret) {
|
||||
dev_err(indio_dev->dev.parent,
|
||||
@ -894,6 +1053,17 @@ static const struct meson_sar_adc_param meson_sar_adc_meson8_param = {
|
||||
.bandgap_reg = MESON_SAR_ADC_DELTA_10,
|
||||
.regmap_config = &meson_sar_adc_regmap_config_meson8,
|
||||
.resolution = 10,
|
||||
.temperature_trimming_bits = 4,
|
||||
.temperature_multiplier = 18 * 10000,
|
||||
.temperature_divider = 1024 * 10 * 85,
|
||||
};
|
||||
|
||||
static const struct meson_sar_adc_param meson_sar_adc_meson8b_param = {
|
||||
.has_bl30_integration = false,
|
||||
.clock_rate = 1150000,
|
||||
.bandgap_reg = MESON_SAR_ADC_DELTA_10,
|
||||
.regmap_config = &meson_sar_adc_regmap_config_meson8,
|
||||
.resolution = 10,
|
||||
};
|
||||
|
||||
static const struct meson_sar_adc_param meson_sar_adc_gxbb_param = {
|
||||
@ -918,12 +1088,12 @@ static const struct meson_sar_adc_data meson_sar_adc_meson8_data = {
|
||||
};
|
||||
|
||||
static const struct meson_sar_adc_data meson_sar_adc_meson8b_data = {
|
||||
.param = &meson_sar_adc_meson8_param,
|
||||
.param = &meson_sar_adc_meson8b_param,
|
||||
.name = "meson-meson8b-saradc",
|
||||
};
|
||||
|
||||
static const struct meson_sar_adc_data meson_sar_adc_meson8m2_data = {
|
||||
.param = &meson_sar_adc_meson8_param,
|
||||
.param = &meson_sar_adc_meson8b_param,
|
||||
.name = "meson-meson8m2-saradc",
|
||||
};
|
||||
|
||||
@ -1009,9 +1179,6 @@ static int meson_sar_adc_probe(struct platform_device *pdev)
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->info = &meson_sar_adc_iio_info;
|
||||
|
||||
indio_dev->channels = meson_sar_adc_iio_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(meson_sar_adc_iio_channels);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(base))
|
||||
@ -1078,6 +1245,22 @@ static int meson_sar_adc_probe(struct platform_device *pdev)
|
||||
|
||||
priv->calibscale = MILLION;
|
||||
|
||||
if (priv->param->temperature_trimming_bits) {
|
||||
ret = meson_sar_adc_temp_sensor_init(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (priv->temperature_sensor_calibrated) {
|
||||
indio_dev->channels = meson_sar_adc_and_temp_iio_channels;
|
||||
indio_dev->num_channels =
|
||||
ARRAY_SIZE(meson_sar_adc_and_temp_iio_channels);
|
||||
} else {
|
||||
indio_dev->channels = meson_sar_adc_iio_channels;
|
||||
indio_dev->num_channels =
|
||||
ARRAY_SIZE(meson_sar_adc_iio_channels);
|
||||
}
|
||||
|
||||
ret = meson_sar_adc_init(indio_dev);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
@ -423,6 +423,7 @@ struct adc5_channels {
|
||||
enum vadc_scale_fn_type scale_fn_type;
|
||||
};
|
||||
|
||||
/* In these definitions, _pre refers to an index into adc5_prescale_ratios. */
|
||||
#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale) \
|
||||
{ \
|
||||
.datasheet_name = _dname, \
|
||||
@ -443,63 +444,63 @@ struct adc5_channels {
|
||||
_pre, _scale) \
|
||||
|
||||
static const struct adc5_channels adc5_chans_pmic[ADC5_MAX_CHANNEL] = {
|
||||
[ADC5_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 1,
|
||||
[ADC5_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 0,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 1,
|
||||
[ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 3,
|
||||
[ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 3,
|
||||
[ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 1,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 1,
|
||||
[ADC5_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0,
|
||||
SCALE_HW_CALIB_PMIC_THERM)
|
||||
[ADC5_USB_IN_I] = ADC5_CHAN_VOLT("usb_in_i_uv", 1,
|
||||
[ADC5_USB_IN_I] = ADC5_CHAN_VOLT("usb_in_i_uv", 0,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_USB_IN_V_16] = ADC5_CHAN_VOLT("usb_in_v_div_16", 16,
|
||||
[ADC5_USB_IN_V_16] = ADC5_CHAN_VOLT("usb_in_v_div_16", 8,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_CHG_TEMP] = ADC5_CHAN_TEMP("chg_temp", 1,
|
||||
[ADC5_CHG_TEMP] = ADC5_CHAN_TEMP("chg_temp", 0,
|
||||
SCALE_HW_CALIB_PM5_CHG_TEMP)
|
||||
/* Charger prescales SBUx and MID_CHG to fit within 1.8V upper unit */
|
||||
[ADC5_SBUx] = ADC5_CHAN_VOLT("chg_sbux", 3,
|
||||
[ADC5_SBUx] = ADC5_CHAN_VOLT("chg_sbux", 1,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_MID_CHG_DIV6] = ADC5_CHAN_VOLT("chg_mid_chg", 6,
|
||||
[ADC5_MID_CHG_DIV6] = ADC5_CHAN_VOLT("chg_mid_chg", 3,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm", 1,
|
||||
[ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm", 0,
|
||||
SCALE_HW_CALIB_XOTHERM)
|
||||
[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
|
||||
[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 0,
|
||||
SCALE_HW_CALIB_THERM_100K_PULLUP)
|
||||
[ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 1,
|
||||
[ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 0,
|
||||
SCALE_HW_CALIB_THERM_100K_PULLUP)
|
||||
[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
|
||||
[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 0,
|
||||
SCALE_HW_CALIB_THERM_100K_PULLUP)
|
||||
[ADC5_AMUX_THM2] = ADC5_CHAN_TEMP("amux_thm2", 1,
|
||||
[ADC5_AMUX_THM2] = ADC5_CHAN_TEMP("amux_thm2", 0,
|
||||
SCALE_HW_CALIB_PM5_SMB_TEMP)
|
||||
};
|
||||
|
||||
static const struct adc5_channels adc5_chans_rev2[ADC5_MAX_CHANNEL] = {
|
||||
[ADC5_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 1,
|
||||
[ADC5_REF_GND] = ADC5_CHAN_VOLT("ref_gnd", 0,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 1,
|
||||
[ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 3,
|
||||
[ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 3,
|
||||
[ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 1,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_VCOIN] = ADC5_CHAN_VOLT("vcoin", 3,
|
||||
[ADC5_VCOIN] = ADC5_CHAN_VOLT("vcoin", 1,
|
||||
SCALE_HW_CALIB_DEFAULT)
|
||||
[ADC5_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 1,
|
||||
[ADC5_DIE_TEMP] = ADC5_CHAN_TEMP("die_temp", 0,
|
||||
SCALE_HW_CALIB_PMIC_THERM)
|
||||
[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
|
||||
[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 0,
|
||||
SCALE_HW_CALIB_THERM_100K_PULLUP)
|
||||
[ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 1,
|
||||
[ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 0,
|
||||
SCALE_HW_CALIB_THERM_100K_PULLUP)
|
||||
[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
|
||||
[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 0,
|
||||
SCALE_HW_CALIB_THERM_100K_PULLUP)
|
||||
[ADC5_AMUX_THM4_100K_PU] = ADC5_CHAN_TEMP("amux_thm4_100k_pu", 1,
|
||||
[ADC5_AMUX_THM4_100K_PU] = ADC5_CHAN_TEMP("amux_thm4_100k_pu", 0,
|
||||
SCALE_HW_CALIB_THERM_100K_PULLUP)
|
||||
[ADC5_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_100k_pu", 1,
|
||||
[ADC5_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_100k_pu", 0,
|
||||
SCALE_HW_CALIB_THERM_100K_PULLUP)
|
||||
[ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm_100k_pu", 1,
|
||||
[ADC5_XO_THERM_100K_PU] = ADC5_CHAN_TEMP("xo_therm_100k_pu", 0,
|
||||
SCALE_HW_CALIB_THERM_100K_PULLUP)
|
||||
};
|
||||
|
||||
@ -558,6 +559,9 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
|
||||
return ret;
|
||||
}
|
||||
prop->prescale = ret;
|
||||
} else {
|
||||
prop->prescale =
|
||||
adc->data->adc_chans[prop->channel].prescale_index;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(node, "qcom,hw-settle-time", &value);
|
||||
|
@ -1,17 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Renesas R-Car GyroADC driver
|
||||
*
|
||||
* Copyright 2016 Marek Vasut <marek.vasut@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
|
@ -52,6 +52,9 @@
|
||||
/* Timeout (ms) for the trylock of hardware spinlocks */
|
||||
#define SC27XX_ADC_HWLOCK_TIMEOUT 5000
|
||||
|
||||
/* Timeout (ms) for ADC data conversion according to ADC datasheet */
|
||||
#define SC27XX_ADC_RDY_TIMEOUT 100
|
||||
|
||||
/* Maximum ADC channel number */
|
||||
#define SC27XX_ADC_CHANNEL_MAX 32
|
||||
|
||||
@ -223,7 +226,14 @@ static int sc27xx_adc_read(struct sc27xx_adc_data *data, int channel,
|
||||
if (ret)
|
||||
goto disable_adc;
|
||||
|
||||
wait_for_completion(&data->completion);
|
||||
ret = wait_for_completion_timeout(&data->completion,
|
||||
msecs_to_jiffies(SC27XX_ADC_RDY_TIMEOUT));
|
||||
if (!ret) {
|
||||
dev_err(data->dev, "read ADC data timeout\n");
|
||||
ret = -ETIMEDOUT;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
disable_adc:
|
||||
regmap_update_bits(data->regmap, data->base + SC27XX_ADC_CTL,
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
@ -48,15 +49,19 @@
|
||||
#define STM32H7_CKMODE_SHIFT 16
|
||||
#define STM32H7_CKMODE_MASK GENMASK(17, 16)
|
||||
|
||||
#define STM32_ADC_CORE_SLEEP_DELAY_MS 2000
|
||||
|
||||
/**
|
||||
* stm32_adc_common_regs - stm32 common registers, compatible dependent data
|
||||
* @csr: common status register offset
|
||||
* @ccr: common control register offset
|
||||
* @eoc1: adc1 end of conversion flag in @csr
|
||||
* @eoc2: adc2 end of conversion flag in @csr
|
||||
* @eoc3: adc3 end of conversion flag in @csr
|
||||
*/
|
||||
struct stm32_adc_common_regs {
|
||||
u32 csr;
|
||||
u32 ccr;
|
||||
u32 eoc1_msk;
|
||||
u32 eoc2_msk;
|
||||
u32 eoc3_msk;
|
||||
@ -85,6 +90,7 @@ struct stm32_adc_priv_cfg {
|
||||
* @vref: regulator reference
|
||||
* @cfg: compatible configuration data
|
||||
* @common: common data for all ADC instances
|
||||
* @ccr_bak: backup CCR in low power mode
|
||||
*/
|
||||
struct stm32_adc_priv {
|
||||
int irq[STM32_ADC_MAX_ADCS];
|
||||
@ -94,6 +100,7 @@ struct stm32_adc_priv {
|
||||
struct regulator *vref;
|
||||
const struct stm32_adc_priv_cfg *cfg;
|
||||
struct stm32_adc_common common;
|
||||
u32 ccr_bak;
|
||||
};
|
||||
|
||||
static struct stm32_adc_priv *to_stm32_adc_priv(struct stm32_adc_common *com)
|
||||
@ -265,6 +272,7 @@ out:
|
||||
/* STM32F4 common registers definitions */
|
||||
static const struct stm32_adc_common_regs stm32f4_adc_common_regs = {
|
||||
.csr = STM32F4_ADC_CSR,
|
||||
.ccr = STM32F4_ADC_CCR,
|
||||
.eoc1_msk = STM32F4_EOC1,
|
||||
.eoc2_msk = STM32F4_EOC2,
|
||||
.eoc3_msk = STM32F4_EOC3,
|
||||
@ -273,6 +281,7 @@ static const struct stm32_adc_common_regs stm32f4_adc_common_regs = {
|
||||
/* STM32H7 common registers definitions */
|
||||
static const struct stm32_adc_common_regs stm32h7_adc_common_regs = {
|
||||
.csr = STM32H7_ADC_CSR,
|
||||
.ccr = STM32H7_ADC_CCR,
|
||||
.eoc1_msk = STM32H7_EOC_MST,
|
||||
.eoc2_msk = STM32H7_EOC_SLV,
|
||||
};
|
||||
@ -379,6 +388,61 @@ static void stm32_adc_irq_remove(struct platform_device *pdev,
|
||||
}
|
||||
}
|
||||
|
||||
static int stm32_adc_core_hw_start(struct device *dev)
|
||||
{
|
||||
struct stm32_adc_common *common = dev_get_drvdata(dev);
|
||||
struct stm32_adc_priv *priv = to_stm32_adc_priv(common);
|
||||
int ret;
|
||||
|
||||
ret = regulator_enable(priv->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "vref enable failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (priv->bclk) {
|
||||
ret = clk_prepare_enable(priv->bclk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "bus clk enable failed\n");
|
||||
goto err_regulator_disable;
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->aclk) {
|
||||
ret = clk_prepare_enable(priv->aclk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "adc clk enable failed\n");
|
||||
goto err_bclk_disable;
|
||||
}
|
||||
}
|
||||
|
||||
writel_relaxed(priv->ccr_bak, priv->common.base + priv->cfg->regs->ccr);
|
||||
|
||||
return 0;
|
||||
|
||||
err_bclk_disable:
|
||||
if (priv->bclk)
|
||||
clk_disable_unprepare(priv->bclk);
|
||||
err_regulator_disable:
|
||||
regulator_disable(priv->vref);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void stm32_adc_core_hw_stop(struct device *dev)
|
||||
{
|
||||
struct stm32_adc_common *common = dev_get_drvdata(dev);
|
||||
struct stm32_adc_priv *priv = to_stm32_adc_priv(common);
|
||||
|
||||
/* Backup CCR that may be lost (depends on power state to achieve) */
|
||||
priv->ccr_bak = readl_relaxed(priv->common.base + priv->cfg->regs->ccr);
|
||||
if (priv->aclk)
|
||||
clk_disable_unprepare(priv->aclk);
|
||||
if (priv->bclk)
|
||||
clk_disable_unprepare(priv->bclk);
|
||||
regulator_disable(priv->vref);
|
||||
}
|
||||
|
||||
static int stm32_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct stm32_adc_priv *priv;
|
||||
@ -393,6 +457,7 @@ static int stm32_adc_probe(struct platform_device *pdev)
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, &priv->common);
|
||||
|
||||
priv->cfg = (const struct stm32_adc_priv_cfg *)
|
||||
of_match_device(dev->driver->of_match_table, dev)->data;
|
||||
@ -410,67 +475,51 @@ static int stm32_adc_probe(struct platform_device *pdev)
|
||||
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);
|
||||
if (ret == -ENOENT) {
|
||||
priv->aclk = NULL;
|
||||
} else {
|
||||
if (ret != -ENOENT) {
|
||||
dev_err(&pdev->dev, "Can't get 'adc' clock\n");
|
||||
goto err_regulator_disable;
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->aclk) {
|
||||
ret = clk_prepare_enable(priv->aclk);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "adc clk enable failed\n");
|
||||
goto err_regulator_disable;
|
||||
return ret;
|
||||
}
|
||||
priv->aclk = NULL;
|
||||
}
|
||||
|
||||
priv->bclk = devm_clk_get(&pdev->dev, "bus");
|
||||
if (IS_ERR(priv->bclk)) {
|
||||
ret = PTR_ERR(priv->bclk);
|
||||
if (ret == -ENOENT) {
|
||||
priv->bclk = NULL;
|
||||
} else {
|
||||
if (ret != -ENOENT) {
|
||||
dev_err(&pdev->dev, "Can't get 'bus' clock\n");
|
||||
goto err_aclk_disable;
|
||||
return ret;
|
||||
}
|
||||
priv->bclk = NULL;
|
||||
}
|
||||
|
||||
if (priv->bclk) {
|
||||
ret = clk_prepare_enable(priv->bclk);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "adc clk enable failed\n");
|
||||
goto err_aclk_disable;
|
||||
}
|
||||
pm_runtime_get_noresume(dev);
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_set_autosuspend_delay(dev, STM32_ADC_CORE_SLEEP_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
ret = stm32_adc_core_hw_start(dev);
|
||||
if (ret)
|
||||
goto err_pm_stop;
|
||||
|
||||
ret = regulator_get_voltage(priv->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "vref get voltage failed, %d\n", ret);
|
||||
goto err_hw_stop;
|
||||
}
|
||||
priv->common.vref_mv = ret / 1000;
|
||||
dev_dbg(&pdev->dev, "vref+=%dmV\n", priv->common.vref_mv);
|
||||
|
||||
ret = priv->cfg->clk_sel(pdev, priv);
|
||||
if (ret < 0)
|
||||
goto err_bclk_disable;
|
||||
goto err_hw_stop;
|
||||
|
||||
ret = stm32_adc_irq_probe(pdev, priv);
|
||||
if (ret < 0)
|
||||
goto err_bclk_disable;
|
||||
|
||||
platform_set_drvdata(pdev, &priv->common);
|
||||
goto err_hw_stop;
|
||||
|
||||
ret = of_platform_populate(np, NULL, NULL, &pdev->dev);
|
||||
if (ret < 0) {
|
||||
@ -478,21 +527,19 @@ static int stm32_adc_probe(struct platform_device *pdev)
|
||||
goto err_irq_remove;
|
||||
}
|
||||
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_irq_remove:
|
||||
stm32_adc_irq_remove(pdev, priv);
|
||||
|
||||
err_bclk_disable:
|
||||
if (priv->bclk)
|
||||
clk_disable_unprepare(priv->bclk);
|
||||
|
||||
err_aclk_disable:
|
||||
if (priv->aclk)
|
||||
clk_disable_unprepare(priv->aclk);
|
||||
|
||||
err_regulator_disable:
|
||||
regulator_disable(priv->vref);
|
||||
err_hw_stop:
|
||||
stm32_adc_core_hw_stop(dev);
|
||||
err_pm_stop:
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_set_suspended(dev);
|
||||
pm_runtime_put_noidle(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -502,17 +549,39 @@ 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);
|
||||
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
of_platform_depopulate(&pdev->dev);
|
||||
stm32_adc_irq_remove(pdev, priv);
|
||||
if (priv->bclk)
|
||||
clk_disable_unprepare(priv->bclk);
|
||||
if (priv->aclk)
|
||||
clk_disable_unprepare(priv->aclk);
|
||||
regulator_disable(priv->vref);
|
||||
stm32_adc_core_hw_stop(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
pm_runtime_set_suspended(&pdev->dev);
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_PM)
|
||||
static int stm32_adc_core_runtime_suspend(struct device *dev)
|
||||
{
|
||||
stm32_adc_core_hw_stop(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_adc_core_runtime_resume(struct device *dev)
|
||||
{
|
||||
return stm32_adc_core_hw_start(dev);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops stm32_adc_core_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume)
|
||||
SET_RUNTIME_PM_OPS(stm32_adc_core_runtime_suspend,
|
||||
stm32_adc_core_runtime_resume,
|
||||
NULL)
|
||||
};
|
||||
|
||||
static const struct stm32_adc_priv_cfg stm32f4_adc_priv_cfg = {
|
||||
.regs = &stm32f4_adc_common_regs,
|
||||
.clk_sel = stm32f4_adc_clk_sel,
|
||||
@ -552,6 +621,7 @@ static struct platform_driver stm32_adc_driver = {
|
||||
.driver = {
|
||||
.name = "stm32-adc-core",
|
||||
.of_match_table = stm32_adc_of_match,
|
||||
.pm = &stm32_adc_core_pm_ops,
|
||||
},
|
||||
};
|
||||
module_platform_driver(stm32_adc_driver);
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
@ -148,6 +149,7 @@ enum stm32h7_adc_dmngt {
|
||||
#define STM32_ADC_MAX_SMP 7 /* SMPx range is [0..7] */
|
||||
#define STM32_ADC_TIMEOUT_US 100000
|
||||
#define STM32_ADC_TIMEOUT (msecs_to_jiffies(STM32_ADC_TIMEOUT_US / 1000))
|
||||
#define STM32_ADC_HW_STOP_DELAY_MS 100
|
||||
|
||||
#define STM32_DMA_BUFFER_SIZE PAGE_SIZE
|
||||
|
||||
@ -199,11 +201,13 @@ struct stm32_adc_trig_info {
|
||||
* @calfact_s: Calibration offset for single ended channels
|
||||
* @calfact_d: Calibration offset in differential
|
||||
* @lincalfact: Linearity calibration factor
|
||||
* @calibrated: Indicates calibration status
|
||||
*/
|
||||
struct stm32_adc_calib {
|
||||
u32 calfact_s;
|
||||
u32 calfact_d;
|
||||
u32 lincalfact[STM32H7_LINCALFACT_NUM];
|
||||
bool calibrated;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -251,7 +255,6 @@ struct stm32_adc;
|
||||
* @trigs: external trigger sources
|
||||
* @clk_required: clock is required
|
||||
* @has_vregready: vregready status flag presence
|
||||
* @selfcalib: optional routine for self-calibration
|
||||
* @prepare: optional prepare routine (power-up, enable)
|
||||
* @start_conv: routine to start conversions
|
||||
* @stop_conv: routine to stop conversions
|
||||
@ -264,7 +267,6 @@ struct stm32_adc_cfg {
|
||||
struct stm32_adc_trig_info *trigs;
|
||||
bool clk_required;
|
||||
bool has_vregready;
|
||||
int (*selfcalib)(struct stm32_adc *);
|
||||
int (*prepare)(struct stm32_adc *);
|
||||
void (*start_conv)(struct stm32_adc *, bool dma);
|
||||
void (*stop_conv)(struct stm32_adc *);
|
||||
@ -623,6 +625,47 @@ static void stm32_adc_set_res(struct stm32_adc *adc)
|
||||
stm32_adc_writel(adc, res->reg, val);
|
||||
}
|
||||
|
||||
static int stm32_adc_hw_stop(struct device *dev)
|
||||
{
|
||||
struct stm32_adc *adc = dev_get_drvdata(dev);
|
||||
|
||||
if (adc->cfg->unprepare)
|
||||
adc->cfg->unprepare(adc);
|
||||
|
||||
if (adc->clk)
|
||||
clk_disable_unprepare(adc->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_adc_hw_start(struct device *dev)
|
||||
{
|
||||
struct stm32_adc *adc = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
if (adc->clk) {
|
||||
ret = clk_prepare_enable(adc->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
stm32_adc_set_res(adc);
|
||||
|
||||
if (adc->cfg->prepare) {
|
||||
ret = adc->cfg->prepare(adc);
|
||||
if (ret)
|
||||
goto err_clk_dis;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_clk_dis:
|
||||
if (adc->clk)
|
||||
clk_disable_unprepare(adc->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* stm32f4_adc_start_conv() - Start conversions for regular channels.
|
||||
* @adc: stm32 adc instance
|
||||
@ -777,6 +820,7 @@ static void stm32h7_adc_disable(struct stm32_adc *adc)
|
||||
/**
|
||||
* stm32h7_adc_read_selfcalib() - read calibration shadow regs, save result
|
||||
* @adc: stm32 adc instance
|
||||
* Note: Must be called once ADC is enabled, so LINCALRDYW[1..6] are writable
|
||||
*/
|
||||
static int stm32h7_adc_read_selfcalib(struct stm32_adc *adc)
|
||||
{
|
||||
@ -784,11 +828,6 @@ static int stm32h7_adc_read_selfcalib(struct stm32_adc *adc)
|
||||
int i, ret;
|
||||
u32 lincalrdyw_mask, val;
|
||||
|
||||
/* Enable adc so LINCALRDYW1..6 bits are writable */
|
||||
ret = stm32h7_adc_enable(adc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read linearity calibration */
|
||||
lincalrdyw_mask = STM32H7_LINCALRDYW6;
|
||||
for (i = STM32H7_LINCALFACT_NUM - 1; i >= 0; i--) {
|
||||
@ -801,7 +840,7 @@ static int stm32h7_adc_read_selfcalib(struct stm32_adc *adc)
|
||||
100, STM32_ADC_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "Failed to read calfact\n");
|
||||
goto disable;
|
||||
return ret;
|
||||
}
|
||||
|
||||
val = stm32_adc_readl(adc, STM32H7_ADC_CALFACT2);
|
||||
@ -817,11 +856,9 @@ static int stm32h7_adc_read_selfcalib(struct stm32_adc *adc)
|
||||
adc->cal.calfact_s >>= STM32H7_CALFACT_S_SHIFT;
|
||||
adc->cal.calfact_d = (val & STM32H7_CALFACT_D_MASK);
|
||||
adc->cal.calfact_d >>= STM32H7_CALFACT_D_SHIFT;
|
||||
adc->cal.calibrated = true;
|
||||
|
||||
disable:
|
||||
stm32h7_adc_disable(adc);
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -898,9 +935,9 @@ static int stm32h7_adc_restore_selfcalib(struct stm32_adc *adc)
|
||||
#define STM32H7_ADC_CALIB_TIMEOUT_US 100000
|
||||
|
||||
/**
|
||||
* stm32h7_adc_selfcalib() - Procedure to calibrate ADC (from power down)
|
||||
* stm32h7_adc_selfcalib() - Procedure to calibrate ADC
|
||||
* @adc: stm32 adc instance
|
||||
* Exit from power down, calibrate ADC, then return to power down.
|
||||
* Note: Must be called once ADC is out of power down.
|
||||
*/
|
||||
static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
|
||||
{
|
||||
@ -908,9 +945,8 @@ static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
ret = stm32h7_adc_exit_pwr_down(adc);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (adc->cal.calibrated)
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Select calibration mode:
|
||||
@ -927,7 +963,7 @@ static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
|
||||
STM32H7_ADC_CALIB_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "calibration failed\n");
|
||||
goto pwr_dwn;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -944,18 +980,13 @@ static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
|
||||
STM32H7_ADC_CALIB_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "calibration failed\n");
|
||||
goto pwr_dwn;
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
stm32_adc_clr_bits(adc, STM32H7_ADC_CR,
|
||||
STM32H7_ADCALDIF | STM32H7_ADCALLIN);
|
||||
|
||||
/* Read calibration result for future reference */
|
||||
ret = stm32h7_adc_read_selfcalib(adc);
|
||||
|
||||
pwr_dwn:
|
||||
stm32h7_adc_enter_pwr_down(adc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -972,19 +1003,28 @@ pwr_dwn:
|
||||
*/
|
||||
static int stm32h7_adc_prepare(struct stm32_adc *adc)
|
||||
{
|
||||
int ret;
|
||||
int calib, ret;
|
||||
|
||||
ret = stm32h7_adc_exit_pwr_down(adc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = stm32h7_adc_selfcalib(adc);
|
||||
if (ret < 0)
|
||||
goto pwr_dwn;
|
||||
calib = ret;
|
||||
|
||||
stm32_adc_writel(adc, STM32H7_ADC_DIFSEL, adc->difsel);
|
||||
|
||||
ret = stm32h7_adc_enable(adc);
|
||||
if (ret)
|
||||
goto pwr_dwn;
|
||||
|
||||
ret = stm32h7_adc_restore_selfcalib(adc);
|
||||
/* Either restore or read calibration result for future reference */
|
||||
if (calib)
|
||||
ret = stm32h7_adc_restore_selfcalib(adc);
|
||||
else
|
||||
ret = stm32h7_adc_read_selfcalib(adc);
|
||||
if (ret)
|
||||
goto disable;
|
||||
|
||||
@ -1174,6 +1214,7 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev,
|
||||
int *res)
|
||||
{
|
||||
struct stm32_adc *adc = iio_priv(indio_dev);
|
||||
struct device *dev = indio_dev->dev.parent;
|
||||
const struct stm32_adc_regspec *regs = adc->cfg->regs;
|
||||
long timeout;
|
||||
u32 val;
|
||||
@ -1183,10 +1224,10 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev,
|
||||
|
||||
adc->bufi = 0;
|
||||
|
||||
if (adc->cfg->prepare) {
|
||||
ret = adc->cfg->prepare(adc);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = pm_runtime_get_sync(dev);
|
||||
if (ret < 0) {
|
||||
pm_runtime_put_noidle(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Apply sampling time settings */
|
||||
@ -1224,8 +1265,8 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev,
|
||||
|
||||
stm32_adc_conv_irq_disable(adc);
|
||||
|
||||
if (adc->cfg->unprepare)
|
||||
adc->cfg->unprepare(adc);
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -1333,15 +1374,22 @@ static int stm32_adc_update_scan_mode(struct iio_dev *indio_dev,
|
||||
const unsigned long *scan_mask)
|
||||
{
|
||||
struct stm32_adc *adc = iio_priv(indio_dev);
|
||||
struct device *dev = indio_dev->dev.parent;
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_get_sync(dev);
|
||||
if (ret < 0) {
|
||||
pm_runtime_put_noidle(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
adc->num_conv = bitmap_weight(scan_mask, indio_dev->masklength);
|
||||
|
||||
ret = stm32_adc_conf_scan_seq(indio_dev, scan_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_adc_of_xlate(struct iio_dev *indio_dev,
|
||||
@ -1371,12 +1419,23 @@ static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev,
|
||||
unsigned *readval)
|
||||
{
|
||||
struct stm32_adc *adc = iio_priv(indio_dev);
|
||||
struct device *dev = indio_dev->dev.parent;
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_get_sync(dev);
|
||||
if (ret < 0) {
|
||||
pm_runtime_put_noidle(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!readval)
|
||||
stm32_adc_writel(adc, reg, writeval);
|
||||
else
|
||||
*readval = stm32_adc_readl(adc, reg);
|
||||
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1459,21 +1518,22 @@ static int stm32_adc_dma_start(struct iio_dev *indio_dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
|
||||
static int __stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct stm32_adc *adc = iio_priv(indio_dev);
|
||||
struct device *dev = indio_dev->dev.parent;
|
||||
int ret;
|
||||
|
||||
if (adc->cfg->prepare) {
|
||||
ret = adc->cfg->prepare(adc);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = pm_runtime_get_sync(dev);
|
||||
if (ret < 0) {
|
||||
pm_runtime_put_noidle(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = stm32_adc_set_trig(indio_dev, indio_dev->trig);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "Can't set trigger\n");
|
||||
goto err_unprepare;
|
||||
goto err_pm_put;
|
||||
}
|
||||
|
||||
ret = stm32_adc_dma_start(indio_dev);
|
||||
@ -1482,10 +1542,6 @@ static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
|
||||
goto err_clr_trig;
|
||||
}
|
||||
|
||||
ret = iio_triggered_buffer_postenable(indio_dev);
|
||||
if (ret < 0)
|
||||
goto err_stop_dma;
|
||||
|
||||
/* Reset adc buffer index */
|
||||
adc->bufi = 0;
|
||||
|
||||
@ -1496,39 +1552,58 @@ static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
|
||||
|
||||
return 0;
|
||||
|
||||
err_stop_dma:
|
||||
if (adc->dma_chan)
|
||||
dmaengine_terminate_all(adc->dma_chan);
|
||||
err_clr_trig:
|
||||
stm32_adc_set_trig(indio_dev, NULL);
|
||||
err_unprepare:
|
||||
if (adc->cfg->unprepare)
|
||||
adc->cfg->unprepare(adc);
|
||||
err_pm_put:
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_adc_buffer_predisable(struct iio_dev *indio_dev)
|
||||
static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = iio_triggered_buffer_postenable(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = __stm32_adc_buffer_postenable(indio_dev);
|
||||
if (ret < 0)
|
||||
iio_triggered_buffer_predisable(indio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __stm32_adc_buffer_predisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct stm32_adc *adc = iio_priv(indio_dev);
|
||||
int ret;
|
||||
struct device *dev = indio_dev->dev.parent;
|
||||
|
||||
adc->cfg->stop_conv(adc);
|
||||
if (!adc->dma_chan)
|
||||
stm32_adc_conv_irq_disable(adc);
|
||||
|
||||
ret = iio_triggered_buffer_predisable(indio_dev);
|
||||
if (ret < 0)
|
||||
dev_err(&indio_dev->dev, "predisable failed\n");
|
||||
|
||||
if (adc->dma_chan)
|
||||
dmaengine_terminate_all(adc->dma_chan);
|
||||
|
||||
if (stm32_adc_set_trig(indio_dev, NULL))
|
||||
dev_err(&indio_dev->dev, "Can't clear trigger\n");
|
||||
|
||||
if (adc->cfg->unprepare)
|
||||
adc->cfg->unprepare(adc);
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
}
|
||||
|
||||
static int stm32_adc_buffer_predisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
__stm32_adc_buffer_predisable(indio_dev);
|
||||
|
||||
ret = iio_triggered_buffer_predisable(indio_dev);
|
||||
if (ret < 0)
|
||||
dev_err(&indio_dev->dev, "predisable failed\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -1867,32 +1942,17 @@ static int stm32_adc_probe(struct platform_device *pdev)
|
||||
}
|
||||
}
|
||||
|
||||
if (adc->clk) {
|
||||
ret = clk_prepare_enable(adc->clk);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "clk enable failed\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = stm32_adc_of_get_resolution(indio_dev);
|
||||
if (ret < 0)
|
||||
goto err_clk_disable;
|
||||
stm32_adc_set_res(adc);
|
||||
|
||||
if (adc->cfg->selfcalib) {
|
||||
ret = adc->cfg->selfcalib(adc);
|
||||
if (ret)
|
||||
goto err_clk_disable;
|
||||
}
|
||||
return ret;
|
||||
|
||||
ret = stm32_adc_chan_of_init(indio_dev);
|
||||
if (ret < 0)
|
||||
goto err_clk_disable;
|
||||
return ret;
|
||||
|
||||
ret = stm32_adc_dma_request(indio_dev);
|
||||
if (ret < 0)
|
||||
goto err_clk_disable;
|
||||
return ret;
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev,
|
||||
&iio_pollfunc_store_time,
|
||||
@ -1903,15 +1963,35 @@ static int stm32_adc_probe(struct platform_device *pdev)
|
||||
goto err_dma_disable;
|
||||
}
|
||||
|
||||
/* Get stm32-adc-core PM online */
|
||||
pm_runtime_get_noresume(dev);
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_set_autosuspend_delay(dev, STM32_ADC_HW_STOP_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
ret = stm32_adc_hw_start(dev);
|
||||
if (ret)
|
||||
goto err_buffer_cleanup;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "iio dev register failed\n");
|
||||
goto err_buffer_cleanup;
|
||||
goto err_hw_stop;
|
||||
}
|
||||
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_hw_stop:
|
||||
stm32_adc_hw_stop(dev);
|
||||
|
||||
err_buffer_cleanup:
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_set_suspended(dev);
|
||||
pm_runtime_put_noidle(dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
||||
err_dma_disable:
|
||||
@ -1921,9 +2001,6 @@ err_dma_disable:
|
||||
adc->rx_buf, adc->rx_dma_buf);
|
||||
dma_release_channel(adc->dma_chan);
|
||||
}
|
||||
err_clk_disable:
|
||||
if (adc->clk)
|
||||
clk_disable_unprepare(adc->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -1933,7 +2010,12 @@ 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);
|
||||
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
iio_device_unregister(indio_dev);
|
||||
stm32_adc_hw_stop(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
pm_runtime_set_suspended(&pdev->dev);
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
if (adc->dma_chan) {
|
||||
dma_free_coherent(adc->dma_chan->device->dev,
|
||||
@ -1941,12 +2023,62 @@ static int stm32_adc_remove(struct platform_device *pdev)
|
||||
adc->rx_buf, adc->rx_dma_buf);
|
||||
dma_release_channel(adc->dma_chan);
|
||||
}
|
||||
if (adc->clk)
|
||||
clk_disable_unprepare(adc->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_PM_SLEEP)
|
||||
static int stm32_adc_suspend(struct device *dev)
|
||||
{
|
||||
struct stm32_adc *adc = dev_get_drvdata(dev);
|
||||
struct iio_dev *indio_dev = iio_priv_to_dev(adc);
|
||||
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
__stm32_adc_buffer_predisable(indio_dev);
|
||||
|
||||
return pm_runtime_force_suspend(dev);
|
||||
}
|
||||
|
||||
static int stm32_adc_resume(struct device *dev)
|
||||
{
|
||||
struct stm32_adc *adc = dev_get_drvdata(dev);
|
||||
struct iio_dev *indio_dev = iio_priv_to_dev(adc);
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_force_resume(dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!iio_buffer_enabled(indio_dev))
|
||||
return 0;
|
||||
|
||||
ret = stm32_adc_update_scan_mode(indio_dev,
|
||||
indio_dev->active_scan_mask);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return __stm32_adc_buffer_postenable(indio_dev);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_PM)
|
||||
static int stm32_adc_runtime_suspend(struct device *dev)
|
||||
{
|
||||
return stm32_adc_hw_stop(dev);
|
||||
}
|
||||
|
||||
static int stm32_adc_runtime_resume(struct device *dev)
|
||||
{
|
||||
return stm32_adc_hw_start(dev);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops stm32_adc_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(stm32_adc_suspend, stm32_adc_resume)
|
||||
SET_RUNTIME_PM_OPS(stm32_adc_runtime_suspend, stm32_adc_runtime_resume,
|
||||
NULL)
|
||||
};
|
||||
|
||||
static const struct stm32_adc_cfg stm32f4_adc_cfg = {
|
||||
.regs = &stm32f4_adc_regspec,
|
||||
.adc_info = &stm32f4_adc_info,
|
||||
@ -1961,7 +2093,6 @@ static const struct stm32_adc_cfg stm32h7_adc_cfg = {
|
||||
.regs = &stm32h7_adc_regspec,
|
||||
.adc_info = &stm32h7_adc_info,
|
||||
.trigs = stm32h7_adc_trigs,
|
||||
.selfcalib = stm32h7_adc_selfcalib,
|
||||
.start_conv = stm32h7_adc_start_conv,
|
||||
.stop_conv = stm32h7_adc_stop_conv,
|
||||
.prepare = stm32h7_adc_prepare,
|
||||
@ -1974,7 +2105,6 @@ static const struct stm32_adc_cfg stm32mp1_adc_cfg = {
|
||||
.adc_info = &stm32h7_adc_info,
|
||||
.trigs = stm32h7_adc_trigs,
|
||||
.has_vregready = true,
|
||||
.selfcalib = stm32h7_adc_selfcalib,
|
||||
.start_conv = stm32h7_adc_start_conv,
|
||||
.stop_conv = stm32h7_adc_stop_conv,
|
||||
.prepare = stm32h7_adc_prepare,
|
||||
@ -1996,6 +2126,7 @@ static struct platform_driver stm32_adc_driver = {
|
||||
.driver = {
|
||||
.name = "stm32-adc",
|
||||
.of_match_table = stm32_adc_of_match,
|
||||
.pm = &stm32_adc_pm_ops,
|
||||
},
|
||||
};
|
||||
module_platform_driver(stm32_adc_driver);
|
||||
|
@ -1,3 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2014 Angelo Compagnucci <angelo.compagnucci@gmail.com>
|
||||
*
|
||||
@ -6,16 +7,14 @@
|
||||
* http://www.ti.com/lit/ds/symlink/adc128s052.pdf
|
||||
* http://www.ti.com/lit/ds/symlink/adc122s021.pdf
|
||||
* http://www.ti.com/lit/ds/symlink/adc124s021.pdf
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
struct adc128_configuration {
|
||||
@ -135,10 +134,15 @@ static const struct iio_info adc128_info = {
|
||||
static int adc128_probe(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
unsigned int config;
|
||||
struct adc128 *adc;
|
||||
int config = spi_get_device_id(spi)->driver_data;
|
||||
int ret;
|
||||
|
||||
if (dev_fwnode(&spi->dev))
|
||||
config = (unsigned long) device_get_match_data(&spi->dev);
|
||||
else
|
||||
config = spi_get_device_id(spi)->driver_data;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
@ -186,23 +190,40 @@ static int adc128_remove(struct spi_device *spi)
|
||||
static const struct of_device_id adc128_of_match[] = {
|
||||
{ .compatible = "ti,adc128s052", },
|
||||
{ .compatible = "ti,adc122s021", },
|
||||
{ .compatible = "ti,adc122s051", },
|
||||
{ .compatible = "ti,adc122s101", },
|
||||
{ .compatible = "ti,adc124s021", },
|
||||
{ .compatible = "ti,adc124s051", },
|
||||
{ .compatible = "ti,adc124s101", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adc128_of_match);
|
||||
|
||||
static const struct spi_device_id adc128_id[] = {
|
||||
{ "adc128s052", 0}, /* index into adc128_config */
|
||||
{ "adc122s021", 1},
|
||||
{ "adc124s021", 2},
|
||||
{ "adc128s052", 0 }, /* index into adc128_config */
|
||||
{ "adc122s021", 1 },
|
||||
{ "adc122s051", 1 },
|
||||
{ "adc122s101", 1 },
|
||||
{ "adc124s021", 2 },
|
||||
{ "adc124s051", 2 },
|
||||
{ "adc124s101", 2 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, adc128_id);
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static const struct acpi_device_id adc128_acpi_match[] = {
|
||||
{ "AANT1280", 2 }, /* ADC124S021 compatible ACPI ID */
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, adc128_acpi_match);
|
||||
#endif
|
||||
|
||||
static struct spi_driver adc128_driver = {
|
||||
.driver = {
|
||||
.name = "adc128s052",
|
||||
.of_match_table = of_match_ptr(adc128_of_match),
|
||||
.acpi_match_table = ACPI_PTR(adc128_acpi_match),
|
||||
},
|
||||
.probe = adc128_probe,
|
||||
.remove = adc128_remove,
|
||||
|
@ -336,7 +336,7 @@ static void adjust_exponent_nano(int *val0, int *val1, int scale0,
|
||||
scale1 = scale1 % pow_10(8 - i);
|
||||
}
|
||||
*val0 += res;
|
||||
*val1 = scale1 * pow_10(exp);
|
||||
*val1 = scale1 * pow_10(exp);
|
||||
} else if (exp < 0) {
|
||||
exp = abs(exp);
|
||||
if (exp > 9) {
|
||||
|
@ -462,43 +462,35 @@ static struct ssp_data *ssp_parse_dt(struct device *dev)
|
||||
|
||||
data->mcu_ap_gpio = of_get_named_gpio(node, "mcu-ap-gpios", 0);
|
||||
if (data->mcu_ap_gpio < 0)
|
||||
goto err_free_pd;
|
||||
return NULL;
|
||||
|
||||
data->ap_mcu_gpio = of_get_named_gpio(node, "ap-mcu-gpios", 0);
|
||||
if (data->ap_mcu_gpio < 0)
|
||||
goto err_free_pd;
|
||||
return NULL;
|
||||
|
||||
data->mcu_reset_gpio = of_get_named_gpio(node, "mcu-reset-gpios", 0);
|
||||
if (data->mcu_reset_gpio < 0)
|
||||
goto err_free_pd;
|
||||
return NULL;
|
||||
|
||||
ret = devm_gpio_request_one(dev, data->ap_mcu_gpio, GPIOF_OUT_INIT_HIGH,
|
||||
"ap-mcu-gpios");
|
||||
if (ret)
|
||||
goto err_free_pd;
|
||||
return NULL;
|
||||
|
||||
ret = devm_gpio_request_one(dev, data->mcu_reset_gpio,
|
||||
GPIOF_OUT_INIT_HIGH, "mcu-reset-gpios");
|
||||
if (ret)
|
||||
goto err_ap_mcu;
|
||||
return NULL;
|
||||
|
||||
match = of_match_node(ssp_of_match, node);
|
||||
if (!match)
|
||||
goto err_mcu_reset_gpio;
|
||||
return NULL;
|
||||
|
||||
data->sensorhub_info = match->data;
|
||||
|
||||
dev_set_drvdata(dev, data);
|
||||
|
||||
return data;
|
||||
|
||||
err_mcu_reset_gpio:
|
||||
devm_gpio_free(dev, data->mcu_reset_gpio);
|
||||
err_ap_mcu:
|
||||
devm_gpio_free(dev, data->ap_mcu_gpio);
|
||||
err_free_pd:
|
||||
devm_kfree(dev, data);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
static struct ssp_data *ssp_parse_dt(struct device *pdev)
|
||||
|
@ -133,7 +133,7 @@ static int st_sensors_match_fs(struct st_sensor_settings *sensor_settings,
|
||||
|
||||
for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) {
|
||||
if (sensor_settings->fs.fs_avl[i].num == 0)
|
||||
goto st_sensors_match_odr_error;
|
||||
return ret;
|
||||
|
||||
if (sensor_settings->fs.fs_avl[i].num == fs) {
|
||||
*index_fs_avl = i;
|
||||
@ -142,7 +142,6 @@ static int st_sensors_match_fs(struct st_sensor_settings *sensor_settings,
|
||||
}
|
||||
}
|
||||
|
||||
st_sensors_match_odr_error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ static irqreturn_t st_sensors_irq_thread(int irq, void *p)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
/*
|
||||
* If we are using egde IRQs, new samples arrived while processing
|
||||
* If we are using edge IRQs, new samples arrived while processing
|
||||
* the IRQ and those may be missed unless we pick them here, so poll
|
||||
* again. If the sensor delivery frequency is very high, this thread
|
||||
* turns into a polled loop handler.
|
||||
@ -148,7 +148,7 @@ int st_sensors_allocate_trigger(struct iio_dev *indio_dev,
|
||||
if (!sdata->sensor_settings->drdy_irq.addr_ihl) {
|
||||
dev_err(&indio_dev->dev,
|
||||
"falling/low specified for IRQ "
|
||||
"but hardware only support rising/high: "
|
||||
"but hardware supports only rising/high: "
|
||||
"will request rising/high\n");
|
||||
if (irq_trig == IRQF_TRIGGER_FALLING)
|
||||
irq_trig = IRQF_TRIGGER_RISING;
|
||||
|
@ -366,6 +366,15 @@ config TI_DAC5571
|
||||
|
||||
If compiled as a module, it will be called ti-dac5571.
|
||||
|
||||
config TI_DAC7311
|
||||
tristate "Texas Instruments 8/10/12-bit 1-channel DAC driver"
|
||||
depends on SPI
|
||||
help
|
||||
Driver for the Texas Instruments
|
||||
DAC7311, DAC6311, DAC5311.
|
||||
|
||||
If compiled as a module, it will be called ti-dac7311.
|
||||
|
||||
config VF610_DAC
|
||||
tristate "Vybrid vf610 DAC driver"
|
||||
depends on OF
|
||||
|
@ -40,4 +40,5 @@ obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o
|
||||
obj-$(CONFIG_STM32_DAC) += stm32-dac.o
|
||||
obj-$(CONFIG_TI_DAC082S085) += ti-dac082s085.o
|
||||
obj-$(CONFIG_TI_DAC5571) += ti-dac5571.o
|
||||
obj-$(CONFIG_TI_DAC7311) += ti-dac7311.o
|
||||
obj-$(CONFIG_VF610_DAC) += vf610_dac.o
|
||||
|
@ -19,6 +19,12 @@ static int ad5686_spi_write(struct ad5686_state *st,
|
||||
u8 tx_len, *buf;
|
||||
|
||||
switch (st->chip_info->regmap_type) {
|
||||
case AD5310_REGMAP:
|
||||
st->data[0].d16 = cpu_to_be16(AD5310_CMD(cmd) |
|
||||
val);
|
||||
buf = &st->data[0].d8[0];
|
||||
tx_len = 2;
|
||||
break;
|
||||
case AD5683_REGMAP:
|
||||
st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) |
|
||||
AD5683_DATA(val));
|
||||
@ -56,10 +62,18 @@ static int ad5686_spi_read(struct ad5686_state *st, u8 addr)
|
||||
u8 cmd = 0;
|
||||
int ret;
|
||||
|
||||
if (st->chip_info->regmap_type == AD5686_REGMAP)
|
||||
cmd = AD5686_CMD_READBACK_ENABLE;
|
||||
else if (st->chip_info->regmap_type == AD5683_REGMAP)
|
||||
switch (st->chip_info->regmap_type) {
|
||||
case AD5310_REGMAP:
|
||||
return -ENOTSUPP;
|
||||
case AD5683_REGMAP:
|
||||
cmd = AD5686_CMD_READBACK_ENABLE_V2;
|
||||
break;
|
||||
case AD5686_REGMAP:
|
||||
cmd = AD5686_CMD_READBACK_ENABLE;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) |
|
||||
AD5686_ADDR(addr));
|
||||
@ -86,6 +100,7 @@ static int ad5686_spi_remove(struct spi_device *spi)
|
||||
}
|
||||
|
||||
static const struct spi_device_id ad5686_spi_id[] = {
|
||||
{"ad5310r", ID_AD5310R},
|
||||
{"ad5672r", ID_AD5672R},
|
||||
{"ad5676", ID_AD5676},
|
||||
{"ad5676r", ID_AD5676R},
|
||||
|
@ -83,6 +83,10 @@ static ssize_t ad5686_write_dac_powerdown(struct iio_dev *indio_dev,
|
||||
st->pwr_down_mask &= ~(0x3 << (chan->channel * 2));
|
||||
|
||||
switch (st->chip_info->regmap_type) {
|
||||
case AD5310_REGMAP:
|
||||
shift = 9;
|
||||
ref_bit_msk = AD5310_REF_BIT_MSK;
|
||||
break;
|
||||
case AD5683_REGMAP:
|
||||
shift = 13;
|
||||
ref_bit_msk = AD5683_REF_BIT_MSK;
|
||||
@ -124,7 +128,8 @@ static int ad5686_read_raw(struct iio_dev *indio_dev,
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
*val = (ret >> chan->scan_type.shift) &
|
||||
GENMASK(chan->scan_type.realbits - 1, 0);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = st->vref_mv;
|
||||
@ -221,6 +226,7 @@ static struct iio_chan_spec name[] = { \
|
||||
AD5868_CHANNEL(7, 7, bits, _shift), \
|
||||
}
|
||||
|
||||
DECLARE_AD5693_CHANNELS(ad5310r_channels, 10, 2);
|
||||
DECLARE_AD5693_CHANNELS(ad5311r_channels, 10, 6);
|
||||
DECLARE_AD5676_CHANNELS(ad5672_channels, 12, 4);
|
||||
DECLARE_AD5676_CHANNELS(ad5676_channels, 16, 0);
|
||||
@ -232,6 +238,12 @@ DECLARE_AD5693_CHANNELS(ad5692r_channels, 14, 2);
|
||||
DECLARE_AD5693_CHANNELS(ad5691r_channels, 12, 4);
|
||||
|
||||
static const struct ad5686_chip_info ad5686_chip_info_tbl[] = {
|
||||
[ID_AD5310R] = {
|
||||
.channels = ad5310r_channels,
|
||||
.int_vref_mv = 2500,
|
||||
.num_channels = 1,
|
||||
.regmap_type = AD5310_REGMAP,
|
||||
},
|
||||
[ID_AD5311R] = {
|
||||
.channels = ad5311r_channels,
|
||||
.int_vref_mv = 2500,
|
||||
@ -419,6 +431,11 @@ int ad5686_probe(struct device *dev,
|
||||
indio_dev->num_channels = st->chip_info->num_channels;
|
||||
|
||||
switch (st->chip_info->regmap_type) {
|
||||
case AD5310_REGMAP:
|
||||
cmd = AD5686_CMD_CONTROL_REG;
|
||||
ref_bit_msk = AD5310_REF_BIT_MSK;
|
||||
st->use_internal_vref = !voltage_uv;
|
||||
break;
|
||||
case AD5683_REGMAP:
|
||||
cmd = AD5686_CMD_CONTROL_REG;
|
||||
ref_bit_msk = AD5683_REF_BIT_MSK;
|
||||
|
@ -13,7 +13,10 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#define AD5310_CMD(x) ((x) << 12)
|
||||
|
||||
#define AD5683_DATA(x) ((x) << 4)
|
||||
|
||||
#define AD5686_ADDR(x) ((x) << 16)
|
||||
#define AD5686_CMD(x) ((x) << 20)
|
||||
|
||||
@ -38,6 +41,8 @@
|
||||
|
||||
#define AD5686_CMD_CONTROL_REG 0x4
|
||||
#define AD5686_CMD_READBACK_ENABLE_V2 0x5
|
||||
|
||||
#define AD5310_REF_BIT_MSK BIT(8)
|
||||
#define AD5683_REF_BIT_MSK BIT(12)
|
||||
#define AD5693_REF_BIT_MSK BIT(12)
|
||||
|
||||
@ -45,6 +50,7 @@
|
||||
* ad5686_supported_device_ids:
|
||||
*/
|
||||
enum ad5686_supported_device_ids {
|
||||
ID_AD5310R,
|
||||
ID_AD5311R,
|
||||
ID_AD5671R,
|
||||
ID_AD5672R,
|
||||
@ -72,6 +78,7 @@ enum ad5686_supported_device_ids {
|
||||
};
|
||||
|
||||
enum ad5686_regmap_type {
|
||||
AD5310_REGMAP,
|
||||
AD5683_REGMAP,
|
||||
AD5686_REGMAP,
|
||||
AD5693_REGMAP
|
||||
|
@ -74,11 +74,11 @@ static int dpot_dac_read_raw(struct iio_dev *indio_dev,
|
||||
case IIO_VAL_INT:
|
||||
/*
|
||||
* Convert integer scale to fractional scale by
|
||||
* setting the denominator (val2) to one...
|
||||
* setting the denominator (val2) to one, and...
|
||||
*/
|
||||
*val2 = 1;
|
||||
ret = IIO_VAL_FRACTIONAL;
|
||||
/* ...and fall through. */
|
||||
/* fall through */
|
||||
case IIO_VAL_FRACTIONAL:
|
||||
*val *= regulator_get_voltage(dac->vref) / 1000;
|
||||
*val2 *= dac->max_ohms;
|
||||
|
338
drivers/iio/dac/ti-dac7311.c
Normal file
338
drivers/iio/dac/ti-dac7311.c
Normal file
@ -0,0 +1,338 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* ti-dac7311.c - Texas Instruments 8/10/12-bit 1-channel DAC driver
|
||||
*
|
||||
* Copyright (C) 2018 CMC NV
|
||||
*
|
||||
* http://www.ti.com/lit/ds/symlink/dac7311.pdf
|
||||
*/
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
enum {
|
||||
ID_DAC5311 = 0,
|
||||
ID_DAC6311,
|
||||
ID_DAC7311,
|
||||
};
|
||||
|
||||
enum {
|
||||
POWER_1KOHM_TO_GND = 0,
|
||||
POWER_100KOHM_TO_GND,
|
||||
POWER_TRI_STATE,
|
||||
};
|
||||
|
||||
struct ti_dac_spec {
|
||||
u8 resolution;
|
||||
};
|
||||
|
||||
static const struct ti_dac_spec ti_dac_spec[] = {
|
||||
[ID_DAC5311] = { .resolution = 8 },
|
||||
[ID_DAC6311] = { .resolution = 10 },
|
||||
[ID_DAC7311] = { .resolution = 12 },
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ti_dac_chip - TI DAC chip
|
||||
* @lock: protects write sequences
|
||||
* @vref: regulator generating Vref
|
||||
* @spi: SPI device to send data to the device
|
||||
* @val: cached value
|
||||
* @powerdown: whether the chip is powered down
|
||||
* @powerdown_mode: selected by the user
|
||||
* @resolution: resolution of the chip
|
||||
* @buf: buffer for transfer data
|
||||
*/
|
||||
struct ti_dac_chip {
|
||||
struct mutex lock;
|
||||
struct regulator *vref;
|
||||
struct spi_device *spi;
|
||||
u16 val;
|
||||
bool powerdown;
|
||||
u8 powerdown_mode;
|
||||
u8 resolution;
|
||||
u8 buf[2] ____cacheline_aligned;
|
||||
};
|
||||
|
||||
static u8 ti_dac_get_power(struct ti_dac_chip *ti_dac, bool powerdown)
|
||||
{
|
||||
if (powerdown)
|
||||
return ti_dac->powerdown_mode + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ti_dac_cmd(struct ti_dac_chip *ti_dac, u8 power, u16 val)
|
||||
{
|
||||
u8 shift = 14 - ti_dac->resolution;
|
||||
|
||||
ti_dac->buf[0] = (val << shift) & 0xFF;
|
||||
ti_dac->buf[1] = (power << 6) | (val >> (8 - shift));
|
||||
return spi_write(ti_dac->spi, ti_dac->buf, 2);
|
||||
}
|
||||
|
||||
static const char * const ti_dac_powerdown_modes[] = {
|
||||
"1kohm_to_gnd",
|
||||
"100kohm_to_gnd",
|
||||
"three_state",
|
||||
};
|
||||
|
||||
static int ti_dac_get_powerdown_mode(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan)
|
||||
{
|
||||
struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
|
||||
|
||||
return ti_dac->powerdown_mode;
|
||||
}
|
||||
|
||||
static int ti_dac_set_powerdown_mode(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
unsigned int mode)
|
||||
{
|
||||
struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
|
||||
|
||||
ti_dac->powerdown_mode = mode;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_enum ti_dac_powerdown_mode = {
|
||||
.items = ti_dac_powerdown_modes,
|
||||
.num_items = ARRAY_SIZE(ti_dac_powerdown_modes),
|
||||
.get = ti_dac_get_powerdown_mode,
|
||||
.set = ti_dac_set_powerdown_mode,
|
||||
};
|
||||
|
||||
static ssize_t ti_dac_read_powerdown(struct iio_dev *indio_dev,
|
||||
uintptr_t private,
|
||||
const struct iio_chan_spec *chan,
|
||||
char *buf)
|
||||
{
|
||||
struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
|
||||
|
||||
return sprintf(buf, "%d\n", ti_dac->powerdown);
|
||||
}
|
||||
|
||||
static ssize_t ti_dac_write_powerdown(struct iio_dev *indio_dev,
|
||||
uintptr_t private,
|
||||
const struct iio_chan_spec *chan,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
|
||||
bool powerdown;
|
||||
u8 power;
|
||||
int ret;
|
||||
|
||||
ret = strtobool(buf, &powerdown);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
power = ti_dac_get_power(ti_dac, powerdown);
|
||||
|
||||
mutex_lock(&ti_dac->lock);
|
||||
ret = ti_dac_cmd(ti_dac, power, 0);
|
||||
if (!ret)
|
||||
ti_dac->powerdown = powerdown;
|
||||
mutex_unlock(&ti_dac->lock);
|
||||
|
||||
return ret ? ret : len;
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec_ext_info ti_dac_ext_info[] = {
|
||||
{
|
||||
.name = "powerdown",
|
||||
.read = ti_dac_read_powerdown,
|
||||
.write = ti_dac_write_powerdown,
|
||||
.shared = IIO_SHARED_BY_TYPE,
|
||||
},
|
||||
IIO_ENUM("powerdown_mode", IIO_SHARED_BY_TYPE, &ti_dac_powerdown_mode),
|
||||
IIO_ENUM_AVAILABLE("powerdown_mode", &ti_dac_powerdown_mode),
|
||||
{ },
|
||||
};
|
||||
|
||||
#define TI_DAC_CHANNEL(chan) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.channel = (chan), \
|
||||
.output = true, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.ext_info = ti_dac_ext_info, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec ti_dac_channels[] = {
|
||||
TI_DAC_CHANNEL(0),
|
||||
};
|
||||
|
||||
static int ti_dac_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
*val = ti_dac->val;
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = regulator_get_voltage(ti_dac->vref);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret / 1000;
|
||||
*val2 = ti_dac->resolution;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ti_dac_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
|
||||
u8 power = ti_dac_get_power(ti_dac, ti_dac->powerdown);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (ti_dac->val == val)
|
||||
return 0;
|
||||
|
||||
if (val >= (1 << ti_dac->resolution) || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (ti_dac->powerdown)
|
||||
return -EBUSY;
|
||||
|
||||
mutex_lock(&ti_dac->lock);
|
||||
ret = ti_dac_cmd(ti_dac, power, val);
|
||||
if (!ret)
|
||||
ti_dac->val = val;
|
||||
mutex_unlock(&ti_dac->lock);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ti_dac_write_raw_get_fmt(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, long mask)
|
||||
{
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
static const struct iio_info ti_dac_info = {
|
||||
.read_raw = ti_dac_read_raw,
|
||||
.write_raw = ti_dac_write_raw,
|
||||
.write_raw_get_fmt = ti_dac_write_raw_get_fmt,
|
||||
};
|
||||
|
||||
static int ti_dac_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
const struct ti_dac_spec *spec;
|
||||
struct ti_dac_chip *ti_dac;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*ti_dac));
|
||||
if (!indio_dev) {
|
||||
dev_err(dev, "can not allocate iio device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
spi->mode = SPI_MODE_1;
|
||||
spi->bits_per_word = 16;
|
||||
spi_setup(spi);
|
||||
|
||||
indio_dev->dev.parent = dev;
|
||||
indio_dev->dev.of_node = spi->dev.of_node;
|
||||
indio_dev->info = &ti_dac_info;
|
||||
indio_dev->name = spi_get_device_id(spi)->name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = ti_dac_channels;
|
||||
spi_set_drvdata(spi, indio_dev);
|
||||
|
||||
ti_dac = iio_priv(indio_dev);
|
||||
ti_dac->powerdown = false;
|
||||
ti_dac->spi = spi;
|
||||
|
||||
spec = &ti_dac_spec[spi_get_device_id(spi)->driver_data];
|
||||
indio_dev->num_channels = 1;
|
||||
ti_dac->resolution = spec->resolution;
|
||||
|
||||
ti_dac->vref = devm_regulator_get(dev, "vref");
|
||||
if (IS_ERR(ti_dac->vref)) {
|
||||
dev_err(dev, "error to get regulator\n");
|
||||
return PTR_ERR(ti_dac->vref);
|
||||
}
|
||||
|
||||
ret = regulator_enable(ti_dac->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "can not enable regulator\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
mutex_init(&ti_dac->lock);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "fail to register iio device: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
mutex_destroy(&ti_dac->lock);
|
||||
regulator_disable(ti_dac->vref);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ti_dac_remove(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev = spi_get_drvdata(spi);
|
||||
struct ti_dac_chip *ti_dac = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
mutex_destroy(&ti_dac->lock);
|
||||
regulator_disable(ti_dac->vref);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ti_dac_of_id[] = {
|
||||
{ .compatible = "ti,dac5311" },
|
||||
{ .compatible = "ti,dac6311" },
|
||||
{ .compatible = "ti,dac7311" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ti_dac_of_id);
|
||||
|
||||
static const struct spi_device_id ti_dac_spi_id[] = {
|
||||
{ "dac5311", ID_DAC5311 },
|
||||
{ "dac6311", ID_DAC6311 },
|
||||
{ "dac7311", ID_DAC7311 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ti_dac_spi_id);
|
||||
|
||||
static struct spi_driver ti_dac_driver = {
|
||||
.driver = {
|
||||
.name = "ti-dac7311",
|
||||
.of_match_table = ti_dac_of_id,
|
||||
},
|
||||
.probe = ti_dac_probe,
|
||||
.remove = ti_dac_remove,
|
||||
.id_table = ti_dac_spi_id,
|
||||
};
|
||||
module_spi_driver(ti_dac_driver);
|
||||
|
||||
MODULE_AUTHOR("Charles-Antoine Couret <charles-antoine.couret@essensium.com>");
|
||||
MODULE_DESCRIPTION("Texas Instruments 8/10/12-bit 1-channel DAC driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -6,6 +6,5 @@ extern const struct regmap_config bmi160_regmap_config;
|
||||
|
||||
int bmi160_core_probe(struct device *dev, struct regmap *regmap,
|
||||
const char *name, bool use_spi);
|
||||
void bmi160_core_remove(struct device *dev);
|
||||
|
||||
#endif /* BMI160_H_ */
|
||||
|
@ -542,10 +542,12 @@ static int bmi160_chip_init(struct bmi160_data *data, bool use_spi)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bmi160_chip_uninit(struct bmi160_data *data)
|
||||
static void bmi160_chip_uninit(void *data)
|
||||
{
|
||||
bmi160_set_mode(data, BMI160_GYRO, false);
|
||||
bmi160_set_mode(data, BMI160_ACCEL, false);
|
||||
struct bmi160_data *bmi_data = data;
|
||||
|
||||
bmi160_set_mode(bmi_data, BMI160_GYRO, false);
|
||||
bmi160_set_mode(bmi_data, BMI160_ACCEL, false);
|
||||
}
|
||||
|
||||
int bmi160_core_probe(struct device *dev, struct regmap *regmap,
|
||||
@ -567,6 +569,10 @@ int bmi160_core_probe(struct device *dev, struct regmap *regmap,
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, bmi160_chip_uninit, data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!name && ACPI_HANDLE(dev))
|
||||
name = bmi160_match_acpi_device(dev);
|
||||
|
||||
@ -577,35 +583,19 @@ int bmi160_core_probe(struct device *dev, struct regmap *regmap,
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->info = &bmi160_info;
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
bmi160_trigger_handler, NULL);
|
||||
ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
|
||||
bmi160_trigger_handler, NULL);
|
||||
if (ret < 0)
|
||||
goto uninit;
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
ret = devm_iio_device_register(dev, indio_dev);
|
||||
if (ret < 0)
|
||||
goto buffer_cleanup;
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
buffer_cleanup:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
uninit:
|
||||
bmi160_chip_uninit(data);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bmi160_core_probe);
|
||||
|
||||
void bmi160_core_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct bmi160_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
bmi160_chip_uninit(data);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bmi160_core_remove);
|
||||
|
||||
MODULE_AUTHOR("Daniel Baluta <daniel.baluta@intel.com");
|
||||
MODULE_DESCRIPTION("Bosch BMI160 driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
@ -38,13 +38,6 @@ static int bmi160_i2c_probe(struct i2c_client *client,
|
||||
return bmi160_core_probe(&client->dev, regmap, name, false);
|
||||
}
|
||||
|
||||
static int bmi160_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
bmi160_core_remove(&client->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id bmi160_i2c_id[] = {
|
||||
{"bmi160", 0},
|
||||
{}
|
||||
@ -72,7 +65,6 @@ static struct i2c_driver bmi160_i2c_driver = {
|
||||
.of_match_table = of_match_ptr(bmi160_of_match),
|
||||
},
|
||||
.probe = bmi160_i2c_probe,
|
||||
.remove = bmi160_i2c_remove,
|
||||
.id_table = bmi160_i2c_id,
|
||||
};
|
||||
module_i2c_driver(bmi160_i2c_driver);
|
||||
|
@ -29,13 +29,6 @@ static int bmi160_spi_probe(struct spi_device *spi)
|
||||
return bmi160_core_probe(&spi->dev, regmap, id->name, true);
|
||||
}
|
||||
|
||||
static int bmi160_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
bmi160_core_remove(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id bmi160_spi_id[] = {
|
||||
{"bmi160", 0},
|
||||
{}
|
||||
@ -58,7 +51,6 @@ MODULE_DEVICE_TABLE(of, bmi160_of_match);
|
||||
|
||||
static struct spi_driver bmi160_spi_driver = {
|
||||
.probe = bmi160_spi_probe,
|
||||
.remove = bmi160_spi_remove,
|
||||
.id_table = bmi160_spi_id,
|
||||
.driver = {
|
||||
.acpi_match_table = ACPI_PTR(bmi160_acpi_match),
|
||||
|
@ -1,4 +1,5 @@
|
||||
st_lsm6dsx-y := st_lsm6dsx_core.o st_lsm6dsx_buffer.o
|
||||
st_lsm6dsx-y := st_lsm6dsx_core.o st_lsm6dsx_buffer.o \
|
||||
st_lsm6dsx_shub.o
|
||||
|
||||
obj-$(CONFIG_IIO_ST_LSM6DSX) += st_lsm6dsx.o
|
||||
obj-$(CONFIG_IIO_ST_LSM6DSX_I2C) += st_lsm6dsx_i2c.o
|
||||
|
@ -43,6 +43,24 @@ enum st_lsm6dsx_hw_id {
|
||||
* ST_LSM6DSX_TAGGED_SAMPLE_SIZE)
|
||||
#define ST_LSM6DSX_SHIFT_VAL(val, mask) (((val) << __ffs(mask)) & (mask))
|
||||
|
||||
#define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx) \
|
||||
{ \
|
||||
.type = chan_type, \
|
||||
.address = addr, \
|
||||
.modified = 1, \
|
||||
.channel2 = mod, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.scan_index = scan_idx, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
}
|
||||
|
||||
struct st_lsm6dsx_reg {
|
||||
u8 addr;
|
||||
u8 mask;
|
||||
@ -50,6 +68,28 @@ struct st_lsm6dsx_reg {
|
||||
|
||||
struct st_lsm6dsx_hw;
|
||||
|
||||
struct st_lsm6dsx_odr {
|
||||
u16 hz;
|
||||
u8 val;
|
||||
};
|
||||
|
||||
#define ST_LSM6DSX_ODR_LIST_SIZE 6
|
||||
struct st_lsm6dsx_odr_table_entry {
|
||||
struct st_lsm6dsx_reg reg;
|
||||
struct st_lsm6dsx_odr odr_avl[ST_LSM6DSX_ODR_LIST_SIZE];
|
||||
};
|
||||
|
||||
struct st_lsm6dsx_fs {
|
||||
u32 gain;
|
||||
u8 val;
|
||||
};
|
||||
|
||||
#define ST_LSM6DSX_FS_LIST_SIZE 4
|
||||
struct st_lsm6dsx_fs_table_entry {
|
||||
struct st_lsm6dsx_reg reg;
|
||||
struct st_lsm6dsx_fs fs_avl[ST_LSM6DSX_FS_LIST_SIZE];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct st_lsm6dsx_fifo_ops - ST IMU FIFO settings
|
||||
* @read_fifo: Read FIFO callback.
|
||||
@ -84,6 +124,70 @@ struct st_lsm6dsx_hw_ts_settings {
|
||||
struct st_lsm6dsx_reg decimator;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct st_lsm6dsx_shub_settings - ST IMU hw i2c controller settings
|
||||
* @page_mux: register page mux info (addr + mask).
|
||||
* @master_en: master config register info (addr + mask).
|
||||
* @pullup_en: i2c controller pull-up register info (addr + mask).
|
||||
* @aux_sens: aux sensor register info (addr + mask).
|
||||
* @wr_once: write_once register info (addr + mask).
|
||||
* @shub_out: sensor hub first output register info.
|
||||
* @slv0_addr: slave0 address in secondary page.
|
||||
* @dw_slv0_addr: slave0 write register address in secondary page.
|
||||
* @batch_en: Enable/disable FIFO batching.
|
||||
*/
|
||||
struct st_lsm6dsx_shub_settings {
|
||||
struct st_lsm6dsx_reg page_mux;
|
||||
struct st_lsm6dsx_reg master_en;
|
||||
struct st_lsm6dsx_reg pullup_en;
|
||||
struct st_lsm6dsx_reg aux_sens;
|
||||
struct st_lsm6dsx_reg wr_once;
|
||||
u8 shub_out;
|
||||
u8 slv0_addr;
|
||||
u8 dw_slv0_addr;
|
||||
u8 batch_en;
|
||||
};
|
||||
|
||||
enum st_lsm6dsx_ext_sensor_id {
|
||||
ST_LSM6DSX_ID_MAGN,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct st_lsm6dsx_ext_dev_settings - i2c controller slave settings
|
||||
* @i2c_addr: I2c slave address list.
|
||||
* @wai: Wai address info.
|
||||
* @id: external sensor id.
|
||||
* @odr: Output data rate of the sensor [Hz].
|
||||
* @gain: Configured sensor sensitivity.
|
||||
* @temp_comp: Temperature compensation register info (addr + mask).
|
||||
* @pwr_table: Power on register info (addr + mask).
|
||||
* @off_canc: Offset cancellation register info (addr + mask).
|
||||
* @bdu: Block data update register info (addr + mask).
|
||||
* @out: Output register info.
|
||||
*/
|
||||
struct st_lsm6dsx_ext_dev_settings {
|
||||
u8 i2c_addr[2];
|
||||
struct {
|
||||
u8 addr;
|
||||
u8 val;
|
||||
} wai;
|
||||
enum st_lsm6dsx_ext_sensor_id id;
|
||||
struct st_lsm6dsx_odr_table_entry odr_table;
|
||||
struct st_lsm6dsx_fs_table_entry fs_table;
|
||||
struct st_lsm6dsx_reg temp_comp;
|
||||
struct {
|
||||
struct st_lsm6dsx_reg reg;
|
||||
u8 off_val;
|
||||
u8 on_val;
|
||||
} pwr_table;
|
||||
struct st_lsm6dsx_reg off_canc;
|
||||
struct st_lsm6dsx_reg bdu;
|
||||
struct {
|
||||
u8 addr;
|
||||
u8 len;
|
||||
} out;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct st_lsm6dsx_settings - ST IMU sensor settings
|
||||
* @wai: Sensor WhoAmI default value.
|
||||
@ -93,6 +197,7 @@ struct st_lsm6dsx_hw_ts_settings {
|
||||
* @batch: List of FIFO batching register info (addr + mask).
|
||||
* @fifo_ops: Sensor hw FIFO parameters.
|
||||
* @ts_settings: Hw timer related settings.
|
||||
* @shub_settings: i2c controller related settings.
|
||||
*/
|
||||
struct st_lsm6dsx_settings {
|
||||
u8 wai;
|
||||
@ -102,11 +207,15 @@ struct st_lsm6dsx_settings {
|
||||
struct st_lsm6dsx_reg batch[ST_LSM6DSX_MAX_ID];
|
||||
struct st_lsm6dsx_fifo_ops fifo_ops;
|
||||
struct st_lsm6dsx_hw_ts_settings ts_settings;
|
||||
struct st_lsm6dsx_shub_settings shub_settings;
|
||||
};
|
||||
|
||||
enum st_lsm6dsx_sensor_id {
|
||||
ST_LSM6DSX_ID_ACC,
|
||||
ST_LSM6DSX_ID_GYRO,
|
||||
ST_LSM6DSX_ID_ACC,
|
||||
ST_LSM6DSX_ID_EXT0,
|
||||
ST_LSM6DSX_ID_EXT1,
|
||||
ST_LSM6DSX_ID_EXT2,
|
||||
ST_LSM6DSX_ID_MAX,
|
||||
};
|
||||
|
||||
@ -126,6 +235,7 @@ enum st_lsm6dsx_fifo_mode {
|
||||
* @sip: Number of samples in a given pattern.
|
||||
* @decimator: FIFO decimation factor.
|
||||
* @ts_ref: Sensor timestamp reference for hw one.
|
||||
* @ext_info: Sensor settings if it is connected to i2c controller
|
||||
*/
|
||||
struct st_lsm6dsx_sensor {
|
||||
char name[32];
|
||||
@ -139,6 +249,11 @@ struct st_lsm6dsx_sensor {
|
||||
u8 sip;
|
||||
u8 decimator;
|
||||
s64 ts_ref;
|
||||
|
||||
struct {
|
||||
const struct st_lsm6dsx_ext_dev_settings *settings;
|
||||
u8 addr;
|
||||
} ext_info;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -148,6 +263,7 @@ struct st_lsm6dsx_sensor {
|
||||
* @irq: Device interrupt line (I2C or SPI).
|
||||
* @fifo_lock: Mutex to prevent concurrent access to the hw FIFO.
|
||||
* @conf_lock: Mutex to prevent concurrent FIFO configuration update.
|
||||
* @page_lock: Mutex to prevent concurrent memory page configuration.
|
||||
* @fifo_mode: FIFO operating mode supported by the device.
|
||||
* @enable_mask: Enabled sensor bitmask.
|
||||
* @ts_sip: Total number of timestamp samples in a given pattern.
|
||||
@ -163,6 +279,7 @@ struct st_lsm6dsx_hw {
|
||||
|
||||
struct mutex fifo_lock;
|
||||
struct mutex conf_lock;
|
||||
struct mutex page_lock;
|
||||
|
||||
enum st_lsm6dsx_fifo_mode fifo_mode;
|
||||
u8 enable_mask;
|
||||
@ -176,13 +293,15 @@ struct st_lsm6dsx_hw {
|
||||
const struct st_lsm6dsx_settings *settings;
|
||||
};
|
||||
|
||||
static const unsigned long st_lsm6dsx_available_scan_masks[] = {0x7, 0x0};
|
||||
extern const struct dev_pm_ops st_lsm6dsx_pm_ops;
|
||||
|
||||
int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
|
||||
struct regmap *regmap);
|
||||
int st_lsm6dsx_sensor_enable(struct st_lsm6dsx_sensor *sensor);
|
||||
int st_lsm6dsx_sensor_disable(struct st_lsm6dsx_sensor *sensor);
|
||||
int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor,
|
||||
bool enable);
|
||||
int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw);
|
||||
int st_lsm6dsx_set_watermark(struct iio_dev *iio_dev, unsigned int val);
|
||||
int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor,
|
||||
u16 watermark);
|
||||
int st_lsm6dsx_flush_fifo(struct st_lsm6dsx_hw *hw);
|
||||
@ -191,5 +310,47 @@ int st_lsm6dsx_set_fifo_mode(struct st_lsm6dsx_hw *hw,
|
||||
int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw);
|
||||
int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw);
|
||||
int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u16 odr, u8 *val);
|
||||
int st_lsm6dsx_shub_probe(struct st_lsm6dsx_hw *hw, const char *name);
|
||||
int st_lsm6dsx_shub_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable);
|
||||
int st_lsm6dsx_set_page(struct st_lsm6dsx_hw *hw, bool enable);
|
||||
|
||||
static inline int
|
||||
st_lsm6dsx_update_bits_locked(struct st_lsm6dsx_hw *hw, unsigned int addr,
|
||||
unsigned int mask, unsigned int val)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_lock(&hw->page_lock);
|
||||
err = regmap_update_bits(hw->regmap, addr, mask, val);
|
||||
mutex_unlock(&hw->page_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline int
|
||||
st_lsm6dsx_read_locked(struct st_lsm6dsx_hw *hw, unsigned int addr,
|
||||
void *val, unsigned int len)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_lock(&hw->page_lock);
|
||||
err = regmap_bulk_read(hw->regmap, addr, val, len);
|
||||
mutex_unlock(&hw->page_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline int
|
||||
st_lsm6dsx_write_locked(struct st_lsm6dsx_hw *hw, unsigned int addr,
|
||||
unsigned int val)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_lock(&hw->page_lock);
|
||||
err = regmap_write(hw->regmap, addr, val);
|
||||
mutex_unlock(&hw->page_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#endif /* ST_LSM6DSX_H */
|
||||
|
@ -68,6 +68,9 @@ enum st_lsm6dsx_fifo_tag {
|
||||
ST_LSM6DSX_GYRO_TAG = 0x01,
|
||||
ST_LSM6DSX_ACC_TAG = 0x02,
|
||||
ST_LSM6DSX_TS_TAG = 0x04,
|
||||
ST_LSM6DSX_EXT0_TAG = 0x0f,
|
||||
ST_LSM6DSX_EXT1_TAG = 0x10,
|
||||
ST_LSM6DSX_EXT2_TAG = 0x11,
|
||||
};
|
||||
|
||||
static const
|
||||
@ -102,6 +105,9 @@ static void st_lsm6dsx_get_max_min_odr(struct st_lsm6dsx_hw *hw,
|
||||
|
||||
*max_odr = 0, *min_odr = ~0;
|
||||
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
|
||||
if (!hw->iio_devs[i])
|
||||
continue;
|
||||
|
||||
sensor = iio_priv(hw->iio_devs[i]);
|
||||
|
||||
if (!(hw->enable_mask & BIT(sensor->id)))
|
||||
@ -125,6 +131,9 @@ static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw)
|
||||
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
|
||||
const struct st_lsm6dsx_reg *dec_reg;
|
||||
|
||||
if (!hw->iio_devs[i])
|
||||
continue;
|
||||
|
||||
sensor = iio_priv(hw->iio_devs[i]);
|
||||
/* update fifo decimators and sample in pattern */
|
||||
if (hw->enable_mask & BIT(sensor->id)) {
|
||||
@ -142,8 +151,9 @@ static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw)
|
||||
if (dec_reg->addr) {
|
||||
int val = ST_LSM6DSX_SHIFT_VAL(data, dec_reg->mask);
|
||||
|
||||
err = regmap_update_bits(hw->regmap, dec_reg->addr,
|
||||
dec_reg->mask, val);
|
||||
err = st_lsm6dsx_update_bits_locked(hw, dec_reg->addr,
|
||||
dec_reg->mask,
|
||||
val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
@ -162,8 +172,8 @@ static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw)
|
||||
int val, ts_dec = !!hw->ts_sip;
|
||||
|
||||
val = ST_LSM6DSX_SHIFT_VAL(ts_dec, ts_dec_reg->mask);
|
||||
err = regmap_update_bits(hw->regmap, ts_dec_reg->addr,
|
||||
ts_dec_reg->mask, val);
|
||||
err = st_lsm6dsx_update_bits_locked(hw, ts_dec_reg->addr,
|
||||
ts_dec_reg->mask, val);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
@ -171,12 +181,12 @@ static int st_lsm6dsx_update_decimators(struct st_lsm6dsx_hw *hw)
|
||||
int st_lsm6dsx_set_fifo_mode(struct st_lsm6dsx_hw *hw,
|
||||
enum st_lsm6dsx_fifo_mode fifo_mode)
|
||||
{
|
||||
unsigned int data;
|
||||
int err;
|
||||
|
||||
err = regmap_update_bits(hw->regmap, ST_LSM6DSX_REG_FIFO_MODE_ADDR,
|
||||
ST_LSM6DSX_FIFO_MODE_MASK,
|
||||
FIELD_PREP(ST_LSM6DSX_FIFO_MODE_MASK,
|
||||
fifo_mode));
|
||||
data = FIELD_PREP(ST_LSM6DSX_FIFO_MODE_MASK, fifo_mode);
|
||||
err = st_lsm6dsx_update_bits_locked(hw, ST_LSM6DSX_REG_FIFO_MODE_ADDR,
|
||||
ST_LSM6DSX_FIFO_MODE_MASK, data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
@ -207,15 +217,15 @@ static int st_lsm6dsx_set_fifo_odr(struct st_lsm6dsx_sensor *sensor,
|
||||
data = 0;
|
||||
}
|
||||
val = ST_LSM6DSX_SHIFT_VAL(data, batch_reg->mask);
|
||||
return regmap_update_bits(hw->regmap, batch_reg->addr,
|
||||
batch_reg->mask, val);
|
||||
return st_lsm6dsx_update_bits_locked(hw, batch_reg->addr,
|
||||
batch_reg->mask, val);
|
||||
} else {
|
||||
data = hw->enable_mask ? ST_LSM6DSX_MAX_FIFO_ODR_VAL : 0;
|
||||
return regmap_update_bits(hw->regmap,
|
||||
ST_LSM6DSX_REG_FIFO_MODE_ADDR,
|
||||
ST_LSM6DSX_FIFO_ODR_MASK,
|
||||
FIELD_PREP(ST_LSM6DSX_FIFO_ODR_MASK,
|
||||
data));
|
||||
return st_lsm6dsx_update_bits_locked(hw,
|
||||
ST_LSM6DSX_REG_FIFO_MODE_ADDR,
|
||||
ST_LSM6DSX_FIFO_ODR_MASK,
|
||||
FIELD_PREP(ST_LSM6DSX_FIFO_ODR_MASK,
|
||||
data));
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,6 +241,9 @@ int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor, u16 watermark)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
|
||||
if (!hw->iio_devs[i])
|
||||
continue;
|
||||
|
||||
cur_sensor = iio_priv(hw->iio_devs[i]);
|
||||
|
||||
if (!(hw->enable_mask & BIT(cur_sensor->id)))
|
||||
@ -246,19 +259,23 @@ int st_lsm6dsx_update_watermark(struct st_lsm6dsx_sensor *sensor, u16 watermark)
|
||||
fifo_watermark = (fifo_watermark / hw->sip) * hw->sip;
|
||||
fifo_watermark = fifo_watermark * hw->settings->fifo_ops.th_wl;
|
||||
|
||||
mutex_lock(&hw->page_lock);
|
||||
err = regmap_read(hw->regmap, hw->settings->fifo_ops.fifo_th.addr + 1,
|
||||
&data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
goto out;
|
||||
|
||||
fifo_th_mask = hw->settings->fifo_ops.fifo_th.mask;
|
||||
fifo_watermark = ((data << 8) & ~fifo_th_mask) |
|
||||
(fifo_watermark & fifo_th_mask);
|
||||
|
||||
wdata = cpu_to_le16(fifo_watermark);
|
||||
return regmap_bulk_write(hw->regmap,
|
||||
hw->settings->fifo_ops.fifo_th.addr,
|
||||
&wdata, sizeof(wdata));
|
||||
err = regmap_bulk_write(hw->regmap,
|
||||
hw->settings->fifo_ops.fifo_th.addr,
|
||||
&wdata, sizeof(wdata));
|
||||
out:
|
||||
mutex_unlock(&hw->page_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int st_lsm6dsx_reset_hw_ts(struct st_lsm6dsx_hw *hw)
|
||||
@ -267,12 +284,15 @@ static int st_lsm6dsx_reset_hw_ts(struct st_lsm6dsx_hw *hw)
|
||||
int i, err;
|
||||
|
||||
/* reset hw ts counter */
|
||||
err = regmap_write(hw->regmap, ST_LSM6DSX_REG_TS_RESET_ADDR,
|
||||
ST_LSM6DSX_TS_RESET_VAL);
|
||||
err = st_lsm6dsx_write_locked(hw, ST_LSM6DSX_REG_TS_RESET_ADDR,
|
||||
ST_LSM6DSX_TS_RESET_VAL);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
|
||||
if (!hw->iio_devs[i])
|
||||
continue;
|
||||
|
||||
sensor = iio_priv(hw->iio_devs[i]);
|
||||
/*
|
||||
* store enable buffer timestamp as reference for
|
||||
@ -297,8 +317,8 @@ static inline int st_lsm6dsx_read_block(struct st_lsm6dsx_hw *hw, u8 addr,
|
||||
while (read_len < data_len) {
|
||||
word_len = min_t(unsigned int, data_len - read_len,
|
||||
max_word_len);
|
||||
err = regmap_bulk_read(hw->regmap, addr, data + read_len,
|
||||
word_len);
|
||||
err = st_lsm6dsx_read_locked(hw, addr, data + read_len,
|
||||
word_len);
|
||||
if (err < 0)
|
||||
return err;
|
||||
read_len += word_len;
|
||||
@ -328,9 +348,9 @@ int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw)
|
||||
__le16 fifo_status;
|
||||
s64 ts = 0;
|
||||
|
||||
err = regmap_bulk_read(hw->regmap,
|
||||
hw->settings->fifo_ops.fifo_diff.addr,
|
||||
&fifo_status, sizeof(fifo_status));
|
||||
err = st_lsm6dsx_read_locked(hw,
|
||||
hw->settings->fifo_ops.fifo_diff.addr,
|
||||
&fifo_status, sizeof(fifo_status));
|
||||
if (err < 0) {
|
||||
dev_err(hw->dev, "failed to read fifo status (err=%d)\n",
|
||||
err);
|
||||
@ -436,6 +456,55 @@ int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw)
|
||||
return read_len;
|
||||
}
|
||||
|
||||
static int
|
||||
st_lsm6dsx_push_tagged_data(struct st_lsm6dsx_hw *hw, u8 tag,
|
||||
u8 *data, s64 ts)
|
||||
{
|
||||
struct st_lsm6dsx_sensor *sensor;
|
||||
struct iio_dev *iio_dev;
|
||||
|
||||
/*
|
||||
* EXT_TAG are managed in FIFO fashion so ST_LSM6DSX_EXT0_TAG
|
||||
* corresponds to the first enabled channel, ST_LSM6DSX_EXT1_TAG
|
||||
* to the second one and ST_LSM6DSX_EXT2_TAG to the last enabled
|
||||
* channel
|
||||
*/
|
||||
switch (tag) {
|
||||
case ST_LSM6DSX_GYRO_TAG:
|
||||
iio_dev = hw->iio_devs[ST_LSM6DSX_ID_GYRO];
|
||||
break;
|
||||
case ST_LSM6DSX_ACC_TAG:
|
||||
iio_dev = hw->iio_devs[ST_LSM6DSX_ID_ACC];
|
||||
break;
|
||||
case ST_LSM6DSX_EXT0_TAG:
|
||||
if (hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT0))
|
||||
iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT0];
|
||||
else if (hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT1))
|
||||
iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT1];
|
||||
else
|
||||
iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT2];
|
||||
break;
|
||||
case ST_LSM6DSX_EXT1_TAG:
|
||||
if ((hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT0)) &&
|
||||
(hw->enable_mask & BIT(ST_LSM6DSX_ID_EXT1)))
|
||||
iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT1];
|
||||
else
|
||||
iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT2];
|
||||
break;
|
||||
case ST_LSM6DSX_EXT2_TAG:
|
||||
iio_dev = hw->iio_devs[ST_LSM6DSX_ID_EXT2];
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sensor = iio_priv(iio_dev);
|
||||
iio_push_to_buffers_with_timestamp(iio_dev, data,
|
||||
ts + sensor->ts_ref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* st_lsm6dsx_read_tagged_fifo() - LSM6DSO read FIFO routine
|
||||
* @hw: Pointer to instance of struct st_lsm6dsx_hw.
|
||||
@ -455,9 +524,9 @@ int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw)
|
||||
__le16 fifo_status;
|
||||
s64 ts = 0;
|
||||
|
||||
err = regmap_bulk_read(hw->regmap,
|
||||
hw->settings->fifo_ops.fifo_diff.addr,
|
||||
&fifo_status, sizeof(fifo_status));
|
||||
err = st_lsm6dsx_read_locked(hw,
|
||||
hw->settings->fifo_ops.fifo_diff.addr,
|
||||
&fifo_status, sizeof(fifo_status));
|
||||
if (err < 0) {
|
||||
dev_err(hw->dev, "failed to read fifo status (err=%d)\n",
|
||||
err);
|
||||
@ -491,8 +560,7 @@ int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw)
|
||||
ST_LSM6DSX_SAMPLE_SIZE);
|
||||
|
||||
tag = hw->buff[i] >> 3;
|
||||
switch (tag) {
|
||||
case ST_LSM6DSX_TS_TAG:
|
||||
if (tag == ST_LSM6DSX_TS_TAG) {
|
||||
/*
|
||||
* hw timestamp is 4B long and it is stored
|
||||
* in FIFO according to this schema:
|
||||
@ -509,19 +577,9 @@ int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw)
|
||||
if (!reset_ts && ts >= 0xffff0000)
|
||||
reset_ts = true;
|
||||
ts *= ST_LSM6DSX_TS_SENSITIVITY;
|
||||
break;
|
||||
case ST_LSM6DSX_GYRO_TAG:
|
||||
iio_push_to_buffers_with_timestamp(
|
||||
hw->iio_devs[ST_LSM6DSX_ID_GYRO],
|
||||
iio_buff, gyro_sensor->ts_ref + ts);
|
||||
break;
|
||||
case ST_LSM6DSX_ACC_TAG:
|
||||
iio_push_to_buffers_with_timestamp(
|
||||
hw->iio_devs[ST_LSM6DSX_ID_ACC],
|
||||
iio_buff, acc_sensor->ts_ref + ts);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
} else {
|
||||
st_lsm6dsx_push_tagged_data(hw, tag, iio_buff,
|
||||
ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -562,20 +620,22 @@ static int st_lsm6dsx_update_fifo(struct iio_dev *iio_dev, bool enable)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
err = st_lsm6dsx_sensor_enable(sensor);
|
||||
if (sensor->id == ST_LSM6DSX_ID_EXT0 ||
|
||||
sensor->id == ST_LSM6DSX_ID_EXT1 ||
|
||||
sensor->id == ST_LSM6DSX_ID_EXT2) {
|
||||
err = st_lsm6dsx_shub_set_enable(sensor, enable);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
} else {
|
||||
err = st_lsm6dsx_sensor_disable(sensor);
|
||||
err = st_lsm6dsx_sensor_set_enable(sensor, enable);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
|
||||
err = st_lsm6dsx_set_fifo_odr(sensor, enable);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = st_lsm6dsx_set_fifo_odr(sensor, enable);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
|
||||
err = st_lsm6dsx_update_decimators(hw);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
@ -690,6 +750,9 @@ int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw)
|
||||
}
|
||||
|
||||
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
|
||||
if (!hw->iio_devs[i])
|
||||
continue;
|
||||
|
||||
buffer = devm_iio_kfifo_allocate(hw->dev);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
@ -56,6 +56,7 @@
|
||||
#define ST_LSM6DSX_REG_WHOAMI_ADDR 0x0f
|
||||
#define ST_LSM6DSX_REG_RESET_ADDR 0x12
|
||||
#define ST_LSM6DSX_REG_RESET_MASK BIT(0)
|
||||
#define ST_LSM6DSX_REG_BOOT_MASK BIT(7)
|
||||
#define ST_LSM6DSX_REG_BDU_ADDR 0x12
|
||||
#define ST_LSM6DSX_REG_BDU_MASK BIT(6)
|
||||
#define ST_LSM6DSX_REG_INT2_ON_INT1_ADDR 0x13
|
||||
@ -87,17 +88,6 @@
|
||||
#define ST_LSM6DSX_GYRO_FS_1000_GAIN IIO_DEGREE_TO_RAD(35000)
|
||||
#define ST_LSM6DSX_GYRO_FS_2000_GAIN IIO_DEGREE_TO_RAD(70000)
|
||||
|
||||
struct st_lsm6dsx_odr {
|
||||
u16 hz;
|
||||
u8 val;
|
||||
};
|
||||
|
||||
#define ST_LSM6DSX_ODR_LIST_SIZE 6
|
||||
struct st_lsm6dsx_odr_table_entry {
|
||||
struct st_lsm6dsx_reg reg;
|
||||
struct st_lsm6dsx_odr odr_avl[ST_LSM6DSX_ODR_LIST_SIZE];
|
||||
};
|
||||
|
||||
static const struct st_lsm6dsx_odr_table_entry st_lsm6dsx_odr_table[] = {
|
||||
[ST_LSM6DSX_ID_ACC] = {
|
||||
.reg = {
|
||||
@ -125,17 +115,6 @@ static const struct st_lsm6dsx_odr_table_entry st_lsm6dsx_odr_table[] = {
|
||||
}
|
||||
};
|
||||
|
||||
struct st_lsm6dsx_fs {
|
||||
u32 gain;
|
||||
u8 val;
|
||||
};
|
||||
|
||||
#define ST_LSM6DSX_FS_LIST_SIZE 4
|
||||
struct st_lsm6dsx_fs_table_entry {
|
||||
struct st_lsm6dsx_reg reg;
|
||||
struct st_lsm6dsx_fs fs_avl[ST_LSM6DSX_FS_LIST_SIZE];
|
||||
};
|
||||
|
||||
static const struct st_lsm6dsx_fs_table_entry st_lsm6dsx_fs_table[] = {
|
||||
[ST_LSM6DSX_ID_ACC] = {
|
||||
.reg = {
|
||||
@ -341,27 +320,35 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = {
|
||||
.mask = GENMASK(7, 6),
|
||||
},
|
||||
},
|
||||
.shub_settings = {
|
||||
.page_mux = {
|
||||
.addr = 0x01,
|
||||
.mask = BIT(6),
|
||||
},
|
||||
.master_en = {
|
||||
.addr = 0x14,
|
||||
.mask = BIT(2),
|
||||
},
|
||||
.pullup_en = {
|
||||
.addr = 0x14,
|
||||
.mask = BIT(3),
|
||||
},
|
||||
.aux_sens = {
|
||||
.addr = 0x14,
|
||||
.mask = GENMASK(1, 0),
|
||||
},
|
||||
.wr_once = {
|
||||
.addr = 0x14,
|
||||
.mask = BIT(6),
|
||||
},
|
||||
.shub_out = 0x02,
|
||||
.slv0_addr = 0x15,
|
||||
.dw_slv0_addr = 0x21,
|
||||
.batch_en = BIT(3),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
#define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx) \
|
||||
{ \
|
||||
.type = chan_type, \
|
||||
.address = addr, \
|
||||
.modified = 1, \
|
||||
.channel2 = mod, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.scan_index = scan_idx, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = {
|
||||
ST_LSM6DSX_CHANNEL(IIO_ACCEL, ST_LSM6DSX_REG_ACC_OUT_X_L_ADDR,
|
||||
IIO_MOD_X, 0),
|
||||
@ -382,6 +369,21 @@ static const struct iio_chan_spec st_lsm6dsx_gyro_channels[] = {
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
};
|
||||
|
||||
int st_lsm6dsx_set_page(struct st_lsm6dsx_hw *hw, bool enable)
|
||||
{
|
||||
const struct st_lsm6dsx_shub_settings *hub_settings;
|
||||
unsigned int data;
|
||||
int err;
|
||||
|
||||
hub_settings = &hw->settings->shub_settings;
|
||||
data = ST_LSM6DSX_SHIFT_VAL(enable, hub_settings->page_mux.mask);
|
||||
err = regmap_update_bits(hw->regmap, hub_settings->page_mux.addr,
|
||||
hub_settings->page_mux.mask, data);
|
||||
usleep_range(100, 150);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int st_lsm6dsx_check_whoami(struct st_lsm6dsx_hw *hw, int id)
|
||||
{
|
||||
int err, i, j, data;
|
||||
@ -421,6 +423,7 @@ static int st_lsm6dsx_set_full_scale(struct st_lsm6dsx_sensor *sensor,
|
||||
{
|
||||
struct st_lsm6dsx_hw *hw = sensor->hw;
|
||||
const struct st_lsm6dsx_reg *reg;
|
||||
unsigned int data;
|
||||
int i, err;
|
||||
u8 val;
|
||||
|
||||
@ -433,8 +436,8 @@ static int st_lsm6dsx_set_full_scale(struct st_lsm6dsx_sensor *sensor,
|
||||
|
||||
val = st_lsm6dsx_fs_table[sensor->id].fs_avl[i].val;
|
||||
reg = &st_lsm6dsx_fs_table[sensor->id].reg;
|
||||
err = regmap_update_bits(hw->regmap, reg->addr, reg->mask,
|
||||
ST_LSM6DSX_SHIFT_VAL(val, reg->mask));
|
||||
data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask);
|
||||
err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
@ -448,7 +451,11 @@ int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u16 odr, u8 *val)
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++)
|
||||
if (st_lsm6dsx_odr_table[sensor->id].odr_avl[i].hz == odr)
|
||||
/*
|
||||
* ext devices can run at different odr respect to
|
||||
* accel sensor
|
||||
*/
|
||||
if (st_lsm6dsx_odr_table[sensor->id].odr_avl[i].hz >= odr)
|
||||
break;
|
||||
|
||||
if (i == ST_LSM6DSX_ODR_LIST_SIZE)
|
||||
@ -459,48 +466,86 @@ int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u16 odr, u8 *val)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u16 odr)
|
||||
static u16 st_lsm6dsx_check_odr_dependency(struct st_lsm6dsx_hw *hw, u16 odr,
|
||||
enum st_lsm6dsx_sensor_id id)
|
||||
{
|
||||
struct st_lsm6dsx_hw *hw = sensor->hw;
|
||||
const struct st_lsm6dsx_reg *reg;
|
||||
int err;
|
||||
u8 val;
|
||||
struct st_lsm6dsx_sensor *ref = iio_priv(hw->iio_devs[id]);
|
||||
|
||||
err = st_lsm6dsx_check_odr(sensor, odr, &val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
reg = &st_lsm6dsx_odr_table[sensor->id].reg;
|
||||
return regmap_update_bits(hw->regmap, reg->addr, reg->mask,
|
||||
ST_LSM6DSX_SHIFT_VAL(val, reg->mask));
|
||||
if (odr > 0) {
|
||||
if (hw->enable_mask & BIT(id))
|
||||
return max_t(u16, ref->odr, odr);
|
||||
else
|
||||
return odr;
|
||||
} else {
|
||||
return (hw->enable_mask & BIT(id)) ? ref->odr : 0;
|
||||
}
|
||||
}
|
||||
|
||||
int st_lsm6dsx_sensor_enable(struct st_lsm6dsx_sensor *sensor)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = st_lsm6dsx_set_odr(sensor, sensor->odr);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
sensor->hw->enable_mask |= BIT(sensor->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int st_lsm6dsx_sensor_disable(struct st_lsm6dsx_sensor *sensor)
|
||||
static int st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u16 req_odr)
|
||||
{
|
||||
struct st_lsm6dsx_sensor *ref_sensor = sensor;
|
||||
struct st_lsm6dsx_hw *hw = sensor->hw;
|
||||
const struct st_lsm6dsx_reg *reg;
|
||||
unsigned int data;
|
||||
u8 val = 0;
|
||||
int err;
|
||||
|
||||
reg = &st_lsm6dsx_odr_table[sensor->id].reg;
|
||||
err = regmap_update_bits(hw->regmap, reg->addr, reg->mask,
|
||||
ST_LSM6DSX_SHIFT_VAL(0, reg->mask));
|
||||
switch (sensor->id) {
|
||||
case ST_LSM6DSX_ID_EXT0:
|
||||
case ST_LSM6DSX_ID_EXT1:
|
||||
case ST_LSM6DSX_ID_EXT2:
|
||||
case ST_LSM6DSX_ID_ACC: {
|
||||
u16 odr;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* i2c embedded controller relies on the accelerometer sensor as
|
||||
* bus read/write trigger so we need to enable accel device
|
||||
* at odr = max(accel_odr, ext_odr) in order to properly
|
||||
* communicate with i2c slave devices
|
||||
*/
|
||||
ref_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
|
||||
for (i = ST_LSM6DSX_ID_ACC; i < ST_LSM6DSX_ID_MAX; i++) {
|
||||
if (!hw->iio_devs[i] || i == sensor->id)
|
||||
continue;
|
||||
|
||||
odr = st_lsm6dsx_check_odr_dependency(hw, req_odr, i);
|
||||
if (odr != req_odr)
|
||||
/* device already configured */
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (req_odr > 0) {
|
||||
err = st_lsm6dsx_check_odr(ref_sensor, req_odr, &val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
reg = &st_lsm6dsx_odr_table[ref_sensor->id].reg;
|
||||
data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask);
|
||||
return st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data);
|
||||
}
|
||||
|
||||
int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor,
|
||||
bool enable)
|
||||
{
|
||||
struct st_lsm6dsx_hw *hw = sensor->hw;
|
||||
u16 odr = enable ? sensor->odr : 0;
|
||||
int err;
|
||||
|
||||
err = st_lsm6dsx_set_odr(sensor, odr);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
sensor->hw->enable_mask &= ~BIT(sensor->id);
|
||||
if (enable)
|
||||
hw->enable_mask |= BIT(sensor->id);
|
||||
else
|
||||
hw->enable_mask &= ~BIT(sensor->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -512,18 +557,18 @@ static int st_lsm6dsx_read_oneshot(struct st_lsm6dsx_sensor *sensor,
|
||||
int err, delay;
|
||||
__le16 data;
|
||||
|
||||
err = st_lsm6dsx_sensor_enable(sensor);
|
||||
err = st_lsm6dsx_sensor_set_enable(sensor, true);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
delay = 1000000 / sensor->odr;
|
||||
usleep_range(delay, 2 * delay);
|
||||
|
||||
err = regmap_bulk_read(hw->regmap, addr, &data, sizeof(data));
|
||||
err = st_lsm6dsx_read_locked(hw, addr, &data, sizeof(data));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
st_lsm6dsx_sensor_disable(sensor);
|
||||
st_lsm6dsx_sensor_set_enable(sensor, false);
|
||||
|
||||
*val = (s16)le16_to_cpu(data);
|
||||
|
||||
@ -596,7 +641,7 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev,
|
||||
return err;
|
||||
}
|
||||
|
||||
static int st_lsm6dsx_set_watermark(struct iio_dev *iio_dev, unsigned int val)
|
||||
int st_lsm6dsx_set_watermark(struct iio_dev *iio_dev, unsigned int val)
|
||||
{
|
||||
struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
|
||||
struct st_lsm6dsx_hw *hw = sensor->hw;
|
||||
@ -692,8 +737,6 @@ static const struct iio_info st_lsm6dsx_gyro_info = {
|
||||
.hwfifo_set_watermark = st_lsm6dsx_set_watermark,
|
||||
};
|
||||
|
||||
static const unsigned long st_lsm6dsx_available_scan_masks[] = {0x7, 0x0};
|
||||
|
||||
static int st_lsm6dsx_of_get_drdy_pin(struct st_lsm6dsx_hw *hw, int *drdy_pin)
|
||||
{
|
||||
struct device_node *np = hw->dev->of_node;
|
||||
@ -732,6 +775,51 @@ static int st_lsm6dsx_get_drdy_reg(struct st_lsm6dsx_hw *hw, u8 *drdy_reg)
|
||||
return err;
|
||||
}
|
||||
|
||||
static int st_lsm6dsx_init_shub(struct st_lsm6dsx_hw *hw)
|
||||
{
|
||||
const struct st_lsm6dsx_shub_settings *hub_settings;
|
||||
struct device_node *np = hw->dev->of_node;
|
||||
struct st_sensors_platform_data *pdata;
|
||||
unsigned int data;
|
||||
int err = 0;
|
||||
|
||||
hub_settings = &hw->settings->shub_settings;
|
||||
|
||||
pdata = (struct st_sensors_platform_data *)hw->dev->platform_data;
|
||||
if ((np && of_property_read_bool(np, "st,pullups")) ||
|
||||
(pdata && pdata->pullups)) {
|
||||
err = st_lsm6dsx_set_page(hw, true);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data = ST_LSM6DSX_SHIFT_VAL(1, hub_settings->pullup_en.mask);
|
||||
err = regmap_update_bits(hw->regmap,
|
||||
hub_settings->pullup_en.addr,
|
||||
hub_settings->pullup_en.mask, data);
|
||||
|
||||
st_lsm6dsx_set_page(hw, false);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (hub_settings->aux_sens.addr) {
|
||||
/* configure aux sensors */
|
||||
err = st_lsm6dsx_set_page(hw, true);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data = ST_LSM6DSX_SHIFT_VAL(3, hub_settings->aux_sens.mask);
|
||||
err = regmap_update_bits(hw->regmap,
|
||||
hub_settings->aux_sens.addr,
|
||||
hub_settings->aux_sens.mask, data);
|
||||
|
||||
st_lsm6dsx_set_page(hw, false);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int st_lsm6dsx_init_hw_timer(struct st_lsm6dsx_hw *hw)
|
||||
{
|
||||
const struct st_lsm6dsx_hw_ts_settings *ts_settings;
|
||||
@ -775,12 +863,23 @@ static int st_lsm6dsx_init_device(struct st_lsm6dsx_hw *hw)
|
||||
u8 drdy_int_reg;
|
||||
int err;
|
||||
|
||||
err = regmap_write(hw->regmap, ST_LSM6DSX_REG_RESET_ADDR,
|
||||
ST_LSM6DSX_REG_RESET_MASK);
|
||||
/* device sw reset */
|
||||
err = regmap_update_bits(hw->regmap, ST_LSM6DSX_REG_RESET_ADDR,
|
||||
ST_LSM6DSX_REG_RESET_MASK,
|
||||
FIELD_PREP(ST_LSM6DSX_REG_RESET_MASK, 1));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
msleep(200);
|
||||
msleep(50);
|
||||
|
||||
/* reload trimming parameter */
|
||||
err = regmap_update_bits(hw->regmap, ST_LSM6DSX_REG_RESET_ADDR,
|
||||
ST_LSM6DSX_REG_BOOT_MASK,
|
||||
FIELD_PREP(ST_LSM6DSX_REG_BOOT_MASK, 1));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
msleep(50);
|
||||
|
||||
/* enable Block Data Update */
|
||||
err = regmap_update_bits(hw->regmap, ST_LSM6DSX_REG_BDU_ADDR,
|
||||
@ -801,6 +900,10 @@ static int st_lsm6dsx_init_device(struct st_lsm6dsx_hw *hw)
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = st_lsm6dsx_init_shub(hw);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return st_lsm6dsx_init_hw_timer(hw);
|
||||
}
|
||||
|
||||
@ -854,6 +957,7 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw,
|
||||
int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
|
||||
struct regmap *regmap)
|
||||
{
|
||||
const struct st_lsm6dsx_shub_settings *hub_settings;
|
||||
struct st_lsm6dsx_hw *hw;
|
||||
int i, err;
|
||||
|
||||
@ -865,6 +969,7 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
|
||||
|
||||
mutex_init(&hw->fifo_lock);
|
||||
mutex_init(&hw->conf_lock);
|
||||
mutex_init(&hw->page_lock);
|
||||
|
||||
hw->buff = devm_kzalloc(dev, ST_LSM6DSX_BUFF_SIZE, GFP_KERNEL);
|
||||
if (!hw->buff)
|
||||
@ -878,7 +983,7 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
|
||||
for (i = 0; i < ST_LSM6DSX_ID_EXT0; i++) {
|
||||
hw->iio_devs[i] = st_lsm6dsx_alloc_iiodev(hw, i, name);
|
||||
if (!hw->iio_devs[i])
|
||||
return -ENOMEM;
|
||||
@ -888,6 +993,13 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
hub_settings = &hw->settings->shub_settings;
|
||||
if (hub_settings->master_en.addr) {
|
||||
err = st_lsm6dsx_shub_probe(hw, name);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (hw->irq > 0) {
|
||||
err = st_lsm6dsx_fifo_setup(hw);
|
||||
if (err < 0)
|
||||
@ -895,6 +1007,9 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
|
||||
}
|
||||
|
||||
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
|
||||
if (!hw->iio_devs[i])
|
||||
continue;
|
||||
|
||||
err = devm_iio_device_register(hw->dev, hw->iio_devs[i]);
|
||||
if (err)
|
||||
return err;
|
||||
@ -909,16 +1024,21 @@ static int __maybe_unused st_lsm6dsx_suspend(struct device *dev)
|
||||
struct st_lsm6dsx_hw *hw = dev_get_drvdata(dev);
|
||||
struct st_lsm6dsx_sensor *sensor;
|
||||
const struct st_lsm6dsx_reg *reg;
|
||||
unsigned int data;
|
||||
int i, err = 0;
|
||||
|
||||
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
|
||||
if (!hw->iio_devs[i])
|
||||
continue;
|
||||
|
||||
sensor = iio_priv(hw->iio_devs[i]);
|
||||
if (!(hw->enable_mask & BIT(sensor->id)))
|
||||
continue;
|
||||
|
||||
reg = &st_lsm6dsx_odr_table[sensor->id].reg;
|
||||
err = regmap_update_bits(hw->regmap, reg->addr, reg->mask,
|
||||
ST_LSM6DSX_SHIFT_VAL(0, reg->mask));
|
||||
data = ST_LSM6DSX_SHIFT_VAL(0, reg->mask);
|
||||
err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask,
|
||||
data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
@ -936,6 +1056,9 @@ static int __maybe_unused st_lsm6dsx_resume(struct device *dev)
|
||||
int i, err = 0;
|
||||
|
||||
for (i = 0; i < ST_LSM6DSX_ID_MAX; i++) {
|
||||
if (!hw->iio_devs[i])
|
||||
continue;
|
||||
|
||||
sensor = iio_priv(hw->iio_devs[i]);
|
||||
if (!(hw->enable_mask & BIT(sensor->id)))
|
||||
continue;
|
||||
|
779
drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c
Normal file
779
drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c
Normal file
@ -0,0 +1,779 @@
|
||||
/*
|
||||
* STMicroelectronics st_lsm6dsx i2c controller driver
|
||||
*
|
||||
* i2c controller embedded in lsm6dx series can connect up to four
|
||||
* slave devices using accelerometer sensor as trigger for i2c
|
||||
* read/write operations. Current implementation relies on SLV0 channel
|
||||
* for slave configuration and SLV{1,2,3} to read data and push them into
|
||||
* the hw FIFO
|
||||
*
|
||||
* Copyright (C) 2018 Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
#include "st_lsm6dsx.h"
|
||||
|
||||
#define ST_LSM6DSX_MAX_SLV_NUM 3
|
||||
#define ST_LSM6DSX_SLV_ADDR(n, base) ((base) + (n) * 3)
|
||||
#define ST_LSM6DSX_SLV_SUB_ADDR(n, base) ((base) + 1 + (n) * 3)
|
||||
#define ST_LSM6DSX_SLV_CONFIG(n, base) ((base) + 2 + (n) * 3)
|
||||
|
||||
#define ST_LS6DSX_READ_OP_MASK GENMASK(2, 0)
|
||||
|
||||
static const struct st_lsm6dsx_ext_dev_settings st_lsm6dsx_ext_dev_table[] = {
|
||||
/* LIS2MDL */
|
||||
{
|
||||
.i2c_addr = { 0x1e },
|
||||
.wai = {
|
||||
.addr = 0x4f,
|
||||
.val = 0x40,
|
||||
},
|
||||
.id = ST_LSM6DSX_ID_MAGN,
|
||||
.odr_table = {
|
||||
.reg = {
|
||||
.addr = 0x60,
|
||||
.mask = GENMASK(3, 2),
|
||||
},
|
||||
.odr_avl[0] = { 10, 0x0 },
|
||||
.odr_avl[1] = { 20, 0x1 },
|
||||
.odr_avl[2] = { 50, 0x2 },
|
||||
.odr_avl[3] = { 100, 0x3 },
|
||||
},
|
||||
.fs_table = {
|
||||
.fs_avl[0] = {
|
||||
.gain = 1500,
|
||||
.val = 0x0,
|
||||
}, /* 1500 uG/LSB */
|
||||
},
|
||||
.temp_comp = {
|
||||
.addr = 0x60,
|
||||
.mask = BIT(7),
|
||||
},
|
||||
.pwr_table = {
|
||||
.reg = {
|
||||
.addr = 0x60,
|
||||
.mask = GENMASK(1, 0),
|
||||
},
|
||||
.off_val = 0x2,
|
||||
.on_val = 0x0,
|
||||
},
|
||||
.off_canc = {
|
||||
.addr = 0x61,
|
||||
.mask = BIT(1),
|
||||
},
|
||||
.bdu = {
|
||||
.addr = 0x62,
|
||||
.mask = BIT(4),
|
||||
},
|
||||
.out = {
|
||||
.addr = 0x68,
|
||||
.len = 6,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static void st_lsm6dsx_shub_wait_complete(struct st_lsm6dsx_hw *hw)
|
||||
{
|
||||
struct st_lsm6dsx_sensor *sensor;
|
||||
|
||||
sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
|
||||
msleep((2000U / sensor->odr) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* st_lsm6dsx_shub_read_reg - read i2c controller register
|
||||
*
|
||||
* Read st_lsm6dsx i2c controller register
|
||||
*/
|
||||
static int st_lsm6dsx_shub_read_reg(struct st_lsm6dsx_hw *hw, u8 addr,
|
||||
u8 *data, int len)
|
||||
{
|
||||
const struct st_lsm6dsx_shub_settings *hub_settings;
|
||||
int err;
|
||||
|
||||
mutex_lock(&hw->page_lock);
|
||||
|
||||
hub_settings = &hw->settings->shub_settings;
|
||||
err = st_lsm6dsx_set_page(hw, true);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
|
||||
err = regmap_bulk_read(hw->regmap, addr, data, len);
|
||||
|
||||
st_lsm6dsx_set_page(hw, false);
|
||||
out:
|
||||
mutex_unlock(&hw->page_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* st_lsm6dsx_shub_write_reg - write i2c controller register
|
||||
*
|
||||
* Write st_lsm6dsx i2c controller register
|
||||
*/
|
||||
static int st_lsm6dsx_shub_write_reg(struct st_lsm6dsx_hw *hw, u8 addr,
|
||||
u8 *data, int len)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_lock(&hw->page_lock);
|
||||
err = st_lsm6dsx_set_page(hw, true);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
|
||||
err = regmap_bulk_write(hw->regmap, addr, data, len);
|
||||
|
||||
st_lsm6dsx_set_page(hw, false);
|
||||
out:
|
||||
mutex_unlock(&hw->page_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
st_lsm6dsx_shub_write_reg_with_mask(struct st_lsm6dsx_hw *hw, u8 addr,
|
||||
u8 mask, u8 val)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_lock(&hw->page_lock);
|
||||
err = st_lsm6dsx_set_page(hw, true);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
|
||||
err = regmap_update_bits(hw->regmap, addr, mask, val);
|
||||
|
||||
st_lsm6dsx_set_page(hw, false);
|
||||
out:
|
||||
mutex_unlock(&hw->page_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int st_lsm6dsx_shub_master_enable(struct st_lsm6dsx_sensor *sensor,
|
||||
bool enable)
|
||||
{
|
||||
const struct st_lsm6dsx_shub_settings *hub_settings;
|
||||
struct st_lsm6dsx_hw *hw = sensor->hw;
|
||||
unsigned int data;
|
||||
int err;
|
||||
|
||||
/* enable acc sensor as trigger */
|
||||
err = st_lsm6dsx_sensor_set_enable(sensor, enable);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mutex_lock(&hw->page_lock);
|
||||
|
||||
hub_settings = &hw->settings->shub_settings;
|
||||
err = st_lsm6dsx_set_page(hw, true);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
|
||||
data = ST_LSM6DSX_SHIFT_VAL(enable, hub_settings->master_en.mask);
|
||||
err = regmap_update_bits(hw->regmap, hub_settings->master_en.addr,
|
||||
hub_settings->master_en.mask, data);
|
||||
|
||||
st_lsm6dsx_set_page(hw, false);
|
||||
out:
|
||||
mutex_unlock(&hw->page_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* st_lsm6dsx_shub_read - read data from slave device register
|
||||
*
|
||||
* Read data from slave device register. SLV0 is used for
|
||||
* one-shot read operation
|
||||
*/
|
||||
static int
|
||||
st_lsm6dsx_shub_read(struct st_lsm6dsx_sensor *sensor, u8 addr,
|
||||
u8 *data, int len)
|
||||
{
|
||||
const struct st_lsm6dsx_shub_settings *hub_settings;
|
||||
struct st_lsm6dsx_hw *hw = sensor->hw;
|
||||
u8 config[3], slv_addr;
|
||||
int err;
|
||||
|
||||
hub_settings = &hw->settings->shub_settings;
|
||||
slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
|
||||
|
||||
config[0] = (sensor->ext_info.addr << 1) | 1;
|
||||
config[1] = addr;
|
||||
config[2] = len & ST_LS6DSX_READ_OP_MASK;
|
||||
|
||||
err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
||||
sizeof(config));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = st_lsm6dsx_shub_master_enable(sensor, true);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
st_lsm6dsx_shub_wait_complete(hw);
|
||||
|
||||
err = st_lsm6dsx_shub_read_reg(hw, hub_settings->shub_out, data,
|
||||
len & ST_LS6DSX_READ_OP_MASK);
|
||||
|
||||
st_lsm6dsx_shub_master_enable(sensor, false);
|
||||
|
||||
memset(config, 0, sizeof(config));
|
||||
return st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
||||
sizeof(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* st_lsm6dsx_shub_write - write data to slave device register
|
||||
*
|
||||
* Write data from slave device register. SLV0 is used for
|
||||
* one-shot write operation
|
||||
*/
|
||||
static int
|
||||
st_lsm6dsx_shub_write(struct st_lsm6dsx_sensor *sensor, u8 addr,
|
||||
u8 *data, int len)
|
||||
{
|
||||
const struct st_lsm6dsx_shub_settings *hub_settings;
|
||||
struct st_lsm6dsx_hw *hw = sensor->hw;
|
||||
u8 config[2], slv_addr;
|
||||
int err, i;
|
||||
|
||||
hub_settings = &hw->settings->shub_settings;
|
||||
if (hub_settings->wr_once.addr) {
|
||||
unsigned int data;
|
||||
|
||||
data = ST_LSM6DSX_SHIFT_VAL(1, hub_settings->wr_once.mask);
|
||||
err = st_lsm6dsx_shub_write_reg_with_mask(hw,
|
||||
hub_settings->wr_once.addr,
|
||||
hub_settings->wr_once.mask,
|
||||
data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
|
||||
config[0] = sensor->ext_info.addr << 1;
|
||||
for (i = 0 ; i < len; i++) {
|
||||
config[1] = addr + i;
|
||||
|
||||
err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
||||
sizeof(config));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = st_lsm6dsx_shub_write_reg(hw, hub_settings->dw_slv0_addr,
|
||||
&data[i], 1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = st_lsm6dsx_shub_master_enable(sensor, true);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
st_lsm6dsx_shub_wait_complete(hw);
|
||||
|
||||
st_lsm6dsx_shub_master_enable(sensor, false);
|
||||
}
|
||||
|
||||
memset(config, 0, sizeof(config));
|
||||
return st_lsm6dsx_shub_write_reg(hw, slv_addr, config, sizeof(config));
|
||||
}
|
||||
|
||||
static int
|
||||
st_lsm6dsx_shub_write_with_mask(struct st_lsm6dsx_sensor *sensor,
|
||||
u8 addr, u8 mask, u8 val)
|
||||
{
|
||||
int err;
|
||||
u8 data;
|
||||
|
||||
err = st_lsm6dsx_shub_read(sensor, addr, &data, sizeof(data));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
data = ((data & ~mask) | (val << __ffs(mask) & mask));
|
||||
|
||||
return st_lsm6dsx_shub_write(sensor, addr, &data, sizeof(data));
|
||||
}
|
||||
|
||||
static int
|
||||
st_lsm6dsx_shub_get_odr_val(struct st_lsm6dsx_sensor *sensor,
|
||||
u16 odr, u16 *val)
|
||||
{
|
||||
const struct st_lsm6dsx_ext_dev_settings *settings;
|
||||
int i;
|
||||
|
||||
settings = sensor->ext_info.settings;
|
||||
for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++)
|
||||
if (settings->odr_table.odr_avl[i].hz == odr)
|
||||
break;
|
||||
|
||||
if (i == ST_LSM6DSX_ODR_LIST_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
*val = settings->odr_table.odr_avl[i].val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
st_lsm6dsx_shub_set_odr(struct st_lsm6dsx_sensor *sensor, u16 odr)
|
||||
{
|
||||
const struct st_lsm6dsx_ext_dev_settings *settings;
|
||||
u16 val;
|
||||
int err;
|
||||
|
||||
err = st_lsm6dsx_shub_get_odr_val(sensor, odr, &val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
settings = sensor->ext_info.settings;
|
||||
return st_lsm6dsx_shub_write_with_mask(sensor,
|
||||
settings->odr_table.reg.addr,
|
||||
settings->odr_table.reg.mask,
|
||||
val);
|
||||
}
|
||||
|
||||
/* use SLV{1,2,3} for FIFO read operations */
|
||||
static int
|
||||
st_lsm6dsx_shub_config_channels(struct st_lsm6dsx_sensor *sensor,
|
||||
bool enable)
|
||||
{
|
||||
const struct st_lsm6dsx_shub_settings *hub_settings;
|
||||
const struct st_lsm6dsx_ext_dev_settings *settings;
|
||||
u8 config[9] = {}, enable_mask, slv_addr;
|
||||
struct st_lsm6dsx_hw *hw = sensor->hw;
|
||||
struct st_lsm6dsx_sensor *cur_sensor;
|
||||
int i, j = 0;
|
||||
|
||||
hub_settings = &hw->settings->shub_settings;
|
||||
if (enable)
|
||||
enable_mask = hw->enable_mask | BIT(sensor->id);
|
||||
else
|
||||
enable_mask = hw->enable_mask & ~BIT(sensor->id);
|
||||
|
||||
for (i = ST_LSM6DSX_ID_EXT0; i <= ST_LSM6DSX_ID_EXT2; i++) {
|
||||
if (!hw->iio_devs[i])
|
||||
continue;
|
||||
|
||||
cur_sensor = iio_priv(hw->iio_devs[i]);
|
||||
if (!(enable_mask & BIT(cur_sensor->id)))
|
||||
continue;
|
||||
|
||||
settings = cur_sensor->ext_info.settings;
|
||||
config[j] = (sensor->ext_info.addr << 1) | 1;
|
||||
config[j + 1] = settings->out.addr;
|
||||
config[j + 2] = (settings->out.len & ST_LS6DSX_READ_OP_MASK) |
|
||||
hub_settings->batch_en;
|
||||
j += 3;
|
||||
}
|
||||
|
||||
slv_addr = ST_LSM6DSX_SLV_ADDR(1, hub_settings->slv0_addr);
|
||||
return st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
||||
sizeof(config));
|
||||
}
|
||||
|
||||
int st_lsm6dsx_shub_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable)
|
||||
{
|
||||
const struct st_lsm6dsx_ext_dev_settings *settings;
|
||||
int err;
|
||||
|
||||
err = st_lsm6dsx_shub_config_channels(sensor, enable);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
settings = sensor->ext_info.settings;
|
||||
if (enable) {
|
||||
err = st_lsm6dsx_shub_set_odr(sensor, sensor->odr);
|
||||
if (err < 0)
|
||||
return err;
|
||||
} else {
|
||||
err = st_lsm6dsx_shub_write_with_mask(sensor,
|
||||
settings->odr_table.reg.addr,
|
||||
settings->odr_table.reg.mask, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (settings->pwr_table.reg.addr) {
|
||||
u8 val;
|
||||
|
||||
val = enable ? settings->pwr_table.on_val
|
||||
: settings->pwr_table.off_val;
|
||||
err = st_lsm6dsx_shub_write_with_mask(sensor,
|
||||
settings->pwr_table.reg.addr,
|
||||
settings->pwr_table.reg.mask, val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
return st_lsm6dsx_shub_master_enable(sensor, enable);
|
||||
}
|
||||
|
||||
static int
|
||||
st_lsm6dsx_shub_read_oneshot(struct st_lsm6dsx_sensor *sensor,
|
||||
struct iio_chan_spec const *ch,
|
||||
int *val)
|
||||
{
|
||||
int err, delay, len;
|
||||
u8 data[4];
|
||||
|
||||
err = st_lsm6dsx_shub_set_enable(sensor, true);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
delay = 1000000 / sensor->odr;
|
||||
usleep_range(delay, 2 * delay);
|
||||
|
||||
len = min_t(int, sizeof(data), ch->scan_type.realbits >> 3);
|
||||
err = st_lsm6dsx_shub_read(sensor, ch->address, data, len);
|
||||
|
||||
st_lsm6dsx_shub_set_enable(sensor, false);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
switch (len) {
|
||||
case 2:
|
||||
*val = (s16)le16_to_cpu(*((__le16 *)data));
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
static int
|
||||
st_lsm6dsx_shub_read_raw(struct iio_dev *iio_dev,
|
||||
struct iio_chan_spec const *ch,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = iio_device_claim_direct_mode(iio_dev);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = st_lsm6dsx_shub_read_oneshot(sensor, ch, val);
|
||||
iio_device_release_direct_mode(iio_dev);
|
||||
break;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*val = sensor->odr;
|
||||
ret = IIO_VAL_INT;
|
||||
break;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
*val2 = sensor->gain;
|
||||
ret = IIO_VAL_INT_PLUS_MICRO;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
st_lsm6dsx_shub_write_raw(struct iio_dev *iio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
|
||||
int err;
|
||||
|
||||
err = iio_device_claim_direct_mode(iio_dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ: {
|
||||
u16 data;
|
||||
|
||||
err = st_lsm6dsx_shub_get_odr_val(sensor, val, &data);
|
||||
if (!err)
|
||||
sensor->odr = val;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
iio_device_release_direct_mode(iio_dev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
st_lsm6dsx_shub_sampling_freq_avail(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
|
||||
const struct st_lsm6dsx_ext_dev_settings *settings;
|
||||
int i, len = 0;
|
||||
|
||||
settings = sensor->ext_info.settings;
|
||||
for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++) {
|
||||
u16 val = settings->odr_table.odr_avl[i].hz;
|
||||
|
||||
if (val > 0)
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len, "%d ",
|
||||
val);
|
||||
}
|
||||
buf[len - 1] = '\n';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t st_lsm6dsx_shub_scale_avail(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
|
||||
const struct st_lsm6dsx_ext_dev_settings *settings;
|
||||
int i, len = 0;
|
||||
|
||||
settings = sensor->ext_info.settings;
|
||||
for (i = 0; i < ST_LSM6DSX_FS_LIST_SIZE; i++) {
|
||||
u16 val = settings->fs_table.fs_avl[i].gain;
|
||||
|
||||
if (val > 0)
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ",
|
||||
val);
|
||||
}
|
||||
buf[len - 1] = '\n';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(st_lsm6dsx_shub_sampling_freq_avail);
|
||||
static IIO_DEVICE_ATTR(in_scale_available, 0444,
|
||||
st_lsm6dsx_shub_scale_avail, NULL, 0);
|
||||
static struct attribute *st_lsm6dsx_ext_attributes[] = {
|
||||
&iio_dev_attr_sampling_frequency_available.dev_attr.attr,
|
||||
&iio_dev_attr_in_scale_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group st_lsm6dsx_ext_attribute_group = {
|
||||
.attrs = st_lsm6dsx_ext_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_info st_lsm6dsx_ext_info = {
|
||||
.attrs = &st_lsm6dsx_ext_attribute_group,
|
||||
.read_raw = st_lsm6dsx_shub_read_raw,
|
||||
.write_raw = st_lsm6dsx_shub_write_raw,
|
||||
.hwfifo_set_watermark = st_lsm6dsx_set_watermark,
|
||||
};
|
||||
|
||||
static struct iio_dev *
|
||||
st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw,
|
||||
enum st_lsm6dsx_sensor_id id,
|
||||
const struct st_lsm6dsx_ext_dev_settings *info,
|
||||
u8 i2c_addr, const char *name)
|
||||
{
|
||||
struct iio_chan_spec *ext_channels;
|
||||
struct st_lsm6dsx_sensor *sensor;
|
||||
struct iio_dev *iio_dev;
|
||||
|
||||
iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor));
|
||||
if (!iio_dev)
|
||||
return NULL;
|
||||
|
||||
iio_dev->modes = INDIO_DIRECT_MODE;
|
||||
iio_dev->dev.parent = hw->dev;
|
||||
iio_dev->info = &st_lsm6dsx_ext_info;
|
||||
|
||||
sensor = iio_priv(iio_dev);
|
||||
sensor->id = id;
|
||||
sensor->hw = hw;
|
||||
sensor->odr = info->odr_table.odr_avl[0].hz;
|
||||
sensor->gain = info->fs_table.fs_avl[0].gain;
|
||||
sensor->ext_info.settings = info;
|
||||
sensor->ext_info.addr = i2c_addr;
|
||||
sensor->watermark = 1;
|
||||
|
||||
switch (info->id) {
|
||||
case ST_LSM6DSX_ID_MAGN: {
|
||||
const struct iio_chan_spec magn_channels[] = {
|
||||
ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr,
|
||||
IIO_MOD_X, 0),
|
||||
ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr + 2,
|
||||
IIO_MOD_Y, 1),
|
||||
ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr + 4,
|
||||
IIO_MOD_Z, 2),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
};
|
||||
|
||||
ext_channels = devm_kzalloc(hw->dev, sizeof(magn_channels),
|
||||
GFP_KERNEL);
|
||||
if (!ext_channels)
|
||||
return NULL;
|
||||
|
||||
memcpy(ext_channels, magn_channels, sizeof(magn_channels));
|
||||
iio_dev->available_scan_masks = st_lsm6dsx_available_scan_masks;
|
||||
iio_dev->channels = ext_channels;
|
||||
iio_dev->num_channels = ARRAY_SIZE(magn_channels);
|
||||
|
||||
scnprintf(sensor->name, sizeof(sensor->name), "%s_magn",
|
||||
name);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
iio_dev->name = sensor->name;
|
||||
|
||||
return iio_dev;
|
||||
}
|
||||
|
||||
static int st_lsm6dsx_shub_init_device(struct st_lsm6dsx_sensor *sensor)
|
||||
{
|
||||
const struct st_lsm6dsx_ext_dev_settings *settings;
|
||||
int err;
|
||||
|
||||
settings = sensor->ext_info.settings;
|
||||
if (settings->bdu.addr) {
|
||||
err = st_lsm6dsx_shub_write_with_mask(sensor,
|
||||
settings->bdu.addr,
|
||||
settings->bdu.mask, 1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (settings->temp_comp.addr) {
|
||||
err = st_lsm6dsx_shub_write_with_mask(sensor,
|
||||
settings->temp_comp.addr,
|
||||
settings->temp_comp.mask, 1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (settings->off_canc.addr) {
|
||||
err = st_lsm6dsx_shub_write_with_mask(sensor,
|
||||
settings->off_canc.addr,
|
||||
settings->off_canc.mask, 1);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
st_lsm6dsx_shub_check_wai(struct st_lsm6dsx_hw *hw, u8 *i2c_addr,
|
||||
const struct st_lsm6dsx_ext_dev_settings *settings)
|
||||
{
|
||||
const struct st_lsm6dsx_shub_settings *hub_settings;
|
||||
struct st_lsm6dsx_sensor *sensor;
|
||||
u8 config[3], data, slv_addr;
|
||||
bool found = false;
|
||||
int i, err;
|
||||
|
||||
hub_settings = &hw->settings->shub_settings;
|
||||
slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
|
||||
sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(settings->i2c_addr); i++) {
|
||||
if (!settings->i2c_addr[i])
|
||||
continue;
|
||||
|
||||
/* read wai slave register */
|
||||
config[0] = (settings->i2c_addr[i] << 1) | 0x1;
|
||||
config[1] = settings->wai.addr;
|
||||
config[2] = 0x1;
|
||||
|
||||
err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
||||
sizeof(config));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = st_lsm6dsx_shub_master_enable(sensor, true);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
st_lsm6dsx_shub_wait_complete(hw);
|
||||
|
||||
err = st_lsm6dsx_shub_read_reg(hw,
|
||||
hub_settings->shub_out,
|
||||
&data, sizeof(data));
|
||||
|
||||
st_lsm6dsx_shub_master_enable(sensor, false);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (data != settings->wai.val)
|
||||
continue;
|
||||
|
||||
*i2c_addr = settings->i2c_addr[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* reset SLV0 channel */
|
||||
memset(config, 0, sizeof(config));
|
||||
err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
|
||||
sizeof(config));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return found ? 0 : -ENODEV;
|
||||
}
|
||||
|
||||
int st_lsm6dsx_shub_probe(struct st_lsm6dsx_hw *hw, const char *name)
|
||||
{
|
||||
enum st_lsm6dsx_sensor_id id = ST_LSM6DSX_ID_EXT0;
|
||||
struct st_lsm6dsx_sensor *sensor;
|
||||
int err, i, num_ext_dev = 0;
|
||||
u8 i2c_addr = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(st_lsm6dsx_ext_dev_table); i++) {
|
||||
err = st_lsm6dsx_shub_check_wai(hw, &i2c_addr,
|
||||
&st_lsm6dsx_ext_dev_table[i]);
|
||||
if (err == -ENODEV)
|
||||
continue;
|
||||
else if (err < 0)
|
||||
return err;
|
||||
|
||||
hw->iio_devs[id] = st_lsm6dsx_shub_alloc_iiodev(hw, id,
|
||||
&st_lsm6dsx_ext_dev_table[i],
|
||||
i2c_addr, name);
|
||||
if (!hw->iio_devs[id])
|
||||
return -ENOMEM;
|
||||
|
||||
sensor = iio_priv(hw->iio_devs[id]);
|
||||
err = st_lsm6dsx_shub_init_device(sensor);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (++num_ext_dev >= ST_LSM6DSX_MAX_SLV_NUM)
|
||||
break;
|
||||
id++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1671,6 +1671,9 @@ int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (!indio_dev->info)
|
||||
return -EINVAL;
|
||||
|
||||
/* configure elements for the chrdev */
|
||||
indio_dev->dev.devt = MKDEV(MAJOR(iio_devt), indio_dev->id);
|
||||
|
||||
|
@ -460,6 +460,19 @@ config VCNL4000
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called vcnl4000.
|
||||
|
||||
config VCNL4035
|
||||
tristate "VCNL4035 combined ALS and proximity sensor"
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
select REGMAP_I2C
|
||||
depends on I2C
|
||||
help
|
||||
Say Y here if you want to build a driver for the Vishay VCNL4035,
|
||||
combined ambient light (ALS) and proximity sensor. Currently only ALS
|
||||
function is available.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called vcnl4035.
|
||||
|
||||
config VEML6070
|
||||
tristate "VEML6070 UV A light sensor"
|
||||
depends on I2C
|
||||
|
@ -45,6 +45,7 @@ obj-$(CONFIG_TSL2772) += tsl2772.o
|
||||
obj-$(CONFIG_TSL4531) += tsl4531.o
|
||||
obj-$(CONFIG_US5182D) += us5182d.o
|
||||
obj-$(CONFIG_VCNL4000) += vcnl4000.o
|
||||
obj-$(CONFIG_VCNL4035) += vcnl4035.o
|
||||
obj-$(CONFIG_VEML6070) += veml6070.o
|
||||
obj-$(CONFIG_VL6180) += vl6180.o
|
||||
obj-$(CONFIG_ZOPT2201) += zopt2201.o
|
||||
|
676
drivers/iio/light/vcnl4035.c
Normal file
676
drivers/iio/light/vcnl4035.c
Normal file
@ -0,0 +1,676 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* VCNL4035 Ambient Light and Proximity Sensor - 7-bit I2C slave address 0x60
|
||||
*
|
||||
* Copyright (c) 2018, DENX Software Engineering GmbH
|
||||
* Author: Parthiban Nallathambi <pn@denx.de>
|
||||
*
|
||||
* TODO: Proximity
|
||||
*/
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/events.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
#define VCNL4035_DRV_NAME "vcnl4035"
|
||||
#define VCNL4035_IRQ_NAME "vcnl4035_event"
|
||||
#define VCNL4035_REGMAP_NAME "vcnl4035_regmap"
|
||||
|
||||
/* Device registers */
|
||||
#define VCNL4035_ALS_CONF 0x00
|
||||
#define VCNL4035_ALS_THDH 0x01
|
||||
#define VCNL4035_ALS_THDL 0x02
|
||||
#define VCNL4035_ALS_DATA 0x0B
|
||||
#define VCNL4035_WHITE_DATA 0x0C
|
||||
#define VCNL4035_INT_FLAG 0x0D
|
||||
#define VCNL4035_DEV_ID 0x0E
|
||||
|
||||
/* Register masks */
|
||||
#define VCNL4035_MODE_ALS_MASK BIT(0)
|
||||
#define VCNL4035_MODE_ALS_WHITE_CHAN BIT(8)
|
||||
#define VCNL4035_MODE_ALS_INT_MASK BIT(1)
|
||||
#define VCNL4035_ALS_IT_MASK GENMASK(7, 5)
|
||||
#define VCNL4035_ALS_PERS_MASK GENMASK(3, 2)
|
||||
#define VCNL4035_INT_ALS_IF_H_MASK BIT(12)
|
||||
#define VCNL4035_INT_ALS_IF_L_MASK BIT(13)
|
||||
|
||||
/* Default values */
|
||||
#define VCNL4035_MODE_ALS_ENABLE BIT(0)
|
||||
#define VCNL4035_MODE_ALS_DISABLE 0x00
|
||||
#define VCNL4035_MODE_ALS_INT_ENABLE BIT(1)
|
||||
#define VCNL4035_MODE_ALS_INT_DISABLE 0
|
||||
#define VCNL4035_DEV_ID_VAL 0x80
|
||||
#define VCNL4035_ALS_IT_DEFAULT 0x01
|
||||
#define VCNL4035_ALS_PERS_DEFAULT 0x00
|
||||
#define VCNL4035_ALS_THDH_DEFAULT 5000
|
||||
#define VCNL4035_ALS_THDL_DEFAULT 100
|
||||
#define VCNL4035_SLEEP_DELAY_MS 2000
|
||||
|
||||
struct vcnl4035_data {
|
||||
struct i2c_client *client;
|
||||
struct regmap *regmap;
|
||||
unsigned int als_it_val;
|
||||
unsigned int als_persistence;
|
||||
unsigned int als_thresh_low;
|
||||
unsigned int als_thresh_high;
|
||||
struct iio_trigger *drdy_trigger0;
|
||||
};
|
||||
|
||||
static inline bool vcnl4035_is_triggered(struct vcnl4035_data *data)
|
||||
{
|
||||
int ret;
|
||||
int reg;
|
||||
|
||||
ret = regmap_read(data->regmap, VCNL4035_INT_FLAG, ®);
|
||||
if (ret < 0)
|
||||
return false;
|
||||
|
||||
return !!(reg &
|
||||
(VCNL4035_INT_ALS_IF_H_MASK | VCNL4035_INT_ALS_IF_L_MASK));
|
||||
}
|
||||
|
||||
static irqreturn_t vcnl4035_drdy_irq_thread(int irq, void *private)
|
||||
{
|
||||
struct iio_dev *indio_dev = private;
|
||||
struct vcnl4035_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (vcnl4035_is_triggered(data)) {
|
||||
iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
|
||||
0,
|
||||
IIO_EV_TYPE_THRESH,
|
||||
IIO_EV_DIR_EITHER),
|
||||
iio_get_time_ns(indio_dev));
|
||||
iio_trigger_poll_chained(data->drdy_trigger0);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
/* Triggered buffer */
|
||||
static irqreturn_t vcnl4035_trigger_consumer_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct vcnl4035_data *data = iio_priv(indio_dev);
|
||||
u8 buffer[ALIGN(sizeof(u16), sizeof(s64)) + sizeof(s64)];
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(data->regmap, VCNL4035_ALS_DATA, (int *)buffer);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev,
|
||||
"Trigger consumer can't read from sensor.\n");
|
||||
goto fail_read;
|
||||
}
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, buffer,
|
||||
iio_get_time_ns(indio_dev));
|
||||
|
||||
fail_read:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int vcnl4035_als_drdy_set_state(struct iio_trigger *trigger,
|
||||
bool enable_drdy)
|
||||
{
|
||||
struct iio_dev *indio_dev = iio_trigger_get_drvdata(trigger);
|
||||
struct vcnl4035_data *data = iio_priv(indio_dev);
|
||||
int val = enable_drdy ? VCNL4035_MODE_ALS_INT_ENABLE :
|
||||
VCNL4035_MODE_ALS_INT_DISABLE;
|
||||
|
||||
return regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
|
||||
VCNL4035_MODE_ALS_INT_MASK,
|
||||
val);
|
||||
}
|
||||
|
||||
static const struct iio_trigger_ops vcnl4035_trigger_ops = {
|
||||
.validate_device = iio_trigger_validate_own_device,
|
||||
.set_trigger_state = vcnl4035_als_drdy_set_state,
|
||||
};
|
||||
|
||||
static int vcnl4035_set_pm_runtime_state(struct vcnl4035_data *data, bool on)
|
||||
{
|
||||
int ret;
|
||||
struct device *dev = &data->client->dev;
|
||||
|
||||
if (on) {
|
||||
ret = pm_runtime_get_sync(dev);
|
||||
if (ret < 0)
|
||||
pm_runtime_put_noidle(dev);
|
||||
} else {
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
ret = pm_runtime_put_autosuspend(dev);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Device IT INT Time (ms) Scale (lux/step)
|
||||
* 000 50 0.064
|
||||
* 001 100 0.032
|
||||
* 010 200 0.016
|
||||
* 100 400 0.008
|
||||
* 101 - 111 800 0.004
|
||||
* Values are proportional, so ALS INT is selected for input due to
|
||||
* simplicity reason. Integration time value and scaling is
|
||||
* calculated based on device INT value
|
||||
*
|
||||
* Raw value needs to be scaled using ALS steps
|
||||
*/
|
||||
static int vcnl4035_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val,
|
||||
int *val2, long mask)
|
||||
{
|
||||
struct vcnl4035_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
int raw_data;
|
||||
unsigned int reg;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = vcnl4035_set_pm_runtime_state(data, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (!ret) {
|
||||
if (chan->channel)
|
||||
reg = VCNL4035_ALS_DATA;
|
||||
else
|
||||
reg = VCNL4035_WHITE_DATA;
|
||||
ret = regmap_read(data->regmap, reg, &raw_data);
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
if (!ret) {
|
||||
*val = raw_data;
|
||||
ret = IIO_VAL_INT;
|
||||
}
|
||||
}
|
||||
vcnl4035_set_pm_runtime_state(data, false);
|
||||
return ret;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
*val = 50;
|
||||
if (data->als_it_val)
|
||||
*val = data->als_it_val * 100;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 64;
|
||||
if (!data->als_it_val)
|
||||
*val2 = 1000;
|
||||
else
|
||||
*val2 = data->als_it_val * 2 * 1000;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int vcnl4035_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
struct vcnl4035_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
if (val <= 0 || val > 800)
|
||||
return -EINVAL;
|
||||
|
||||
ret = vcnl4035_set_pm_runtime_state(data, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
|
||||
VCNL4035_ALS_IT_MASK,
|
||||
val / 100);
|
||||
if (!ret)
|
||||
data->als_it_val = val / 100;
|
||||
|
||||
vcnl4035_set_pm_runtime_state(data, false);
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* No direct ABI for persistence and threshold, so eventing */
|
||||
static int vcnl4035_read_thresh(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, enum iio_event_type type,
|
||||
enum iio_event_direction dir, enum iio_event_info info,
|
||||
int *val, int *val2)
|
||||
{
|
||||
struct vcnl4035_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (info) {
|
||||
case IIO_EV_INFO_VALUE:
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_RISING:
|
||||
*val = data->als_thresh_high;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_EV_DIR_FALLING:
|
||||
*val = data->als_thresh_low;
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case IIO_EV_INFO_PERIOD:
|
||||
*val = data->als_persistence;
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static int vcnl4035_write_thresh(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, enum iio_event_type type,
|
||||
enum iio_event_direction dir, enum iio_event_info info, int val,
|
||||
int val2)
|
||||
{
|
||||
struct vcnl4035_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (info) {
|
||||
case IIO_EV_INFO_VALUE:
|
||||
/* 16 bit threshold range 0 - 65535 */
|
||||
if (val < 0 || val > 65535)
|
||||
return -EINVAL;
|
||||
if (dir == IIO_EV_DIR_RISING) {
|
||||
if (val < data->als_thresh_low)
|
||||
return -EINVAL;
|
||||
ret = regmap_write(data->regmap, VCNL4035_ALS_THDH,
|
||||
val);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->als_thresh_high = val;
|
||||
} else {
|
||||
if (val > data->als_thresh_high)
|
||||
return -EINVAL;
|
||||
ret = regmap_write(data->regmap, VCNL4035_ALS_THDL,
|
||||
val);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->als_thresh_low = val;
|
||||
}
|
||||
return ret;
|
||||
case IIO_EV_INFO_PERIOD:
|
||||
/* allow only 1 2 4 8 as persistence value */
|
||||
if (val < 0 || val > 8 || hweight8(val) != 1)
|
||||
return -EINVAL;
|
||||
ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
|
||||
VCNL4035_ALS_PERS_MASK, val);
|
||||
if (!ret)
|
||||
data->als_persistence = val;
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static IIO_CONST_ATTR_INT_TIME_AVAIL("50 100 200 400 800");
|
||||
|
||||
static struct attribute *vcnl4035_attributes[] = {
|
||||
&iio_const_attr_integration_time_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group vcnl4035_attribute_group = {
|
||||
.attrs = vcnl4035_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_info vcnl4035_info = {
|
||||
.read_raw = vcnl4035_read_raw,
|
||||
.write_raw = vcnl4035_write_raw,
|
||||
.read_event_value = vcnl4035_read_thresh,
|
||||
.write_event_value = vcnl4035_write_thresh,
|
||||
.attrs = &vcnl4035_attribute_group,
|
||||
};
|
||||
|
||||
static const struct iio_event_spec vcnl4035_event_spec[] = {
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_RISING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE),
|
||||
}, {
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_FALLING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE),
|
||||
}, {
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_EITHER,
|
||||
.mask_separate = BIT(IIO_EV_INFO_PERIOD),
|
||||
},
|
||||
};
|
||||
|
||||
enum vcnl4035_scan_index_order {
|
||||
VCNL4035_CHAN_INDEX_LIGHT,
|
||||
VCNL4035_CHAN_INDEX_WHITE_LED,
|
||||
};
|
||||
|
||||
static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = {
|
||||
.validate_scan_mask = &iio_validate_scan_mask_onehot,
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec vcnl4035_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.channel = 0,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_INT_TIME) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
.event_spec = vcnl4035_event_spec,
|
||||
.num_event_specs = ARRAY_SIZE(vcnl4035_event_spec),
|
||||
.scan_index = VCNL4035_CHAN_INDEX_LIGHT,
|
||||
.scan_type = {
|
||||
.sign = 'u',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_LE,
|
||||
},
|
||||
},
|
||||
{
|
||||
.type = IIO_INTENSITY,
|
||||
.channel = 1,
|
||||
.modified = 1,
|
||||
.channel2 = IIO_MOD_LIGHT_BOTH,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.scan_index = VCNL4035_CHAN_INDEX_WHITE_LED,
|
||||
.scan_type = {
|
||||
.sign = 'u',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_LE,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static int vcnl4035_set_als_power_state(struct vcnl4035_data *data, u8 status)
|
||||
{
|
||||
return regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
|
||||
VCNL4035_MODE_ALS_MASK,
|
||||
status);
|
||||
}
|
||||
|
||||
static int vcnl4035_init(struct vcnl4035_data *data)
|
||||
{
|
||||
int ret;
|
||||
int id;
|
||||
|
||||
ret = regmap_read(data->regmap, VCNL4035_DEV_ID, &id);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev, "Failed to read DEV_ID register\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (id != VCNL4035_DEV_ID_VAL) {
|
||||
dev_err(&data->client->dev, "Wrong id, got %x, expected %x\n",
|
||||
id, VCNL4035_DEV_ID_VAL);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_ENABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* ALS white channel enable */
|
||||
ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
|
||||
VCNL4035_MODE_ALS_WHITE_CHAN,
|
||||
1);
|
||||
if (ret) {
|
||||
dev_err(&data->client->dev, "set white channel enable %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set default integration time - 100 ms for ALS */
|
||||
ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
|
||||
VCNL4035_ALS_IT_MASK,
|
||||
VCNL4035_ALS_IT_DEFAULT);
|
||||
if (ret) {
|
||||
dev_err(&data->client->dev, "set default ALS IT returned %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
data->als_it_val = VCNL4035_ALS_IT_DEFAULT;
|
||||
|
||||
/* set default persistence time - 1 for ALS */
|
||||
ret = regmap_update_bits(data->regmap, VCNL4035_ALS_CONF,
|
||||
VCNL4035_ALS_PERS_MASK,
|
||||
VCNL4035_ALS_PERS_DEFAULT);
|
||||
if (ret) {
|
||||
dev_err(&data->client->dev, "set default PERS returned %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
data->als_persistence = VCNL4035_ALS_PERS_DEFAULT;
|
||||
|
||||
/* set default HIGH threshold for ALS */
|
||||
ret = regmap_write(data->regmap, VCNL4035_ALS_THDH,
|
||||
VCNL4035_ALS_THDH_DEFAULT);
|
||||
if (ret) {
|
||||
dev_err(&data->client->dev, "set default THDH returned %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
data->als_thresh_high = VCNL4035_ALS_THDH_DEFAULT;
|
||||
|
||||
/* set default LOW threshold for ALS */
|
||||
ret = regmap_write(data->regmap, VCNL4035_ALS_THDL,
|
||||
VCNL4035_ALS_THDL_DEFAULT);
|
||||
if (ret) {
|
||||
dev_err(&data->client->dev, "set default THDL returned %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
data->als_thresh_low = VCNL4035_ALS_THDL_DEFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool vcnl4035_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case VCNL4035_ALS_CONF:
|
||||
case VCNL4035_DEV_ID:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct regmap_config vcnl4035_regmap_config = {
|
||||
.name = VCNL4035_REGMAP_NAME,
|
||||
.reg_bits = 8,
|
||||
.val_bits = 16,
|
||||
.max_register = VCNL4035_DEV_ID,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.volatile_reg = vcnl4035_is_volatile_reg,
|
||||
.val_format_endian = REGMAP_ENDIAN_LITTLE,
|
||||
};
|
||||
|
||||
static int vcnl4035_probe_trigger(struct iio_dev *indio_dev)
|
||||
{
|
||||
int ret;
|
||||
struct vcnl4035_data *data = iio_priv(indio_dev);
|
||||
|
||||
data->drdy_trigger0 = devm_iio_trigger_alloc(
|
||||
indio_dev->dev.parent,
|
||||
"%s-dev%d", indio_dev->name, indio_dev->id);
|
||||
if (!data->drdy_trigger0)
|
||||
return -ENOMEM;
|
||||
|
||||
data->drdy_trigger0->dev.parent = indio_dev->dev.parent;
|
||||
data->drdy_trigger0->ops = &vcnl4035_trigger_ops;
|
||||
iio_trigger_set_drvdata(data->drdy_trigger0, indio_dev);
|
||||
ret = devm_iio_trigger_register(indio_dev->dev.parent,
|
||||
data->drdy_trigger0);
|
||||
if (ret) {
|
||||
dev_err(&data->client->dev, "iio trigger register failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Trigger setup */
|
||||
ret = devm_iio_triggered_buffer_setup(indio_dev->dev.parent, indio_dev,
|
||||
NULL, vcnl4035_trigger_consumer_handler,
|
||||
&iio_triggered_buffer_setup_ops);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev, "iio triggered buffer setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* IRQ to trigger mapping */
|
||||
ret = devm_request_threaded_irq(&data->client->dev, data->client->irq,
|
||||
NULL, vcnl4035_drdy_irq_thread,
|
||||
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
||||
VCNL4035_IRQ_NAME, indio_dev);
|
||||
if (ret < 0)
|
||||
dev_err(&data->client->dev, "request irq %d for trigger0 failed\n",
|
||||
data->client->irq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int vcnl4035_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct vcnl4035_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &vcnl4035_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&client->dev, "regmap_init failed!\n");
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
data->regmap = regmap;
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->info = &vcnl4035_info;
|
||||
indio_dev->name = VCNL4035_DRV_NAME;
|
||||
indio_dev->channels = vcnl4035_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(vcnl4035_channels);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = vcnl4035_init(data);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "vcnl4035 chip init failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (client->irq > 0) {
|
||||
ret = vcnl4035_probe_trigger(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "vcnl4035 unable init trigger\n");
|
||||
goto fail_poweroff;
|
||||
}
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret < 0)
|
||||
goto fail_poweroff;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
goto fail_poweroff;
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev, VCNL4035_SLEEP_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_poweroff:
|
||||
vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_DISABLE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int vcnl4035_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
pm_runtime_dont_use_autosuspend(&client->dev);
|
||||
pm_runtime_disable(&client->dev);
|
||||
iio_device_unregister(indio_dev);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
|
||||
return vcnl4035_set_als_power_state(iio_priv(indio_dev),
|
||||
VCNL4035_MODE_ALS_DISABLE);
|
||||
}
|
||||
|
||||
static int __maybe_unused vcnl4035_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct vcnl4035_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_DISABLE);
|
||||
regcache_mark_dirty(data->regmap);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __maybe_unused vcnl4035_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct vcnl4035_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
regcache_sync(data->regmap);
|
||||
ret = vcnl4035_set_als_power_state(data, VCNL4035_MODE_ALS_ENABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* wait for 1 ALS integration cycle */
|
||||
msleep(data->als_it_val * 100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops vcnl4035_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume)
|
||||
SET_RUNTIME_PM_OPS(vcnl4035_runtime_suspend,
|
||||
vcnl4035_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct of_device_id vcnl4035_of_match[] = {
|
||||
{ .compatible = "vishay,vcnl4035", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, vcnl4035_of_match);
|
||||
|
||||
static struct i2c_driver vcnl4035_driver = {
|
||||
.driver = {
|
||||
.name = VCNL4035_DRV_NAME,
|
||||
.pm = &vcnl4035_pm_ops,
|
||||
.of_match_table = vcnl4035_of_match,
|
||||
},
|
||||
.probe = vcnl4035_probe,
|
||||
.remove = vcnl4035_remove,
|
||||
};
|
||||
|
||||
module_i2c_driver(vcnl4035_driver);
|
||||
|
||||
MODULE_AUTHOR("Parthiban Nallathambi <pn@denx.de>");
|
||||
MODULE_DESCRIPTION("VCNL4035 Ambient Light Sensor driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -175,4 +175,33 @@ config SENSORS_HMC5843_SPI
|
||||
- hmc5843_core (core functions)
|
||||
- hmc5843_spi (support for HMC5983)
|
||||
|
||||
config SENSORS_RM3100
|
||||
tristate
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
|
||||
config SENSORS_RM3100_I2C
|
||||
tristate "PNI RM3100 3-Axis Magnetometer (I2C)"
|
||||
depends on I2C
|
||||
select SENSORS_RM3100
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y here to add support for the PNI RM3100 3-Axis Magnetometer.
|
||||
|
||||
This driver can also be compiled as a module.
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called rm3100-i2c.
|
||||
|
||||
config SENSORS_RM3100_SPI
|
||||
tristate "PNI RM3100 3-Axis Magnetometer (SPI)"
|
||||
depends on SPI_MASTER
|
||||
select SENSORS_RM3100
|
||||
select REGMAP_SPI
|
||||
help
|
||||
Say Y here to add support for the PNI RM3100 3-Axis Magnetometer.
|
||||
|
||||
This driver can also be compiled as a module.
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called rm3100-spi.
|
||||
|
||||
endmenu
|
||||
|
@ -24,3 +24,7 @@ obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o
|
||||
obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o
|
||||
obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o
|
||||
obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o
|
||||
|
||||
obj-$(CONFIG_SENSORS_RM3100) += rm3100-core.o
|
||||
obj-$(CONFIG_SENSORS_RM3100_I2C) += rm3100-i2c.o
|
||||
obj-$(CONFIG_SENSORS_RM3100_SPI) += rm3100-spi.o
|
||||
|
@ -790,6 +790,7 @@ static const struct acpi_device_id ak_acpi_match[] = {
|
||||
{"INVN6500", AK8963},
|
||||
{"AK009911", AK09911},
|
||||
{"AK09911", AK09911},
|
||||
{"AKM9911", AK09911},
|
||||
{"AK09912", AK09912},
|
||||
{ },
|
||||
};
|
||||
|
616
drivers/iio/magnetometer/rm3100-core.c
Normal file
616
drivers/iio/magnetometer/rm3100-core.c
Normal file
@ -0,0 +1,616 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* PNI RM3100 3-axis geomagnetic sensor driver core.
|
||||
*
|
||||
* Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
|
||||
*
|
||||
* User Manual available at
|
||||
* <https://www.pnicorp.com/download/rm3100-user-manual/>
|
||||
*
|
||||
* TODO: event generation, pm.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
|
||||
#include "rm3100.h"
|
||||
|
||||
/* Cycle Count Registers. */
|
||||
#define RM3100_REG_CC_X 0x05
|
||||
#define RM3100_REG_CC_Y 0x07
|
||||
#define RM3100_REG_CC_Z 0x09
|
||||
|
||||
/* Poll Measurement Mode register. */
|
||||
#define RM3100_REG_POLL 0x00
|
||||
#define RM3100_POLL_X BIT(4)
|
||||
#define RM3100_POLL_Y BIT(5)
|
||||
#define RM3100_POLL_Z BIT(6)
|
||||
|
||||
/* Continuous Measurement Mode register. */
|
||||
#define RM3100_REG_CMM 0x01
|
||||
#define RM3100_CMM_START BIT(0)
|
||||
#define RM3100_CMM_X BIT(4)
|
||||
#define RM3100_CMM_Y BIT(5)
|
||||
#define RM3100_CMM_Z BIT(6)
|
||||
|
||||
/* TiMe Rate Configuration register. */
|
||||
#define RM3100_REG_TMRC 0x0B
|
||||
#define RM3100_TMRC_OFFSET 0x92
|
||||
|
||||
/* Result Status register. */
|
||||
#define RM3100_REG_STATUS 0x34
|
||||
#define RM3100_STATUS_DRDY BIT(7)
|
||||
|
||||
/* Measurement result registers. */
|
||||
#define RM3100_REG_MX2 0x24
|
||||
#define RM3100_REG_MY2 0x27
|
||||
#define RM3100_REG_MZ2 0x2a
|
||||
|
||||
#define RM3100_W_REG_START RM3100_REG_POLL
|
||||
#define RM3100_W_REG_END RM3100_REG_TMRC
|
||||
#define RM3100_R_REG_START RM3100_REG_POLL
|
||||
#define RM3100_R_REG_END RM3100_REG_STATUS
|
||||
#define RM3100_V_REG_START RM3100_REG_POLL
|
||||
#define RM3100_V_REG_END RM3100_REG_STATUS
|
||||
|
||||
/*
|
||||
* This is computed by hand, is the sum of channel storage bits and padding
|
||||
* bits, which is 4+4+4+12=24 in here.
|
||||
*/
|
||||
#define RM3100_SCAN_BYTES 24
|
||||
|
||||
#define RM3100_CMM_AXIS_SHIFT 4
|
||||
|
||||
struct rm3100_data {
|
||||
struct regmap *regmap;
|
||||
struct completion measuring_done;
|
||||
bool use_interrupt;
|
||||
int conversion_time;
|
||||
int scale;
|
||||
u8 buffer[RM3100_SCAN_BYTES];
|
||||
struct iio_trigger *drdy_trig;
|
||||
|
||||
/*
|
||||
* This lock is for protecting the consistency of series of i2c
|
||||
* operations, that is, to make sure a measurement process will
|
||||
* not be interrupted by a set frequency operation, which should
|
||||
* be taken where a series of i2c operation starts, released where
|
||||
* the operation ends.
|
||||
*/
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
static const struct regmap_range rm3100_readable_ranges[] = {
|
||||
regmap_reg_range(RM3100_R_REG_START, RM3100_R_REG_END),
|
||||
};
|
||||
|
||||
const struct regmap_access_table rm3100_readable_table = {
|
||||
.yes_ranges = rm3100_readable_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(rm3100_readable_ranges),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(rm3100_readable_table);
|
||||
|
||||
static const struct regmap_range rm3100_writable_ranges[] = {
|
||||
regmap_reg_range(RM3100_W_REG_START, RM3100_W_REG_END),
|
||||
};
|
||||
|
||||
const struct regmap_access_table rm3100_writable_table = {
|
||||
.yes_ranges = rm3100_writable_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(rm3100_writable_ranges),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(rm3100_writable_table);
|
||||
|
||||
static const struct regmap_range rm3100_volatile_ranges[] = {
|
||||
regmap_reg_range(RM3100_V_REG_START, RM3100_V_REG_END),
|
||||
};
|
||||
|
||||
const struct regmap_access_table rm3100_volatile_table = {
|
||||
.yes_ranges = rm3100_volatile_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(rm3100_volatile_ranges),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(rm3100_volatile_table);
|
||||
|
||||
static irqreturn_t rm3100_thread_fn(int irq, void *d)
|
||||
{
|
||||
struct iio_dev *indio_dev = d;
|
||||
struct rm3100_data *data = iio_priv(indio_dev);
|
||||
|
||||
/*
|
||||
* Write operation to any register or read operation
|
||||
* to first byte of results will clear the interrupt.
|
||||
*/
|
||||
regmap_write(data->regmap, RM3100_REG_POLL, 0);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t rm3100_irq_handler(int irq, void *d)
|
||||
{
|
||||
struct iio_dev *indio_dev = d;
|
||||
struct rm3100_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (indio_dev->currentmode) {
|
||||
case INDIO_DIRECT_MODE:
|
||||
complete(&data->measuring_done);
|
||||
break;
|
||||
case INDIO_BUFFER_TRIGGERED:
|
||||
iio_trigger_poll(data->drdy_trig);
|
||||
break;
|
||||
default:
|
||||
dev_err(indio_dev->dev.parent,
|
||||
"device mode out of control, current mode: %d",
|
||||
indio_dev->currentmode);
|
||||
}
|
||||
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
static int rm3100_wait_measurement(struct rm3100_data *data)
|
||||
{
|
||||
struct regmap *regmap = data->regmap;
|
||||
unsigned int val;
|
||||
int tries = 20;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* A read cycle of 400kbits i2c bus is about 20us, plus the time
|
||||
* used for scheduling, a read cycle of fast mode of this device
|
||||
* can reach 1.7ms, it may be possible for data to arrive just
|
||||
* after we check the RM3100_REG_STATUS. In this case, irq_handler is
|
||||
* called before measuring_done is reinitialized, it will wait
|
||||
* forever for data that has already been ready.
|
||||
* Reinitialize measuring_done before looking up makes sure we
|
||||
* will always capture interrupt no matter when it happens.
|
||||
*/
|
||||
if (data->use_interrupt)
|
||||
reinit_completion(&data->measuring_done);
|
||||
|
||||
ret = regmap_read(regmap, RM3100_REG_STATUS, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if ((val & RM3100_STATUS_DRDY) != RM3100_STATUS_DRDY) {
|
||||
if (data->use_interrupt) {
|
||||
ret = wait_for_completion_timeout(&data->measuring_done,
|
||||
msecs_to_jiffies(data->conversion_time));
|
||||
if (!ret)
|
||||
return -ETIMEDOUT;
|
||||
} else {
|
||||
do {
|
||||
usleep_range(1000, 5000);
|
||||
|
||||
ret = regmap_read(regmap, RM3100_REG_STATUS,
|
||||
&val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (val & RM3100_STATUS_DRDY)
|
||||
break;
|
||||
} while (--tries);
|
||||
if (!tries)
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rm3100_read_mag(struct rm3100_data *data, int idx, int *val)
|
||||
{
|
||||
struct regmap *regmap = data->regmap;
|
||||
u8 buffer[3];
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
ret = regmap_write(regmap, RM3100_REG_POLL, BIT(4 + idx));
|
||||
if (ret < 0)
|
||||
goto unlock_return;
|
||||
|
||||
ret = rm3100_wait_measurement(data);
|
||||
if (ret < 0)
|
||||
goto unlock_return;
|
||||
|
||||
ret = regmap_bulk_read(regmap, RM3100_REG_MX2 + 3 * idx, buffer, 3);
|
||||
if (ret < 0)
|
||||
goto unlock_return;
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
*val = sign_extend32((buffer[0] << 16) | (buffer[1] << 8) | buffer[2],
|
||||
23);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
|
||||
unlock_return:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define RM3100_CHANNEL(axis, idx) \
|
||||
{ \
|
||||
.type = IIO_MAGN, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.scan_index = idx, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 24, \
|
||||
.storagebits = 32, \
|
||||
.shift = 8, \
|
||||
.endianness = IIO_BE, \
|
||||
}, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec rm3100_channels[] = {
|
||||
RM3100_CHANNEL(X, 0),
|
||||
RM3100_CHANNEL(Y, 1),
|
||||
RM3100_CHANNEL(Z, 2),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
};
|
||||
|
||||
static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(
|
||||
"600 300 150 75 37 18 9 4.5 2.3 1.2 0.6 0.3 0.015 0.075"
|
||||
);
|
||||
|
||||
static struct attribute *rm3100_attributes[] = {
|
||||
&iio_const_attr_sampling_frequency_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group rm3100_attribute_group = {
|
||||
.attrs = rm3100_attributes,
|
||||
};
|
||||
|
||||
#define RM3100_SAMP_NUM 14
|
||||
|
||||
/*
|
||||
* Frequency : rm3100_samp_rates[][0].rm3100_samp_rates[][1]Hz.
|
||||
* Time between reading: rm3100_sam_rates[][2]ms.
|
||||
* The first one is actually 1.7ms.
|
||||
*/
|
||||
static const int rm3100_samp_rates[RM3100_SAMP_NUM][3] = {
|
||||
{600, 0, 2}, {300, 0, 3}, {150, 0, 7}, {75, 0, 13}, {37, 0, 27},
|
||||
{18, 0, 55}, {9, 0, 110}, {4, 500000, 220}, {2, 300000, 440},
|
||||
{1, 200000, 800}, {0, 600000, 1600}, {0, 300000, 3300},
|
||||
{0, 15000, 6700}, {0, 75000, 13000}
|
||||
};
|
||||
|
||||
static int rm3100_get_samp_freq(struct rm3100_data *data, int *val, int *val2)
|
||||
{
|
||||
unsigned int tmp;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
ret = regmap_read(data->regmap, RM3100_REG_TMRC, &tmp);
|
||||
mutex_unlock(&data->lock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][0];
|
||||
*val2 = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][1];
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
|
||||
static int rm3100_set_cycle_count(struct rm3100_data *data, int val)
|
||||
{
|
||||
int ret;
|
||||
u8 i;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
ret = regmap_write(data->regmap, RM3100_REG_CC_X + 2 * i, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The scale of this sensor depends on the cycle count value, these
|
||||
* three values are corresponding to the cycle count value 50, 100,
|
||||
* 200. scale = output / gain * 10^4.
|
||||
*/
|
||||
switch (val) {
|
||||
case 50:
|
||||
data->scale = 500;
|
||||
break;
|
||||
case 100:
|
||||
data->scale = 263;
|
||||
break;
|
||||
/*
|
||||
* case 200:
|
||||
* This function will never be called by users' code, so here we
|
||||
* assume that it will never get a wrong parameter.
|
||||
*/
|
||||
default:
|
||||
data->scale = 133;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rm3100_set_samp_freq(struct iio_dev *indio_dev, int val, int val2)
|
||||
{
|
||||
struct rm3100_data *data = iio_priv(indio_dev);
|
||||
struct regmap *regmap = data->regmap;
|
||||
unsigned int cycle_count;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
/* All cycle count registers use the same value. */
|
||||
ret = regmap_read(regmap, RM3100_REG_CC_X, &cycle_count);
|
||||
if (ret < 0)
|
||||
goto unlock_return;
|
||||
|
||||
for (i = 0; i < RM3100_SAMP_NUM; i++) {
|
||||
if (val == rm3100_samp_rates[i][0] &&
|
||||
val2 == rm3100_samp_rates[i][1])
|
||||
break;
|
||||
}
|
||||
if (i == RM3100_SAMP_NUM) {
|
||||
ret = -EINVAL;
|
||||
goto unlock_return;
|
||||
}
|
||||
|
||||
ret = regmap_write(regmap, RM3100_REG_TMRC, i + RM3100_TMRC_OFFSET);
|
||||
if (ret < 0)
|
||||
goto unlock_return;
|
||||
|
||||
/* Checking if cycle count registers need changing. */
|
||||
if (val == 600 && cycle_count == 200) {
|
||||
ret = rm3100_set_cycle_count(data, 100);
|
||||
if (ret < 0)
|
||||
goto unlock_return;
|
||||
} else if (val != 600 && cycle_count == 100) {
|
||||
ret = rm3100_set_cycle_count(data, 200);
|
||||
if (ret < 0)
|
||||
goto unlock_return;
|
||||
}
|
||||
|
||||
if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) {
|
||||
/* Writing TMRC registers requires CMM reset. */
|
||||
ret = regmap_write(regmap, RM3100_REG_CMM, 0);
|
||||
if (ret < 0)
|
||||
goto unlock_return;
|
||||
ret = regmap_write(data->regmap, RM3100_REG_CMM,
|
||||
(*indio_dev->active_scan_mask & 0x7) <<
|
||||
RM3100_CMM_AXIS_SHIFT | RM3100_CMM_START);
|
||||
if (ret < 0)
|
||||
goto unlock_return;
|
||||
}
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
data->conversion_time = rm3100_samp_rates[i][2] * 2;
|
||||
return 0;
|
||||
|
||||
unlock_return:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rm3100_read_raw(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct rm3100_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = rm3100_read_mag(data, chan->scan_index, val);
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
|
||||
return ret;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
*val2 = data->scale;
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return rm3100_get_samp_freq(data, val, val2);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int rm3100_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return rm3100_set_samp_freq(indio_dev, val, val2);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info rm3100_info = {
|
||||
.attrs = &rm3100_attribute_group,
|
||||
.read_raw = rm3100_read_raw,
|
||||
.write_raw = rm3100_write_raw,
|
||||
};
|
||||
|
||||
static int rm3100_buffer_preenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct rm3100_data *data = iio_priv(indio_dev);
|
||||
|
||||
/* Starting channels enabled. */
|
||||
return regmap_write(data->regmap, RM3100_REG_CMM,
|
||||
(*indio_dev->active_scan_mask & 0x7) << RM3100_CMM_AXIS_SHIFT |
|
||||
RM3100_CMM_START);
|
||||
}
|
||||
|
||||
static int rm3100_buffer_postdisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct rm3100_data *data = iio_priv(indio_dev);
|
||||
|
||||
return regmap_write(data->regmap, RM3100_REG_CMM, 0);
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops rm3100_buffer_ops = {
|
||||
.preenable = rm3100_buffer_preenable,
|
||||
.postenable = iio_triggered_buffer_postenable,
|
||||
.predisable = iio_triggered_buffer_predisable,
|
||||
.postdisable = rm3100_buffer_postdisable,
|
||||
};
|
||||
|
||||
static irqreturn_t rm3100_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
unsigned long scan_mask = *indio_dev->active_scan_mask;
|
||||
unsigned int mask_len = indio_dev->masklength;
|
||||
struct rm3100_data *data = iio_priv(indio_dev);
|
||||
struct regmap *regmap = data->regmap;
|
||||
int ret, i, bit;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
switch (scan_mask) {
|
||||
case BIT(0) | BIT(1) | BIT(2):
|
||||
ret = regmap_bulk_read(regmap, RM3100_REG_MX2, data->buffer, 9);
|
||||
mutex_unlock(&data->lock);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
/* Convert XXXYYYZZZxxx to XXXxYYYxZZZx. x for paddings. */
|
||||
for (i = 2; i > 0; i--)
|
||||
memmove(data->buffer + i * 4, data->buffer + i * 3, 3);
|
||||
break;
|
||||
case BIT(0) | BIT(1):
|
||||
ret = regmap_bulk_read(regmap, RM3100_REG_MX2, data->buffer, 6);
|
||||
mutex_unlock(&data->lock);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
memmove(data->buffer + 4, data->buffer + 3, 3);
|
||||
break;
|
||||
case BIT(1) | BIT(2):
|
||||
ret = regmap_bulk_read(regmap, RM3100_REG_MY2, data->buffer, 6);
|
||||
mutex_unlock(&data->lock);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
memmove(data->buffer + 4, data->buffer + 3, 3);
|
||||
break;
|
||||
case BIT(0) | BIT(2):
|
||||
ret = regmap_bulk_read(regmap, RM3100_REG_MX2, data->buffer, 9);
|
||||
mutex_unlock(&data->lock);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
memmove(data->buffer + 4, data->buffer + 6, 3);
|
||||
break;
|
||||
default:
|
||||
for_each_set_bit(bit, &scan_mask, mask_len) {
|
||||
ret = regmap_bulk_read(regmap, RM3100_REG_MX2 + 3 * bit,
|
||||
data->buffer, 3);
|
||||
if (ret < 0) {
|
||||
mutex_unlock(&data->lock);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&data->lock);
|
||||
}
|
||||
/*
|
||||
* Always using the same buffer so that we wouldn't need to set the
|
||||
* paddings to 0 in case of leaking any data.
|
||||
*/
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
|
||||
pf->timestamp);
|
||||
done:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int rm3100_common_probe(struct device *dev, struct regmap *regmap, int irq)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
struct rm3100_data *data;
|
||||
unsigned int tmp;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->regmap = regmap;
|
||||
|
||||
mutex_init(&data->lock);
|
||||
|
||||
indio_dev->dev.parent = dev;
|
||||
indio_dev->name = "rm3100";
|
||||
indio_dev->info = &rm3100_info;
|
||||
indio_dev->channels = rm3100_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(rm3100_channels);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_TRIGGERED;
|
||||
indio_dev->currentmode = INDIO_DIRECT_MODE;
|
||||
|
||||
if (!irq)
|
||||
data->use_interrupt = false;
|
||||
else {
|
||||
data->use_interrupt = true;
|
||||
|
||||
init_completion(&data->measuring_done);
|
||||
ret = devm_request_threaded_irq(dev,
|
||||
irq,
|
||||
rm3100_irq_handler,
|
||||
rm3100_thread_fn,
|
||||
IRQF_TRIGGER_HIGH |
|
||||
IRQF_ONESHOT,
|
||||
indio_dev->name,
|
||||
indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "request irq line failed.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->drdy_trig = devm_iio_trigger_alloc(dev, "%s-drdy%d",
|
||||
indio_dev->name,
|
||||
indio_dev->id);
|
||||
if (!data->drdy_trig)
|
||||
return -ENOMEM;
|
||||
|
||||
data->drdy_trig->dev.parent = dev;
|
||||
ret = devm_iio_trigger_register(dev, data->drdy_trig);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
|
||||
&iio_pollfunc_store_time,
|
||||
rm3100_trigger_handler,
|
||||
&rm3100_buffer_ops);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(regmap, RM3100_REG_TMRC, &tmp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/* Initializing max wait time, which is double conversion time. */
|
||||
data->conversion_time = rm3100_samp_rates[tmp - RM3100_TMRC_OFFSET][2]
|
||||
* 2;
|
||||
|
||||
/* Cycle count values may not be what we want. */
|
||||
if ((tmp - RM3100_TMRC_OFFSET) == 0)
|
||||
rm3100_set_cycle_count(data, 100);
|
||||
else
|
||||
rm3100_set_cycle_count(data, 200);
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rm3100_common_probe);
|
||||
|
||||
MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
|
||||
MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer i2c driver");
|
||||
MODULE_LICENSE("GPL v2");
|
54
drivers/iio/magnetometer/rm3100-i2c.c
Normal file
54
drivers/iio/magnetometer/rm3100-i2c.c
Normal file
@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Support for PNI RM3100 3-axis geomagnetic sensor on a i2c bus.
|
||||
*
|
||||
* Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
|
||||
*
|
||||
* i2c slave address: 0x20 + SA1 << 1 + SA0.
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "rm3100.h"
|
||||
|
||||
static const struct regmap_config rm3100_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.rd_table = &rm3100_readable_table,
|
||||
.wr_table = &rm3100_writable_table,
|
||||
.volatile_table = &rm3100_volatile_table,
|
||||
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
};
|
||||
|
||||
static int rm3100_probe(struct i2c_client *client)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &rm3100_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return PTR_ERR(regmap);
|
||||
|
||||
return rm3100_common_probe(&client->dev, regmap, client->irq);
|
||||
}
|
||||
|
||||
static const struct of_device_id rm3100_dt_match[] = {
|
||||
{ .compatible = "pni,rm3100", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rm3100_dt_match);
|
||||
|
||||
static struct i2c_driver rm3100_driver = {
|
||||
.driver = {
|
||||
.name = "rm3100-i2c",
|
||||
.of_match_table = rm3100_dt_match,
|
||||
},
|
||||
.probe_new = rm3100_probe,
|
||||
};
|
||||
module_i2c_driver(rm3100_driver);
|
||||
|
||||
MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
|
||||
MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer i2c driver");
|
||||
MODULE_LICENSE("GPL v2");
|
64
drivers/iio/magnetometer/rm3100-spi.c
Normal file
64
drivers/iio/magnetometer/rm3100-spi.c
Normal file
@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Support for PNI RM3100 3-axis geomagnetic sensor on a spi bus.
|
||||
*
|
||||
* Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include "rm3100.h"
|
||||
|
||||
static const struct regmap_config rm3100_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.rd_table = &rm3100_readable_table,
|
||||
.wr_table = &rm3100_writable_table,
|
||||
.volatile_table = &rm3100_volatile_table,
|
||||
|
||||
.read_flag_mask = 0x80,
|
||||
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
};
|
||||
|
||||
static int rm3100_probe(struct spi_device *spi)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
/* Actually this device supports both mode 0 and mode 3. */
|
||||
spi->mode = SPI_MODE_0;
|
||||
/* Data rates cannot exceed 1Mbits. */
|
||||
spi->max_speed_hz = 1000000;
|
||||
spi->bits_per_word = 8;
|
||||
ret = spi_setup(spi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
regmap = devm_regmap_init_spi(spi, &rm3100_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return PTR_ERR(regmap);
|
||||
|
||||
return rm3100_common_probe(&spi->dev, regmap, spi->irq);
|
||||
}
|
||||
|
||||
static const struct of_device_id rm3100_dt_match[] = {
|
||||
{ .compatible = "pni,rm3100", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rm3100_dt_match);
|
||||
|
||||
static struct spi_driver rm3100_driver = {
|
||||
.driver = {
|
||||
.name = "rm3100-spi",
|
||||
.of_match_table = rm3100_dt_match,
|
||||
},
|
||||
.probe = rm3100_probe,
|
||||
};
|
||||
module_spi_driver(rm3100_driver);
|
||||
|
||||
MODULE_AUTHOR("Song Qiang <songqiang1304521@gmail.com>");
|
||||
MODULE_DESCRIPTION("PNI RM3100 3-axis magnetometer spi driver");
|
||||
MODULE_LICENSE("GPL v2");
|
17
drivers/iio/magnetometer/rm3100.h
Normal file
17
drivers/iio/magnetometer/rm3100.h
Normal file
@ -0,0 +1,17 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2018 Song Qiang <songqiang1304521@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef RM3100_CORE_H
|
||||
#define RM3100_CORE_H
|
||||
|
||||
#include <linux/regmap.h>
|
||||
|
||||
extern const struct regmap_access_table rm3100_readable_table;
|
||||
extern const struct regmap_access_table rm3100_writable_table;
|
||||
extern const struct regmap_access_table rm3100_volatile_table;
|
||||
|
||||
int rm3100_common_probe(struct device *dev, struct regmap *regmap, int irq);
|
||||
|
||||
#endif /* RM3100_CORE_H */
|
@ -20,6 +20,7 @@
|
||||
#define LIS3MDL_MAGN_DEV_NAME "lis3mdl"
|
||||
#define LSM303AGR_MAGN_DEV_NAME "lsm303agr_magn"
|
||||
#define LIS2MDL_MAGN_DEV_NAME "lis2mdl"
|
||||
#define LSM9DS1_MAGN_DEV_NAME "lsm9ds1_magn"
|
||||
|
||||
int st_magn_common_probe(struct iio_dev *indio_dev);
|
||||
void st_magn_common_remove(struct iio_dev *indio_dev);
|
||||
|
@ -29,9 +29,9 @@
|
||||
#define ST_MAGN_NUMBER_DATA_CHANNELS 3
|
||||
|
||||
/* DEFAULT VALUE FOR SENSORS */
|
||||
#define ST_MAGN_DEFAULT_OUT_X_H_ADDR 0X03
|
||||
#define ST_MAGN_DEFAULT_OUT_Y_H_ADDR 0X07
|
||||
#define ST_MAGN_DEFAULT_OUT_Z_H_ADDR 0X05
|
||||
#define ST_MAGN_DEFAULT_OUT_X_H_ADDR 0x03
|
||||
#define ST_MAGN_DEFAULT_OUT_Y_H_ADDR 0x07
|
||||
#define ST_MAGN_DEFAULT_OUT_Z_H_ADDR 0x05
|
||||
|
||||
/* FULLSCALE */
|
||||
#define ST_MAGN_FS_AVL_1300MG 1300
|
||||
@ -267,6 +267,7 @@ static const struct st_sensor_settings st_magn_sensors_settings[] = {
|
||||
.wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS,
|
||||
.sensors_supported = {
|
||||
[0] = LIS3MDL_MAGN_DEV_NAME,
|
||||
[1] = LSM9DS1_MAGN_DEV_NAME,
|
||||
},
|
||||
.ch = (struct iio_chan_spec *)st_magn_2_16bit_channels,
|
||||
.odr = {
|
||||
@ -315,6 +316,10 @@ static const struct st_sensor_settings st_magn_sensors_settings[] = {
|
||||
},
|
||||
},
|
||||
},
|
||||
.bdu = {
|
||||
.addr = 0x24,
|
||||
.mask = 0x40,
|
||||
},
|
||||
.drdy_irq = {
|
||||
/* drdy line is routed drdy pin */
|
||||
.stat_drdy = {
|
||||
|
@ -44,6 +44,10 @@ static const struct of_device_id st_magn_of_match[] = {
|
||||
.compatible = "st,lis2mdl",
|
||||
.data = LIS2MDL_MAGN_DEV_NAME,
|
||||
},
|
||||
{
|
||||
.compatible = "st,lsm9ds1-magn",
|
||||
.data = LSM9DS1_MAGN_DEV_NAME,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, st_magn_of_match);
|
||||
@ -90,6 +94,7 @@ static const struct i2c_device_id st_magn_id_table[] = {
|
||||
{ LIS3MDL_MAGN_DEV_NAME },
|
||||
{ LSM303AGR_MAGN_DEV_NAME },
|
||||
{ LIS2MDL_MAGN_DEV_NAME },
|
||||
{ LSM9DS1_MAGN_DEV_NAME },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, st_magn_id_table);
|
||||
|
@ -23,6 +23,8 @@
|
||||
* For new single-chip sensors use <device_name> as compatible string.
|
||||
* For old single-chip devices keep <device_name>-magn to maintain
|
||||
* compatibility
|
||||
* For multi-chip devices, use <device_name>-magn to distinguish which
|
||||
* capability is being used
|
||||
*/
|
||||
static const struct of_device_id st_magn_of_match[] = {
|
||||
{
|
||||
@ -37,6 +39,10 @@ static const struct of_device_id st_magn_of_match[] = {
|
||||
.compatible = "st,lis2mdl",
|
||||
.data = LIS2MDL_MAGN_DEV_NAME,
|
||||
},
|
||||
{
|
||||
.compatible = "st,lsm9ds1-magn",
|
||||
.data = LSM9DS1_MAGN_DEV_NAME,
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, st_magn_of_match);
|
||||
@ -79,6 +85,7 @@ static const struct spi_device_id st_magn_id_table[] = {
|
||||
{ LIS3MDL_MAGN_DEV_NAME },
|
||||
{ LSM303AGR_MAGN_DEV_NAME },
|
||||
{ LIS2MDL_MAGN_DEV_NAME },
|
||||
{ LSM9DS1_MAGN_DEV_NAME },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, st_magn_id_table);
|
||||
|
@ -90,6 +90,18 @@ config MCP4531
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called mcp4531.
|
||||
|
||||
config MCP41010
|
||||
tristate "Microchip MCP41xxx/MCP42xxx Digital Potentiometer driver"
|
||||
depends on SPI
|
||||
help
|
||||
Say yes here to build support for the Microchip
|
||||
MCP41010, MCP41050, MCP41100,
|
||||
MCP42010, MCP42050, MCP42100
|
||||
digital potentiometer chips.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called mcp41010.
|
||||
|
||||
config TPL0102
|
||||
tristate "Texas Instruments digital potentiometer driver"
|
||||
depends on I2C
|
||||
|
@ -11,4 +11,5 @@ obj-$(CONFIG_MAX5487) += max5487.o
|
||||
obj-$(CONFIG_MCP4018) += mcp4018.o
|
||||
obj-$(CONFIG_MCP4131) += mcp4131.o
|
||||
obj-$(CONFIG_MCP4531) += mcp4531.o
|
||||
obj-$(CONFIG_MCP41010) += mcp41010.o
|
||||
obj-$(CONFIG_TPL0102) += tpl0102.o
|
||||
|
203
drivers/iio/potentiometer/mcp41010.c
Normal file
203
drivers/iio/potentiometer/mcp41010.c
Normal file
@ -0,0 +1,203 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Industrial I/O driver for Microchip digital potentiometers
|
||||
*
|
||||
* Copyright (c) 2018 Chris Coffey <cmc@babblebit.net>
|
||||
* Based on: Slawomir Stepien's code from mcp4131.c
|
||||
*
|
||||
* Datasheet: http://ww1.microchip.com/downloads/en/devicedoc/11195c.pdf
|
||||
*
|
||||
* DEVID #Wipers #Positions Resistance (kOhm)
|
||||
* mcp41010 1 256 10
|
||||
* mcp41050 1 256 50
|
||||
* mcp41100 1 256 100
|
||||
* mcp42010 2 256 10
|
||||
* mcp42050 2 256 50
|
||||
* mcp42100 2 256 100
|
||||
*/
|
||||
|
||||
#include <linux/cache.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/types.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define MCP41010_MAX_WIPERS 2
|
||||
#define MCP41010_WRITE BIT(4)
|
||||
#define MCP41010_WIPER_MAX 255
|
||||
#define MCP41010_WIPER_CHANNEL BIT(0)
|
||||
|
||||
struct mcp41010_cfg {
|
||||
char name[16];
|
||||
int wipers;
|
||||
int kohms;
|
||||
};
|
||||
|
||||
enum mcp41010_type {
|
||||
MCP41010,
|
||||
MCP41050,
|
||||
MCP41100,
|
||||
MCP42010,
|
||||
MCP42050,
|
||||
MCP42100,
|
||||
};
|
||||
|
||||
static const struct mcp41010_cfg mcp41010_cfg[] = {
|
||||
[MCP41010] = { .name = "mcp41010", .wipers = 1, .kohms = 10, },
|
||||
[MCP41050] = { .name = "mcp41050", .wipers = 1, .kohms = 50, },
|
||||
[MCP41100] = { .name = "mcp41100", .wipers = 1, .kohms = 100, },
|
||||
[MCP42010] = { .name = "mcp42010", .wipers = 2, .kohms = 10, },
|
||||
[MCP42050] = { .name = "mcp42050", .wipers = 2, .kohms = 50, },
|
||||
[MCP42100] = { .name = "mcp42100", .wipers = 2, .kohms = 100, },
|
||||
};
|
||||
|
||||
struct mcp41010_data {
|
||||
struct spi_device *spi;
|
||||
const struct mcp41010_cfg *cfg;
|
||||
struct mutex lock; /* Protect write sequences */
|
||||
unsigned int value[MCP41010_MAX_WIPERS]; /* Cache wiper values */
|
||||
u8 buf[2] ____cacheline_aligned;
|
||||
};
|
||||
|
||||
#define MCP41010_CHANNEL(ch) { \
|
||||
.type = IIO_RESISTANCE, \
|
||||
.indexed = 1, \
|
||||
.output = 1, \
|
||||
.channel = (ch), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec mcp41010_channels[] = {
|
||||
MCP41010_CHANNEL(0),
|
||||
MCP41010_CHANNEL(1),
|
||||
};
|
||||
|
||||
static int mcp41010_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct mcp41010_data *data = iio_priv(indio_dev);
|
||||
int channel = chan->channel;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
*val = data->value[channel];
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 1000 * data->cfg->kohms;
|
||||
*val2 = MCP41010_WIPER_MAX;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int mcp41010_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
int err;
|
||||
struct mcp41010_data *data = iio_priv(indio_dev);
|
||||
int channel = chan->channel;
|
||||
|
||||
if (mask != IIO_CHAN_INFO_RAW)
|
||||
return -EINVAL;
|
||||
|
||||
if (val > MCP41010_WIPER_MAX || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
data->buf[0] = MCP41010_WIPER_CHANNEL << channel;
|
||||
data->buf[0] |= MCP41010_WRITE;
|
||||
data->buf[1] = val & 0xff;
|
||||
|
||||
err = spi_write(data->spi, data->buf, sizeof(data->buf));
|
||||
if (!err)
|
||||
data->value[channel] = val;
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct iio_info mcp41010_info = {
|
||||
.read_raw = mcp41010_read_raw,
|
||||
.write_raw = mcp41010_write_raw,
|
||||
};
|
||||
|
||||
static int mcp41010_probe(struct spi_device *spi)
|
||||
{
|
||||
int err;
|
||||
struct device *dev = &spi->dev;
|
||||
struct mcp41010_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
spi_set_drvdata(spi, indio_dev);
|
||||
data->spi = spi;
|
||||
data->cfg = of_device_get_match_data(&spi->dev);
|
||||
if (!data->cfg)
|
||||
data->cfg = &mcp41010_cfg[spi_get_device_id(spi)->driver_data];
|
||||
|
||||
mutex_init(&data->lock);
|
||||
|
||||
indio_dev->dev.parent = dev;
|
||||
indio_dev->info = &mcp41010_info;
|
||||
indio_dev->channels = mcp41010_channels;
|
||||
indio_dev->num_channels = data->cfg->wipers;
|
||||
indio_dev->name = data->cfg->name;
|
||||
|
||||
err = devm_iio_device_register(dev, indio_dev);
|
||||
if (err)
|
||||
dev_info(&spi->dev, "Unable to register %s\n", indio_dev->name);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct of_device_id mcp41010_match[] = {
|
||||
{ .compatible = "microchip,mcp41010", .data = &mcp41010_cfg[MCP41010] },
|
||||
{ .compatible = "microchip,mcp41050", .data = &mcp41010_cfg[MCP41050] },
|
||||
{ .compatible = "microchip,mcp41100", .data = &mcp41010_cfg[MCP41100] },
|
||||
{ .compatible = "microchip,mcp42010", .data = &mcp41010_cfg[MCP42010] },
|
||||
{ .compatible = "microchip,mcp42050", .data = &mcp41010_cfg[MCP42050] },
|
||||
{ .compatible = "microchip,mcp42100", .data = &mcp41010_cfg[MCP42100] },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mcp41010_match);
|
||||
|
||||
static const struct spi_device_id mcp41010_id[] = {
|
||||
{ "mcp41010", MCP41010 },
|
||||
{ "mcp41050", MCP41050 },
|
||||
{ "mcp41100", MCP41100 },
|
||||
{ "mcp42010", MCP42010 },
|
||||
{ "mcp42050", MCP42050 },
|
||||
{ "mcp42100", MCP42100 },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, mcp41010_id);
|
||||
|
||||
static struct spi_driver mcp41010_driver = {
|
||||
.driver = {
|
||||
.name = "mcp41010",
|
||||
.of_match_table = mcp41010_match,
|
||||
},
|
||||
.probe = mcp41010_probe,
|
||||
.id_table = mcp41010_id,
|
||||
};
|
||||
|
||||
module_spi_driver(mcp41010_driver);
|
||||
|
||||
MODULE_AUTHOR("Chris Coffey <cmc@babblebit.net>");
|
||||
MODULE_DESCRIPTION("MCP41010 digital potentiometer");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -42,6 +42,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define MCP4131_WRITE (0x00 << 2)
|
||||
@ -243,7 +244,7 @@ static int mcp4131_probe(struct spi_device *spi)
|
||||
{
|
||||
int err;
|
||||
struct device *dev = &spi->dev;
|
||||
unsigned long devid = spi_get_device_id(spi)->driver_data;
|
||||
unsigned long devid;
|
||||
struct mcp4131_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
|
||||
@ -254,7 +255,11 @@ static int mcp4131_probe(struct spi_device *spi)
|
||||
data = iio_priv(indio_dev);
|
||||
spi_set_drvdata(spi, indio_dev);
|
||||
data->spi = spi;
|
||||
data->cfg = &mcp4131_cfg[devid];
|
||||
data->cfg = of_device_get_match_data(&spi->dev);
|
||||
if (!data->cfg) {
|
||||
devid = spi_get_device_id(spi)->driver_data;
|
||||
data->cfg = &mcp4131_cfg[devid];
|
||||
}
|
||||
|
||||
mutex_init(&data->lock);
|
||||
|
||||
@ -273,7 +278,6 @@ static int mcp4131_probe(struct spi_device *spi)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_OF)
|
||||
static const struct of_device_id mcp4131_dt_ids[] = {
|
||||
{ .compatible = "microchip,mcp4131-502",
|
||||
.data = &mcp4131_cfg[MCP413x_502] },
|
||||
@ -406,7 +410,6 @@ static const struct of_device_id mcp4131_dt_ids[] = {
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mcp4131_dt_ids);
|
||||
#endif /* CONFIG_OF */
|
||||
|
||||
static const struct spi_device_id mcp4131_id[] = {
|
||||
{ "mcp4131-502", MCP413x_502 },
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
struct tpl0102_cfg {
|
||||
int wipers;
|
||||
int max_pos;
|
||||
int avail[3];
|
||||
int kohms;
|
||||
};
|
||||
|
||||
@ -28,16 +28,16 @@ enum tpl0102_type {
|
||||
|
||||
static const struct tpl0102_cfg tpl0102_cfg[] = {
|
||||
/* on-semiconductor parts */
|
||||
[CAT5140_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, },
|
||||
[CAT5140_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, },
|
||||
[CAT5140_503] = { .wipers = 1, .avail = { 0, 1, 255 }, .kohms = 50, },
|
||||
[CAT5140_104] = { .wipers = 1, .avail = { 0, 1, 255 }, .kohms = 100, },
|
||||
/* ti parts */
|
||||
[TPL0102_104] = { .wipers = 2, .max_pos = 256, .kohms = 100 },
|
||||
[TPL0401_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, },
|
||||
[TPL0102_104] = { .wipers = 2, .avail = { 0, 1, 255 }, .kohms = 100 },
|
||||
[TPL0401_103] = { .wipers = 1, .avail = { 0, 1, 127 }, .kohms = 10, },
|
||||
};
|
||||
|
||||
struct tpl0102_data {
|
||||
struct regmap *regmap;
|
||||
unsigned long devid;
|
||||
const struct tpl0102_cfg *cfg;
|
||||
};
|
||||
|
||||
static const struct regmap_config tpl0102_regmap_config = {
|
||||
@ -52,6 +52,7 @@ static const struct regmap_config tpl0102_regmap_config = {
|
||||
.channel = (ch), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec tpl0102_channels[] = {
|
||||
@ -72,14 +73,32 @@ static int tpl0102_read_raw(struct iio_dev *indio_dev,
|
||||
return ret ? ret : IIO_VAL_INT;
|
||||
}
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 1000 * tpl0102_cfg[data->devid].kohms;
|
||||
*val2 = tpl0102_cfg[data->devid].max_pos;
|
||||
*val = 1000 * data->cfg->kohms;
|
||||
*val2 = data->cfg->avail[2] + 1;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int tpl0102_read_avail(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
const int **vals, int *type, int *length,
|
||||
long mask)
|
||||
{
|
||||
struct tpl0102_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 tpl0102_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
@ -89,7 +108,7 @@ static int tpl0102_write_raw(struct iio_dev *indio_dev,
|
||||
if (mask != IIO_CHAN_INFO_RAW)
|
||||
return -EINVAL;
|
||||
|
||||
if (val >= tpl0102_cfg[data->devid].max_pos || val < 0)
|
||||
if (val > data->cfg->avail[2] || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_write(data->regmap, chan->channel, val);
|
||||
@ -97,6 +116,7 @@ static int tpl0102_write_raw(struct iio_dev *indio_dev,
|
||||
|
||||
static const struct iio_info tpl0102_info = {
|
||||
.read_raw = tpl0102_read_raw,
|
||||
.read_avail = tpl0102_read_avail,
|
||||
.write_raw = tpl0102_write_raw,
|
||||
};
|
||||
|
||||
@ -113,7 +133,7 @@ static int tpl0102_probe(struct i2c_client *client,
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
data->devid = id->driver_data;
|
||||
data->cfg = &tpl0102_cfg[id->driver_data];
|
||||
data->regmap = devm_regmap_init_i2c(client, &tpl0102_regmap_config);
|
||||
if (IS_ERR(data->regmap)) {
|
||||
dev_err(dev, "regmap initialization failed\n");
|
||||
@ -123,7 +143,7 @@ static int tpl0102_probe(struct i2c_client *client,
|
||||
indio_dev->dev.parent = dev;
|
||||
indio_dev->info = &tpl0102_info;
|
||||
indio_dev->channels = tpl0102_channels;
|
||||
indio_dev->num_channels = tpl0102_cfg[data->devid].wipers;
|
||||
indio_dev->num_channels = data->cfg->wipers;
|
||||
indio_dev->name = client->name;
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
|
@ -3,6 +3,16 @@
|
||||
#
|
||||
menu "Resolver to digital converters"
|
||||
|
||||
config AD2S90
|
||||
tristate "Analog Devices ad2s90 driver"
|
||||
depends on SPI
|
||||
help
|
||||
Say yes here to build support for Analog Devices spi resolver
|
||||
to digital converters, ad2s90, provides direct access via sysfs.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called ad2s90.
|
||||
|
||||
config AD2S1200
|
||||
tristate "Analog Devices ad2s1200/ad2s1205 driver"
|
||||
depends on SPI
|
||||
|
@ -2,4 +2,5 @@
|
||||
# Makefile for Resolver/Synchro drivers
|
||||
#
|
||||
|
||||
obj-$(CONFIG_AD2S90) += ad2s90.o
|
||||
obj-$(CONFIG_AD2S1200) += ad2s1200.o
|
||||
|
@ -1,12 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* ad2s90.c simple support for the ADI Resolver to Digital Converters: AD2S90
|
||||
*
|
||||
* Copyright (c) 2010-2010 Analog Devices Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
#include <linux/types.h>
|
||||
#include <linux/mutex.h>
|
||||
@ -19,8 +15,14 @@
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
||||
/*
|
||||
* Although chip's max frequency is 2Mhz, it needs 600ns between CS and the
|
||||
* first falling edge of SCLK, so frequency should be at most 1 / (2 * 6e-7)
|
||||
*/
|
||||
#define AD2S90_MAX_SPI_FREQ_HZ 830000
|
||||
|
||||
struct ad2s90_state {
|
||||
struct mutex lock;
|
||||
struct mutex lock; /* lock to protect rx buffer */
|
||||
struct spi_device *sdev;
|
||||
u8 rx[2] ____cacheline_aligned;
|
||||
};
|
||||
@ -34,16 +36,32 @@ static int ad2s90_read_raw(struct iio_dev *indio_dev,
|
||||
int ret;
|
||||
struct ad2s90_state *st = iio_priv(indio_dev);
|
||||
|
||||
mutex_lock(&st->lock);
|
||||
ret = spi_read(st->sdev, st->rx, 2);
|
||||
if (ret)
|
||||
goto error_ret;
|
||||
*val = (((u16)(st->rx[0])) << 4) | ((st->rx[1] & 0xF0) >> 4);
|
||||
if (chan->type != IIO_ANGL)
|
||||
return -EINVAL;
|
||||
|
||||
error_ret:
|
||||
mutex_unlock(&st->lock);
|
||||
switch (m) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
/* 2 * Pi / 2^12 */
|
||||
*val = 6283; /* mV */
|
||||
*val2 = 12;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
mutex_lock(&st->lock);
|
||||
ret = spi_read(st->sdev, st->rx, 2);
|
||||
if (ret < 0) {
|
||||
mutex_unlock(&st->lock);
|
||||
return ret;
|
||||
}
|
||||
*val = (((u16)(st->rx[0])) << 4) | ((st->rx[1] & 0xF0) >> 4);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
mutex_unlock(&st->lock);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct iio_info ad2s90_info = {
|
||||
@ -54,14 +72,19 @@ static const struct iio_chan_spec ad2s90_chan = {
|
||||
.type = IIO_ANGL,
|
||||
.indexed = 1,
|
||||
.channel = 0,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
|
||||
};
|
||||
|
||||
static int ad2s90_probe(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
struct ad2s90_state *st;
|
||||
int ret = 0;
|
||||
|
||||
if (spi->max_speed_hz > AD2S90_MAX_SPI_FREQ_HZ) {
|
||||
dev_err(&spi->dev, "SPI CLK, %d Hz exceeds %d Hz\n",
|
||||
spi->max_speed_hz, AD2S90_MAX_SPI_FREQ_HZ);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
|
||||
if (!indio_dev)
|
||||
@ -78,18 +101,15 @@ static int ad2s90_probe(struct spi_device *spi)
|
||||
indio_dev->num_channels = 1;
|
||||
indio_dev->name = spi_get_device_id(spi)->name;
|
||||
|
||||
ret = devm_iio_device_register(indio_dev->dev.parent, indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* need 600ns between CS and the first falling edge of SCLK */
|
||||
spi->max_speed_hz = 830000;
|
||||
spi->mode = SPI_MODE_3;
|
||||
spi_setup(spi);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(indio_dev->dev.parent, indio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id ad2s90_of_match[] = {
|
||||
{ .compatible = "adi,ad2s90", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ad2s90_of_match);
|
||||
|
||||
static const struct spi_device_id ad2s90_id[] = {
|
||||
{ "ad2s90" },
|
||||
{}
|
||||
@ -99,6 +119,7 @@ MODULE_DEVICE_TABLE(spi, ad2s90_id);
|
||||
static struct spi_driver ad2s90_driver = {
|
||||
.driver = {
|
||||
.name = "ad2s90",
|
||||
.of_match_table = ad2s90_of_match,
|
||||
},
|
||||
.probe = ad2s90_probe,
|
||||
.id_table = ad2s90_id,
|
@ -195,7 +195,7 @@ static int range_alloc(struct ashmem_area *asma,
|
||||
}
|
||||
|
||||
/**
|
||||
* range_del() - Deletes and dealloctes an ashmem_range structure
|
||||
* range_del() - Deletes and deallocates an ashmem_range structure
|
||||
* @range: The associated ashmem_range that has previously been allocated
|
||||
*/
|
||||
static void range_del(struct ashmem_range *range)
|
||||
@ -521,7 +521,7 @@ static int set_name(struct ashmem_area *asma, void __user *name)
|
||||
* an data abort which would try to access mmap_sem. If another
|
||||
* thread has invoked ashmem_mmap then it will be holding the
|
||||
* semaphore and will be waiting for ashmem_mutex, there by leading to
|
||||
* deadlock. We'll release the mutex and take the name to a local
|
||||
* deadlock. We'll release the mutex and take the name to a local
|
||||
* variable that does not need protection and later copy the local
|
||||
* variable to the structure member with lock held.
|
||||
*/
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include <linux/mm_types.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/vmalloc.h>
|
||||
@ -95,6 +94,13 @@ static struct ion_buffer *ion_buffer_create(struct ion_heap *heap,
|
||||
goto err1;
|
||||
}
|
||||
|
||||
spin_lock(&heap->stat_lock);
|
||||
heap->num_of_buffers++;
|
||||
heap->num_of_alloc_bytes += len;
|
||||
if (heap->num_of_alloc_bytes > heap->alloc_bytes_wm)
|
||||
heap->alloc_bytes_wm = heap->num_of_alloc_bytes;
|
||||
spin_unlock(&heap->stat_lock);
|
||||
|
||||
INIT_LIST_HEAD(&buffer->attachments);
|
||||
mutex_init(&buffer->lock);
|
||||
mutex_lock(&dev->buffer_lock);
|
||||
@ -117,6 +123,11 @@ void ion_buffer_destroy(struct ion_buffer *buffer)
|
||||
buffer->heap->ops->unmap_kernel(buffer->heap, buffer);
|
||||
}
|
||||
buffer->heap->ops->free(buffer);
|
||||
spin_lock(&buffer->heap->stat_lock);
|
||||
buffer->heap->num_of_buffers--;
|
||||
buffer->heap->num_of_alloc_bytes -= buffer->size;
|
||||
spin_unlock(&buffer->heap->stat_lock);
|
||||
|
||||
kfree(buffer);
|
||||
}
|
||||
|
||||
@ -528,12 +539,15 @@ void ion_device_add_heap(struct ion_heap *heap)
|
||||
{
|
||||
struct ion_device *dev = internal_dev;
|
||||
int ret;
|
||||
struct dentry *heap_root;
|
||||
char debug_name[64];
|
||||
|
||||
if (!heap->ops->allocate || !heap->ops->free)
|
||||
pr_err("%s: can not add heap with invalid ops struct.\n",
|
||||
__func__);
|
||||
|
||||
spin_lock_init(&heap->free_lock);
|
||||
spin_lock_init(&heap->stat_lock);
|
||||
heap->free_list_size = 0;
|
||||
|
||||
if (heap->flags & ION_HEAP_FLAG_DEFER_FREE)
|
||||
@ -546,6 +560,33 @@ void ion_device_add_heap(struct ion_heap *heap)
|
||||
}
|
||||
|
||||
heap->dev = dev;
|
||||
heap->num_of_buffers = 0;
|
||||
heap->num_of_alloc_bytes = 0;
|
||||
heap->alloc_bytes_wm = 0;
|
||||
|
||||
heap_root = debugfs_create_dir(heap->name, dev->debug_root);
|
||||
debugfs_create_u64("num_of_buffers",
|
||||
0444, heap_root,
|
||||
&heap->num_of_buffers);
|
||||
debugfs_create_u64("num_of_alloc_bytes",
|
||||
0444,
|
||||
heap_root,
|
||||
&heap->num_of_alloc_bytes);
|
||||
debugfs_create_u64("alloc_bytes_wm",
|
||||
0444,
|
||||
heap_root,
|
||||
&heap->alloc_bytes_wm);
|
||||
|
||||
if (heap->shrinker.count_objects &&
|
||||
heap->shrinker.scan_objects) {
|
||||
snprintf(debug_name, 64, "%s_shrink", heap->name);
|
||||
debugfs_create_file(debug_name,
|
||||
0644,
|
||||
heap_root,
|
||||
heap,
|
||||
&debug_shrink_fops);
|
||||
}
|
||||
|
||||
down_write(&dev->lock);
|
||||
heap->id = heap_id++;
|
||||
/*
|
||||
@ -555,14 +596,6 @@ void ion_device_add_heap(struct ion_heap *heap)
|
||||
plist_node_init(&heap->node, -heap->id);
|
||||
plist_add(&heap->node, &dev->heaps);
|
||||
|
||||
if (heap->shrinker.count_objects && heap->shrinker.scan_objects) {
|
||||
char debug_name[64];
|
||||
|
||||
snprintf(debug_name, 64, "%s_shrink", heap->name);
|
||||
debugfs_create_file(debug_name, 0644, dev->debug_root,
|
||||
heap, &debug_shrink_fops);
|
||||
}
|
||||
|
||||
dev->heap_cnt++;
|
||||
up_write(&dev->lock);
|
||||
}
|
||||
|
@ -157,6 +157,9 @@ struct ion_heap_ops {
|
||||
* @lock: protects the free list
|
||||
* @waitqueue: queue to wait on from deferred free thread
|
||||
* @task: task struct of deferred free thread
|
||||
* @num_of_buffers the number of currently allocated buffers
|
||||
* @num_of_alloc_bytes the number of allocated bytes
|
||||
* @alloc_bytes_wm the number of allocated bytes watermark
|
||||
*
|
||||
* Represents a pool of memory from which buffers can be made. In some
|
||||
* systems the only heap is regular system memory allocated via vmalloc.
|
||||
@ -177,6 +180,12 @@ struct ion_heap {
|
||||
spinlock_t free_lock;
|
||||
wait_queue_head_t waitqueue;
|
||||
struct task_struct *task;
|
||||
u64 num_of_buffers;
|
||||
u64 num_of_alloc_bytes;
|
||||
u64 alloc_bytes_wm;
|
||||
|
||||
/* protect heap statistics */
|
||||
spinlock_t stat_lock;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include "ion.h"
|
||||
|
@ -485,7 +485,8 @@ static ssize_t axis_fifo_write(struct file *f, const char __user *buf,
|
||||
ioread32(fifo->base_addr + XLLF_TDFV_OFFSET)
|
||||
>= words_to_write,
|
||||
fifo->write_queue_lock,
|
||||
(write_timeout >= 0) ? msecs_to_jiffies(write_timeout) :
|
||||
(write_timeout >= 0) ?
|
||||
msecs_to_jiffies(write_timeout) :
|
||||
MAX_SCHEDULE_TIMEOUT);
|
||||
spin_unlock_irq(&fifo->write_queue_lock);
|
||||
|
||||
|
@ -1209,6 +1209,7 @@ static int check_insn_config_length(struct comedi_insn *insn,
|
||||
break;
|
||||
case INSN_CONFIG_PWM_OUTPUT:
|
||||
case INSN_CONFIG_ANALOG_TRIG:
|
||||
case INSN_CONFIG_TIMER_1:
|
||||
if (insn->n == 5)
|
||||
return 0;
|
||||
break;
|
||||
@ -1500,25 +1501,21 @@ out:
|
||||
* data (for reads) to insns[].data pointers
|
||||
*/
|
||||
/* arbitrary limits */
|
||||
#define MAX_SAMPLES 256
|
||||
#define MIN_SAMPLES 16
|
||||
#define MAX_SAMPLES 65536
|
||||
static int do_insnlist_ioctl(struct comedi_device *dev,
|
||||
struct comedi_insnlist __user *arg, void *file)
|
||||
{
|
||||
struct comedi_insnlist insnlist;
|
||||
struct comedi_insn *insns = NULL;
|
||||
unsigned int *data = NULL;
|
||||
unsigned int max_n_data_required = MIN_SAMPLES;
|
||||
int i = 0;
|
||||
int ret = 0;
|
||||
|
||||
if (copy_from_user(&insnlist, arg, sizeof(insnlist)))
|
||||
return -EFAULT;
|
||||
|
||||
data = kmalloc_array(MAX_SAMPLES, sizeof(unsigned int), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
insns = kcalloc(insnlist.n_insns, sizeof(*insns), GFP_KERNEL);
|
||||
if (!insns) {
|
||||
ret = -ENOMEM;
|
||||
@ -1532,13 +1529,26 @@ static int do_insnlist_ioctl(struct comedi_device *dev,
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (i = 0; i < insnlist.n_insns; i++) {
|
||||
/* Determine maximum memory needed for all instructions. */
|
||||
for (i = 0; i < insnlist.n_insns; ++i) {
|
||||
if (insns[i].n > MAX_SAMPLES) {
|
||||
dev_dbg(dev->class_dev,
|
||||
"number of samples too large\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
max_n_data_required = max(max_n_data_required, insns[i].n);
|
||||
}
|
||||
|
||||
/* Allocate scratch space for all instruction data. */
|
||||
data = kmalloc_array(max_n_data_required, sizeof(unsigned int),
|
||||
GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (i = 0; i < insnlist.n_insns; ++i) {
|
||||
if (insns[i].insn & INSN_MASK_WRITE) {
|
||||
if (copy_from_user(data, insns[i].data,
|
||||
insns[i].n * sizeof(unsigned int))) {
|
||||
@ -1592,22 +1602,27 @@ static int do_insn_ioctl(struct comedi_device *dev,
|
||||
{
|
||||
struct comedi_insn insn;
|
||||
unsigned int *data = NULL;
|
||||
unsigned int n_data = MIN_SAMPLES;
|
||||
int ret = 0;
|
||||
|
||||
data = kmalloc_array(MAX_SAMPLES, sizeof(unsigned int), GFP_KERNEL);
|
||||
if (copy_from_user(&insn, arg, sizeof(insn))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
n_data = max(n_data, insn.n);
|
||||
|
||||
/* This is where the behavior of insn and insnlist deviate. */
|
||||
if (insn.n > MAX_SAMPLES) {
|
||||
insn.n = MAX_SAMPLES;
|
||||
n_data = MAX_SAMPLES;
|
||||
}
|
||||
|
||||
data = kmalloc_array(n_data, sizeof(unsigned int), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (copy_from_user(&insn, arg, sizeof(insn))) {
|
||||
ret = -EFAULT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* This is where the behavior of insn and insnlist deviate. */
|
||||
if (insn.n > MAX_SAMPLES)
|
||||
insn.n = MAX_SAMPLES;
|
||||
if (insn.insn & INSN_MASK_WRITE) {
|
||||
if (copy_from_user(data,
|
||||
insn.data,
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* module/8255.h
|
||||
* Header file for 8255
|
||||
|
@ -258,8 +258,15 @@ static int apci3501_eeprom_insn_read(struct comedi_device *dev,
|
||||
{
|
||||
struct apci3501_private *devpriv = dev->private;
|
||||
unsigned short addr = CR_CHAN(insn->chanspec);
|
||||
unsigned int val;
|
||||
unsigned int i;
|
||||
|
||||
data[0] = apci3501_eeprom_readw(devpriv->amcc, 2 * addr);
|
||||
if (insn->n) {
|
||||
/* No point reading the same EEPROM location more than once. */
|
||||
val = apci3501_eeprom_readw(devpriv->amcc, 2 * addr);
|
||||
for (i = 0; i < insn->n; i++)
|
||||
data[i] = val;
|
||||
}
|
||||
|
||||
return insn->n;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* comedi/drivers/amplc_dio.h
|
||||
*
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* comedi/drivers/amplc_pc236.h
|
||||
* Header for "amplc_pc236", "amplc_pci236" and "amplc_pc236_common".
|
||||
|
@ -116,7 +116,7 @@
|
||||
#define PCIDAS_TRIG_SEL_ANALOG PCIDAS_TRIG_SEL(3) /* ext. analog trigger */
|
||||
#define PCIDAS_TRIG_SEL_MASK PCIDAS_TRIG_SEL(3) /* start trigger mask */
|
||||
#define PCIDAS_TRIG_POL BIT(2) /* invert trigger (1602 only) */
|
||||
#define PCIDAS_TRIG_MODE BIT(3) /* edge/level trigerred (1602 only) */
|
||||
#define PCIDAS_TRIG_MODE BIT(3) /* edge/level triggered (1602 only) */
|
||||
#define PCIDAS_TRIG_EN BIT(4) /* enable external start trigger */
|
||||
#define PCIDAS_TRIG_BURSTE BIT(5) /* burst mode enable */
|
||||
#define PCIDAS_TRIG_CLR BIT(7) /* clear external trigger */
|
||||
|
@ -3097,8 +3097,10 @@ static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
|
||||
{
|
||||
const struct pcidas64_board *board = dev->board_ptr;
|
||||
struct pcidas64_private *devpriv = dev->private;
|
||||
int chan = CR_CHAN(insn->chanspec);
|
||||
int range = CR_RANGE(insn->chanspec);
|
||||
unsigned int chan = CR_CHAN(insn->chanspec);
|
||||
unsigned int range = CR_RANGE(insn->chanspec);
|
||||
unsigned int val = s->readback[chan];
|
||||
unsigned int i;
|
||||
|
||||
/* do some initializing */
|
||||
writew(0, devpriv->main_iobase + DAC_CONTROL0_REG);
|
||||
@ -3108,20 +3110,24 @@ static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
|
||||
writew(devpriv->dac_control1_bits,
|
||||
devpriv->main_iobase + DAC_CONTROL1_REG);
|
||||
|
||||
/* write to channel */
|
||||
if (board->layout == LAYOUT_4020) {
|
||||
writew(data[0] & 0xff,
|
||||
devpriv->main_iobase + dac_lsb_4020_reg(chan));
|
||||
writew((data[0] >> 8) & 0xf,
|
||||
devpriv->main_iobase + dac_msb_4020_reg(chan));
|
||||
} else {
|
||||
writew(data[0], devpriv->main_iobase + dac_convert_reg(chan));
|
||||
for (i = 0; i < insn->n; i++) {
|
||||
/* write to channel */
|
||||
val = data[i];
|
||||
if (board->layout == LAYOUT_4020) {
|
||||
writew(val & 0xff,
|
||||
devpriv->main_iobase + dac_lsb_4020_reg(chan));
|
||||
writew((val >> 8) & 0xf,
|
||||
devpriv->main_iobase + dac_msb_4020_reg(chan));
|
||||
} else {
|
||||
writew(val,
|
||||
devpriv->main_iobase + dac_convert_reg(chan));
|
||||
}
|
||||
}
|
||||
|
||||
/* remember output value */
|
||||
s->readback[chan] = data[0];
|
||||
/* remember last output value */
|
||||
s->readback[chan] = val;
|
||||
|
||||
return 1;
|
||||
return insn->n;
|
||||
}
|
||||
|
||||
static void set_dac_control0_reg(struct comedi_device *dev,
|
||||
@ -3762,9 +3768,17 @@ static int eeprom_read_insn(struct comedi_device *dev,
|
||||
struct comedi_subdevice *s,
|
||||
struct comedi_insn *insn, unsigned int *data)
|
||||
{
|
||||
data[0] = read_eeprom(dev, CR_CHAN(insn->chanspec));
|
||||
unsigned int val;
|
||||
unsigned int i;
|
||||
|
||||
return 1;
|
||||
if (insn->n) {
|
||||
/* No point reading the same EEPROM location more than once. */
|
||||
val = read_eeprom(dev, CR_CHAN(insn->chanspec));
|
||||
for (i = 0; i < insn->n; i++)
|
||||
data[i] = val;
|
||||
}
|
||||
|
||||
return insn->n;
|
||||
}
|
||||
|
||||
/* Allocate and initialize the subdevice structures. */
|
||||
|
@ -291,6 +291,7 @@ static int cb_pcidda_ao_insn_write(struct comedi_device *dev,
|
||||
unsigned int channel = CR_CHAN(insn->chanspec);
|
||||
unsigned int range = CR_RANGE(insn->chanspec);
|
||||
unsigned int ctrl;
|
||||
unsigned int i;
|
||||
|
||||
if (range != devpriv->ao_range[channel])
|
||||
cb_pcidda_calibrate(dev, channel, range);
|
||||
@ -317,7 +318,8 @@ static int cb_pcidda_ao_insn_write(struct comedi_device *dev,
|
||||
|
||||
outw(ctrl, devpriv->daqio + CB_DDA_DA_CTRL_REG);
|
||||
|
||||
outw(data[0], devpriv->daqio + CB_DDA_DA_DATA_REG(channel));
|
||||
for (i = 0; i < insn->n; i++)
|
||||
outw(data[i], devpriv->daqio + CB_DDA_DA_DATA_REG(channel));
|
||||
|
||||
return insn->n;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* comedi_8254.h
|
||||
* Generic 8254 timer/counter support
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* COMEDI ISA DMA support functions
|
||||
* Copyright (c) 2014 H Hartley Sweeten <hsweeten@visionengravers.com>
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* das08.h
|
||||
*
|
||||
|
@ -40,7 +40,7 @@
|
||||
#define DT9812_MAX_WRITE_CMD_PIPE_SIZE 32
|
||||
#define DT9812_MAX_READ_CMD_PIPE_SIZE 32
|
||||
|
||||
/* usb_bulk_msg() timout in milliseconds */
|
||||
/* usb_bulk_msg() timeout in milliseconds */
|
||||
#define DT9812_USB_TIMEOUT 1000
|
||||
|
||||
/*
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* module/mite.h
|
||||
* Hardware driver for NI Mite PCI interface chip
|
||||
|
@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Header for ni_labpc ISA/PCMCIA/PCI drivers
|
||||
*
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user