aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2024-04-29 15:23:25 +1000
committerStephen Rothwell <sfr@canb.auug.org.au>2024-04-29 15:23:25 +1000
commit197f75685647db872962313662314c591cb140a1 (patch)
tree318e118e7e40fd245fcd3687b7290a3160d2d33a
parentffe3cea23602869aa0a7e43f1bf83435f9062e4d (diff)
parente22810ab3f5e0de0151a5a0d05a54cfc39a780a8 (diff)
downloadlinux-next-history-197f75685647db872962313662314c591cb140a1.tar.gz
Merge branch 'char-misc-next' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git
Notice: this object is not reachable from any branch.
Notice: this object is not reachable from any branch.
-rw-r--r--Documentation/ABI/testing/sysfs-bus-iio3
-rw-r--r--Documentation/ABI/testing/sysfs-bus-iio-ad9739a19
-rw-r--r--Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml2
-rw-r--r--Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml279
-rw-r--r--Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml213
-rw-r--r--Documentation/devicetree/bindings/iio/adc/allwinner,sun20i-d1-gpadc.yaml9
-rw-r--r--Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml95
-rw-r--r--Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml62
-rw-r--r--Documentation/devicetree/bindings/iio/dac/ti,dac5571.yaml1
-rw-r--r--Documentation/devicetree/bindings/iio/health/maxim,max30102.yaml12
-rw-r--r--Documentation/devicetree/bindings/iio/humidity/ti,hdc3020.yaml5
-rw-r--r--Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml1
-rw-r--r--Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml17
-rw-r--r--Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml20
-rw-r--r--Documentation/devicetree/bindings/iio/light/avago,apds9960.yaml44
-rw-r--r--Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml4
-rw-r--r--Documentation/iio/ad7944.rst130
-rw-r--r--Documentation/iio/index.rst1
-rw-r--r--Documentation/userspace-api/ioctl/ioctl-number.rst2
-rw-r--r--MAINTAINERS58
-rw-r--r--drivers/accessibility/speakup/devsynth.c59
-rw-r--r--drivers/accessibility/speakup/speakup.h2
-rw-r--r--drivers/accessibility/speakup/synth.c92
-rw-r--r--drivers/base/property.c14
-rw-r--r--drivers/cdx/controller/cdx_controller.c6
-rw-r--r--drivers/char/mem.c6
-rw-r--r--drivers/char/powernv-op-panel.c5
-rw-r--r--drivers/char/sonypi.c6
-rw-r--r--drivers/comedi/drivers/cb_pcidas64.c5
-rw-r--r--drivers/counter/counter-core.c4
-rw-r--r--drivers/counter/stm32-timer-cnt.c461
-rw-r--r--drivers/counter/ti-ecap-capture.c8
-rw-r--r--drivers/counter/ti-eqep.c6
-rw-r--r--drivers/hv/Makefile2
-rw-r--r--drivers/hv/channel_mgmt.c15
-rw-r--r--drivers/hv/hv_fcopy.c427
-rw-r--r--drivers/hv/hv_util.c12
-rw-r--r--drivers/hv/hyperv_vmbus.h5
-rw-r--r--drivers/iio/accel/adxl345.h36
-rw-r--r--drivers/iio/accel/adxl345_core.c92
-rw-r--r--drivers/iio/accel/adxl345_i2c.c2
-rw-r--r--drivers/iio/accel/adxl345_spi.c10
-rw-r--r--drivers/iio/accel/adxl367.c2
-rw-r--r--drivers/iio/accel/fxls8962af-core.c10
-rw-r--r--drivers/iio/accel/mma8452.c6
-rw-r--r--drivers/iio/adc/Kconfig27
-rw-r--r--drivers/iio/adc/Makefile2
-rw-r--r--drivers/iio/adc/ab8500-gpadc.c8
-rw-r--r--drivers/iio/adc/ad4130.c7
-rw-r--r--drivers/iio/adc/ad7124.c55
-rw-r--r--drivers/iio/adc/ad7173.c1180
-rw-r--r--drivers/iio/adc/ad7192.c38
-rw-r--r--drivers/iio/adc/ad7292.c13
-rw-r--r--drivers/iio/adc/ad7944.c524
-rw-r--r--drivers/iio/adc/ad_sigma_delta.c23
-rw-r--r--drivers/iio/adc/adi-axi-adc.c20
-rw-r--r--drivers/iio/adc/fsl-imx25-gcq.c140
-rw-r--r--drivers/iio/adc/hx711.c5
-rw-r--r--drivers/iio/adc/max11410.c27
-rw-r--r--drivers/iio/adc/pac1934.c77
-rw-r--r--drivers/iio/adc/qcom-spmi-adc5.c7
-rw-r--r--drivers/iio/adc/rcar-gyroadc.c21
-rw-r--r--drivers/iio/adc/rzg2l_adc.c11
-rw-r--r--drivers/iio/adc/spear_adc.c25
-rw-r--r--drivers/iio/adc/stm32-adc.c61
-rw-r--r--drivers/iio/adc/ti-ads131e08.c12
-rw-r--r--drivers/iio/adc/twl4030-madc.c19
-rw-r--r--drivers/iio/addac/ad74413r.c10
-rw-r--r--drivers/iio/buffer/industrialio-buffer-dma.c100
-rw-r--r--drivers/iio/buffer/industrialio-buffer-dmaengine.c86
-rw-r--r--drivers/iio/dac/Kconfig37
-rw-r--r--drivers/iio/dac/Makefile2
-rw-r--r--drivers/iio/dac/ad3552r.c110
-rw-r--r--drivers/iio/dac/ad5755.c24
-rw-r--r--drivers/iio/dac/ad5770r.c19
-rw-r--r--drivers/iio/dac/ad9739a.c463
-rw-r--r--drivers/iio/dac/adi-axi-dac.c635
-rw-r--r--drivers/iio/dac/ltc2688.c28
-rw-r--r--drivers/iio/dac/ti-dac5571.c3
-rw-r--r--drivers/iio/frequency/admfm2000.c24
-rw-r--r--drivers/iio/health/max30102.c2
-rw-r--r--drivers/iio/humidity/hdc3020.c111
-rw-r--r--drivers/iio/humidity/hts221_core.c2
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600.h2
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_core.c5
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c3
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c3
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_core.c542
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h36
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c17
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c83
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c2
-rw-r--r--drivers/iio/industrialio-backend.c179
-rw-r--r--drivers/iio/industrialio-buffer.c120
-rw-r--r--drivers/iio/industrialio-core.c49
-rw-r--r--drivers/iio/industrialio-trigger.c71
-rw-r--r--drivers/iio/inkern.c263
-rw-r--r--drivers/iio/light/Kconfig12
-rw-r--r--drivers/iio/light/Makefile1
-rw-r--r--drivers/iio/light/apds9306.c1361
-rw-r--r--drivers/iio/light/st_uvis25_core.c2
-rw-r--r--drivers/iio/pressure/bmp280-core.c16
-rw-r--r--drivers/iio/pressure/dps310.c138
-rw-r--r--drivers/iio/pressure/hsc030pa_spi.c7
-rw-r--r--drivers/iio/temperature/ltc2983.c142
-rw-r--r--drivers/misc/Kconfig11
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/ds1682.c37
-rw-r--r--drivers/misc/mei/bus.c2
-rw-r--r--drivers/misc/mei/hw.h2
-rw-r--r--drivers/misc/ntsync.c249
-rw-r--r--drivers/misc/ti-st/st_kim.c4
-rw-r--r--drivers/parport/parport_mfc3.c3
-rw-r--r--drivers/peci/core.c4
-rw-r--r--drivers/peci/device.c2
-rw-r--r--drivers/peci/internal.h6
-rw-r--r--drivers/uio/Kconfig18
-rw-r--r--drivers/uio/Makefile1
-rw-r--r--drivers/uio/uio.c24
-rw-r--r--drivers/uio/uio_fsl_elbc_gpcm.c6
-rw-r--r--drivers/uio/uio_hv_generic.c19
-rw-r--r--drivers/uio/uio_pdrv_genirq.c10
-rw-r--r--drivers/uio/uio_pruss.c255
-rw-r--r--include/linux/counter.h4
-rw-r--r--include/linux/hyperv.h2
-rw-r--r--include/linux/iio/adc/ad_sigma_delta.h3
-rw-r--r--include/linux/iio/backend.h49
-rw-r--r--include/linux/iio/buffer-dma.h4
-rw-r--r--include/linux/iio/buffer-dmaengine.h24
-rw-r--r--include/linux/mfd/stm32-timers.h13
-rw-r--r--include/linux/platform_data/uio_pruss.h18
-rw-r--r--include/linux/property.h22
-rw-r--r--include/uapi/linux/ntsync.h23
-rw-r--r--include/uapi/misc/pvpanic.h7
-rw-r--r--tools/hv/Build3
-rw-r--r--tools/hv/Makefile14
-rw-r--r--tools/hv/hv_fcopy_daemon.c266
-rw-r--r--tools/hv/hv_fcopy_uio_daemon.c490
-rw-r--r--tools/hv/vmbus_bufring.c318
-rw-r--r--tools/hv/vmbus_bufring.h158
-rw-r--r--tools/testing/nvdimm/test/ndtest.c5
141 files changed, 8934 insertions, 2425 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio
index 2e6d5ebfd3c73d..7cee78ad410856 100644
--- a/Documentation/ABI/testing/sysfs-bus-iio
+++ b/Documentation/ABI/testing/sysfs-bus-iio
@@ -243,7 +243,8 @@ Description:
less measurements. Units after application of scale and offset
are milli degrees Celsius.
-What: /sys/bus/iio/devices/iio:deviceX/in_tempX_input
+What: /sys/bus/iio/devices/iio:deviceX/in_tempY_input
+What: /sys/bus/iio/devices/iio:deviceX/in_temp_input
KernelVersion: 2.6.38
Contact: linux-iio@vger.kernel.org
Description:
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-ad9739a b/Documentation/ABI/testing/sysfs-bus-iio-ad9739a
new file mode 100644
index 00000000000000..ed59299e6f8d9a
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-ad9739a
@@ -0,0 +1,19 @@
+What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_operating_mode
+KernelVersion: 6.9
+Contact: linux-iio@vger.kernel.org
+Description:
+ DAC operating mode. One of the following modes can be selected:
+
+ * normal: This is DAC normal mode.
+ * mixed-mode: In this mode the output is effectively chopped at
+ the DAC sample rate. This has the effect of
+ reducing the power of the fundamental signal while
+ increasing the power of the images centered around
+ the DAC sample rate, thus improving the output
+ power of these images.
+
+What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_operating_mode_available
+KernelVersion: 6.9
+Contact: linux-iio@vger.kernel.org
+Description:
+ Available operating modes.
diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml
index 07cacc3f6a975b..280ed479ef5abc 100644
--- a/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml
+++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml
@@ -32,6 +32,8 @@ properties:
spi-cpol: true
+ spi-3wire: true
+
interrupts:
maxItems: 1
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml
new file mode 100644
index 00000000000000..ea6cfcd0aff463
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml
@@ -0,0 +1,279 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+# Copyright 2023 Analog Devices Inc.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ad7173.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD7173 ADC
+
+maintainers:
+ - Ceclan Dumitru <dumitru.ceclan@analog.com>
+
+description: |
+ Analog Devices AD717x ADC's:
+ The AD717x family offer a complete integrated Sigma-Delta ADC solution which
+ can be used in high precision, low noise single channel applications
+ (Life Science measurements) or higher speed multiplexed applications
+ (Factory Automation PLC Input modules). The Sigma-Delta ADC is intended
+ primarily for measurement of signals close to DC but also delivers
+ outstanding performance with input bandwidths out to ~10kHz.
+
+ Datasheets for supported chips:
+ https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-4.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/AD7173-8.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/AD7175-2.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/AD7175-8.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/AD7176-2.pdf
+ https://www.analog.com/media/en/technical-documentation/data-sheets/AD7177-2.pdf
+
+properties:
+ compatible:
+ enum:
+ - adi,ad7172-2
+ - adi,ad7172-4
+ - adi,ad7173-8
+ - adi,ad7175-2
+ - adi,ad7175-8
+ - adi,ad7176-2
+ - adi,ad7177-2
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ minItems: 1
+ items:
+ - description: |
+ Ready: multiplexed with SPI data out. While SPI CS is low,
+ can be used to indicate the completion of a conversion.
+
+ - description: |
+ Error: The three error bits in the status register (ADC_ERROR, CRC_ERROR,
+ and REG_ERROR) are OR'ed, inverted, and mapped to the ERROR pin.
+ Therefore, the ERROR pin indicates that an error has occurred.
+
+ interrupt-names:
+ minItems: 1
+ items:
+ - const: rdy
+ - const: err
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ spi-max-frequency:
+ maximum: 20000000
+
+ gpio-controller:
+ description: Marks the device node as a GPIO controller.
+
+ '#gpio-cells':
+ const: 2
+ description:
+ The first cell is the GPIO number and the second cell specifies
+ GPIO flags, as defined in <dt-bindings/gpio/gpio.h>.
+
+ vref-supply:
+ description: |
+ Differential external reference supply used for conversion. The reference
+ voltage (Vref) specified here must be the voltage difference between the
+ REF+ and REF- pins: Vref = (REF+) - (REF-).
+
+ vref2-supply:
+ description: |
+ Differential external reference supply used for conversion. The reference
+ voltage (Vref2) specified here must be the voltage difference between the
+ REF2+ and REF2- pins: Vref2 = (REF2+) - (REF2-).
+
+ avdd-supply:
+ description: Avdd supply, can be used as reference for conversion.
+ This supply is referenced to AVSS, voltage specified here
+ represents (AVDD1 - AVSS).
+
+ avdd2-supply:
+ description: Avdd2 supply, used as the input to the internal voltage regulator.
+ This supply is referenced to AVSS, voltage specified here
+ represents (AVDD2 - AVSS).
+
+ iovdd-supply:
+ description: iovdd supply, used for the chip digital interface.
+
+ clocks:
+ maxItems: 1
+ description: |
+ Optional external clock source. Can include one clock source: external
+ clock or external crystal.
+
+ clock-names:
+ enum:
+ - ext-clk
+ - xtal
+
+ '#clock-cells':
+ const: 0
+
+patternProperties:
+ "^channel@[0-9a-f]$":
+ type: object
+ $ref: adc.yaml
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 15
+
+ diff-channels:
+ items:
+ minimum: 0
+ maximum: 31
+
+ adi,reference-select:
+ description: |
+ Select the reference source to use when converting on
+ the specific channel. Valid values are:
+ vref : REF+ /REF−
+ vref2 : REF2+ /REF2−
+ refout-avss: REFOUT/AVSS (Internal reference)
+ avdd : AVDD /AVSS
+
+ External reference ref2 only available on ad7173-8 and ad7172-4.
+ Internal reference refout-avss not available on ad7172-4.
+
+ If not specified, internal reference used (if available).
+ $ref: /schemas/types.yaml#/definitions/string
+ enum:
+ - vref
+ - vref2
+ - refout-avss
+ - avdd
+ default: refout-avss
+
+ required:
+ - reg
+ - diff-channels
+
+required:
+ - compatible
+ - reg
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+ # Only ad7172-4, ad7173-8 and ad7175-8 support vref2
+ # Other models have [0-3] channel registers
+ - if:
+ properties:
+ compatible:
+ not:
+ contains:
+ enum:
+ - adi,ad7172-4
+ - adi,ad7173-8
+ - adi,ad7175-8
+ then:
+ properties:
+ vref2-supply: false
+ patternProperties:
+ "^channel@[0-9a-f]$":
+ properties:
+ adi,reference-select:
+ enum:
+ - vref
+ - refout-avss
+ - avdd
+ reg:
+ maximum: 3
+
+ # Model ad7172-4 does not support internal reference
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: adi,ad7172-4
+ then:
+ patternProperties:
+ "^channel@[0-9a-f]$":
+ properties:
+ reg:
+ maximum: 7
+ adi,reference-select:
+ enum:
+ - vref
+ - vref2
+ - avdd
+ required:
+ - adi,reference-select
+
+ - if:
+ anyOf:
+ - required: [clock-names]
+ - required: [clocks]
+ then:
+ properties:
+ '#clock-cells': false
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "adi,ad7173-8";
+ reg = <0>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ interrupts = <25 IRQ_TYPE_EDGE_FALLING>;
+ interrupt-names = "rdy";
+ interrupt-parent = <&gpio>;
+ spi-max-frequency = <5000000>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ #clock-cells = <0>;
+
+ vref-supply = <&dummy_regulator>;
+
+ channel@0 {
+ reg = <0>;
+ bipolar;
+ diff-channels = <0 1>;
+ adi,reference-select = "vref";
+ };
+
+ channel@1 {
+ reg = <1>;
+ diff-channels = <2 3>;
+ };
+
+ channel@2 {
+ reg = <2>;
+ bipolar;
+ diff-channels = <4 5>;
+ };
+
+ channel@3 {
+ reg = <3>;
+ bipolar;
+ diff-channels = <6 7>;
+ };
+
+ channel@4 {
+ reg = <4>;
+ diff-channels = <8 9>;
+ adi,reference-select = "avdd";
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml
new file mode 100644
index 00000000000000..d17d184842d329
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml
@@ -0,0 +1,213 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ad7944.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices PulSAR LFCSP Analog to Digital Converters
+
+maintainers:
+ - Michael Hennerich <Michael.Hennerich@analog.com>
+ - Nuno Sá <nuno.sa@analog.com>
+
+description: |
+ A family of pin-compatible single channel differential analog to digital
+ converters with SPI support in a LFCSP package.
+
+ * https://www.analog.com/en/products/ad7944.html
+ * https://www.analog.com/en/products/ad7985.html
+ * https://www.analog.com/en/products/ad7986.html
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ enum:
+ - adi,ad7944
+ - adi,ad7985
+ - adi,ad7986
+
+ reg:
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 111111111
+
+ spi-cpol: true
+ spi-cpha: true
+
+ adi,spi-mode:
+ $ref: /schemas/types.yaml#/definitions/string
+ enum: [ single, chain ]
+ description: |
+ This property indicates the SPI wiring configuration.
+
+ When this property is omitted, it is assumed that the device is using what
+ the datasheet calls "4-wire mode". This is the conventional SPI mode used
+ when there are multiple devices on the same bus. In this mode, the CNV
+ line is used to initiate the conversion and the SDI line is connected to
+ CS on the SPI controller.
+
+ When this property is present, it indicates that the device is using one
+ of the following alternative wiring configurations:
+
+ * single: The datasheet calls this "3-wire mode". (NOTE: The datasheet's
+ definition of 3-wire mode is NOT at all related to the standard
+ spi-3wire property!) This mode is often used when the ADC is the only
+ device on the bus. In this mode, SDI is tied to VIO, and the CNV line
+ can be connected to the CS line of the SPI controller or to a GPIO, in
+ which case the CS line of the controller is unused.
+ * chain: The datasheet calls this "chain mode". This mode is used to save
+ on wiring when multiple ADCs are used. In this mode, the SDI line of
+ one chip is tied to the SDO of the next chip in the chain and the SDI of
+ the last chip in the chain is tied to GND. Only the first chip in the
+ chain is connected to the SPI bus. The CNV line of all chips are tied
+ together. The CS line of the SPI controller can be used as the CNV line
+ only if it is active high.
+
+ '#daisy-chained-devices': true
+
+ avdd-supply:
+ description: A 2.5V supply that powers the analog circuitry.
+
+ dvdd-supply:
+ description: A 2.5V supply that powers the digital circuitry.
+
+ vio-supply:
+ description:
+ A 1.8V to 2.7V supply for the digital inputs and outputs.
+
+ bvdd-supply:
+ description:
+ A voltage supply for the buffered power. When using an external reference
+ without an internal buffer (PDREF high, REFIN low), this should be
+ connected to the same supply as ref-supply. Otherwise, when using an
+ internal reference or an external reference with an internal buffer, this
+ is connected to a 5V supply.
+
+ ref-supply:
+ description:
+ Voltage regulator for the external reference voltage (REF). This property
+ is omitted when using an internal reference.
+
+ refin-supply:
+ description:
+ Voltage regulator for the reference buffer input (REFIN). When using an
+ external buffer with internal reference, this should be connected to a
+ 1.2V external reference voltage supply. Otherwise, this property is
+ omitted.
+
+ cnv-gpios:
+ description:
+ The Convert Input (CNV). This input has multiple functions. It initiates
+ the conversions and selects the SPI mode of the device (chain or CS). In
+ 'single' mode, this property is omitted if the CNV pin is connected to the
+ CS line of the SPI controller.
+ maxItems: 1
+
+ turbo-gpios:
+ description:
+ GPIO connected to the TURBO line. If omitted, it is assumed that the TURBO
+ line is hard-wired and the state is determined by the adi,always-turbo
+ property.
+ maxItems: 1
+
+ adi,always-turbo:
+ type: boolean
+ description:
+ When present, this property indicates that the TURBO line is hard-wired
+ and the state is always high. If neither this property nor turbo-gpios is
+ present, the TURBO line is assumed to be hard-wired and the state is
+ always low.
+
+ interrupts:
+ description:
+ The SDO pin can also function as a busy indicator. This node should be
+ connected to an interrupt that is triggered when the SDO line goes low
+ while the SDI line is high and the CNV line is low ('single' mode) or the
+ SDI line is low and the CNV line is high ('multi' mode); or when the SDO
+ line goes high while the SDI and CNV lines are high (chain mode),
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - avdd-supply
+ - dvdd-supply
+ - vio-supply
+ - bvdd-supply
+
+allOf:
+ # ref-supply and refin-supply are mutually exclusive (neither is also valid)
+ - if:
+ required:
+ - ref-supply
+ then:
+ properties:
+ refin-supply: false
+ - if:
+ required:
+ - refin-supply
+ then:
+ properties:
+ ref-supply: false
+ # in '4-wire' mode, cnv-gpios is required, for other modes it is optional
+ - if:
+ not:
+ required:
+ - adi,spi-mode
+ then:
+ required:
+ - cnv-gpios
+ # chain mode has lower SCLK max rate and doesn't work when TURBO is enabled
+ - if:
+ required:
+ - adi,spi-mode
+ properties:
+ adi,spi-mode:
+ const: chain
+ then:
+ properties:
+ spi-max-frequency:
+ maximum: 90909090
+ adi,always-turbo: false
+ required:
+ - '#daisy-chained-devices'
+ else:
+ properties:
+ '#daisy-chained-devices': false
+ # turbo-gpios and adi,always-turbo are mutually exclusive
+ - if:
+ required:
+ - turbo-gpios
+ then:
+ properties:
+ adi,always-turbo: false
+ - if:
+ required:
+ - adi,always-turbo
+ then:
+ properties:
+ turbo-gpios: false
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ adc@0 {
+ compatible = "adi,ad7944";
+ reg = <0>;
+ spi-cpha;
+ spi-max-frequency = <111111111>;
+ avdd-supply = <&supply_2_5V>;
+ dvdd-supply = <&supply_2_5V>;
+ vio-supply = <&supply_1_8V>;
+ bvdd-supply = <&supply_5V>;
+ cnv-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
+ turbo-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/iio/adc/allwinner,sun20i-d1-gpadc.yaml b/Documentation/devicetree/bindings/iio/adc/allwinner,sun20i-d1-gpadc.yaml
index 7ef46c90ebc8ea..da605a051b9497 100644
--- a/Documentation/devicetree/bindings/iio/adc/allwinner,sun20i-d1-gpadc.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/allwinner,sun20i-d1-gpadc.yaml
@@ -11,8 +11,13 @@ maintainers:
properties:
compatible:
- enum:
- - allwinner,sun20i-d1-gpadc
+ oneOf:
+ - enum:
+ - allwinner,sun20i-d1-gpadc
+ - items:
+ - enum:
+ - allwinner,sun50i-h616-gpadc
+ - const: allwinner,sun20i-d1-gpadc
"#io-channel-cells":
const: 1
diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml
new file mode 100644
index 00000000000000..c0b36476113ac4
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml
@@ -0,0 +1,95 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/dac/adi,ad9739a.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD9739A RF DAC
+
+maintainers:
+ - Dragos Bogdan <dragos.bogdan@analog.com>
+ - Nuno Sa <nuno.sa@analog.com>
+
+description: |
+ The AD9739A is a 14-bit, 2.5 GSPS high performance RF DACs that are capable
+ of synthesizing wideband signals from dc up to 3 GHz.
+
+ https://www.analog.com/media/en/technical-documentation/data-sheets/ad9737a_9739a.pdf
+
+properties:
+ compatible:
+ enum:
+ - adi,ad9739a
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ vdd-3p3-supply:
+ description: 3.3V Digital input supply.
+
+ vdd-supply:
+ description: 1.8V Digital input supply.
+
+ vdda-supply:
+ description: 3.3V Analog input supply.
+
+ vddc-supply:
+ description: 1.8V Clock input supply.
+
+ vref-supply:
+ description: Input/Output reference supply.
+
+ io-backends:
+ maxItems: 1
+
+ adi,full-scale-microamp:
+ description: This property represents the DAC full scale current.
+ minimum: 8580
+ maximum: 31700
+ default: 20000
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - io-backends
+ - vdd-3p3-supply
+ - vdd-supply
+ - vdda-supply
+ - vddc-supply
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ dac@0 {
+ compatible = "adi,ad9739a";
+ reg = <0>;
+
+ clocks = <&dac_clk>;
+
+ io-backends = <&iio_backend>;
+
+ vdd-3p3-supply = <&vdd_3_3>;
+ vdd-supply = <&vdd>;
+ vdda-supply = <&vdd_3_3>;
+ vddc-supply = <&vdd>;
+ };
+ };
+...
diff --git a/Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml b/Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml
new file mode 100644
index 00000000000000..a55e9bfc66d746
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/dac/adi,axi-dac.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AXI DAC IP core
+
+maintainers:
+ - Nuno Sa <nuno.sa@analog.com>
+
+description: |
+ Analog Devices Generic AXI DAC IP core for interfacing a DAC device
+ with a high speed serial (JESD204B/C) or source synchronous parallel
+ interface (LVDS/CMOS).
+ Usually, some other interface type (i.e SPI) is used as a control
+ interface for the actual DAC, while this IP core will interface
+ to the data-lines of the DAC and handle the streaming of data from
+ memory via DMA into the DAC.
+
+ https://wiki.analog.com/resources/fpga/docs/axi_dac_ip
+
+properties:
+ compatible:
+ enum:
+ - adi,axi-dac-9.1.b
+
+ reg:
+ maxItems: 1
+
+ dmas:
+ maxItems: 1
+
+ dma-names:
+ items:
+ - const: tx
+
+ clocks:
+ maxItems: 1
+
+ '#io-backend-cells':
+ const: 0
+
+required:
+ - compatible
+ - dmas
+ - reg
+ - clocks
+
+additionalProperties: false
+
+examples:
+ - |
+ dac@44a00000 {
+ compatible = "adi,axi-dac-9.1.b";
+ reg = <0x44a00000 0x10000>;
+ dmas = <&tx_dma 0>;
+ dma-names = "tx";
+ #io-backend-cells = <0>;
+ clocks = <&axi_clk>;
+ };
+...
diff --git a/Documentation/devicetree/bindings/iio/dac/ti,dac5571.yaml b/Documentation/devicetree/bindings/iio/dac/ti,dac5571.yaml
index 79da0323c327ee..e59db861e2eb10 100644
--- a/Documentation/devicetree/bindings/iio/dac/ti,dac5571.yaml
+++ b/Documentation/devicetree/bindings/iio/dac/ti,dac5571.yaml
@@ -21,6 +21,7 @@ properties:
- ti,dac5573
- ti,dac6573
- ti,dac7573
+ - ti,dac081c081
- ti,dac121c081
reg:
diff --git a/Documentation/devicetree/bindings/iio/health/maxim,max30102.yaml b/Documentation/devicetree/bindings/iio/health/maxim,max30102.yaml
index eed0df9d3a2322..205d352ab46727 100644
--- a/Documentation/devicetree/bindings/iio/health/maxim,max30102.yaml
+++ b/Documentation/devicetree/bindings/iio/health/maxim,max30102.yaml
@@ -4,16 +4,20 @@
$id: http://devicetree.org/schemas/iio/health/maxim,max30102.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Maxim MAX30102 heart rate and pulse oximeter and MAX30105 particle-sensor
+title: Maxim MAX30101/2 heart rate and pulse oximeter and MAX30105 particle-sensor
maintainers:
- Matt Ranostay <matt.ranostay@konsulko.com>
properties:
compatible:
- enum:
- - maxim,max30102
- - maxim,max30105
+ oneOf:
+ - enum:
+ - maxim,max30102
+ - maxim,max30105
+ - items:
+ - const: maxim,max30101
+ - const: maxim,max30105
reg:
maxItems: 1
diff --git a/Documentation/devicetree/bindings/iio/humidity/ti,hdc3020.yaml b/Documentation/devicetree/bindings/iio/humidity/ti,hdc3020.yaml
index 8b5dedd1a598cf..b375d307513fec 100644
--- a/Documentation/devicetree/bindings/iio/humidity/ti,hdc3020.yaml
+++ b/Documentation/devicetree/bindings/iio/humidity/ti,hdc3020.yaml
@@ -34,6 +34,9 @@ properties:
reg:
maxItems: 1
+ reset-gpios:
+ maxItems: 1
+
required:
- compatible
- reg
@@ -43,6 +46,7 @@ additionalProperties: false
examples:
- |
+ #include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
@@ -54,5 +58,6 @@ examples:
vdd-supply = <&vcc_3v3>;
interrupt-parent = <&gpio3>;
interrupts = <23 IRQ_TYPE_EDGE_RISING>;
+ reset-gpios = <&gpio3 27 GPIO_ACTIVE_LOW>;
};
};
diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml
index 7cd05bcbee317c..5e0bed2c45ded3 100644
--- a/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml
+++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml
@@ -32,6 +32,7 @@ properties:
- invensense,icm42605
- invensense,icm42622
- invensense,icm42631
+ - invensense,icm42688
reg:
maxItems: 1
diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml
index 297b8a1a7ffbca..587ff2bced2ddd 100644
--- a/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml
+++ b/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml
@@ -62,14 +62,15 @@ properties:
allOf:
- $ref: /schemas/spi/spi-peripheral-props.yaml#
- if:
- not:
- properties:
- compatible:
- contains:
- enum:
- - invensense,mpu9150
- - invensense,mpu9250
- - invensense,mpu9255
+ properties:
+ compatible:
+ contains:
+ enum:
+ - invensense,iam20680
+ - invensense,icm20602
+ - invensense,icm20608
+ - invensense,icm20609
+ - invensense,icm20689
then:
properties:
i2c-gate: false
diff --git a/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml b/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml
index 206af44f2c4333..b750096530bc63 100644
--- a/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml
+++ b/Documentation/devicetree/bindings/iio/light/avago,apds9300.yaml
@@ -4,17 +4,22 @@
$id: http://devicetree.org/schemas/iio/light/avago,apds9300.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Avago APDS9300 ambient light sensor
+title: Avago Gesture/RGB/ALS/Proximity sensors
maintainers:
- - Jonathan Cameron <jic23@kernel.org>
+ - Subhajit Ghosh <subhajit.ghosh@tweaklogic.com>
description: |
- Datasheet at https://www.avagotech.com/docs/AV02-1077EN
+ Datasheet: https://www.avagotech.com/docs/AV02-1077EN
+ Datasheet: https://www.avagotech.com/docs/AV02-4191EN
+ Datasheet: https://www.avagotech.com/docs/AV02-4755EN
properties:
compatible:
- const: avago,apds9300
+ enum:
+ - avago,apds9300
+ - avago,apds9306
+ - avago,apds9960
reg:
maxItems: 1
@@ -22,6 +27,8 @@ properties:
interrupts:
maxItems: 1
+ vdd-supply: true
+
additionalProperties: false
required:
@@ -30,6 +37,8 @@ required:
examples:
- |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
i2c {
#address-cells = <1>;
#size-cells = <0>;
@@ -38,7 +47,8 @@ examples:
compatible = "avago,apds9300";
reg = <0x39>;
interrupt-parent = <&gpio2>;
- interrupts = <29 8>;
+ interrupts = <29 IRQ_TYPE_LEVEL_LOW>;
+ vdd-supply = <&regulator_3v3>;
};
};
...
diff --git a/Documentation/devicetree/bindings/iio/light/avago,apds9960.yaml b/Documentation/devicetree/bindings/iio/light/avago,apds9960.yaml
deleted file mode 100644
index f06e0fda56299a..00000000000000
--- a/Documentation/devicetree/bindings/iio/light/avago,apds9960.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
-%YAML 1.2
----
-$id: http://devicetree.org/schemas/iio/light/avago,apds9960.yaml#
-$schema: http://devicetree.org/meta-schemas/core.yaml#
-
-title: Avago APDS9960 gesture/RGB/ALS/proximity sensor
-
-maintainers:
- - Matt Ranostay <matt.ranostay@konsulko.com>
-
-description: |
- Datasheet at https://www.avagotech.com/docs/AV02-4191EN
-
-properties:
- compatible:
- const: avago,apds9960
-
- reg:
- maxItems: 1
-
- interrupts:
- maxItems: 1
-
-additionalProperties: false
-
-required:
- - compatible
- - reg
-
-examples:
- - |
- i2c {
- #address-cells = <1>;
- #size-cells = <0>;
-
- light-sensor@39 {
- compatible = "avago,apds9960";
- reg = <0x39>;
- interrupt-parent = <&gpio1>;
- interrupts = <16 1>;
- };
- };
-...
diff --git a/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml b/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml
index dbb85135fd6688..312febeeb3bba8 100644
--- a/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml
+++ b/Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml
@@ -57,6 +57,8 @@ properties:
interrupts:
maxItems: 1
+ vdd-supply: true
+
adi,mux-delay-config-us:
description: |
Extra delay prior to each conversion, in addition to the internal 1ms
@@ -460,6 +462,7 @@ required:
- compatible
- reg
- interrupts
+ - vdd-supply
additionalProperties: false
@@ -489,6 +492,7 @@ examples:
#address-cells = <1>;
#size-cells = <0>;
+ vdd-supply = <&supply>;
interrupts = <20 IRQ_TYPE_EDGE_RISING>;
interrupt-parent = <&gpio>;
diff --git a/Documentation/iio/ad7944.rst b/Documentation/iio/ad7944.rst
new file mode 100644
index 00000000000000..f418ab1288ae35
--- /dev/null
+++ b/Documentation/iio/ad7944.rst
@@ -0,0 +1,130 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+=============
+AD7944 driver
+=============
+
+ADC driver for Analog Devices Inc. AD7944 and similar devices. The module name
+is ``ad7944``.
+
+
+Supported devices
+=================
+
+The following chips are supported by this driver:
+
+* `AD7944 <https://www.analog.com/AD7944>`_
+* `AD7985 <https://www.analog.com/AD7985>`_
+* `AD7986 <https://www.analog.com/AD7986>`_
+
+
+Supported features
+==================
+
+SPI wiring modes
+----------------
+
+The driver currently supports two of the many possible SPI wiring configurations.
+
+CS mode, 3-wire, without busy indicator
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block::
+
+ +-------------+
+ +--------------------| CS |
+ v | |
+ VIO +--------------------+ | HOST |
+ | | CNV | | |
+ +--->| SDI AD7944 SDO |-------->| SDI |
+ | SCK | | |
+ +--------------------+ | |
+ ^ | |
+ +--------------------| SCLK |
+ +-------------+
+
+To select this mode in the device tree, set the ``adi,spi-mode`` property to
+``"single"`` and omit the ``cnv-gpios`` property.
+
+CS mode, 4-wire, without busy indicator
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. code-block::
+
+ +-------------+
+ +-----------------------------------| CS |
+ | | |
+ | +--------------------| GPIO |
+ | v | |
+ | +--------------------+ | HOST |
+ | | CNV | | |
+ +--->| SDI AD7944 SDO |-------->| SDI |
+ | SCK | | |
+ +--------------------+ | |
+ ^ | |
+ +--------------------| SCLK |
+ +-------------+
+
+To select this mode in the device tree, omit the ``adi,spi-mode`` property and
+provide the ``cnv-gpios`` property.
+
+Reference voltage
+-----------------
+
+All 3 possible reference voltage sources are supported:
+
+- Internal reference
+- External 1.2V reference and internal buffer
+- External reference
+
+The source is determined by the device tree. If ``ref-supply`` is present, then
+the external reference is used. If ``refin-supply`` is present, then the internal
+buffer is used. If neither is present, then the internal reference is used.
+
+Unimplemented features
+----------------------
+
+- ``BUSY`` indication
+- ``TURBO`` mode
+- Daisy chain mode
+
+
+Device attributes
+=================
+
+There are two types of ADCs in this family, pseudo-differential and fully
+differential. The channel name is different depending on the type of ADC.
+
+Pseudo-differential ADCs
+------------------------
+
+AD7944 and AD7985 are pseudo-differential ADCs and have the following attributes:
+
++---------------------------------------+--------------------------------------------------------------+
+| Attribute | Description |
++=======================================+==============================================================+
+| ``in_voltage0_raw`` | Raw ADC voltage value (*IN+* referenced to ground sense). |
++---------------------------------------+--------------------------------------------------------------+
+| ``in_voltage0_scale`` | Scale factor to convert raw value to mV. |
++---------------------------------------+--------------------------------------------------------------+
+
+Fully-differential ADCs
+-----------------------
+
+AD7986 is a fully-differential ADC and has the following attributes:
+
++---------------------------------------+--------------------------------------------------------------+
+| Attribute | Description |
++=======================================+==============================================================+
+| ``in_voltage0-voltage1_raw`` | Raw ADC voltage value (*IN+* - *IN-*). |
++---------------------------------------+--------------------------------------------------------------+
+| ``in_voltage0-voltage1_scale`` | Scale factor to convert raw value to mV. |
++---------------------------------------+--------------------------------------------------------------+
+
+
+Device buffers
+==============
+
+This driver supports IIO triggered buffers.
+
+See :doc:`iio_devbuf` for more information.
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index 30b09eefe75edf..fb6f9d7432116e 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -16,6 +16,7 @@ Industrial I/O Kernel Drivers
.. toctree::
:maxdepth: 1
+ ad7944
adis16475
bno055
ep93xx_adc
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index c472423412bf27..a141e8e65c5d3a 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -174,6 +174,8 @@ Code Seq# Include File Comments
'M' 00-0F drivers/video/fsl-diu-fb.h conflict!
'N' 00-1F drivers/usb/scanner.h
'N' 40-7F drivers/block/nvme.c
+'N' 80-8F uapi/linux/ntsync.h NT synchronization primitives
+ <mailto:wine-devel@winehq.org>
'O' 00-06 mtd/ubi-user.h UBI
'P' all linux/soundcard.h conflict!
'P' 60-6F sound/sscape_ioctl.h conflict!
diff --git a/MAINTAINERS b/MAINTAINERS
index 6d22a60318ed6f..de42cb690160f5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -210,44 +210,44 @@ S: Maintained
F: drivers/hwmon/abituguru3.c
ACCES 104-DIO-48E GPIO DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-gpio@vger.kernel.org
S: Maintained
F: drivers/gpio/gpio-104-dio-48e.c
ACCES 104-IDI-48 GPIO DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-gpio@vger.kernel.org
S: Maintained
F: drivers/gpio/gpio-104-idi-48.c
ACCES 104-IDIO-16 GPIO DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-gpio@vger.kernel.org
S: Maintained
F: drivers/gpio/gpio-104-idio-16.c
ACCES 104-QUAD-8 DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-iio@vger.kernel.org
S: Maintained
F: drivers/counter/104-quad-8.c
ACCES IDIO-16 GPIO LIBRARY
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-gpio@vger.kernel.org
S: Maintained
F: drivers/gpio/gpio-idio-16.c
F: drivers/gpio/gpio-idio-16.h
ACCES PCI-IDIO-16 GPIO DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-gpio@vger.kernel.org
S: Maintained
F: drivers/gpio/gpio-pci-idio-16.c
ACCES PCIe-IDIO-24 GPIO DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-gpio@vger.kernel.org
S: Maintained
F: drivers/gpio/gpio-pcie-idio-24.c
@@ -453,6 +453,16 @@ W: http://wiki.analog.com/AD7879
W: https://ez.analog.com/linux-software-drivers
F: drivers/input/touchscreen/ad7879.c
+AD7944 ADC DRIVER (AD7944/AD7985/AD7986)
+M: Michael Hennerich <michael.hennerich@analog.com>
+M: Nuno Sá <nuno.sa@analog.com>
+R: David Lechner <dlechner@baylibre.com>
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/adc/adi,ad7944.yaml
+F: Documentation/iio/ad7944.rst
+F: drivers/iio/adc/ad7944.c
+
ADAFRUIT MINI I2C GAMEPAD
M: Anshul Dalal <anshulusr@gmail.com>
L: linux-input@vger.kernel.org
@@ -1243,6 +1253,15 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ad7780.yaml
F: drivers/iio/adc/ad7780.c
+ANALOG DEVICES INC AD9739a DRIVER
+M: Nuno Sa <nuno.sa@analog.com>
+M: Dragos Bogdan <dragos.bogdan@analog.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml
+F: drivers/iio/dac/ad9739a.c
+
ANALOG DEVICES INC ADA4250 DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
L: linux-iio@vger.kernel.org
@@ -1408,6 +1427,14 @@ F: sound/soc/codecs/adav*
F: sound/soc/codecs/sigmadsp.*
F: sound/soc/codecs/ssm*
+ANALOG DEVICES INC AXI DAC DRIVER
+M: Nuno Sa <nuno.sa@analog.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/dac/adi,axi-dac.yaml
+F: drivers/iio/dac/adi-axi-dac.c
+
ANALOG DEVICES INC DMA DRIVERS
M: Lars-Peter Clausen <lars@metafoo.de>
S: Supported
@@ -1472,7 +1499,7 @@ S: Maintained
F: sound/aoa/
APEX EMBEDDED SYSTEMS STX104 IIO DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-iio@vger.kernel.org
S: Maintained
F: drivers/iio/addac/stx104.c
@@ -5549,7 +5576,7 @@ F: Documentation/hwmon/corsair-psu.rst
F: drivers/hwmon/corsair-psu.c
COUNTER SUBSYSTEM
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-iio@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/wbg/counter.git
@@ -6337,7 +6364,7 @@ F: include/sound/da[79]*.h
F: sound/soc/codecs/da[79]*.[ch]
DIAMOND SYSTEMS GPIO-MM GPIO DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-gpio@vger.kernel.org
S: Maintained
F: drivers/gpio/gpio-gpio-mm.c
@@ -10647,6 +10674,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git
F: Documentation/ABI/testing/configfs-iio*
F: Documentation/ABI/testing/sysfs-bus-iio*
F: Documentation/devicetree/bindings/iio/
+F: Documentation/iio/
F: drivers/iio/
F: drivers/staging/iio/
F: include/dt-bindings/iio/
@@ -10854,14 +10882,14 @@ S: Maintained
F: drivers/video/fbdev/i810/
INTEL 8254 COUNTER DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-iio@vger.kernel.org
S: Maintained
F: drivers/counter/i8254.c
F: include/linux/i8254.h
INTEL 8255 GPIO DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-gpio@vger.kernel.org
S: Maintained
F: drivers/gpio/gpio-i8255.c
@@ -11564,7 +11592,7 @@ F: drivers/irqchip/
F: include/linux/irqchip.h
ISA
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
S: Maintained
F: Documentation/driver-api/isa.rst
F: drivers/base/isa.c
@@ -13597,7 +13625,7 @@ F: drivers/net/mdio/mdio-regmap.c
F: include/linux/mdio/mdio-regmap.h
MEASUREMENT COMPUTING CIO-DAC IIO DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-iio@vger.kernel.org
S: Maintained
F: drivers/iio/dac/cio-dac.c
@@ -24089,7 +24117,7 @@ S: Orphan
F: drivers/watchdog/ebc-c384_wdt.c
WINSYSTEMS WS16C48 GPIO DRIVER
-M: William Breathitt Gray <william.gray@linaro.org>
+M: William Breathitt Gray <wbg@kernel.org>
L: linux-gpio@vger.kernel.org
S: Maintained
F: drivers/gpio/gpio-ws16c48.c
diff --git a/drivers/accessibility/speakup/devsynth.c b/drivers/accessibility/speakup/devsynth.c
index cb7e1114e8ebe2..e3d909bd048048 100644
--- a/drivers/accessibility/speakup/devsynth.c
+++ b/drivers/accessibility/speakup/devsynth.c
@@ -39,13 +39,13 @@ static ssize_t speakup_file_write(struct file *fp, const char __user *buffer,
static ssize_t speakup_file_writeu(struct file *fp, const char __user *buffer,
size_t nbytes, loff_t *ppos)
{
- size_t count = nbytes, want;
+ size_t count = nbytes, consumed, want;
const char __user *ptr = buffer;
size_t bytes;
unsigned long flags;
unsigned char buf[256];
u16 ubuf[256];
- size_t in, in2, out;
+ size_t in, out;
if (!synth)
return -ENODEV;
@@ -58,57 +58,24 @@ static ssize_t speakup_file_writeu(struct file *fp, const char __user *buffer,
return -EFAULT;
/* Convert to u16 */
- for (in = 0, out = 0; in < bytes; in++) {
- unsigned char c = buf[in];
- int nbytes = 8 - fls(c ^ 0xff);
- u32 value;
-
- switch (nbytes) {
- case 8: /* 0xff */
- case 7: /* 0xfe */
- case 1: /* 0x80 */
- /* Invalid, drop */
- goto drop;
-
- case 0:
- /* ASCII, copy */
- ubuf[out++] = c;
- continue;
+ for (in = 0, out = 0; in < bytes; in += consumed) {
+ s32 value;
- default:
- /* 2..6-byte UTF-8 */
+ value = synth_utf8_get(buf + in, bytes - in, &consumed, &want);
+ if (value == -1) {
+ /* Invalid or incomplete */
- if (bytes - in < nbytes) {
+ if (want > bytes - in)
/* We don't have it all yet, stop here
* and wait for the rest
*/
bytes = in;
- want = nbytes;
- continue;
- }
-
- /* First byte */
- value = c & ((1u << (7 - nbytes)) - 1);
-
- /* Other bytes */
- for (in2 = 2; in2 <= nbytes; in2++) {
- c = buf[in + 1];
- if ((c & 0xc0) != 0x80) {
- /* Invalid, drop the head */
- want = 1;
- goto drop;
- }
- value = (value << 6) | (c & 0x3f);
- in++;
- }
-
- if (value < 0x10000)
- ubuf[out++] = value;
- want = 1;
- break;
+
+ continue;
}
-drop:
- /* empty statement */;
+
+ if (value < 0x10000)
+ ubuf[out++] = value;
}
count -= bytes;
diff --git a/drivers/accessibility/speakup/speakup.h b/drivers/accessibility/speakup/speakup.h
index 364fde99749ed1..54f1226ea06191 100644
--- a/drivers/accessibility/speakup/speakup.h
+++ b/drivers/accessibility/speakup/speakup.h
@@ -76,7 +76,9 @@ int speakup_paste_selection(struct tty_struct *tty);
void speakup_cancel_paste(void);
void speakup_register_devsynth(void);
void speakup_unregister_devsynth(void);
+s32 synth_utf8_get(const char *buf, size_t count, size_t *consumed, size_t *want);
void synth_write(const char *buf, size_t count);
+void synth_writeu(const char *buf, size_t count);
int synth_supports_indexing(void);
extern struct vc_data *spk_sel_cons;
diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c
index 45f90610313382..85062e605d796d 100644
--- a/drivers/accessibility/speakup/synth.c
+++ b/drivers/accessibility/speakup/synth.c
@@ -217,10 +217,95 @@ void synth_write(const char *_buf, size_t count)
synth_start();
}
+/* Consume one utf-8 character from buf (that contains up to count bytes),
+ * returns the unicode codepoint if valid, -1 otherwise.
+ * In all cases, returns the number of consumed bytes in *consumed,
+ * and the minimum number of bytes that would be needed for the next character
+ * in *want.
+ */
+s32 synth_utf8_get(const char *buf, size_t count, size_t *consumed, size_t *want)
+{
+ unsigned char c = buf[0];
+ int nbytes = 8 - fls(c ^ 0xff);
+ u32 value;
+ size_t i;
+
+ switch (nbytes) {
+ case 8: /* 0xff */
+ case 7: /* 0xfe */
+ case 1: /* 0x80 */
+ /* Invalid, drop */
+ *consumed = 1;
+ *want = 1;
+ return -1;
+
+ case 0:
+ /* ASCII, take as such */
+ *consumed = 1;
+ *want = 1;
+ return c;
+
+ default:
+ /* 2..6-byte UTF-8 */
+
+ if (count < nbytes) {
+ /* We don't have it all */
+ *consumed = 0;
+ *want = nbytes;
+ return -1;
+ }
+
+ /* First byte */
+ value = c & ((1u << (7 - nbytes)) - 1);
+
+ /* Other bytes */
+ for (i = 1; i < nbytes; i++) {
+ c = buf[i];
+ if ((c & 0xc0) != 0x80) {
+ /* Invalid, drop the head */
+ *consumed = i;
+ *want = 1;
+ return -1;
+ }
+ value = (value << 6) | (c & 0x3f);
+ }
+
+ *consumed = nbytes;
+ *want = 1;
+ return value;
+ }
+}
+
+void synth_writeu(const char *buf, size_t count)
+{
+ size_t i, consumed, want;
+
+ /* Convert to u16 */
+ for (i = 0; i < count; i++) {
+ s32 value;
+
+ value = synth_utf8_get(buf + i, count - i, &consumed, &want);
+ if (value == -1) {
+ /* Invalid or incomplete */
+
+ if (want > count - i)
+ /* We don't have it all, stop */
+ count = i;
+
+ continue;
+ }
+
+ if (value < 0x10000)
+ synth_buffer_add(value);
+ }
+
+ synth_start();
+}
+
void synth_printf(const char *fmt, ...)
{
va_list args;
- unsigned char buf[160], *p;
+ unsigned char buf[160];
int r;
va_start(args, fmt);
@@ -229,10 +314,7 @@ void synth_printf(const char *fmt, ...)
if (r > sizeof(buf) - 1)
r = sizeof(buf) - 1;
- p = buf;
- while (r--)
- synth_buffer_add(*p++);
- synth_start();
+ synth_writeu(buf, r);
}
EXPORT_SYMBOL_GPL(synth_printf);
diff --git a/drivers/base/property.c b/drivers/base/property.c
index 7324a704a9a118..6a3a434d0d6f42 100644
--- a/drivers/base/property.c
+++ b/drivers/base/property.c
@@ -869,20 +869,6 @@ struct fwnode_handle *fwnode_handle_get(struct fwnode_handle *fwnode)
EXPORT_SYMBOL_GPL(fwnode_handle_get);
/**
- * fwnode_handle_put - Drop reference to a device node
- * @fwnode: Pointer to the device node to drop the reference to.
- *
- * This has to be used when terminating device_for_each_child_node() iteration
- * with break or return to prevent stale device node references from being left
- * behind.
- */
-void fwnode_handle_put(struct fwnode_handle *fwnode)
-{
- fwnode_call_void_op(fwnode, put);
-}
-EXPORT_SYMBOL_GPL(fwnode_handle_put);
-
-/**
* fwnode_device_is_available - check if a device is available for use
* @fwnode: Pointer to the fwnode of the device.
*
diff --git a/drivers/cdx/controller/cdx_controller.c b/drivers/cdx/controller/cdx_controller.c
index 112a1541de6d7e..201f9a6fbde702 100644
--- a/drivers/cdx/controller/cdx_controller.c
+++ b/drivers/cdx/controller/cdx_controller.c
@@ -222,7 +222,7 @@ mcdi_init_fail:
return ret;
}
-static int xlnx_cdx_remove(struct platform_device *pdev)
+static void xlnx_cdx_remove(struct platform_device *pdev)
{
struct cdx_controller *cdx = platform_get_drvdata(pdev);
struct cdx_mcdi *cdx_mcdi = cdx->priv;
@@ -234,8 +234,6 @@ static int xlnx_cdx_remove(struct platform_device *pdev)
cdx_mcdi_finish(cdx_mcdi);
kfree(cdx_mcdi);
-
- return 0;
}
static const struct of_device_id cdx_match_table[] = {
@@ -252,7 +250,7 @@ static struct platform_driver cdx_pdriver = {
.of_match_table = cdx_match_table,
},
.probe = xlnx_cdx_probe,
- .remove = xlnx_cdx_remove,
+ .remove_new = xlnx_cdx_remove,
};
static int __init cdx_controller_init(void)
diff --git a/drivers/char/mem.c b/drivers/char/mem.c
index 9b80e622ae80a8..7c359cc406d5bc 100644
--- a/drivers/char/mem.c
+++ b/drivers/char/mem.c
@@ -383,6 +383,7 @@ static int mmap_mem(struct file *file, struct vm_area_struct *vma)
return 0;
}
+#ifdef CONFIG_DEVPORT
static ssize_t read_port(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
@@ -424,6 +425,7 @@ static ssize_t write_port(struct file *file, const char __user *buf,
*ppos = i;
return tmp-buf;
}
+#endif
static ssize_t read_null(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
@@ -653,12 +655,14 @@ static const struct file_operations null_fops = {
.uring_cmd = uring_cmd_null,
};
-static const struct file_operations __maybe_unused port_fops = {
+#ifdef CONFIG_DEVPORT
+static const struct file_operations port_fops = {
.llseek = memory_lseek,
.read = read_port,
.write = write_port,
.open = open_port,
};
+#endif
static const struct file_operations zero_fops = {
.llseek = zero_lseek,
diff --git a/drivers/char/powernv-op-panel.c b/drivers/char/powernv-op-panel.c
index 3c99696b145ee6..f2cff1a6fed520 100644
--- a/drivers/char/powernv-op-panel.c
+++ b/drivers/char/powernv-op-panel.c
@@ -195,12 +195,11 @@ free_oppanel_data:
return rc;
}
-static int oppanel_remove(struct platform_device *pdev)
+static void oppanel_remove(struct platform_device *pdev)
{
misc_deregister(&oppanel_dev);
kfree(oppanel_lines);
kfree(oppanel_data);
- return 0;
}
static const struct of_device_id oppanel_match[] = {
@@ -214,7 +213,7 @@ static struct platform_driver oppanel_driver = {
.of_match_table = oppanel_match,
},
.probe = oppanel_probe,
- .remove = oppanel_remove,
+ .remove_new = oppanel_remove,
};
module_platform_driver(oppanel_driver);
diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c
index 22d249333f530f..bb5115b1736a46 100644
--- a/drivers/char/sonypi.c
+++ b/drivers/char/sonypi.c
@@ -1408,7 +1408,7 @@ static int sonypi_probe(struct platform_device *dev)
return error;
}
-static int sonypi_remove(struct platform_device *dev)
+static void sonypi_remove(struct platform_device *dev)
{
sonypi_disable();
@@ -1432,8 +1432,6 @@ static int sonypi_remove(struct platform_device *dev)
}
kfifo_free(&sonypi_device.fifo);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
@@ -1470,7 +1468,7 @@ static struct platform_driver sonypi_driver = {
.pm = SONYPI_PM,
},
.probe = sonypi_probe,
- .remove = sonypi_remove,
+ .remove_new = sonypi_remove,
.shutdown = sonypi_shutdown,
};
diff --git a/drivers/comedi/drivers/cb_pcidas64.c b/drivers/comedi/drivers/cb_pcidas64.c
index ff19fc3859e47b..d398c6df948229 100644
--- a/drivers/comedi/drivers/cb_pcidas64.c
+++ b/drivers/comedi/drivers/cb_pcidas64.c
@@ -374,11 +374,6 @@ static inline u16 pipe_full_bits(u16 hw_status_bits)
return (hw_status_bits >> 10) & 0x3;
};
-static inline unsigned int dma_chain_flag_bits(u16 prepost_bits)
-{
- return (prepost_bits >> 6) & 0x3;
-}
-
static inline unsigned int adc_upper_read_ptr_code(u16 prepost_bits)
{
return (prepost_bits >> 12) & 0x3;
diff --git a/drivers/counter/counter-core.c b/drivers/counter/counter-core.c
index 3f24481fc04a12..893b4f0726d2e6 100644
--- a/drivers/counter/counter-core.c
+++ b/drivers/counter/counter-core.c
@@ -49,12 +49,12 @@ static void counter_device_release(struct device *dev)
kfree(container_of(counter, struct counter_device_allochelper, counter));
}
-static struct device_type counter_device_type = {
+static const struct device_type counter_device_type = {
.name = "counter_device",
.release = counter_device_release,
};
-static struct bus_type counter_bus_type = {
+static const struct bus_type counter_bus_type = {
.name = "counter",
.dev_name = "counter",
};
diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c
index 6206d2dc3d4709..0664ef969f797c 100644
--- a/drivers/counter/stm32-timer-cnt.c
+++ b/drivers/counter/stm32-timer-cnt.c
@@ -8,9 +8,11 @@
*
*/
#include <linux/counter.h>
+#include <linux/interrupt.h>
#include <linux/mfd/stm32-timers.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/types.h>
@@ -21,6 +23,12 @@
#define TIM_CCER_MASK (TIM_CCER_CC1P | TIM_CCER_CC1NP | \
TIM_CCER_CC2P | TIM_CCER_CC2NP)
+#define STM32_CH1_SIG 0
+#define STM32_CH2_SIG 1
+#define STM32_CLOCK_SIG 2
+#define STM32_CH3_SIG 3
+#define STM32_CH4_SIG 4
+
struct stm32_timer_regs {
u32 cr1;
u32 cnt;
@@ -34,6 +42,11 @@ struct stm32_timer_cnt {
u32 max_arr;
bool enabled;
struct stm32_timer_regs bak;
+ bool has_encoder;
+ unsigned int nchannels;
+ unsigned int nr_irqs;
+ spinlock_t lock; /* protects nb_ovf */
+ u64 nb_ovf;
};
static const enum counter_function stm32_count_functions[] = {
@@ -107,12 +120,18 @@ static int stm32_count_function_write(struct counter_device *counter,
sms = TIM_SMCR_SMS_SLAVE_MODE_DISABLED;
break;
case COUNTER_FUNCTION_QUADRATURE_X2_A:
+ if (!priv->has_encoder)
+ return -EOPNOTSUPP;
sms = TIM_SMCR_SMS_ENCODER_MODE_1;
break;
case COUNTER_FUNCTION_QUADRATURE_X2_B:
+ if (!priv->has_encoder)
+ return -EOPNOTSUPP;
sms = TIM_SMCR_SMS_ENCODER_MODE_2;
break;
case COUNTER_FUNCTION_QUADRATURE_X4:
+ if (!priv->has_encoder)
+ return -EOPNOTSUPP;
sms = TIM_SMCR_SMS_ENCODER_MODE_3;
break;
default:
@@ -216,11 +235,108 @@ static int stm32_count_enable_write(struct counter_device *counter,
return 0;
}
+static int stm32_count_prescaler_read(struct counter_device *counter,
+ struct counter_count *count, u64 *prescaler)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ u32 psc;
+
+ regmap_read(priv->regmap, TIM_PSC, &psc);
+
+ *prescaler = psc + 1;
+
+ return 0;
+}
+
+static int stm32_count_prescaler_write(struct counter_device *counter,
+ struct counter_count *count, u64 prescaler)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ u32 psc;
+
+ if (!prescaler || prescaler > MAX_TIM_PSC + 1)
+ return -ERANGE;
+
+ psc = prescaler - 1;
+
+ return regmap_write(priv->regmap, TIM_PSC, psc);
+}
+
+static int stm32_count_cap_read(struct counter_device *counter,
+ struct counter_count *count,
+ size_t ch, u64 *cap)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ u32 ccrx;
+
+ if (ch >= priv->nchannels)
+ return -EOPNOTSUPP;
+
+ switch (ch) {
+ case 0:
+ regmap_read(priv->regmap, TIM_CCR1, &ccrx);
+ break;
+ case 1:
+ regmap_read(priv->regmap, TIM_CCR2, &ccrx);
+ break;
+ case 2:
+ regmap_read(priv->regmap, TIM_CCR3, &ccrx);
+ break;
+ case 3:
+ regmap_read(priv->regmap, TIM_CCR4, &ccrx);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(counter->parent, "CCR%zu: 0x%08x\n", ch + 1, ccrx);
+
+ *cap = ccrx;
+
+ return 0;
+}
+
+static int stm32_count_nb_ovf_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+ *val = priv->nb_ovf;
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int stm32_count_nb_ovf_write(struct counter_device *counter,
+ struct counter_count *count, u64 val)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+ priv->nb_ovf = val;
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static DEFINE_COUNTER_ARRAY_CAPTURE(stm32_count_cap_array, 4);
+
static struct counter_comp stm32_count_ext[] = {
COUNTER_COMP_DIRECTION(stm32_count_direction_read),
COUNTER_COMP_ENABLE(stm32_count_enable_read, stm32_count_enable_write),
COUNTER_COMP_CEILING(stm32_count_ceiling_read,
stm32_count_ceiling_write),
+ COUNTER_COMP_COUNT_U64("prescaler", stm32_count_prescaler_read,
+ stm32_count_prescaler_write),
+ COUNTER_COMP_ARRAY_CAPTURE(stm32_count_cap_read, NULL, stm32_count_cap_array),
+ COUNTER_COMP_COUNT_U64("num_overflows", stm32_count_nb_ovf_read, stm32_count_nb_ovf_write),
+};
+
+static const enum counter_synapse_action stm32_clock_synapse_actions[] = {
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
};
static const enum counter_synapse_action stm32_synapse_actions[] = {
@@ -243,25 +359,152 @@ static int stm32_action_read(struct counter_device *counter,
switch (function) {
case COUNTER_FUNCTION_INCREASE:
/* counts on internal clock when CEN=1 */
- *action = COUNTER_SYNAPSE_ACTION_NONE;
+ if (synapse->signal->id == STM32_CLOCK_SIG)
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ else
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
case COUNTER_FUNCTION_QUADRATURE_X2_A:
/* counts up/down on TI1FP1 edge depending on TI2FP2 level */
- if (synapse->signal->id == count->synapses[0].signal->id)
+ if (synapse->signal->id == STM32_CH1_SIG)
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
else
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
case COUNTER_FUNCTION_QUADRATURE_X2_B:
/* counts up/down on TI2FP2 edge depending on TI1FP1 level */
- if (synapse->signal->id == count->synapses[1].signal->id)
+ if (synapse->signal->id == STM32_CH2_SIG)
*action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
else
*action = COUNTER_SYNAPSE_ACTION_NONE;
return 0;
case COUNTER_FUNCTION_QUADRATURE_X4:
/* counts up/down on both TI1FP1 and TI2FP2 edges */
- *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ if (synapse->signal->id == STM32_CH1_SIG || synapse->signal->id == STM32_CH2_SIG)
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ else
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+struct stm32_count_cc_regs {
+ u32 ccmr_reg;
+ u32 ccmr_mask;
+ u32 ccmr_bits;
+ u32 ccer_bits;
+};
+
+static const struct stm32_count_cc_regs stm32_cc[] = {
+ { TIM_CCMR1, TIM_CCMR_CC1S, TIM_CCMR_CC1S_TI1,
+ TIM_CCER_CC1E | TIM_CCER_CC1P | TIM_CCER_CC1NP },
+ { TIM_CCMR1, TIM_CCMR_CC2S, TIM_CCMR_CC2S_TI2,
+ TIM_CCER_CC2E | TIM_CCER_CC2P | TIM_CCER_CC2NP },
+ { TIM_CCMR2, TIM_CCMR_CC3S, TIM_CCMR_CC3S_TI3,
+ TIM_CCER_CC3E | TIM_CCER_CC3P | TIM_CCER_CC3NP },
+ { TIM_CCMR2, TIM_CCMR_CC4S, TIM_CCMR_CC4S_TI4,
+ TIM_CCER_CC4E | TIM_CCER_CC4P | TIM_CCER_CC4NP },
+};
+
+static int stm32_count_capture_configure(struct counter_device *counter, unsigned int ch,
+ bool enable)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ const struct stm32_count_cc_regs *cc;
+ u32 ccmr, ccer;
+
+ if (ch >= ARRAY_SIZE(stm32_cc) || ch >= priv->nchannels) {
+ dev_err(counter->parent, "invalid ch: %d\n", ch);
+ return -EINVAL;
+ }
+
+ cc = &stm32_cc[ch];
+
+ /*
+ * configure channel in input capture mode, map channel 1 on TI1, channel2 on TI2...
+ * Select both edges / non-inverted to trigger a capture.
+ */
+ if (enable) {
+ /* first clear possibly latched capture flag upon enabling */
+ if (!regmap_test_bits(priv->regmap, TIM_CCER, cc->ccer_bits))
+ regmap_write(priv->regmap, TIM_SR, ~TIM_SR_CC_IF(ch));
+ regmap_update_bits(priv->regmap, cc->ccmr_reg, cc->ccmr_mask,
+ cc->ccmr_bits);
+ regmap_set_bits(priv->regmap, TIM_CCER, cc->ccer_bits);
+ } else {
+ regmap_clear_bits(priv->regmap, TIM_CCER, cc->ccer_bits);
+ regmap_clear_bits(priv->regmap, cc->ccmr_reg, cc->ccmr_mask);
+ }
+
+ regmap_read(priv->regmap, cc->ccmr_reg, &ccmr);
+ regmap_read(priv->regmap, TIM_CCER, &ccer);
+ dev_dbg(counter->parent, "%s(%s) ch%d 0x%08x 0x%08x\n", __func__, enable ? "ena" : "dis",
+ ch, ccmr, ccer);
+
+ return 0;
+}
+
+static int stm32_count_events_configure(struct counter_device *counter)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ struct counter_event_node *event_node;
+ u32 dier = 0;
+ int i, ret;
+
+ list_for_each_entry(event_node, &counter->events_list, l) {
+ switch (event_node->event) {
+ case COUNTER_EVENT_OVERFLOW_UNDERFLOW:
+ /* first clear possibly latched UIF before enabling */
+ if (!regmap_test_bits(priv->regmap, TIM_DIER, TIM_DIER_UIE))
+ regmap_write(priv->regmap, TIM_SR, (u32)~TIM_SR_UIF);
+ dier |= TIM_DIER_UIE;
+ break;
+ case COUNTER_EVENT_CAPTURE:
+ ret = stm32_count_capture_configure(counter, event_node->channel, true);
+ if (ret)
+ return ret;
+ dier |= TIM_DIER_CC_IE(event_node->channel);
+ break;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+ }
+
+ /* Enable / disable all events at once, from events_list, so write all DIER bits */
+ regmap_write(priv->regmap, TIM_DIER, dier);
+
+ /* check for disabled capture events */
+ for (i = 0 ; i < priv->nchannels; i++) {
+ if (!(dier & TIM_DIER_CC_IE(i))) {
+ ret = stm32_count_capture_configure(counter, i, false);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int stm32_count_watch_validate(struct counter_device *counter,
+ const struct counter_watch *watch)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+
+ /* Interrupts are optional */
+ if (!priv->nr_irqs)
+ return -EOPNOTSUPP;
+
+ switch (watch->event) {
+ case COUNTER_EVENT_CAPTURE:
+ if (watch->channel >= priv->nchannels) {
+ dev_err(counter->parent, "Invalid channel %d\n", watch->channel);
+ return -EINVAL;
+ }
+ return 0;
+ case COUNTER_EVENT_OVERFLOW_UNDERFLOW:
return 0;
default:
return -EINVAL;
@@ -274,35 +517,89 @@ static const struct counter_ops stm32_timer_cnt_ops = {
.function_read = stm32_count_function_read,
.function_write = stm32_count_function_write,
.action_read = stm32_action_read,
+ .events_configure = stm32_count_events_configure,
+ .watch_validate = stm32_count_watch_validate,
+};
+
+static int stm32_count_clk_get_freq(struct counter_device *counter,
+ struct counter_signal *signal, u64 *freq)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+
+ *freq = clk_get_rate(priv->clk);
+
+ return 0;
+}
+
+static struct counter_comp stm32_count_clock_ext[] = {
+ COUNTER_COMP_FREQUENCY(stm32_count_clk_get_freq),
};
static struct counter_signal stm32_signals[] = {
+ /*
+ * Need to declare all the signals as a static array, and keep the signals order here,
+ * even if they're unused or unexisting on some timer instances. It's an abstraction,
+ * e.g. high level view of the counter features.
+ *
+ * Userspace programs may rely on signal0 to be "Channel 1", signal1 to be "Channel 2",
+ * and so on. When a signal is unexisting, the COUNTER_SYNAPSE_ACTION_NONE can be used,
+ * to indicate that a signal doesn't affect the counter.
+ */
{
- .id = 0,
- .name = "Channel 1 Quadrature A"
+ .id = STM32_CH1_SIG,
+ .name = "Channel 1"
},
{
- .id = 1,
- .name = "Channel 1 Quadrature B"
- }
+ .id = STM32_CH2_SIG,
+ .name = "Channel 2"
+ },
+ {
+ .id = STM32_CLOCK_SIG,
+ .name = "Clock",
+ .ext = stm32_count_clock_ext,
+ .num_ext = ARRAY_SIZE(stm32_count_clock_ext),
+ },
+ {
+ .id = STM32_CH3_SIG,
+ .name = "Channel 3"
+ },
+ {
+ .id = STM32_CH4_SIG,
+ .name = "Channel 4"
+ },
};
static struct counter_synapse stm32_count_synapses[] = {
{
.actions_list = stm32_synapse_actions,
.num_actions = ARRAY_SIZE(stm32_synapse_actions),
- .signal = &stm32_signals[0]
+ .signal = &stm32_signals[STM32_CH1_SIG]
},
{
.actions_list = stm32_synapse_actions,
.num_actions = ARRAY_SIZE(stm32_synapse_actions),
- .signal = &stm32_signals[1]
- }
+ .signal = &stm32_signals[STM32_CH2_SIG]
+ },
+ {
+ .actions_list = stm32_clock_synapse_actions,
+ .num_actions = ARRAY_SIZE(stm32_clock_synapse_actions),
+ .signal = &stm32_signals[STM32_CLOCK_SIG]
+ },
+ {
+ .actions_list = stm32_synapse_actions,
+ .num_actions = ARRAY_SIZE(stm32_synapse_actions),
+ .signal = &stm32_signals[STM32_CH3_SIG]
+ },
+ {
+ .actions_list = stm32_synapse_actions,
+ .num_actions = ARRAY_SIZE(stm32_synapse_actions),
+ .signal = &stm32_signals[STM32_CH4_SIG]
+ },
};
static struct counter_count stm32_counts = {
.id = 0,
- .name = "Channel 1 Count",
+ .name = "STM32 Timer Counter",
.functions_list = stm32_count_functions,
.num_functions = ARRAY_SIZE(stm32_count_functions),
.synapses = stm32_count_synapses,
@@ -311,13 +608,111 @@ static struct counter_count stm32_counts = {
.num_ext = ARRAY_SIZE(stm32_count_ext)
};
+static irqreturn_t stm32_timer_cnt_isr(int irq, void *ptr)
+{
+ struct counter_device *counter = ptr;
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ u32 clr = GENMASK(31, 0); /* SR flags can be cleared by writing 0 (wr 1 has no effect) */
+ u32 sr, dier;
+ int i;
+
+ regmap_read(priv->regmap, TIM_SR, &sr);
+ regmap_read(priv->regmap, TIM_DIER, &dier);
+ /*
+ * Some status bits in SR don't match with the enable bits in DIER. Only take care of
+ * the possibly enabled bits in DIER (that matches in between SR and DIER).
+ */
+ dier &= (TIM_DIER_UIE | TIM_DIER_CC1IE | TIM_DIER_CC2IE | TIM_DIER_CC3IE | TIM_DIER_CC4IE);
+ sr &= dier;
+
+ if (sr & TIM_SR_UIF) {
+ spin_lock(&priv->lock);
+ priv->nb_ovf++;
+ spin_unlock(&priv->lock);
+ counter_push_event(counter, COUNTER_EVENT_OVERFLOW_UNDERFLOW, 0);
+ dev_dbg(counter->parent, "COUNTER_EVENT_OVERFLOW_UNDERFLOW\n");
+ /* SR flags can be cleared by writing 0, only clear relevant flag */
+ clr &= ~TIM_SR_UIF;
+ }
+
+ /* Check capture events */
+ for (i = 0 ; i < priv->nchannels; i++) {
+ if (sr & TIM_SR_CC_IF(i)) {
+ counter_push_event(counter, COUNTER_EVENT_CAPTURE, i);
+ clr &= ~TIM_SR_CC_IF(i);
+ dev_dbg(counter->parent, "COUNTER_EVENT_CAPTURE, %d\n", i);
+ }
+ }
+
+ regmap_write(priv->regmap, TIM_SR, clr);
+
+ return IRQ_HANDLED;
+};
+
+static void stm32_timer_cnt_detect_channels(struct device *dev,
+ struct stm32_timer_cnt *priv)
+{
+ u32 ccer, ccer_backup;
+
+ regmap_read(priv->regmap, TIM_CCER, &ccer_backup);
+ regmap_set_bits(priv->regmap, TIM_CCER, TIM_CCER_CCXE);
+ regmap_read(priv->regmap, TIM_CCER, &ccer);
+ regmap_write(priv->regmap, TIM_CCER, ccer_backup);
+ priv->nchannels = hweight32(ccer & TIM_CCER_CCXE);
+
+ dev_dbg(dev, "has %d cc channels\n", priv->nchannels);
+}
+
+/* encoder supported on TIM1 TIM2 TIM3 TIM4 TIM5 TIM8 */
+#define STM32_TIM_ENCODER_SUPPORTED (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(7))
+
+static const char * const stm32_timer_trigger_compat[] = {
+ "st,stm32-timer-trigger",
+ "st,stm32h7-timer-trigger",
+};
+
+static int stm32_timer_cnt_probe_encoder(struct device *dev,
+ struct stm32_timer_cnt *priv)
+{
+ struct device *parent = dev->parent;
+ struct device_node *tnode = NULL, *pnode = parent->of_node;
+ int i, ret;
+ u32 idx;
+
+ /*
+ * Need to retrieve the trigger node index from DT, to be able
+ * to determine if the counter supports encoder mode. It also
+ * enforce backward compatibility, and allow to support other
+ * counter modes in this driver (when the timer doesn't support
+ * encoder).
+ */
+ for (i = 0; i < ARRAY_SIZE(stm32_timer_trigger_compat) && !tnode; i++)
+ tnode = of_get_compatible_child(pnode, stm32_timer_trigger_compat[i]);
+ if (!tnode) {
+ dev_err(dev, "Can't find trigger node\n");
+ return -ENODATA;
+ }
+
+ ret = of_property_read_u32(tnode, "reg", &idx);
+ if (ret) {
+ dev_err(dev, "Can't get index (%d)\n", ret);
+ return ret;
+ }
+
+ priv->has_encoder = !!(STM32_TIM_ENCODER_SUPPORTED & BIT(idx));
+
+ dev_dbg(dev, "encoder support: %s\n", priv->has_encoder ? "yes" : "no");
+
+ return 0;
+}
+
static int stm32_timer_cnt_probe(struct platform_device *pdev)
{
struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct stm32_timer_cnt *priv;
struct counter_device *counter;
- int ret;
+ int i, ret;
if (IS_ERR_OR_NULL(ddata))
return -EINVAL;
@@ -331,6 +726,13 @@ static int stm32_timer_cnt_probe(struct platform_device *pdev)
priv->regmap = ddata->regmap;
priv->clk = ddata->clk;
priv->max_arr = ddata->max_arr;
+ priv->nr_irqs = ddata->nr_irqs;
+
+ ret = stm32_timer_cnt_probe_encoder(dev, priv);
+ if (ret)
+ return ret;
+
+ stm32_timer_cnt_detect_channels(dev, priv);
counter->name = dev_name(dev);
counter->parent = dev;
@@ -340,8 +742,39 @@ static int stm32_timer_cnt_probe(struct platform_device *pdev)
counter->signals = stm32_signals;
counter->num_signals = ARRAY_SIZE(stm32_signals);
+ spin_lock_init(&priv->lock);
+
platform_set_drvdata(pdev, priv);
+ /* STM32 Timers can have either 1 global, or 4 dedicated interrupts (optional) */
+ if (priv->nr_irqs == 1) {
+ /* All events reported through the global interrupt */
+ ret = devm_request_irq(&pdev->dev, ddata->irq[0], stm32_timer_cnt_isr,
+ 0, dev_name(dev), counter);
+ if (ret) {
+ dev_err(dev, "Failed to request irq %d (err %d)\n",
+ ddata->irq[0], ret);
+ return ret;
+ }
+ } else {
+ for (i = 0; i < priv->nr_irqs; i++) {
+ /*
+ * Only take care of update IRQ for overflow events, and cc for
+ * capture events.
+ */
+ if (i != STM32_TIMERS_IRQ_UP && i != STM32_TIMERS_IRQ_CC)
+ continue;
+
+ ret = devm_request_irq(&pdev->dev, ddata->irq[i], stm32_timer_cnt_isr,
+ 0, dev_name(dev), counter);
+ if (ret) {
+ dev_err(dev, "Failed to request irq %d (err %d)\n",
+ ddata->irq[i], ret);
+ return ret;
+ }
+ }
+ }
+
/* Reset input selector to its default input */
regmap_write(priv->regmap, TIM_TISEL, 0x0);
diff --git a/drivers/counter/ti-ecap-capture.c b/drivers/counter/ti-ecap-capture.c
index fb1cb1774674a5..675447315cafb8 100644
--- a/drivers/counter/ti-ecap-capture.c
+++ b/drivers/counter/ti-ecap-capture.c
@@ -369,7 +369,7 @@ static const enum counter_synapse_action ecap_cnt_input_actions[] = {
};
static struct counter_comp ecap_cnt_clock_ext[] = {
- COUNTER_COMP_SIGNAL_U64("frequency", ecap_cnt_clk_get_freq, NULL),
+ COUNTER_COMP_FREQUENCY(ecap_cnt_clk_get_freq),
};
static const enum counter_signal_polarity ecap_cnt_pol_avail[] = {
@@ -537,15 +537,13 @@ static int ecap_cnt_probe(struct platform_device *pdev)
return 0;
}
-static int ecap_cnt_remove(struct platform_device *pdev)
+static void ecap_cnt_remove(struct platform_device *pdev)
{
struct counter_device *counter_dev = platform_get_drvdata(pdev);
struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
if (ecap_dev->enabled)
ecap_cnt_capture_disable(counter_dev);
-
- return 0;
}
static int ecap_cnt_suspend(struct device *dev)
@@ -600,7 +598,7 @@ MODULE_DEVICE_TABLE(of, ecap_cnt_of_match);
static struct platform_driver ecap_cnt_driver = {
.probe = ecap_cnt_probe,
- .remove = ecap_cnt_remove,
+ .remove_new = ecap_cnt_remove,
.driver = {
.name = "ecap-capture",
.of_match_table = ecap_cnt_of_match,
diff --git a/drivers/counter/ti-eqep.c b/drivers/counter/ti-eqep.c
index b0f24cf3e891de..072b11fd6b32e8 100644
--- a/drivers/counter/ti-eqep.c
+++ b/drivers/counter/ti-eqep.c
@@ -425,7 +425,7 @@ static int ti_eqep_probe(struct platform_device *pdev)
return 0;
}
-static int ti_eqep_remove(struct platform_device *pdev)
+static void ti_eqep_remove(struct platform_device *pdev)
{
struct counter_device *counter = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
@@ -433,8 +433,6 @@ static int ti_eqep_remove(struct platform_device *pdev)
counter_unregister(counter);
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
-
- return 0;
}
static const struct of_device_id ti_eqep_of_match[] = {
@@ -445,7 +443,7 @@ MODULE_DEVICE_TABLE(of, ti_eqep_of_match);
static struct platform_driver ti_eqep_driver = {
.probe = ti_eqep_probe,
- .remove = ti_eqep_remove,
+ .remove_new = ti_eqep_remove,
.driver = {
.name = "ti-eqep-cnt",
.of_match_table = ti_eqep_of_match,
diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index d76df5c8c2a916..b992c0ed182bdd 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -10,7 +10,7 @@ hv_vmbus-y := vmbus_drv.o \
hv.o connection.o channel.o \
channel_mgmt.o ring_buffer.o hv_trace.o
hv_vmbus-$(CONFIG_HYPERV_TESTING) += hv_debugfs.o
-hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_fcopy.o hv_utils_transport.o
+hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_utils_transport.o
# Code that must be built-in
obj-$(subst m,y,$(CONFIG_HYPERV)) += hv_common.o
diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c
index 2f4d09ce027a3f..3c6011a48dabe7 100644
--- a/drivers/hv/channel_mgmt.c
+++ b/drivers/hv/channel_mgmt.c
@@ -120,7 +120,9 @@ const struct vmbus_device vmbus_devs[] = {
},
/* File copy */
- { .dev_type = HV_FCOPY,
+ /* fcopy always uses 16KB ring buffer size and is working well for last many years */
+ { .pref_ring_size = 0x4000,
+ .dev_type = HV_FCOPY,
HV_FCOPY_GUID,
.perf_device = false,
.allowed_in_isolated = false,
@@ -140,12 +142,19 @@ const struct vmbus_device vmbus_devs[] = {
.allowed_in_isolated = false,
},
- /* Unknown GUID */
- { .dev_type = HV_UNKNOWN,
+ /*
+ * Unknown GUID
+ * 64 KB ring buffer + 4 KB header should be sufficient size for any Hyper-V device apart
+ * from HV_NIC and HV_SCSI. This case avoid the fallback for unknown devices to allocate
+ * much bigger (2 MB) of ring size.
+ */
+ { .pref_ring_size = 0x11000,
+ .dev_type = HV_UNKNOWN,
.perf_device = false,
.allowed_in_isolated = false,
},
};
+EXPORT_SYMBOL_GPL(vmbus_devs);
static const struct {
guid_t guid;
diff --git a/drivers/hv/hv_fcopy.c b/drivers/hv/hv_fcopy.c
deleted file mode 100644
index 922d83eb7ddfac..00000000000000
--- a/drivers/hv/hv_fcopy.c
+++ /dev/null
@@ -1,427 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * An implementation of file copy service.
- *
- * Copyright (C) 2014, Microsoft, Inc.
- *
- * Author : K. Y. Srinivasan <ksrinivasan@novell.com>
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/nls.h>
-#include <linux/workqueue.h>
-#include <linux/hyperv.h>
-#include <linux/sched.h>
-#include <asm/hyperv-tlfs.h>
-
-#include "hyperv_vmbus.h"
-#include "hv_utils_transport.h"
-
-#define WIN8_SRV_MAJOR 1
-#define WIN8_SRV_MINOR 1
-#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR)
-
-#define FCOPY_VER_COUNT 1
-static const int fcopy_versions[] = {
- WIN8_SRV_VERSION
-};
-
-#define FW_VER_COUNT 1
-static const int fw_versions[] = {
- UTIL_FW_VERSION
-};
-
-/*
- * Global state maintained for transaction that is being processed.
- * For a class of integration services, including the "file copy service",
- * the specified protocol is a "request/response" protocol which means that
- * there can only be single outstanding transaction from the host at any
- * given point in time. We use this to simplify memory management in this
- * driver - we cache and process only one message at a time.
- *
- * While the request/response protocol is guaranteed by the host, we further
- * ensure this by serializing packet processing in this driver - we do not
- * read additional packets from the VMBUs until the current packet is fully
- * handled.
- */
-
-static struct {
- int state; /* hvutil_device_state */
- int recv_len; /* number of bytes received. */
- struct hv_fcopy_hdr *fcopy_msg; /* current message */
- struct vmbus_channel *recv_channel; /* chn we got the request */
- u64 recv_req_id; /* request ID. */
-} fcopy_transaction;
-
-static void fcopy_respond_to_host(int error);
-static void fcopy_send_data(struct work_struct *dummy);
-static void fcopy_timeout_func(struct work_struct *dummy);
-static DECLARE_DELAYED_WORK(fcopy_timeout_work, fcopy_timeout_func);
-static DECLARE_WORK(fcopy_send_work, fcopy_send_data);
-static const char fcopy_devname[] = "vmbus/hv_fcopy";
-static u8 *recv_buffer;
-static struct hvutil_transport *hvt;
-/*
- * This state maintains the version number registered by the daemon.
- */
-static int dm_reg_value;
-
-static void fcopy_poll_wrapper(void *channel)
-{
- /* Transaction is finished, reset the state here to avoid races. */
- fcopy_transaction.state = HVUTIL_READY;
- tasklet_schedule(&((struct vmbus_channel *)channel)->callback_event);
-}
-
-static void fcopy_timeout_func(struct work_struct *dummy)
-{
- /*
- * If the timer fires, the user-mode component has not responded;
- * process the pending transaction.
- */
- fcopy_respond_to_host(HV_E_FAIL);
- hv_poll_channel(fcopy_transaction.recv_channel, fcopy_poll_wrapper);
-}
-
-static void fcopy_register_done(void)
-{
- pr_debug("FCP: userspace daemon registered\n");
- hv_poll_channel(fcopy_transaction.recv_channel, fcopy_poll_wrapper);
-}
-
-static int fcopy_handle_handshake(u32 version)
-{
- u32 our_ver = FCOPY_CURRENT_VERSION;
-
- switch (version) {
- case FCOPY_VERSION_0:
- /* Daemon doesn't expect us to reply */
- dm_reg_value = version;
- break;
- case FCOPY_VERSION_1:
- /* Daemon expects us to reply with our own version */
- if (hvutil_transport_send(hvt, &our_ver, sizeof(our_ver),
- fcopy_register_done))
- return -EFAULT;
- dm_reg_value = version;
- break;
- default:
- /*
- * For now we will fail the registration.
- * If and when we have multiple versions to
- * deal with, we will be backward compatible.
- * We will add this code when needed.
- */
- return -EINVAL;
- }
- pr_debug("FCP: userspace daemon ver. %d connected\n", version);
- return 0;
-}
-
-static void fcopy_send_data(struct work_struct *dummy)
-{
- struct hv_start_fcopy *smsg_out = NULL;
- int operation = fcopy_transaction.fcopy_msg->operation;
- struct hv_start_fcopy *smsg_in;
- void *out_src;
- int rc, out_len;
-
- /*
- * The strings sent from the host are encoded in
- * utf16; convert it to utf8 strings.
- * The host assures us that the utf16 strings will not exceed
- * the max lengths specified. We will however, reserve room
- * for the string terminating character - in the utf16s_utf8s()
- * function we limit the size of the buffer where the converted
- * string is placed to W_MAX_PATH -1 to guarantee
- * that the strings can be properly terminated!
- */
-
- switch (operation) {
- case START_FILE_COPY:
- out_len = sizeof(struct hv_start_fcopy);
- smsg_out = kzalloc(sizeof(*smsg_out), GFP_KERNEL);
- if (!smsg_out)
- return;
-
- smsg_out->hdr.operation = operation;
- smsg_in = (struct hv_start_fcopy *)fcopy_transaction.fcopy_msg;
-
- utf16s_to_utf8s((wchar_t *)smsg_in->file_name, W_MAX_PATH,
- UTF16_LITTLE_ENDIAN,
- (__u8 *)&smsg_out->file_name, W_MAX_PATH - 1);
-
- utf16s_to_utf8s((wchar_t *)smsg_in->path_name, W_MAX_PATH,
- UTF16_LITTLE_ENDIAN,
- (__u8 *)&smsg_out->path_name, W_MAX_PATH - 1);
-
- smsg_out->copy_flags = smsg_in->copy_flags;
- smsg_out->file_size = smsg_in->file_size;
- out_src = smsg_out;
- break;
-
- case WRITE_TO_FILE:
- out_src = fcopy_transaction.fcopy_msg;
- out_len = sizeof(struct hv_do_fcopy);
- break;
- default:
- out_src = fcopy_transaction.fcopy_msg;
- out_len = fcopy_transaction.recv_len;
- break;
- }
-
- fcopy_transaction.state = HVUTIL_USERSPACE_REQ;
- rc = hvutil_transport_send(hvt, out_src, out_len, NULL);
- if (rc) {
- pr_debug("FCP: failed to communicate to the daemon: %d\n", rc);
- if (cancel_delayed_work_sync(&fcopy_timeout_work)) {
- fcopy_respond_to_host(HV_E_FAIL);
- fcopy_transaction.state = HVUTIL_READY;
- }
- }
- kfree(smsg_out);
-}
-
-/*
- * Send a response back to the host.
- */
-
-static void
-fcopy_respond_to_host(int error)
-{
- struct icmsg_hdr *icmsghdr;
- u32 buf_len;
- struct vmbus_channel *channel;
- u64 req_id;
-
- /*
- * Copy the global state for completing the transaction. Note that
- * only one transaction can be active at a time. This is guaranteed
- * by the file copy protocol implemented by the host. Furthermore,
- * the "transaction active" state we maintain ensures that there can
- * only be one active transaction at a time.
- */
-
- buf_len = fcopy_transaction.recv_len;
- channel = fcopy_transaction.recv_channel;
- req_id = fcopy_transaction.recv_req_id;
-
- icmsghdr = (struct icmsg_hdr *)
- &recv_buffer[sizeof(struct vmbuspipe_hdr)];
-
- if (channel->onchannel_callback == NULL)
- /*
- * We have raced with util driver being unloaded;
- * silently return.
- */
- return;
-
- icmsghdr->status = error;
- icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE;
- vmbus_sendpacket(channel, recv_buffer, buf_len, req_id,
- VM_PKT_DATA_INBAND, 0);
-}
-
-void hv_fcopy_onchannelcallback(void *context)
-{
- struct vmbus_channel *channel = context;
- u32 recvlen;
- u64 requestid;
- struct hv_fcopy_hdr *fcopy_msg;
- struct icmsg_hdr *icmsghdr;
- int fcopy_srv_version;
-
- if (fcopy_transaction.state > HVUTIL_READY)
- return;
-
- if (vmbus_recvpacket(channel, recv_buffer, HV_HYP_PAGE_SIZE * 2, &recvlen, &requestid)) {
- pr_err_ratelimited("Fcopy request received. Could not read into recv buf\n");
- return;
- }
-
- if (!recvlen)
- return;
-
- /* Ensure recvlen is big enough to read header data */
- if (recvlen < ICMSG_HDR) {
- pr_err_ratelimited("Fcopy request received. Packet length too small: %d\n",
- recvlen);
- return;
- }
-
- icmsghdr = (struct icmsg_hdr *)&recv_buffer[
- sizeof(struct vmbuspipe_hdr)];
-
- if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) {
- if (vmbus_prep_negotiate_resp(icmsghdr,
- recv_buffer, recvlen,
- fw_versions, FW_VER_COUNT,
- fcopy_versions, FCOPY_VER_COUNT,
- NULL, &fcopy_srv_version)) {
-
- pr_info("FCopy IC version %d.%d\n",
- fcopy_srv_version >> 16,
- fcopy_srv_version & 0xFFFF);
- }
- } else if (icmsghdr->icmsgtype == ICMSGTYPE_FCOPY) {
- /* Ensure recvlen is big enough to contain hv_fcopy_hdr */
- if (recvlen < ICMSG_HDR + sizeof(struct hv_fcopy_hdr)) {
- pr_err_ratelimited("Invalid Fcopy hdr. Packet length too small: %u\n",
- recvlen);
- return;
- }
- fcopy_msg = (struct hv_fcopy_hdr *)&recv_buffer[ICMSG_HDR];
-
- /*
- * Stash away this global state for completing the
- * transaction; note transactions are serialized.
- */
-
- fcopy_transaction.recv_len = recvlen;
- fcopy_transaction.recv_req_id = requestid;
- fcopy_transaction.fcopy_msg = fcopy_msg;
-
- if (fcopy_transaction.state < HVUTIL_READY) {
- /* Userspace is not registered yet */
- fcopy_respond_to_host(HV_E_FAIL);
- return;
- }
- fcopy_transaction.state = HVUTIL_HOSTMSG_RECEIVED;
-
- /*
- * Send the information to the user-level daemon.
- */
- schedule_work(&fcopy_send_work);
- schedule_delayed_work(&fcopy_timeout_work,
- HV_UTIL_TIMEOUT * HZ);
- return;
- } else {
- pr_err_ratelimited("Fcopy request received. Invalid msg type: %d\n",
- icmsghdr->icmsgtype);
- return;
- }
- icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE;
- vmbus_sendpacket(channel, recv_buffer, recvlen, requestid,
- VM_PKT_DATA_INBAND, 0);
-}
-
-/* Callback when data is received from userspace */
-static int fcopy_on_msg(void *msg, int len)
-{
- int *val = (int *)msg;
-
- if (len != sizeof(int))
- return -EINVAL;
-
- if (fcopy_transaction.state == HVUTIL_DEVICE_INIT)
- return fcopy_handle_handshake(*val);
-
- if (fcopy_transaction.state != HVUTIL_USERSPACE_REQ)
- return -EINVAL;
-
- /*
- * Complete the transaction by forwarding the result
- * to the host. But first, cancel the timeout.
- */
- if (cancel_delayed_work_sync(&fcopy_timeout_work)) {
- fcopy_transaction.state = HVUTIL_USERSPACE_RECV;
- fcopy_respond_to_host(*val);
- hv_poll_channel(fcopy_transaction.recv_channel,
- fcopy_poll_wrapper);
- }
-
- return 0;
-}
-
-static void fcopy_on_reset(void)
-{
- /*
- * The daemon has exited; reset the state.
- */
- fcopy_transaction.state = HVUTIL_DEVICE_INIT;
-
- if (cancel_delayed_work_sync(&fcopy_timeout_work))
- fcopy_respond_to_host(HV_E_FAIL);
-}
-
-int hv_fcopy_init(struct hv_util_service *srv)
-{
- recv_buffer = srv->recv_buffer;
- fcopy_transaction.recv_channel = srv->channel;
- fcopy_transaction.recv_channel->max_pkt_size = HV_HYP_PAGE_SIZE * 2;
-
- /*
- * When this driver loads, the user level daemon that
- * processes the host requests may not yet be running.
- * Defer processing channel callbacks until the daemon
- * has registered.
- */
- fcopy_transaction.state = HVUTIL_DEVICE_INIT;
-
- hvt = hvutil_transport_init(fcopy_devname, 0, 0,
- fcopy_on_msg, fcopy_on_reset);
- if (!hvt)
- return -EFAULT;
-
- return 0;
-}
-
-static void hv_fcopy_cancel_work(void)
-{
- cancel_delayed_work_sync(&fcopy_timeout_work);
- cancel_work_sync(&fcopy_send_work);
-}
-
-int hv_fcopy_pre_suspend(void)
-{
- struct vmbus_channel *channel = fcopy_transaction.recv_channel;
- struct hv_fcopy_hdr *fcopy_msg;
-
- /*
- * Fake a CANCEL_FCOPY message for the user space daemon in case the
- * daemon is in the middle of copying some file. It doesn't matter if
- * there is already a message pending to be delivered to the user
- * space since we force fcopy_transaction.state to be HVUTIL_READY, so
- * the user space daemon's write() will fail with EINVAL (see
- * fcopy_on_msg()), and the daemon will reset the device by closing
- * and re-opening it.
- */
- fcopy_msg = kzalloc(sizeof(*fcopy_msg), GFP_KERNEL);
- if (!fcopy_msg)
- return -ENOMEM;
-
- tasklet_disable(&channel->callback_event);
-
- fcopy_msg->operation = CANCEL_FCOPY;
-
- hv_fcopy_cancel_work();
-
- /* We don't care about the return value. */
- hvutil_transport_send(hvt, fcopy_msg, sizeof(*fcopy_msg), NULL);
-
- kfree(fcopy_msg);
-
- fcopy_transaction.state = HVUTIL_READY;
-
- /* tasklet_enable() will be called in hv_fcopy_pre_resume(). */
- return 0;
-}
-
-int hv_fcopy_pre_resume(void)
-{
- struct vmbus_channel *channel = fcopy_transaction.recv_channel;
-
- tasklet_enable(&channel->callback_event);
-
- return 0;
-}
-
-void hv_fcopy_deinit(void)
-{
- fcopy_transaction.state = HVUTIL_DEVICE_DYING;
-
- hv_fcopy_cancel_work();
-
- hvutil_transport_destroy(hvt);
-}
diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c
index 9c97c4065fe736..c4f525325790fa 100644
--- a/drivers/hv/hv_util.c
+++ b/drivers/hv/hv_util.c
@@ -154,14 +154,6 @@ static struct hv_util_service util_vss = {
.util_deinit = hv_vss_deinit,
};
-static struct hv_util_service util_fcopy = {
- .util_cb = hv_fcopy_onchannelcallback,
- .util_init = hv_fcopy_init,
- .util_pre_suspend = hv_fcopy_pre_suspend,
- .util_pre_resume = hv_fcopy_pre_resume,
- .util_deinit = hv_fcopy_deinit,
-};
-
static void perform_shutdown(struct work_struct *dummy)
{
orderly_poweroff(true);
@@ -700,10 +692,6 @@ static const struct hv_vmbus_device_id id_table[] = {
{ HV_VSS_GUID,
.driver_data = (unsigned long)&util_vss
},
- /* File copy GUID */
- { HV_FCOPY_GUID,
- .driver_data = (unsigned long)&util_fcopy
- },
{ },
};
diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h
index f6b1e710f80551..76ac5185a01a88 100644
--- a/drivers/hv/hyperv_vmbus.h
+++ b/drivers/hv/hyperv_vmbus.h
@@ -417,6 +417,11 @@ static inline bool hv_is_perf_channel(struct vmbus_channel *channel)
return vmbus_devs[channel->device_id].perf_device;
}
+static inline size_t hv_dev_ring_size(struct vmbus_channel *channel)
+{
+ return vmbus_devs[channel->device_id].pref_ring_size;
+}
+
static inline bool hv_is_allocated_cpu(unsigned int cpu)
{
struct vmbus_channel *channel, *sc;
diff --git a/drivers/iio/accel/adxl345.h b/drivers/iio/accel/adxl345.h
index 284bd387ce69b0..3d5c8719db3d78 100644
--- a/drivers/iio/accel/adxl345.h
+++ b/drivers/iio/accel/adxl345.h
@@ -8,6 +8,39 @@
#ifndef _ADXL345_H_
#define _ADXL345_H_
+#define ADXL345_REG_DEVID 0x00
+#define ADXL345_REG_OFSX 0x1E
+#define ADXL345_REG_OFSY 0x1F
+#define ADXL345_REG_OFSZ 0x20
+#define ADXL345_REG_OFS_AXIS(index) (ADXL345_REG_OFSX + (index))
+#define ADXL345_REG_BW_RATE 0x2C
+#define ADXL345_REG_POWER_CTL 0x2D
+#define ADXL345_REG_DATA_FORMAT 0x31
+#define ADXL345_REG_DATAX0 0x32
+#define ADXL345_REG_DATAY0 0x34
+#define ADXL345_REG_DATAZ0 0x36
+#define ADXL345_REG_DATA_AXIS(index) \
+ (ADXL345_REG_DATAX0 + (index) * sizeof(__le16))
+
+#define ADXL345_BW_RATE GENMASK(3, 0)
+#define ADXL345_BASE_RATE_NANO_HZ 97656250LL
+
+#define ADXL345_POWER_CTL_MEASURE BIT(3)
+#define ADXL345_POWER_CTL_STANDBY 0x00
+
+#define ADXL345_DATA_FORMAT_RANGE GENMASK(1, 0) /* Set the g range */
+#define ADXL345_DATA_FORMAT_JUSTIFY BIT(2) /* Left-justified (MSB) mode */
+#define ADXL345_DATA_FORMAT_FULL_RES BIT(3) /* Up to 13-bits resolution */
+#define ADXL345_DATA_FORMAT_SPI_3WIRE BIT(6) /* 3-wire SPI mode */
+#define ADXL345_DATA_FORMAT_SELF_TEST BIT(7) /* Enable a self test */
+
+#define ADXL345_DATA_FORMAT_2G 0
+#define ADXL345_DATA_FORMAT_4G 1
+#define ADXL345_DATA_FORMAT_8G 2
+#define ADXL345_DATA_FORMAT_16G 3
+
+#define ADXL345_DEVID 0xE5
+
/*
* In full-resolution mode, scale factor is maintained at ~4 mg/LSB
* in all g ranges.
@@ -28,6 +61,7 @@ struct adxl345_chip_info {
int uscale;
};
-int adxl345_core_probe(struct device *dev, struct regmap *regmap);
+int adxl345_core_probe(struct device *dev, struct regmap *regmap,
+ int (*setup)(struct device*, struct regmap*));
#endif /* _ADXL345_H_ */
diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c
index 8bd30a23ed3b64..006ce66c0aa388 100644
--- a/drivers/iio/accel/adxl345_core.c
+++ b/drivers/iio/accel/adxl345_core.c
@@ -17,38 +17,9 @@
#include "adxl345.h"
-#define ADXL345_REG_DEVID 0x00
-#define ADXL345_REG_OFSX 0x1e
-#define ADXL345_REG_OFSY 0x1f
-#define ADXL345_REG_OFSZ 0x20
-#define ADXL345_REG_OFS_AXIS(index) (ADXL345_REG_OFSX + (index))
-#define ADXL345_REG_BW_RATE 0x2C
-#define ADXL345_REG_POWER_CTL 0x2D
-#define ADXL345_REG_DATA_FORMAT 0x31
-#define ADXL345_REG_DATAX0 0x32
-#define ADXL345_REG_DATAY0 0x34
-#define ADXL345_REG_DATAZ0 0x36
-#define ADXL345_REG_DATA_AXIS(index) \
- (ADXL345_REG_DATAX0 + (index) * sizeof(__le16))
-
-#define ADXL345_BW_RATE GENMASK(3, 0)
-#define ADXL345_BASE_RATE_NANO_HZ 97656250LL
-
-#define ADXL345_POWER_CTL_MEASURE BIT(3)
-#define ADXL345_POWER_CTL_STANDBY 0x00
-
-#define ADXL345_DATA_FORMAT_FULL_RES BIT(3) /* Up to 13-bits resolution */
-#define ADXL345_DATA_FORMAT_2G 0
-#define ADXL345_DATA_FORMAT_4G 1
-#define ADXL345_DATA_FORMAT_8G 2
-#define ADXL345_DATA_FORMAT_16G 3
-
-#define ADXL345_DEVID 0xE5
-
struct adxl345_data {
const struct adxl345_chip_info *info;
struct regmap *regmap;
- u8 data_range;
};
#define ADXL345_CHANNEL(index, axis) { \
@@ -197,44 +168,75 @@ static void adxl345_powerdown(void *regmap)
regmap_write(regmap, ADXL345_REG_POWER_CTL, ADXL345_POWER_CTL_STANDBY);
}
-int adxl345_core_probe(struct device *dev, struct regmap *regmap)
+/**
+ * adxl345_core_probe() - probe and setup for the adxl345 accelerometer,
+ * also covers the adlx375 accelerometer
+ * @dev: Driver model representation of the device
+ * @regmap: Regmap instance for the device
+ * @setup: Setup routine to be executed right before the standard device
+ * setup
+ *
+ * Return: 0 on success, negative errno on error
+ */
+int adxl345_core_probe(struct device *dev, struct regmap *regmap,
+ int (*setup)(struct device*, struct regmap*))
{
struct adxl345_data *data;
struct iio_dev *indio_dev;
u32 regval;
+ unsigned int data_format_mask = (ADXL345_DATA_FORMAT_RANGE |
+ ADXL345_DATA_FORMAT_JUSTIFY |
+ ADXL345_DATA_FORMAT_FULL_RES |
+ ADXL345_DATA_FORMAT_SELF_TEST);
int ret;
- ret = regmap_read(regmap, ADXL345_REG_DEVID, &regval);
- if (ret < 0)
- return dev_err_probe(dev, ret, "Error reading device ID\n");
-
- if (regval != ADXL345_DEVID)
- return dev_err_probe(dev, -ENODEV, "Invalid device ID: %x, expected %x\n",
- regval, ADXL345_DEVID);
-
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
data->regmap = regmap;
- /* Enable full-resolution mode */
- data->data_range = ADXL345_DATA_FORMAT_FULL_RES;
data->info = device_get_match_data(dev);
if (!data->info)
return -ENODEV;
- ret = regmap_write(data->regmap, ADXL345_REG_DATA_FORMAT,
- data->data_range);
- if (ret < 0)
- return dev_err_probe(dev, ret, "Failed to set data range\n");
-
indio_dev->name = data->info->name;
indio_dev->info = &adxl345_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = adxl345_channels;
indio_dev->num_channels = ARRAY_SIZE(adxl345_channels);
+ if (setup) {
+ /* Perform optional initial bus specific configuration */
+ ret = setup(dev, data->regmap);
+ if (ret)
+ return ret;
+
+ /* Enable full-resolution mode */
+ ret = regmap_update_bits(data->regmap, ADXL345_REG_DATA_FORMAT,
+ data_format_mask,
+ ADXL345_DATA_FORMAT_FULL_RES);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set data range\n");
+
+ } else {
+ /* Enable full-resolution mode (init all data_format bits) */
+ ret = regmap_write(data->regmap, ADXL345_REG_DATA_FORMAT,
+ ADXL345_DATA_FORMAT_FULL_RES);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set data range\n");
+ }
+
+ ret = regmap_read(data->regmap, ADXL345_REG_DEVID, &regval);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Error reading device ID\n");
+
+ if (regval != ADXL345_DEVID)
+ return dev_err_probe(dev, -ENODEV, "Invalid device ID: %x, expected %x\n",
+ regval, ADXL345_DEVID);
+
/* Enable measurement mode */
ret = adxl345_powerup(data->regmap);
if (ret < 0)
diff --git a/drivers/iio/accel/adxl345_i2c.c b/drivers/iio/accel/adxl345_i2c.c
index a3084b0a8f7853..4065b8f7c8a8ae 100644
--- a/drivers/iio/accel/adxl345_i2c.c
+++ b/drivers/iio/accel/adxl345_i2c.c
@@ -27,7 +27,7 @@ static int adxl345_i2c_probe(struct i2c_client *client)
if (IS_ERR(regmap))
return dev_err_probe(&client->dev, PTR_ERR(regmap), "Error initializing regmap\n");
- return adxl345_core_probe(&client->dev, regmap);
+ return adxl345_core_probe(&client->dev, regmap, NULL);
}
static const struct adxl345_chip_info adxl345_i2c_info = {
diff --git a/drivers/iio/accel/adxl345_spi.c b/drivers/iio/accel/adxl345_spi.c
index 93ca349f178090..57e16b44170214 100644
--- a/drivers/iio/accel/adxl345_spi.c
+++ b/drivers/iio/accel/adxl345_spi.c
@@ -20,6 +20,11 @@ static const struct regmap_config adxl345_spi_regmap_config = {
.read_flag_mask = BIT(7) | BIT(6),
};
+static int adxl345_spi_setup(struct device *dev, struct regmap *regmap)
+{
+ return regmap_write(regmap, ADXL345_REG_DATA_FORMAT, ADXL345_DATA_FORMAT_SPI_3WIRE);
+}
+
static int adxl345_spi_probe(struct spi_device *spi)
{
struct regmap *regmap;
@@ -33,7 +38,10 @@ static int adxl345_spi_probe(struct spi_device *spi)
if (IS_ERR(regmap))
return dev_err_probe(&spi->dev, PTR_ERR(regmap), "Error initializing regmap\n");
- return adxl345_core_probe(&spi->dev, regmap);
+ if (spi->mode & SPI_3WIRE)
+ return adxl345_core_probe(&spi->dev, regmap, adxl345_spi_setup);
+ else
+ return adxl345_core_probe(&spi->dev, regmap, NULL);
}
static const struct adxl345_chip_info adxl345_spi_info = {
diff --git a/drivers/iio/accel/adxl367.c b/drivers/iio/accel/adxl367.c
index 210228affb80d3..5cf4828a5eb5eb 100644
--- a/drivers/iio/accel/adxl367.c
+++ b/drivers/iio/accel/adxl367.c
@@ -621,7 +621,7 @@ static int _adxl367_set_odr(struct adxl367_state *st, enum adxl367_odr odr)
static int adxl367_set_odr(struct iio_dev *indio_dev, enum adxl367_odr odr)
{
iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
- struct adxl367_state *st = iio_priv(indio_dev);;
+ struct adxl367_state *st = iio_priv(indio_dev);
int ret;
guard(mutex)(&st->lock);
diff --git a/drivers/iio/accel/fxls8962af-core.c b/drivers/iio/accel/fxls8962af-core.c
index be8a15cb945fdf..4fbc01bda62e4c 100644
--- a/drivers/iio/accel/fxls8962af-core.c
+++ b/drivers/iio/accel/fxls8962af-core.c
@@ -15,9 +15,11 @@
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/i2c.h>
+#include <linux/irq.h>
#include <linux/module.h>
-#include <linux/of_irq.h>
+#include <linux/mod_devicetable.h>
#include <linux/pm_runtime.h>
+#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/regmap.h>
@@ -1062,12 +1064,12 @@ static void fxls8962af_pm_disable(void *dev_ptr)
fxls8962af_standby(iio_priv(indio_dev));
}
-static void fxls8962af_get_irq(struct device_node *of_node,
+static void fxls8962af_get_irq(struct device *dev,
enum fxls8962af_int_pin *pin)
{
int irq;
- irq = of_irq_get_byname(of_node, "INT2");
+ irq = fwnode_irq_get_byname(dev_fwnode(dev), "INT2");
if (irq > 0) {
*pin = FXLS8962AF_PIN_INT2;
return;
@@ -1086,7 +1088,7 @@ static int fxls8962af_irq_setup(struct iio_dev *indio_dev, int irq)
u8 int_pin_sel;
int ret;
- fxls8962af_get_irq(dev->of_node, &int_pin);
+ fxls8962af_get_irq(dev, &int_pin);
switch (int_pin) {
case FXLS8962AF_PIN_INT1:
int_pin_sel = FXLS8962AF_INT_PIN_SEL_INT1;
diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c
index d3fd0318e47bf5..62e6369e22696c 100644
--- a/drivers/iio/accel/mma8452.c
+++ b/drivers/iio/accel/mma8452.c
@@ -19,6 +19,8 @@
*/
#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
@@ -28,8 +30,6 @@
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/events.h>
#include <linux/delay.h>
-#include <linux/of.h>
-#include <linux/of_irq.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
@@ -1642,7 +1642,7 @@ static int mma8452_probe(struct i2c_client *client)
if (client->irq) {
int irq2;
- irq2 = of_irq_get_byname(client->dev.of_node, "INT2");
+ irq2 = fwnode_irq_get_byname(dev_fwnode(&client->dev), "INT2");
if (irq2 == client->irq) {
dev_dbg(&client->dev, "using interrupt line INT2\n");
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 0d9282fa67f59c..8db68b80b39156 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -70,6 +70,23 @@ config AD7124
To compile this driver as a module, choose M here: the module will be
called ad7124.
+config AD7173
+ tristate "Analog Devices AD7173 driver"
+ depends on SPI_MASTER
+ select AD_SIGMA_DELTA
+ select GPIO_REGMAP if GPIOLIB
+ select REGMAP_SPI if GPIOLIB
+ help
+ Say yes here to build support for Analog Devices AD7173 and similar ADC
+ Currently supported models:
+ - AD7172-2
+ - AD7173-8
+ - AD7175-2
+ - AD7176-2
+
+ To compile this driver as a module, choose M here: the module will be
+ called ad7173.
+
config AD7192
tristate "Analog Devices AD7190 AD7192 AD7193 AD7195 ADC driver"
depends on SPI
@@ -264,6 +281,16 @@ config AD7923
To compile this driver as a module, choose M here: the
module will be called ad7923.
+config AD7944
+ tristate "Analog Devices AD7944 and similar ADCs driver"
+ depends on SPI
+ help
+ Say yes here to build support for Analog Devices
+ AD7944, AD7985, AD7986 ADCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad7944
+
config AD7949
tristate "Analog Devices AD7949 and similar ADCs driver"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index b3c434722364a2..edb32ce2af026b 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_AD7091R) += ad7091r-base.o
obj-$(CONFIG_AD7091R5) += ad7091r5.o
obj-$(CONFIG_AD7091R8) += ad7091r8.o
obj-$(CONFIG_AD7124) += ad7124.o
+obj-$(CONFIG_AD7173) += ad7173.o
obj-$(CONFIG_AD7192) += ad7192.o
obj-$(CONFIG_AD7266) += ad7266.o
obj-$(CONFIG_AD7280) += ad7280a.o
@@ -28,6 +29,7 @@ obj-$(CONFIG_AD7780) += ad7780.o
obj-$(CONFIG_AD7791) += ad7791.o
obj-$(CONFIG_AD7793) += ad7793.o
obj-$(CONFIG_AD7887) += ad7887.o
+obj-$(CONFIG_AD7944) += ad7944.o
obj-$(CONFIG_AD7949) += ad7949.o
obj-$(CONFIG_AD799X) += ad799x.o
obj-$(CONFIG_AD9467) += ad9467.o
diff --git a/drivers/iio/adc/ab8500-gpadc.c b/drivers/iio/adc/ab8500-gpadc.c
index 80645fee79a4cc..59f66e9cb0e867 100644
--- a/drivers/iio/adc/ab8500-gpadc.c
+++ b/drivers/iio/adc/ab8500-gpadc.c
@@ -1021,14 +1021,13 @@ static int ab8500_gpadc_parse_channel(struct device *dev,
/**
* ab8500_gpadc_parse_channels() - Parse the GPADC channels from DT
* @gpadc: the GPADC to configure the channels for
- * @chans: the IIO channels we parsed
- * @nchans: the number of IIO channels we parsed
+ * @chans_parsed: the IIO channels we parsed
+ * @nchans_parsed: the number of IIO channels we parsed
*/
static int ab8500_gpadc_parse_channels(struct ab8500_gpadc *gpadc,
struct iio_chan_spec **chans_parsed,
unsigned int *nchans_parsed)
{
- struct fwnode_handle *child;
struct ab8500_gpadc_chan_info *ch;
struct iio_chan_spec *iio_chans;
unsigned int nchans;
@@ -1052,7 +1051,7 @@ static int ab8500_gpadc_parse_channels(struct ab8500_gpadc *gpadc,
return -ENOMEM;
i = 0;
- device_for_each_child_node(gpadc->dev, child) {
+ device_for_each_child_node_scoped(gpadc->dev, child) {
struct iio_chan_spec *iio_chan;
int ret;
@@ -1062,7 +1061,6 @@ static int ab8500_gpadc_parse_channels(struct ab8500_gpadc *gpadc,
ret = ab8500_gpadc_parse_channel(gpadc->dev, child, ch,
iio_chan);
if (ret) {
- fwnode_handle_put(child);
return ret;
}
i++;
diff --git a/drivers/iio/adc/ad4130.c b/drivers/iio/adc/ad4130.c
index febb64e67955ee..aaf1fb0ac44772 100644
--- a/drivers/iio/adc/ad4130.c
+++ b/drivers/iio/adc/ad4130.c
@@ -1600,17 +1600,14 @@ static int ad4130_parse_fw_children(struct iio_dev *indio_dev)
{
struct ad4130_state *st = iio_priv(indio_dev);
struct device *dev = &st->spi->dev;
- struct fwnode_handle *child;
int ret;
indio_dev->channels = st->chans;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
ret = ad4130_parse_fw_channel(indio_dev, child);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return ret;
- }
}
return 0;
diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c
index b9b206fcd748f5..e7b1d517d3def7 100644
--- a/drivers/iio/adc/ad7124.c
+++ b/drivers/iio/adc/ad7124.c
@@ -14,7 +14,8 @@
#include <linux/kernel.h>
#include <linux/kfifo.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
@@ -807,22 +808,19 @@ static int ad7124_check_chip_id(struct ad7124_state *st)
return 0;
}
-static int ad7124_of_parse_channel_config(struct iio_dev *indio_dev,
- struct device_node *np)
+static int ad7124_parse_channel_config(struct iio_dev *indio_dev,
+ struct device *dev)
{
struct ad7124_state *st = iio_priv(indio_dev);
struct ad7124_channel_config *cfg;
struct ad7124_channel *channels;
- 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;
- }
+ st->num_channels = device_get_child_node_count(dev);
+ if (!st->num_channels)
+ return dev_err_probe(dev, -ENODEV, "no channel children\n");
chan = devm_kcalloc(indio_dev->dev.parent, st->num_channels,
sizeof(*chan), GFP_KERNEL);
@@ -838,39 +836,38 @@ static int ad7124_of_parse_channel_config(struct iio_dev *indio_dev,
indio_dev->num_channels = st->num_channels;
st->channels = channels;
- for_each_available_child_of_node(np, child) {
+ device_for_each_child_node_scoped(dev, child) {
cfg = &st->channels[channel].cfg;
- ret = of_property_read_u32(child, "reg", &channel);
+ ret = fwnode_property_read_u32(child, "reg", &channel);
if (ret)
- goto err;
+ return ret;
- if (channel >= indio_dev->num_channels) {
- dev_err(indio_dev->dev.parent,
+ if (channel >= indio_dev->num_channels)
+ return dev_err_probe(dev, -EINVAL,
"Channel index >= number of channels\n");
- ret = -EINVAL;
- goto err;
- }
- ret = of_property_read_u32_array(child, "diff-channels",
- ain, 2);
+ ret = fwnode_property_read_u32_array(child, "diff-channels",
+ ain, 2);
if (ret)
- goto err;
+ return ret;
st->channels[channel].nr = channel;
st->channels[channel].ain = AD7124_CHANNEL_AINP(ain[0]) |
AD7124_CHANNEL_AINM(ain[1]);
- cfg->bipolar = of_property_read_bool(child, "bipolar");
+ cfg->bipolar = fwnode_property_read_bool(child, "bipolar");
- ret = of_property_read_u32(child, "adi,reference-select", &tmp);
+ ret = fwnode_property_read_u32(child, "adi,reference-select", &tmp);
if (ret)
cfg->refsel = AD7124_INT_REF;
else
cfg->refsel = tmp;
- cfg->buf_positive = of_property_read_bool(child, "adi,buffered-positive");
- cfg->buf_negative = of_property_read_bool(child, "adi,buffered-negative");
+ cfg->buf_positive =
+ fwnode_property_read_bool(child, "adi,buffered-positive");
+ cfg->buf_negative =
+ fwnode_property_read_bool(child, "adi,buffered-negative");
chan[channel] = ad7124_channel_template;
chan[channel].address = channel;
@@ -880,10 +877,6 @@ static int ad7124_of_parse_channel_config(struct iio_dev *indio_dev,
}
return 0;
-err:
- of_node_put(child);
-
- return ret;
}
static int ad7124_setup(struct ad7124_state *st)
@@ -943,9 +936,7 @@ static int ad7124_probe(struct spi_device *spi)
struct iio_dev *indio_dev;
int i, ret;
- info = of_device_get_match_data(&spi->dev);
- if (!info)
- info = (void *)spi_get_device_id(spi)->driver_data;
+ info = spi_get_device_match_data(spi);
if (!info)
return -ENODEV;
@@ -965,7 +956,7 @@ static int ad7124_probe(struct spi_device *spi)
if (ret < 0)
return ret;
- ret = ad7124_of_parse_channel_config(indio_dev, spi->dev.of_node);
+ ret = ad7124_parse_channel_config(indio_dev, &spi->dev);
if (ret < 0)
return ret;
diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c
new file mode 100644
index 00000000000000..a7826bba0852af
--- /dev/null
+++ b/drivers/iio/adc/ad7173.c
@@ -0,0 +1,1180 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * AD717x family SPI ADC driver
+ *
+ * Supported devices:
+ * AD7172-2/AD7172-4/AD7173-8/AD7175-2
+ * AD7175-8/AD7176-2/AD7177-2
+ *
+ * Copyright (C) 2015, 2024 Analog Devices, Inc.
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitmap.h>
+#include <linux/container_of.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include <linux/iio/adc/ad_sigma_delta.h>
+
+#define AD7173_REG_COMMS 0x00
+#define AD7173_REG_ADC_MODE 0x01
+#define AD7173_REG_INTERFACE_MODE 0x02
+#define AD7173_REG_CRC 0x03
+#define AD7173_REG_DATA 0x04
+#define AD7173_REG_GPIO 0x06
+#define AD7173_REG_ID 0x07
+#define AD7173_REG_CH(x) (0x10 + (x))
+#define AD7173_REG_SETUP(x) (0x20 + (x))
+#define AD7173_REG_FILTER(x) (0x28 + (x))
+#define AD7173_REG_OFFSET(x) (0x30 + (x))
+#define AD7173_REG_GAIN(x) (0x38 + (x))
+
+#define AD7173_RESET_LENGTH BITS_TO_BYTES(64)
+
+#define AD7173_CH_ENABLE BIT(15)
+#define AD7173_CH_SETUP_SEL_MASK GENMASK(14, 12)
+#define AD7173_CH_SETUP_AINPOS_MASK GENMASK(9, 5)
+#define AD7173_CH_SETUP_AINNEG_MASK GENMASK(4, 0)
+
+#define AD7173_CH_ADDRESS(pos, neg) \
+ (FIELD_PREP(AD7173_CH_SETUP_AINPOS_MASK, pos) | \
+ FIELD_PREP(AD7173_CH_SETUP_AINNEG_MASK, neg))
+#define AD7173_AIN_TEMP_POS 17
+#define AD7173_AIN_TEMP_NEG 18
+
+#define AD7172_2_ID 0x00d0
+#define AD7175_ID 0x0cd0
+#define AD7176_ID 0x0c90
+#define AD7175_2_ID 0x0cd0
+#define AD7172_4_ID 0x2050
+#define AD7173_ID 0x30d0
+#define AD7175_8_ID 0x3cd0
+#define AD7177_ID 0x4fd0
+#define AD7173_ID_MASK GENMASK(15, 4)
+
+#define AD7173_ADC_MODE_REF_EN BIT(15)
+#define AD7173_ADC_MODE_SING_CYC BIT(13)
+#define AD7173_ADC_MODE_MODE_MASK GENMASK(6, 4)
+#define AD7173_ADC_MODE_CLOCKSEL_MASK GENMASK(3, 2)
+#define AD7173_ADC_MODE_CLOCKSEL_INT 0x0
+#define AD7173_ADC_MODE_CLOCKSEL_INT_OUTPUT 0x1
+#define AD7173_ADC_MODE_CLOCKSEL_EXT 0x2
+#define AD7173_ADC_MODE_CLOCKSEL_XTAL 0x3
+
+#define AD7173_GPIO_PDSW BIT(14)
+#define AD7173_GPIO_OP_EN2_3 BIT(13)
+#define AD7173_GPIO_MUX_IO BIT(12)
+#define AD7173_GPIO_SYNC_EN BIT(11)
+#define AD7173_GPIO_ERR_EN BIT(10)
+#define AD7173_GPIO_ERR_DAT BIT(9)
+#define AD7173_GPIO_GP_DATA3 BIT(7)
+#define AD7173_GPIO_GP_DATA2 BIT(6)
+#define AD7173_GPIO_IP_EN1 BIT(5)
+#define AD7173_GPIO_IP_EN0 BIT(4)
+#define AD7173_GPIO_OP_EN1 BIT(3)
+#define AD7173_GPIO_OP_EN0 BIT(2)
+#define AD7173_GPIO_GP_DATA1 BIT(1)
+#define AD7173_GPIO_GP_DATA0 BIT(0)
+
+#define AD7173_GPO12_DATA(x) BIT((x) + 0)
+#define AD7173_GPO23_DATA(x) BIT((x) + 4)
+#define AD7173_GPO_DATA(x) ((x) < 2 ? AD7173_GPO12_DATA(x) : AD7173_GPO23_DATA(x))
+
+#define AD7173_INTERFACE_DATA_STAT BIT(6)
+#define AD7173_INTERFACE_DATA_STAT_EN(x) \
+ FIELD_PREP(AD7173_INTERFACE_DATA_STAT, x)
+
+#define AD7173_SETUP_BIPOLAR BIT(12)
+#define AD7173_SETUP_AREF_BUF_MASK GENMASK(11, 10)
+#define AD7173_SETUP_AIN_BUF_MASK GENMASK(9, 8)
+
+#define AD7173_SETUP_REF_SEL_MASK GENMASK(5, 4)
+#define AD7173_SETUP_REF_SEL_AVDD1_AVSS 0x3
+#define AD7173_SETUP_REF_SEL_INT_REF 0x2
+#define AD7173_SETUP_REF_SEL_EXT_REF2 0x1
+#define AD7173_SETUP_REF_SEL_EXT_REF 0x0
+#define AD7173_VOLTAGE_INT_REF_uV 2500000
+#define AD7173_TEMP_SENSIIVITY_uV_per_C 477
+#define AD7177_ODR_START_VALUE 0x07
+
+#define AD7173_FILTER_ODR0_MASK GENMASK(5, 0)
+#define AD7173_MAX_CONFIGS 8
+
+enum ad7173_ids {
+ ID_AD7172_2,
+ ID_AD7172_4,
+ ID_AD7173_8,
+ ID_AD7175_2,
+ ID_AD7175_8,
+ ID_AD7176_2,
+ ID_AD7177_2,
+};
+
+struct ad7173_device_info {
+ const unsigned int *sinc5_data_rates;
+ unsigned int num_sinc5_data_rates;
+ unsigned int odr_start_value;
+ unsigned int num_channels;
+ unsigned int num_configs;
+ unsigned int num_inputs;
+ unsigned int clock;
+ unsigned int id;
+ char *name;
+ bool has_temp;
+ bool has_int_ref;
+ bool has_ref2;
+ u8 num_gpios;
+};
+
+struct ad7173_channel_config {
+ u8 cfg_slot;
+ bool live;
+
+ /* Following fields are used to compare equality. */
+ struct_group(config_props,
+ bool bipolar;
+ bool input_buf;
+ u8 odr;
+ u8 ref_sel;
+ );
+};
+
+struct ad7173_channel {
+ unsigned int chan_reg;
+ unsigned int ain;
+ struct ad7173_channel_config cfg;
+};
+
+struct ad7173_state {
+ struct ad_sigma_delta sd;
+ const struct ad7173_device_info *info;
+ struct ad7173_channel *channels;
+ struct regulator_bulk_data regulators[3];
+ unsigned int adc_mode;
+ unsigned int interface_mode;
+ unsigned int num_channels;
+ struct ida cfg_slots_status;
+ unsigned long long config_usage_counter;
+ unsigned long long *config_cnts;
+ struct clk *ext_clk;
+ struct clk_hw int_clk_hw;
+#if IS_ENABLED(CONFIG_GPIOLIB)
+ struct regmap *reg_gpiocon_regmap;
+ struct gpio_regmap *gpio_regmap;
+#endif
+};
+
+static const unsigned int ad7173_sinc5_data_rates[] = {
+ 6211000, 6211000, 6211000, 6211000, 6211000, 6211000, 5181000, 4444000, /* 0-7 */
+ 3115000, 2597000, 1007000, 503800, 381000, 200300, 100500, 59520, /* 8-15 */
+ 49680, 20010, 16333, 10000, 5000, 2500, 1250, /* 16-22 */
+};
+
+static const unsigned int ad7175_sinc5_data_rates[] = {
+ 50000000, 41667000, 31250000, 27778000, /* 0-3 */
+ 20833000, 17857000, 12500000, 10000000, /* 4-7 */
+ 5000000, 2500000, 1000000, 500000, /* 8-11 */
+ 397500, 200000, 100000, 59920, /* 12-15 */
+ 49960, 20000, 16666, 10000, /* 16-19 */
+ 5000, /* 20 */
+};
+
+static const struct ad7173_device_info ad7173_device_info[] = {
+ [ID_AD7172_2] = {
+ .name = "ad7172-2",
+ .id = AD7172_2_ID,
+ .num_inputs = 5,
+ .num_channels = 4,
+ .num_configs = 4,
+ .num_gpios = 2,
+ .has_temp = true,
+ .has_int_ref = true,
+ .clock = 2 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad7173_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
+ },
+ [ID_AD7172_4] = {
+ .id = AD7172_4_ID,
+ .num_inputs = 9,
+ .num_channels = 8,
+ .num_configs = 8,
+ .num_gpios = 4,
+ .has_temp = false,
+ .has_ref2 = true,
+ .clock = 2 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad7173_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
+ },
+ [ID_AD7173_8] = {
+ .name = "ad7173-8",
+ .id = AD7173_ID,
+ .num_inputs = 17,
+ .num_channels = 16,
+ .num_configs = 8,
+ .num_gpios = 4,
+ .has_temp = true,
+ .has_int_ref = true,
+ .has_ref2 = true,
+ .clock = 2 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad7173_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
+ },
+ [ID_AD7175_2] = {
+ .name = "ad7175-2",
+ .id = AD7175_2_ID,
+ .num_inputs = 5,
+ .num_channels = 4,
+ .num_configs = 4,
+ .num_gpios = 2,
+ .has_temp = true,
+ .has_int_ref = true,
+ .clock = 16 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad7175_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates),
+ },
+ [ID_AD7175_8] = {
+ .id = AD7175_8_ID,
+ .num_inputs = 17,
+ .num_channels = 16,
+ .num_configs = 8,
+ .num_gpios = 4,
+ .has_temp = true,
+ .has_int_ref = true,
+ .has_ref2 = true,
+ .clock = 16 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad7175_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates),
+ },
+ [ID_AD7176_2] = {
+ .name = "ad7176-2",
+ .id = AD7176_ID,
+ .num_inputs = 5,
+ .num_channels = 4,
+ .num_configs = 4,
+ .num_gpios = 2,
+ .has_temp = false,
+ .has_int_ref = true,
+ .clock = 16 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad7175_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates),
+ },
+ [ID_AD7177_2] = {
+ .id = AD7177_ID,
+ .num_inputs = 5,
+ .num_channels = 4,
+ .num_configs = 4,
+ .num_gpios = 2,
+ .has_temp = true,
+ .has_int_ref = true,
+ .clock = 16 * HZ_PER_MHZ,
+ .odr_start_value = AD7177_ODR_START_VALUE,
+ .sinc5_data_rates = ad7175_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad7175_sinc5_data_rates),
+ },
+};
+
+static const char *const ad7173_ref_sel_str[] = {
+ [AD7173_SETUP_REF_SEL_EXT_REF] = "vref",
+ [AD7173_SETUP_REF_SEL_EXT_REF2] = "vref2",
+ [AD7173_SETUP_REF_SEL_INT_REF] = "refout-avss",
+ [AD7173_SETUP_REF_SEL_AVDD1_AVSS] = "avdd",
+};
+
+static const char *const ad7173_clk_sel[] = {
+ "ext-clk", "xtal"
+};
+
+#if IS_ENABLED(CONFIG_GPIOLIB)
+
+static const struct regmap_range ad7173_range_gpio[] = {
+ regmap_reg_range(AD7173_REG_GPIO, AD7173_REG_GPIO),
+};
+
+static const struct regmap_access_table ad7173_access_table = {
+ .yes_ranges = ad7173_range_gpio,
+ .n_yes_ranges = ARRAY_SIZE(ad7173_range_gpio),
+};
+
+static const struct regmap_config ad7173_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .rd_table = &ad7173_access_table,
+ .wr_table = &ad7173_access_table,
+ .read_flag_mask = BIT(6),
+};
+
+static int ad7173_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
+ unsigned int offset, unsigned int *reg,
+ unsigned int *mask)
+{
+ *mask = AD7173_GPO_DATA(offset);
+ *reg = base;
+ return 0;
+}
+
+static void ad7173_gpio_disable(void *data)
+{
+ struct ad7173_state *st = data;
+ unsigned int mask;
+
+ mask = AD7173_GPIO_OP_EN0 | AD7173_GPIO_OP_EN1 | AD7173_GPIO_OP_EN2_3;
+ regmap_update_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, mask, ~mask);
+}
+
+static int ad7173_gpio_init(struct ad7173_state *st)
+{
+ struct gpio_regmap_config gpio_regmap = {};
+ struct device *dev = &st->sd.spi->dev;
+ unsigned int mask;
+ int ret;
+
+ st->reg_gpiocon_regmap = devm_regmap_init_spi(st->sd.spi, &ad7173_regmap_config);
+ ret = PTR_ERR_OR_ZERO(st->reg_gpiocon_regmap);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unable to init regmap\n");
+
+ mask = AD7173_GPIO_OP_EN0 | AD7173_GPIO_OP_EN1 | AD7173_GPIO_OP_EN2_3;
+ regmap_update_bits(st->reg_gpiocon_regmap, AD7173_REG_GPIO, mask, mask);
+
+ ret = devm_add_action_or_reset(dev, ad7173_gpio_disable, st);
+ if (ret)
+ return ret;
+
+ gpio_regmap.parent = dev;
+ gpio_regmap.regmap = st->reg_gpiocon_regmap;
+ gpio_regmap.ngpio = st->info->num_gpios;
+ gpio_regmap.reg_set_base = AD7173_REG_GPIO;
+ gpio_regmap.reg_mask_xlate = ad7173_mask_xlate;
+
+ st->gpio_regmap = devm_gpio_regmap_register(dev, &gpio_regmap);
+ ret = PTR_ERR_OR_ZERO(st->gpio_regmap);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unable to init gpio-regmap\n");
+
+ return 0;
+}
+#else
+static int ad7173_gpio_init(struct ad7173_state *st)
+{
+ return 0;
+}
+#endif /* CONFIG_GPIOLIB */
+
+static struct ad7173_state *ad_sigma_delta_to_ad7173(struct ad_sigma_delta *sd)
+{
+ return container_of(sd, struct ad7173_state, sd);
+}
+
+static struct ad7173_state *clk_hw_to_ad7173(struct clk_hw *hw)
+{
+ return container_of(hw, struct ad7173_state, int_clk_hw);
+}
+
+static void ad7173_ida_destroy(void *data)
+{
+ struct ad7173_state *st = data;
+
+ ida_destroy(&st->cfg_slots_status);
+}
+
+static void ad7173_reset_usage_cnts(struct ad7173_state *st)
+{
+ memset64(st->config_cnts, 0, st->info->num_configs);
+ st->config_usage_counter = 0;
+}
+
+static struct ad7173_channel_config *
+ad7173_find_live_config(struct ad7173_state *st, struct ad7173_channel_config *cfg)
+{
+ struct ad7173_channel_config *cfg_aux;
+ ptrdiff_t cmp_size;
+ int i;
+
+ cmp_size = sizeof_field(struct ad7173_channel_config, config_props);
+ for (i = 0; i < st->num_channels; i++) {
+ cfg_aux = &st->channels[i].cfg;
+
+ if (cfg_aux->live &&
+ !memcmp(&cfg->config_props, &cfg_aux->config_props, cmp_size))
+ return cfg_aux;
+ }
+ return NULL;
+}
+
+/* Could be replaced with a generic LRU implementation */
+static int ad7173_free_config_slot_lru(struct ad7173_state *st)
+{
+ int i, lru_position = 0;
+
+ for (i = 1; i < st->info->num_configs; i++)
+ if (st->config_cnts[i] < st->config_cnts[lru_position])
+ lru_position = i;
+
+ for (i = 0; i < st->num_channels; i++)
+ if (st->channels[i].cfg.cfg_slot == lru_position)
+ st->channels[i].cfg.live = false;
+
+ ida_free(&st->cfg_slots_status, lru_position);
+ return ida_alloc(&st->cfg_slots_status, GFP_KERNEL);
+}
+
+/* Could be replaced with a generic LRU implementation */
+static int ad7173_load_config(struct ad7173_state *st,
+ struct ad7173_channel_config *cfg)
+{
+ unsigned int config;
+ int free_cfg_slot, ret;
+
+ free_cfg_slot = ida_alloc_range(&st->cfg_slots_status, 0,
+ st->info->num_configs - 1, GFP_KERNEL);
+ if (free_cfg_slot < 0)
+ free_cfg_slot = ad7173_free_config_slot_lru(st);
+
+ cfg->cfg_slot = free_cfg_slot;
+ config = FIELD_PREP(AD7173_SETUP_REF_SEL_MASK, cfg->ref_sel);
+
+ if (cfg->bipolar)
+ config |= AD7173_SETUP_BIPOLAR;
+
+ if (cfg->input_buf)
+ config |= AD7173_SETUP_AIN_BUF_MASK;
+
+ ret = ad_sd_write_reg(&st->sd, AD7173_REG_SETUP(free_cfg_slot), 2, config);
+ if (ret)
+ return ret;
+
+ return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2,
+ AD7173_FILTER_ODR0_MASK & cfg->odr);
+}
+
+static int ad7173_config_channel(struct ad7173_state *st, int addr)
+{
+ struct ad7173_channel_config *cfg = &st->channels[addr].cfg;
+ struct ad7173_channel_config *live_cfg;
+ int ret;
+
+ if (!cfg->live) {
+ live_cfg = ad7173_find_live_config(st, cfg);
+ if (live_cfg) {
+ cfg->cfg_slot = live_cfg->cfg_slot;
+ } else {
+ ret = ad7173_load_config(st, cfg);
+ if (ret)
+ return ret;
+ cfg->live = true;
+ }
+ }
+
+ if (st->config_usage_counter == U64_MAX)
+ ad7173_reset_usage_cnts(st);
+
+ st->config_usage_counter++;
+ st->config_cnts[cfg->cfg_slot] = st->config_usage_counter;
+
+ return 0;
+}
+
+static int ad7173_set_channel(struct ad_sigma_delta *sd, unsigned int channel)
+{
+ struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd);
+ unsigned int val;
+ int ret;
+
+ ret = ad7173_config_channel(st, channel);
+ if (ret)
+ return ret;
+
+ val = AD7173_CH_ENABLE |
+ FIELD_PREP(AD7173_CH_SETUP_SEL_MASK, st->channels[channel].cfg.cfg_slot) |
+ st->channels[channel].ain;
+
+ return ad_sd_write_reg(&st->sd, AD7173_REG_CH(channel), 2, val);
+}
+
+static int ad7173_set_mode(struct ad_sigma_delta *sd,
+ enum ad_sigma_delta_mode mode)
+{
+ struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd);
+
+ st->adc_mode &= ~AD7173_ADC_MODE_MODE_MASK;
+ st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_MODE_MASK, mode);
+
+ return ad_sd_write_reg(&st->sd, AD7173_REG_ADC_MODE, 2, st->adc_mode);
+}
+
+static int ad7173_append_status(struct ad_sigma_delta *sd, bool append)
+{
+ struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd);
+ unsigned int interface_mode = st->interface_mode;
+ int ret;
+
+ interface_mode |= AD7173_INTERFACE_DATA_STAT_EN(append);
+ ret = ad_sd_write_reg(&st->sd, AD7173_REG_INTERFACE_MODE, 2, interface_mode);
+ if (ret)
+ return ret;
+
+ st->interface_mode = interface_mode;
+
+ return 0;
+}
+
+static int ad7173_disable_all(struct ad_sigma_delta *sd)
+{
+ struct ad7173_state *st = ad_sigma_delta_to_ad7173(sd);
+ int ret;
+ int i;
+
+ for (i = 0; i < st->num_channels; i++) {
+ ret = ad_sd_write_reg(sd, AD7173_REG_CH(i), 2, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct ad_sigma_delta_info ad7173_sigma_delta_info = {
+ .set_channel = ad7173_set_channel,
+ .append_status = ad7173_append_status,
+ .disable_all = ad7173_disable_all,
+ .set_mode = ad7173_set_mode,
+ .has_registers = true,
+ .addr_shift = 0,
+ .read_mask = BIT(6),
+ .status_ch_mask = GENMASK(3, 0),
+ .data_reg = AD7173_REG_DATA,
+};
+
+static int ad7173_setup(struct iio_dev *indio_dev)
+{
+ struct ad7173_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->sd.spi->dev;
+ u8 buf[AD7173_RESET_LENGTH];
+ unsigned int id;
+ int ret;
+
+ /* reset the serial interface */
+ memset(buf, 0xff, AD7173_RESET_LENGTH);
+ ret = spi_write_then_read(st->sd.spi, buf, sizeof(buf), NULL, 0);
+ if (ret < 0)
+ return ret;
+
+ /* datasheet recommends a delay of at least 500us after reset */
+ fsleep(500);
+
+ ret = ad_sd_read_reg(&st->sd, AD7173_REG_ID, 2, &id);
+ if (ret)
+ return ret;
+
+ id &= AD7173_ID_MASK;
+ if (id != st->info->id)
+ dev_warn(dev, "Unexpected device id: 0x%04X, expected: 0x%04X\n",
+ id, st->info->id);
+
+ st->adc_mode |= AD7173_ADC_MODE_SING_CYC;
+ st->interface_mode = 0x0;
+
+ st->config_usage_counter = 0;
+ st->config_cnts = devm_kcalloc(dev, st->info->num_configs,
+ sizeof(*st->config_cnts), GFP_KERNEL);
+ if (!st->config_cnts)
+ return -ENOMEM;
+
+ /* All channels are enabled by default after a reset */
+ return ad7173_disable_all(&st->sd);
+}
+
+static unsigned int ad7173_get_ref_voltage_milli(struct ad7173_state *st,
+ u8 reference_select)
+{
+ int vref;
+
+ switch (reference_select) {
+ case AD7173_SETUP_REF_SEL_EXT_REF:
+ vref = regulator_get_voltage(st->regulators[0].consumer);
+ break;
+
+ case AD7173_SETUP_REF_SEL_EXT_REF2:
+ vref = regulator_get_voltage(st->regulators[1].consumer);
+ break;
+
+ case AD7173_SETUP_REF_SEL_INT_REF:
+ vref = AD7173_VOLTAGE_INT_REF_uV;
+ break;
+
+ case AD7173_SETUP_REF_SEL_AVDD1_AVSS:
+ vref = regulator_get_voltage(st->regulators[2].consumer);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (vref < 0)
+ return vref;
+
+ return vref / (MICRO / MILLI);
+}
+
+static int ad7173_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct ad7173_state *st = iio_priv(indio_dev);
+ struct ad7173_channel *ch = &st->channels[chan->address];
+ unsigned int reg;
+ u64 temp;
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ ret = ad_sigma_delta_single_conversion(indio_dev, chan, val);
+ if (ret < 0)
+ return ret;
+
+ /* disable channel after single conversion */
+ ret = ad_sd_write_reg(&st->sd, AD7173_REG_CH(chan->address), 2, 0);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_TEMP) {
+ temp = AD7173_VOLTAGE_INT_REF_uV * MILLI;
+ temp /= AD7173_TEMP_SENSIIVITY_uV_per_C;
+ *val = temp;
+ *val2 = chan->scan_type.realbits;
+ } else {
+ *val = ad7173_get_ref_voltage_milli(st, ch->cfg.ref_sel);
+ *val2 = chan->scan_type.realbits - !!(ch->cfg.bipolar);
+ }
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_OFFSET:
+ if (chan->type == IIO_TEMP) {
+ /* 0 Kelvin -> raw sample */
+ temp = -ABSOLUTE_ZERO_MILLICELSIUS;
+ temp *= AD7173_TEMP_SENSIIVITY_uV_per_C;
+ temp <<= chan->scan_type.realbits;
+ temp = DIV_U64_ROUND_CLOSEST(temp,
+ AD7173_VOLTAGE_INT_REF_uV *
+ MILLI);
+ *val = -temp;
+ } else {
+ *val = -BIT(chan->scan_type.realbits - 1);
+ }
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ reg = st->channels[chan->address].cfg.odr;
+
+ *val = st->info->sinc5_data_rates[reg] / MILLI;
+ *val2 = (st->info->sinc5_data_rates[reg] % MILLI) * (MICRO / MILLI);
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad7173_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct ad7173_state *st = iio_priv(indio_dev);
+ struct ad7173_channel_config *cfg;
+ unsigned int freq, i, reg;
+ int ret;
+
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ freq = val * MILLI + val2 / MILLI;
+ for (i = st->info->odr_start_value; i < st->info->num_sinc5_data_rates - 1; i++)
+ if (freq >= st->info->sinc5_data_rates[i])
+ break;
+
+ cfg = &st->channels[chan->address].cfg;
+ cfg->odr = i;
+
+ if (!cfg->live)
+ break;
+
+ ret = ad_sd_read_reg(&st->sd, AD7173_REG_FILTER(cfg->cfg_slot), 2, &reg);
+ if (ret)
+ break;
+ reg &= ~AD7173_FILTER_ODR0_MASK;
+ reg |= FIELD_PREP(AD7173_FILTER_ODR0_MASK, i);
+ ret = ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(cfg->cfg_slot), 2, reg);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+}
+
+static int ad7173_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ struct ad7173_state *st = iio_priv(indio_dev);
+ int i, ret;
+
+ for (i = 0; i < indio_dev->num_channels; i++) {
+ if (test_bit(i, scan_mask))
+ ret = ad7173_set_channel(&st->sd, i);
+ else
+ ret = ad_sd_write_reg(&st->sd, AD7173_REG_CH(i), 2, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ad7173_debug_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct ad7173_state *st = iio_priv(indio_dev);
+ u8 reg_size;
+
+ if (reg == AD7173_REG_COMMS)
+ reg_size = 1;
+ else if (reg == AD7173_REG_CRC || reg == AD7173_REG_DATA ||
+ reg >= AD7173_REG_OFFSET(0))
+ reg_size = 3;
+ else
+ reg_size = 2;
+
+ if (readval)
+ return ad_sd_read_reg(&st->sd, reg, reg_size, readval);
+
+ return ad_sd_write_reg(&st->sd, reg, reg_size, writeval);
+}
+
+static const struct iio_info ad7173_info = {
+ .read_raw = &ad7173_read_raw,
+ .write_raw = &ad7173_write_raw,
+ .debugfs_reg_access = &ad7173_debug_reg_access,
+ .validate_trigger = ad_sd_validate_trigger,
+ .update_scan_mode = ad7173_update_scan_mode,
+};
+
+static const struct iio_chan_spec ad7173_channel_template = {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .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_type = {
+ .sign = 'u',
+ .realbits = 24,
+ .storagebits = 32,
+ .endianness = IIO_BE,
+ },
+};
+
+static const struct iio_chan_spec ad7173_temp_iio_channel_template = {
+ .type = IIO_TEMP,
+ .indexed = 1,
+ .channel = AD7173_AIN_TEMP_POS,
+ .channel2 = AD7173_AIN_TEMP_NEG,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 24,
+ .storagebits = 32,
+ .endianness = IIO_BE,
+ },
+};
+
+static void ad7173_disable_regulators(void *data)
+{
+ struct ad7173_state *st = data;
+
+ regulator_bulk_disable(ARRAY_SIZE(st->regulators), st->regulators);
+}
+
+static void ad7173_clk_disable_unprepare(void *clk)
+{
+ clk_disable_unprepare(clk);
+}
+
+static unsigned long ad7173_sel_clk(struct ad7173_state *st,
+ unsigned int clk_sel)
+{
+ int ret;
+
+ st->adc_mode &= ~AD7173_ADC_MODE_CLOCKSEL_MASK;
+ st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK, clk_sel);
+ ret = ad_sd_write_reg(&st->sd, AD7173_REG_ADC_MODE, 0x2, st->adc_mode);
+
+ return ret;
+}
+
+static unsigned long ad7173_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct ad7173_state *st = clk_hw_to_ad7173(hw);
+
+ return st->info->clock / HZ_PER_KHZ;
+}
+
+static int ad7173_clk_output_is_enabled(struct clk_hw *hw)
+{
+ struct ad7173_state *st = clk_hw_to_ad7173(hw);
+ u32 clk_sel;
+
+ clk_sel = FIELD_GET(AD7173_ADC_MODE_CLOCKSEL_MASK, st->adc_mode);
+ return clk_sel == AD7173_ADC_MODE_CLOCKSEL_INT_OUTPUT;
+}
+
+static int ad7173_clk_output_prepare(struct clk_hw *hw)
+{
+ struct ad7173_state *st = clk_hw_to_ad7173(hw);
+
+ return ad7173_sel_clk(st, AD7173_ADC_MODE_CLOCKSEL_INT_OUTPUT);
+}
+
+static void ad7173_clk_output_unprepare(struct clk_hw *hw)
+{
+ struct ad7173_state *st = clk_hw_to_ad7173(hw);
+
+ ad7173_sel_clk(st, AD7173_ADC_MODE_CLOCKSEL_INT);
+}
+
+static const struct clk_ops ad7173_int_clk_ops = {
+ .recalc_rate = ad7173_clk_recalc_rate,
+ .is_enabled = ad7173_clk_output_is_enabled,
+ .prepare = ad7173_clk_output_prepare,
+ .unprepare = ad7173_clk_output_unprepare,
+};
+
+static int ad7173_register_clk_provider(struct iio_dev *indio_dev)
+{
+ struct ad7173_state *st = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ struct clk_init_data init = {};
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_COMMON_CLK))
+ return 0;
+
+ init.name = fwnode_get_name(fwnode);
+ init.ops = &ad7173_int_clk_ops;
+
+ st->int_clk_hw.init = &init;
+ ret = devm_clk_hw_register(dev, &st->int_clk_hw);
+ if (ret)
+ return ret;
+
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+ &st->int_clk_hw);
+}
+
+static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
+{
+ struct ad7173_channel *chans_st_arr, *chan_st_priv;
+ struct ad7173_state *st = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
+ struct iio_chan_spec *chan_arr, *chan;
+ unsigned int ain[2], chan_index = 0;
+ int ref_sel, ret;
+
+ chan_arr = devm_kcalloc(dev, sizeof(*indio_dev->channels),
+ st->num_channels, GFP_KERNEL);
+ if (!chan_arr)
+ return -ENOMEM;
+
+ chans_st_arr = devm_kcalloc(dev, st->num_channels, sizeof(*st->channels),
+ GFP_KERNEL);
+ if (!chans_st_arr)
+ return -ENOMEM;
+
+ indio_dev->channels = chan_arr;
+ st->channels = chans_st_arr;
+
+ if (st->info->has_temp) {
+ chan_arr[chan_index] = ad7173_temp_iio_channel_template;
+ chan_st_priv = &chans_st_arr[chan_index];
+ chan_st_priv->ain =
+ AD7173_CH_ADDRESS(chan_arr[chan_index].channel,
+ chan_arr[chan_index].channel2);
+ chan_st_priv->cfg.bipolar = false;
+ chan_st_priv->cfg.input_buf = true;
+ chan_st_priv->cfg.ref_sel = AD7173_SETUP_REF_SEL_INT_REF;
+ st->adc_mode |= AD7173_ADC_MODE_REF_EN;
+
+ chan_index++;
+ }
+
+ device_for_each_child_node_scoped(dev, child) {
+ chan = &chan_arr[chan_index];
+ chan_st_priv = &chans_st_arr[chan_index];
+ ret = fwnode_property_read_u32_array(child, "diff-channels",
+ ain, ARRAY_SIZE(ain));
+ if (ret)
+ return ret;
+
+ if (ain[0] >= st->info->num_inputs ||
+ ain[1] >= st->info->num_inputs)
+ return dev_err_probe(dev, -EINVAL,
+ "Input pin number out of range for pair (%d %d).\n",
+ ain[0], ain[1]);
+
+ ret = fwnode_property_match_property_string(child,
+ "adi,reference-select",
+ ad7173_ref_sel_str,
+ ARRAY_SIZE(ad7173_ref_sel_str));
+ if (ret < 0)
+ ref_sel = AD7173_SETUP_REF_SEL_INT_REF;
+ else
+ ref_sel = ret;
+
+ if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF &&
+ !st->info->has_int_ref)
+ return dev_err_probe(dev, -EINVAL,
+ "Internal reference is not available on current model.\n");
+
+ if (ref_sel == AD7173_SETUP_REF_SEL_EXT_REF2 && !st->info->has_ref2)
+ return dev_err_probe(dev, -EINVAL,
+ "External reference 2 is not available on current model.\n");
+
+ ret = ad7173_get_ref_voltage_milli(st, ref_sel);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Cannot use reference %u\n", ref_sel);
+
+ if (ref_sel == AD7173_SETUP_REF_SEL_INT_REF)
+ st->adc_mode |= AD7173_ADC_MODE_REF_EN;
+ chan_st_priv->cfg.ref_sel = ref_sel;
+
+ *chan = ad7173_channel_template;
+ chan->address = chan_index;
+ chan->scan_index = chan_index;
+ chan->channel = ain[0];
+ chan->channel2 = ain[1];
+ chan->differential = true;
+
+ chan_st_priv->ain = AD7173_CH_ADDRESS(ain[0], ain[1]);
+ chan_st_priv->chan_reg = chan_index;
+ chan_st_priv->cfg.input_buf = true;
+ chan_st_priv->cfg.odr = 0;
+
+ chan_st_priv->cfg.bipolar = fwnode_property_read_bool(child, "bipolar");
+ if (chan_st_priv->cfg.bipolar)
+ chan->info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET);
+
+ chan_index++;
+ }
+ return 0;
+}
+
+static int ad7173_fw_parse_device_config(struct iio_dev *indio_dev)
+{
+ struct ad7173_state *st = iio_priv(indio_dev);
+ struct device *dev = indio_dev->dev.parent;
+ unsigned int num_channels;
+ int ret;
+
+ st->regulators[0].supply = ad7173_ref_sel_str[AD7173_SETUP_REF_SEL_EXT_REF];
+ st->regulators[1].supply = ad7173_ref_sel_str[AD7173_SETUP_REF_SEL_EXT_REF2];
+ st->regulators[2].supply = ad7173_ref_sel_str[AD7173_SETUP_REF_SEL_AVDD1_AVSS];
+
+ /*
+ * If a regulator is not available, it will be set to a dummy regulator.
+ * Each channel reference is checked with regulator_get_voltage() before
+ * setting attributes so if any channel uses a dummy supply the driver
+ * probe will fail.
+ */
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(st->regulators),
+ st->regulators);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get regulators\n");
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(st->regulators), st->regulators);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable regulators\n");
+
+ ret = devm_add_action_or_reset(dev, ad7173_disable_regulators, st);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to add regulators disable action\n");
+
+ ret = device_property_match_property_string(dev, "clock-names",
+ ad7173_clk_sel,
+ ARRAY_SIZE(ad7173_clk_sel));
+ if (ret < 0) {
+ st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK,
+ AD7173_ADC_MODE_CLOCKSEL_INT);
+ ad7173_register_clk_provider(indio_dev);
+ } else {
+ st->adc_mode |= FIELD_PREP(AD7173_ADC_MODE_CLOCKSEL_MASK,
+ AD7173_ADC_MODE_CLOCKSEL_EXT + ret);
+ st->ext_clk = devm_clk_get(dev, ad7173_clk_sel[ret]);
+ if (IS_ERR(st->ext_clk))
+ return dev_err_probe(dev, PTR_ERR(st->ext_clk),
+ "Failed to get external clock\n");
+
+ ret = clk_prepare_enable(st->ext_clk);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to enable external clock\n");
+
+ ret = devm_add_action_or_reset(dev, ad7173_clk_disable_unprepare,
+ st->ext_clk);
+ if (ret)
+ return ret;
+ }
+
+ ret = fwnode_irq_get_byname(dev_fwnode(dev), "rdy");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Interrupt 'rdy' is required\n");
+
+ ad7173_sigma_delta_info.irq_line = ret;
+
+ num_channels = device_get_child_node_count(dev);
+
+ if (st->info->has_temp)
+ num_channels++;
+
+ if (num_channels == 0)
+ return dev_err_probe(dev, -ENODATA, "No channels specified\n");
+ indio_dev->num_channels = num_channels;
+ st->num_channels = num_channels;
+
+ return ad7173_fw_parse_channel_config(indio_dev);
+}
+
+static int ad7173_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct ad7173_state *st;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -ENODEV;
+
+ ida_init(&st->cfg_slots_status);
+ ret = devm_add_action_or_reset(dev, ad7173_ida_destroy, st);
+ if (ret)
+ return ret;
+
+ indio_dev->name = st->info->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ad7173_info;
+
+ spi->mode = SPI_MODE_3;
+ spi_setup(spi);
+
+ ad7173_sigma_delta_info.num_slots = st->info->num_configs;
+ ret = ad_sd_init(&st->sd, indio_dev, spi, &ad7173_sigma_delta_info);
+ if (ret)
+ return ret;
+
+ ret = ad7173_fw_parse_device_config(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_ad_sd_setup_buffer_and_trigger(dev, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = ad7173_setup(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return ret;
+
+ if (IS_ENABLED(CONFIG_GPIOLIB))
+ return ad7173_gpio_init(st);
+
+ return 0;
+}
+
+static const struct of_device_id ad7173_of_match[] = {
+ { .compatible = "adi,ad7172-2",
+ .data = &ad7173_device_info[ID_AD7172_2]},
+ { .compatible = "adi,ad7172-4",
+ .data = &ad7173_device_info[ID_AD7172_4]},
+ { .compatible = "adi,ad7173-8",
+ .data = &ad7173_device_info[ID_AD7173_8]},
+ { .compatible = "adi,ad7175-2",
+ .data = &ad7173_device_info[ID_AD7175_2]},
+ { .compatible = "adi,ad7175-8",
+ .data = &ad7173_device_info[ID_AD7175_8]},
+ { .compatible = "adi,ad7176-2",
+ .data = &ad7173_device_info[ID_AD7176_2]},
+ { .compatible = "adi,ad7177-2",
+ .data = &ad7173_device_info[ID_AD7177_2]},
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad7173_of_match);
+
+static const struct spi_device_id ad7173_id_table[] = {
+ { "ad7172-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7172_2]},
+ { "ad7172-4", (kernel_ulong_t)&ad7173_device_info[ID_AD7172_4]},
+ { "ad7173-8", (kernel_ulong_t)&ad7173_device_info[ID_AD7173_8]},
+ { "ad7175-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7175_2]},
+ { "ad7175-8", (kernel_ulong_t)&ad7173_device_info[ID_AD7175_8]},
+ { "ad7176-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7176_2]},
+ { "ad7177-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7177_2]},
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad7173_id_table);
+
+static struct spi_driver ad7173_driver = {
+ .driver = {
+ .name = "ad7173",
+ .of_match_table = ad7173_of_match,
+ },
+ .probe = ad7173_probe,
+ .id_table = ad7173_id_table,
+};
+module_spi_driver(ad7173_driver);
+
+MODULE_IMPORT_NS(IIO_AD_SIGMA_DELTA);
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafo.de>");
+MODULE_AUTHOR("Dumitru Ceclan <dumitru.ceclan@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7172/AD7173/AD7175/AD7176 ADC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/ad7192.c b/drivers/iio/adc/ad7192.c
index adc3cbe92d6e6e..7bcc7e2aa2a20c 100644
--- a/drivers/iio/adc/ad7192.c
+++ b/drivers/iio/adc/ad7192.c
@@ -17,7 +17,9 @@
#include <linux/err.h>
#include <linux/sched.h>
#include <linux/delay.h>
-#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
@@ -364,19 +366,19 @@ static inline bool ad7192_valid_external_frequency(u32 freq)
freq <= AD7192_EXT_FREQ_MHZ_MAX);
}
-static int ad7192_of_clock_select(struct ad7192_state *st)
+static int ad7192_clock_select(struct ad7192_state *st)
{
- struct device_node *np = st->sd.spi->dev.of_node;
+ struct device *dev = &st->sd.spi->dev;
unsigned int clock_sel;
clock_sel = AD7192_CLK_INT;
/* use internal clock */
if (!st->mclk) {
- if (of_property_read_bool(np, "adi,int-clock-output-enable"))
+ if (device_property_read_bool(dev, "adi,int-clock-output-enable"))
clock_sel = AD7192_CLK_INT_CO;
} else {
- if (of_property_read_bool(np, "adi,clock-xtal"))
+ if (device_property_read_bool(dev, "adi,clock-xtal"))
clock_sel = AD7192_CLK_EXT_MCLK1_2;
else
clock_sel = AD7192_CLK_EXT_MCLK2;
@@ -385,7 +387,7 @@ static int ad7192_of_clock_select(struct ad7192_state *st)
return clock_sel;
}
-static int ad7192_setup(struct iio_dev *indio_dev, struct device_node *np)
+static int ad7192_setup(struct iio_dev *indio_dev, struct device *dev)
{
struct ad7192_state *st = iio_priv(indio_dev);
bool rej60_en, refin2_en;
@@ -407,7 +409,7 @@ static int ad7192_setup(struct iio_dev *indio_dev, struct device_node *np)
id = FIELD_GET(AD7192_ID_MASK, id);
if (id != st->chip_info->chip_id)
- dev_warn(&st->sd.spi->dev, "device ID query failed (0x%X != 0x%X)\n",
+ dev_warn(dev, "device ID query failed (0x%X != 0x%X)\n",
id, st->chip_info->chip_id);
st->mode = FIELD_PREP(AD7192_MODE_SEL_MASK, AD7192_MODE_IDLE) |
@@ -416,30 +418,30 @@ static int ad7192_setup(struct iio_dev *indio_dev, struct device_node *np)
st->conf = FIELD_PREP(AD7192_CONF_GAIN_MASK, 0);
- rej60_en = of_property_read_bool(np, "adi,rejection-60-Hz-enable");
+ rej60_en = device_property_read_bool(dev, "adi,rejection-60-Hz-enable");
if (rej60_en)
st->mode |= AD7192_MODE_REJ60;
- refin2_en = of_property_read_bool(np, "adi,refin2-pins-enable");
+ refin2_en = device_property_read_bool(dev, "adi,refin2-pins-enable");
if (refin2_en && st->chip_info->chip_id != CHIPID_AD7195)
st->conf |= AD7192_CONF_REFSEL;
st->conf &= ~AD7192_CONF_CHOP;
- buf_en = of_property_read_bool(np, "adi,buffer-enable");
+ buf_en = device_property_read_bool(dev, "adi,buffer-enable");
if (buf_en)
st->conf |= AD7192_CONF_BUF;
- bipolar = of_property_read_bool(np, "bipolar");
+ bipolar = device_property_read_bool(dev, "bipolar");
if (!bipolar)
st->conf |= AD7192_CONF_UNIPOLAR;
- burnout_curr_en = of_property_read_bool(np,
- "adi,burnout-currents-enable");
+ burnout_curr_en = device_property_read_bool(dev,
+ "adi,burnout-currents-enable");
if (burnout_curr_en && buf_en) {
st->conf |= AD7192_CONF_BURN;
} else if (burnout_curr_en) {
- dev_warn(&st->sd.spi->dev,
+ dev_warn(dev,
"Can't enable burnout currents: see CHOP or buffer\n");
}
@@ -1117,9 +1119,7 @@ static int ad7192_probe(struct spi_device *spi)
}
st->int_vref_mv = ret / 1000;
- st->chip_info = of_device_get_match_data(&spi->dev);
- if (!st->chip_info)
- st->chip_info = (void *)spi_get_device_id(spi)->driver_data;
+ st->chip_info = spi_get_device_match_data(spi);
indio_dev->name = st->chip_info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = st->chip_info->channels;
@@ -1140,7 +1140,7 @@ static int ad7192_probe(struct spi_device *spi)
if (IS_ERR(st->mclk))
return PTR_ERR(st->mclk);
- st->clock_sel = ad7192_of_clock_select(st);
+ st->clock_sel = ad7192_clock_select(st);
if (st->clock_sel == AD7192_CLK_EXT_MCLK1_2 ||
st->clock_sel == AD7192_CLK_EXT_MCLK2) {
@@ -1152,7 +1152,7 @@ static int ad7192_probe(struct spi_device *spi)
}
}
- ret = ad7192_setup(indio_dev, spi->dev.of_node);
+ ret = ad7192_setup(indio_dev, &spi->dev);
if (ret)
return ret;
diff --git a/drivers/iio/adc/ad7292.c b/drivers/iio/adc/ad7292.c
index cccacec5db6d22..6aadd14f459dfe 100644
--- a/drivers/iio/adc/ad7292.c
+++ b/drivers/iio/adc/ad7292.c
@@ -8,7 +8,8 @@
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
@@ -260,7 +261,6 @@ static int ad7292_probe(struct spi_device *spi)
{
struct ad7292_state *st;
struct iio_dev *indio_dev;
- struct device_node *child;
bool diff_channels = false;
int ret;
@@ -305,12 +305,11 @@ static int ad7292_probe(struct spi_device *spi)
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &ad7292_info;
- for_each_available_child_of_node(spi->dev.of_node, child) {
- diff_channels = of_property_read_bool(child, "diff-channels");
- if (diff_channels) {
- of_node_put(child);
+ device_for_each_child_node_scoped(&spi->dev, child) {
+ diff_channels = fwnode_property_read_bool(child,
+ "diff-channels");
+ if (diff_channels)
break;
- }
}
if (diff_channels) {
diff --git a/drivers/iio/adc/ad7944.c b/drivers/iio/adc/ad7944.c
new file mode 100644
index 00000000000000..4af574ffa8641b
--- /dev/null
+++ b/drivers/iio/adc/ad7944.c
@@ -0,0 +1,524 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices AD7944/85/86 PulSAR ADC family driver.
+ *
+ * Copyright 2024 Analog Devices, Inc.
+ * Copyright 2024 BayLibre, SAS
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/string_helpers.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define AD7944_INTERNAL_REF_MV 4096
+
+struct ad7944_timing_spec {
+ /* Normal mode max conversion time (t_{CONV}). */
+ unsigned int conv_ns;
+ /* TURBO mode max conversion time (t_{CONV}). */
+ unsigned int turbo_conv_ns;
+};
+
+enum ad7944_spi_mode {
+ /* datasheet calls this "4-wire mode" */
+ AD7944_SPI_MODE_DEFAULT,
+ /* datasheet calls this "3-wire mode" (not related to SPI_3WIRE!) */
+ AD7944_SPI_MODE_SINGLE,
+ /* datasheet calls this "chain mode" */
+ AD7944_SPI_MODE_CHAIN,
+};
+
+/* maps adi,spi-mode property value to enum */
+static const char * const ad7944_spi_modes[] = {
+ [AD7944_SPI_MODE_DEFAULT] = "",
+ [AD7944_SPI_MODE_SINGLE] = "single",
+ [AD7944_SPI_MODE_CHAIN] = "chain",
+};
+
+struct ad7944_adc {
+ struct spi_device *spi;
+ enum ad7944_spi_mode spi_mode;
+ struct spi_transfer xfers[3];
+ struct spi_message msg;
+ /* Chip-specific timing specifications. */
+ const struct ad7944_timing_spec *timing_spec;
+ /* GPIO connected to CNV pin. */
+ struct gpio_desc *cnv;
+ /* Optional GPIO to enable turbo mode. */
+ struct gpio_desc *turbo;
+ /* Indicates TURBO is hard-wired to be always enabled. */
+ bool always_turbo;
+ /* Reference voltage (millivolts). */
+ unsigned int ref_mv;
+
+ /*
+ * DMA (thus cache coherency maintenance) requires the
+ * transfer buffers to live in their own cache lines.
+ */
+ struct {
+ union {
+ u16 u16;
+ u32 u32;
+ } raw;
+ u64 timestamp __aligned(8);
+ } sample __aligned(IIO_DMA_MINALIGN);
+};
+
+/* quite time before CNV rising edge */
+#define T_QUIET_NS 20
+
+static const struct ad7944_timing_spec ad7944_timing_spec = {
+ .conv_ns = 420,
+ .turbo_conv_ns = 320,
+};
+
+static const struct ad7944_timing_spec ad7986_timing_spec = {
+ .conv_ns = 500,
+ .turbo_conv_ns = 400,
+};
+
+struct ad7944_chip_info {
+ const char *name;
+ const struct ad7944_timing_spec *timing_spec;
+ const struct iio_chan_spec channels[2];
+};
+
+/*
+ * AD7944_DEFINE_CHIP_INFO - Define a chip info structure for a specific chip
+ * @_name: The name of the chip
+ * @_ts: The timing specification for the chip
+ * @_bits: The number of bits in the conversion result
+ * @_diff: Whether the chip is true differential or not
+ */
+#define AD7944_DEFINE_CHIP_INFO(_name, _ts, _bits, _diff) \
+static const struct ad7944_chip_info _name##_chip_info = { \
+ .name = #_name, \
+ .timing_spec = &_ts##_timing_spec, \
+ .channels = { \
+ { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .differential = _diff, \
+ .channel = 0, \
+ .channel2 = _diff ? 1 : 0, \
+ .scan_index = 0, \
+ .scan_type.sign = _diff ? 's' : 'u', \
+ .scan_type.realbits = _bits, \
+ .scan_type.storagebits = _bits > 16 ? 32 : 16, \
+ .scan_type.endianness = IIO_CPU, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
+ | BIT(IIO_CHAN_INFO_SCALE), \
+ }, \
+ IIO_CHAN_SOFT_TIMESTAMP(1), \
+ }, \
+}
+
+/* pseudo-differential with ground sense */
+AD7944_DEFINE_CHIP_INFO(ad7944, ad7944, 14, 0);
+AD7944_DEFINE_CHIP_INFO(ad7985, ad7944, 16, 0);
+/* fully differential */
+AD7944_DEFINE_CHIP_INFO(ad7986, ad7986, 18, 1);
+
+static void ad7944_unoptimize_msg(void *msg)
+{
+ spi_unoptimize_message(msg);
+}
+
+static int ad7944_3wire_cs_mode_init_msg(struct device *dev, struct ad7944_adc *adc,
+ const struct iio_chan_spec *chan)
+{
+ unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns
+ : adc->timing_spec->conv_ns;
+ struct spi_transfer *xfers = adc->xfers;
+ int ret;
+
+ /*
+ * NB: can get better performance from some SPI controllers if we use
+ * the same bits_per_word in every transfer.
+ */
+ xfers[0].bits_per_word = chan->scan_type.realbits;
+ /*
+ * CS is tied to CNV and we need a low to high transition to start the
+ * conversion, so place CNV low for t_QUIET to prepare for this.
+ */
+ xfers[0].delay.value = T_QUIET_NS;
+ xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS;
+
+ /*
+ * CS has to be high for full conversion time to avoid triggering the
+ * busy indication.
+ */
+ xfers[1].cs_off = 1;
+ xfers[1].delay.value = t_conv_ns;
+ xfers[1].delay.unit = SPI_DELAY_UNIT_NSECS;
+ xfers[1].bits_per_word = chan->scan_type.realbits;
+
+ /* Then we can read the data during the acquisition phase */
+ xfers[2].rx_buf = &adc->sample.raw;
+ xfers[2].len = BITS_TO_BYTES(chan->scan_type.storagebits);
+ xfers[2].bits_per_word = chan->scan_type.realbits;
+
+ spi_message_init_with_transfers(&adc->msg, xfers, 3);
+
+ ret = spi_optimize_message(adc->spi, &adc->msg);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->msg);
+}
+
+static int ad7944_4wire_mode_init_msg(struct device *dev, struct ad7944_adc *adc,
+ const struct iio_chan_spec *chan)
+{
+ unsigned int t_conv_ns = adc->always_turbo ? adc->timing_spec->turbo_conv_ns
+ : adc->timing_spec->conv_ns;
+ struct spi_transfer *xfers = adc->xfers;
+ int ret;
+
+ /*
+ * NB: can get better performance from some SPI controllers if we use
+ * the same bits_per_word in every transfer.
+ */
+ xfers[0].bits_per_word = chan->scan_type.realbits;
+ /*
+ * CS has to be high for full conversion time to avoid triggering the
+ * busy indication.
+ */
+ xfers[0].cs_off = 1;
+ xfers[0].delay.value = t_conv_ns;
+ xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS;
+
+ xfers[1].rx_buf = &adc->sample.raw;
+ xfers[1].len = BITS_TO_BYTES(chan->scan_type.storagebits);
+ xfers[1].bits_per_word = chan->scan_type.realbits;
+
+ spi_message_init_with_transfers(&adc->msg, xfers, 2);
+
+ ret = spi_optimize_message(adc->spi, &adc->msg);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, ad7944_unoptimize_msg, &adc->msg);
+}
+
+/**
+ * ad7944_convert_and_acquire - Perform a single conversion and acquisition
+ * @adc: The ADC device structure
+ * @chan: The channel specification
+ * Return: 0 on success, a negative error code on failure
+ *
+ * Perform a conversion and acquisition of a single sample using the
+ * pre-optimized adc->msg.
+ *
+ * Upon successful return adc->sample.raw will contain the conversion result.
+ */
+static int ad7944_convert_and_acquire(struct ad7944_adc *adc,
+ const struct iio_chan_spec *chan)
+{
+ int ret;
+
+ /*
+ * In 4-wire mode, the CNV line is held high for the entire conversion
+ * and acquisition process. In other modes adc->cnv is NULL and is
+ * ignored (CS is wired to CNV in those cases).
+ */
+ gpiod_set_value_cansleep(adc->cnv, 1);
+ ret = spi_sync(adc->spi, &adc->msg);
+ gpiod_set_value_cansleep(adc->cnv, 0);
+
+ return ret;
+}
+
+static int ad7944_single_conversion(struct ad7944_adc *adc,
+ const struct iio_chan_spec *chan,
+ int *val)
+{
+ int ret;
+
+ ret = ad7944_convert_and_acquire(adc, chan);
+ if (ret)
+ return ret;
+
+ if (chan->scan_type.storagebits > 16)
+ *val = adc->sample.raw.u32;
+ else
+ *val = adc->sample.raw.u16;
+
+ if (chan->scan_type.sign == 's')
+ *val = sign_extend32(*val, chan->scan_type.realbits - 1);
+
+ return IIO_VAL_INT;
+}
+
+static int ad7944_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long info)
+{
+ struct ad7944_adc *adc = iio_priv(indio_dev);
+ int ret;
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = ad7944_single_conversion(adc, chan, val);
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ *val = adc->ref_mv;
+
+ if (chan->scan_type.sign == 's')
+ *val2 = chan->scan_type.realbits - 1;
+ else
+ *val2 = chan->scan_type.realbits;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info ad7944_iio_info = {
+ .read_raw = &ad7944_read_raw,
+};
+
+static irqreturn_t ad7944_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ad7944_adc *adc = iio_priv(indio_dev);
+ int ret;
+
+ ret = ad7944_convert_and_acquire(adc, &indio_dev->channels[0]);
+ if (ret)
+ goto out;
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &adc->sample.raw,
+ pf->timestamp);
+
+out:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static const char * const ad7944_power_supplies[] = {
+ "avdd", "dvdd", "bvdd", "vio"
+};
+
+static void ad7944_ref_disable(void *ref)
+{
+ regulator_disable(ref);
+}
+
+static int ad7944_probe(struct spi_device *spi)
+{
+ const struct ad7944_chip_info *chip_info;
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ad7944_adc *adc;
+ bool have_refin = false;
+ struct regulator *ref;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ adc = iio_priv(indio_dev);
+ adc->spi = spi;
+
+ chip_info = spi_get_device_match_data(spi);
+ if (!chip_info)
+ return dev_err_probe(dev, -EINVAL, "no chip info\n");
+
+ adc->timing_spec = chip_info->timing_spec;
+
+ ret = device_property_match_property_string(dev, "adi,spi-mode",
+ ad7944_spi_modes,
+ ARRAY_SIZE(ad7944_spi_modes));
+ /* absence of adi,spi-mode property means default mode */
+ if (ret == -EINVAL)
+ adc->spi_mode = AD7944_SPI_MODE_DEFAULT;
+ else if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "getting adi,spi-mode property failed\n");
+ else
+ adc->spi_mode = ret;
+
+ /*
+ * Some chips use unusual word sizes, so check now instead of waiting
+ * for the first xfer.
+ */
+ if (!spi_is_bpw_supported(spi, chip_info->channels[0].scan_type.realbits))
+ return dev_err_probe(dev, -EINVAL,
+ "SPI host does not support %d bits per word\n",
+ chip_info->channels[0].scan_type.realbits);
+
+ ret = devm_regulator_bulk_get_enable(dev,
+ ARRAY_SIZE(ad7944_power_supplies),
+ ad7944_power_supplies);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get and enable supplies\n");
+
+ /*
+ * Sort out what is being used for the reference voltage. Options are:
+ * - internal reference: neither REF or REFIN is connected
+ * - internal reference with external buffer: REF not connected, REFIN
+ * is connected
+ * - external reference: REF is connected, REFIN is not connected
+ */
+
+ ref = devm_regulator_get_optional(dev, "ref");
+ if (IS_ERR(ref)) {
+ if (PTR_ERR(ref) != -ENODEV)
+ return dev_err_probe(dev, PTR_ERR(ref),
+ "failed to get REF supply\n");
+
+ ref = NULL;
+ }
+
+ ret = devm_regulator_get_enable_optional(dev, "refin");
+ if (ret == 0)
+ have_refin = true;
+ else if (ret != -ENODEV)
+ return dev_err_probe(dev, ret,
+ "failed to get and enable REFIN supply\n");
+
+ if (have_refin && ref)
+ return dev_err_probe(dev, -EINVAL,
+ "cannot have both refin and ref supplies\n");
+
+ if (ref) {
+ ret = regulator_enable(ref);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to enable REF supply\n");
+
+ ret = devm_add_action_or_reset(dev, ad7944_ref_disable, ref);
+ if (ret)
+ return ret;
+
+ ret = regulator_get_voltage(ref);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "failed to get REF voltage\n");
+
+ /* external reference */
+ adc->ref_mv = ret / 1000;
+ } else {
+ /* internal reference */
+ adc->ref_mv = AD7944_INTERNAL_REF_MV;
+ }
+
+ adc->cnv = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW);
+ if (IS_ERR(adc->cnv))
+ return dev_err_probe(dev, PTR_ERR(adc->cnv),
+ "failed to get CNV GPIO\n");
+
+ if (!adc->cnv && adc->spi_mode == AD7944_SPI_MODE_DEFAULT)
+ return dev_err_probe(&spi->dev, -EINVAL, "CNV GPIO is required\n");
+ if (adc->cnv && adc->spi_mode != AD7944_SPI_MODE_DEFAULT)
+ return dev_err_probe(&spi->dev, -EINVAL,
+ "CNV GPIO in single and chain mode is not currently supported\n");
+
+ adc->turbo = devm_gpiod_get_optional(dev, "turbo", GPIOD_OUT_LOW);
+ if (IS_ERR(adc->turbo))
+ return dev_err_probe(dev, PTR_ERR(adc->turbo),
+ "failed to get TURBO GPIO\n");
+
+ adc->always_turbo = device_property_present(dev, "adi,always-turbo");
+
+ if (adc->turbo && adc->always_turbo)
+ return dev_err_probe(dev, -EINVAL,
+ "cannot have both turbo-gpios and adi,always-turbo\n");
+
+ if (adc->spi_mode == AD7944_SPI_MODE_CHAIN && adc->always_turbo)
+ return dev_err_probe(dev, -EINVAL,
+ "cannot have both chain mode and always turbo\n");
+
+ switch (adc->spi_mode) {
+ case AD7944_SPI_MODE_DEFAULT:
+ ret = ad7944_4wire_mode_init_msg(dev, adc, &chip_info->channels[0]);
+ if (ret)
+ return ret;
+
+ break;
+ case AD7944_SPI_MODE_SINGLE:
+ ret = ad7944_3wire_cs_mode_init_msg(dev, adc, &chip_info->channels[0]);
+ if (ret)
+ return ret;
+
+ break;
+ case AD7944_SPI_MODE_CHAIN:
+ return dev_err_probe(dev, -EINVAL, "chain mode is not implemented\n");
+ }
+
+ indio_dev->name = chip_info->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ad7944_iio_info;
+ indio_dev->channels = chip_info->channels;
+ indio_dev->num_channels = ARRAY_SIZE(chip_info->channels);
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ ad7944_trigger_handler, NULL);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ad7944_of_match[] = {
+ { .compatible = "adi,ad7944", .data = &ad7944_chip_info },
+ { .compatible = "adi,ad7985", .data = &ad7985_chip_info },
+ { .compatible = "adi,ad7986", .data = &ad7986_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad7944_of_match);
+
+static const struct spi_device_id ad7944_spi_id[] = {
+ { "ad7944", (kernel_ulong_t)&ad7944_chip_info },
+ { "ad7985", (kernel_ulong_t)&ad7985_chip_info },
+ { "ad7986", (kernel_ulong_t)&ad7986_chip_info },
+ { }
+
+};
+MODULE_DEVICE_TABLE(spi, ad7944_spi_id);
+
+static struct spi_driver ad7944_driver = {
+ .driver = {
+ .name = "ad7944",
+ .of_match_table = ad7944_of_match,
+ },
+ .probe = ad7944_probe,
+ .id_table = ad7944_spi_id,
+};
+module_spi_driver(ad7944_driver);
+
+MODULE_AUTHOR("David Lechner <dlechner@baylibre.com>");
+MODULE_DESCRIPTION("Analog Devices AD7944 PulSAR ADC family driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c
index a602429cdde4ef..97a05f325df7cf 100644
--- a/drivers/iio/adc/ad_sigma_delta.c
+++ b/drivers/iio/adc/ad_sigma_delta.c
@@ -222,11 +222,11 @@ int ad_sd_calibrate(struct ad_sigma_delta *sigma_delta,
goto out;
sigma_delta->irq_dis = false;
- enable_irq(sigma_delta->spi->irq);
+ enable_irq(sigma_delta->irq_line);
timeout = wait_for_completion_timeout(&sigma_delta->completion, 2 * HZ);
if (timeout == 0) {
sigma_delta->irq_dis = true;
- disable_irq_nosync(sigma_delta->spi->irq);
+ disable_irq_nosync(sigma_delta->irq_line);
ret = -EIO;
} else {
ret = 0;
@@ -295,7 +295,7 @@ int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev,
ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_SINGLE);
sigma_delta->irq_dis = false;
- enable_irq(sigma_delta->spi->irq);
+ enable_irq(sigma_delta->irq_line);
ret = wait_for_completion_interruptible_timeout(
&sigma_delta->completion, HZ);
@@ -315,7 +315,7 @@ int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev,
out:
if (!sigma_delta->irq_dis) {
- disable_irq_nosync(sigma_delta->spi->irq);
+ disable_irq_nosync(sigma_delta->irq_line);
sigma_delta->irq_dis = true;
}
@@ -396,7 +396,7 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev)
goto err_unlock;
sigma_delta->irq_dis = false;
- enable_irq(sigma_delta->spi->irq);
+ enable_irq(sigma_delta->irq_line);
return 0;
@@ -414,7 +414,7 @@ static int ad_sd_buffer_postdisable(struct iio_dev *indio_dev)
wait_for_completion_timeout(&sigma_delta->completion, HZ);
if (!sigma_delta->irq_dis) {
- disable_irq_nosync(sigma_delta->spi->irq);
+ disable_irq_nosync(sigma_delta->irq_line);
sigma_delta->irq_dis = true;
}
@@ -516,7 +516,7 @@ static irqreturn_t ad_sd_trigger_handler(int irq, void *p)
irq_handled:
iio_trigger_notify_done(indio_dev->trig);
sigma_delta->irq_dis = false;
- enable_irq(sigma_delta->spi->irq);
+ enable_irq(sigma_delta->irq_line);
return IRQ_HANDLED;
}
@@ -587,13 +587,13 @@ static int devm_ad_sd_probe_trigger(struct device *dev, struct iio_dev *indio_de
sigma_delta->irq_dis = true;
/* the IRQ core clears IRQ_DISABLE_UNLAZY flag when freeing an IRQ */
- irq_set_status_flags(sigma_delta->spi->irq, IRQ_DISABLE_UNLAZY);
+ irq_set_status_flags(sigma_delta->irq_line, IRQ_DISABLE_UNLAZY);
/* Allow overwriting the flags from firmware */
if (!irq_flags)
irq_flags = sigma_delta->info->irq_flags;
- ret = devm_request_irq(dev, sigma_delta->spi->irq,
+ ret = devm_request_irq(dev, sigma_delta->irq_line,
ad_sd_data_rdy_trig_poll,
irq_flags | IRQF_NO_AUTOEN,
indio_dev->name,
@@ -673,6 +673,11 @@ int ad_sd_init(struct ad_sigma_delta *sigma_delta, struct iio_dev *indio_dev,
}
}
+ if (info->irq_line)
+ sigma_delta->irq_line = info->irq_line;
+ else
+ sigma_delta->irq_line = spi->irq;
+
iio_device_set_drvdata(indio_dev, sigma_delta);
return 0;
diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c
index 4156639b3c8bdb..9444b0c5a93c6a 100644
--- a/drivers/iio/adc/adi-axi-adc.c
+++ b/drivers/iio/adc/adi-axi-adc.c
@@ -124,26 +124,12 @@ static struct iio_buffer *axi_adc_request_buffer(struct iio_backend *back,
struct iio_dev *indio_dev)
{
struct adi_axi_adc_state *st = iio_backend_get_priv(back);
- struct iio_buffer *buffer;
const char *dma_name;
- int ret;
if (device_property_read_string(st->dev, "dma-names", &dma_name))
dma_name = "rx";
- buffer = iio_dmaengine_buffer_alloc(st->dev, dma_name);
- if (IS_ERR(buffer)) {
- dev_err(st->dev, "Could not get DMA buffer, %ld\n",
- PTR_ERR(buffer));
- return ERR_CAST(buffer);
- }
-
- indio_dev->modes |= INDIO_BUFFER_HARDWARE;
- ret = iio_device_attach_buffer(indio_dev, buffer);
- if (ret)
- return ERR_PTR(ret);
-
- return buffer;
+ return iio_dmaengine_buffer_setup(st->dev, indio_dev, dma_name);
}
static void axi_adc_free_buffer(struct iio_backend *back,
@@ -207,9 +193,9 @@ static int adi_axi_adc_probe(struct platform_device *pdev)
if (ret)
return ret;
- if (*expected_ver > ver) {
+ if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(*expected_ver)) {
dev_err(&pdev->dev,
- "IP core version is too old. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
+ "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
ADI_AXI_PCORE_VER_MAJOR(*expected_ver),
ADI_AXI_PCORE_VER_MINOR(*expected_ver),
ADI_AXI_PCORE_VER_PATCH(*expected_ver),
diff --git a/drivers/iio/adc/fsl-imx25-gcq.c b/drivers/iio/adc/fsl-imx25-gcq.c
index 68c813de0605bc..394396b916309e 100644
--- a/drivers/iio/adc/fsl-imx25-gcq.c
+++ b/drivers/iio/adc/fsl-imx25-gcq.c
@@ -12,8 +12,9 @@
#include <linux/interrupt.h>
#include <linux/mfd/imx25-tsadc.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -198,8 +199,6 @@ static int mx25_gcq_ext_regulator_setup(struct device *dev,
static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
struct mx25_gcq_priv *priv)
{
- struct device_node *np = pdev->dev.of_node;
- struct device_node *child;
struct device *dev = &pdev->dev;
int ret, i;
@@ -216,37 +215,30 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
MX25_ADCQ_CFG_IN(i) |
MX25_ADCQ_CFG_REFN_NGND2);
- for_each_child_of_node(np, child) {
+ device_for_each_child_node_scoped(dev, child) {
u32 reg;
u32 refp = MX25_ADCQ_CFG_REFP_INT;
u32 refn = MX25_ADCQ_CFG_REFN_NGND2;
- ret = of_property_read_u32(child, "reg", &reg);
- if (ret) {
- dev_err(dev, "Failed to get reg property\n");
- of_node_put(child);
- return ret;
- }
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get reg property\n");
- if (reg >= MX25_NUM_CFGS) {
- dev_err(dev,
+ if (reg >= MX25_NUM_CFGS)
+ return dev_err_probe(dev, -EINVAL,
"reg value is greater than the number of available configuration registers\n");
- of_node_put(child);
- return -EINVAL;
- }
- of_property_read_u32(child, "fsl,adc-refp", &refp);
- of_property_read_u32(child, "fsl,adc-refn", &refn);
+ fwnode_property_read_u32(child, "fsl,adc-refp", &refp);
+ fwnode_property_read_u32(child, "fsl,adc-refn", &refn);
switch (refp) {
case MX25_ADC_REFP_EXT:
case MX25_ADC_REFP_XP:
case MX25_ADC_REFP_YP:
ret = mx25_gcq_ext_regulator_setup(&pdev->dev, priv, refp);
- if (ret) {
- of_node_put(child);
+ if (ret)
return ret;
- }
priv->channel_vref_mv[reg] =
regulator_get_voltage(priv->vref[refp]);
/* Conversion from uV to mV */
@@ -256,9 +248,8 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
priv->channel_vref_mv[reg] = 2500;
break;
default:
- dev_err(dev, "Invalid positive reference %d\n", refp);
- of_node_put(child);
- return -EINVAL;
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid positive reference %d\n", refp);
}
/*
@@ -268,16 +259,13 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
refp = MX25_ADCQ_CFG_REFP(refp);
refn = MX25_ADCQ_CFG_REFN(refn);
- if ((refp & MX25_ADCQ_CFG_REFP_MASK) != refp) {
- dev_err(dev, "Invalid fsl,adc-refp property value\n");
- of_node_put(child);
- return -EINVAL;
- }
- if ((refn & MX25_ADCQ_CFG_REFN_MASK) != refn) {
- dev_err(dev, "Invalid fsl,adc-refn property value\n");
- of_node_put(child);
- return -EINVAL;
- }
+ if ((refp & MX25_ADCQ_CFG_REFP_MASK) != refp)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid fsl,adc-refp property value\n");
+
+ if ((refn & MX25_ADCQ_CFG_REFN_MASK) != refn)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid fsl,adc-refn property value\n");
regmap_update_bits(priv->regs, MX25_ADCQ_CFG(reg),
MX25_ADCQ_CFG_REFP_MASK |
@@ -294,6 +282,17 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
return 0;
}
+static void mx25_gcq_reg_disable(void *reg)
+{
+ regulator_disable(reg);
+}
+
+/* Custom handling needed as this driver doesn't own the clock */
+static void mx25_gcq_clk_disable(void *clk)
+{
+ clk_disable_unprepare(clk);
+}
+
static int mx25_gcq_probe(struct platform_device *pdev)
{
struct iio_dev *indio_dev;
@@ -315,10 +314,9 @@ static int mx25_gcq_probe(struct platform_device *pdev)
return PTR_ERR(mem);
priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_gcq_regconfig);
- if (IS_ERR(priv->regs)) {
- dev_err(dev, "Failed to initialize regmap\n");
- return PTR_ERR(priv->regs);
- }
+ if (IS_ERR(priv->regs))
+ return dev_err_probe(dev, PTR_ERR(priv->regs),
+ "Failed to initialize regmap\n");
mutex_init(&priv->lock);
@@ -334,69 +332,44 @@ static int mx25_gcq_probe(struct platform_device *pdev)
ret = regulator_enable(priv->vref[i]);
if (ret)
- goto err_regulator_disable;
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, mx25_gcq_reg_disable,
+ priv->vref[i]);
+ if (ret)
+ return ret;
}
priv->clk = tsadc->clk;
ret = clk_prepare_enable(priv->clk);
- if (ret) {
- dev_err(dev, "Failed to enable clock\n");
- goto err_vref_disable;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable clock\n");
+
+ ret = devm_add_action_or_reset(dev, mx25_gcq_clk_disable,
+ priv->clk);
+ if (ret)
+ return ret;
ret = platform_get_irq(pdev, 0);
if (ret < 0)
- goto err_clk_unprepare;
+ return ret;
priv->irq = ret;
- ret = request_irq(priv->irq, mx25_gcq_irq, 0, pdev->name, priv);
- if (ret) {
- dev_err(dev, "Failed requesting IRQ\n");
- goto err_clk_unprepare;
- }
+ ret = devm_request_irq(dev, priv->irq, mx25_gcq_irq, 0, pdev->name,
+ priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed requesting IRQ\n");
indio_dev->channels = mx25_gcq_channels;
indio_dev->num_channels = ARRAY_SIZE(mx25_gcq_channels);
indio_dev->info = &mx25_gcq_iio_info;
indio_dev->name = driver_name;
- ret = iio_device_register(indio_dev);
- if (ret) {
- dev_err(dev, "Failed to register iio device\n");
- goto err_irq_free;
- }
-
- platform_set_drvdata(pdev, indio_dev);
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register iio device\n");
return 0;
-
-err_irq_free:
- free_irq(priv->irq, priv);
-err_clk_unprepare:
- clk_disable_unprepare(priv->clk);
-err_vref_disable:
- i = 4;
-err_regulator_disable:
- for (; i-- > 0;) {
- if (priv->vref[i])
- regulator_disable(priv->vref[i]);
- }
- return ret;
-}
-
-static void mx25_gcq_remove(struct platform_device *pdev)
-{
- struct iio_dev *indio_dev = platform_get_drvdata(pdev);
- struct mx25_gcq_priv *priv = iio_priv(indio_dev);
- int i;
-
- iio_device_unregister(indio_dev);
- free_irq(priv->irq, priv);
- clk_disable_unprepare(priv->clk);
- for (i = 4; i-- > 0;) {
- if (priv->vref[i])
- regulator_disable(priv->vref[i]);
- }
}
static const struct of_device_id mx25_gcq_ids[] = {
@@ -411,7 +384,6 @@ static struct platform_driver mx25_gcq_driver = {
.of_match_table = mx25_gcq_ids,
},
.probe = mx25_gcq_probe,
- .remove_new = mx25_gcq_remove,
};
module_platform_driver(mx25_gcq_driver);
diff --git a/drivers/iio/adc/hx711.c b/drivers/iio/adc/hx711.c
index c80c55fb8c6cea..fef97c1d226a55 100644
--- a/drivers/iio/adc/hx711.c
+++ b/drivers/iio/adc/hx711.c
@@ -7,7 +7,7 @@
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
@@ -459,7 +459,6 @@ static const struct iio_chan_spec hx711_chan_spec[] = {
static int hx711_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct device_node *np = dev->of_node;
struct hx711_data *hx711_data;
struct iio_dev *indio_dev;
int ret;
@@ -533,7 +532,7 @@ static int hx711_probe(struct platform_device *pdev)
hx711_data->gain_chan_a = 128;
hx711_data->clock_frequency = 400000;
- ret = of_property_read_u32(np, "clock-frequency",
+ ret = device_property_read_u32(&pdev->dev, "clock-frequency",
&hx711_data->clock_frequency);
/*
diff --git a/drivers/iio/adc/max11410.c b/drivers/iio/adc/max11410.c
index 6af829349b4e1f..45368850b2201f 100644
--- a/drivers/iio/adc/max11410.c
+++ b/drivers/iio/adc/max11410.c
@@ -696,7 +696,6 @@ static int max11410_parse_channels(struct max11410_state *st,
struct device *dev = &st->spi_dev->dev;
struct max11410_channel_config *cfg;
struct iio_chan_spec *channels;
- struct fwnode_handle *child;
u32 reference, sig_path;
const char *node_name;
u32 inputs[2], scale;
@@ -720,7 +719,7 @@ static int max11410_parse_channels(struct max11410_state *st,
if (!st->channels)
return -ENOMEM;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
node_name = fwnode_get_name(child);
if (fwnode_property_present(child, "diff-channels")) {
ret = fwnode_property_read_u32_array(child,
@@ -735,47 +734,37 @@ static int max11410_parse_channels(struct max11410_state *st,
inputs[1] = 0;
chanspec.differential = 0;
}
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return ret;
- }
if (inputs[0] > MAX11410_CHANNEL_INDEX_MAX ||
- inputs[1] > MAX11410_CHANNEL_INDEX_MAX) {
- fwnode_handle_put(child);
+ inputs[1] > MAX11410_CHANNEL_INDEX_MAX)
return dev_err_probe(&indio_dev->dev, -EINVAL,
"Invalid channel index for %s, should be less than %d\n",
node_name,
MAX11410_CHANNEL_INDEX_MAX + 1);
- }
cfg = &st->channels[chan_idx];
reference = MAX11410_REFSEL_AVDD_AGND;
fwnode_property_read_u32(child, "adi,reference", &reference);
- if (reference > MAX11410_REFSEL_MAX) {
- fwnode_handle_put(child);
+ if (reference > MAX11410_REFSEL_MAX)
return dev_err_probe(&indio_dev->dev, -EINVAL,
"Invalid adi,reference value for %s, should be less than %d.\n",
node_name, MAX11410_REFSEL_MAX + 1);
- }
if (!max11410_get_vrefp(st, reference) ||
- (!max11410_get_vrefn(st, reference) && reference <= 2)) {
- fwnode_handle_put(child);
+ (!max11410_get_vrefn(st, reference) && reference <= 2))
return dev_err_probe(&indio_dev->dev, -EINVAL,
"Invalid VREF configuration for %s, either specify corresponding VREF regulators or change adi,reference property.\n",
node_name);
- }
sig_path = MAX11410_PGA_SIG_PATH_BUFFERED;
fwnode_property_read_u32(child, "adi,input-mode", &sig_path);
- if (sig_path > MAX11410_SIG_PATH_MAX) {
- fwnode_handle_put(child);
+ if (sig_path > MAX11410_SIG_PATH_MAX)
return dev_err_probe(&indio_dev->dev, -EINVAL,
"Invalid adi,input-mode value for %s, should be less than %d.\n",
node_name, MAX11410_SIG_PATH_MAX + 1);
- }
fwnode_property_read_u32(child, "settling-time-us",
&cfg->settling_time_us);
@@ -793,10 +782,8 @@ static int max11410_parse_channels(struct max11410_state *st,
cfg->scale_avail = devm_kcalloc(dev, MAX11410_SCALE_AVAIL_SIZE * 2,
sizeof(*cfg->scale_avail),
GFP_KERNEL);
- if (!cfg->scale_avail) {
- fwnode_handle_put(child);
+ if (!cfg->scale_avail)
return -ENOMEM;
- }
scale = max11410_get_scale(st, *cfg);
for (i = 0; i < MAX11410_SCALE_AVAIL_SIZE; i++) {
diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c
index e0c2742da5236f..f751260605e4e1 100644
--- a/drivers/iio/adc/pac1934.c
+++ b/drivers/iio/adc/pac1934.c
@@ -1079,8 +1079,8 @@ static int pac1934_chip_identify(struct pac1934_chip_info *info)
* documentation related to the ACPI device definition
* https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/PAC1934-Integration-Notes-for-Microsoft-Windows-10-and-Windows-11-Driver-Support-DS00002534.pdf
*/
-static bool pac1934_acpi_parse_channel_config(struct i2c_client *client,
- struct pac1934_chip_info *info)
+static int pac1934_acpi_parse_channel_config(struct i2c_client *client,
+ struct pac1934_chip_info *info)
{
acpi_handle handle;
union acpi_object *rez;
@@ -1095,7 +1095,7 @@ static bool pac1934_acpi_parse_channel_config(struct i2c_client *client,
rez = acpi_evaluate_dsm(handle, &guid, 0, PAC1934_ACPI_GET_NAMES_AND_MOHMS_VALS, NULL);
if (!rez)
- return false;
+ return -EINVAL;
for (i = 0; i < rez->package.count; i += 2) {
idx = i / 2;
@@ -1118,7 +1118,7 @@ static bool pac1934_acpi_parse_channel_config(struct i2c_client *client,
* and assign the default sampling rate
*/
info->sample_rate_value = PAC1934_DEFAULT_CHIP_SAMP_SPEED_HZ;
- return true;
+ return 0;
}
for (i = 0; i < rez->package.count; i++) {
@@ -1131,7 +1131,7 @@ static bool pac1934_acpi_parse_channel_config(struct i2c_client *client,
rez = acpi_evaluate_dsm(handle, &guid, 1, PAC1934_ACPI_GET_BIPOLAR_SETTINGS, NULL);
if (!rez)
- return false;
+ return -EINVAL;
bi_dir_mask = rez->package.elements[0].integer.value;
info->bi_dir[0] = ((bi_dir_mask & (1 << 3)) | (bi_dir_mask & (1 << 7))) != 0;
@@ -1143,19 +1143,18 @@ static bool pac1934_acpi_parse_channel_config(struct i2c_client *client,
rez = acpi_evaluate_dsm(handle, &guid, 1, PAC1934_ACPI_GET_SAMP, NULL);
if (!rez)
- return false;
+ return -EINVAL;
info->sample_rate_value = rez->package.elements[0].integer.value;
ACPI_FREE(rez);
- return true;
+ return 0;
}
-static bool pac1934_of_parse_channel_config(struct i2c_client *client,
- struct pac1934_chip_info *info)
+static int pac1934_fw_parse_channel_config(struct i2c_client *client,
+ struct pac1934_chip_info *info)
{
- struct fwnode_handle *node, *fwnode;
struct device *dev = &client->dev;
unsigned int current_channel;
int idx, ret;
@@ -1163,46 +1162,38 @@ static bool pac1934_of_parse_channel_config(struct i2c_client *client,
info->sample_rate_value = 1024;
current_channel = 1;
- fwnode = dev_fwnode(dev);
- fwnode_for_each_available_child_node(fwnode, node) {
+ device_for_each_child_node_scoped(dev, node) {
ret = fwnode_property_read_u32(node, "reg", &idx);
- if (ret) {
- dev_err_probe(dev, ret,
- "reading invalid channel index\n");
- goto err_fwnode;
- }
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "reading invalid channel index\n");
+
/* adjust idx to match channel index (1 to 4) from the datasheet */
idx--;
if (current_channel >= (info->phys_channels + 1) ||
- idx >= info->phys_channels || idx < 0) {
- dev_err_probe(dev, -EINVAL,
- "%s: invalid channel_index %d value\n",
- fwnode_get_name(node), idx);
- goto err_fwnode;
- }
+ idx >= info->phys_channels || idx < 0)
+ return dev_err_probe(dev, -EINVAL,
+ "%s: invalid channel_index %d value\n",
+ fwnode_get_name(node), idx);
/* enable channel */
info->active_channels[idx] = true;
ret = fwnode_property_read_u32(node, "shunt-resistor-micro-ohms",
&info->shunts[idx]);
- if (ret) {
- dev_err_probe(dev, ret,
- "%s: invalid shunt-resistor value: %d\n",
- fwnode_get_name(node), info->shunts[idx]);
- goto err_fwnode;
- }
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "%s: invalid shunt-resistor value: %d\n",
+ fwnode_get_name(node), info->shunts[idx]);
if (fwnode_property_present(node, "label")) {
ret = fwnode_property_read_string(node, "label",
(const char **)&info->labels[idx]);
- if (ret) {
- dev_err_probe(dev, ret,
- "%s: invalid rail-name value\n",
- fwnode_get_name(node));
- goto err_fwnode;
- }
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "%s: invalid rail-name value\n",
+ fwnode_get_name(node));
}
info->bi_dir[idx] = fwnode_property_read_bool(node, "bipolar");
@@ -1210,12 +1201,7 @@ static bool pac1934_of_parse_channel_config(struct i2c_client *client,
current_channel++;
}
- return true;
-
-err_fwnode:
- fwnode_handle_put(node);
-
- return false;
+ return 0;
}
static void pac1934_cancel_delayed_work(void *dwork)
@@ -1485,7 +1471,6 @@ static int pac1934_probe(struct i2c_client *client)
const struct pac1934_features *chip;
struct iio_dev *indio_dev;
int cnt, ret;
- bool match = false;
struct device *dev = &client->dev;
indio_dev = devm_iio_device_alloc(dev, sizeof(*info));
@@ -1519,16 +1504,16 @@ static int pac1934_probe(struct i2c_client *client)
}
if (acpi_match_device(dev->driver->acpi_match_table, dev))
- match = pac1934_acpi_parse_channel_config(client, info);
+ ret = pac1934_acpi_parse_channel_config(client, info);
else
/*
* This makes it possible to use also ACPI PRP0001 for
* registering the device using device tree properties.
*/
- match = pac1934_of_parse_channel_config(client, info);
+ ret = pac1934_fw_parse_channel_config(client, info);
- if (!match)
- return dev_err_probe(dev, -EINVAL,
+ if (ret)
+ return dev_err_probe(dev, ret,
"parameter parsing returned an error\n");
mutex_init(&info->lock);
diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
index b6b612d733ff23..9b69f40beed8ea 100644
--- a/drivers/iio/adc/qcom-spmi-adc5.c
+++ b/drivers/iio/adc/qcom-spmi-adc5.c
@@ -825,7 +825,6 @@ static int adc5_get_fw_data(struct adc5_chip *adc)
const struct adc5_channels *adc_chan;
struct iio_chan_spec *iio_chan;
struct adc5_channel_prop prop, *chan_props;
- struct fwnode_handle *child;
unsigned int index = 0;
int ret;
@@ -849,12 +848,10 @@ static int adc5_get_fw_data(struct adc5_chip *adc)
if (!adc->data)
adc->data = &adc5_data_pmic;
- device_for_each_child_node(adc->dev, child) {
+ device_for_each_child_node_scoped(adc->dev, child) {
ret = adc5_get_fw_channel_data(adc, &prop, child, adc->data);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return ret;
- }
prop.scale_fn_type =
adc->data->adc_chans[prop.channel].scale_fn_type;
diff --git a/drivers/iio/adc/rcar-gyroadc.c b/drivers/iio/adc/rcar-gyroadc.c
index d524f2e8e92775..15a21d2860e758 100644
--- a/drivers/iio/adc/rcar-gyroadc.c
+++ b/drivers/iio/adc/rcar-gyroadc.c
@@ -318,7 +318,6 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev)
struct rcar_gyroadc *priv = iio_priv(indio_dev);
struct device *dev = priv->dev;
struct device_node *np = dev->of_node;
- struct device_node *child;
struct regulator *vref;
unsigned int reg;
unsigned int adcmode = -1, childmode;
@@ -326,7 +325,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev)
unsigned int num_channels;
int ret, first = 1;
- for_each_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
of_id = of_match_node(rcar_gyroadc_child_match, child);
if (!of_id) {
dev_err(dev, "Ignoring unsupported ADC \"%pOFn\".",
@@ -352,7 +351,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev)
num_channels = ARRAY_SIZE(rcar_gyroadc_iio_channels_3);
break;
default:
- goto err_e_inval;
+ return -EINVAL;
}
/*
@@ -369,7 +368,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev)
dev_err(dev,
"Failed to get child reg property of ADC \"%pOFn\".\n",
child);
- goto err_of_node_put;
+ return ret;
}
/* Channel number is too high. */
@@ -377,7 +376,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev)
dev_err(dev,
"Only %i channels supported with %pOFn, but reg = <%i>.\n",
num_channels, child, reg);
- goto err_e_inval;
+ return -EINVAL;
}
}
@@ -386,7 +385,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev)
dev_err(dev,
"Channel %i uses different ADC mode than the rest.\n",
reg);
- goto err_e_inval;
+ return -EINVAL;
}
/* Channel is valid, grab the regulator. */
@@ -396,8 +395,7 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev)
if (IS_ERR(vref)) {
dev_dbg(dev, "Channel %i 'vref' supply not connected.\n",
reg);
- ret = PTR_ERR(vref);
- goto err_of_node_put;
+ return PTR_ERR(vref);
}
priv->vref[reg] = vref;
@@ -422,7 +420,6 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev)
* we can stop parsing here.
*/
if (childmode == RCAR_GYROADC_MODE_SELECT_1_MB88101A) {
- of_node_put(child);
break;
}
}
@@ -433,12 +430,6 @@ static int rcar_gyroadc_parse_subdevs(struct iio_dev *indio_dev)
}
return 0;
-
-err_e_inval:
- ret = -EINVAL;
-err_of_node_put:
- of_node_put(child);
- return ret;
}
static void rcar_gyroadc_deinit_supplies(struct iio_dev *indio_dev)
diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c
index 0921ff2d9b3a10..cd3a7e46ea53c0 100644
--- a/drivers/iio/adc/rzg2l_adc.c
+++ b/drivers/iio/adc/rzg2l_adc.c
@@ -302,7 +302,6 @@ static irqreturn_t rzg2l_adc_isr(int irq, void *dev_id)
static int rzg2l_adc_parse_properties(struct platform_device *pdev, struct rzg2l_adc *adc)
{
struct iio_chan_spec *chan_array;
- struct fwnode_handle *fwnode;
struct rzg2l_adc_data *data;
unsigned int channel;
int num_channels;
@@ -330,17 +329,13 @@ static int rzg2l_adc_parse_properties(struct platform_device *pdev, struct rzg2l
return -ENOMEM;
i = 0;
- device_for_each_child_node(&pdev->dev, fwnode) {
+ device_for_each_child_node_scoped(&pdev->dev, fwnode) {
ret = fwnode_property_read_u32(fwnode, "reg", &channel);
- if (ret) {
- fwnode_handle_put(fwnode);
+ if (ret)
return ret;
- }
- if (channel >= RZG2L_ADC_MAX_CHANNELS) {
- fwnode_handle_put(fwnode);
+ if (channel >= RZG2L_ADC_MAX_CHANNELS)
return -EINVAL;
- }
chan_array[i].type = IIO_VOLTAGE;
chan_array[i].indexed = 1;
diff --git a/drivers/iio/adc/spear_adc.c b/drivers/iio/adc/spear_adc.c
index 71362c2ddf897d..b6dd096391c11a 100644
--- a/drivers/iio/adc/spear_adc.c
+++ b/drivers/iio/adc/spear_adc.c
@@ -5,8 +5,10 @@
* Copyright 2012 Stefan Roese <sr@denx.de>
*/
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/kernel.h>
@@ -15,8 +17,6 @@
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/completion.h>
-#include <linux/of.h>
-#include <linux/of_address.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
@@ -70,7 +70,7 @@ struct adc_regs_spear6xx {
};
struct spear_adc_state {
- struct device_node *np;
+ struct device *dev;
struct adc_regs_spear3xx __iomem *adc_base_spear3xx;
struct adc_regs_spear6xx __iomem *adc_base_spear6xx;
struct clk *clk;
@@ -123,7 +123,7 @@ static void spear_adc_set_ctrl(struct spear_adc_state *st, int n,
static u32 spear_adc_get_average(struct spear_adc_state *st)
{
- if (of_device_is_compatible(st->np, "st,spear600-adc")) {
+ if (device_is_compatible(st->dev, "st,spear600-adc")) {
return __raw_readl(&st->adc_base_spear6xx->average.msb) &
SPEAR_ADC_DATA_MASK;
} else {
@@ -134,7 +134,7 @@ static u32 spear_adc_get_average(struct spear_adc_state *st)
static void spear_adc_set_scanrate(struct spear_adc_state *st, u32 rate)
{
- if (of_device_is_compatible(st->np, "st,spear600-adc")) {
+ if (device_is_compatible(st->dev, "st,spear600-adc")) {
__raw_writel(SPEAR600_ADC_SCAN_RATE_LO(rate),
&st->adc_base_spear6xx->scan_rate_lo);
__raw_writel(SPEAR600_ADC_SCAN_RATE_HI(rate),
@@ -266,7 +266,6 @@ static const struct iio_info spear_adc_info = {
static int spear_adc_probe(struct platform_device *pdev)
{
- struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct spear_adc_state *st;
struct iio_dev *indio_dev = NULL;
@@ -279,11 +278,10 @@ static int spear_adc_probe(struct platform_device *pdev)
"failed allocating iio device\n");
st = iio_priv(indio_dev);
+ st->dev = dev;
mutex_init(&st->lock);
- st->np = np;
-
/*
* SPEAr600 has a different register layout than other SPEAr SoC's
* (e.g. SPEAr3xx). Let's provide two register base addresses
@@ -310,8 +308,7 @@ static int spear_adc_probe(struct platform_device *pdev)
if (ret < 0)
return dev_err_probe(dev, ret, "failed requesting interrupt\n");
- if (of_property_read_u32(np, "sampling-frequency",
- &st->sampling_freq))
+ if (device_property_read_u32(dev, "sampling-frequency", &st->sampling_freq))
return dev_err_probe(dev, -EINVAL,
"sampling-frequency missing in DT\n");
@@ -319,13 +316,13 @@ static int spear_adc_probe(struct platform_device *pdev)
* Optional avg_samples defaults to 0, resulting in single data
* conversion
*/
- of_property_read_u32(np, "average-samples", &st->avg_samples);
+ device_property_read_u32(dev, "average-samples", &st->avg_samples);
/*
* Optional vref_external defaults to 0, resulting in internal vref
* selection
*/
- of_property_read_u32(np, "vref-external", &st->vref_external);
+ device_property_read_u32(dev, "vref-external", &st->vref_external);
spear_adc_configure(st);
@@ -346,19 +343,17 @@ static int spear_adc_probe(struct platform_device *pdev)
return 0;
}
-#ifdef CONFIG_OF
static const struct of_device_id spear_adc_dt_ids[] = {
{ .compatible = "st,spear600-adc", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, spear_adc_dt_ids);
-#endif
static struct platform_driver spear_adc_driver = {
.probe = spear_adc_probe,
.driver = {
.name = SPEAR_ADC_MOD_NAME,
- .of_match_table = of_match_ptr(spear_adc_dt_ids),
+ .of_match_table = spear_adc_dt_ids,
},
};
diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
index b5d3c9cea5c4e3..36add95212c370 100644
--- a/drivers/iio/adc/stm32-adc.c
+++ b/drivers/iio/adc/stm32-adc.c
@@ -2187,58 +2187,52 @@ static int stm32_adc_generic_chan_init(struct iio_dev *indio_dev,
struct iio_chan_spec *channels)
{
const struct stm32_adc_info *adc_info = adc->cfg->adc_info;
- struct fwnode_handle *child;
+ struct device *dev = &indio_dev->dev;
const char *name;
int val, scan_index = 0, ret;
bool differential;
u32 vin[2];
- device_for_each_child_node(&indio_dev->dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
ret = fwnode_property_read_u32(child, "reg", &val);
- if (ret) {
- dev_err(&indio_dev->dev, "Missing channel index %d\n", ret);
- goto err;
- }
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Missing channel index\n");
ret = fwnode_property_read_string(child, "label", &name);
/* label is optional */
if (!ret) {
- if (strlen(name) >= STM32_ADC_CH_SZ) {
- dev_err(&indio_dev->dev, "Label %s exceeds %d characters\n",
- name, STM32_ADC_CH_SZ);
- ret = -EINVAL;
- goto err;
- }
+ if (strlen(name) >= STM32_ADC_CH_SZ)
+ return dev_err_probe(dev, -EINVAL,
+ "Label %s exceeds %d characters\n",
+ name, STM32_ADC_CH_SZ);
+
strscpy(adc->chan_name[val], name, STM32_ADC_CH_SZ);
ret = stm32_adc_populate_int_ch(indio_dev, name, val);
if (ret == -ENOENT)
continue;
else if (ret)
- goto err;
+ return ret;
} else if (ret != -EINVAL) {
- dev_err(&indio_dev->dev, "Invalid label %d\n", ret);
- goto err;
+ return dev_err_probe(dev, ret, "Invalid label\n");
}
- if (val >= adc_info->max_channels) {
- dev_err(&indio_dev->dev, "Invalid channel %d\n", val);
- ret = -EINVAL;
- goto err;
- }
+ if (val >= adc_info->max_channels)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid channel %d\n", val);
differential = false;
ret = fwnode_property_read_u32_array(child, "diff-channels", vin, 2);
/* diff-channels is optional */
if (!ret) {
differential = true;
- if (vin[0] != val || vin[1] >= adc_info->max_channels) {
- dev_err(&indio_dev->dev, "Invalid channel in%d-in%d\n",
- vin[0], vin[1]);
- goto err;
- }
+ if (vin[0] != val || vin[1] >= adc_info->max_channels)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid channel in%d-in%d\n",
+ vin[0], vin[1]);
} else if (ret != -EINVAL) {
- dev_err(&indio_dev->dev, "Invalid diff-channels property %d\n", ret);
- goto err;
+ return dev_err_probe(dev, ret,
+ "Invalid diff-channels property\n");
}
stm32_adc_chan_init_one(indio_dev, &channels[scan_index], val,
@@ -2247,11 +2241,9 @@ static int stm32_adc_generic_chan_init(struct iio_dev *indio_dev,
val = 0;
ret = fwnode_property_read_u32(child, "st,min-sample-time-ns", &val);
/* st,min-sample-time-ns is optional */
- if (ret && ret != -EINVAL) {
- dev_err(&indio_dev->dev, "Invalid st,min-sample-time-ns property %d\n",
- ret);
- goto err;
- }
+ if (ret && ret != -EINVAL)
+ return dev_err_probe(dev, ret,
+ "Invalid st,min-sample-time-ns property\n");
stm32_adc_smpr_init(adc, channels[scan_index].channel, val);
if (differential)
@@ -2261,11 +2253,6 @@ static int stm32_adc_generic_chan_init(struct iio_dev *indio_dev,
}
return scan_index;
-
-err:
- fwnode_handle_put(child);
-
- return ret;
}
static int stm32_adc_chan_fw_init(struct iio_dev *indio_dev, bool timestamping)
diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c
index fcfc4625431332..cb04a29b3dbab9 100644
--- a/drivers/iio/adc/ti-ads131e08.c
+++ b/drivers/iio/adc/ti-ads131e08.c
@@ -694,7 +694,6 @@ static int ads131e08_alloc_channels(struct iio_dev *indio_dev)
struct ads131e08_channel_config *channel_config;
struct device *dev = &st->spi->dev;
struct iio_chan_spec *channels;
- struct fwnode_handle *node;
unsigned int channel, tmp;
int num_channels, i, ret;
@@ -736,10 +735,10 @@ static int ads131e08_alloc_channels(struct iio_dev *indio_dev)
return -ENOMEM;
i = 0;
- device_for_each_child_node(dev, node) {
+ device_for_each_child_node_scoped(dev, node) {
ret = fwnode_property_read_u32(node, "reg", &channel);
if (ret)
- goto err_child_out;
+ return ret;
ret = fwnode_property_read_u32(node, "ti,gain", &tmp);
if (ret) {
@@ -747,7 +746,7 @@ static int ads131e08_alloc_channels(struct iio_dev *indio_dev)
} else {
ret = ads131e08_pga_gain_to_field_value(st, tmp);
if (ret < 0)
- goto err_child_out;
+ return ret;
channel_config[i].pga_gain = tmp;
}
@@ -758,7 +757,7 @@ static int ads131e08_alloc_channels(struct iio_dev *indio_dev)
} else {
ret = ads131e08_validate_channel_mux(st, tmp);
if (ret)
- goto err_child_out;
+ return ret;
channel_config[i].mux = tmp;
}
@@ -785,9 +784,6 @@ static int ads131e08_alloc_channels(struct iio_dev *indio_dev)
return 0;
-err_child_out:
- fwnode_handle_put(node);
- return ret;
}
static void ads131e08_regulator_disable(void *data)
diff --git a/drivers/iio/adc/twl4030-madc.c b/drivers/iio/adc/twl4030-madc.c
index 4a247ca25a4497..0253064fadec84 100644
--- a/drivers/iio/adc/twl4030-madc.c
+++ b/drivers/iio/adc/twl4030-madc.c
@@ -19,10 +19,12 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/delay.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include <linux/slab.h>
#include <linux/mfd/twl.h>
-#include <linux/module.h>
#include <linux/stddef.h>
#include <linux/mutex.h>
#include <linux/bitops.h>
@@ -30,7 +32,6 @@
#include <linux/types.h>
#include <linux/gfp.h>
#include <linux/err.h>
-#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <linux/iio/iio.h>
@@ -744,14 +745,14 @@ static int twl4030_madc_set_power(struct twl4030_madc_data *madc, int on)
*/
static int twl4030_madc_probe(struct platform_device *pdev)
{
+ struct device *dev = &pdev->dev;
+ struct twl4030_madc_platform_data *pdata = dev_get_platdata(dev);
struct twl4030_madc_data *madc;
- struct twl4030_madc_platform_data *pdata = dev_get_platdata(&pdev->dev);
- struct device_node *np = pdev->dev.of_node;
int irq, ret;
u8 regval;
struct iio_dev *iio_dev = NULL;
- if (!pdata && !np) {
+ if (!pdata && !dev_fwnode(dev)) {
dev_err(&pdev->dev, "neither platform data nor Device Tree node available\n");
return -EINVAL;
}
@@ -779,7 +780,7 @@ static int twl4030_madc_probe(struct platform_device *pdev)
if (pdata)
madc->use_second_irq = (pdata->irq_line != 1);
else
- madc->use_second_irq = of_property_read_bool(np,
+ madc->use_second_irq = device_property_read_bool(dev,
"ti,system-uses-second-madc-irq");
madc->imr = madc->use_second_irq ? TWL4030_MADC_IMR2 :
@@ -905,20 +906,18 @@ static void twl4030_madc_remove(struct platform_device *pdev)
regulator_disable(madc->usb3v1);
}
-#ifdef CONFIG_OF
static const struct of_device_id twl_madc_of_match[] = {
{ .compatible = "ti,twl4030-madc", },
- { },
+ { }
};
MODULE_DEVICE_TABLE(of, twl_madc_of_match);
-#endif
static struct platform_driver twl4030_madc_driver = {
.probe = twl4030_madc_probe,
.remove_new = twl4030_madc_remove,
.driver = {
.name = "twl4030_madc",
- .of_match_table = of_match_ptr(twl_madc_of_match),
+ .of_match_table = twl_madc_of_match,
},
};
diff --git a/drivers/iio/addac/ad74413r.c b/drivers/iio/addac/ad74413r.c
index 7af3e4b8fe3bb9..cd26a16dc0ff80 100644
--- a/drivers/iio/addac/ad74413r.c
+++ b/drivers/iio/addac/ad74413r.c
@@ -1255,21 +1255,15 @@ static int ad74413r_parse_channel_config(struct iio_dev *indio_dev,
static int ad74413r_parse_channel_configs(struct iio_dev *indio_dev)
{
struct ad74413r_state *st = iio_priv(indio_dev);
- struct fwnode_handle *channel_node = NULL;
int ret;
- fwnode_for_each_available_child_node(dev_fwnode(st->dev), channel_node) {
+ device_for_each_child_node_scoped(st->dev, channel_node) {
ret = ad74413r_parse_channel_config(indio_dev, channel_node);
if (ret)
- goto put_channel_node;
+ return ret;
}
return 0;
-
-put_channel_node:
- fwnode_handle_put(channel_node);
-
- return ret;
}
static int ad74413r_setup_channels(struct iio_dev *indio_dev)
diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c
index 5610ba67925efc..13b1a858969e9e 100644
--- a/drivers/iio/buffer/industrialio-buffer-dma.c
+++ b/drivers/iio/buffer/industrialio-buffer-dma.c
@@ -195,6 +195,18 @@ static void _iio_dma_buffer_block_done(struct iio_dma_buffer_block *block)
block->state = IIO_BLOCK_STATE_DONE;
}
+static void iio_dma_buffer_queue_wake(struct iio_dma_buffer_queue *queue)
+{
+ __poll_t flags;
+
+ if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN)
+ flags = EPOLLIN | EPOLLRDNORM;
+ else
+ flags = EPOLLOUT | EPOLLWRNORM;
+
+ wake_up_interruptible_poll(&queue->buffer.pollq, flags);
+}
+
/**
* iio_dma_buffer_block_done() - Indicate that a block has been completed
* @block: The completed block
@@ -212,7 +224,7 @@ void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block)
spin_unlock_irqrestore(&queue->list_lock, flags);
iio_buffer_block_put_atomic(block);
- wake_up_interruptible_poll(&queue->buffer.pollq, EPOLLIN | EPOLLRDNORM);
+ iio_dma_buffer_queue_wake(queue);
}
EXPORT_SYMBOL_GPL(iio_dma_buffer_block_done);
@@ -241,7 +253,7 @@ void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue,
}
spin_unlock_irqrestore(&queue->list_lock, flags);
- wake_up_interruptible_poll(&queue->buffer.pollq, EPOLLIN | EPOLLRDNORM);
+ iio_dma_buffer_queue_wake(queue);
}
EXPORT_SYMBOL_GPL(iio_dma_buffer_block_list_abort);
@@ -335,8 +347,24 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer)
queue->fileio.blocks[i] = block;
}
- block->state = IIO_BLOCK_STATE_QUEUED;
- list_add_tail(&block->head, &queue->incoming);
+ /*
+ * block->bytes_used may have been modified previously, e.g. by
+ * iio_dma_buffer_block_list_abort(). Reset it here to the
+ * block's so that iio_dma_buffer_io() will work.
+ */
+ block->bytes_used = block->size;
+
+ /*
+ * If it's an input buffer, mark the block as queued, and
+ * iio_dma_buffer_enable() will submit it. Otherwise mark it as
+ * done, which means it's ready to be dequeued.
+ */
+ if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) {
+ block->state = IIO_BLOCK_STATE_QUEUED;
+ list_add_tail(&block->head, &queue->incoming);
+ } else {
+ block->state = IIO_BLOCK_STATE_DONE;
+ }
}
out_unlock:
@@ -488,20 +516,12 @@ static struct iio_dma_buffer_block *iio_dma_buffer_dequeue(
return block;
}
-/**
- * iio_dma_buffer_read() - DMA buffer read callback
- * @buffer: Buffer to read form
- * @n: Number of bytes to read
- * @user_buffer: Userspace buffer to copy the data to
- *
- * Should be used as the read callback for iio_buffer_access_ops
- * struct for DMA buffers.
- */
-int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n,
- char __user *user_buffer)
+static int iio_dma_buffer_io(struct iio_buffer *buffer, size_t n,
+ char __user *user_buffer, bool is_from_user)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
struct iio_dma_buffer_block *block;
+ void *addr;
int ret;
if (n < buffer->bytes_per_datum)
@@ -524,8 +544,13 @@ int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n,
n = rounddown(n, buffer->bytes_per_datum);
if (n > block->bytes_used - queue->fileio.pos)
n = block->bytes_used - queue->fileio.pos;
+ addr = block->vaddr + queue->fileio.pos;
- if (copy_to_user(user_buffer, block->vaddr + queue->fileio.pos, n)) {
+ if (is_from_user)
+ ret = copy_from_user(addr, user_buffer, n);
+ else
+ ret = copy_to_user(user_buffer, addr, n);
+ if (ret) {
ret = -EFAULT;
goto out_unlock;
}
@@ -544,16 +569,49 @@ out_unlock:
return ret;
}
+
+/**
+ * iio_dma_buffer_read() - DMA buffer read callback
+ * @buffer: Buffer to read form
+ * @n: Number of bytes to read
+ * @user_buffer: Userspace buffer to copy the data to
+ *
+ * Should be used as the read callback for iio_buffer_access_ops
+ * struct for DMA buffers.
+ */
+int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n,
+ char __user *user_buffer)
+{
+ return iio_dma_buffer_io(buffer, n, user_buffer, false);
+}
EXPORT_SYMBOL_GPL(iio_dma_buffer_read);
/**
- * iio_dma_buffer_data_available() - DMA buffer data_available callback
- * @buf: Buffer to check for data availability
+ * iio_dma_buffer_write() - DMA buffer write callback
+ * @buffer: Buffer to read form
+ * @n: Number of bytes to read
+ * @user_buffer: Userspace buffer to copy the data from
*
- * Should be used as the data_available callback for iio_buffer_access_ops
+ * Should be used as the write callback for iio_buffer_access_ops
* struct for DMA buffers.
*/
-size_t iio_dma_buffer_data_available(struct iio_buffer *buf)
+int iio_dma_buffer_write(struct iio_buffer *buffer, size_t n,
+ const char __user *user_buffer)
+{
+ return iio_dma_buffer_io(buffer, n,
+ (__force __user char *)user_buffer, true);
+}
+EXPORT_SYMBOL_GPL(iio_dma_buffer_write);
+
+/**
+ * iio_dma_buffer_usage() - DMA buffer data_available and
+ * space_available callback
+ * @buf: Buffer to check for data availability
+ *
+ * Should be used as the data_available and space_available callbacks for
+ * iio_buffer_access_ops struct for DMA buffers.
+ */
+size_t iio_dma_buffer_usage(struct iio_buffer *buf)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buf);
struct iio_dma_buffer_block *block;
@@ -586,7 +644,7 @@ size_t iio_dma_buffer_data_available(struct iio_buffer *buf)
return data_available;
}
-EXPORT_SYMBOL_GPL(iio_dma_buffer_data_available);
+EXPORT_SYMBOL_GPL(iio_dma_buffer_usage);
/**
* iio_dma_buffer_set_bytes_per_datum() - DMA buffer set_bytes_per_datum callback
diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
index a18c1da292af22..918f6f8d65b6ca 100644
--- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
+++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
@@ -64,14 +64,25 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue,
struct dmaengine_buffer *dmaengine_buffer =
iio_buffer_to_dmaengine_buffer(&queue->buffer);
struct dma_async_tx_descriptor *desc;
+ enum dma_transfer_direction dma_dir;
+ size_t max_size;
dma_cookie_t cookie;
- block->bytes_used = min(block->size, dmaengine_buffer->max_size);
- block->bytes_used = round_down(block->bytes_used,
- dmaengine_buffer->align);
+ max_size = min(block->size, dmaengine_buffer->max_size);
+ max_size = round_down(max_size, dmaengine_buffer->align);
+
+ if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) {
+ block->bytes_used = max_size;
+ dma_dir = DMA_DEV_TO_MEM;
+ } else {
+ dma_dir = DMA_MEM_TO_DEV;
+ }
+
+ if (!block->bytes_used || block->bytes_used > max_size)
+ return -EINVAL;
desc = dmaengine_prep_slave_single(dmaengine_buffer->chan,
- block->phys_addr, block->bytes_used, DMA_DEV_TO_MEM,
+ block->phys_addr, block->bytes_used, dma_dir,
DMA_PREP_INTERRUPT);
if (!desc)
return -ENOMEM;
@@ -112,12 +123,14 @@ static void iio_dmaengine_buffer_release(struct iio_buffer *buf)
static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = {
.read = iio_dma_buffer_read,
+ .write = iio_dma_buffer_write,
.set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum,
.set_length = iio_dma_buffer_set_length,
.request_update = iio_dma_buffer_request_update,
.enable = iio_dma_buffer_enable,
.disable = iio_dma_buffer_disable,
- .data_available = iio_dma_buffer_data_available,
+ .data_available = iio_dma_buffer_usage,
+ .space_available = iio_dma_buffer_usage,
.release = iio_dmaengine_buffer_release,
.modes = INDIO_BUFFER_HARDWARE,
@@ -159,7 +172,7 @@ static const struct iio_dev_attr *iio_dmaengine_buffer_attrs[] = {
* Once done using the buffer iio_dmaengine_buffer_free() should be used to
* release it.
*/
-struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
+static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
const char *channel)
{
struct dmaengine_buffer *dmaengine_buffer;
@@ -210,7 +223,6 @@ err_free:
kfree(dmaengine_buffer);
return ERR_PTR(ret);
}
-EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_alloc, IIO_DMAENGINE_BUFFER);
/**
* iio_dmaengine_buffer_free() - Free dmaengine buffer
@@ -230,66 +242,64 @@ void iio_dmaengine_buffer_free(struct iio_buffer *buffer)
}
EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_free, IIO_DMAENGINE_BUFFER);
-static void __devm_iio_dmaengine_buffer_free(void *buffer)
-{
- iio_dmaengine_buffer_free(buffer);
-}
-
-/**
- * devm_iio_dmaengine_buffer_alloc() - Resource-managed iio_dmaengine_buffer_alloc()
- * @dev: Parent device for the buffer
- * @channel: DMA channel name, typically "rx".
- *
- * This allocates a new IIO buffer which internally uses the DMAengine framework
- * to perform its transfers. The parent device will be used to request the DMA
- * channel.
- *
- * The buffer will be automatically de-allocated once the device gets destroyed.
- */
-static struct iio_buffer *devm_iio_dmaengine_buffer_alloc(struct device *dev,
- const char *channel)
+struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
+ struct iio_dev *indio_dev,
+ const char *channel,
+ enum iio_buffer_direction dir)
{
struct iio_buffer *buffer;
int ret;
buffer = iio_dmaengine_buffer_alloc(dev, channel);
if (IS_ERR(buffer))
- return buffer;
+ return ERR_CAST(buffer);
- ret = devm_add_action_or_reset(dev, __devm_iio_dmaengine_buffer_free,
- buffer);
- if (ret)
+ indio_dev->modes |= INDIO_BUFFER_HARDWARE;
+
+ buffer->direction = dir;
+
+ ret = iio_device_attach_buffer(indio_dev, buffer);
+ if (ret) {
+ iio_dmaengine_buffer_free(buffer);
return ERR_PTR(ret);
+ }
return buffer;
}
+EXPORT_SYMBOL_NS_GPL(iio_dmaengine_buffer_setup_ext, IIO_DMAENGINE_BUFFER);
+
+static void __devm_iio_dmaengine_buffer_free(void *buffer)
+{
+ iio_dmaengine_buffer_free(buffer);
+}
/**
- * devm_iio_dmaengine_buffer_setup() - Setup a DMA buffer for an IIO device
+ * devm_iio_dmaengine_buffer_setup_ext() - Setup a DMA buffer for an IIO device
* @dev: Parent device for the buffer
* @indio_dev: IIO device to which to attach this buffer.
* @channel: DMA channel name, typically "rx".
+ * @dir: Direction of buffer (in or out)
*
* This allocates a new IIO buffer with devm_iio_dmaengine_buffer_alloc()
* and attaches it to an IIO device with iio_device_attach_buffer().
* It also appends the INDIO_BUFFER_HARDWARE mode to the supported modes of the
* IIO device.
*/
-int devm_iio_dmaengine_buffer_setup(struct device *dev,
- struct iio_dev *indio_dev,
- const char *channel)
+int devm_iio_dmaengine_buffer_setup_ext(struct device *dev,
+ struct iio_dev *indio_dev,
+ const char *channel,
+ enum iio_buffer_direction dir)
{
struct iio_buffer *buffer;
- buffer = devm_iio_dmaengine_buffer_alloc(dev, channel);
+ buffer = iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, dir);
if (IS_ERR(buffer))
return PTR_ERR(buffer);
- indio_dev->modes |= INDIO_BUFFER_HARDWARE;
-
- return iio_device_attach_buffer(indio_dev, buffer);
+ return devm_add_action_or_reset(dev, __devm_iio_dmaengine_buffer_free,
+ buffer);
}
-EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup, IIO_DMAENGINE_BUFFER);
+EXPORT_SYMBOL_NS_GPL(devm_iio_dmaengine_buffer_setup_ext, IIO_DMAENGINE_BUFFER);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("DMA buffer for the IIO framework");
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index 34eb40bb95291a..3c2bf620f00fb4 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -131,6 +131,43 @@ config AD5624R_SPI
Say yes here to build support for Analog Devices AD5624R, AD5644R and
AD5664R converters (DAC). This driver uses the common SPI interface.
+config AD9739A
+ tristate "Analog Devices AD9739A RF DAC spi driver"
+ depends on SPI || COMPILE_TEST
+ select REGMAP_SPI
+ select IIO_BACKEND
+ help
+ Say yes here to build support for Analog Devices AD9739A Digital-to
+ Analog Converter.
+
+ The driver requires the assistance of the AXI DAC IP core to operate,
+ since SPI is used for configuration only, while data has to be
+ streamed into memory via DMA.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ad9739a.
+
+config ADI_AXI_DAC
+ tristate "Analog Devices Generic AXI DAC IP core driver"
+ select IIO_BUFFER
+ select IIO_BUFFER_DMAENGINE
+ select REGMAP_MMIO
+ select IIO_BACKEND
+ help
+ Say yes here to build support for Analog Devices Generic
+ AXI DAC IP core. The IP core is used for interfacing with
+ digital-to-analog (DAC) converters that require either a high-speed
+ serial interface (JESD204B/C) or a source synchronous parallel
+ interface (LVDS/CMOS).
+ Typically (for such devices) SPI will be used for configuration only,
+ while this IP core handles the streaming of data into memory via DMA.
+
+ Link: https://wiki.analog.com/resources/fpga/docs/axi_dac_ip
+ If unsure, say N (but it's safe to say "Y").
+
+ To compile this driver as a module, choose M here: the
+ module will be called adi-axi-dac.
+
config LTC2688
tristate "Analog Devices LTC2688 DAC spi driver"
depends on SPI
diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
index 55bf89739d14b9..8432a81a19dc2f 100644
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -29,6 +29,8 @@ obj-$(CONFIG_AD5696_I2C) += ad5696-i2c.o
obj-$(CONFIG_AD7293) += ad7293.o
obj-$(CONFIG_AD7303) += ad7303.o
obj-$(CONFIG_AD8801) += ad8801.o
+obj-$(CONFIG_AD9739A) += ad9739a.o
+obj-$(CONFIG_ADI_AXI_DAC) += adi-axi-dac.o
obj-$(CONFIG_CIO_DAC) += cio-dac.o
obj-$(CONFIG_DPOT_DAC) += dpot-dac.o
obj-$(CONFIG_DS4424) += ds4424.o
diff --git a/drivers/iio/dac/ad3552r.c b/drivers/iio/dac/ad3552r.c
index a492e8f2fc0fbc..8aa942896b5bc1 100644
--- a/drivers/iio/dac/ad3552r.c
+++ b/drivers/iio/dac/ad3552r.c
@@ -801,51 +801,45 @@ static int ad3552r_configure_custom_gain(struct ad3552r_desc *dac,
u32 ch)
{
struct device *dev = &dac->spi->dev;
- struct fwnode_handle *gain_child;
u32 val;
int err;
u8 addr;
u16 reg = 0, offset;
- gain_child = fwnode_get_named_child_node(child,
- "custom-output-range-config");
- if (!gain_child) {
- dev_err(dev,
- "mandatory custom-output-range-config property missing\n");
- return -EINVAL;
- }
+ struct fwnode_handle *gain_child __free(fwnode_handle)
+ = fwnode_get_named_child_node(child,
+ "custom-output-range-config");
+ if (!gain_child)
+ return dev_err_probe(dev, -EINVAL,
+ "mandatory custom-output-range-config property missing\n");
dac->ch_data[ch].range_override = 1;
reg |= ad3552r_field_prep(1, AD3552R_MASK_CH_RANGE_OVERRIDE);
err = fwnode_property_read_u32(gain_child, "adi,gain-scaling-p", &val);
- if (err) {
- dev_err(dev, "mandatory adi,gain-scaling-p property missing\n");
- goto put_child;
- }
+ if (err)
+ return dev_err_probe(dev, err,
+ "mandatory adi,gain-scaling-p property missing\n");
reg |= ad3552r_field_prep(val, AD3552R_MASK_CH_GAIN_SCALING_P);
dac->ch_data[ch].p = val;
err = fwnode_property_read_u32(gain_child, "adi,gain-scaling-n", &val);
- if (err) {
- dev_err(dev, "mandatory adi,gain-scaling-n property missing\n");
- goto put_child;
- }
+ if (err)
+ return dev_err_probe(dev, err,
+ "mandatory adi,gain-scaling-n property missing\n");
reg |= ad3552r_field_prep(val, AD3552R_MASK_CH_GAIN_SCALING_N);
dac->ch_data[ch].n = val;
err = fwnode_property_read_u32(gain_child, "adi,rfb-ohms", &val);
- if (err) {
- dev_err(dev, "mandatory adi,rfb-ohms property missing\n");
- goto put_child;
- }
+ if (err)
+ return dev_err_probe(dev, err,
+ "mandatory adi,rfb-ohms property missing\n");
dac->ch_data[ch].rfb = val;
err = fwnode_property_read_u32(gain_child, "adi,gain-offset", &val);
- if (err) {
- dev_err(dev, "mandatory adi,gain-offset property missing\n");
- goto put_child;
- }
+ if (err)
+ return dev_err_probe(dev, err,
+ "mandatory adi,gain-offset property missing\n");
dac->ch_data[ch].gain_offset = val;
offset = abs((s32)val);
@@ -855,21 +849,14 @@ static int ad3552r_configure_custom_gain(struct ad3552r_desc *dac,
addr = AD3552R_REG_ADDR_CH_GAIN(ch);
err = ad3552r_write_reg(dac, addr,
offset & AD3552R_MASK_CH_OFFSET_BITS_0_7);
- if (err) {
- dev_err(dev, "Error writing register\n");
- goto put_child;
- }
+ if (err)
+ return dev_err_probe(dev, err, "Error writing register\n");
err = ad3552r_write_reg(dac, addr, reg);
- if (err) {
- dev_err(dev, "Error writing register\n");
- goto put_child;
- }
-
-put_child:
- fwnode_handle_put(gain_child);
+ if (err)
+ return dev_err_probe(dev, err, "Error writing register\n");
- return err;
+ return 0;
}
static void ad3552r_reg_disable(void *reg)
@@ -880,7 +867,6 @@ static void ad3552r_reg_disable(void *reg)
static int ad3552r_configure_device(struct ad3552r_desc *dac)
{
struct device *dev = &dac->spi->dev;
- struct fwnode_handle *child;
struct regulator *vref;
int err, cnt = 0, voltage, delta = 100000;
u32 vals[2], val, ch;
@@ -949,53 +935,45 @@ static int ad3552r_configure_device(struct ad3552r_desc *dac)
return -ENODEV;
}
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
err = fwnode_property_read_u32(child, "reg", &ch);
- if (err) {
- dev_err(dev, "mandatory reg property missing\n");
- goto put_child;
- }
- if (ch >= AD3552R_NUM_CH) {
- dev_err(dev, "reg must be less than %d\n",
- AD3552R_NUM_CH);
- err = -EINVAL;
- goto put_child;
- }
+ if (err)
+ return dev_err_probe(dev, err,
+ "mandatory reg property missing\n");
+ if (ch >= AD3552R_NUM_CH)
+ return dev_err_probe(dev, -EINVAL,
+ "reg must be less than %d\n",
+ AD3552R_NUM_CH);
if (fwnode_property_present(child, "adi,output-range-microvolt")) {
err = fwnode_property_read_u32_array(child,
"adi,output-range-microvolt",
vals,
2);
- if (err) {
- dev_err(dev,
+ if (err)
+ return dev_err_probe(dev, err,
"adi,output-range-microvolt property could not be parsed\n");
- goto put_child;
- }
err = ad3552r_find_range(dac->chip_id, vals);
- if (err < 0) {
- dev_err(dev,
- "Invalid adi,output-range-microvolt value\n");
- goto put_child;
- }
+ if (err < 0)
+ return dev_err_probe(dev, err,
+ "Invalid adi,output-range-microvolt value\n");
+
val = err;
err = ad3552r_set_ch_value(dac,
AD3552R_CH_OUTPUT_RANGE_SEL,
ch, val);
if (err)
- goto put_child;
+ return err;
dac->ch_data[ch].range = val;
} else if (dac->chip_id == AD3542R_ID) {
- dev_err(dev,
- "adi,output-range-microvolt is required for ad3542r\n");
- err = -EINVAL;
- goto put_child;
+ return dev_err_probe(dev, -EINVAL,
+ "adi,output-range-microvolt is required for ad3542r\n");
} else {
err = ad3552r_configure_custom_gain(dac, child, ch);
if (err)
- goto put_child;
+ return err;
}
ad3552r_calc_gain_and_offset(dac, ch);
@@ -1003,7 +981,7 @@ static int ad3552r_configure_device(struct ad3552r_desc *dac)
err = ad3552r_set_ch_value(dac, AD3552R_CH_SELECT, ch, 1);
if (err < 0)
- goto put_child;
+ return err;
dac->channels[cnt] = AD3552R_CH_DAC(ch);
++cnt;
@@ -1021,10 +999,6 @@ static int ad3552r_configure_device(struct ad3552r_desc *dac)
dac->num_ch = cnt;
return 0;
-put_child:
- fwnode_handle_put(child);
-
- return err;
}
static int ad3552r_init(struct ad3552r_desc *dac)
diff --git a/drivers/iio/dac/ad5755.c b/drivers/iio/dac/ad5755.c
index 404865e354602c..0b24cb19ac9d1c 100644
--- a/drivers/iio/dac/ad5755.c
+++ b/drivers/iio/dac/ad5755.c
@@ -809,7 +809,6 @@ static struct ad5755_platform_data *ad5755_parse_fw(struct device *dev)
static int ad5755_probe(struct spi_device *spi)
{
- enum ad5755_type type = spi_get_device_id(spi)->driver_data;
const struct ad5755_platform_data *pdata;
struct iio_dev *indio_dev;
struct ad5755_state *st;
@@ -824,7 +823,7 @@ static int ad5755_probe(struct spi_device *spi)
st = iio_priv(indio_dev);
spi_set_drvdata(spi, indio_dev);
- st->chip_info = &ad5755_chip_info_tbl[type];
+ st->chip_info = spi_get_device_match_data(spi);
st->spi = spi;
st->pwr_down = 0xf;
@@ -854,21 +853,21 @@ static int ad5755_probe(struct spi_device *spi)
}
static const struct spi_device_id ad5755_id[] = {
- { "ad5755", ID_AD5755 },
- { "ad5755-1", ID_AD5755 },
- { "ad5757", ID_AD5757 },
- { "ad5735", ID_AD5735 },
- { "ad5737", ID_AD5737 },
+ { "ad5755", (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5755] },
+ { "ad5755-1", (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5755] },
+ { "ad5757", (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5757] },
+ { "ad5735", (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5735] },
+ { "ad5737", (kernel_ulong_t)&ad5755_chip_info_tbl[ID_AD5737] },
{}
};
MODULE_DEVICE_TABLE(spi, ad5755_id);
static const struct of_device_id ad5755_of_match[] = {
- { .compatible = "adi,ad5755" },
- { .compatible = "adi,ad5755-1" },
- { .compatible = "adi,ad5757" },
- { .compatible = "adi,ad5735" },
- { .compatible = "adi,ad5737" },
+ { .compatible = "adi,ad5755", &ad5755_chip_info_tbl[ID_AD5755] },
+ { .compatible = "adi,ad5755-1", &ad5755_chip_info_tbl[ID_AD5755] },
+ { .compatible = "adi,ad5757", &ad5755_chip_info_tbl[ID_AD5757] },
+ { .compatible = "adi,ad5735", &ad5755_chip_info_tbl[ID_AD5735] },
+ { .compatible = "adi,ad5737", &ad5755_chip_info_tbl[ID_AD5737] },
{ }
};
MODULE_DEVICE_TABLE(of, ad5755_of_match);
@@ -876,6 +875,7 @@ MODULE_DEVICE_TABLE(of, ad5755_of_match);
static struct spi_driver ad5755_driver = {
.driver = {
.name = "ad5755",
+ .of_match_table = ad5755_of_match,
},
.probe = ad5755_probe,
.id_table = ad5755_id,
diff --git a/drivers/iio/dac/ad5770r.c b/drivers/iio/dac/ad5770r.c
index f66d67402e4365..c360ebf5297ab9 100644
--- a/drivers/iio/dac/ad5770r.c
+++ b/drivers/iio/dac/ad5770r.c
@@ -515,39 +515,32 @@ static int ad5770r_channel_config(struct ad5770r_state *st)
{
int ret, tmp[2], min, max;
unsigned int num;
- struct fwnode_handle *child;
num = device_get_child_node_count(&st->spi->dev);
if (num != AD5770R_MAX_CHANNELS)
return -EINVAL;
- device_for_each_child_node(&st->spi->dev, child) {
+ device_for_each_child_node_scoped(&st->spi->dev, child) {
ret = fwnode_property_read_u32(child, "reg", &num);
if (ret)
- goto err_child_out;
- if (num >= AD5770R_MAX_CHANNELS) {
- ret = -EINVAL;
- goto err_child_out;
- }
+ return ret;
+ if (num >= AD5770R_MAX_CHANNELS)
+ return -EINVAL;
ret = fwnode_property_read_u32_array(child,
"adi,range-microamp",
tmp, 2);
if (ret)
- goto err_child_out;
+ return ret;
min = tmp[0] / 1000;
max = tmp[1] / 1000;
ret = ad5770r_store_output_range(st, min, max, num);
if (ret)
- goto err_child_out;
+ return ret;
}
return 0;
-
-err_child_out:
- fwnode_handle_put(child);
- return ret;
}
static int ad5770r_init(struct ad5770r_state *st)
diff --git a/drivers/iio/dac/ad9739a.c b/drivers/iio/dac/ad9739a.c
new file mode 100644
index 00000000000000..ff33120075bf60
--- /dev/null
+++ b/drivers/iio/dac/ad9739a.c
@@ -0,0 +1,463 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices AD9739a SPI DAC driver
+ *
+ * Copyright 2015-2024 Analog Devices Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/gpio/consumer.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+#include <linux/units.h>
+
+#include <linux/iio/backend.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+
+#define AD9739A_REG_MODE 0
+#define AD9739A_RESET_MASK BIT(5)
+#define AD9739A_REG_FSC_1 0x06
+#define AD9739A_REG_FSC_2 0x07
+#define AD9739A_FSC_MSB GENMASK(1, 0)
+#define AD9739A_REG_DEC_CNT 0x8
+#define AD9739A_NORMAL_MODE 0
+#define AD9739A_MIXED_MODE 2
+#define AD9739A_DAC_DEC GENMASK(1, 0)
+#define AD9739A_REG_LVDS_REC_CNT1 0x10
+#define AD9739A_RCVR_LOOP_EN_MASK GENMASK(1, 0)
+#define AD9739A_REG_LVDS_REC_CNT4 0x13
+#define AD9739A_FINE_DEL_SKW_MASK GENMASK(3, 0)
+#define AD9739A_REG_LVDS_REC_STAT9 0x21
+#define AD9739A_RCVR_TRACK_AND_LOCK (BIT(3) | BIT(0))
+#define AD9739A_REG_CROSS_CNT1 0x22
+#define AD9739A_REG_CROSS_CNT2 0x23
+#define AD9739A_REG_PHS_DET 0x24
+#define AD9739A_REG_MU_DUTY 0x25
+#define AD9739A_REG_MU_CNT1 0x26
+#define AD9739A_MU_EN_MASK BIT(0)
+#define AD9739A_REG_MU_CNT2 0x27
+#define AD9739A_REG_MU_CNT3 0x28
+#define AD9739A_REG_MU_CNT4 0x29
+#define AD9739A_MU_CNT4_DEFAULT 0xcb
+#define AD9739A_REG_MU_STAT1 0x2A
+#define AD9739A_MU_LOCK_MASK BIT(0)
+#define AD9739A_REG_ANA_CNT_1 0x32
+#define AD9739A_REG_ID 0x35
+
+#define AD9739A_ID 0x24
+#define AD9739A_REG_IS_RESERVED(reg) \
+ ((reg) == 0x5 || (reg) == 0x9 || (reg) == 0x0E || (reg) == 0x0D || \
+ (reg) == 0x2B || (reg) == 0x2C || (reg) == 0x34)
+
+#define AD9739A_FSC_MIN 8580
+#define AD9739A_FSC_MAX 31700
+#define AD9739A_FSC_RANGE (AD9739A_FSC_MAX - AD9739A_FSC_MIN + 1)
+
+#define AD9739A_MIN_DAC_CLK (1600 * MEGA)
+#define AD9739A_MAX_DAC_CLK (2500 * MEGA)
+#define AD9739A_DAC_CLK_RANGE (AD9739A_MAX_DAC_CLK - AD9739A_MIN_DAC_CLK + 1)
+/* as recommended by the datasheet */
+#define AD9739A_LOCK_N_TRIES 3
+
+struct ad9739a_state {
+ struct iio_backend *back;
+ struct regmap *regmap;
+ unsigned long sample_rate;
+};
+
+static int ad9739a_oper_mode_get(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ad9739a_state *st = iio_priv(indio_dev);
+ u32 mode;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD9739A_REG_DEC_CNT, &mode);
+ if (ret)
+ return ret;
+
+ mode = FIELD_GET(AD9739A_DAC_DEC, mode);
+ /* sanity check we get valid values from the HW */
+ if (mode != AD9739A_NORMAL_MODE && mode != AD9739A_MIXED_MODE)
+ return -EIO;
+ if (!mode)
+ return AD9739A_NORMAL_MODE;
+
+ /*
+ * We get 2 from the device but for IIO modes, that means 1. Hence the
+ * minus 1.
+ */
+ return AD9739A_MIXED_MODE - 1;
+}
+
+static int ad9739a_oper_mode_set(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan, u32 mode)
+{
+ struct ad9739a_state *st = iio_priv(indio_dev);
+
+ /*
+ * On the IIO interface we have 0 and 1 for mode. But for mixed_mode, we
+ * need to write 2 in the device. That's what the below check is about.
+ */
+ if (mode == AD9739A_MIXED_MODE - 1)
+ mode = AD9739A_MIXED_MODE;
+
+ return regmap_update_bits(st->regmap, AD9739A_REG_DEC_CNT,
+ AD9739A_DAC_DEC, mode);
+}
+
+static int ad9739a_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ad9739a_state *st = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = st->sample_rate;
+ *val2 = 0;
+ return IIO_VAL_INT_64;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9739a_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct ad9739a_state *st = iio_priv(indio_dev);
+
+ return iio_backend_data_source_set(st->back, 0, IIO_BACKEND_EXTERNAL);
+}
+
+static int ad9739a_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct ad9739a_state *st = iio_priv(indio_dev);
+
+ return iio_backend_data_source_set(st->back, 0,
+ IIO_BACKEND_INTERNAL_CONTINUOS_WAVE);
+}
+
+static bool ad9739a_reg_accessible(struct device *dev, unsigned int reg)
+{
+ if (AD9739A_REG_IS_RESERVED(reg))
+ return false;
+ if (reg > AD9739A_REG_MU_STAT1 && reg < AD9739A_REG_ANA_CNT_1)
+ return false;
+
+ return true;
+}
+
+static int ad9739a_reset(struct device *dev, const struct ad9739a_state *st)
+{
+ struct gpio_desc *gpio;
+ int ret;
+
+ gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(gpio))
+ return PTR_ERR(gpio);
+ if (gpio) {
+ /* minimum pulse width of 40ns */
+ ndelay(40);
+ gpiod_set_value_cansleep(gpio, 0);
+ return 0;
+ }
+
+ /* bring all registers to their default state */
+ ret = regmap_set_bits(st->regmap, AD9739A_REG_MODE, AD9739A_RESET_MASK);
+ if (ret)
+ return ret;
+
+ ndelay(40);
+
+ return regmap_clear_bits(st->regmap, AD9739A_REG_MODE,
+ AD9739A_RESET_MASK);
+}
+
+/*
+ * Recommended values (as per datasheet) for the dac clk common mode voltage
+ * and Mu controller. Look at table 29.
+ */
+static const struct reg_sequence ad9739a_clk_mu_ctrl[] = {
+ /* DAC clk common mode voltage */
+ { AD9739A_REG_CROSS_CNT1, 0x0f },
+ { AD9739A_REG_CROSS_CNT2, 0x0f },
+ /* Mu controller configuration */
+ { AD9739A_REG_PHS_DET, 0x30 },
+ { AD9739A_REG_MU_DUTY, 0x80 },
+ { AD9739A_REG_MU_CNT2, 0x44 },
+ { AD9739A_REG_MU_CNT3, 0x6c },
+};
+
+static int ad9739a_init(struct device *dev, const struct ad9739a_state *st)
+{
+ unsigned int i = 0, lock, fsc;
+ u32 fsc_raw;
+ int ret;
+
+ ret = regmap_multi_reg_write(st->regmap, ad9739a_clk_mu_ctrl,
+ ARRAY_SIZE(ad9739a_clk_mu_ctrl));
+ if (ret)
+ return ret;
+
+ /*
+ * Try to get the Mu lock. Repeat the below steps AD9739A_LOCK_N_TRIES
+ * (as specified by the datasheet) until we get the lock.
+ */
+ do {
+ ret = regmap_write(st->regmap, AD9739A_REG_MU_CNT4,
+ AD9739A_MU_CNT4_DEFAULT);
+ if (ret)
+ return ret;
+
+ /* Enable the Mu controller search and track mode. */
+ ret = regmap_set_bits(st->regmap, AD9739A_REG_MU_CNT1,
+ AD9739A_MU_EN_MASK);
+ if (ret)
+ return ret;
+
+ /* Ensure the DLL loop is locked */
+ ret = regmap_read_poll_timeout(st->regmap, AD9739A_REG_MU_STAT1,
+ lock, lock & AD9739A_MU_LOCK_MASK,
+ 0, 1000);
+ if (ret && ret != -ETIMEDOUT)
+ return ret;
+ } while (ret && ++i < AD9739A_LOCK_N_TRIES);
+
+ if (i == AD9739A_LOCK_N_TRIES)
+ return dev_err_probe(dev, ret, "Mu lock timeout\n");
+
+ /* Receiver tracking and lock. Same deal as the Mu controller */
+ i = 0;
+ do {
+ ret = regmap_update_bits(st->regmap, AD9739A_REG_LVDS_REC_CNT4,
+ AD9739A_FINE_DEL_SKW_MASK,
+ FIELD_PREP(AD9739A_FINE_DEL_SKW_MASK, 2));
+ if (ret)
+ return ret;
+
+ /* Disable the receiver and the loop. */
+ ret = regmap_write(st->regmap, AD9739A_REG_LVDS_REC_CNT1, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * Re-enable the loop so it falls out of lock and begins the
+ * search/track routine again.
+ */
+ ret = regmap_set_bits(st->regmap, AD9739A_REG_LVDS_REC_CNT1,
+ AD9739A_RCVR_LOOP_EN_MASK);
+ if (ret)
+ return ret;
+
+ /* Ensure the DLL loop is locked */
+ ret = regmap_read_poll_timeout(st->regmap,
+ AD9739A_REG_LVDS_REC_STAT9, lock,
+ lock == AD9739A_RCVR_TRACK_AND_LOCK,
+ 0, 1000);
+ if (ret && ret != -ETIMEDOUT)
+ return ret;
+ } while (ret && ++i < AD9739A_LOCK_N_TRIES);
+
+ if (i == AD9739A_LOCK_N_TRIES)
+ return dev_err_probe(dev, ret, "Receiver lock timeout\n");
+
+ ret = device_property_read_u32(dev, "adi,full-scale-microamp", &fsc);
+ if (ret && ret == -EINVAL)
+ return 0;
+ if (ret)
+ return ret;
+ if (!in_range(fsc, AD9739A_FSC_MIN, AD9739A_FSC_RANGE))
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid full scale current(%u) [%u %u]\n",
+ fsc, AD9739A_FSC_MIN, AD9739A_FSC_MAX);
+ /*
+ * IOUTFS is given by
+ * Ioutfs = 0.0226 * FSC + 8.58
+ * and is given in mA. Hence we'll have to multiply by 10 * MILLI in
+ * order to get rid of the fractional.
+ */
+ fsc_raw = DIV_ROUND_CLOSEST(fsc * 10 - 85800, 226);
+
+ ret = regmap_write(st->regmap, AD9739A_REG_FSC_1, fsc_raw & 0xff);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(st->regmap, AD9739A_REG_FSC_2,
+ AD9739A_FSC_MSB, fsc_raw >> 8);
+}
+
+static const char * const ad9739a_modes_avail[] = { "normal", "mixed-mode" };
+
+static const struct iio_enum ad9739a_modes = {
+ .items = ad9739a_modes_avail,
+ .num_items = ARRAY_SIZE(ad9739a_modes_avail),
+ .get = ad9739a_oper_mode_get,
+ .set = ad9739a_oper_mode_set,
+};
+
+static const struct iio_chan_spec_ext_info ad9739a_ext_info[] = {
+ IIO_ENUM_AVAILABLE("operating_mode", IIO_SEPARATE, &ad9739a_modes),
+ IIO_ENUM("operating_mode", IIO_SEPARATE, &ad9739a_modes),
+ { }
+};
+
+/*
+ * The reason for having two different channels is because we have, in reality,
+ * two sources of data:
+ * ALTVOLTAGE: It's a Continuous Wave that's internally generated by the
+ * backend device.
+ * VOLTAGE: It's the typical data we can have in a DAC device and the source
+ * of it has nothing to do with the backend. The backend will only
+ * forward it into our data interface to be sent out.
+ */
+static struct iio_chan_spec ad9739a_channels[] = {
+ {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .scan_index = -1,
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .output = 1,
+ .ext_info = ad9739a_ext_info,
+ .scan_type = {
+ .sign = 's',
+ .storagebits = 16,
+ .realbits = 16,
+ },
+ }
+};
+
+static const struct iio_info ad9739a_info = {
+ .read_raw = ad9739a_read_raw,
+};
+
+static const struct iio_buffer_setup_ops ad9739a_buffer_setup_ops = {
+ .preenable = &ad9739a_buffer_preenable,
+ .postdisable = &ad9739a_buffer_postdisable,
+};
+
+static const struct regmap_config ad9739a_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .readable_reg = ad9739a_reg_accessible,
+ .writeable_reg = ad9739a_reg_accessible,
+ .max_register = AD9739A_REG_ID,
+};
+
+static int ad9739a_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ad9739a_state *st;
+ unsigned int id;
+ struct clk *clk;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Could not get clkin\n");
+
+ st->sample_rate = clk_get_rate(clk);
+ if (!in_range(st->sample_rate, AD9739A_MIN_DAC_CLK,
+ AD9739A_DAC_CLK_RANGE))
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid dac clk range(%lu) [%lu %lu]\n",
+ st->sample_rate, AD9739A_MIN_DAC_CLK,
+ AD9739A_MAX_DAC_CLK);
+
+ st->regmap = devm_regmap_init_spi(spi, &ad9739a_regmap_config);
+ if (IS_ERR(st->regmap))
+ return PTR_ERR(st->regmap);
+
+ ret = regmap_read(st->regmap, AD9739A_REG_ID, &id);
+ if (ret)
+ return ret;
+
+ if (id != AD9739A_ID)
+ dev_warn(dev, "Unrecognized CHIP_ID 0x%X", id);
+
+ ret = ad9739a_reset(dev, st);
+ if (ret)
+ return ret;
+
+ ret = ad9739a_init(dev, st);
+ if (ret)
+ return ret;
+
+ st->back = devm_iio_backend_get(dev, NULL);
+ if (IS_ERR(st->back))
+ return PTR_ERR(st->back);
+
+ ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev);
+ if (ret)
+ return ret;
+
+ ret = iio_backend_extend_chan_spec(indio_dev, st->back,
+ &ad9739a_channels[0]);
+ if (ret)
+ return ret;
+
+ ret = iio_backend_set_sampling_freq(st->back, 0, st->sample_rate);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_backend_enable(dev, st->back);
+ if (ret)
+ return ret;
+
+ indio_dev->name = "ad9739a";
+ indio_dev->info = &ad9739a_info;
+ indio_dev->channels = ad9739a_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ad9739a_channels);
+ indio_dev->setup_ops = &ad9739a_buffer_setup_ops;
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct of_device_id ad9739a_of_match[] = {
+ { .compatible = "adi,ad9739a" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ad9739a_of_match);
+
+static const struct spi_device_id ad9739a_id[] = {
+ {"ad9739a"},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ad9739a_id);
+
+static struct spi_driver ad9739a_driver = {
+ .driver = {
+ .name = "ad9739a",
+ .of_match_table = ad9739a_of_match,
+ },
+ .probe = ad9739a_probe,
+ .id_table = ad9739a_id,
+};
+module_spi_driver(ad9739a_driver);
+
+MODULE_AUTHOR("Dragos Bogdan <dragos.bogdan@analog.com>");
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD9739 DAC");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_BACKEND);
diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c
new file mode 100644
index 00000000000000..9047c5aec0ff9d
--- /dev/null
+++ b/drivers/iio/dac/adi-axi-dac.c
@@ -0,0 +1,635 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices Generic AXI DAC IP core
+ * Link: https://wiki.analog.com/resources/fpga/docs/axi_dac_ip
+ *
+ * Copyright 2016-2024 Analog Devices Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/limits.h>
+#include <linux/kstrtox.h>
+#include <linux/math.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <linux/fpga/adi-axi-common.h>
+#include <linux/iio/backend.h>
+#include <linux/iio/buffer-dmaengine.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+
+/*
+ * Register definitions:
+ * https://wiki.analog.com/resources/fpga/docs/axi_dac_ip#register_map
+ */
+
+/* Base controls */
+#define AXI_DAC_REG_CONFIG 0x0c
+#define AXI_DDS_DISABLE BIT(6)
+
+ /* DAC controls */
+#define AXI_DAC_REG_RSTN 0x0040
+#define AXI_DAC_RSTN_CE_N BIT(2)
+#define AXI_DAC_RSTN_MMCM_RSTN BIT(1)
+#define AXI_DAC_RSTN_RSTN BIT(0)
+#define AXI_DAC_REG_CNTRL_1 0x0044
+#define AXI_DAC_SYNC BIT(0)
+#define AXI_DAC_REG_CNTRL_2 0x0048
+#define ADI_DAC_R1_MODE BIT(4)
+#define AXI_DAC_DRP_STATUS 0x0074
+#define AXI_DAC_DRP_LOCKED BIT(17)
+/* DAC Channel controls */
+#define AXI_DAC_REG_CHAN_CNTRL_1(c) (0x0400 + (c) * 0x40)
+#define AXI_DAC_REG_CHAN_CNTRL_3(c) (0x0408 + (c) * 0x40)
+#define AXI_DAC_SCALE_SIGN BIT(15)
+#define AXI_DAC_SCALE_INT BIT(14)
+#define AXI_DAC_SCALE GENMASK(14, 0)
+#define AXI_DAC_REG_CHAN_CNTRL_2(c) (0x0404 + (c) * 0x40)
+#define AXI_DAC_REG_CHAN_CNTRL_4(c) (0x040c + (c) * 0x40)
+#define AXI_DAC_PHASE GENMASK(31, 16)
+#define AXI_DAC_FREQUENCY GENMASK(15, 0)
+#define AXI_DAC_REG_CHAN_CNTRL_7(c) (0x0418 + (c) * 0x40)
+#define AXI_DAC_DATA_SEL GENMASK(3, 0)
+
+/* 360 degrees in rad */
+#define AXI_DAC_2_PI_MEGA 6283190
+enum {
+ AXI_DAC_DATA_INTERNAL_TONE,
+ AXI_DAC_DATA_DMA = 2,
+};
+
+struct axi_dac_state {
+ struct regmap *regmap;
+ struct device *dev;
+ /*
+ * lock to protect multiple accesses to the device registers and global
+ * data/variables.
+ */
+ struct mutex lock;
+ u64 dac_clk;
+ u32 reg_config;
+ bool int_tone;
+};
+
+static int axi_dac_enable(struct iio_backend *back)
+{
+ struct axi_dac_state *st = iio_backend_get_priv(back);
+ unsigned int __val;
+ int ret;
+
+ guard(mutex)(&st->lock);
+ ret = regmap_set_bits(st->regmap, AXI_DAC_REG_RSTN,
+ AXI_DAC_RSTN_MMCM_RSTN);
+ if (ret)
+ return ret;
+ /*
+ * Make sure the DRP (Dynamic Reconfiguration Port) is locked. Not all
+ * designs really use it but if they don't we still get the lock bit
+ * set. So let's do it all the time so the code is generic.
+ */
+ ret = regmap_read_poll_timeout(st->regmap, AXI_DAC_DRP_STATUS, __val,
+ __val & AXI_DAC_DRP_LOCKED, 100, 1000);
+ if (ret)
+ return ret;
+
+ return regmap_set_bits(st->regmap, AXI_DAC_REG_RSTN,
+ AXI_DAC_RSTN_RSTN | AXI_DAC_RSTN_MMCM_RSTN);
+}
+
+static void axi_dac_disable(struct iio_backend *back)
+{
+ struct axi_dac_state *st = iio_backend_get_priv(back);
+
+ guard(mutex)(&st->lock);
+ regmap_write(st->regmap, AXI_DAC_REG_RSTN, 0);
+}
+
+static struct iio_buffer *axi_dac_request_buffer(struct iio_backend *back,
+ struct iio_dev *indio_dev)
+{
+ struct axi_dac_state *st = iio_backend_get_priv(back);
+ const char *dma_name;
+
+ if (device_property_read_string(st->dev, "dma-names", &dma_name))
+ dma_name = "tx";
+
+ return iio_dmaengine_buffer_setup_ext(st->dev, indio_dev, dma_name,
+ IIO_BUFFER_DIRECTION_OUT);
+}
+
+static void axi_dac_free_buffer(struct iio_backend *back,
+ struct iio_buffer *buffer)
+{
+ iio_dmaengine_buffer_free(buffer);
+}
+
+enum {
+ AXI_DAC_FREQ_TONE_1,
+ AXI_DAC_FREQ_TONE_2,
+ AXI_DAC_SCALE_TONE_1,
+ AXI_DAC_SCALE_TONE_2,
+ AXI_DAC_PHASE_TONE_1,
+ AXI_DAC_PHASE_TONE_2,
+};
+
+static int __axi_dac_frequency_get(struct axi_dac_state *st, unsigned int chan,
+ unsigned int tone_2, unsigned int *freq)
+{
+ u32 reg, raw;
+ int ret;
+
+ if (!st->dac_clk) {
+ dev_err(st->dev, "Sampling rate is 0...\n");
+ return -EINVAL;
+ }
+
+ if (tone_2)
+ reg = AXI_DAC_REG_CHAN_CNTRL_4(chan);
+ else
+ reg = AXI_DAC_REG_CHAN_CNTRL_2(chan);
+
+ ret = regmap_read(st->regmap, reg, &raw);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(AXI_DAC_FREQUENCY, raw);
+ *freq = DIV_ROUND_CLOSEST_ULL(raw * st->dac_clk, BIT(16));
+
+ return 0;
+}
+
+static int axi_dac_frequency_get(struct axi_dac_state *st,
+ const struct iio_chan_spec *chan, char *buf,
+ unsigned int tone_2)
+{
+ unsigned int freq;
+ int ret;
+
+ scoped_guard(mutex, &st->lock) {
+ ret = __axi_dac_frequency_get(st, chan->channel, tone_2, &freq);
+ if (ret)
+ return ret;
+ }
+
+ return sysfs_emit(buf, "%u\n", freq);
+}
+
+static int axi_dac_scale_get(struct axi_dac_state *st,
+ const struct iio_chan_spec *chan, char *buf,
+ unsigned int tone_2)
+{
+ unsigned int scale, sign;
+ int ret, vals[2];
+ u32 reg, raw;
+
+ if (tone_2)
+ reg = AXI_DAC_REG_CHAN_CNTRL_3(chan->channel);
+ else
+ reg = AXI_DAC_REG_CHAN_CNTRL_1(chan->channel);
+
+ ret = regmap_read(st->regmap, reg, &raw);
+ if (ret)
+ return ret;
+
+ sign = FIELD_GET(AXI_DAC_SCALE_SIGN, raw);
+ raw = FIELD_GET(AXI_DAC_SCALE, raw);
+ scale = DIV_ROUND_CLOSEST_ULL((u64)raw * MEGA, AXI_DAC_SCALE_INT);
+
+ vals[0] = scale / MEGA;
+ vals[1] = scale % MEGA;
+
+ if (sign) {
+ vals[0] *= -1;
+ if (!vals[0])
+ vals[1] *= -1;
+ }
+
+ return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals),
+ vals);
+}
+
+static int axi_dac_phase_get(struct axi_dac_state *st,
+ const struct iio_chan_spec *chan, char *buf,
+ unsigned int tone_2)
+{
+ u32 reg, raw, phase;
+ int ret, vals[2];
+
+ if (tone_2)
+ reg = AXI_DAC_REG_CHAN_CNTRL_4(chan->channel);
+ else
+ reg = AXI_DAC_REG_CHAN_CNTRL_2(chan->channel);
+
+ ret = regmap_read(st->regmap, reg, &raw);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(AXI_DAC_PHASE, raw);
+ phase = DIV_ROUND_CLOSEST_ULL((u64)raw * AXI_DAC_2_PI_MEGA, U16_MAX);
+
+ vals[0] = phase / MEGA;
+ vals[1] = phase % MEGA;
+
+ return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals),
+ vals);
+}
+
+static int __axi_dac_frequency_set(struct axi_dac_state *st, unsigned int chan,
+ u64 sample_rate, unsigned int freq,
+ unsigned int tone_2)
+{
+ u32 reg;
+ u16 raw;
+ int ret;
+
+ if (!sample_rate || freq > sample_rate / 2) {
+ dev_err(st->dev, "Invalid frequency(%u) dac_clk(%llu)\n",
+ freq, sample_rate);
+ return -EINVAL;
+ }
+
+ if (tone_2)
+ reg = AXI_DAC_REG_CHAN_CNTRL_4(chan);
+ else
+ reg = AXI_DAC_REG_CHAN_CNTRL_2(chan);
+
+ raw = DIV64_U64_ROUND_CLOSEST((u64)freq * BIT(16), sample_rate);
+
+ ret = regmap_update_bits(st->regmap, reg, AXI_DAC_FREQUENCY, raw);
+ if (ret)
+ return ret;
+
+ /* synchronize channels */
+ return regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_1, AXI_DAC_SYNC);
+}
+
+static int axi_dac_frequency_set(struct axi_dac_state *st,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len, unsigned int tone_2)
+{
+ unsigned int freq;
+ int ret;
+
+ ret = kstrtou32(buf, 10, &freq);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+ ret = __axi_dac_frequency_set(st, chan->channel, st->dac_clk, freq,
+ tone_2);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static int axi_dac_scale_set(struct axi_dac_state *st,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len, unsigned int tone_2)
+{
+ int integer, frac, scale;
+ u32 raw = 0, reg;
+ int ret;
+
+ ret = iio_str_to_fixpoint(buf, 100000, &integer, &frac);
+ if (ret)
+ return ret;
+
+ scale = integer * MEGA + frac;
+ if (scale <= -2 * (int)MEGA || scale >= 2 * (int)MEGA)
+ return -EINVAL;
+
+ /* format is 1.1.14 (sign, integer and fractional bits) */
+ if (scale < 0) {
+ raw = FIELD_PREP(AXI_DAC_SCALE_SIGN, 1);
+ scale *= -1;
+ }
+
+ raw |= div_u64((u64)scale * AXI_DAC_SCALE_INT, MEGA);
+
+ if (tone_2)
+ reg = AXI_DAC_REG_CHAN_CNTRL_3(chan->channel);
+ else
+ reg = AXI_DAC_REG_CHAN_CNTRL_1(chan->channel);
+
+ guard(mutex)(&st->lock);
+ ret = regmap_write(st->regmap, reg, raw);
+ if (ret)
+ return ret;
+
+ /* synchronize channels */
+ ret = regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_1, AXI_DAC_SYNC);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static int axi_dac_phase_set(struct axi_dac_state *st,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len, unsigned int tone_2)
+{
+ int integer, frac, phase;
+ u32 raw, reg;
+ int ret;
+
+ ret = iio_str_to_fixpoint(buf, 100000, &integer, &frac);
+ if (ret)
+ return ret;
+
+ phase = integer * MEGA + frac;
+ if (phase < 0 || phase > AXI_DAC_2_PI_MEGA)
+ return -EINVAL;
+
+ raw = DIV_ROUND_CLOSEST_ULL((u64)phase * U16_MAX, AXI_DAC_2_PI_MEGA);
+
+ if (tone_2)
+ reg = AXI_DAC_REG_CHAN_CNTRL_4(chan->channel);
+ else
+ reg = AXI_DAC_REG_CHAN_CNTRL_2(chan->channel);
+
+ guard(mutex)(&st->lock);
+ ret = regmap_update_bits(st->regmap, reg, AXI_DAC_PHASE,
+ FIELD_PREP(AXI_DAC_PHASE, raw));
+ if (ret)
+ return ret;
+
+ /* synchronize channels */
+ ret = regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_1, AXI_DAC_SYNC);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static int axi_dac_ext_info_set(struct iio_backend *back, uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct axi_dac_state *st = iio_backend_get_priv(back);
+
+ switch (private) {
+ case AXI_DAC_FREQ_TONE_1:
+ case AXI_DAC_FREQ_TONE_2:
+ return axi_dac_frequency_set(st, chan, buf, len,
+ private - AXI_DAC_FREQ_TONE_1);
+ case AXI_DAC_SCALE_TONE_1:
+ case AXI_DAC_SCALE_TONE_2:
+ return axi_dac_scale_set(st, chan, buf, len,
+ private - AXI_DAC_SCALE_TONE_1);
+ case AXI_DAC_PHASE_TONE_1:
+ case AXI_DAC_PHASE_TONE_2:
+ return axi_dac_phase_set(st, chan, buf, len,
+ private - AXI_DAC_PHASE_TONE_2);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int axi_dac_ext_info_get(struct iio_backend *back, uintptr_t private,
+ const struct iio_chan_spec *chan, char *buf)
+{
+ struct axi_dac_state *st = iio_backend_get_priv(back);
+
+ switch (private) {
+ case AXI_DAC_FREQ_TONE_1:
+ case AXI_DAC_FREQ_TONE_2:
+ return axi_dac_frequency_get(st, chan, buf,
+ private - AXI_DAC_FREQ_TONE_1);
+ case AXI_DAC_SCALE_TONE_1:
+ case AXI_DAC_SCALE_TONE_2:
+ return axi_dac_scale_get(st, chan, buf,
+ private - AXI_DAC_SCALE_TONE_1);
+ case AXI_DAC_PHASE_TONE_1:
+ case AXI_DAC_PHASE_TONE_2:
+ return axi_dac_phase_get(st, chan, buf,
+ private - AXI_DAC_PHASE_TONE_1);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct iio_chan_spec_ext_info axi_dac_ext_info[] = {
+ IIO_BACKEND_EX_INFO("frequency0", IIO_SEPARATE, AXI_DAC_FREQ_TONE_1),
+ IIO_BACKEND_EX_INFO("frequency1", IIO_SEPARATE, AXI_DAC_FREQ_TONE_2),
+ IIO_BACKEND_EX_INFO("scale0", IIO_SEPARATE, AXI_DAC_SCALE_TONE_1),
+ IIO_BACKEND_EX_INFO("scale1", IIO_SEPARATE, AXI_DAC_SCALE_TONE_2),
+ IIO_BACKEND_EX_INFO("phase0", IIO_SEPARATE, AXI_DAC_PHASE_TONE_1),
+ IIO_BACKEND_EX_INFO("phase1", IIO_SEPARATE, AXI_DAC_PHASE_TONE_2),
+ {}
+};
+
+static int axi_dac_extend_chan(struct iio_backend *back,
+ struct iio_chan_spec *chan)
+{
+ struct axi_dac_state *st = iio_backend_get_priv(back);
+
+ if (chan->type != IIO_ALTVOLTAGE)
+ return -EINVAL;
+ if (st->reg_config & AXI_DDS_DISABLE)
+ /* nothing to extend */
+ return 0;
+
+ chan->ext_info = axi_dac_ext_info;
+
+ return 0;
+}
+
+static int axi_dac_data_source_set(struct iio_backend *back, unsigned int chan,
+ enum iio_backend_data_source data)
+{
+ struct axi_dac_state *st = iio_backend_get_priv(back);
+
+ switch (data) {
+ case IIO_BACKEND_INTERNAL_CONTINUOS_WAVE:
+ return regmap_update_bits(st->regmap,
+ AXI_DAC_REG_CHAN_CNTRL_7(chan),
+ AXI_DAC_DATA_SEL,
+ AXI_DAC_DATA_INTERNAL_TONE);
+ case IIO_BACKEND_EXTERNAL:
+ return regmap_update_bits(st->regmap,
+ AXI_DAC_REG_CHAN_CNTRL_7(chan),
+ AXI_DAC_DATA_SEL, AXI_DAC_DATA_DMA);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int axi_dac_set_sample_rate(struct iio_backend *back, unsigned int chan,
+ u64 sample_rate)
+{
+ struct axi_dac_state *st = iio_backend_get_priv(back);
+ unsigned int freq;
+ int ret, tone;
+
+ if (!sample_rate)
+ return -EINVAL;
+ if (st->reg_config & AXI_DDS_DISABLE)
+ /* sample_rate has no meaning if DDS is disabled */
+ return 0;
+
+ guard(mutex)(&st->lock);
+ /*
+ * If dac_clk is 0 then this must be the first time we're being notified
+ * about the interface sample rate. Hence, just update our internal
+ * variable and bail... If it's not 0, then we get the current DDS
+ * frequency (for the old rate) and update the registers for the new
+ * sample rate.
+ */
+ if (!st->dac_clk) {
+ st->dac_clk = sample_rate;
+ return 0;
+ }
+
+ for (tone = 0; tone <= AXI_DAC_FREQ_TONE_2; tone++) {
+ ret = __axi_dac_frequency_get(st, chan, tone, &freq);
+ if (ret)
+ return ret;
+
+ ret = __axi_dac_frequency_set(st, chan, sample_rate, tone, freq);
+ if (ret)
+ return ret;
+ }
+
+ st->dac_clk = sample_rate;
+
+ return 0;
+}
+
+static const struct iio_backend_ops axi_dac_generic = {
+ .enable = axi_dac_enable,
+ .disable = axi_dac_disable,
+ .request_buffer = axi_dac_request_buffer,
+ .free_buffer = axi_dac_free_buffer,
+ .extend_chan_spec = axi_dac_extend_chan,
+ .ext_info_set = axi_dac_ext_info_set,
+ .ext_info_get = axi_dac_ext_info_get,
+ .data_source_set = axi_dac_data_source_set,
+ .set_sample_rate = axi_dac_set_sample_rate,
+};
+
+static const struct regmap_config axi_dac_regmap_config = {
+ .val_bits = 32,
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x0800,
+};
+
+static int axi_dac_probe(struct platform_device *pdev)
+{
+ const unsigned int *expected_ver;
+ struct axi_dac_state *st;
+ void __iomem *base;
+ unsigned int ver;
+ struct clk *clk;
+ int ret;
+
+ st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ expected_ver = device_get_match_data(&pdev->dev);
+ if (!expected_ver)
+ return -ENODEV;
+
+ clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ st->dev = &pdev->dev;
+ st->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &axi_dac_regmap_config);
+ if (IS_ERR(st->regmap))
+ return PTR_ERR(st->regmap);
+
+ /*
+ * Force disable the core. Up to the frontend to enable us. And we can
+ * still read/write registers...
+ */
+ ret = regmap_write(st->regmap, AXI_DAC_REG_RSTN, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, ADI_AXI_REG_VERSION, &ver);
+ if (ret)
+ return ret;
+
+ if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(*expected_ver)) {
+ dev_err(&pdev->dev,
+ "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
+ ADI_AXI_PCORE_VER_MAJOR(*expected_ver),
+ ADI_AXI_PCORE_VER_MINOR(*expected_ver),
+ ADI_AXI_PCORE_VER_PATCH(*expected_ver),
+ ADI_AXI_PCORE_VER_MAJOR(ver),
+ ADI_AXI_PCORE_VER_MINOR(ver),
+ ADI_AXI_PCORE_VER_PATCH(ver));
+ return -ENODEV;
+ }
+
+ /* Let's get the core read only configuration */
+ ret = regmap_read(st->regmap, AXI_DAC_REG_CONFIG, &st->reg_config);
+ if (ret)
+ return ret;
+
+ /*
+ * In some designs, setting the R1_MODE bit to 0 (which is the default
+ * value) causes all channels of the frontend to be routed to the same
+ * DMA (so they are sampled together). This is for things like
+ * Multiple-Input and Multiple-Output (MIMO). As most of the times we
+ * want independent channels let's override the core's default value and
+ * set the R1_MODE bit.
+ */
+ ret = regmap_set_bits(st->regmap, AXI_DAC_REG_CNTRL_2, ADI_DAC_R1_MODE);
+ if (ret)
+ return ret;
+
+ mutex_init(&st->lock);
+ ret = devm_iio_backend_register(&pdev->dev, &axi_dac_generic, st);
+ if (ret)
+ return ret;
+
+ dev_info(&pdev->dev, "AXI DAC IP core (%d.%.2d.%c) probed\n",
+ ADI_AXI_PCORE_VER_MAJOR(ver),
+ ADI_AXI_PCORE_VER_MINOR(ver),
+ ADI_AXI_PCORE_VER_PATCH(ver));
+
+ return 0;
+}
+
+static unsigned int axi_dac_9_1_b_info = ADI_AXI_PCORE_VER(9, 1, 'b');
+
+static const struct of_device_id axi_dac_of_match[] = {
+ { .compatible = "adi,axi-dac-9.1.b", .data = &axi_dac_9_1_b_info },
+ {}
+};
+MODULE_DEVICE_TABLE(of, axi_dac_of_match);
+
+static struct platform_driver axi_dac_driver = {
+ .driver = {
+ .name = "adi-axi-dac",
+ .of_match_table = axi_dac_of_match,
+ },
+ .probe = axi_dac_probe,
+};
+module_platform_driver(axi_dac_driver);
+
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("Analog Devices Generic AXI DAC IP core driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_DMAENGINE_BUFFER);
+MODULE_IMPORT_NS(IIO_BACKEND);
diff --git a/drivers/iio/dac/ltc2688.c b/drivers/iio/dac/ltc2688.c
index fc8eb53c65be3b..c4b1ba30f93545 100644
--- a/drivers/iio/dac/ltc2688.c
+++ b/drivers/iio/dac/ltc2688.c
@@ -746,26 +746,21 @@ static int ltc2688_span_lookup(const struct ltc2688_state *st, int min, int max)
static int ltc2688_channel_config(struct ltc2688_state *st)
{
struct device *dev = &st->spi->dev;
- struct fwnode_handle *child;
u32 reg, clk_input, val, tmp[2];
int ret, span;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct ltc2688_chan *chan;
ret = fwnode_property_read_u32(child, "reg", &reg);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return dev_err_probe(dev, ret,
"Failed to get reg property\n");
- }
- if (reg >= LTC2688_DAC_CHANNELS) {
- fwnode_handle_put(child);
+ if (reg >= LTC2688_DAC_CHANNELS)
return dev_err_probe(dev, -EINVAL,
"reg bigger than: %d\n",
LTC2688_DAC_CHANNELS);
- }
val = 0;
chan = &st->channels[reg];
@@ -786,12 +781,10 @@ static int ltc2688_channel_config(struct ltc2688_state *st)
if (!ret) {
span = ltc2688_span_lookup(st, (int)tmp[0] / 1000,
tmp[1] / 1000);
- if (span < 0) {
- fwnode_handle_put(child);
- return dev_err_probe(dev, -EINVAL,
+ if (span < 0)
+ return dev_err_probe(dev, span,
"output range not valid:[%d %d]\n",
tmp[0], tmp[1]);
- }
val |= FIELD_PREP(LTC2688_CH_SPAN_MSK, span);
}
@@ -800,17 +793,14 @@ static int ltc2688_channel_config(struct ltc2688_state *st)
&clk_input);
if (!ret) {
if (clk_input >= LTC2688_CH_TGP_MAX) {
- fwnode_handle_put(child);
return dev_err_probe(dev, -EINVAL,
"toggle-dither-input inv value(%d)\n",
clk_input);
}
ret = ltc2688_tgp_clk_setup(st, chan, child, clk_input);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return ret;
- }
/*
* 0 means software toggle which is the default mode.
@@ -844,11 +834,9 @@ static int ltc2688_channel_config(struct ltc2688_state *st)
ret = regmap_write(st->regmap, LTC2688_CMD_CH_SETTING(reg),
val);
- if (ret) {
- fwnode_handle_put(child);
- return dev_err_probe(dev, -EINVAL,
+ if (ret)
+ return dev_err_probe(dev, ret,
"failed to set chan settings\n");
- }
}
return 0;
diff --git a/drivers/iio/dac/ti-dac5571.c b/drivers/iio/dac/ti-dac5571.c
index efb1269a77c1e5..c5162b72951af6 100644
--- a/drivers/iio/dac/ti-dac5571.c
+++ b/drivers/iio/dac/ti-dac5571.c
@@ -13,6 +13,7 @@
* https://www.ti.com/lit/ds/symlink/dac5573.pdf
* https://www.ti.com/lit/ds/symlink/dac6573.pdf
* https://www.ti.com/lit/ds/symlink/dac7573.pdf
+ * https://www.ti.com/lit/ds/symlink/dac081c081.pdf
* https://www.ti.com/lit/ds/symlink/dac121c081.pdf
*/
@@ -386,6 +387,7 @@ static void dac5571_remove(struct i2c_client *i2c)
}
static const struct of_device_id dac5571_of_id[] = {
+ {.compatible = "ti,dac081c081", .data = &dac5571_spec[single_8bit] },
{.compatible = "ti,dac121c081", .data = &dac5571_spec[single_12bit] },
{.compatible = "ti,dac5571", .data = &dac5571_spec[single_8bit] },
{.compatible = "ti,dac6571", .data = &dac5571_spec[single_10bit] },
@@ -401,6 +403,7 @@ static const struct of_device_id dac5571_of_id[] = {
MODULE_DEVICE_TABLE(of, dac5571_of_id);
static const struct i2c_device_id dac5571_id[] = {
+ {"dac081c081", (kernel_ulong_t)&dac5571_spec[single_8bit] },
{"dac121c081", (kernel_ulong_t)&dac5571_spec[single_12bit] },
{"dac5571", (kernel_ulong_t)&dac5571_spec[single_8bit] },
{"dac6571", (kernel_ulong_t)&dac5571_spec[single_10bit] },
diff --git a/drivers/iio/frequency/admfm2000.c b/drivers/iio/frequency/admfm2000.c
index c34d79e55a7c58..b2263b9afeda33 100644
--- a/drivers/iio/frequency/admfm2000.c
+++ b/drivers/iio/frequency/admfm2000.c
@@ -160,26 +160,21 @@ static int admfm2000_channel_config(struct admfm2000_state *st,
{
struct platform_device *pdev = to_platform_device(indio_dev->dev.parent);
struct device *dev = &pdev->dev;
- struct fwnode_handle *child;
struct gpio_desc **dsa;
struct gpio_desc **sw;
int ret, i;
bool mode;
u32 reg;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
ret = fwnode_property_read_u32(child, "reg", &reg);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return dev_err_probe(dev, ret,
"Failed to get reg property\n");
- }
- if (reg >= indio_dev->num_channels) {
- fwnode_handle_put(child);
+ if (reg >= indio_dev->num_channels)
return dev_err_probe(dev, -EINVAL, "reg bigger than: %d\n",
indio_dev->num_channels);
- }
if (fwnode_property_present(child, "adi,mixer-mode"))
mode = ADMFM2000_MIXER_MODE;
@@ -196,36 +191,29 @@ static int admfm2000_channel_config(struct admfm2000_state *st,
dsa = st->dsa2_gpios;
break;
default:
- fwnode_handle_put(child);
return -EINVAL;
}
for (i = 0; i < ADMFM2000_MODE_GPIOS; i++) {
sw[i] = devm_fwnode_gpiod_get_index(dev, child, "switch",
i, GPIOD_OUT_LOW, NULL);
- if (IS_ERR(sw[i])) {
- fwnode_handle_put(child);
+ if (IS_ERR(sw[i]))
return dev_err_probe(dev, PTR_ERR(sw[i]),
"Failed to get gpios\n");
- }
}
for (i = 0; i < ADMFM2000_DSA_GPIOS; i++) {
dsa[i] = devm_fwnode_gpiod_get_index(dev, child,
"attenuation", i,
GPIOD_OUT_LOW, NULL);
- if (IS_ERR(dsa[i])) {
- fwnode_handle_put(child);
+ if (IS_ERR(dsa[i]))
return dev_err_probe(dev, PTR_ERR(dsa[i]),
"Failed to get gpios\n");
- }
}
ret = admfm2000_mode(indio_dev, reg, mode);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return ret;
- }
}
return 0;
diff --git a/drivers/iio/health/max30102.c b/drivers/iio/health/max30102.c
index 37e619827e8a11..6616729af5b779 100644
--- a/drivers/iio/health/max30102.c
+++ b/drivers/iio/health/max30102.c
@@ -613,6 +613,7 @@ static void max30102_remove(struct i2c_client *client)
}
static const struct i2c_device_id max30102_id[] = {
+ { "max30101", max30105 },
{ "max30102", max30102 },
{ "max30105", max30105 },
{}
@@ -620,6 +621,7 @@ static const struct i2c_device_id max30102_id[] = {
MODULE_DEVICE_TABLE(i2c, max30102_id);
static const struct of_device_id max30102_dt_ids[] = {
+ { .compatible = "maxim,max30101" },
{ .compatible = "maxim,max30102" },
{ .compatible = "maxim,max30105" },
{ }
diff --git a/drivers/iio/humidity/hdc3020.c b/drivers/iio/humidity/hdc3020.c
index 1e5d0d4797b16e..cdc4789213ba70 100644
--- a/drivers/iio/humidity/hdc3020.c
+++ b/drivers/iio/humidity/hdc3020.c
@@ -15,11 +15,14 @@
#include <linux/cleanup.h>
#include <linux/crc8.h>
#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/regulator/consumer.h>
#include <linux/units.h>
#include <asm/unaligned.h>
@@ -68,6 +71,8 @@
struct hdc3020_data {
struct i2c_client *client;
+ struct gpio_desc *reset_gpio;
+ struct regulator *vdd_supply;
/*
* Ensure that the sensor configuration (currently only heater is
* supported) will not be changed during the process of reading
@@ -551,9 +556,53 @@ static const struct iio_info hdc3020_info = {
.write_event_value = hdc3020_write_thresh,
};
-static void hdc3020_stop(void *data)
+static int hdc3020_power_off(struct hdc3020_data *data)
{
- hdc3020_exec_cmd((struct hdc3020_data *)data, HDC3020_EXIT_AUTO);
+ hdc3020_exec_cmd(data, HDC3020_EXIT_AUTO);
+
+ if (data->reset_gpio)
+ gpiod_set_value_cansleep(data->reset_gpio, 1);
+
+ return regulator_disable(data->vdd_supply);
+}
+
+static int hdc3020_power_on(struct hdc3020_data *data)
+{
+ int ret;
+
+ ret = regulator_enable(data->vdd_supply);
+ if (ret)
+ return ret;
+
+ fsleep(5000);
+
+ if (data->reset_gpio) {
+ gpiod_set_value_cansleep(data->reset_gpio, 0);
+ fsleep(3000);
+ }
+
+ if (data->client->irq) {
+ /*
+ * The alert output is activated by default upon power up,
+ * hardware reset, and soft reset. Clear the status register.
+ */
+ ret = hdc3020_exec_cmd(data, HDC3020_S_STATUS);
+ if (ret) {
+ hdc3020_power_off(data);
+ return ret;
+ }
+ }
+
+ ret = hdc3020_exec_cmd(data, HDC3020_S_AUTO_10HZ_MOD0);
+ if (ret)
+ hdc3020_power_off(data);
+
+ return ret;
+}
+
+static void hdc3020_exit(void *data)
+{
+ hdc3020_power_off(data);
}
static int hdc3020_probe(struct i2c_client *client)
@@ -569,6 +618,8 @@ static int hdc3020_probe(struct i2c_client *client)
if (!indio_dev)
return -ENOMEM;
+ dev_set_drvdata(&client->dev, indio_dev);
+
data = iio_priv(indio_dev);
data->client = client;
mutex_init(&data->lock);
@@ -580,6 +631,26 @@ static int hdc3020_probe(struct i2c_client *client)
indio_dev->info = &hdc3020_info;
indio_dev->channels = hdc3020_channels;
indio_dev->num_channels = ARRAY_SIZE(hdc3020_channels);
+
+ data->vdd_supply = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(data->vdd_supply))
+ return dev_err_probe(&client->dev, PTR_ERR(data->vdd_supply),
+ "Unable to get VDD regulator\n");
+
+ data->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(data->reset_gpio))
+ return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio),
+ "Cannot get reset GPIO\n");
+
+ ret = hdc3020_power_on(data);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Power on failed\n");
+
+ ret = devm_add_action_or_reset(&data->client->dev, hdc3020_exit, data);
+ if (ret)
+ return ret;
+
if (client->irq) {
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, hdc3020_interrupt_handler,
@@ -588,25 +659,8 @@ static int hdc3020_probe(struct i2c_client *client)
if (ret)
return dev_err_probe(&client->dev, ret,
"Failed to request IRQ\n");
-
- /*
- * The alert output is activated by default upon power up,
- * hardware reset, and soft reset. Clear the status register.
- */
- ret = hdc3020_exec_cmd(data, HDC3020_S_STATUS);
- if (ret)
- return ret;
}
- ret = hdc3020_exec_cmd(data, HDC3020_S_AUTO_10HZ_MOD0);
- if (ret)
- return dev_err_probe(&client->dev, ret,
- "Unable to set up measurement\n");
-
- ret = devm_add_action_or_reset(&data->client->dev, hdc3020_stop, data);
- if (ret)
- return ret;
-
ret = devm_iio_device_register(&data->client->dev, indio_dev);
if (ret)
return dev_err_probe(&client->dev, ret, "Failed to add device");
@@ -614,6 +668,24 @@ static int hdc3020_probe(struct i2c_client *client)
return 0;
}
+static int hdc3020_suspend(struct device *dev)
+{
+ struct iio_dev *iio_dev = dev_get_drvdata(dev);
+ struct hdc3020_data *data = iio_priv(iio_dev);
+
+ return hdc3020_power_off(data);
+}
+
+static int hdc3020_resume(struct device *dev)
+{
+ struct iio_dev *iio_dev = dev_get_drvdata(dev);
+ struct hdc3020_data *data = iio_priv(iio_dev);
+
+ return hdc3020_power_on(data);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(hdc3020_pm_ops, hdc3020_suspend, hdc3020_resume);
+
static const struct i2c_device_id hdc3020_id[] = {
{ "hdc3020" },
{ "hdc3021" },
@@ -633,6 +705,7 @@ MODULE_DEVICE_TABLE(of, hdc3020_dt_ids);
static struct i2c_driver hdc3020_driver = {
.driver = {
.name = "hdc3020",
+ .pm = pm_sleep_ptr(&hdc3020_pm_ops),
.of_match_table = hdc3020_dt_ids,
},
.probe = hdc3020_probe,
diff --git a/drivers/iio/humidity/hts221_core.c b/drivers/iio/humidity/hts221_core.c
index 2a413da87b760f..87627d116eff05 100644
--- a/drivers/iio/humidity/hts221_core.c
+++ b/drivers/iio/humidity/hts221_core.c
@@ -573,7 +573,7 @@ int hts221_probe(struct device *dev, int irq, const char *name,
if (!iio_dev)
return -ENOMEM;
- dev_set_drvdata(dev, (void *)iio_dev);
+ dev_set_drvdata(dev, iio_dev);
hw = iio_priv(iio_dev);
hw->name = name;
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
index 0e290c807b0f91..0566340b266049 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h
@@ -22,6 +22,7 @@ enum inv_icm42600_chip {
INV_CHIP_ICM42602,
INV_CHIP_ICM42605,
INV_CHIP_ICM42622,
+ INV_CHIP_ICM42688,
INV_CHIP_ICM42631,
INV_CHIP_NB,
};
@@ -304,6 +305,7 @@ struct inv_icm42600_state {
#define INV_ICM42600_WHOAMI_ICM42602 0x41
#define INV_ICM42600_WHOAMI_ICM42605 0x42
#define INV_ICM42600_WHOAMI_ICM42622 0x46
+#define INV_ICM42600_WHOAMI_ICM42688 0x47
#define INV_ICM42600_WHOAMI_ICM42631 0x5C
/* User bank 1 (MSB 0x10) */
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
index a5e81906e37ecf..82e0a2e2ad706c 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c
@@ -87,6 +87,11 @@ static const struct inv_icm42600_hw inv_icm42600_hw[INV_CHIP_NB] = {
.name = "icm42622",
.conf = &inv_icm42600_default_conf,
},
+ [INV_CHIP_ICM42688] = {
+ .whoami = INV_ICM42600_WHOAMI_ICM42688,
+ .name = "icm42688",
+ .conf = &inv_icm42600_default_conf,
+ },
[INV_CHIP_ICM42631] = {
.whoami = INV_ICM42600_WHOAMI_ICM42631,
.name = "icm42631",
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
index 1af559403ba66d..ebb28f84ba9853 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_i2c.c
@@ -85,6 +85,9 @@ static const struct of_device_id inv_icm42600_of_matches[] = {
.compatible = "invensense,icm42622",
.data = (void *)INV_CHIP_ICM42622,
}, {
+ .compatible = "invensense,icm42688",
+ .data = (void *)INV_CHIP_ICM42688,
+ }, {
.compatible = "invensense,icm42631",
.data = (void *)INV_CHIP_ICM42631,
},
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
index 6be4ac79493794..50217a10e0bbf3 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_spi.c
@@ -81,6 +81,9 @@ static const struct of_device_id inv_icm42600_of_matches[] = {
.compatible = "invensense,icm42622",
.data = (void *)INV_CHIP_ICM42622,
}, {
+ .compatible = "invensense,icm42688",
+ .data = (void *)INV_CHIP_ICM42688,
+ }, {
.compatible = "invensense,icm42631",
.data = (void *)INV_CHIP_ICM42631,
},
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
index 0e94e5335e9371..14d95f34e981c8 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
@@ -15,6 +15,8 @@
#include <linux/acpi.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
@@ -287,7 +289,7 @@ static const struct inv_mpu6050_hw hw_info[] = {
};
static int inv_mpu6050_pwr_mgmt_1_write(struct inv_mpu6050_state *st, bool sleep,
- int clock, int temp_dis)
+ bool cycle, int clock, int temp_dis)
{
u8 val;
@@ -301,6 +303,8 @@ static int inv_mpu6050_pwr_mgmt_1_write(struct inv_mpu6050_state *st, bool sleep
val |= INV_MPU6050_BIT_TEMP_DIS;
if (sleep)
val |= INV_MPU6050_BIT_SLEEP;
+ if (cycle)
+ val |= INV_MPU6050_BIT_CYCLE;
dev_dbg(regmap_get_device(st->map), "pwr_mgmt_1: 0x%x\n", val);
return regmap_write(st->map, st->reg->pwr_mgmt_1, val);
@@ -316,7 +320,7 @@ static int inv_mpu6050_clock_switch(struct inv_mpu6050_state *st,
case INV_MPU6000:
case INV_MPU9150:
/* old chips: switch clock manually */
- ret = inv_mpu6050_pwr_mgmt_1_write(st, false, clock, -1);
+ ret = inv_mpu6050_pwr_mgmt_1_write(st, false, false, clock, -1);
if (ret)
return ret;
st->chip_config.clk = clock;
@@ -332,7 +336,7 @@ static int inv_mpu6050_clock_switch(struct inv_mpu6050_state *st,
int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en,
unsigned int mask)
{
- unsigned int sleep;
+ unsigned int sleep, val;
u8 pwr_mgmt2, user_ctrl;
int ret;
@@ -345,12 +349,20 @@ int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en,
mask &= ~INV_MPU6050_SENSOR_TEMP;
if (mask & INV_MPU6050_SENSOR_MAGN && en == st->chip_config.magn_en)
mask &= ~INV_MPU6050_SENSOR_MAGN;
+ if (mask & INV_MPU6050_SENSOR_WOM && en == st->chip_config.wom_en)
+ mask &= ~INV_MPU6050_SENSOR_WOM;
+
+ /* force accel on if WoM is on and not going off */
+ if (!en && (mask & INV_MPU6050_SENSOR_ACCL) && st->chip_config.wom_en &&
+ !(mask & INV_MPU6050_SENSOR_WOM))
+ mask &= ~INV_MPU6050_SENSOR_ACCL;
+
if (mask == 0)
return 0;
/* turn on/off temperature sensor */
if (mask & INV_MPU6050_SENSOR_TEMP) {
- ret = inv_mpu6050_pwr_mgmt_1_write(st, false, -1, !en);
+ ret = inv_mpu6050_pwr_mgmt_1_write(st, false, false, -1, !en);
if (ret)
return ret;
st->chip_config.temp_en = en;
@@ -439,6 +451,16 @@ int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en,
}
}
+ /* enable/disable accel intelligence control */
+ if (mask & INV_MPU6050_SENSOR_WOM) {
+ val = en ? INV_MPU6500_BIT_ACCEL_INTEL_EN |
+ INV_MPU6500_BIT_ACCEL_INTEL_MODE : 0;
+ ret = regmap_write(st->map, INV_MPU6500_REG_ACCEL_INTEL_CTRL, val);
+ if (ret)
+ return ret;
+ st->chip_config.wom_en = en;
+ }
+
return 0;
}
@@ -447,7 +469,7 @@ static int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st,
{
int result;
- result = inv_mpu6050_pwr_mgmt_1_write(st, !power_on, -1, -1);
+ result = inv_mpu6050_pwr_mgmt_1_write(st, !power_on, false, -1, -1);
if (result)
return result;
@@ -477,22 +499,9 @@ static int inv_mpu6050_set_gyro_fsr(struct inv_mpu6050_state *st,
return regmap_write(st->map, st->reg->gyro_config, data);
}
-/*
- * inv_mpu6050_set_lpf_regs() - set low pass filter registers, chip dependent
- *
- * MPU60xx/MPU9150 use only 1 register for accelerometer + gyroscope
- * MPU6500 and above have a dedicated register for accelerometer
- */
-static int inv_mpu6050_set_lpf_regs(struct inv_mpu6050_state *st,
- enum inv_mpu6050_filter_e val)
+static int inv_mpu6050_set_accel_lpf_regs(struct inv_mpu6050_state *st,
+ enum inv_mpu6050_filter_e val)
{
- int result;
-
- result = regmap_write(st->map, st->reg->lpf, val);
- if (result)
- return result;
-
- /* set accel lpf */
switch (st->chip_type) {
case INV_MPU6050:
case INV_MPU6000:
@@ -512,6 +521,25 @@ static int inv_mpu6050_set_lpf_regs(struct inv_mpu6050_state *st,
}
/*
+ * inv_mpu6050_set_lpf_regs() - set low pass filter registers, chip dependent
+ *
+ * MPU60xx/MPU9150 use only 1 register for accelerometer + gyroscope
+ * MPU6500 and above have a dedicated register for accelerometer
+ */
+static int inv_mpu6050_set_lpf_regs(struct inv_mpu6050_state *st,
+ enum inv_mpu6050_filter_e val)
+{
+ int result;
+
+ result = regmap_write(st->map, st->reg->lpf, val);
+ if (result)
+ return result;
+
+ /* set accel lpf */
+ return inv_mpu6050_set_accel_lpf_regs(st, val);
+}
+
+/*
* inv_mpu6050_init_config() - Initialize hardware, disable FIFO.
*
* Initial configuration:
@@ -893,6 +921,317 @@ error_write_raw_unlock:
return result;
}
+static u64 inv_mpu6050_convert_wom_to_roc(unsigned int threshold, unsigned int freq_div)
+{
+ /* 4mg per LSB converted in m/s² in micro (1000000) */
+ const unsigned int convert = 4U * 9807U;
+ u64 value;
+
+ value = threshold * convert;
+
+ /* compute the differential by multiplying by the frequency */
+ return div_u64(value * INV_MPU6050_INTERNAL_FREQ_HZ, freq_div);
+}
+
+static unsigned int inv_mpu6050_convert_roc_to_wom(u64 roc, unsigned int freq_div)
+{
+ /* 4mg per LSB converted in m/s² in micro (1000000) */
+ const unsigned int convert = 4U * 9807U;
+ u64 value;
+
+ /* return 0 only if roc is 0 */
+ if (roc == 0)
+ return 0;
+
+ value = div_u64(roc * freq_div, convert * INV_MPU6050_INTERNAL_FREQ_HZ);
+
+ /* limit value to 8 bits and prevent 0 */
+ return min(255, max(1, value));
+}
+
+static int inv_mpu6050_set_wom_int(struct inv_mpu6050_state *st, bool on)
+{
+ unsigned int reg_val, val;
+
+ switch (st->chip_type) {
+ case INV_MPU6050:
+ case INV_MPU6500:
+ case INV_MPU6515:
+ case INV_MPU6880:
+ case INV_MPU6000:
+ case INV_MPU9150:
+ case INV_MPU9250:
+ case INV_MPU9255:
+ reg_val = INV_MPU6500_BIT_WOM_INT_EN;
+ break;
+ default:
+ reg_val = INV_ICM20608_BIT_WOM_INT_EN;
+ break;
+ }
+
+ val = on ? reg_val : 0;
+
+ return regmap_update_bits(st->map, st->reg->int_enable, reg_val, val);
+}
+
+static int inv_mpu6050_set_wom_threshold(struct inv_mpu6050_state *st, u64 value,
+ unsigned int freq_div)
+{
+ unsigned int threshold;
+ int result;
+
+ /* convert roc to wom threshold and convert back to handle clipping */
+ threshold = inv_mpu6050_convert_roc_to_wom(value, freq_div);
+ value = inv_mpu6050_convert_wom_to_roc(threshold, freq_div);
+
+ dev_dbg(regmap_get_device(st->map), "wom_threshold: 0x%x\n", threshold);
+
+ switch (st->chip_type) {
+ case INV_ICM20609:
+ case INV_ICM20689:
+ case INV_ICM20600:
+ case INV_ICM20602:
+ case INV_ICM20690:
+ st->data[0] = threshold;
+ st->data[1] = threshold;
+ st->data[2] = threshold;
+ result = regmap_bulk_write(st->map, INV_ICM20609_REG_ACCEL_WOM_X_THR,
+ st->data, 3);
+ break;
+ default:
+ result = regmap_write(st->map, INV_MPU6500_REG_WOM_THRESHOLD, threshold);
+ break;
+ }
+ if (result)
+ return result;
+
+ st->chip_config.roc_threshold = value;
+
+ return 0;
+}
+
+static int inv_mpu6050_set_lp_odr(struct inv_mpu6050_state *st, unsigned int freq_div,
+ unsigned int *lp_div)
+{
+ static const unsigned int freq_dividers[] = {2, 4, 8, 16, 32, 64, 128, 256};
+ static const unsigned int reg_values[] = {
+ INV_MPU6050_LPOSC_500HZ, INV_MPU6050_LPOSC_250HZ,
+ INV_MPU6050_LPOSC_125HZ, INV_MPU6050_LPOSC_62HZ,
+ INV_MPU6050_LPOSC_31HZ, INV_MPU6050_LPOSC_16HZ,
+ INV_MPU6050_LPOSC_8HZ, INV_MPU6050_LPOSC_4HZ,
+ };
+ unsigned int val, i;
+
+ switch (st->chip_type) {
+ case INV_ICM20609:
+ case INV_ICM20689:
+ case INV_ICM20600:
+ case INV_ICM20602:
+ case INV_ICM20690:
+ /* nothing to do */
+ *lp_div = INV_MPU6050_FREQ_DIVIDER(st);
+ return 0;
+ default:
+ break;
+ }
+
+ /* found the nearest superior frequency divider */
+ i = ARRAY_SIZE(reg_values) - 1;
+ val = reg_values[i];
+ *lp_div = freq_dividers[i];
+ for (i = 0; i < ARRAY_SIZE(freq_dividers); ++i) {
+ if (freq_div <= freq_dividers[i]) {
+ val = reg_values[i];
+ *lp_div = freq_dividers[i];
+ break;
+ }
+ }
+
+ dev_dbg(regmap_get_device(st->map), "lp_odr: 0x%x\n", val);
+ return regmap_write(st->map, INV_MPU6500_REG_LP_ODR, val);
+}
+
+static int inv_mpu6050_set_wom_lp(struct inv_mpu6050_state *st, bool on)
+{
+ unsigned int lp_div;
+ int result;
+
+ if (on) {
+ /* set low power ODR */
+ result = inv_mpu6050_set_lp_odr(st, INV_MPU6050_FREQ_DIVIDER(st), &lp_div);
+ if (result)
+ return result;
+ /* disable accel low pass filter */
+ result = inv_mpu6050_set_accel_lpf_regs(st, INV_MPU6050_FILTER_NOLPF);
+ if (result)
+ return result;
+ /* update wom threshold with new low-power frequency divider */
+ result = inv_mpu6050_set_wom_threshold(st, st->chip_config.roc_threshold, lp_div);
+ if (result)
+ return result;
+ /* set cycle mode */
+ result = inv_mpu6050_pwr_mgmt_1_write(st, false, true, -1, -1);
+ } else {
+ /* disable cycle mode */
+ result = inv_mpu6050_pwr_mgmt_1_write(st, false, false, -1, -1);
+ if (result)
+ return result;
+ /* restore wom threshold */
+ result = inv_mpu6050_set_wom_threshold(st, st->chip_config.roc_threshold,
+ INV_MPU6050_FREQ_DIVIDER(st));
+ if (result)
+ return result;
+ /* restore accel low pass filter */
+ result = inv_mpu6050_set_accel_lpf_regs(st, st->chip_config.lpf);
+ }
+
+ return result;
+}
+
+static int inv_mpu6050_enable_wom(struct inv_mpu6050_state *st, bool en)
+{
+ struct device *pdev = regmap_get_device(st->map);
+ unsigned int mask;
+ int result;
+
+ if (en) {
+ result = pm_runtime_resume_and_get(pdev);
+ if (result)
+ return result;
+
+ mask = INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_WOM;
+ result = inv_mpu6050_switch_engine(st, true, mask);
+ if (result)
+ goto error_suspend;
+
+ result = inv_mpu6050_set_wom_int(st, true);
+ if (result)
+ goto error_suspend;
+ } else {
+ result = inv_mpu6050_set_wom_int(st, false);
+ if (result)
+ dev_err(pdev, "error %d disabling WoM interrupt bit", result);
+
+ /* disable only WoM and let accel be disabled by autosuspend */
+ result = inv_mpu6050_switch_engine(st, false, INV_MPU6050_SENSOR_WOM);
+ if (result) {
+ dev_err(pdev, "error %d disabling WoM force off", result);
+ /* force WoM off */
+ st->chip_config.wom_en = false;
+ }
+
+ pm_runtime_mark_last_busy(pdev);
+ pm_runtime_put_autosuspend(pdev);
+ }
+
+ return result;
+
+error_suspend:
+ pm_runtime_mark_last_busy(pdev);
+ pm_runtime_put_autosuspend(pdev);
+ return result;
+}
+
+static int inv_mpu6050_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct inv_mpu6050_state *st = iio_priv(indio_dev);
+
+ /* support only WoM (accel roc rising) event */
+ if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC ||
+ dir != IIO_EV_DIR_RISING)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ return st->chip_config.wom_en ? 1 : 0;
+}
+
+static int inv_mpu6050_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ int state)
+{
+ struct inv_mpu6050_state *st = iio_priv(indio_dev);
+ int enable;
+
+ /* support only WoM (accel roc rising) event */
+ if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC ||
+ dir != IIO_EV_DIR_RISING)
+ return -EINVAL;
+
+ enable = !!state;
+
+ guard(mutex)(&st->lock);
+
+ if (st->chip_config.wom_en == enable)
+ return 0;
+
+ return inv_mpu6050_enable_wom(st, enable);
+}
+
+static int inv_mpu6050_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int *val, int *val2)
+{
+ struct inv_mpu6050_state *st = iio_priv(indio_dev);
+ u32 rem;
+
+ /* support only WoM (accel roc rising) event value */
+ if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC ||
+ dir != IIO_EV_DIR_RISING || info != IIO_EV_INFO_VALUE)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ /* return value in micro */
+ *val = div_u64_rem(st->chip_config.roc_threshold, 1000000U, &rem);
+ *val2 = rem;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int inv_mpu6050_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info,
+ int val, int val2)
+{
+ struct inv_mpu6050_state *st = iio_priv(indio_dev);
+ struct device *pdev = regmap_get_device(st->map);
+ u64 value;
+ int result;
+
+ /* support only WoM (accel roc rising) event value */
+ if (chan->type != IIO_ACCEL || type != IIO_EV_TYPE_ROC ||
+ dir != IIO_EV_DIR_RISING || info != IIO_EV_INFO_VALUE)
+ return -EINVAL;
+
+ if (val < 0 || val2 < 0)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ result = pm_runtime_resume_and_get(pdev);
+ if (result)
+ return result;
+
+ value = (u64)val * 1000000ULL + (u64)val2;
+ result = inv_mpu6050_set_wom_threshold(st, value, INV_MPU6050_FREQ_DIVIDER(st));
+
+ pm_runtime_mark_last_busy(pdev);
+ pm_runtime_put_autosuspend(pdev);
+
+ return result;
+}
+
/*
* inv_mpu6050_set_lpf() - set low pass filer based on fifo rate.
*
@@ -989,6 +1328,12 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr,
if (result)
goto fifo_rate_fail_power_off;
+ /* update wom threshold since roc is dependent on sampling frequency */
+ result = inv_mpu6050_set_wom_threshold(st, st->chip_config.roc_threshold,
+ INV_MPU6050_FREQ_DIVIDER(st));
+ if (result)
+ goto fifo_rate_fail_power_off;
+
pm_runtime_mark_last_busy(pdev);
fifo_rate_fail_power_off:
pm_runtime_put_autosuspend(pdev);
@@ -1089,6 +1434,15 @@ static const struct iio_chan_spec_ext_info inv_ext_info[] = {
{ }
};
+static const struct iio_event_spec inv_wom_events[] = {
+ {
+ .type = IIO_EV_TYPE_ROC,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE),
+ },
+};
+
#define INV_MPU6050_CHAN(_type, _channel2, _index) \
{ \
.type = _type, \
@@ -1124,7 +1478,31 @@ static const struct iio_chan_spec_ext_info inv_ext_info[] = {
}, \
}
-static const struct iio_chan_spec inv_mpu_channels[] = {
+#define INV_MPU6050_EVENT_CHAN(_type, _channel2, _events, _events_nb) \
+{ \
+ .type = _type, \
+ .modified = 1, \
+ .channel2 = _channel2, \
+ .event_spec = _events, \
+ .num_event_specs = _events_nb, \
+ .scan_index = -1, \
+}
+
+static const struct iio_chan_spec inv_mpu6050_channels[] = {
+ IIO_CHAN_SOFT_TIMESTAMP(INV_MPU6050_SCAN_TIMESTAMP),
+
+ INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP),
+
+ INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_X, INV_MPU6050_SCAN_GYRO_X),
+ INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Y, INV_MPU6050_SCAN_GYRO_Y),
+ INV_MPU6050_CHAN(IIO_ANGL_VEL, IIO_MOD_Z, INV_MPU6050_SCAN_GYRO_Z),
+
+ INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X),
+ INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y),
+ INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z),
+};
+
+static const struct iio_chan_spec inv_mpu6500_channels[] = {
IIO_CHAN_SOFT_TIMESTAMP(INV_MPU6050_SCAN_TIMESTAMP),
INV_MPU6050_TEMP_CHAN(INV_MPU6050_SCAN_TEMP),
@@ -1136,6 +1514,9 @@ static const struct iio_chan_spec inv_mpu_channels[] = {
INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_X, INV_MPU6050_SCAN_ACCL_X),
INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Y, INV_MPU6050_SCAN_ACCL_Y),
INV_MPU6050_CHAN(IIO_ACCEL, IIO_MOD_Z, INV_MPU6050_SCAN_ACCL_Z),
+
+ INV_MPU6050_EVENT_CHAN(IIO_ACCEL, IIO_MOD_X_OR_Y_OR_Z,
+ inv_wom_events, ARRAY_SIZE(inv_wom_events)),
};
#define INV_MPU6050_SCAN_MASK_3AXIS_ACCEL \
@@ -1326,6 +1707,10 @@ static const struct iio_info mpu_info = {
.write_raw = &inv_mpu6050_write_raw,
.write_raw_get_fmt = &inv_write_raw_get_fmt,
.attrs = &inv_attribute_group,
+ .read_event_config = inv_mpu6050_read_event_config,
+ .write_event_config = inv_mpu6050_write_event_config,
+ .read_event_value = inv_mpu6050_read_event_value,
+ .write_event_value = inv_mpu6050_write_event_value,
.validate_trigger = inv_mpu6050_validate_trigger,
.debugfs_reg_access = &inv_mpu6050_reg_access,
};
@@ -1537,6 +1922,7 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name,
irq_type);
return -EINVAL;
}
+ device_set_wakeup_capable(dev, true);
st->vdd_supply = devm_regulator_get(dev, "vdd");
if (IS_ERR(st->vdd_supply))
@@ -1613,6 +1999,12 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name,
return result;
switch (chip_type) {
+ case INV_MPU6000:
+ case INV_MPU6050:
+ indio_dev->channels = inv_mpu6050_channels;
+ indio_dev->num_channels = ARRAY_SIZE(inv_mpu6050_channels);
+ indio_dev->available_scan_masks = inv_mpu_scan_masks;
+ break;
case INV_MPU9150:
indio_dev->channels = inv_mpu9150_channels;
indio_dev->num_channels = ARRAY_SIZE(inv_mpu9150_channels);
@@ -1626,13 +2018,13 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name,
break;
case INV_ICM20600:
case INV_ICM20602:
- indio_dev->channels = inv_mpu_channels;
- indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels);
+ indio_dev->channels = inv_mpu6500_channels;
+ indio_dev->num_channels = ARRAY_SIZE(inv_mpu6500_channels);
indio_dev->available_scan_masks = inv_icm20602_scan_masks;
break;
default:
- indio_dev->channels = inv_mpu_channels;
- indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels);
+ indio_dev->channels = inv_mpu6500_channels;
+ indio_dev->num_channels = ARRAY_SIZE(inv_mpu6500_channels);
indio_dev->available_scan_masks = inv_mpu_scan_masks;
break;
}
@@ -1641,9 +2033,18 @@ int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name,
* auxiliary device in use. Otherwise Going back to 6-axis only.
*/
if (st->magn_disabled) {
- indio_dev->channels = inv_mpu_channels;
- indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels);
- indio_dev->available_scan_masks = inv_mpu_scan_masks;
+ switch (chip_type) {
+ case INV_MPU9150:
+ indio_dev->channels = inv_mpu6050_channels;
+ indio_dev->num_channels = ARRAY_SIZE(inv_mpu6050_channels);
+ indio_dev->available_scan_masks = inv_mpu_scan_masks;
+ break;
+ default:
+ indio_dev->channels = inv_mpu6500_channels;
+ indio_dev->num_channels = ARRAY_SIZE(inv_mpu6500_channels);
+ indio_dev->available_scan_masks = inv_mpu_scan_masks;
+ break;
+ }
}
indio_dev->info = &mpu_info;
@@ -1687,16 +2088,27 @@ static int inv_mpu_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct inv_mpu6050_state *st = iio_priv(indio_dev);
+ bool wakeup;
int result;
- mutex_lock(&st->lock);
- result = inv_mpu_core_enable_regulator_vddio(st);
- if (result)
- goto out_unlock;
+ guard(mutex)(&st->lock);
- result = inv_mpu6050_set_power_itg(st, true);
- if (result)
- goto out_unlock;
+ wakeup = device_may_wakeup(dev) && st->chip_config.wom_en;
+
+ if (wakeup) {
+ enable_irq(st->irq);
+ disable_irq_wake(st->irq);
+ result = inv_mpu6050_set_wom_lp(st, false);
+ if (result)
+ return result;
+ } else {
+ result = inv_mpu_core_enable_regulator_vddio(st);
+ if (result)
+ return result;
+ result = inv_mpu6050_set_power_itg(st, true);
+ if (result)
+ return result;
+ }
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
@@ -1704,14 +2116,17 @@ static int inv_mpu_resume(struct device *dev)
result = inv_mpu6050_switch_engine(st, true, st->suspended_sensors);
if (result)
- goto out_unlock;
+ return result;
+
+ if (st->chip_config.wom_en && !wakeup) {
+ result = inv_mpu6050_set_wom_int(st, true);
+ if (result)
+ return result;
+ }
if (iio_buffer_enabled(indio_dev))
result = inv_mpu6050_prepare_fifo(st, true);
-out_unlock:
- mutex_unlock(&st->lock);
-
return result;
}
@@ -1719,23 +2134,30 @@ static int inv_mpu_suspend(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct inv_mpu6050_state *st = iio_priv(indio_dev);
+ bool wakeup;
int result;
- mutex_lock(&st->lock);
+ guard(mutex)(&st->lock);
st->suspended_sensors = 0;
- if (pm_runtime_suspended(dev)) {
- result = 0;
- goto out_unlock;
- }
+ if (pm_runtime_suspended(dev))
+ return 0;
if (iio_buffer_enabled(indio_dev)) {
result = inv_mpu6050_prepare_fifo(st, false);
if (result)
- goto out_unlock;
+ return result;
+ }
+
+ wakeup = device_may_wakeup(dev) && st->chip_config.wom_en;
+
+ if (st->chip_config.wom_en && !wakeup) {
+ result = inv_mpu6050_set_wom_int(st, false);
+ if (result)
+ return result;
}
- if (st->chip_config.accl_en)
+ if (st->chip_config.accl_en && !wakeup)
st->suspended_sensors |= INV_MPU6050_SENSOR_ACCL;
if (st->chip_config.gyro_en)
st->suspended_sensors |= INV_MPU6050_SENSOR_GYRO;
@@ -1743,19 +2165,26 @@ static int inv_mpu_suspend(struct device *dev)
st->suspended_sensors |= INV_MPU6050_SENSOR_TEMP;
if (st->chip_config.magn_en)
st->suspended_sensors |= INV_MPU6050_SENSOR_MAGN;
+ if (st->chip_config.wom_en && !wakeup)
+ st->suspended_sensors |= INV_MPU6050_SENSOR_WOM;
result = inv_mpu6050_switch_engine(st, false, st->suspended_sensors);
if (result)
- goto out_unlock;
-
- result = inv_mpu6050_set_power_itg(st, false);
- if (result)
- goto out_unlock;
+ return result;
- inv_mpu_core_disable_regulator_vddio(st);
-out_unlock:
- mutex_unlock(&st->lock);
+ if (wakeup) {
+ result = inv_mpu6050_set_wom_lp(st, true);
+ if (result)
+ return result;
+ enable_irq_wake(st->irq);
+ disable_irq(st->irq);
+ } else {
+ result = inv_mpu6050_set_power_itg(st, false);
+ if (result)
+ return result;
+ inv_mpu_core_disable_regulator_vddio(st);
+ }
- return result;
+ return 0;
}
static int inv_mpu_runtime_suspend(struct device *dev)
@@ -1767,7 +2196,8 @@ static int inv_mpu_runtime_suspend(struct device *dev)
mutex_lock(&st->lock);
sensors = INV_MPU6050_SENSOR_ACCL | INV_MPU6050_SENSOR_GYRO |
- INV_MPU6050_SENSOR_TEMP | INV_MPU6050_SENSOR_MAGN;
+ INV_MPU6050_SENSOR_TEMP | INV_MPU6050_SENSOR_MAGN |
+ INV_MPU6050_SENSOR_WOM;
ret = inv_mpu6050_switch_engine(st, false, sensors);
if (ret)
goto out_unlock;
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
index 5950e2419ebb50..e1c0c51468761a 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h
@@ -88,11 +88,12 @@ enum inv_devices {
INV_NUM_PARTS
};
-/* chip sensors mask: accelerometer, gyroscope, temperature, magnetometer */
+/* chip sensors mask: accelerometer, gyroscope, temperature, magnetometer, WoM */
#define INV_MPU6050_SENSOR_ACCL BIT(0)
#define INV_MPU6050_SENSOR_GYRO BIT(1)
#define INV_MPU6050_SENSOR_TEMP BIT(2)
#define INV_MPU6050_SENSOR_MAGN BIT(3)
+#define INV_MPU6050_SENSOR_WOM BIT(4)
/**
* struct inv_mpu6050_chip_config - Cached chip configuration data.
@@ -104,11 +105,13 @@ enum inv_devices {
* @gyro_en: gyro engine enabled
* @temp_en: temperature sensor enabled
* @magn_en: magn engine (i2c master) enabled
+ * @wom_en: Wake-on-Motion enabled
* @accl_fifo_enable: enable accel data output
* @gyro_fifo_enable: enable gyro data output
* @temp_fifo_enable: enable temp data output
* @magn_fifo_enable: enable magn data output
* @divider: chip sample rate divider (sample rate divider - 1)
+ * @roc_threshold: save ROC threshold (WoM) set value
*/
struct inv_mpu6050_chip_config {
unsigned int clk:3;
@@ -119,12 +122,14 @@ struct inv_mpu6050_chip_config {
unsigned int gyro_en:1;
unsigned int temp_en:1;
unsigned int magn_en:1;
+ unsigned int wom_en:1;
unsigned int accl_fifo_enable:1;
unsigned int gyro_fifo_enable:1;
unsigned int temp_fifo_enable:1;
unsigned int magn_fifo_enable:1;
u8 divider;
u8 user_ctrl;
+ u64 roc_threshold;
};
/*
@@ -180,6 +185,7 @@ struct inv_mpu6050_hw {
* @magn_orient: magnetometer sensor chip orientation if available.
* @suspended_sensors: sensors mask of sensors turned off for suspend
* @data: read buffer used for bulk reads.
+ * @it_timestamp: interrupt timestamp.
*/
struct inv_mpu6050_state {
struct mutex lock;
@@ -205,6 +211,7 @@ struct inv_mpu6050_state {
unsigned int suspended_sensors;
bool level_shifter;
u8 *data;
+ s64 it_timestamp;
};
/*register and associated bit definition*/
@@ -256,12 +263,16 @@ struct inv_mpu6050_state {
#define INV_MPU6050_REG_INT_ENABLE 0x38
#define INV_MPU6050_BIT_DATA_RDY_EN 0x01
#define INV_MPU6050_BIT_DMP_INT_EN 0x02
+#define INV_MPU6500_BIT_WOM_INT_EN BIT(6)
+#define INV_ICM20608_BIT_WOM_INT_EN GENMASK(7, 5)
#define INV_MPU6050_REG_RAW_ACCEL 0x3B
#define INV_MPU6050_REG_TEMPERATURE 0x41
#define INV_MPU6050_REG_RAW_GYRO 0x43
#define INV_MPU6050_REG_INT_STATUS 0x3A
+#define INV_MPU6500_BIT_WOM_INT BIT(6)
+#define INV_ICM20608_BIT_WOM_INT GENMASK(7, 5)
#define INV_MPU6050_BIT_FIFO_OVERFLOW_INT 0x10
#define INV_MPU6050_BIT_RAW_DATA_RDY_INT 0x01
@@ -294,6 +305,7 @@ struct inv_mpu6050_state {
#define INV_MPU6050_REG_PWR_MGMT_1 0x6B
#define INV_MPU6050_BIT_H_RESET 0x80
#define INV_MPU6050_BIT_SLEEP 0x40
+#define INV_MPU6050_BIT_CYCLE 0x20
#define INV_MPU6050_BIT_TEMP_DIS 0x08
#define INV_MPU6050_BIT_CLK_MASK 0x7
@@ -301,6 +313,11 @@ struct inv_mpu6050_state {
#define INV_MPU6050_BIT_PWR_ACCL_STBY 0x38
#define INV_MPU6050_BIT_PWR_GYRO_STBY 0x07
+/* ICM20609 registers */
+#define INV_ICM20609_REG_ACCEL_WOM_X_THR 0x20
+#define INV_ICM20609_REG_ACCEL_WOM_Y_THR 0x21
+#define INV_ICM20609_REG_ACCEL_WOM_Z_THR 0x22
+
/* ICM20602 register */
#define INV_ICM20602_REG_I2C_IF 0x70
#define INV_ICM20602_BIT_I2C_IF_DIS 0x40
@@ -320,6 +337,11 @@ struct inv_mpu6050_state {
/* mpu6500 registers */
#define INV_MPU6500_REG_ACCEL_CONFIG_2 0x1D
#define INV_ICM20689_BITS_FIFO_SIZE_MAX 0xC0
+#define INV_MPU6500_REG_LP_ODR 0x1E
+#define INV_MPU6500_REG_WOM_THRESHOLD 0x1F
+#define INV_MPU6500_REG_ACCEL_INTEL_CTRL 0x69
+#define INV_MPU6500_BIT_ACCEL_INTEL_EN BIT(7)
+#define INV_MPU6500_BIT_ACCEL_INTEL_MODE BIT(6)
#define INV_MPU6500_REG_ACCEL_OFFSET 0x77
/* delay time in milliseconds */
@@ -432,6 +454,18 @@ enum inv_mpu6050_filter_e {
NUM_MPU6050_FILTER
};
+enum inv_mpu6050_lposc_e {
+ INV_MPU6050_LPOSC_4HZ = 4,
+ INV_MPU6050_LPOSC_8HZ,
+ INV_MPU6050_LPOSC_16HZ,
+ INV_MPU6050_LPOSC_31HZ,
+ INV_MPU6050_LPOSC_62HZ,
+ INV_MPU6050_LPOSC_125HZ,
+ INV_MPU6050_LPOSC_250HZ,
+ INV_MPU6050_LPOSC_500HZ,
+ NUM_MPU6050_LPOSC,
+};
+
/* IIO attribute address */
enum INV_MPU6050_IIO_ATTR_ADDR {
ATTR_GYRO_MATRIX,
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c
index d404e59eb5e855..0dc0f22a558279 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c
@@ -33,10 +33,8 @@ static int inv_reset_fifo(struct iio_dev *indio_dev)
reset_fifo_fail:
dev_err(regmap_get_device(st->map), "reset fifo failed %d\n", result);
- result = regmap_write(st->map, st->reg->int_enable,
- INV_MPU6050_BIT_DATA_RDY_EN);
-
- return result;
+ return regmap_update_bits(st->map, st->reg->int_enable,
+ INV_MPU6050_BIT_DATA_RDY_EN, INV_MPU6050_BIT_DATA_RDY_EN);
}
/*
@@ -53,21 +51,10 @@ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p)
u32 fifo_period;
s64 timestamp;
u8 data[INV_MPU6050_OUTPUT_DATA_SIZE];
- int int_status;
size_t i, nb;
mutex_lock(&st->lock);
- /* ack interrupt and check status */
- result = regmap_read(st->map, st->reg->int_status, &int_status);
- if (result) {
- dev_err(regmap_get_device(st->map),
- "failed to ack interrupt\n");
- goto flush_fifo;
- }
- if (!(int_status & INV_MPU6050_BIT_RAW_DATA_RDY_INT))
- goto end_session;
-
if (!(st->chip_config.accl_fifo_enable |
st->chip_config.gyro_fifo_enable |
st->chip_config.magn_fifo_enable))
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c
index e6e6e94452a328..1b603567ccc804 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c
@@ -6,6 +6,7 @@
#include <linux/pm_runtime.h>
#include <linux/iio/common/inv_sensors_timestamp.h>
+#include <linux/iio/events.h>
#include "inv_mpu_iio.h"
@@ -135,11 +136,13 @@ int inv_mpu6050_prepare_fifo(struct inv_mpu6050_state *st, bool enable)
ret = regmap_write(st->map, st->reg->user_ctrl, d);
if (ret)
return ret;
- /* enable interrupt */
- ret = regmap_write(st->map, st->reg->int_enable,
- INV_MPU6050_BIT_DATA_RDY_EN);
+ /* enable data interrupt */
+ ret = regmap_update_bits(st->map, st->reg->int_enable,
+ INV_MPU6050_BIT_DATA_RDY_EN, INV_MPU6050_BIT_DATA_RDY_EN);
} else {
- ret = regmap_write(st->map, st->reg->int_enable, 0);
+ /* disable data interrupt */
+ ret = regmap_update_bits(st->map, st->reg->int_enable,
+ INV_MPU6050_BIT_DATA_RDY_EN, 0);
if (ret)
return ret;
ret = regmap_write(st->map, st->reg->fifo_en, 0);
@@ -172,9 +175,9 @@ static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable)
return result;
/*
* In case autosuspend didn't trigger, turn off first not
- * required sensors.
+ * required sensors excepted WoM
*/
- result = inv_mpu6050_switch_engine(st, false, ~scan);
+ result = inv_mpu6050_switch_engine(st, false, ~scan & ~INV_MPU6050_SENSOR_WOM);
if (result)
goto error_power_off;
result = inv_mpu6050_switch_engine(st, true, scan);
@@ -226,6 +229,65 @@ static const struct iio_trigger_ops inv_mpu_trigger_ops = {
.set_trigger_state = &inv_mpu_data_rdy_trigger_set_state,
};
+static irqreturn_t inv_mpu6050_interrupt_timestamp(int irq, void *p)
+{
+ struct iio_dev *indio_dev = p;
+ struct inv_mpu6050_state *st = iio_priv(indio_dev);
+
+ st->it_timestamp = iio_get_time_ns(indio_dev);
+
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t inv_mpu6050_interrupt_handle(int irq, void *p)
+{
+ struct iio_dev *indio_dev = p;
+ struct inv_mpu6050_state *st = iio_priv(indio_dev);
+ unsigned int int_status, wom_bits;
+ u64 ev_code;
+ int result;
+
+ switch (st->chip_type) {
+ case INV_MPU6050:
+ case INV_MPU6500:
+ case INV_MPU6515:
+ case INV_MPU6880:
+ case INV_MPU6000:
+ case INV_MPU9150:
+ case INV_MPU9250:
+ case INV_MPU9255:
+ wom_bits = INV_MPU6500_BIT_WOM_INT;
+ break;
+ default:
+ wom_bits = INV_ICM20608_BIT_WOM_INT;
+ break;
+ }
+
+ scoped_guard(mutex, &st->lock) {
+ /* ack interrupt and check status */
+ result = regmap_read(st->map, st->reg->int_status, &int_status);
+ if (result) {
+ dev_err(regmap_get_device(st->map), "failed to ack interrupt\n");
+ return IRQ_HANDLED;
+ }
+
+ /* handle WoM event */
+ if (st->chip_config.wom_en && (int_status & wom_bits)) {
+ ev_code = IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_ROC, IIO_EV_DIR_RISING);
+ iio_push_event(indio_dev, ev_code, st->it_timestamp);
+ }
+ }
+
+ /* handle raw data interrupt */
+ if (int_status & INV_MPU6050_BIT_RAW_DATA_RDY_INT) {
+ indio_dev->pollfunc->timestamp = st->it_timestamp;
+ iio_trigger_poll_nested(st->trig);
+ }
+
+ return IRQ_HANDLED;
+}
+
int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev, int irq_type)
{
int ret;
@@ -238,11 +300,10 @@ int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev, int irq_type)
if (!st->trig)
return -ENOMEM;
- ret = devm_request_irq(&indio_dev->dev, st->irq,
- &iio_trigger_generic_data_rdy_poll,
- irq_type,
- "inv_mpu",
- st->trig);
+ ret = devm_request_threaded_irq(&indio_dev->dev, st->irq,
+ &inv_mpu6050_interrupt_timestamp,
+ &inv_mpu6050_interrupt_handle,
+ irq_type, "inv_mpu", indio_dev);
if (ret)
return ret;
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
index 0716986f981299..937ff9c5a74c76 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
@@ -2726,7 +2726,7 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id,
if (!hw)
return -ENOMEM;
- dev_set_drvdata(dev, (void *)hw);
+ dev_set_drvdata(dev, hw);
mutex_init(&hw->fifo_lock);
mutex_init(&hw->conf_lock);
diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c
index 2fea2bbbe47fd0..f08ed6d70ae590 100644
--- a/drivers/iio/industrialio-backend.c
+++ b/drivers/iio/industrialio-backend.c
@@ -43,10 +43,12 @@
#include <linux/types.h>
#include <linux/iio/backend.h>
+#include <linux/iio/iio.h>
struct iio_backend {
struct list_head entry;
const struct iio_backend_ops *ops;
+ struct device *frontend_dev;
struct device *dev;
struct module *owner;
void *priv;
@@ -186,6 +188,44 @@ int iio_backend_data_format_set(struct iio_backend *back, unsigned int chan,
}
EXPORT_SYMBOL_NS_GPL(iio_backend_data_format_set, IIO_BACKEND);
+/**
+ * iio_backend_data_source_set - Select data source
+ * @back: Backend device
+ * @chan: Channel number
+ * @data: Data source
+ *
+ * A given backend may have different sources to stream/sync data. This allows
+ * to choose that source.
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int iio_backend_data_source_set(struct iio_backend *back, unsigned int chan,
+ enum iio_backend_data_source data)
+{
+ if (data >= IIO_BACKEND_DATA_SOURCE_MAX)
+ return -EINVAL;
+
+ return iio_backend_op_call(back, data_source_set, chan, data);
+}
+EXPORT_SYMBOL_NS_GPL(iio_backend_data_source_set, IIO_BACKEND);
+
+/**
+ * iio_backend_set_sampling_freq - Set channel sampling rate
+ * @back: Backend device
+ * @chan: Channel number
+ * @sample_rate_hz: Sample rate
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int iio_backend_set_sampling_freq(struct iio_backend *back, unsigned int chan,
+ u64 sample_rate_hz)
+{
+ return iio_backend_op_call(back, set_sample_rate, chan, sample_rate_hz);
+}
+EXPORT_SYMBOL_NS_GPL(iio_backend_set_sampling_freq, IIO_BACKEND);
+
static void iio_backend_free_buffer(void *arg)
{
struct iio_backend_buffer_pair *pair = arg;
@@ -231,6 +271,143 @@ int devm_iio_backend_request_buffer(struct device *dev,
}
EXPORT_SYMBOL_NS_GPL(devm_iio_backend_request_buffer, IIO_BACKEND);
+static struct iio_backend *iio_backend_from_indio_dev_parent(const struct device *dev)
+{
+ struct iio_backend *back = ERR_PTR(-ENODEV), *iter;
+
+ /*
+ * We deliberately go through all backends even after finding a match.
+ * The reason is that we want to catch frontend devices which have more
+ * than one backend in which case returning the first we find is bogus.
+ * For those cases, frontends need to explicitly define
+ * get_iio_backend() in struct iio_info.
+ */
+ guard(mutex)(&iio_back_lock);
+ list_for_each_entry(iter, &iio_back_list, entry) {
+ if (dev == iter->frontend_dev) {
+ if (!IS_ERR(back)) {
+ dev_warn(dev,
+ "Multiple backends! get_iio_backend() needs to be implemented");
+ return ERR_PTR(-ENODEV);
+ }
+
+ back = iter;
+ }
+ }
+
+ return back;
+}
+
+/**
+ * iio_backend_ext_info_get - IIO ext_info read callback
+ * @indio_dev: IIO device
+ * @private: Data private to the driver
+ * @chan: IIO channel
+ * @buf: Buffer where to place the attribute data
+ *
+ * This helper is intended to be used by backends that extend an IIO channel
+ * (through iio_backend_extend_chan_spec()) with extended info. In that case,
+ * backends are not supposed to give their own callbacks (as they would not have
+ * a way to get the backend from indio_dev). This is the getter.
+ *
+ * RETURNS:
+ * Number of bytes written to buf, negative error number on failure.
+ */
+ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private,
+ const struct iio_chan_spec *chan, char *buf)
+{
+ struct iio_backend *back;
+
+ /*
+ * The below should work for the majority of the cases. It will not work
+ * when one frontend has multiple backends in which case we'll need a
+ * new callback in struct iio_info so we can directly request the proper
+ * backend from the frontend. Anyways, let's only introduce new options
+ * when really needed...
+ */
+ back = iio_backend_from_indio_dev_parent(indio_dev->dev.parent);
+ if (IS_ERR(back))
+ return PTR_ERR(back);
+
+ return iio_backend_op_call(back, ext_info_get, private, chan, buf);
+}
+EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_get, IIO_BACKEND);
+
+/**
+ * iio_backend_ext_info_set - IIO ext_info write callback
+ * @indio_dev: IIO device
+ * @private: Data private to the driver
+ * @chan: IIO channel
+ * @buf: Buffer holding the sysfs attribute
+ * @len: Buffer length
+ *
+ * This helper is intended to be used by backends that extend an IIO channel
+ * (trough iio_backend_extend_chan_spec()) with extended info. In that case,
+ * backends are not supposed to give their own callbacks (as they would not have
+ * a way to get the backend from indio_dev). This is the setter.
+ *
+ * RETURNS:
+ * Buffer length on success, negative error number on failure.
+ */
+ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len)
+{
+ struct iio_backend *back;
+
+ back = iio_backend_from_indio_dev_parent(indio_dev->dev.parent);
+ if (IS_ERR(back))
+ return PTR_ERR(back);
+
+ return iio_backend_op_call(back, ext_info_set, private, chan, buf, len);
+}
+EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_set, IIO_BACKEND);
+
+/**
+ * iio_backend_extend_chan_spec - Extend an IIO channel
+ * @indio_dev: IIO device
+ * @back: Backend device
+ * @chan: IIO channel
+ *
+ * Some backends may have their own functionalities and hence capable of
+ * extending a frontend's channel.
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int iio_backend_extend_chan_spec(struct iio_dev *indio_dev,
+ struct iio_backend *back,
+ struct iio_chan_spec *chan)
+{
+ const struct iio_chan_spec_ext_info *frontend_ext_info = chan->ext_info;
+ const struct iio_chan_spec_ext_info *back_ext_info;
+ int ret;
+
+ ret = iio_backend_op_call(back, extend_chan_spec, chan);
+ if (ret)
+ return ret;
+ /*
+ * Let's keep things simple for now. Don't allow to overwrite the
+ * frontend's extended info. If ever needed, we can support appending
+ * it.
+ */
+ if (frontend_ext_info && chan->ext_info != frontend_ext_info)
+ return -EOPNOTSUPP;
+ if (!chan->ext_info)
+ return 0;
+
+ /* Don't allow backends to get creative and force their own handlers */
+ for (back_ext_info = chan->ext_info; back_ext_info->name; back_ext_info++) {
+ if (back_ext_info->read != iio_backend_ext_info_get)
+ return -EINVAL;
+ if (back_ext_info->write != iio_backend_ext_info_set)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(iio_backend_extend_chan_spec, IIO_BACKEND);
+
static void iio_backend_release(void *arg)
{
struct iio_backend *back = arg;
@@ -263,6 +440,8 @@ static int __devm_iio_backend_get(struct device *dev, struct iio_backend *back)
"Could not link to supplier(%s)\n",
dev_name(back->dev));
+ back->frontend_dev = dev;
+
dev_dbg(dev, "Found backend(%s) device\n", dev_name(back->dev));
return 0;
diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c
index b581a7e8056621..1d950a3e153b8a 100644
--- a/drivers/iio/industrialio-buffer.c
+++ b/drivers/iio/industrialio-buffer.c
@@ -10,6 +10,7 @@
* - Alternative access techniques?
*/
#include <linux/anon_inodes.h>
+#include <linux/cleanup.h>
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/device.h>
@@ -533,28 +534,26 @@ static ssize_t iio_scan_el_store(struct device *dev,
ret = kstrtobool(buf, &state);
if (ret < 0)
return ret;
- mutex_lock(&iio_dev_opaque->mlock);
- if (iio_buffer_is_active(buffer)) {
- ret = -EBUSY;
- goto error_ret;
- }
+
+ guard(mutex)(&iio_dev_opaque->mlock);
+ if (iio_buffer_is_active(buffer))
+ return -EBUSY;
+
ret = iio_scan_mask_query(indio_dev, buffer, this_attr->address);
if (ret < 0)
- goto error_ret;
- if (!state && ret) {
- ret = iio_scan_mask_clear(buffer, this_attr->address);
- if (ret)
- goto error_ret;
- } else if (state && !ret) {
- ret = iio_scan_mask_set(indio_dev, buffer, this_attr->address);
- if (ret)
- goto error_ret;
- }
+ return ret;
-error_ret:
- mutex_unlock(&iio_dev_opaque->mlock);
+ if (state && ret)
+ return len;
- return ret < 0 ? ret : len;
+ if (state)
+ ret = iio_scan_mask_set(indio_dev, buffer, this_attr->address);
+ else
+ ret = iio_scan_mask_clear(buffer, this_attr->address);
+ if (ret)
+ return ret;
+
+ return len;
}
static ssize_t iio_scan_el_ts_show(struct device *dev,
@@ -581,16 +580,13 @@ static ssize_t iio_scan_el_ts_store(struct device *dev,
if (ret < 0)
return ret;
- mutex_lock(&iio_dev_opaque->mlock);
- if (iio_buffer_is_active(buffer)) {
- ret = -EBUSY;
- goto error_ret;
- }
+ guard(mutex)(&iio_dev_opaque->mlock);
+ if (iio_buffer_is_active(buffer))
+ return -EBUSY;
+
buffer->scan_timestamp = state;
-error_ret:
- mutex_unlock(&iio_dev_opaque->mlock);
- return ret ? ret : len;
+ return len;
}
static int iio_buffer_add_channel_sysfs(struct iio_dev *indio_dev,
@@ -674,21 +670,16 @@ static ssize_t length_store(struct device *dev, struct device_attribute *attr,
if (val == buffer->length)
return len;
- mutex_lock(&iio_dev_opaque->mlock);
- if (iio_buffer_is_active(buffer)) {
- ret = -EBUSY;
- } else {
- buffer->access->set_length(buffer, val);
- ret = 0;
- }
- if (ret)
- goto out;
+ guard(mutex)(&iio_dev_opaque->mlock);
+ if (iio_buffer_is_active(buffer))
+ return -EBUSY;
+
+ buffer->access->set_length(buffer, val);
+
if (buffer->length && buffer->length < buffer->watermark)
buffer->watermark = buffer->length;
-out:
- mutex_unlock(&iio_dev_opaque->mlock);
- return ret ? ret : len;
+ return len;
}
static ssize_t enable_show(struct device *dev, struct device_attribute *attr,
@@ -1268,7 +1259,6 @@ int iio_update_buffers(struct iio_dev *indio_dev,
struct iio_buffer *remove_buffer)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
- int ret;
if (insert_buffer == remove_buffer)
return 0;
@@ -1277,8 +1267,8 @@ int iio_update_buffers(struct iio_dev *indio_dev,
insert_buffer->direction == IIO_BUFFER_DIRECTION_OUT)
return -EINVAL;
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- mutex_lock(&iio_dev_opaque->mlock);
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ guard(mutex)(&iio_dev_opaque->mlock);
if (insert_buffer && iio_buffer_is_active(insert_buffer))
insert_buffer = NULL;
@@ -1286,23 +1276,13 @@ int iio_update_buffers(struct iio_dev *indio_dev,
if (remove_buffer && !iio_buffer_is_active(remove_buffer))
remove_buffer = NULL;
- if (!insert_buffer && !remove_buffer) {
- ret = 0;
- goto out_unlock;
- }
-
- if (!indio_dev->info) {
- ret = -ENODEV;
- goto out_unlock;
- }
-
- ret = __iio_update_buffers(indio_dev, insert_buffer, remove_buffer);
+ if (!insert_buffer && !remove_buffer)
+ return 0;
-out_unlock:
- mutex_unlock(&iio_dev_opaque->mlock);
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
+ if (!indio_dev->info)
+ return -ENODEV;
- return ret;
+ return __iio_update_buffers(indio_dev, insert_buffer, remove_buffer);
}
EXPORT_SYMBOL_GPL(iio_update_buffers);
@@ -1326,22 +1306,22 @@ static ssize_t enable_store(struct device *dev, struct device_attribute *attr,
if (ret < 0)
return ret;
- mutex_lock(&iio_dev_opaque->mlock);
+ guard(mutex)(&iio_dev_opaque->mlock);
/* Find out if it is in the list */
inlist = iio_buffer_is_active(buffer);
/* Already in desired state */
if (inlist == requested_state)
- goto done;
+ return len;
if (requested_state)
ret = __iio_update_buffers(indio_dev, buffer, NULL);
else
ret = __iio_update_buffers(indio_dev, NULL, buffer);
+ if (ret)
+ return ret;
-done:
- mutex_unlock(&iio_dev_opaque->mlock);
- return (ret < 0) ? ret : len;
+ return len;
}
static ssize_t watermark_show(struct device *dev, struct device_attribute *attr,
@@ -1368,23 +1348,17 @@ static ssize_t watermark_store(struct device *dev,
if (!val)
return -EINVAL;
- mutex_lock(&iio_dev_opaque->mlock);
+ guard(mutex)(&iio_dev_opaque->mlock);
- if (val > buffer->length) {
- ret = -EINVAL;
- goto out;
- }
+ if (val > buffer->length)
+ return -EINVAL;
- if (iio_buffer_is_active(buffer)) {
- ret = -EBUSY;
- goto out;
- }
+ if (iio_buffer_is_active(buffer))
+ return -EBUSY;
buffer->watermark = val;
-out:
- mutex_unlock(&iio_dev_opaque->mlock);
- return ret ? ret : len;
+ return len;
}
static ssize_t data_available_show(struct device *dev,
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index 4302093b92c75a..fa7cc051b4c49b 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -11,6 +11,7 @@
#include <linux/anon_inodes.h>
#include <linux/cdev.h>
+#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/err.h>
@@ -1643,19 +1644,20 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv)
struct iio_dev *indio_dev;
size_t alloc_size;
- alloc_size = sizeof(struct iio_dev_opaque);
- if (sizeof_priv) {
- alloc_size = ALIGN(alloc_size, IIO_DMA_MINALIGN);
- alloc_size += sizeof_priv;
- }
+ if (sizeof_priv)
+ alloc_size = ALIGN(sizeof(*iio_dev_opaque), IIO_DMA_MINALIGN) + sizeof_priv;
+ else
+ alloc_size = sizeof(*iio_dev_opaque);
iio_dev_opaque = kzalloc(alloc_size, GFP_KERNEL);
if (!iio_dev_opaque)
return NULL;
indio_dev = &iio_dev_opaque->indio_dev;
- indio_dev->priv = (char *)iio_dev_opaque +
- ALIGN(sizeof(struct iio_dev_opaque), IIO_DMA_MINALIGN);
+
+ if (sizeof_priv)
+ indio_dev->priv = (char *)iio_dev_opaque +
+ ALIGN(sizeof(*iio_dev_opaque), IIO_DMA_MINALIGN);
indio_dev->dev.parent = parent;
indio_dev->dev.type = &iio_device_type;
@@ -1809,31 +1811,24 @@ static long iio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
struct iio_dev *indio_dev = ib->indio_dev;
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
struct iio_ioctl_handler *h;
- int ret = -ENODEV;
-
- mutex_lock(&iio_dev_opaque->info_exist_lock);
+ int ret;
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
/*
* The NULL check here is required to prevent crashing when a device
* is being removed while userspace would still have open file handles
* to try to access this device.
*/
if (!indio_dev->info)
- goto out_unlock;
+ return -ENODEV;
list_for_each_entry(h, &iio_dev_opaque->ioctl_handlers, entry) {
ret = h->ioctl(indio_dev, filp, cmd, arg);
if (ret != IIO_IOCTL_UNHANDLED)
- break;
+ return ret;
}
- if (ret == IIO_IOCTL_UNHANDLED)
- ret = -ENODEV;
-
-out_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
-
- return ret;
+ return -ENODEV;
}
static const struct file_operations iio_buffer_fileops = {
@@ -2061,18 +2056,16 @@ void iio_device_unregister(struct iio_dev *indio_dev)
cdev_device_del(&iio_dev_opaque->chrdev, &indio_dev->dev);
- mutex_lock(&iio_dev_opaque->info_exist_lock);
+ scoped_guard(mutex, &iio_dev_opaque->info_exist_lock) {
+ iio_device_unregister_debugfs(indio_dev);
- iio_device_unregister_debugfs(indio_dev);
+ iio_disable_all_buffers(indio_dev);
- iio_disable_all_buffers(indio_dev);
+ indio_dev->info = NULL;
- indio_dev->info = NULL;
-
- iio_device_wakeup_eventset(indio_dev);
- iio_buffer_wakeup_poll(indio_dev);
-
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
+ iio_device_wakeup_eventset(indio_dev);
+ iio_buffer_wakeup_poll(indio_dev);
+ }
iio_buffers_free_sysfs_and_mask(indio_dev);
}
diff --git a/drivers/iio/industrialio-trigger.c b/drivers/iio/industrialio-trigger.c
index 18f83158f637f1..16de57846bd9c1 100644
--- a/drivers/iio/industrialio-trigger.c
+++ b/drivers/iio/industrialio-trigger.c
@@ -4,6 +4,7 @@
* Copyright (c) 2008 Jonathan Cameron
*/
+#include <linux/cleanup.h>
#include <linux/kernel.h>
#include <linux/idr.h>
#include <linux/err.h>
@@ -80,19 +81,18 @@ int iio_trigger_register(struct iio_trigger *trig_info)
goto error_unregister_id;
/* Add to list of available triggers held by the IIO core */
- mutex_lock(&iio_trigger_list_lock);
- if (__iio_trigger_find_by_name(trig_info->name)) {
- pr_err("Duplicate trigger name '%s'\n", trig_info->name);
- ret = -EEXIST;
- goto error_device_del;
+ scoped_guard(mutex, &iio_trigger_list_lock) {
+ if (__iio_trigger_find_by_name(trig_info->name)) {
+ pr_err("Duplicate trigger name '%s'\n", trig_info->name);
+ ret = -EEXIST;
+ goto error_device_del;
+ }
+ list_add_tail(&trig_info->list, &iio_trigger_list);
}
- list_add_tail(&trig_info->list, &iio_trigger_list);
- mutex_unlock(&iio_trigger_list_lock);
return 0;
error_device_del:
- mutex_unlock(&iio_trigger_list_lock);
device_del(&trig_info->dev);
error_unregister_id:
ida_free(&iio_trigger_ida, trig_info->id);
@@ -102,9 +102,8 @@ EXPORT_SYMBOL(iio_trigger_register);
void iio_trigger_unregister(struct iio_trigger *trig_info)
{
- mutex_lock(&iio_trigger_list_lock);
- list_del(&trig_info->list);
- mutex_unlock(&iio_trigger_list_lock);
+ scoped_guard(mutex, &iio_trigger_list_lock)
+ list_del(&trig_info->list);
ida_free(&iio_trigger_ida, trig_info->id);
/* Possible issue in here */
@@ -120,12 +119,11 @@ int iio_trigger_set_immutable(struct iio_dev *indio_dev, struct iio_trigger *tri
return -EINVAL;
iio_dev_opaque = to_iio_dev_opaque(indio_dev);
- mutex_lock(&iio_dev_opaque->mlock);
+ guard(mutex)(&iio_dev_opaque->mlock);
WARN_ON(iio_dev_opaque->trig_readonly);
indio_dev->trig = iio_trigger_get(trig);
iio_dev_opaque->trig_readonly = true;
- mutex_unlock(&iio_dev_opaque->mlock);
return 0;
}
@@ -145,18 +143,14 @@ static struct iio_trigger *__iio_trigger_find_by_name(const char *name)
static struct iio_trigger *iio_trigger_acquire_by_name(const char *name)
{
- struct iio_trigger *trig = NULL, *iter;
+ struct iio_trigger *iter;
- mutex_lock(&iio_trigger_list_lock);
+ guard(mutex)(&iio_trigger_list_lock);
list_for_each_entry(iter, &iio_trigger_list, list)
- if (sysfs_streq(iter->name, name)) {
- trig = iter;
- iio_trigger_get(trig);
- break;
- }
- mutex_unlock(&iio_trigger_list_lock);
+ if (sysfs_streq(iter->name, name))
+ return iio_trigger_get(iter);
- return trig;
+ return NULL;
}
static void iio_reenable_work_fn(struct work_struct *work)
@@ -259,22 +253,21 @@ static int iio_trigger_get_irq(struct iio_trigger *trig)
{
int ret;
- mutex_lock(&trig->pool_lock);
- ret = bitmap_find_free_region(trig->pool,
- CONFIG_IIO_CONSUMERS_PER_TRIGGER,
- ilog2(1));
- mutex_unlock(&trig->pool_lock);
- if (ret >= 0)
- ret += trig->subirq_base;
+ scoped_guard(mutex, &trig->pool_lock) {
+ ret = bitmap_find_free_region(trig->pool,
+ CONFIG_IIO_CONSUMERS_PER_TRIGGER,
+ ilog2(1));
+ if (ret < 0)
+ return ret;
+ }
- return ret;
+ return ret + trig->subirq_base;
}
static void iio_trigger_put_irq(struct iio_trigger *trig, int irq)
{
- mutex_lock(&trig->pool_lock);
+ guard(mutex)(&trig->pool_lock);
clear_bit(irq - trig->subirq_base, trig->pool);
- mutex_unlock(&trig->pool_lock);
}
/* Complexity in here. With certain triggers (datardy) an acknowledgement
@@ -451,16 +444,12 @@ static ssize_t current_trigger_store(struct device *dev,
struct iio_trigger *trig;
int ret;
- mutex_lock(&iio_dev_opaque->mlock);
- if (iio_dev_opaque->currentmode == INDIO_BUFFER_TRIGGERED) {
- mutex_unlock(&iio_dev_opaque->mlock);
- return -EBUSY;
- }
- if (iio_dev_opaque->trig_readonly) {
- mutex_unlock(&iio_dev_opaque->mlock);
- return -EPERM;
+ scoped_guard(mutex, &iio_dev_opaque->mlock) {
+ if (iio_dev_opaque->currentmode == INDIO_BUFFER_TRIGGERED)
+ return -EBUSY;
+ if (iio_dev_opaque->trig_readonly)
+ return -EPERM;
}
- mutex_unlock(&iio_dev_opaque->mlock);
trig = iio_trigger_acquire_by_name(buf);
if (oldtrig == trig) {
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index 7a1f6713318a36..52d7732618282e 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -3,6 +3,7 @@
*
* Copyright (c) 2011 Jonathan Cameron
*/
+#include <linux/cleanup.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/minmax.h>
@@ -43,13 +44,14 @@ static int iio_map_array_unregister_locked(struct iio_dev *indio_dev)
int iio_map_array_register(struct iio_dev *indio_dev, struct iio_map *maps)
{
- int i = 0, ret = 0;
struct iio_map_internal *mapi;
+ int i = 0;
+ int ret;
if (!maps)
return 0;
- mutex_lock(&iio_map_list_lock);
+ guard(mutex)(&iio_map_list_lock);
while (maps[i].consumer_dev_name) {
mapi = kzalloc(sizeof(*mapi), GFP_KERNEL);
if (!mapi) {
@@ -61,11 +63,10 @@ int iio_map_array_register(struct iio_dev *indio_dev, struct iio_map *maps)
list_add_tail(&mapi->l, &iio_map_list);
i++;
}
-error_ret:
- if (ret)
- iio_map_array_unregister_locked(indio_dev);
- mutex_unlock(&iio_map_list_lock);
+ return 0;
+error_ret:
+ iio_map_array_unregister_locked(indio_dev);
return ret;
}
EXPORT_SYMBOL_GPL(iio_map_array_register);
@@ -75,13 +76,8 @@ EXPORT_SYMBOL_GPL(iio_map_array_register);
*/
int iio_map_array_unregister(struct iio_dev *indio_dev)
{
- int ret;
-
- mutex_lock(&iio_map_list_lock);
- ret = iio_map_array_unregister_locked(indio_dev);
- mutex_unlock(&iio_map_list_lock);
-
- return ret;
+ guard(mutex)(&iio_map_list_lock);
+ return iio_map_array_unregister_locked(indio_dev);
}
EXPORT_SYMBOL_GPL(iio_map_array_unregister);
@@ -183,25 +179,21 @@ err_put:
static struct iio_channel *fwnode_iio_channel_get(struct fwnode_handle *fwnode,
int index)
{
- struct iio_channel *channel;
int err;
if (index < 0)
return ERR_PTR(-EINVAL);
- channel = kzalloc(sizeof(*channel), GFP_KERNEL);
+ struct iio_channel *channel __free(kfree) =
+ kzalloc(sizeof(*channel), GFP_KERNEL);
if (!channel)
return ERR_PTR(-ENOMEM);
err = __fwnode_iio_channel_get(channel, fwnode, index);
if (err)
- goto err_free_channel;
+ return ERR_PTR(err);
- return channel;
-
-err_free_channel:
- kfree(channel);
- return ERR_PTR(err);
+ return_ptr(channel);
}
static struct iio_channel *
@@ -291,7 +283,6 @@ EXPORT_SYMBOL_GPL(fwnode_iio_channel_get_by_name);
static struct iio_channel *fwnode_iio_channel_get_all(struct device *dev)
{
struct fwnode_handle *fwnode = dev_fwnode(dev);
- struct iio_channel *chans;
int i, mapind, nummaps = 0;
int ret;
@@ -307,7 +298,8 @@ static struct iio_channel *fwnode_iio_channel_get_all(struct device *dev)
return ERR_PTR(-ENODEV);
/* NULL terminated array to save passing size */
- chans = kcalloc(nummaps + 1, sizeof(*chans), GFP_KERNEL);
+ struct iio_channel *chans __free(kfree) =
+ kcalloc(nummaps + 1, sizeof(*chans), GFP_KERNEL);
if (!chans)
return ERR_PTR(-ENOMEM);
@@ -317,12 +309,11 @@ static struct iio_channel *fwnode_iio_channel_get_all(struct device *dev)
if (ret)
goto error_free_chans;
}
- return chans;
+ return_ptr(chans);
error_free_chans:
for (i = 0; i < mapind; i++)
iio_device_put(chans[i].indio_dev);
- kfree(chans);
return ERR_PTR(ret);
}
@@ -330,28 +321,28 @@ static struct iio_channel *iio_channel_get_sys(const char *name,
const char *channel_name)
{
struct iio_map_internal *c_i = NULL, *c = NULL;
- struct iio_channel *channel;
int err;
if (!(name || channel_name))
return ERR_PTR(-ENODEV);
/* first find matching entry the channel map */
- mutex_lock(&iio_map_list_lock);
- list_for_each_entry(c_i, &iio_map_list, l) {
- if ((name && strcmp(name, c_i->map->consumer_dev_name) != 0) ||
- (channel_name &&
- strcmp(channel_name, c_i->map->consumer_channel) != 0))
- continue;
- c = c_i;
- iio_device_get(c->indio_dev);
- break;
+ scoped_guard(mutex, &iio_map_list_lock) {
+ list_for_each_entry(c_i, &iio_map_list, l) {
+ if ((name && strcmp(name, c_i->map->consumer_dev_name) != 0) ||
+ (channel_name &&
+ strcmp(channel_name, c_i->map->consumer_channel) != 0))
+ continue;
+ c = c_i;
+ iio_device_get(c->indio_dev);
+ break;
+ }
}
- mutex_unlock(&iio_map_list_lock);
if (!c)
return ERR_PTR(-ENODEV);
- channel = kzalloc(sizeof(*channel), GFP_KERNEL);
+ struct iio_channel *channel __free(kfree) =
+ kzalloc(sizeof(*channel), GFP_KERNEL);
if (!channel) {
err = -ENOMEM;
goto error_no_mem;
@@ -366,14 +357,12 @@ static struct iio_channel *iio_channel_get_sys(const char *name,
if (!channel->channel) {
err = -EINVAL;
- goto error_no_chan;
+ goto error_no_mem;
}
}
- return channel;
+ return_ptr(channel);
-error_no_chan:
- kfree(channel);
error_no_mem:
iio_device_put(c->indio_dev);
return ERR_PTR(err);
@@ -450,8 +439,8 @@ EXPORT_SYMBOL_GPL(devm_fwnode_iio_channel_get_by_name);
struct iio_channel *iio_channel_get_all(struct device *dev)
{
const char *name;
- struct iio_channel *chans;
struct iio_map_internal *c = NULL;
+ struct iio_channel *fw_chans;
int nummaps = 0;
int mapind = 0;
int i, ret;
@@ -459,17 +448,17 @@ struct iio_channel *iio_channel_get_all(struct device *dev)
if (!dev)
return ERR_PTR(-EINVAL);
- chans = fwnode_iio_channel_get_all(dev);
+ fw_chans = fwnode_iio_channel_get_all(dev);
/*
* We only want to carry on if the error is -ENODEV. Anything else
* should be reported up the stack.
*/
- if (!IS_ERR(chans) || PTR_ERR(chans) != -ENODEV)
- return chans;
+ if (!IS_ERR(fw_chans) || PTR_ERR(fw_chans) != -ENODEV)
+ return fw_chans;
name = dev_name(dev);
- mutex_lock(&iio_map_list_lock);
+ guard(mutex)(&iio_map_list_lock);
/* first count the matching maps */
list_for_each_entry(c, &iio_map_list, l)
if (name && strcmp(name, c->map->consumer_dev_name) != 0)
@@ -477,17 +466,14 @@ struct iio_channel *iio_channel_get_all(struct device *dev)
else
nummaps++;
- if (nummaps == 0) {
- ret = -ENODEV;
- goto error_ret;
- }
+ if (nummaps == 0)
+ return ERR_PTR(-ENODEV);
/* NULL terminated array to save passing size */
- chans = kcalloc(nummaps + 1, sizeof(*chans), GFP_KERNEL);
- if (!chans) {
- ret = -ENOMEM;
- goto error_ret;
- }
+ struct iio_channel *chans __free(kfree) =
+ kcalloc(nummaps + 1, sizeof(*chans), GFP_KERNEL);
+ if (!chans)
+ return ERR_PTR(-ENOMEM);
/* for each map fill in the chans element */
list_for_each_entry(c, &iio_map_list, l) {
@@ -509,17 +495,12 @@ struct iio_channel *iio_channel_get_all(struct device *dev)
ret = -ENODEV;
goto error_free_chans;
}
- mutex_unlock(&iio_map_list_lock);
- return chans;
+ return_ptr(chans);
error_free_chans:
for (i = 0; i < nummaps; i++)
iio_device_put(chans[i].indio_dev);
- kfree(chans);
-error_ret:
- mutex_unlock(&iio_map_list_lock);
-
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(iio_channel_get_all);
@@ -590,38 +571,24 @@ static int iio_channel_read(struct iio_channel *chan, int *val, int *val2,
int iio_read_channel_raw(struct iio_channel *chan, int *val)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
- int ret;
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- if (!chan->indio_dev->info) {
- ret = -ENODEV;
- goto err_unlock;
- }
-
- ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW);
-err_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!chan->indio_dev->info)
+ return -ENODEV;
- return ret;
+ return iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW);
}
EXPORT_SYMBOL_GPL(iio_read_channel_raw);
int iio_read_channel_average_raw(struct iio_channel *chan, int *val)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
- int ret;
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- if (!chan->indio_dev->info) {
- ret = -ENODEV;
- goto err_unlock;
- }
-
- ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_AVERAGE_RAW);
-err_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!chan->indio_dev->info)
+ return -ENODEV;
- return ret;
+ return iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_AVERAGE_RAW);
}
EXPORT_SYMBOL_GPL(iio_read_channel_average_raw);
@@ -708,20 +675,13 @@ int iio_convert_raw_to_processed(struct iio_channel *chan, int raw,
int *processed, unsigned int scale)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
- int ret;
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- if (!chan->indio_dev->info) {
- ret = -ENODEV;
- goto err_unlock;
- }
-
- ret = iio_convert_raw_to_processed_unlocked(chan, raw, processed,
- scale);
-err_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!chan->indio_dev->info)
+ return -ENODEV;
- return ret;
+ return iio_convert_raw_to_processed_unlocked(chan, raw, processed,
+ scale);
}
EXPORT_SYMBOL_GPL(iio_convert_raw_to_processed);
@@ -729,19 +689,12 @@ int iio_read_channel_attribute(struct iio_channel *chan, int *val, int *val2,
enum iio_chan_info_enum attribute)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
- int ret;
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- if (!chan->indio_dev->info) {
- ret = -ENODEV;
- goto err_unlock;
- }
-
- ret = iio_channel_read(chan, val, val2, attribute);
-err_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!chan->indio_dev->info)
+ return -ENODEV;
- return ret;
+ return iio_channel_read(chan, val, val2, attribute);
}
EXPORT_SYMBOL_GPL(iio_read_channel_attribute);
@@ -757,30 +710,26 @@ int iio_read_channel_processed_scale(struct iio_channel *chan, int *val,
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
int ret;
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- if (!chan->indio_dev->info) {
- ret = -ENODEV;
- goto err_unlock;
- }
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!chan->indio_dev->info)
+ return -ENODEV;
if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) {
ret = iio_channel_read(chan, val, NULL,
IIO_CHAN_INFO_PROCESSED);
if (ret < 0)
- goto err_unlock;
+ return ret;
*val *= scale;
+
+ return 0;
} else {
ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW);
if (ret < 0)
- goto err_unlock;
- ret = iio_convert_raw_to_processed_unlocked(chan, *val, val,
- scale);
- }
+ return ret;
-err_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
-
- return ret;
+ return iio_convert_raw_to_processed_unlocked(chan, *val, val,
+ scale);
+ }
}
EXPORT_SYMBOL_GPL(iio_read_channel_processed_scale);
@@ -813,19 +762,12 @@ int iio_read_avail_channel_attribute(struct iio_channel *chan,
enum iio_chan_info_enum attribute)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
- int ret;
-
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- if (!chan->indio_dev->info) {
- ret = -ENODEV;
- goto err_unlock;
- }
- ret = iio_channel_read_avail(chan, vals, type, length, attribute);
-err_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!chan->indio_dev->info)
+ return -ENODEV;
- return ret;
+ return iio_channel_read_avail(chan, vals, type, length, attribute);
}
EXPORT_SYMBOL_GPL(iio_read_avail_channel_attribute);
@@ -892,20 +834,13 @@ static int iio_channel_read_max(struct iio_channel *chan,
int iio_read_max_channel_raw(struct iio_channel *chan, int *val)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
- int ret;
int type;
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- if (!chan->indio_dev->info) {
- ret = -ENODEV;
- goto err_unlock;
- }
-
- ret = iio_channel_read_max(chan, val, NULL, &type, IIO_CHAN_INFO_RAW);
-err_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!chan->indio_dev->info)
+ return -ENODEV;
- return ret;
+ return iio_channel_read_max(chan, val, NULL, &type, IIO_CHAN_INFO_RAW);
}
EXPORT_SYMBOL_GPL(iio_read_max_channel_raw);
@@ -955,40 +890,27 @@ static int iio_channel_read_min(struct iio_channel *chan,
int iio_read_min_channel_raw(struct iio_channel *chan, int *val)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
- int ret;
int type;
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- if (!chan->indio_dev->info) {
- ret = -ENODEV;
- goto err_unlock;
- }
-
- ret = iio_channel_read_min(chan, val, NULL, &type, IIO_CHAN_INFO_RAW);
-err_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!chan->indio_dev->info)
+ return -ENODEV;
- return ret;
+ return iio_channel_read_min(chan, val, NULL, &type, IIO_CHAN_INFO_RAW);
}
EXPORT_SYMBOL_GPL(iio_read_min_channel_raw);
int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
- int ret = 0;
- /* Need to verify underlying driver has not gone away */
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- if (!chan->indio_dev->info) {
- ret = -ENODEV;
- goto err_unlock;
- }
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!chan->indio_dev->info)
+ return -ENODEV;
*type = chan->channel->type;
-err_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
- return ret;
+ return 0;
}
EXPORT_SYMBOL_GPL(iio_get_channel_type);
@@ -1003,19 +925,12 @@ int iio_write_channel_attribute(struct iio_channel *chan, int val, int val2,
enum iio_chan_info_enum attribute)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev);
- int ret;
- mutex_lock(&iio_dev_opaque->info_exist_lock);
- if (!chan->indio_dev->info) {
- ret = -ENODEV;
- goto err_unlock;
- }
+ guard(mutex)(&iio_dev_opaque->info_exist_lock);
+ if (!chan->indio_dev->info)
+ return -ENODEV;
- ret = iio_channel_write(chan, val, val2, attribute);
-err_unlock:
- mutex_unlock(&iio_dev_opaque->info_exist_lock);
-
- return ret;
+ return iio_channel_write(chan, val, val2, attribute);
}
EXPORT_SYMBOL_GPL(iio_write_channel_attribute);
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index fd5a9879a582cf..9a587d4031180f 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -73,6 +73,18 @@ config APDS9300
To compile this driver as a module, choose M here: the
module will be called apds9300.
+config APDS9306
+ tristate "Avago APDS9306 Ambient Light Sensor"
+ depends on I2C
+ select REGMAP_I2C
+ select IIO_GTS_HELPER
+ help
+ If you say Y or M here, you get support for Avago APDS9306
+ Ambient Light Sensor.
+
+ If built as a dynamically linked module, it will be called
+ apds9306.
+
config APDS9960
tristate "Avago APDS9960 gesture/RGB/ALS/proximity sensor"
select REGMAP_I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 2e5fdb33e0e979..a30f906e91ba09 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_ADUX1020) += adux1020.o
obj-$(CONFIG_AL3010) += al3010.o
obj-$(CONFIG_AL3320A) += al3320a.o
obj-$(CONFIG_APDS9300) += apds9300.o
+obj-$(CONFIG_APDS9306) += apds9306.o
obj-$(CONFIG_APDS9960) += apds9960.o
obj-$(CONFIG_AS73211) += as73211.o
obj-$(CONFIG_BH1750) += bh1750.o
diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c
new file mode 100644
index 00000000000000..46c647ccd44c51
--- /dev/null
+++ b/drivers/iio/light/apds9306.c
@@ -0,0 +1,1361 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * APDS-9306/APDS-9306-065 Ambient Light Sensor
+ * I2C Address: 0x52
+ * Datasheet: https://docs.broadcom.com/doc/AV02-4755EN
+ *
+ * Copyright (C) 2024 Subhajit Ghosh <subhajit.ghosh@tweaklogic.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/iio-gts-helper.h>
+#include <linux/iio/events.h>
+#include <linux/iio/sysfs.h>
+
+#include <asm/unaligned.h>
+
+#define APDS9306_MAIN_CTRL_REG 0x00
+#define APDS9306_ALS_MEAS_RATE_REG 0x04
+#define APDS9306_ALS_GAIN_REG 0x05
+#define APDS9306_PART_ID_REG 0x06
+#define APDS9306_MAIN_STATUS_REG 0x07
+#define APDS9306_CLEAR_DATA_0_REG 0x0A
+#define APDS9306_CLEAR_DATA_1_REG 0x0B
+#define APDS9306_CLEAR_DATA_2_REG 0x0C
+#define APDS9306_ALS_DATA_0_REG 0x0D
+#define APDS9306_ALS_DATA_1_REG 0x0E
+#define APDS9306_ALS_DATA_2_REG 0x0F
+#define APDS9306_INT_CFG_REG 0x19
+#define APDS9306_INT_PERSISTENCE_REG 0x1A
+#define APDS9306_ALS_THRES_UP_0_REG 0x21
+#define APDS9306_ALS_THRES_UP_1_REG 0x22
+#define APDS9306_ALS_THRES_UP_2_REG 0x23
+#define APDS9306_ALS_THRES_LOW_0_REG 0x24
+#define APDS9306_ALS_THRES_LOW_1_REG 0x25
+#define APDS9306_ALS_THRES_LOW_2_REG 0x26
+#define APDS9306_ALS_THRES_VAR_REG 0x27
+
+#define APDS9306_ALS_INT_STAT_MASK BIT(4)
+#define APDS9306_ALS_DATA_STAT_MASK BIT(3)
+
+#define APDS9306_ALS_THRES_VAL_MAX (BIT(20) - 1)
+#define APDS9306_ALS_THRES_VAR_VAL_MAX (BIT(3) - 1)
+#define APDS9306_ALS_PERSIST_VAL_MAX (BIT(4) - 1)
+#define APDS9306_ALS_READ_DATA_DELAY_US (20 * USEC_PER_MSEC)
+#define APDS9306_NUM_REPEAT_RATES 7
+#define APDS9306_INT_SRC_CLEAR 0
+#define APDS9306_INT_SRC_ALS 1
+#define APDS9306_SAMP_FREQ_10HZ 0
+
+/**
+ * struct part_id_gts_multiplier - Part no. and corresponding gts multiplier
+ *
+ * GTS (Gain Time Scale) are helper functions for Light sensors which along
+ * with hardware gains also has gains associated with Integration times.
+ *
+ * There are two variants of the device with slightly different characteristics,
+ * they have same ADC count for different Lux levels as mentioned in the
+ * datasheet. This multiplier array is used to store the derived Lux per count
+ * value for the two variants to be used by the GTS helper functions.
+ *
+ * @part_id: Part ID of the device
+ * @max_scale_int: Multiplier for iio_init_iio_gts()
+ * @max_scale_nano: Multiplier for iio_init_iio_gts()
+ */
+struct part_id_gts_multiplier {
+ int part_id;
+ int max_scale_int;
+ int max_scale_nano;
+};
+
+/*
+ * As per the datasheet, at HW Gain = 3x, Integration time 100mS (32x),
+ * typical 2000 ADC counts are observed for 49.8 uW per sq cm (340.134 lux)
+ * for apds9306 and 43 uW per sq cm (293.69 lux) for apds9306-065.
+ * Assuming lux per count is linear across all integration time ranges.
+ *
+ * Lux = (raw + offset) * scale; offset can be any value by userspace.
+ * HG = Hardware Gain; ITG = Gain by changing integration time.
+ * Scale table by IIO GTS Helpers = (1 / HG) * (1 / ITG) * Multiplier.
+ *
+ * The Lux values provided in the datasheet are at ITG=32x and HG=3x,
+ * at typical 2000 count for both variants of the device.
+ *
+ * Lux per ADC count at 3x and 32x for apds9306 = 340.134 / 2000
+ * Lux per ADC count at 3x and 32x for apds9306-065 = 293.69 / 2000
+ *
+ * The Multiplier for the scale table provided to userspace:
+ * IIO GTS scale Multiplier for apds9306 = (340.134 / 2000) * 32 * 3 = 16.326432
+ * and for apds9306-065 = (293.69 / 2000) * 32 * 3 = 14.09712
+ */
+static const struct part_id_gts_multiplier apds9306_gts_mul[] = {
+ {
+ .part_id = 0xB1,
+ .max_scale_int = 16,
+ .max_scale_nano = 3264320,
+ }, {
+ .part_id = 0xB3,
+ .max_scale_int = 14,
+ .max_scale_nano = 9712000,
+ },
+};
+
+static const int apds9306_repeat_rate_freq[APDS9306_NUM_REPEAT_RATES][2] = {
+ { 40, 0 },
+ { 20, 0 },
+ { 10, 0 },
+ { 5, 0 },
+ { 2, 0 },
+ { 1, 0 },
+ { 0, 500000 },
+};
+
+static const int apds9306_repeat_rate_period[APDS9306_NUM_REPEAT_RATES] = {
+ 25000, 50000, 100000, 200000, 500000, 1000000, 2000000,
+};
+
+/**
+ * struct apds9306_regfields - apds9306 regmap fields definitions
+ *
+ * @sw_reset: Software reset regfield
+ * @en: Enable regfield
+ * @intg_time: Resolution regfield
+ * @repeat_rate: Measurement Rate regfield
+ * @gain: Hardware gain regfield
+ * @int_src: Interrupt channel regfield
+ * @int_thresh_var_en: Interrupt variance threshold regfield
+ * @int_en: Interrupt enable regfield
+ * @int_persist_val: Interrupt persistence regfield
+ * @int_thresh_var_val: Interrupt threshold variance value regfield
+ */
+struct apds9306_regfields {
+ struct regmap_field *sw_reset;
+ struct regmap_field *en;
+ struct regmap_field *intg_time;
+ struct regmap_field *repeat_rate;
+ struct regmap_field *gain;
+ struct regmap_field *int_src;
+ struct regmap_field *int_thresh_var_en;
+ struct regmap_field *int_en;
+ struct regmap_field *int_persist_val;
+ struct regmap_field *int_thresh_var_val;
+};
+
+/**
+ * struct apds9306_data - apds9306 private data and registers definitions
+ *
+ * @dev: Pointer to the device structure
+ * @gts: IIO Gain Time Scale structure
+ * @mutex: Lock for protecting adc reads, device settings changes where
+ * some calculations are required before or after setting or
+ * getting the raw settings values from regmap writes or reads
+ * respectively.
+ * @regmap: Regmap structure pointer
+ * @rf: Regmap register fields structure
+ * @nlux_per_count: Nano lux per ADC count for a particular model
+ * @read_data_available: Flag set by IRQ handler for ADC data available
+ */
+struct apds9306_data {
+ struct device *dev;
+ struct iio_gts gts;
+
+ struct mutex mutex;
+
+ struct regmap *regmap;
+ struct apds9306_regfields rf;
+
+ int nlux_per_count;
+ int read_data_available;
+};
+
+/*
+ * Available scales with gain 1x - 18x, timings 3.125, 25, 50, 100, 200, 400 ms
+ * Time impacts to gain: 1x, 8x, 16x, 32x, 64x, 128x
+ */
+#define APDS9306_GSEL_1X 0x00
+#define APDS9306_GSEL_3X 0x01
+#define APDS9306_GSEL_6X 0x02
+#define APDS9306_GSEL_9X 0x03
+#define APDS9306_GSEL_18X 0x04
+
+static const struct iio_gain_sel_pair apds9306_gains[] = {
+ GAIN_SCALE_GAIN(1, APDS9306_GSEL_1X),
+ GAIN_SCALE_GAIN(3, APDS9306_GSEL_3X),
+ GAIN_SCALE_GAIN(6, APDS9306_GSEL_6X),
+ GAIN_SCALE_GAIN(9, APDS9306_GSEL_9X),
+ GAIN_SCALE_GAIN(18, APDS9306_GSEL_18X),
+};
+
+#define APDS9306_MEAS_MODE_400MS 0x00
+#define APDS9306_MEAS_MODE_200MS 0x01
+#define APDS9306_MEAS_MODE_100MS 0x02
+#define APDS9306_MEAS_MODE_50MS 0x03
+#define APDS9306_MEAS_MODE_25MS 0x04
+#define APDS9306_MEAS_MODE_3125US 0x05
+
+static const struct iio_itime_sel_mul apds9306_itimes[] = {
+ GAIN_SCALE_ITIME_US(400000, APDS9306_MEAS_MODE_400MS, BIT(7)),
+ GAIN_SCALE_ITIME_US(200000, APDS9306_MEAS_MODE_200MS, BIT(6)),
+ GAIN_SCALE_ITIME_US(100000, APDS9306_MEAS_MODE_100MS, BIT(5)),
+ GAIN_SCALE_ITIME_US(50000, APDS9306_MEAS_MODE_50MS, BIT(4)),
+ GAIN_SCALE_ITIME_US(25000, APDS9306_MEAS_MODE_25MS, BIT(3)),
+ GAIN_SCALE_ITIME_US(3125, APDS9306_MEAS_MODE_3125US, BIT(0)),
+};
+
+static const struct iio_event_spec apds9306_event_spec[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD),
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH_ADAPTIVE,
+ .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+static const struct iio_chan_spec apds9306_channels_with_events[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
+ .event_spec = apds9306_event_spec,
+ .num_event_specs = ARRAY_SIZE(apds9306_event_spec),
+ }, {
+ .type = IIO_INTENSITY,
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .channel2 = IIO_MOD_LIGHT_CLEAR,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .modified = 1,
+ .event_spec = apds9306_event_spec,
+ .num_event_specs = ARRAY_SIZE(apds9306_event_spec),
+ },
+};
+
+static const struct iio_chan_spec apds9306_channels_without_events[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
+ }, {
+ .type = IIO_INTENSITY,
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_CLEAR,
+ },
+};
+
+/* INT_PERSISTENCE available */
+static IIO_CONST_ATTR(thresh_either_period_available, "[0 1 15]");
+
+/* ALS_THRESH_VAR available */
+static IIO_CONST_ATTR(thresh_adaptive_either_values_available, "[0 1 7]");
+
+static struct attribute *apds9306_event_attributes[] = {
+ &iio_const_attr_thresh_either_period_available.dev_attr.attr,
+ &iio_const_attr_thresh_adaptive_either_values_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group apds9306_event_attr_group = {
+ .attrs = apds9306_event_attributes,
+};
+
+static const struct regmap_range apds9306_readable_ranges[] = {
+ regmap_reg_range(APDS9306_MAIN_CTRL_REG, APDS9306_ALS_THRES_VAR_REG)
+};
+
+static const struct regmap_range apds9306_writable_ranges[] = {
+ regmap_reg_range(APDS9306_MAIN_CTRL_REG, APDS9306_ALS_GAIN_REG),
+ regmap_reg_range(APDS9306_INT_CFG_REG, APDS9306_ALS_THRES_VAR_REG)
+};
+
+static const struct regmap_range apds9306_volatile_ranges[] = {
+ regmap_reg_range(APDS9306_MAIN_STATUS_REG, APDS9306_MAIN_STATUS_REG),
+ regmap_reg_range(APDS9306_CLEAR_DATA_0_REG, APDS9306_ALS_DATA_2_REG)
+};
+
+static const struct regmap_range apds9306_precious_ranges[] = {
+ regmap_reg_range(APDS9306_MAIN_STATUS_REG, APDS9306_MAIN_STATUS_REG)
+};
+
+static const struct regmap_access_table apds9306_readable_table = {
+ .yes_ranges = apds9306_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(apds9306_readable_ranges)
+};
+
+static const struct regmap_access_table apds9306_writable_table = {
+ .yes_ranges = apds9306_writable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(apds9306_writable_ranges)
+};
+
+static const struct regmap_access_table apds9306_volatile_table = {
+ .yes_ranges = apds9306_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(apds9306_volatile_ranges)
+};
+
+static const struct regmap_access_table apds9306_precious_table = {
+ .yes_ranges = apds9306_precious_ranges,
+ .n_yes_ranges = ARRAY_SIZE(apds9306_precious_ranges)
+};
+
+static const struct regmap_config apds9306_regmap = {
+ .name = "apds9306_regmap",
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &apds9306_readable_table,
+ .wr_table = &apds9306_writable_table,
+ .volatile_table = &apds9306_volatile_table,
+ .precious_table = &apds9306_precious_table,
+ .max_register = APDS9306_ALS_THRES_VAR_REG,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static const struct reg_field apds9306_rf_sw_reset =
+ REG_FIELD(APDS9306_MAIN_CTRL_REG, 4, 4);
+
+static const struct reg_field apds9306_rf_en =
+ REG_FIELD(APDS9306_MAIN_CTRL_REG, 1, 1);
+
+static const struct reg_field apds9306_rf_intg_time =
+ REG_FIELD(APDS9306_ALS_MEAS_RATE_REG, 4, 6);
+
+static const struct reg_field apds9306_rf_repeat_rate =
+ REG_FIELD(APDS9306_ALS_MEAS_RATE_REG, 0, 2);
+
+static const struct reg_field apds9306_rf_gain =
+ REG_FIELD(APDS9306_ALS_GAIN_REG, 0, 2);
+
+static const struct reg_field apds9306_rf_int_src =
+ REG_FIELD(APDS9306_INT_CFG_REG, 4, 5);
+
+static const struct reg_field apds9306_rf_int_thresh_var_en =
+ REG_FIELD(APDS9306_INT_CFG_REG, 3, 3);
+
+static const struct reg_field apds9306_rf_int_en =
+ REG_FIELD(APDS9306_INT_CFG_REG, 2, 2);
+
+static const struct reg_field apds9306_rf_int_persist_val =
+ REG_FIELD(APDS9306_INT_PERSISTENCE_REG, 4, 7);
+
+static const struct reg_field apds9306_rf_int_thresh_var_val =
+ REG_FIELD(APDS9306_ALS_THRES_VAR_REG, 0, 2);
+
+static int apds9306_regfield_init(struct apds9306_data *data)
+{
+ struct device *dev = data->dev;
+ struct regmap *regmap = data->regmap;
+ struct regmap_field *tmp;
+ struct apds9306_regfields *rf = &data->rf;
+
+ tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_sw_reset);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+ rf->sw_reset = tmp;
+
+ tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_en);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+ rf->en = tmp;
+
+ tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_intg_time);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+ rf->intg_time = tmp;
+
+ tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_repeat_rate);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+ rf->repeat_rate = tmp;
+
+ tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_gain);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+ rf->gain = tmp;
+
+ tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_src);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+ rf->int_src = tmp;
+
+ tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_thresh_var_en);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+ rf->int_thresh_var_en = tmp;
+
+ tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_en);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+ rf->int_en = tmp;
+
+ tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_persist_val);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+ rf->int_persist_val = tmp;
+
+ tmp = devm_regmap_field_alloc(dev, regmap, apds9306_rf_int_thresh_var_val);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+ rf->int_thresh_var_val = tmp;
+
+ return 0;
+}
+
+static int apds9306_power_state(struct apds9306_data *data, bool state)
+{
+ struct apds9306_regfields *rf = &data->rf;
+ int ret;
+
+ /* Reset not included as it causes ugly I2C bus error */
+ if (state) {
+ ret = regmap_field_write(rf->en, 1);
+ if (ret)
+ return ret;
+ /* 5ms wake up time */
+ fsleep(5000);
+ return 0;
+ }
+
+ return regmap_field_write(rf->en, 0);
+}
+
+static int apds9306_read_data(struct apds9306_data *data, int *val, int reg)
+{
+ struct device *dev = data->dev;
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct apds9306_regfields *rf = &data->rf;
+ u64 ev_code;
+ int ret, delay, intg_time, intg_time_idx, repeat_rate_idx, int_src;
+ int status = 0;
+ u8 buff[3];
+
+ ret = pm_runtime_resume_and_get(data->dev);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_read(rf->intg_time, &intg_time_idx);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_read(rf->repeat_rate, &repeat_rate_idx);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_read(rf->int_src, &int_src);
+ if (ret)
+ return ret;
+
+ intg_time = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx);
+ if (intg_time < 0)
+ return intg_time;
+
+ /* Whichever is greater - integration time period or sampling period. */
+ delay = max(intg_time, apds9306_repeat_rate_period[repeat_rate_idx]);
+
+ /*
+ * Clear stale data flag that might have been set by the interrupt
+ * handler if it got data available flag set in the status reg.
+ */
+ data->read_data_available = 0;
+
+ /*
+ * If this function runs parallel with the interrupt handler, either
+ * this reads and clears the status registers or the interrupt handler
+ * does. The interrupt handler sets a flag for read data available
+ * in our private structure which we read here.
+ */
+ ret = regmap_read_poll_timeout(data->regmap, APDS9306_MAIN_STATUS_REG,
+ status, data->read_data_available ||
+ (status & (APDS9306_ALS_DATA_STAT_MASK |
+ APDS9306_ALS_INT_STAT_MASK)),
+ APDS9306_ALS_READ_DATA_DELAY_US, delay * 2);
+ if (ret)
+ return ret;
+
+ /* If we reach here before the interrupt handler we push an event */
+ if ((status & APDS9306_ALS_INT_STAT_MASK)) {
+ if (int_src == APDS9306_INT_SRC_ALS)
+ ev_code = IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER);
+ else
+ ev_code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, 0,
+ IIO_MOD_LIGHT_CLEAR,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER);
+
+ iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev));
+ }
+
+ ret = regmap_bulk_read(data->regmap, reg, buff, sizeof(buff));
+ if (ret) {
+ dev_err_ratelimited(dev, "read data failed\n");
+ return ret;
+ }
+
+ *val = get_unaligned_le24(&buff);
+
+ pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+
+ return 0;
+}
+
+static int apds9306_intg_time_get(struct apds9306_data *data, int *val2)
+{
+ struct apds9306_regfields *rf = &data->rf;
+ int ret, intg_time_idx;
+
+ ret = regmap_field_read(rf->intg_time, &intg_time_idx);
+ if (ret)
+ return ret;
+
+ ret = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx);
+ if (ret < 0)
+ return ret;
+
+ *val2 = ret;
+
+ return 0;
+}
+
+static int apds9306_intg_time_set(struct apds9306_data *data, int val2)
+{
+ struct device *dev = data->dev;
+ struct apds9306_regfields *rf = &data->rf;
+ int ret, intg_old, gain_old, gain_new, gain_new_closest, intg_time_idx;
+ int gain_idx;
+ bool ok;
+
+ if (!iio_gts_valid_time(&data->gts, val2)) {
+ dev_err_ratelimited(dev, "Unsupported integration time %u\n", val2);
+ return -EINVAL;
+ }
+
+ ret = regmap_field_read(rf->intg_time, &intg_time_idx);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_read(rf->gain, &gain_idx);
+ if (ret)
+ return ret;
+
+ intg_old = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx);
+ if (ret < 0)
+ return ret;
+
+ if (intg_old == val2)
+ return 0;
+
+ gain_old = iio_gts_find_gain_by_sel(&data->gts, gain_idx);
+ if (gain_old < 0)
+ return gain_old;
+
+ iio_gts_find_new_gain_by_old_gain_time(&data->gts, gain_old, intg_old,
+ val2, &gain_new);
+
+ if (gain_new < 0) {
+ dev_err_ratelimited(dev, "Unsupported gain with time\n");
+ return gain_new;
+ }
+
+ gain_new_closest = iio_find_closest_gain_low(&data->gts, gain_new, &ok);
+ if (gain_new_closest < 0) {
+ gain_new_closest = iio_gts_get_min_gain(&data->gts);
+ if (gain_new_closest < 0)
+ return gain_new_closest;
+ }
+ if (!ok)
+ dev_dbg(dev, "Unable to find optimum gain, setting minimum");
+
+ ret = iio_gts_find_sel_by_int_time(&data->gts, val2);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_field_write(rf->intg_time, ret);
+ if (ret)
+ return ret;
+
+ ret = iio_gts_find_sel_by_gain(&data->gts, gain_new_closest);
+ if (ret < 0)
+ return ret;
+
+ return regmap_field_write(rf->gain, ret);
+}
+
+static int apds9306_sampling_freq_get(struct apds9306_data *data, int *val,
+ int *val2)
+{
+ struct apds9306_regfields *rf = &data->rf;
+ int ret, repeat_rate_idx;
+
+ ret = regmap_field_read(rf->repeat_rate, &repeat_rate_idx);
+ if (ret)
+ return ret;
+
+ if (repeat_rate_idx >= ARRAY_SIZE(apds9306_repeat_rate_freq))
+ return -EINVAL;
+
+ *val = apds9306_repeat_rate_freq[repeat_rate_idx][0];
+ *val2 = apds9306_repeat_rate_freq[repeat_rate_idx][1];
+
+ return 0;
+}
+
+static int apds9306_sampling_freq_set(struct apds9306_data *data, int val,
+ int val2)
+{
+ struct apds9306_regfields *rf = &data->rf;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(apds9306_repeat_rate_freq); i++) {
+ if (apds9306_repeat_rate_freq[i][0] == val &&
+ apds9306_repeat_rate_freq[i][1] == val2)
+ return regmap_field_write(rf->repeat_rate, i);
+ }
+
+ return -EINVAL;
+}
+
+static int apds9306_scale_get(struct apds9306_data *data, int *val, int *val2)
+{
+ struct apds9306_regfields *rf = &data->rf;
+ int gain, intg, ret, intg_time_idx, gain_idx;
+
+ ret = regmap_field_read(rf->gain, &gain_idx);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_read(rf->intg_time, &intg_time_idx);
+ if (ret)
+ return ret;
+
+ gain = iio_gts_find_gain_by_sel(&data->gts, gain_idx);
+ if (gain < 0)
+ return gain;
+
+ intg = iio_gts_find_int_time_by_sel(&data->gts, intg_time_idx);
+ if (intg < 0)
+ return intg;
+
+ return iio_gts_get_scale(&data->gts, gain, intg, val, val2);
+}
+
+static int apds9306_scale_set(struct apds9306_data *data, int val, int val2)
+{
+ struct apds9306_regfields *rf = &data->rf;
+ int i, ret, time_sel, gain_sel, intg_time_idx;
+
+ ret = regmap_field_read(rf->intg_time, &intg_time_idx);
+ if (ret)
+ return ret;
+
+ ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts,
+ intg_time_idx, val, val2, &gain_sel);
+ if (ret) {
+ for (i = 0; i < data->gts.num_itime; i++) {
+ time_sel = data->gts.itime_table[i].sel;
+
+ if (time_sel == intg_time_idx)
+ continue;
+
+ ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts,
+ time_sel, val, val2, &gain_sel);
+ if (!ret)
+ break;
+ }
+ if (ret)
+ return -EINVAL;
+
+ ret = regmap_field_write(rf->intg_time, time_sel);
+ if (ret)
+ return ret;
+ }
+
+ return regmap_field_write(rf->gain, gain_sel);
+}
+
+static int apds9306_event_period_get(struct apds9306_data *data, int *val)
+{
+ struct apds9306_regfields *rf = &data->rf;
+ int period, ret;
+
+ ret = regmap_field_read(rf->int_persist_val, &period);
+ if (ret)
+ return ret;
+
+ if (!in_range(period, 0, APDS9306_ALS_PERSIST_VAL_MAX))
+ return -EINVAL;
+
+ *val = period;
+
+ return ret;
+}
+
+static int apds9306_event_period_set(struct apds9306_data *data, int val)
+{
+ struct apds9306_regfields *rf = &data->rf;
+
+ if (!in_range(val, 0, APDS9306_ALS_PERSIST_VAL_MAX))
+ return -EINVAL;
+
+ return regmap_field_write(rf->int_persist_val, val);
+}
+
+static int apds9306_event_thresh_get(struct apds9306_data *data, int dir,
+ int *val)
+{
+ int var, ret;
+ u8 buff[3];
+
+ if (dir == IIO_EV_DIR_RISING)
+ var = APDS9306_ALS_THRES_UP_0_REG;
+ else if (dir == IIO_EV_DIR_FALLING)
+ var = APDS9306_ALS_THRES_LOW_0_REG;
+ else
+ return -EINVAL;
+
+ ret = regmap_bulk_read(data->regmap, var, buff, sizeof(buff));
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_le24(&buff);
+
+ return 0;
+}
+
+static int apds9306_event_thresh_set(struct apds9306_data *data, int dir,
+ int val)
+{
+ int var;
+ u8 buff[3];
+
+ if (dir == IIO_EV_DIR_RISING)
+ var = APDS9306_ALS_THRES_UP_0_REG;
+ else if (dir == IIO_EV_DIR_FALLING)
+ var = APDS9306_ALS_THRES_LOW_0_REG;
+ else
+ return -EINVAL;
+
+ if (!in_range(val, 0, APDS9306_ALS_THRES_VAL_MAX))
+ return -EINVAL;
+
+ put_unaligned_le24(val, buff);
+
+ return regmap_bulk_write(data->regmap, var, buff, sizeof(buff));
+}
+
+static int apds9306_event_thresh_adaptive_get(struct apds9306_data *data, int *val)
+{
+ struct apds9306_regfields *rf = &data->rf;
+ int thr_adpt, ret;
+
+ ret = regmap_field_read(rf->int_thresh_var_val, &thr_adpt);
+ if (ret)
+ return ret;
+
+ if (!in_range(thr_adpt, 0, APDS9306_ALS_THRES_VAR_VAL_MAX))
+ return -EINVAL;
+
+ *val = thr_adpt;
+
+ return ret;
+}
+
+static int apds9306_event_thresh_adaptive_set(struct apds9306_data *data, int val)
+{
+ struct apds9306_regfields *rf = &data->rf;
+
+ if (!in_range(val, 0, APDS9306_ALS_THRES_VAR_VAL_MAX))
+ return -EINVAL;
+
+ return regmap_field_write(rf->int_thresh_var_val, val);
+}
+
+static int apds9306_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct apds9306_data *data = iio_priv(indio_dev);
+ int ret, reg;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->channel2 == IIO_MOD_LIGHT_CLEAR)
+ reg = APDS9306_CLEAR_DATA_0_REG;
+ else
+ reg = APDS9306_ALS_DATA_0_REG;
+ /*
+ * Changing device parameters during adc operation, resets
+ * the ADC which has to avoided.
+ */
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+ ret = apds9306_read_data(data, val, reg);
+ iio_device_release_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_INT_TIME:
+ ret = apds9306_intg_time_get(data, val2);
+ if (ret)
+ return ret;
+ *val = 0;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = apds9306_sampling_freq_get(data, val, val2);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SCALE:
+ ret = apds9306_scale_get(data, val, val2);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+};
+
+static int apds9306_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct apds9306_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ return iio_gts_avail_times(&data->gts, vals, type, length);
+ case IIO_CHAN_INFO_SCALE:
+ return iio_gts_all_avail_scales(&data->gts, vals, type, length);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *length = ARRAY_SIZE(apds9306_repeat_rate_freq) * 2;
+ *vals = (const int *)apds9306_repeat_rate_freq;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int apds9306_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+ case IIO_CHAN_INFO_INT_TIME:
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int apds9306_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct apds9306_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->mutex);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ if (val)
+ return -EINVAL;
+ return apds9306_intg_time_set(data, val2);
+ case IIO_CHAN_INFO_SCALE:
+ return apds9306_scale_set(data, val, val2);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return apds9306_sampling_freq_set(data, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static irqreturn_t apds9306_irq_handler(int irq, void *priv)
+{
+ struct iio_dev *indio_dev = priv;
+ struct apds9306_data *data = iio_priv(indio_dev);
+ struct apds9306_regfields *rf = &data->rf;
+ u64 ev_code;
+ int ret, status, int_src;
+
+ /*
+ * The interrupt line is released and the interrupt flag is
+ * cleared as a result of reading the status register. All the
+ * status flags are cleared as a result of this read.
+ */
+ ret = regmap_read(data->regmap, APDS9306_MAIN_STATUS_REG, &status);
+ if (ret < 0) {
+ dev_err_ratelimited(data->dev, "status reg read failed\n");
+ return IRQ_HANDLED;
+ }
+
+ ret = regmap_field_read(rf->int_src, &int_src);
+ if (ret)
+ return ret;
+
+ if ((status & APDS9306_ALS_INT_STAT_MASK)) {
+ if (int_src == APDS9306_INT_SRC_ALS)
+ ev_code = IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER);
+ else
+ ev_code = IIO_MOD_EVENT_CODE(IIO_INTENSITY, 0,
+ IIO_MOD_LIGHT_CLEAR,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER);
+
+ iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev));
+ }
+
+ /*
+ * If a one-shot read through sysfs is underway at the same time
+ * as this interrupt handler is executing and a read data available
+ * flag was set, this flag is set to inform read_poll_timeout()
+ * to exit.
+ */
+ if ((status & APDS9306_ALS_DATA_STAT_MASK))
+ data->read_data_available = 1;
+
+ return IRQ_HANDLED;
+}
+
+static int apds9306_read_event(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 apds9306_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD)
+ ret = apds9306_event_period_get(data, val);
+ else
+ ret = apds9306_event_thresh_get(data, dir, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ case IIO_EV_TYPE_THRESH_ADAPTIVE:
+ ret = apds9306_event_thresh_adaptive_get(data, val);
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int apds9306_write_event(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 apds9306_data *data = iio_priv(indio_dev);
+
+ switch (type) {
+ case IIO_EV_TYPE_THRESH:
+ if (dir == IIO_EV_DIR_EITHER && info == IIO_EV_INFO_PERIOD)
+ return apds9306_event_period_set(data, val);
+
+ return apds9306_event_thresh_set(data, dir, val);
+ case IIO_EV_TYPE_THRESH_ADAPTIVE:
+ return apds9306_event_thresh_adaptive_set(data, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int apds9306_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct apds9306_data *data = iio_priv(indio_dev);
+ struct apds9306_regfields *rf = &data->rf;
+ int int_en, int_src, ret;
+
+ switch (type) {
+ case IIO_EV_TYPE_THRESH: {
+ guard(mutex)(&data->mutex);
+
+ ret = regmap_field_read(rf->int_src, &int_src);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_read(rf->int_en, &int_en);
+ if (ret)
+ return ret;
+
+ if (chan->type == IIO_LIGHT)
+ return int_en && (int_src == APDS9306_INT_SRC_ALS);
+
+ if (chan->type == IIO_INTENSITY)
+ return int_en && (int_src == APDS9306_INT_SRC_CLEAR);
+
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_THRESH_ADAPTIVE:
+ ret = regmap_field_read(rf->int_thresh_var_en, &int_en);
+ if (ret)
+ return ret;
+
+ return int_en;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int apds9306_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ int state)
+{
+ struct apds9306_data *data = iio_priv(indio_dev);
+ struct apds9306_regfields *rf = &data->rf;
+ int ret, enabled;
+
+ switch (type) {
+ case IIO_EV_TYPE_THRESH: {
+ guard(mutex)(&data->mutex);
+
+ ret = regmap_field_read(rf->int_en, &enabled);
+ if (ret)
+ return ret;
+
+ /*
+ * If interrupt is enabled, the channel is set before enabling
+ * the interrupt. In case of disable, no need to switch
+ * channels. In case of different channel is selected while
+ * interrupt in on, just change the channel.
+ */
+ if (state) {
+ if (chan->type == IIO_LIGHT)
+ ret = regmap_field_write(rf->int_src, 1);
+ else if (chan->type == IIO_INTENSITY)
+ ret = regmap_field_write(rf->int_src, 0);
+ else
+ return -EINVAL;
+
+ if (ret)
+ return ret;
+
+ if (enabled)
+ return 0;
+
+ ret = regmap_field_write(rf->int_en, 1);
+ if (ret)
+ return ret;
+
+ return pm_runtime_resume_and_get(data->dev);
+ } else {
+ if (!enabled)
+ return 0;
+
+ ret = regmap_field_write(rf->int_en, 0);
+ if (ret)
+ return ret;
+
+ pm_runtime_mark_last_busy(data->dev);
+ pm_runtime_put_autosuspend(data->dev);
+
+ return 0;
+ }
+ }
+ case IIO_EV_TYPE_THRESH_ADAPTIVE:
+ if (state)
+ return regmap_field_write(rf->int_thresh_var_en, 1);
+ else
+ return regmap_field_write(rf->int_thresh_var_en, 0);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info apds9306_info_no_events = {
+ .read_avail = apds9306_read_avail,
+ .read_raw = apds9306_read_raw,
+ .write_raw = apds9306_write_raw,
+ .write_raw_get_fmt = apds9306_write_raw_get_fmt,
+};
+
+static const struct iio_info apds9306_info = {
+ .read_avail = apds9306_read_avail,
+ .read_raw = apds9306_read_raw,
+ .write_raw = apds9306_write_raw,
+ .write_raw_get_fmt = apds9306_write_raw_get_fmt,
+ .read_event_value = apds9306_read_event,
+ .write_event_value = apds9306_write_event,
+ .read_event_config = apds9306_read_event_config,
+ .write_event_config = apds9306_write_event_config,
+ .event_attrs = &apds9306_event_attr_group,
+};
+
+static int apds9306_init_iio_gts(struct apds9306_data *data)
+{
+ int i, ret, part_id;
+
+ ret = regmap_read(data->regmap, APDS9306_PART_ID_REG, &part_id);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(apds9306_gts_mul); i++)
+ if (part_id == apds9306_gts_mul[i].part_id)
+ break;
+
+ if (i == ARRAY_SIZE(apds9306_gts_mul))
+ return -ENOENT;
+
+ return devm_iio_init_iio_gts(data->dev,
+ apds9306_gts_mul[i].max_scale_int,
+ apds9306_gts_mul[i].max_scale_nano,
+ apds9306_gains, ARRAY_SIZE(apds9306_gains),
+ apds9306_itimes, ARRAY_SIZE(apds9306_itimes),
+ &data->gts);
+}
+
+static void apds9306_powerdown(void *ptr)
+{
+ struct apds9306_data *data = (struct apds9306_data *)ptr;
+ struct apds9306_regfields *rf = &data->rf;
+ int ret;
+
+ ret = regmap_field_write(rf->int_thresh_var_en, 0);
+ if (ret)
+ return;
+
+ ret = regmap_field_write(rf->int_en, 0);
+ if (ret)
+ return;
+
+ apds9306_power_state(data, false);
+}
+
+static int apds9306_device_init(struct apds9306_data *data)
+{
+ struct apds9306_regfields *rf = &data->rf;
+ int ret;
+
+ ret = apds9306_init_iio_gts(data);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(rf->intg_time, APDS9306_MEAS_MODE_100MS);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(rf->repeat_rate, APDS9306_SAMP_FREQ_10HZ);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(rf->gain, APDS9306_GSEL_3X);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(rf->int_src, APDS9306_INT_SRC_ALS);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_write(rf->int_en, 0);
+ if (ret)
+ return ret;
+
+ return regmap_field_write(rf->int_thresh_var_en, 0);
+}
+
+static int apds9306_pm_init(struct apds9306_data *data)
+{
+ struct device *dev = data->dev;
+ int ret;
+
+ ret = apds9306_power_state(data, true);
+ if (ret)
+ return ret;
+
+ ret = pm_runtime_set_active(dev);
+ if (ret)
+ return ret;
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+ pm_runtime_set_autosuspend_delay(dev, 5000);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_get(dev);
+
+ return 0;
+}
+
+static int apds9306_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct apds9306_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+
+ mutex_init(&data->mutex);
+
+ data->regmap = devm_regmap_init_i2c(client, &apds9306_regmap);
+ if (IS_ERR(data->regmap))
+ return dev_err_probe(dev, PTR_ERR(data->regmap),
+ "regmap initialization failed\n");
+
+ data->dev = dev;
+ i2c_set_clientdata(client, indio_dev);
+
+ ret = apds9306_regfield_init(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "regfield initialization failed\n");
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable regulator\n");
+
+ indio_dev->name = "apds9306";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ if (client->irq) {
+ indio_dev->info = &apds9306_info;
+ indio_dev->channels = apds9306_channels_with_events;
+ indio_dev->num_channels = ARRAY_SIZE(apds9306_channels_with_events);
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ apds9306_irq_handler, IRQF_ONESHOT,
+ "apds9306_event", indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to assign interrupt.\n");
+ } else {
+ indio_dev->info = &apds9306_info_no_events;
+ indio_dev->channels = apds9306_channels_without_events;
+ indio_dev->num_channels =
+ ARRAY_SIZE(apds9306_channels_without_events);
+ }
+
+ ret = apds9306_pm_init(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed pm init\n");
+
+ ret = apds9306_device_init(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to init device\n");
+
+ ret = devm_add_action_or_reset(dev, apds9306_powerdown, data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add action or reset\n");
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed iio device registration\n");
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
+static int apds9306_runtime_suspend(struct device *dev)
+{
+ struct apds9306_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return apds9306_power_state(data, false);
+}
+
+static int apds9306_runtime_resume(struct device *dev)
+{
+ struct apds9306_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return apds9306_power_state(data, true);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(apds9306_pm_ops,
+ apds9306_runtime_suspend,
+ apds9306_runtime_resume,
+ NULL);
+
+static const struct of_device_id apds9306_of_match[] = {
+ { .compatible = "avago,apds9306" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, apds9306_of_match);
+
+static struct i2c_driver apds9306_driver = {
+ .driver = {
+ .name = "apds9306",
+ .pm = pm_ptr(&apds9306_pm_ops),
+ .of_match_table = apds9306_of_match,
+ },
+ .probe = apds9306_probe,
+};
+module_i2c_driver(apds9306_driver);
+
+MODULE_AUTHOR("Subhajit Ghosh <subhajit.ghosh@tweaklogic.com>");
+MODULE_DESCRIPTION("APDS9306 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_GTS_HELPER);
diff --git a/drivers/iio/light/st_uvis25_core.c b/drivers/iio/light/st_uvis25_core.c
index 50f95c5d2060aa..d4e17079b2f43a 100644
--- a/drivers/iio/light/st_uvis25_core.c
+++ b/drivers/iio/light/st_uvis25_core.c
@@ -291,7 +291,7 @@ int st_uvis25_probe(struct device *dev, int irq, struct regmap *regmap)
if (!iio_dev)
return -ENOMEM;
- dev_set_drvdata(dev, (void *)iio_dev);
+ dev_set_drvdata(dev, iio_dev);
hw = iio_priv(iio_dev);
hw->irq = irq;
diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c
index 62e9e93d915dc8..09f53d987c7df5 100644
--- a/drivers/iio/pressure/bmp280-core.c
+++ b/drivers/iio/pressure/bmp280-core.c
@@ -27,20 +27,20 @@
#include <linux/bitops.h>
#include <linux/bitfield.h>
-#include <linux/device.h>
-#include <linux/module.h>
-#include <linux/nvmem-provider.h>
-#include <linux/regmap.h>
+#include <linux/completion.h>
#include <linux/delay.h>
-#include <linux/iio/iio.h>
-#include <linux/iio/sysfs.h>
+#include <linux/device.h>
#include <linux/gpio/consumer.h>
-#include <linux/regulator/consumer.h>
#include <linux/interrupt.h>
#include <linux/irq.h> /* For irq_get_irq_data() */
-#include <linux/completion.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
#include <linux/pm_runtime.h>
#include <linux/random.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/iio/iio.h>
#include <asm/unaligned.h>
diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c
index 1ff091b2f764d4..7d882e15e5564a 100644
--- a/drivers/iio/pressure/dps310.c
+++ b/drivers/iio/pressure/dps310.c
@@ -171,7 +171,7 @@ static int dps310_temp_workaround(struct dps310_data *data)
int reg;
rc = regmap_read(data->regmap, 0x32, &reg);
- if (rc)
+ if (rc < 0)
return rc;
/*
@@ -256,24 +256,24 @@ static int dps310_startup(struct dps310_data *data)
return dps310_temp_workaround(data);
}
-static int dps310_get_pres_precision(struct dps310_data *data)
+static int dps310_get_pres_precision(struct dps310_data *data, int *val)
{
- int rc;
- int val;
+ int reg_val, rc;
- rc = regmap_read(data->regmap, DPS310_PRS_CFG, &val);
+ rc = regmap_read(data->regmap, DPS310_PRS_CFG, &reg_val);
if (rc < 0)
return rc;
- return BIT(val & GENMASK(2, 0));
+ *val = BIT(reg_val & GENMASK(2, 0));
+
+ return 0;
}
-static int dps310_get_temp_precision(struct dps310_data *data)
+static int dps310_get_temp_precision(struct dps310_data *data, int *val)
{
- int rc;
- int val;
+ int reg_val, rc;
- rc = regmap_read(data->regmap, DPS310_TMP_CFG, &val);
+ rc = regmap_read(data->regmap, DPS310_TMP_CFG, &reg_val);
if (rc < 0)
return rc;
@@ -281,7 +281,9 @@ static int dps310_get_temp_precision(struct dps310_data *data)
* Scale factor is bottom 4 bits of the register, but 1111 is
* reserved so just grab bottom three
*/
- return BIT(val & GENMASK(2, 0));
+ *val = BIT(reg_val & GENMASK(2, 0));
+
+ return 0;
}
/* Called with lock held */
@@ -350,48 +352,56 @@ static int dps310_set_temp_samp_freq(struct dps310_data *data, int freq)
DPS310_TMP_RATE_BITS, val);
}
-static int dps310_get_pres_samp_freq(struct dps310_data *data)
+static int dps310_get_pres_samp_freq(struct dps310_data *data, int *val)
{
- int rc;
- int val;
+ int reg_val, rc;
- rc = regmap_read(data->regmap, DPS310_PRS_CFG, &val);
+ rc = regmap_read(data->regmap, DPS310_PRS_CFG, &reg_val);
if (rc < 0)
return rc;
- return BIT((val & DPS310_PRS_RATE_BITS) >> 4);
+ *val = BIT((reg_val & DPS310_PRS_RATE_BITS) >> 4);
+
+ return 0;
}
-static int dps310_get_temp_samp_freq(struct dps310_data *data)
+static int dps310_get_temp_samp_freq(struct dps310_data *data, int *val)
{
- int rc;
- int val;
+ int reg_val, rc;
- rc = regmap_read(data->regmap, DPS310_TMP_CFG, &val);
+ rc = regmap_read(data->regmap, DPS310_TMP_CFG, &reg_val);
if (rc < 0)
return rc;
- return BIT((val & DPS310_TMP_RATE_BITS) >> 4);
+ *val = BIT((reg_val & DPS310_TMP_RATE_BITS) >> 4);
+
+ return 0;
}
-static int dps310_get_pres_k(struct dps310_data *data)
+static int dps310_get_pres_k(struct dps310_data *data, int *val)
{
- int rc = dps310_get_pres_precision(data);
+ int reg_val, rc;
+ rc = regmap_read(data->regmap, DPS310_PRS_CFG, &reg_val);
if (rc < 0)
return rc;
- return scale_factors[ilog2(rc)];
+ *val = scale_factors[reg_val & GENMASK(2, 0)];
+
+ return 0;
}
-static int dps310_get_temp_k(struct dps310_data *data)
+static int dps310_get_temp_k(struct dps310_data *data, int *val)
{
- int rc = dps310_get_temp_precision(data);
+ int reg_val, rc;
+ rc = regmap_read(data->regmap, DPS310_TMP_CFG, &reg_val);
if (rc < 0)
return rc;
- return scale_factors[ilog2(rc)];
+ *val = scale_factors[reg_val & GENMASK(2, 0)];
+
+ return 0;
}
static int dps310_reset_wait(struct dps310_data *data)
@@ -464,7 +474,10 @@ static int dps310_read_pres_raw(struct dps310_data *data)
if (mutex_lock_interruptible(&data->lock))
return -EINTR;
- rate = dps310_get_pres_samp_freq(data);
+ rc = dps310_get_pres_samp_freq(data, &rate);
+ if (rc)
+ goto done;
+
timeout = DPS310_POLL_TIMEOUT_US(rate);
/* Poll for sensor readiness; base the timeout upon the sample rate. */
@@ -510,7 +523,10 @@ static int dps310_read_temp_raw(struct dps310_data *data)
if (mutex_lock_interruptible(&data->lock))
return -EINTR;
- rate = dps310_get_temp_samp_freq(data);
+ rc = dps310_get_temp_samp_freq(data, &rate);
+ if (rc)
+ goto done;
+
timeout = DPS310_POLL_TIMEOUT_US(rate);
/* Poll for sensor readiness; base the timeout upon the sample rate. */
@@ -612,13 +628,13 @@ static int dps310_write_raw(struct iio_dev *iio,
return rc;
}
-static int dps310_calculate_pressure(struct dps310_data *data)
+static int dps310_calculate_pressure(struct dps310_data *data, int *val)
{
int i;
int rc;
int t_ready;
- int kpi = dps310_get_pres_k(data);
- int kti = dps310_get_temp_k(data);
+ int kpi;
+ int kti;
s64 rem = 0ULL;
s64 pressure = 0ULL;
s64 p;
@@ -629,11 +645,13 @@ static int dps310_calculate_pressure(struct dps310_data *data)
s64 kp;
s64 kt;
- if (kpi < 0)
- return kpi;
+ rc = dps310_get_pres_k(data, &kpi);
+ if (rc)
+ return rc;
- if (kti < 0)
- return kti;
+ rc = dps310_get_temp_k(data, &kti);
+ if (rc)
+ return rc;
kp = (s64)kpi;
kt = (s64)kti;
@@ -687,7 +705,9 @@ static int dps310_calculate_pressure(struct dps310_data *data)
if (pressure < 0LL)
return -ERANGE;
- return (int)min_t(s64, pressure, INT_MAX);
+ *val = (int)min_t(s64, pressure, INT_MAX);
+
+ return 0;
}
static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2,
@@ -697,11 +717,10 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2,
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
- rc = dps310_get_pres_samp_freq(data);
- if (rc < 0)
+ rc = dps310_get_pres_samp_freq(data, val);
+ if (rc)
return rc;
- *val = rc;
return IIO_VAL_INT;
case IIO_CHAN_INFO_PROCESSED:
@@ -709,20 +728,17 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2,
if (rc)
return rc;
- rc = dps310_calculate_pressure(data);
- if (rc < 0)
+ rc = dps310_calculate_pressure(data, val);
+ if (rc)
return rc;
- *val = rc;
*val2 = 1000; /* Convert Pa to KPa per IIO ABI */
return IIO_VAL_FRACTIONAL;
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
- rc = dps310_get_pres_precision(data);
- if (rc < 0)
+ rc = dps310_get_pres_precision(data, val);
+ if (rc)
return rc;
-
- *val = rc;
return IIO_VAL_INT;
default:
@@ -730,14 +746,15 @@ static int dps310_read_pressure(struct dps310_data *data, int *val, int *val2,
}
}
-static int dps310_calculate_temp(struct dps310_data *data)
+static int dps310_calculate_temp(struct dps310_data *data, int *val)
{
s64 c0;
s64 t;
- int kt = dps310_get_temp_k(data);
+ int kt, rc;
- if (kt < 0)
- return kt;
+ rc = dps310_get_temp_k(data, &kt);
+ if (rc)
+ return rc;
/* Obtain inverse-scaled offset */
c0 = div_s64((s64)kt * (s64)data->c0, 2);
@@ -746,7 +763,9 @@ static int dps310_calculate_temp(struct dps310_data *data)
t = c0 + ((s64)data->temp_raw * (s64)data->c1);
/* Convert to milliCelsius and scale the temperature */
- return (int)div_s64(t * 1000LL, kt);
+ *val = (int)div_s64(t * 1000LL, kt);
+
+ return 0;
}
static int dps310_read_temp(struct dps310_data *data, int *val, int *val2,
@@ -756,11 +775,10 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2,
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
- rc = dps310_get_temp_samp_freq(data);
- if (rc < 0)
+ rc = dps310_get_temp_samp_freq(data, val);
+ if (rc)
return rc;
- *val = rc;
return IIO_VAL_INT;
case IIO_CHAN_INFO_PROCESSED:
@@ -768,19 +786,17 @@ static int dps310_read_temp(struct dps310_data *data, int *val, int *val2,
if (rc)
return rc;
- rc = dps310_calculate_temp(data);
- if (rc < 0)
+ rc = dps310_calculate_temp(data, val);
+ if (rc)
return rc;
- *val = rc;
return IIO_VAL_INT;
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
- rc = dps310_get_temp_precision(data);
- if (rc < 0)
+ rc = dps310_get_temp_precision(data, val);
+ if (rc)
return rc;
- *val = rc;
return IIO_VAL_INT;
default:
diff --git a/drivers/iio/pressure/hsc030pa_spi.c b/drivers/iio/pressure/hsc030pa_spi.c
index 818fa630345450..337eecc577d2a2 100644
--- a/drivers/iio/pressure/hsc030pa_spi.c
+++ b/drivers/iio/pressure/hsc030pa_spi.c
@@ -23,14 +23,9 @@
static int hsc_spi_recv(struct hsc_data *data)
{
struct spi_device *spi = to_spi_device(data->dev);
- struct spi_transfer xfer = {
- .tx_buf = NULL,
- .rx_buf = data->buffer,
- .len = HSC_REG_MEASUREMENT_RD_SIZE,
- };
msleep_interruptible(HSC_RESP_TIME_MS);
- return spi_sync_transfer(spi, &xfer, 1);
+ return spi_read(spi, data->buffer, HSC_REG_MEASUREMENT_RD_SIZE);
}
static int hsc_spi_probe(struct spi_device *spi)
diff --git a/drivers/iio/temperature/ltc2983.c b/drivers/iio/temperature/ltc2983.c
index 39447c786af342..24d19f3c729274 100644
--- a/drivers/iio/temperature/ltc2983.c
+++ b/drivers/iio/temperature/ltc2983.c
@@ -16,6 +16,7 @@
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <asm/byteorder.h>
@@ -657,7 +658,6 @@ ltc2983_thermocouple_new(const struct fwnode_handle *child, struct ltc2983_data
const struct ltc2983_sensor *sensor)
{
struct ltc2983_thermocouple *thermo;
- struct fwnode_handle *ref;
u32 oc_current;
int ret;
@@ -704,7 +704,8 @@ ltc2983_thermocouple_new(const struct fwnode_handle *child, struct ltc2983_data
return ERR_PTR(-EINVAL);
}
- ref = fwnode_find_reference(child, "adi,cold-junction-handle", 0);
+ struct fwnode_handle *ref __free(fwnode_handle) =
+ fwnode_find_reference(child, "adi,cold-junction-handle", 0);
if (IS_ERR(ref)) {
ref = NULL;
} else {
@@ -715,7 +716,7 @@ ltc2983_thermocouple_new(const struct fwnode_handle *child, struct ltc2983_data
* the error right away.
*/
dev_err(&st->spi->dev, "Property reg must be given\n");
- goto fail;
+ return ERR_PTR(ret);
}
}
@@ -726,22 +727,15 @@ ltc2983_thermocouple_new(const struct fwnode_handle *child, struct ltc2983_data
thermo->custom = __ltc2983_custom_sensor_new(st, child,
propname, false,
16384, true);
- if (IS_ERR(thermo->custom)) {
- ret = PTR_ERR(thermo->custom);
- goto fail;
- }
+ if (IS_ERR(thermo->custom))
+ return ERR_CAST(thermo->custom);
}
/* set common parameters */
thermo->sensor.fault_handler = ltc2983_thermocouple_fault_handler;
thermo->sensor.assign_chan = ltc2983_thermocouple_assign_chan;
- fwnode_handle_put(ref);
return &thermo->sensor;
-
-fail:
- fwnode_handle_put(ref);
- return ERR_PTR(ret);
}
static struct ltc2983_sensor *
@@ -751,14 +745,14 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st,
struct ltc2983_rtd *rtd;
int ret = 0;
struct device *dev = &st->spi->dev;
- struct fwnode_handle *ref;
u32 excitation_current = 0, n_wires = 0;
rtd = devm_kzalloc(dev, sizeof(*rtd), GFP_KERNEL);
if (!rtd)
return ERR_PTR(-ENOMEM);
- ref = fwnode_find_reference(child, "adi,rsense-handle", 0);
+ struct fwnode_handle *ref __free(fwnode_handle) =
+ fwnode_find_reference(child, "adi,rsense-handle", 0);
if (IS_ERR(ref)) {
dev_err(dev, "Property adi,rsense-handle missing or invalid");
return ERR_CAST(ref);
@@ -767,7 +761,7 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st,
ret = fwnode_property_read_u32(ref, "reg", &rtd->r_sense_chan);
if (ret) {
dev_err(dev, "Property reg must be given\n");
- goto fail;
+ return ERR_PTR(ret);
}
ret = fwnode_property_read_u32(child, "adi,number-of-wires", &n_wires);
@@ -788,8 +782,7 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st,
break;
default:
dev_err(dev, "Invalid number of wires:%u\n", n_wires);
- ret = -EINVAL;
- goto fail;
+ return ERR_PTR(-EINVAL);
}
}
@@ -799,8 +792,7 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st,
if (n_wires == 2 || n_wires == 3) {
dev_err(dev,
"Rotation not allowed for 2/3 Wire RTDs");
- ret = -EINVAL;
- goto fail;
+ return ERR_PTR(-EINVAL);
}
rtd->sensor_config |= LTC2983_RTD_C_ROTATE(1);
} else {
@@ -830,16 +822,14 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st,
"Invalid rsense chann:%d to use in kelvin rsense",
rtd->r_sense_chan);
- ret = -EINVAL;
- goto fail;
+ return ERR_PTR(-EINVAL);
}
if (sensor->chan < min || sensor->chan > max) {
dev_err(dev, "Invalid chann:%d for the rtd config",
sensor->chan);
- ret = -EINVAL;
- goto fail;
+ return ERR_PTR(-EINVAL);
}
} else {
/* same as differential case */
@@ -847,8 +837,7 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st,
dev_err(&st->spi->dev,
"Invalid chann:%d for RTD", sensor->chan);
- ret = -EINVAL;
- goto fail;
+ return ERR_PTR(-EINVAL);
}
}
@@ -857,10 +846,8 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st,
rtd->custom = __ltc2983_custom_sensor_new(st, child,
"adi,custom-rtd",
false, 2048, false);
- if (IS_ERR(rtd->custom)) {
- ret = PTR_ERR(rtd->custom);
- goto fail;
- }
+ if (IS_ERR(rtd->custom))
+ return ERR_CAST(rtd->custom);
}
/* set common parameters */
@@ -902,18 +889,13 @@ ltc2983_rtd_new(const struct fwnode_handle *child, struct ltc2983_data *st,
dev_err(&st->spi->dev,
"Invalid value for excitation current(%u)",
excitation_current);
- ret = -EINVAL;
- goto fail;
+ return ERR_PTR(-EINVAL);
}
}
fwnode_property_read_u32(child, "adi,rtd-curve", &rtd->rtd_curve);
- fwnode_handle_put(ref);
return &rtd->sensor;
-fail:
- fwnode_handle_put(ref);
- return ERR_PTR(ret);
}
static struct ltc2983_sensor *
@@ -922,7 +904,6 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s
{
struct ltc2983_thermistor *thermistor;
struct device *dev = &st->spi->dev;
- struct fwnode_handle *ref;
u32 excitation_current = 0;
int ret = 0;
@@ -930,7 +911,8 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s
if (!thermistor)
return ERR_PTR(-ENOMEM);
- ref = fwnode_find_reference(child, "adi,rsense-handle", 0);
+ struct fwnode_handle *ref __free(fwnode_handle) =
+ fwnode_find_reference(child, "adi,rsense-handle", 0);
if (IS_ERR(ref)) {
dev_err(dev, "Property adi,rsense-handle missing or invalid");
return ERR_CAST(ref);
@@ -939,7 +921,7 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s
ret = fwnode_property_read_u32(ref, "reg", &thermistor->r_sense_chan);
if (ret) {
dev_err(dev, "rsense channel must be configured...\n");
- goto fail;
+ return ERR_PTR(ret);
}
if (fwnode_property_read_bool(child, "adi,single-ended")) {
@@ -959,8 +941,7 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s
dev_err(&st->spi->dev,
"Invalid chann:%d for differential thermistor",
sensor->chan);
- ret = -EINVAL;
- goto fail;
+ return ERR_PTR(-EINVAL);
}
/* check custom sensor */
@@ -979,10 +960,8 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s
propname,
steinhart,
64, false);
- if (IS_ERR(thermistor->custom)) {
- ret = PTR_ERR(thermistor->custom);
- goto fail;
- }
+ if (IS_ERR(thermistor->custom))
+ return ERR_CAST(thermistor->custom);
}
/* set common parameters */
thermistor->sensor.fault_handler = ltc2983_common_fault_handler;
@@ -1006,8 +985,7 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s
LTC2983_SENSOR_THERMISTOR_STEINHART) {
dev_err(&st->spi->dev,
"Auto Range not allowed for custom sensors\n");
- ret = -EINVAL;
- goto fail;
+ return ERR_PTR(-EINVAL);
}
thermistor->excitation_current = 0x0c;
break;
@@ -1048,16 +1026,11 @@ ltc2983_thermistor_new(const struct fwnode_handle *child, struct ltc2983_data *s
dev_err(&st->spi->dev,
"Invalid value for excitation current(%u)",
excitation_current);
- ret = -EINVAL;
- goto fail;
+ return ERR_PTR(-EINVAL);
}
}
- fwnode_handle_put(ref);
return &thermistor->sensor;
-fail:
- fwnode_handle_put(ref);
- return ERR_PTR(ret);
}
static struct ltc2983_sensor *
@@ -1350,8 +1323,7 @@ static irqreturn_t ltc2983_irq_handler(int irq, void *data)
static int ltc2983_parse_fw(struct ltc2983_data *st)
{
struct device *dev = &st->spi->dev;
- struct fwnode_handle *child;
- int ret = 0, chan = 0, channel_avail_mask = 0;
+ int ret, chan = 0, channel_avail_mask = 0;
device_property_read_u32(dev, "adi,mux-delay-config-us", &st->mux_delay_config);
@@ -1369,38 +1341,35 @@ static int ltc2983_parse_fw(struct ltc2983_data *st)
return -ENOMEM;
st->iio_channels = st->num_channels;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct ltc2983_sensor sensor;
ret = fwnode_property_read_u32(child, "reg", &sensor.chan);
- if (ret) {
- dev_err(dev, "reg property must given for child nodes\n");
- goto put_child;
- }
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "reg property must given for child nodes\n");
/* check if we have a valid channel */
if (sensor.chan < LTC2983_MIN_CHANNELS_NR ||
- sensor.chan > st->info->max_channels_nr) {
- ret = -EINVAL;
- dev_err(dev, "chan:%d must be from %u to %u\n", sensor.chan,
- LTC2983_MIN_CHANNELS_NR, st->info->max_channels_nr);
- goto put_child;
- } else if (channel_avail_mask & BIT(sensor.chan)) {
- ret = -EINVAL;
- dev_err(dev, "chan:%d already in use\n", sensor.chan);
- goto put_child;
- }
+ sensor.chan > st->info->max_channels_nr)
+ return dev_err_probe(dev, -EINVAL,
+ "chan:%d must be from %u to %u\n",
+ sensor.chan,
+ LTC2983_MIN_CHANNELS_NR,
+ st->info->max_channels_nr);
+
+ if (channel_avail_mask & BIT(sensor.chan))
+ return dev_err_probe(dev, -EINVAL,
+ "chan:%d already in use\n",
+ sensor.chan);
ret = fwnode_property_read_u32(child, "adi,sensor-type", &sensor.type);
- if (ret) {
- dev_err(dev,
+ if (ret)
+ return dev_err_probe(dev, ret,
"adi,sensor-type property must given for child nodes\n");
- goto put_child;
- }
dev_dbg(dev, "Create new sensor, type %u, chann %u",
- sensor.type,
- sensor.chan);
+ sensor.type, sensor.chan);
if (sensor.type >= LTC2983_SENSOR_THERMOCOUPLE &&
sensor.type <= LTC2983_SENSOR_THERMOCOUPLE_CUSTOM) {
@@ -1427,17 +1396,15 @@ static int ltc2983_parse_fw(struct ltc2983_data *st)
sensor.type == LTC2983_SENSOR_ACTIVE_TEMP) {
st->sensors[chan] = ltc2983_temp_new(child, st, &sensor);
} else {
- dev_err(dev, "Unknown sensor type %d\n", sensor.type);
- ret = -EINVAL;
- goto put_child;
+ return dev_err_probe(dev, -EINVAL,
+ "Unknown sensor type %d\n",
+ sensor.type);
}
- if (IS_ERR(st->sensors[chan])) {
- dev_err(dev, "Failed to create sensor %ld",
- PTR_ERR(st->sensors[chan]));
- ret = PTR_ERR(st->sensors[chan]);
- goto put_child;
- }
+ if (IS_ERR(st->sensors[chan]))
+ return dev_err_probe(dev, PTR_ERR(st->sensors[chan]),
+ "Failed to create sensor\n");
+
/* set generic sensor parameters */
st->sensors[chan]->chan = sensor.chan;
st->sensors[chan]->type = sensor.type;
@@ -1447,9 +1414,6 @@ static int ltc2983_parse_fw(struct ltc2983_data *st)
}
return 0;
-put_child:
- fwnode_handle_put(child);
- return ret;
}
static int ltc2983_eeprom_cmd(struct ltc2983_data *st, unsigned int cmd,
@@ -1634,6 +1598,10 @@ static int ltc2983_probe(struct spi_device *spi)
if (ret)
return ret;
+ ret = devm_regulator_get_enable(&spi->dev, "vdd");
+ if (ret)
+ return ret;
+
gpio = devm_gpiod_get_optional(&st->spi->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(gpio))
return PTR_ERR(gpio);
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 4fb291f0bf7c89..801ed229ed7dc1 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -506,6 +506,17 @@ config OPEN_DICE
If unsure, say N.
+config NTSYNC
+ tristate "NT synchronization primitive emulation"
+ help
+ This module provides kernel support for emulation of Windows NT
+ synchronization primitives. It is not a hardware driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ntsync.
+
+ If unsure, say N.
+
config VCPU_STALL_DETECTOR
tristate "Guest vCPU stall detector"
depends on OF && HAS_IOMEM
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index ea6ea5bbbc9c62..153a3f4837e8ca 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_PVPANIC) += pvpanic/
obj-$(CONFIG_UACCE) += uacce/
obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o
obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o
+obj-$(CONFIG_NTSYNC) += ntsync.o
obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o
obj-$(CONFIG_OPEN_DICE) += open-dice.o
obj-$(CONFIG_GP_PCI1XXXX) += mchp_pci1xxxx/
diff --git a/drivers/misc/ds1682.c b/drivers/misc/ds1682.c
index 21fc5bc85c5c8c..5f8dcd0e384865 100644
--- a/drivers/misc/ds1682.c
+++ b/drivers/misc/ds1682.c
@@ -32,6 +32,7 @@
#include <linux/i2c.h>
#include <linux/string.h>
#include <linux/list.h>
+#include <linux/nvmem-provider.h>
#include <linux/sysfs.h>
#include <linux/ctype.h>
#include <linux/hwmon-sysfs.h>
@@ -197,11 +198,43 @@ static const struct bin_attribute ds1682_eeprom_attr = {
.write = ds1682_eeprom_write,
};
+static int ds1682_nvmem_read(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct i2c_client *client = priv;
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(client, DS1682_REG_EEPROM + offset,
+ bytes, val);
+ return ret < 0 ? ret : 0;
+}
+
+static int ds1682_nvmem_write(void *priv, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct i2c_client *client = priv;
+ int ret;
+
+ ret = i2c_smbus_write_i2c_block_data(client, DS1682_REG_EEPROM + offset,
+ bytes, val);
+ return ret < 0 ? ret : 0;
+}
+
/*
* Called when a ds1682 device is matched with this driver
*/
static int ds1682_probe(struct i2c_client *client)
{
+ struct nvmem_config config = {
+ .dev = &client->dev,
+ .owner = THIS_MODULE,
+ .type = NVMEM_TYPE_EEPROM,
+ .reg_read = ds1682_nvmem_read,
+ .reg_write = ds1682_nvmem_write,
+ .size = DS1682_EEPROM_SIZE,
+ .priv = client,
+ };
+ struct nvmem_device *nvmem;
int rc;
if (!i2c_check_functionality(client->adapter,
@@ -211,6 +244,10 @@ static int ds1682_probe(struct i2c_client *client)
goto exit;
}
+ nvmem = devm_nvmem_register(&client->dev, &config);
+ if (IS_ENABLED(CONFIG_NVMEM) && IS_ERR(nvmem))
+ return PTR_ERR(nvmem);
+
rc = sysfs_create_group(&client->dev.kobj, &ds1682_group);
if (rc)
goto exit;
diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c
index f9bcff19761512..99393f610cdf6d 100644
--- a/drivers/misc/mei/bus.c
+++ b/drivers/misc/mei/bus.c
@@ -1327,7 +1327,7 @@ static int mei_cl_device_uevent(const struct device *dev, struct kobj_uevent_env
return 0;
}
-static struct bus_type mei_cl_bus_type = {
+static const struct bus_type mei_cl_bus_type = {
.name = "mei",
.dev_groups = mei_cldev_groups,
.match = mei_cl_device_match,
diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h
index eb800a07a84bdd..2e9cf6f4efb6e7 100644
--- a/drivers/misc/mei/hw.h
+++ b/drivers/misc/mei/hw.h
@@ -247,12 +247,10 @@ enum mei_ext_hdr_type {
* struct mei_ext_hdr - extend header descriptor (TLV)
* @type: enum mei_ext_hdr_type
* @length: length excluding descriptor
- * @data: the extended header payload
*/
struct mei_ext_hdr {
u8 type;
u8 length;
- u8 data[];
} __packed;
/**
diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c
new file mode 100644
index 00000000000000..3c2f743c58b00c
--- /dev/null
+++ b/drivers/misc/ntsync.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ntsync.c - Kernel driver for NT synchronization primitives
+ *
+ * Copyright (C) 2024 Elizabeth Figura <zfigura@codeweavers.com>
+ */
+
+#include <linux/anon_inodes.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <uapi/linux/ntsync.h>
+
+#define NTSYNC_NAME "ntsync"
+
+enum ntsync_type {
+ NTSYNC_TYPE_SEM,
+};
+
+/*
+ * Individual synchronization primitives are represented by
+ * struct ntsync_obj, and each primitive is backed by a file.
+ *
+ * The whole namespace is represented by a struct ntsync_device also
+ * backed by a file.
+ *
+ * Both rely on struct file for reference counting. Individual
+ * ntsync_obj objects take a reference to the device when created.
+ */
+
+struct ntsync_obj {
+ spinlock_t lock;
+
+ enum ntsync_type type;
+
+ struct file *file;
+ struct ntsync_device *dev;
+
+ /* The following fields are protected by the object lock. */
+ union {
+ struct {
+ __u32 count;
+ __u32 max;
+ } sem;
+ } u;
+};
+
+struct ntsync_device {
+ struct file *file;
+};
+
+/*
+ * Actually change the semaphore state, returning -EOVERFLOW if it is made
+ * invalid.
+ */
+static int post_sem_state(struct ntsync_obj *sem, __u32 count)
+{
+ __u32 sum;
+
+ lockdep_assert_held(&sem->lock);
+
+ if (check_add_overflow(sem->u.sem.count, count, &sum) ||
+ sum > sem->u.sem.max)
+ return -EOVERFLOW;
+
+ sem->u.sem.count = sum;
+ return 0;
+}
+
+static int ntsync_sem_post(struct ntsync_obj *sem, void __user *argp)
+{
+ __u32 __user *user_args = argp;
+ __u32 prev_count;
+ __u32 args;
+ int ret;
+
+ if (copy_from_user(&args, argp, sizeof(args)))
+ return -EFAULT;
+
+ if (sem->type != NTSYNC_TYPE_SEM)
+ return -EINVAL;
+
+ spin_lock(&sem->lock);
+
+ prev_count = sem->u.sem.count;
+ ret = post_sem_state(sem, args);
+
+ spin_unlock(&sem->lock);
+
+ if (!ret && put_user(prev_count, user_args))
+ ret = -EFAULT;
+
+ return ret;
+}
+
+static int ntsync_obj_release(struct inode *inode, struct file *file)
+{
+ struct ntsync_obj *obj = file->private_data;
+
+ fput(obj->dev->file);
+ kfree(obj);
+
+ return 0;
+}
+
+static long ntsync_obj_ioctl(struct file *file, unsigned int cmd,
+ unsigned long parm)
+{
+ struct ntsync_obj *obj = file->private_data;
+ void __user *argp = (void __user *)parm;
+
+ switch (cmd) {
+ case NTSYNC_IOC_SEM_POST:
+ return ntsync_sem_post(obj, argp);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static const struct file_operations ntsync_obj_fops = {
+ .owner = THIS_MODULE,
+ .release = ntsync_obj_release,
+ .unlocked_ioctl = ntsync_obj_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct ntsync_obj *ntsync_alloc_obj(struct ntsync_device *dev,
+ enum ntsync_type type)
+{
+ struct ntsync_obj *obj;
+
+ obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+ if (!obj)
+ return NULL;
+ obj->type = type;
+ obj->dev = dev;
+ get_file(dev->file);
+ spin_lock_init(&obj->lock);
+
+ return obj;
+}
+
+static int ntsync_obj_get_fd(struct ntsync_obj *obj)
+{
+ struct file *file;
+ int fd;
+
+ fd = get_unused_fd_flags(O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+ file = anon_inode_getfile("ntsync", &ntsync_obj_fops, obj, O_RDWR);
+ if (IS_ERR(file)) {
+ put_unused_fd(fd);
+ return PTR_ERR(file);
+ }
+ obj->file = file;
+ fd_install(fd, file);
+
+ return fd;
+}
+
+static int ntsync_create_sem(struct ntsync_device *dev, void __user *argp)
+{
+ struct ntsync_sem_args __user *user_args = argp;
+ struct ntsync_sem_args args;
+ struct ntsync_obj *sem;
+ int fd;
+
+ if (copy_from_user(&args, argp, sizeof(args)))
+ return -EFAULT;
+
+ if (args.count > args.max)
+ return -EINVAL;
+
+ sem = ntsync_alloc_obj(dev, NTSYNC_TYPE_SEM);
+ if (!sem)
+ return -ENOMEM;
+ sem->u.sem.count = args.count;
+ sem->u.sem.max = args.max;
+ fd = ntsync_obj_get_fd(sem);
+ if (fd < 0) {
+ kfree(sem);
+ return fd;
+ }
+
+ return put_user(fd, &user_args->sem);
+}
+
+static int ntsync_char_open(struct inode *inode, struct file *file)
+{
+ struct ntsync_device *dev;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ file->private_data = dev;
+ dev->file = file;
+ return nonseekable_open(inode, file);
+}
+
+static int ntsync_char_release(struct inode *inode, struct file *file)
+{
+ struct ntsync_device *dev = file->private_data;
+
+ kfree(dev);
+
+ return 0;
+}
+
+static long ntsync_char_ioctl(struct file *file, unsigned int cmd,
+ unsigned long parm)
+{
+ struct ntsync_device *dev = file->private_data;
+ void __user *argp = (void __user *)parm;
+
+ switch (cmd) {
+ case NTSYNC_IOC_CREATE_SEM:
+ return ntsync_create_sem(dev, argp);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static const struct file_operations ntsync_fops = {
+ .owner = THIS_MODULE,
+ .open = ntsync_char_open,
+ .release = ntsync_char_release,
+ .unlocked_ioctl = ntsync_char_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct miscdevice ntsync_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = NTSYNC_NAME,
+ .fops = &ntsync_fops,
+};
+
+module_misc_device(ntsync_misc);
+
+MODULE_AUTHOR("Elizabeth Figura <zfigura@codeweavers.com>");
+MODULE_DESCRIPTION("Kernel driver for NT synchronization primitives");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/ti-st/st_kim.c b/drivers/misc/ti-st/st_kim.c
index 47ebe80bf84998..c4f963cf96f26d 100644
--- a/drivers/misc/ti-st/st_kim.c
+++ b/drivers/misc/ti-st/st_kim.c
@@ -563,7 +563,7 @@ long st_kim_stop(void *kim_data)
static int version_show(struct seq_file *s, void *unused)
{
- struct kim_data_s *kim_gdata = (struct kim_data_s *)s->private;
+ struct kim_data_s *kim_gdata = s->private;
seq_printf(s, "%04X %d.%d.%d\n", kim_gdata->version.full,
kim_gdata->version.chip, kim_gdata->version.maj_ver,
kim_gdata->version.min_ver);
@@ -572,7 +572,7 @@ static int version_show(struct seq_file *s, void *unused)
static int list_show(struct seq_file *s, void *unused)
{
- struct kim_data_s *kim_gdata = (struct kim_data_s *)s->private;
+ struct kim_data_s *kim_gdata = s->private;
kim_st_list_protocols(kim_gdata->core_data, s);
return 0;
}
diff --git a/drivers/parport/parport_mfc3.c b/drivers/parport/parport_mfc3.c
index f4d0da741e8566..bb1817218d7b22 100644
--- a/drivers/parport/parport_mfc3.c
+++ b/drivers/parport/parport_mfc3.c
@@ -102,8 +102,7 @@ static unsigned char control_pc_to_mfc3(unsigned char control)
ret |= 128;
if (control & PARPORT_CONTROL_AUTOFD) /* AUTOLF */
ret &= ~64;
- if (control & PARPORT_CONTROL_STROBE) /* Strobe */
- /* Handled directly by hardware */;
+ /* PARPORT_CONTROL_STROBE handled directly by hardware */
return ret;
}
diff --git a/drivers/peci/core.c b/drivers/peci/core.c
index 0f83a9c6093bc2..8f8bda2f2a62e4 100644
--- a/drivers/peci/core.c
+++ b/drivers/peci/core.c
@@ -25,7 +25,7 @@ static void peci_controller_dev_release(struct device *dev)
kfree(controller);
}
-struct device_type peci_controller_type = {
+const struct device_type peci_controller_type = {
.release = peci_controller_dev_release,
};
@@ -201,7 +201,7 @@ static void peci_bus_device_remove(struct device *dev)
driver->remove(device);
}
-struct bus_type peci_bus_type = {
+const struct bus_type peci_bus_type = {
.name = "peci",
.match = peci_bus_device_match,
.probe = peci_bus_device_probe,
diff --git a/drivers/peci/device.c b/drivers/peci/device.c
index e6b0bffb14f413..ee01f03c29b792 100644
--- a/drivers/peci/device.c
+++ b/drivers/peci/device.c
@@ -246,7 +246,7 @@ static void peci_device_release(struct device *dev)
kfree(device);
}
-struct device_type peci_device_type = {
+const struct device_type peci_device_type = {
.groups = peci_device_groups,
.release = peci_device_release,
};
diff --git a/drivers/peci/internal.h b/drivers/peci/internal.h
index 9d75ea54504c36..506bafcccbbf95 100644
--- a/drivers/peci/internal.h
+++ b/drivers/peci/internal.h
@@ -75,13 +75,13 @@ struct peci_device_id {
u8 model;
};
-extern struct device_type peci_device_type;
+extern const struct device_type peci_device_type;
extern const struct attribute_group *peci_device_groups[];
int peci_device_create(struct peci_controller *controller, u8 addr);
void peci_device_destroy(struct peci_device *device);
-extern struct bus_type peci_bus_type;
+extern const struct bus_type peci_bus_type;
extern const struct attribute_group *peci_bus_groups[];
/**
@@ -129,7 +129,7 @@ void peci_driver_unregister(struct peci_driver *driver);
#define module_peci_driver(__peci_driver) \
module_driver(__peci_driver, peci_driver_register, peci_driver_unregister)
-extern struct device_type peci_controller_type;
+extern const struct device_type peci_controller_type;
int peci_controller_scan_devices(struct peci_controller *controller);
diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig
index 2e16c5338e5b14..b060dcd7c63501 100644
--- a/drivers/uio/Kconfig
+++ b/drivers/uio/Kconfig
@@ -125,24 +125,6 @@ config UIO_FSL_ELBC_GPCM_NETX5152
Information about this hardware can be found at:
http://www.hilscher.com/netx
-config UIO_PRUSS
- tristate "Texas Instruments PRUSS driver"
- select GENERIC_ALLOCATOR
- depends on HAS_IOMEM && HAS_DMA
- help
- PRUSS driver for OMAPL138/DA850/AM18XX devices
- PRUSS driver requires user space components, examples and user space
- driver is available from below SVN repo - you may use anonymous login
-
- https://gforge.ti.com/gf/project/pru_sw/
-
- More info on API is available at below wiki
-
- http://processors.wiki.ti.com/index.php/PRU_Linux_Application_Loader
-
- To compile this driver as a module, choose M here: the module
- will be called uio_pruss.
-
config UIO_MF624
tristate "Humusoft MF624 DAQ PCI card driver"
depends on PCI
diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile
index f2f416a142286b..1c5f3b5a95cf5b 100644
--- a/drivers/uio/Makefile
+++ b/drivers/uio/Makefile
@@ -7,7 +7,6 @@ obj-$(CONFIG_UIO_AEC) += uio_aec.o
obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o
obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o
obj-$(CONFIG_UIO_NETX) += uio_netx.o
-obj-$(CONFIG_UIO_PRUSS) += uio_pruss.o
obj-$(CONFIG_UIO_MF624) += uio_mf624.o
obj-$(CONFIG_UIO_FSL_ELBC_GPCM) += uio_fsl_elbc_gpcm.o
obj-$(CONFIG_UIO_HV_GENERIC) += uio_hv_generic.o
diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c
index 009158fef2a8f1..5ce429286ab047 100644
--- a/drivers/uio/uio.c
+++ b/drivers/uio/uio.c
@@ -438,22 +438,36 @@ void uio_event_notify(struct uio_info *info)
EXPORT_SYMBOL_GPL(uio_event_notify);
/**
- * uio_interrupt - hardware interrupt handler
+ * uio_interrupt_handler - hardware interrupt handler
* @irq: IRQ number, can be UIO_IRQ_CYCLIC for cyclic timer
* @dev_id: Pointer to the devices uio_device structure
*/
-static irqreturn_t uio_interrupt(int irq, void *dev_id)
+static irqreturn_t uio_interrupt_handler(int irq, void *dev_id)
{
struct uio_device *idev = (struct uio_device *)dev_id;
irqreturn_t ret;
ret = idev->info->handler(irq, idev->info);
if (ret == IRQ_HANDLED)
- uio_event_notify(idev->info);
+ ret = IRQ_WAKE_THREAD;
return ret;
}
+/**
+ * uio_interrupt_thread - irq thread handler
+ * @irq: IRQ number
+ * @dev_id: Pointer to the devices uio_device structure
+ */
+static irqreturn_t uio_interrupt_thread(int irq, void *dev_id)
+{
+ struct uio_device *idev = (struct uio_device *)dev_id;
+
+ uio_event_notify(idev->info);
+
+ return IRQ_HANDLED;
+}
+
struct uio_listener {
struct uio_device *dev;
s32 event_count;
@@ -1024,8 +1038,8 @@ int __uio_register_device(struct module *owner,
* FDs at the time of unregister and therefore may not be
* freed until they are released.
*/
- ret = request_irq(info->irq, uio_interrupt,
- info->irq_flags, info->name, idev);
+ ret = request_threaded_irq(info->irq, uio_interrupt_handler, uio_interrupt_thread,
+ info->irq_flags, info->name, idev);
if (ret) {
info->uio_dev = NULL;
goto err_request_irq;
diff --git a/drivers/uio/uio_fsl_elbc_gpcm.c b/drivers/uio/uio_fsl_elbc_gpcm.c
index 82dda799f327d8..496caff66e7ea1 100644
--- a/drivers/uio/uio_fsl_elbc_gpcm.c
+++ b/drivers/uio/uio_fsl_elbc_gpcm.c
@@ -427,7 +427,7 @@ out_err2:
return ret;
}
-static int uio_fsl_elbc_gpcm_remove(struct platform_device *pdev)
+static void uio_fsl_elbc_gpcm_remove(struct platform_device *pdev)
{
struct uio_info *info = platform_get_drvdata(pdev);
struct fsl_elbc_gpcm *priv = info->priv;
@@ -438,8 +438,6 @@ static int uio_fsl_elbc_gpcm_remove(struct platform_device *pdev)
priv->shutdown(info, false);
iounmap(info->mem[0].internal_addr);
- return 0;
-
}
static const struct of_device_id uio_fsl_elbc_gpcm_match[] = {
@@ -455,7 +453,7 @@ static struct platform_driver uio_fsl_elbc_gpcm_driver = {
.dev_groups = uio_fsl_elbc_gpcm_groups,
},
.probe = uio_fsl_elbc_gpcm_probe,
- .remove = uio_fsl_elbc_gpcm_remove,
+ .remove_new = uio_fsl_elbc_gpcm_remove,
};
module_platform_driver(uio_fsl_elbc_gpcm_driver);
diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c
index 6be3462b109ff2..b45653752301df 100644
--- a/drivers/uio/uio_hv_generic.c
+++ b/drivers/uio/uio_hv_generic.c
@@ -36,7 +36,6 @@
#define DRIVER_AUTHOR "Stephen Hemminger <sthemmin at microsoft.com>"
#define DRIVER_DESC "Generic UIO driver for VMBus devices"
-#define HV_RING_SIZE 512 /* pages */
#define SEND_BUFFER_SIZE (16 * 1024 * 1024)
#define RECV_BUFFER_SIZE (31 * 1024 * 1024)
@@ -84,6 +83,9 @@ hv_uio_irqcontrol(struct uio_info *info, s32 irq_state)
dev->channel->inbound.ring_buffer->interrupt_mask = !irq_state;
virt_mb();
+ if (!dev->channel->offermsg.monitor_allocated && irq_state)
+ vmbus_setevent(dev->channel);
+
return 0;
}
@@ -143,7 +145,7 @@ static const struct bin_attribute ring_buffer_bin_attr = {
.name = "ring",
.mode = 0600,
},
- .size = 2 * HV_RING_SIZE * PAGE_SIZE,
+ .size = 2 * SZ_2M,
.mmap = hv_uio_ring_mmap,
};
@@ -153,7 +155,7 @@ hv_uio_new_channel(struct vmbus_channel *new_sc)
{
struct hv_device *hv_dev = new_sc->primary_channel->device_obj;
struct device *device = &hv_dev->device;
- const size_t ring_bytes = HV_RING_SIZE * PAGE_SIZE;
+ const size_t ring_bytes = SZ_2M;
int ret;
/* Create host communication ring */
@@ -240,19 +242,16 @@ hv_uio_probe(struct hv_device *dev,
struct hv_uio_private_data *pdata;
void *ring_buffer;
int ret;
+ size_t ring_size = hv_dev_ring_size(channel);
- /* Communicating with host has to be via shared memory not hypercall */
- if (!channel->offermsg.monitor_allocated) {
- dev_err(&dev->device, "vmbus channel requires hypercall\n");
- return -ENOTSUPP;
- }
+ if (!ring_size)
+ ring_size = SZ_2M;
pdata = devm_kzalloc(&dev->device, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
- ret = vmbus_alloc_ring(channel, HV_RING_SIZE * PAGE_SIZE,
- HV_RING_SIZE * PAGE_SIZE);
+ ret = vmbus_alloc_ring(channel, ring_size, ring_size);
if (ret)
return ret;
diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c
index 63258b6accc468..796f5be0a086a5 100644
--- a/drivers/uio/uio_pdrv_genirq.c
+++ b/drivers/uio/uio_pdrv_genirq.c
@@ -23,8 +23,8 @@
#include <linux/irq.h>
#include <linux/of.h>
-#include <linux/of_platform.h>
-#include <linux/of_address.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
#define DRIVER_NAME "uio_pdrv_genirq"
@@ -110,7 +110,7 @@ static void uio_pdrv_genirq_cleanup(void *data)
static int uio_pdrv_genirq_probe(struct platform_device *pdev)
{
struct uio_info *uioinfo = dev_get_platdata(&pdev->dev);
- struct device_node *node = pdev->dev.of_node;
+ struct fwnode_handle *node = dev_fwnode(&pdev->dev);
struct uio_pdrv_genirq_platdata *priv;
struct uio_mem *uiomem;
int ret = -EINVAL;
@@ -127,11 +127,11 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev)
return -ENOMEM;
}
- if (!of_property_read_string(node, "linux,uio-name", &name))
+ if (!device_property_read_string(&pdev->dev, "linux,uio-name", &name))
uioinfo->name = devm_kstrdup(&pdev->dev, name, GFP_KERNEL);
else
uioinfo->name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
- "%pOFn", node);
+ "%pfwP", node);
uioinfo->version = "devicetree";
/* Multiple IRQs are not supported */
diff --git a/drivers/uio/uio_pruss.c b/drivers/uio/uio_pruss.c
deleted file mode 100644
index f67881cba645ba..00000000000000
--- a/drivers/uio/uio_pruss.c
+++ /dev/null
@@ -1,255 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Programmable Real-Time Unit Sub System (PRUSS) UIO driver (uio_pruss)
- *
- * This driver exports PRUSS host event out interrupts and PRUSS, L3 RAM,
- * and DDR RAM to user space for applications interacting with PRUSS firmware
- *
- * Copyright (C) 2010-11 Texas Instruments Incorporated - http://www.ti.com/
- */
-#include <linux/device.h>
-#include <linux/module.h>
-#include <linux/moduleparam.h>
-#include <linux/platform_device.h>
-#include <linux/uio_driver.h>
-#include <linux/platform_data/uio_pruss.h>
-#include <linux/io.h>
-#include <linux/clk.h>
-#include <linux/dma-mapping.h>
-#include <linux/sizes.h>
-#include <linux/slab.h>
-#include <linux/genalloc.h>
-
-#define DRV_NAME "pruss_uio"
-#define DRV_VERSION "1.0"
-
-static int sram_pool_sz = SZ_16K;
-module_param(sram_pool_sz, int, 0);
-MODULE_PARM_DESC(sram_pool_sz, "sram pool size to allocate ");
-
-static int extram_pool_sz = SZ_256K;
-module_param(extram_pool_sz, int, 0);
-MODULE_PARM_DESC(extram_pool_sz, "external ram pool size to allocate");
-
-/*
- * Host event IRQ numbers from PRUSS - PRUSS can generate up to 8 interrupt
- * events to AINTC of ARM host processor - which can be used for IPC b/w PRUSS
- * firmware and user space application, async notification from PRU firmware
- * to user space application
- * 3 PRU_EVTOUT0
- * 4 PRU_EVTOUT1
- * 5 PRU_EVTOUT2
- * 6 PRU_EVTOUT3
- * 7 PRU_EVTOUT4
- * 8 PRU_EVTOUT5
- * 9 PRU_EVTOUT6
- * 10 PRU_EVTOUT7
-*/
-#define MAX_PRUSS_EVT 8
-
-#define PINTC_HIDISR 0x0038
-#define PINTC_HIPIR 0x0900
-#define HIPIR_NOPEND 0x80000000
-#define PINTC_HIER 0x1500
-
-struct uio_pruss_dev {
- struct uio_info *info;
- struct clk *pruss_clk;
- dma_addr_t sram_paddr;
- dma_addr_t ddr_paddr;
- void __iomem *prussio_vaddr;
- unsigned long sram_vaddr;
- void *ddr_vaddr;
- unsigned int hostirq_start;
- unsigned int pintc_base;
- struct gen_pool *sram_pool;
-};
-
-static irqreturn_t pruss_handler(int irq, struct uio_info *info)
-{
- struct uio_pruss_dev *gdev = info->priv;
- int intr_bit = (irq - gdev->hostirq_start + 2);
- int val, intr_mask = (1 << intr_bit);
- void __iomem *base = gdev->prussio_vaddr + gdev->pintc_base;
- void __iomem *intren_reg = base + PINTC_HIER;
- void __iomem *intrdis_reg = base + PINTC_HIDISR;
- void __iomem *intrstat_reg = base + PINTC_HIPIR + (intr_bit << 2);
-
- val = ioread32(intren_reg);
- /* Is interrupt enabled and active ? */
- if (!(val & intr_mask) && (ioread32(intrstat_reg) & HIPIR_NOPEND))
- return IRQ_NONE;
- /* Disable interrupt */
- iowrite32(intr_bit, intrdis_reg);
- return IRQ_HANDLED;
-}
-
-static void pruss_cleanup(struct device *dev, struct uio_pruss_dev *gdev)
-{
- int cnt;
- struct uio_info *p = gdev->info;
-
- for (cnt = 0; cnt < MAX_PRUSS_EVT; cnt++, p++) {
- uio_unregister_device(p);
- }
- iounmap(gdev->prussio_vaddr);
- if (gdev->ddr_vaddr) {
- dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr,
- gdev->ddr_paddr);
- }
- if (gdev->sram_vaddr)
- gen_pool_free(gdev->sram_pool,
- gdev->sram_vaddr,
- sram_pool_sz);
- clk_disable(gdev->pruss_clk);
-}
-
-static int pruss_probe(struct platform_device *pdev)
-{
- struct uio_info *p;
- struct uio_pruss_dev *gdev;
- struct resource *regs_prussio;
- struct device *dev = &pdev->dev;
- int ret, cnt, i, len;
- struct uio_pruss_pdata *pdata = dev_get_platdata(dev);
-
- gdev = devm_kzalloc(dev, sizeof(struct uio_pruss_dev), GFP_KERNEL);
- if (!gdev)
- return -ENOMEM;
-
- gdev->info = devm_kcalloc(dev, MAX_PRUSS_EVT, sizeof(*p), GFP_KERNEL);
- if (!gdev->info)
- return -ENOMEM;
-
- /* Power on PRU in case its not done as part of boot-loader */
- gdev->pruss_clk = devm_clk_get(dev, "pruss");
- if (IS_ERR(gdev->pruss_clk)) {
- dev_err(dev, "Failed to get clock\n");
- return PTR_ERR(gdev->pruss_clk);
- }
-
- ret = clk_enable(gdev->pruss_clk);
- if (ret) {
- dev_err(dev, "Failed to enable clock\n");
- return ret;
- }
-
- regs_prussio = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!regs_prussio) {
- dev_err(dev, "No PRUSS I/O resource specified\n");
- ret = -EIO;
- goto err_clk_disable;
- }
-
- if (!regs_prussio->start) {
- dev_err(dev, "Invalid memory resource\n");
- ret = -EIO;
- goto err_clk_disable;
- }
-
- if (pdata->sram_pool) {
- gdev->sram_pool = pdata->sram_pool;
- gdev->sram_vaddr =
- (unsigned long)gen_pool_dma_alloc(gdev->sram_pool,
- sram_pool_sz, &gdev->sram_paddr);
- if (!gdev->sram_vaddr) {
- dev_err(dev, "Could not allocate SRAM pool\n");
- ret = -ENOMEM;
- goto err_clk_disable;
- }
- }
-
- gdev->ddr_vaddr = dma_alloc_coherent(dev, extram_pool_sz,
- &(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA);
- if (!gdev->ddr_vaddr) {
- dev_err(dev, "Could not allocate external memory\n");
- ret = -ENOMEM;
- goto err_free_sram;
- }
-
- len = resource_size(regs_prussio);
- gdev->prussio_vaddr = ioremap(regs_prussio->start, len);
- if (!gdev->prussio_vaddr) {
- dev_err(dev, "Can't remap PRUSS I/O address range\n");
- ret = -ENOMEM;
- goto err_free_ddr_vaddr;
- }
-
- ret = platform_get_irq(pdev, 0);
- if (ret < 0)
- goto err_unmap;
-
- gdev->hostirq_start = ret;
- gdev->pintc_base = pdata->pintc_base;
-
- for (cnt = 0, p = gdev->info; cnt < MAX_PRUSS_EVT; cnt++, p++) {
- p->mem[0].addr = regs_prussio->start;
- p->mem[0].size = resource_size(regs_prussio);
- p->mem[0].memtype = UIO_MEM_PHYS;
-
- p->mem[1].addr = gdev->sram_paddr;
- p->mem[1].size = sram_pool_sz;
- p->mem[1].memtype = UIO_MEM_PHYS;
-
- p->mem[2].addr = (uintptr_t) gdev->ddr_vaddr;
- p->mem[2].dma_addr = gdev->ddr_paddr;
- p->mem[2].size = extram_pool_sz;
- p->mem[2].memtype = UIO_MEM_DMA_COHERENT;
- p->mem[2].dma_device = dev;
-
- p->name = devm_kasprintf(dev, GFP_KERNEL, "pruss_evt%d", cnt);
- p->version = DRV_VERSION;
-
- /* Register PRUSS IRQ lines */
- p->irq = gdev->hostirq_start + cnt;
- p->handler = pruss_handler;
- p->priv = gdev;
-
- ret = uio_register_device(dev, p);
- if (ret < 0)
- goto err_unloop;
- }
-
- platform_set_drvdata(pdev, gdev);
- return 0;
-
-err_unloop:
- for (i = 0, p = gdev->info; i < cnt; i++, p++) {
- uio_unregister_device(p);
- }
-err_unmap:
- iounmap(gdev->prussio_vaddr);
-err_free_ddr_vaddr:
- dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr,
- gdev->ddr_paddr);
-err_free_sram:
- if (pdata->sram_pool)
- gen_pool_free(gdev->sram_pool, gdev->sram_vaddr, sram_pool_sz);
-err_clk_disable:
- clk_disable(gdev->pruss_clk);
-
- return ret;
-}
-
-static int pruss_remove(struct platform_device *dev)
-{
- struct uio_pruss_dev *gdev = platform_get_drvdata(dev);
-
- pruss_cleanup(&dev->dev, gdev);
- return 0;
-}
-
-static struct platform_driver pruss_driver = {
- .probe = pruss_probe,
- .remove = pruss_remove,
- .driver = {
- .name = DRV_NAME,
- },
-};
-
-module_platform_driver(pruss_driver);
-
-MODULE_LICENSE("GPL v2");
-MODULE_VERSION(DRV_VERSION);
-MODULE_AUTHOR("Amit Chatterjee <amit.chatterjee@ti.com>");
-MODULE_AUTHOR("Pratheesh Gangadhar <pratheesh@ti.com>");
diff --git a/include/linux/counter.h b/include/linux/counter.h
index 702e9108bbb44e..cd35d8574ee219 100644
--- a/include/linux/counter.h
+++ b/include/linux/counter.h
@@ -359,7 +359,6 @@ struct counter_ops {
* @num_counts: number of Counts specified in @counts
* @ext: optional array of Counter device extensions
* @num_ext: number of Counter device extensions specified in @ext
- * @priv: optional private data supplied by driver
* @dev: internal device structure
* @chrdev: internal character device structure
* @events_list: list of current watching Counter events
@@ -602,6 +601,9 @@ struct counter_array {
#define COUNTER_COMP_FLOOR(_read, _write) \
COUNTER_COMP_COUNT_U64("floor", _read, _write)
+#define COUNTER_COMP_FREQUENCY(_read) \
+ COUNTER_COMP_SIGNAL_U64("frequency", _read, NULL)
+
#define COUNTER_COMP_POLARITY(_read, _write, _available) \
{ \
.type = COUNTER_COMP_SIGNAL_POLARITY, \
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index 96ceb4095425eb..5e39baa7f6cbb6 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -820,6 +820,8 @@ struct vmbus_requestor {
#define VMBUS_RQST_RESET (U64_MAX - 3)
struct vmbus_device {
+ /* preferred ring buffer size in KB, 0 means no preferred size for this device */
+ size_t pref_ring_size;
u16 dev_type;
guid_t guid;
bool perf_device;
diff --git a/include/linux/iio/adc/ad_sigma_delta.h b/include/linux/iio/adc/ad_sigma_delta.h
index 719cf9cc6e1ac4..383614ebd760fb 100644
--- a/include/linux/iio/adc/ad_sigma_delta.h
+++ b/include/linux/iio/adc/ad_sigma_delta.h
@@ -48,6 +48,7 @@ struct iio_dev;
* be used.
* @irq_flags: flags for the interrupt used by the triggered buffer
* @num_slots: Number of sequencer slots
+ * @irq_line: IRQ for reading conversions. If 0, spi->irq will be used
*/
struct ad_sigma_delta_info {
int (*set_channel)(struct ad_sigma_delta *, unsigned int channel);
@@ -62,6 +63,7 @@ struct ad_sigma_delta_info {
unsigned int data_reg;
unsigned long irq_flags;
unsigned int num_slots;
+ int irq_line;
};
/**
@@ -89,6 +91,7 @@ struct ad_sigma_delta {
unsigned int active_slots;
unsigned int current_slot;
unsigned int num_slots;
+ int irq_line;
bool status_appended;
/* map slots to channels in order to know what to expect from devices */
unsigned int *slots;
diff --git a/include/linux/iio/backend.h b/include/linux/iio/backend.h
index a6d79381866ece..9d144631134db0 100644
--- a/include/linux/iio/backend.h
+++ b/include/linux/iio/backend.h
@@ -4,6 +4,7 @@
#include <linux/types.h>
+struct iio_chan_spec;
struct fwnode_handle;
struct iio_backend;
struct device;
@@ -15,6 +16,26 @@ enum iio_backend_data_type {
IIO_BACKEND_DATA_TYPE_MAX
};
+enum iio_backend_data_source {
+ IIO_BACKEND_INTERNAL_CONTINUOS_WAVE,
+ IIO_BACKEND_EXTERNAL,
+ IIO_BACKEND_DATA_SOURCE_MAX
+};
+
+/**
+ * IIO_BACKEND_EX_INFO - Helper for an IIO extended channel attribute
+ * @_name: Attribute name
+ * @_shared: Whether the attribute is shared between all channels
+ * @_what: Data private to the driver
+ */
+#define IIO_BACKEND_EX_INFO(_name, _shared, _what) { \
+ .name = (_name), \
+ .shared = (_shared), \
+ .read = iio_backend_ext_info_get, \
+ .write = iio_backend_ext_info_set, \
+ .private = (_what), \
+}
+
/**
* struct iio_backend_data_fmt - Backend data format
* @type: Data type.
@@ -35,8 +56,13 @@ struct iio_backend_data_fmt {
* @chan_enable: Enable one channel.
* @chan_disable: Disable one channel.
* @data_format_set: Configure the data format for a specific channel.
+ * @data_source_set: Configure the data source for a specific channel.
+ * @set_sample_rate: Configure the sampling rate for a specific channel.
* @request_buffer: Request an IIO buffer.
* @free_buffer: Free an IIO buffer.
+ * @extend_chan_spec: Extend an IIO channel.
+ * @ext_info_set: Extended info setter.
+ * @ext_info_get: Extended info getter.
**/
struct iio_backend_ops {
int (*enable)(struct iio_backend *back);
@@ -45,10 +71,21 @@ struct iio_backend_ops {
int (*chan_disable)(struct iio_backend *back, unsigned int chan);
int (*data_format_set)(struct iio_backend *back, unsigned int chan,
const struct iio_backend_data_fmt *data);
+ int (*data_source_set)(struct iio_backend *back, unsigned int chan,
+ enum iio_backend_data_source data);
+ int (*set_sample_rate)(struct iio_backend *back, unsigned int chan,
+ u64 sample_rate_hz);
struct iio_buffer *(*request_buffer)(struct iio_backend *back,
struct iio_dev *indio_dev);
void (*free_buffer)(struct iio_backend *back,
struct iio_buffer *buffer);
+ int (*extend_chan_spec)(struct iio_backend *back,
+ struct iio_chan_spec *chan);
+ int (*ext_info_set)(struct iio_backend *back, uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len);
+ int (*ext_info_get)(struct iio_backend *back, uintptr_t private,
+ const struct iio_chan_spec *chan, char *buf);
};
int iio_backend_chan_enable(struct iio_backend *back, unsigned int chan);
@@ -56,10 +93,22 @@ int iio_backend_chan_disable(struct iio_backend *back, unsigned int chan);
int devm_iio_backend_enable(struct device *dev, struct iio_backend *back);
int iio_backend_data_format_set(struct iio_backend *back, unsigned int chan,
const struct iio_backend_data_fmt *data);
+int iio_backend_data_source_set(struct iio_backend *back, unsigned int chan,
+ enum iio_backend_data_source data);
+int iio_backend_set_sampling_freq(struct iio_backend *back, unsigned int chan,
+ u64 sample_rate_hz);
int devm_iio_backend_request_buffer(struct device *dev,
struct iio_backend *back,
struct iio_dev *indio_dev);
+ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private,
+ const struct iio_chan_spec *chan,
+ const char *buf, size_t len);
+ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private,
+ const struct iio_chan_spec *chan, char *buf);
+int iio_backend_extend_chan_spec(struct iio_dev *indio_dev,
+ struct iio_backend *back,
+ struct iio_chan_spec *chan);
void *iio_backend_get_priv(const struct iio_backend *conv);
struct iio_backend *devm_iio_backend_get(struct device *dev, const char *name);
struct iio_backend *
diff --git a/include/linux/iio/buffer-dma.h b/include/linux/iio/buffer-dma.h
index 18d3702fa95d13..6e27e47077d567 100644
--- a/include/linux/iio/buffer-dma.h
+++ b/include/linux/iio/buffer-dma.h
@@ -132,7 +132,9 @@ int iio_dma_buffer_disable(struct iio_buffer *buffer,
struct iio_dev *indio_dev);
int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n,
char __user *user_buffer);
-size_t iio_dma_buffer_data_available(struct iio_buffer *buffer);
+int iio_dma_buffer_write(struct iio_buffer *buffer, size_t n,
+ const char __user *user_buffer);
+size_t iio_dma_buffer_usage(struct iio_buffer *buffer);
int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd);
int iio_dma_buffer_set_length(struct iio_buffer *buffer, unsigned int length);
int iio_dma_buffer_request_update(struct iio_buffer *buffer);
diff --git a/include/linux/iio/buffer-dmaengine.h b/include/linux/iio/buffer-dmaengine.h
index cbb8ba957fade3..81d9a19aeb9199 100644
--- a/include/linux/iio/buffer-dmaengine.h
+++ b/include/linux/iio/buffer-dmaengine.h
@@ -7,14 +7,28 @@
#ifndef __IIO_DMAENGINE_H__
#define __IIO_DMAENGINE_H__
+#include <linux/iio/buffer.h>
+
struct iio_dev;
struct device;
-struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
- const char *channel);
void iio_dmaengine_buffer_free(struct iio_buffer *buffer);
-int devm_iio_dmaengine_buffer_setup(struct device *dev,
- struct iio_dev *indio_dev,
- const char *channel);
+struct iio_buffer *iio_dmaengine_buffer_setup_ext(struct device *dev,
+ struct iio_dev *indio_dev,
+ const char *channel,
+ enum iio_buffer_direction dir);
+
+#define iio_dmaengine_buffer_setup(dev, indio_dev, channel) \
+ iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, \
+ IIO_BUFFER_DIRECTION_IN)
+
+int devm_iio_dmaengine_buffer_setup_ext(struct device *dev,
+ struct iio_dev *indio_dev,
+ const char *channel,
+ enum iio_buffer_direction dir);
+
+#define devm_iio_dmaengine_buffer_setup(dev, indio_dev, channel) \
+ devm_iio_dmaengine_buffer_setup_ext(dev, indio_dev, channel, \
+ IIO_BUFFER_DIRECTION_IN)
#endif
diff --git a/include/linux/mfd/stm32-timers.h b/include/linux/mfd/stm32-timers.h
index ca35af30745f7f..9eb17481b07fac 100644
--- a/include/linux/mfd/stm32-timers.h
+++ b/include/linux/mfd/stm32-timers.h
@@ -41,6 +41,11 @@
#define TIM_SMCR_SMS (BIT(0) | BIT(1) | BIT(2)) /* Slave mode selection */
#define TIM_SMCR_TS (BIT(4) | BIT(5) | BIT(6)) /* Trigger selection */
#define TIM_DIER_UIE BIT(0) /* Update interrupt */
+#define TIM_DIER_CC1IE BIT(1) /* CC1 Interrupt Enable */
+#define TIM_DIER_CC2IE BIT(2) /* CC2 Interrupt Enable */
+#define TIM_DIER_CC3IE BIT(3) /* CC3 Interrupt Enable */
+#define TIM_DIER_CC4IE BIT(4) /* CC4 Interrupt Enable */
+#define TIM_DIER_CC_IE(x) BIT((x) + 1) /* CC1, CC2, CC3, CC4 interrupt enable */
#define TIM_DIER_UDE BIT(8) /* Update DMA request Enable */
#define TIM_DIER_CC1DE BIT(9) /* CC1 DMA request Enable */
#define TIM_DIER_CC2DE BIT(10) /* CC2 DMA request Enable */
@@ -49,6 +54,7 @@
#define TIM_DIER_COMDE BIT(13) /* COM DMA request Enable */
#define TIM_DIER_TDE BIT(14) /* Trigger DMA request Enable */
#define TIM_SR_UIF BIT(0) /* Update interrupt flag */
+#define TIM_SR_CC_IF(x) BIT((x) + 1) /* CC1, CC2, CC3, CC4 interrupt flag */
#define TIM_EGR_UG BIT(0) /* Update Generation */
#define TIM_CCMR_PE BIT(3) /* Channel Preload Enable */
#define TIM_CCMR_M1 (BIT(6) | BIT(5)) /* Channel PWM Mode 1 */
@@ -60,16 +66,23 @@
#define TIM_CCMR_CC1S_TI2 BIT(1) /* IC1/IC3 selects TI2/TI4 */
#define TIM_CCMR_CC2S_TI2 BIT(8) /* IC2/IC4 selects TI2/TI4 */
#define TIM_CCMR_CC2S_TI1 BIT(9) /* IC2/IC4 selects TI1/TI3 */
+#define TIM_CCMR_CC3S (BIT(0) | BIT(1)) /* Capture/compare 3 sel */
+#define TIM_CCMR_CC4S (BIT(8) | BIT(9)) /* Capture/compare 4 sel */
+#define TIM_CCMR_CC3S_TI3 BIT(0) /* IC3 selects TI3 */
+#define TIM_CCMR_CC4S_TI4 BIT(8) /* IC4 selects TI4 */
#define TIM_CCER_CC1E BIT(0) /* Capt/Comp 1 out Ena */
#define TIM_CCER_CC1P BIT(1) /* Capt/Comp 1 Polarity */
#define TIM_CCER_CC1NE BIT(2) /* Capt/Comp 1N out Ena */
#define TIM_CCER_CC1NP BIT(3) /* Capt/Comp 1N Polarity */
#define TIM_CCER_CC2E BIT(4) /* Capt/Comp 2 out Ena */
#define TIM_CCER_CC2P BIT(5) /* Capt/Comp 2 Polarity */
+#define TIM_CCER_CC2NP BIT(7) /* Capt/Comp 2N Polarity */
#define TIM_CCER_CC3E BIT(8) /* Capt/Comp 3 out Ena */
#define TIM_CCER_CC3P BIT(9) /* Capt/Comp 3 Polarity */
+#define TIM_CCER_CC3NP BIT(11) /* Capt/Comp 3N Polarity */
#define TIM_CCER_CC4E BIT(12) /* Capt/Comp 4 out Ena */
#define TIM_CCER_CC4P BIT(13) /* Capt/Comp 4 Polarity */
+#define TIM_CCER_CC4NP BIT(15) /* Capt/Comp 4N Polarity */
#define TIM_CCER_CCXE (BIT(0) | BIT(4) | BIT(8) | BIT(12))
#define TIM_BDTR_BKE(x) BIT(12 + (x) * 12) /* Break input enable */
#define TIM_BDTR_BKP(x) BIT(13 + (x) * 12) /* Break input polarity */
diff --git a/include/linux/platform_data/uio_pruss.h b/include/linux/platform_data/uio_pruss.h
deleted file mode 100644
index f76fa393b8023c..00000000000000
--- a/include/linux/platform_data/uio_pruss.h
+++ /dev/null
@@ -1,18 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * include/linux/platform_data/uio_pruss.h
- *
- * Platform data for uio_pruss driver
- *
- * Copyright (C) 2010-11 Texas Instruments Incorporated - https://www.ti.com/
- */
-
-#ifndef _UIO_PRUSS_H_
-#define _UIO_PRUSS_H_
-
-/* To configure the PRUSS INTC base offset for UIO driver */
-struct uio_pruss_pdata {
- u32 pintc_base;
- struct gen_pool *sram_pool;
-};
-#endif /* _UIO_PRUSS_H_ */
diff --git a/include/linux/property.h b/include/linux/property.h
index 3a1045eb786c4c..61fc20e5f81f8d 100644
--- a/include/linux/property.h
+++ b/include/linux/property.h
@@ -13,6 +13,7 @@
#include <linux/args.h>
#include <linux/array_size.h>
#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/fwnode.h>
#include <linux/stddef.h>
#include <linux/types.h>
@@ -174,13 +175,32 @@ struct fwnode_handle *device_get_next_child_node(const struct device *dev,
for (child = device_get_next_child_node(dev, NULL); child; \
child = device_get_next_child_node(dev, child))
+#define device_for_each_child_node_scoped(dev, child) \
+ for (struct fwnode_handle *child __free(fwnode_handle) = \
+ device_get_next_child_node(dev, NULL); \
+ child; child = device_get_next_child_node(dev, child))
+
struct fwnode_handle *fwnode_get_named_child_node(const struct fwnode_handle *fwnode,
const char *childname);
struct fwnode_handle *device_get_named_child_node(const struct device *dev,
const char *childname);
struct fwnode_handle *fwnode_handle_get(struct fwnode_handle *fwnode);
-void fwnode_handle_put(struct fwnode_handle *fwnode);
+
+/**
+ * fwnode_handle_put - Drop reference to a device node
+ * @fwnode: Pointer to the device node to drop the reference to.
+ *
+ * This has to be used when terminating device_for_each_child_node() iteration
+ * with break or return to prevent stale device node references from being left
+ * behind.
+ */
+static inline void fwnode_handle_put(struct fwnode_handle *fwnode)
+{
+ fwnode_call_void_op(fwnode, put);
+}
+
+DEFINE_FREE(fwnode_handle, struct fwnode_handle *, fwnode_handle_put(_T))
int fwnode_irq_get(const struct fwnode_handle *fwnode, unsigned int index);
int fwnode_irq_get_byname(const struct fwnode_handle *fwnode, const char *name);
diff --git a/include/uapi/linux/ntsync.h b/include/uapi/linux/ntsync.h
new file mode 100644
index 00000000000000..dcfa38fdc93c61
--- /dev/null
+++ b/include/uapi/linux/ntsync.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Kernel support for NT synchronization primitive emulation
+ *
+ * Copyright (C) 2021-2022 Elizabeth Figura <zfigura@codeweavers.com>
+ */
+
+#ifndef __LINUX_NTSYNC_H
+#define __LINUX_NTSYNC_H
+
+#include <linux/types.h>
+
+struct ntsync_sem_args {
+ __u32 sem;
+ __u32 count;
+ __u32 max;
+};
+
+#define NTSYNC_IOC_CREATE_SEM _IOWR('N', 0x80, struct ntsync_sem_args)
+
+#define NTSYNC_IOC_SEM_POST _IOWR('N', 0x81, __u32)
+
+#endif
diff --git a/include/uapi/misc/pvpanic.h b/include/uapi/misc/pvpanic.h
index 54b7485390d314..3f1745cd1b52ab 100644
--- a/include/uapi/misc/pvpanic.h
+++ b/include/uapi/misc/pvpanic.h
@@ -3,7 +3,10 @@
#ifndef __PVPANIC_H__
#define __PVPANIC_H__
-#define PVPANIC_PANICKED (1 << 0)
-#define PVPANIC_CRASH_LOADED (1 << 1)
+#include <linux/const.h>
+
+#define PVPANIC_PANICKED _BITUL(0)
+#define PVPANIC_CRASH_LOADED _BITUL(1)
+#define PVPANIC_SHUTDOWN _BITUL(2)
#endif /* __PVPANIC_H__ */
diff --git a/tools/hv/Build b/tools/hv/Build
index 6cf51fa4b30633..7d1f1698069be2 100644
--- a/tools/hv/Build
+++ b/tools/hv/Build
@@ -1,3 +1,4 @@
hv_kvp_daemon-y += hv_kvp_daemon.o
hv_vss_daemon-y += hv_vss_daemon.o
-hv_fcopy_daemon-y += hv_fcopy_daemon.o
+hv_fcopy_uio_daemon-y += hv_fcopy_uio_daemon.o
+hv_fcopy_uio_daemon-y += vmbus_bufring.o
diff --git a/tools/hv/Makefile b/tools/hv/Makefile
index fe770e679ae8fe..bb52871da34123 100644
--- a/tools/hv/Makefile
+++ b/tools/hv/Makefile
@@ -2,6 +2,7 @@
# Makefile for Hyper-V tools
include ../scripts/Makefile.include
+ARCH := $(shell uname -m 2>/dev/null)
sbindir ?= /usr/sbin
libexecdir ?= /usr/libexec
sharedstatedir ?= /var/lib
@@ -17,7 +18,10 @@ MAKEFLAGS += -r
override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include
-ALL_TARGETS := hv_kvp_daemon hv_vss_daemon hv_fcopy_daemon
+ALL_TARGETS := hv_kvp_daemon hv_vss_daemon
+ifneq ($(ARCH), aarch64)
+ALL_TARGETS += hv_fcopy_uio_daemon
+endif
ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
ALL_SCRIPTS := hv_get_dhcp_info.sh hv_get_dns_info.sh hv_set_ifconfig.sh
@@ -39,10 +43,10 @@ $(HV_VSS_DAEMON_IN): FORCE
$(OUTPUT)hv_vss_daemon: $(HV_VSS_DAEMON_IN)
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
-HV_FCOPY_DAEMON_IN := $(OUTPUT)hv_fcopy_daemon-in.o
-$(HV_FCOPY_DAEMON_IN): FORCE
- $(Q)$(MAKE) $(build)=hv_fcopy_daemon
-$(OUTPUT)hv_fcopy_daemon: $(HV_FCOPY_DAEMON_IN)
+HV_FCOPY_UIO_DAEMON_IN := $(OUTPUT)hv_fcopy_uio_daemon-in.o
+$(HV_FCOPY_UIO_DAEMON_IN): FORCE
+ $(Q)$(MAKE) $(build)=hv_fcopy_uio_daemon
+$(OUTPUT)hv_fcopy_uio_daemon: $(HV_FCOPY_UIO_DAEMON_IN)
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
clean:
diff --git a/tools/hv/hv_fcopy_daemon.c b/tools/hv/hv_fcopy_daemon.c
deleted file mode 100644
index 16d629b22c2542..00000000000000
--- a/tools/hv/hv_fcopy_daemon.c
+++ /dev/null
@@ -1,266 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * An implementation of host to guest copy functionality for Linux.
- *
- * Copyright (C) 2014, Microsoft, Inc.
- *
- * Author : K. Y. Srinivasan <kys@microsoft.com>
- */
-
-
-#include <sys/types.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <linux/hyperv.h>
-#include <linux/limits.h>
-#include <syslog.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <getopt.h>
-
-static int target_fd;
-static char target_fname[PATH_MAX];
-static unsigned long long filesize;
-
-static int hv_start_fcopy(struct hv_start_fcopy *smsg)
-{
- int error = HV_E_FAIL;
- char *q, *p;
-
- filesize = 0;
- p = (char *)smsg->path_name;
- snprintf(target_fname, sizeof(target_fname), "%s/%s",
- (char *)smsg->path_name, (char *)smsg->file_name);
-
- syslog(LOG_INFO, "Target file name: %s", target_fname);
- /*
- * Check to see if the path is already in place; if not,
- * create if required.
- */
- while ((q = strchr(p, '/')) != NULL) {
- if (q == p) {
- p++;
- continue;
- }
- *q = '\0';
- if (access((char *)smsg->path_name, F_OK)) {
- if (smsg->copy_flags & CREATE_PATH) {
- if (mkdir((char *)smsg->path_name, 0755)) {
- syslog(LOG_ERR, "Failed to create %s",
- (char *)smsg->path_name);
- goto done;
- }
- } else {
- syslog(LOG_ERR, "Invalid path: %s",
- (char *)smsg->path_name);
- goto done;
- }
- }
- p = q + 1;
- *q = '/';
- }
-
- if (!access(target_fname, F_OK)) {
- syslog(LOG_INFO, "File: %s exists", target_fname);
- if (!(smsg->copy_flags & OVER_WRITE)) {
- error = HV_ERROR_ALREADY_EXISTS;
- goto done;
- }
- }
-
- target_fd = open(target_fname,
- O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0744);
- if (target_fd == -1) {
- syslog(LOG_INFO, "Open Failed: %s", strerror(errno));
- goto done;
- }
-
- error = 0;
-done:
- if (error)
- target_fname[0] = '\0';
- return error;
-}
-
-static int hv_copy_data(struct hv_do_fcopy *cpmsg)
-{
- ssize_t bytes_written;
- int ret = 0;
-
- bytes_written = pwrite(target_fd, cpmsg->data, cpmsg->size,
- cpmsg->offset);
-
- filesize += cpmsg->size;
- if (bytes_written != cpmsg->size) {
- switch (errno) {
- case ENOSPC:
- ret = HV_ERROR_DISK_FULL;
- break;
- default:
- ret = HV_E_FAIL;
- break;
- }
- syslog(LOG_ERR, "pwrite failed to write %llu bytes: %ld (%s)",
- filesize, (long)bytes_written, strerror(errno));
- }
-
- return ret;
-}
-
-/*
- * Reset target_fname to "" in the two below functions for hibernation: if
- * the fcopy operation is aborted by hibernation, the daemon should remove the
- * partially-copied file; to achieve this, the hv_utils driver always fakes a
- * CANCEL_FCOPY message upon suspend, and later when the VM resumes back,
- * the daemon calls hv_copy_cancel() to remove the file; if a file is copied
- * successfully before suspend, hv_copy_finished() must reset target_fname to
- * avoid that the file can be incorrectly removed upon resume, since the faked
- * CANCEL_FCOPY message is spurious in this case.
- */
-static int hv_copy_finished(void)
-{
- close(target_fd);
- target_fname[0] = '\0';
- return 0;
-}
-static int hv_copy_cancel(void)
-{
- close(target_fd);
- if (strlen(target_fname) > 0) {
- unlink(target_fname);
- target_fname[0] = '\0';
- }
- return 0;
-
-}
-
-void print_usage(char *argv[])
-{
- fprintf(stderr, "Usage: %s [options]\n"
- "Options are:\n"
- " -n, --no-daemon stay in foreground, don't daemonize\n"
- " -h, --help print this help\n", argv[0]);
-}
-
-int main(int argc, char *argv[])
-{
- int fcopy_fd = -1;
- int error;
- int daemonize = 1, long_index = 0, opt;
- int version = FCOPY_CURRENT_VERSION;
- union {
- struct hv_fcopy_hdr hdr;
- struct hv_start_fcopy start;
- struct hv_do_fcopy copy;
- __u32 kernel_modver;
- } buffer = { };
- int in_handshake;
-
- static struct option long_options[] = {
- {"help", no_argument, 0, 'h' },
- {"no-daemon", no_argument, 0, 'n' },
- {0, 0, 0, 0 }
- };
-
- while ((opt = getopt_long(argc, argv, "hn", long_options,
- &long_index)) != -1) {
- switch (opt) {
- case 'n':
- daemonize = 0;
- break;
- case 'h':
- default:
- print_usage(argv);
- exit(EXIT_FAILURE);
- }
- }
-
- if (daemonize && daemon(1, 0)) {
- syslog(LOG_ERR, "daemon() failed; error: %s", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- openlog("HV_FCOPY", 0, LOG_USER);
- syslog(LOG_INFO, "starting; pid is:%d", getpid());
-
-reopen_fcopy_fd:
- if (fcopy_fd != -1)
- close(fcopy_fd);
- /* Remove any possible partially-copied file on error */
- hv_copy_cancel();
- in_handshake = 1;
- fcopy_fd = open("/dev/vmbus/hv_fcopy", O_RDWR);
-
- if (fcopy_fd < 0) {
- syslog(LOG_ERR, "open /dev/vmbus/hv_fcopy failed; error: %d %s",
- errno, strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- /*
- * Register with the kernel.
- */
- if ((write(fcopy_fd, &version, sizeof(int))) != sizeof(int)) {
- syslog(LOG_ERR, "Registration failed: %s", strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- while (1) {
- /*
- * In this loop we process fcopy messages after the
- * handshake is complete.
- */
- ssize_t len;
-
- len = pread(fcopy_fd, &buffer, sizeof(buffer), 0);
- if (len < 0) {
- syslog(LOG_ERR, "pread failed: %s", strerror(errno));
- goto reopen_fcopy_fd;
- }
-
- if (in_handshake) {
- if (len != sizeof(buffer.kernel_modver)) {
- syslog(LOG_ERR, "invalid version negotiation");
- exit(EXIT_FAILURE);
- }
- in_handshake = 0;
- syslog(LOG_INFO, "kernel module version: %u",
- buffer.kernel_modver);
- continue;
- }
-
- switch (buffer.hdr.operation) {
- case START_FILE_COPY:
- error = hv_start_fcopy(&buffer.start);
- break;
- case WRITE_TO_FILE:
- error = hv_copy_data(&buffer.copy);
- break;
- case COMPLETE_FCOPY:
- error = hv_copy_finished();
- break;
- case CANCEL_FCOPY:
- error = hv_copy_cancel();
- break;
-
- default:
- error = HV_E_FAIL;
- syslog(LOG_ERR, "Unknown operation: %d",
- buffer.hdr.operation);
-
- }
-
- /*
- * pwrite() may return an error due to the faked CANCEL_FCOPY
- * message upon hibernation. Ignore the error by resetting the
- * dev file, i.e. closing and re-opening it.
- */
- if (pwrite(fcopy_fd, &error, sizeof(int), 0) != sizeof(int)) {
- syslog(LOG_ERR, "pwrite failed: %s", strerror(errno));
- goto reopen_fcopy_fd;
- }
- }
-}
diff --git a/tools/hv/hv_fcopy_uio_daemon.c b/tools/hv/hv_fcopy_uio_daemon.c
new file mode 100644
index 00000000000000..3ce316cc9f970b
--- /dev/null
+++ b/tools/hv/hv_fcopy_uio_daemon.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * An implementation of host to guest copy functionality for Linux.
+ *
+ * Copyright (C) 2023, Microsoft, Inc.
+ *
+ * Author : K. Y. Srinivasan <kys@microsoft.com>
+ * Author : Saurabh Sengar <ssengar@microsoft.com>
+ *
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <sys/stat.h>
+#include <linux/hyperv.h>
+#include <linux/limits.h>
+#include "vmbus_bufring.h"
+
+#define ICMSGTYPE_NEGOTIATE 0
+#define ICMSGTYPE_FCOPY 7
+
+#define WIN8_SRV_MAJOR 1
+#define WIN8_SRV_MINOR 1
+#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR)
+
+#define MAX_FOLDER_NAME 15
+#define MAX_PATH_LEN 15
+#define FCOPY_UIO "/sys/bus/vmbus/devices/eb765408-105f-49b6-b4aa-c123b64d17d4/uio"
+
+#define FCOPY_VER_COUNT 1
+static const int fcopy_versions[] = {
+ WIN8_SRV_VERSION
+};
+
+#define FW_VER_COUNT 1
+static const int fw_versions[] = {
+ UTIL_FW_VERSION
+};
+
+#define HV_RING_SIZE 0x4000 /* 16KB ring buffer size */
+
+unsigned char desc[HV_RING_SIZE];
+
+static int target_fd;
+static char target_fname[PATH_MAX];
+static unsigned long long filesize;
+
+static int hv_fcopy_create_file(char *file_name, char *path_name, __u32 flags)
+{
+ int error = HV_E_FAIL;
+ char *q, *p;
+
+ filesize = 0;
+ p = path_name;
+ snprintf(target_fname, sizeof(target_fname), "%s/%s",
+ path_name, file_name);
+
+ /*
+ * Check to see if the path is already in place; if not,
+ * create if required.
+ */
+ while ((q = strchr(p, '/')) != NULL) {
+ if (q == p) {
+ p++;
+ continue;
+ }
+ *q = '\0';
+ if (access(path_name, F_OK)) {
+ if (flags & CREATE_PATH) {
+ if (mkdir(path_name, 0755)) {
+ syslog(LOG_ERR, "Failed to create %s",
+ path_name);
+ goto done;
+ }
+ } else {
+ syslog(LOG_ERR, "Invalid path: %s", path_name);
+ goto done;
+ }
+ }
+ p = q + 1;
+ *q = '/';
+ }
+
+ if (!access(target_fname, F_OK)) {
+ syslog(LOG_INFO, "File: %s exists", target_fname);
+ if (!(flags & OVER_WRITE)) {
+ error = HV_ERROR_ALREADY_EXISTS;
+ goto done;
+ }
+ }
+
+ target_fd = open(target_fname,
+ O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0744);
+ if (target_fd == -1) {
+ syslog(LOG_INFO, "Open Failed: %s", strerror(errno));
+ goto done;
+ }
+
+ error = 0;
+done:
+ if (error)
+ target_fname[0] = '\0';
+ return error;
+}
+
+/* copy the data into the file */
+static int hv_copy_data(struct hv_do_fcopy *cpmsg)
+{
+ ssize_t len;
+ int ret = 0;
+
+ len = pwrite(target_fd, cpmsg->data, cpmsg->size, cpmsg->offset);
+
+ filesize += cpmsg->size;
+ if (len != cpmsg->size) {
+ switch (errno) {
+ case ENOSPC:
+ ret = HV_ERROR_DISK_FULL;
+ break;
+ default:
+ ret = HV_E_FAIL;
+ break;
+ }
+ syslog(LOG_ERR, "pwrite failed to write %llu bytes: %ld (%s)",
+ filesize, (long)len, strerror(errno));
+ }
+
+ return ret;
+}
+
+static int hv_copy_finished(void)
+{
+ close(target_fd);
+ target_fname[0] = '\0';
+
+ return 0;
+}
+
+static void print_usage(char *argv[])
+{
+ fprintf(stderr, "Usage: %s [options]\n"
+ "Options are:\n"
+ " -n, --no-daemon stay in foreground, don't daemonize\n"
+ " -h, --help print this help\n", argv[0]);
+}
+
+static bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp, unsigned char *buf,
+ unsigned int buflen, const int *fw_version, int fw_vercnt,
+ const int *srv_version, int srv_vercnt,
+ int *nego_fw_version, int *nego_srv_version)
+{
+ int icframe_major, icframe_minor;
+ int icmsg_major, icmsg_minor;
+ int fw_major, fw_minor;
+ int srv_major, srv_minor;
+ int i, j;
+ bool found_match = false;
+ struct icmsg_negotiate *negop;
+
+ /* Check that there's enough space for icframe_vercnt, icmsg_vercnt */
+ if (buflen < ICMSG_HDR + offsetof(struct icmsg_negotiate, reserved)) {
+ syslog(LOG_ERR, "Invalid icmsg negotiate");
+ return false;
+ }
+
+ icmsghdrp->icmsgsize = 0x10;
+ negop = (struct icmsg_negotiate *)&buf[ICMSG_HDR];
+
+ icframe_major = negop->icframe_vercnt;
+ icframe_minor = 0;
+
+ icmsg_major = negop->icmsg_vercnt;
+ icmsg_minor = 0;
+
+ /* Validate negop packet */
+ if (icframe_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT ||
+ icmsg_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT ||
+ ICMSG_NEGOTIATE_PKT_SIZE(icframe_major, icmsg_major) > buflen) {
+ syslog(LOG_ERR, "Invalid icmsg negotiate - icframe_major: %u, icmsg_major: %u\n",
+ icframe_major, icmsg_major);
+ goto fw_error;
+ }
+
+ /*
+ * Select the framework version number we will
+ * support.
+ */
+
+ for (i = 0; i < fw_vercnt; i++) {
+ fw_major = (fw_version[i] >> 16);
+ fw_minor = (fw_version[i] & 0xFFFF);
+
+ for (j = 0; j < negop->icframe_vercnt; j++) {
+ if (negop->icversion_data[j].major == fw_major &&
+ negop->icversion_data[j].minor == fw_minor) {
+ icframe_major = negop->icversion_data[j].major;
+ icframe_minor = negop->icversion_data[j].minor;
+ found_match = true;
+ break;
+ }
+ }
+
+ if (found_match)
+ break;
+ }
+
+ if (!found_match)
+ goto fw_error;
+
+ found_match = false;
+
+ for (i = 0; i < srv_vercnt; i++) {
+ srv_major = (srv_version[i] >> 16);
+ srv_minor = (srv_version[i] & 0xFFFF);
+
+ for (j = negop->icframe_vercnt;
+ (j < negop->icframe_vercnt + negop->icmsg_vercnt);
+ j++) {
+ if (negop->icversion_data[j].major == srv_major &&
+ negop->icversion_data[j].minor == srv_minor) {
+ icmsg_major = negop->icversion_data[j].major;
+ icmsg_minor = negop->icversion_data[j].minor;
+ found_match = true;
+ break;
+ }
+ }
+
+ if (found_match)
+ break;
+ }
+
+ /*
+ * Respond with the framework and service
+ * version numbers we can support.
+ */
+fw_error:
+ if (!found_match) {
+ negop->icframe_vercnt = 0;
+ negop->icmsg_vercnt = 0;
+ } else {
+ negop->icframe_vercnt = 1;
+ negop->icmsg_vercnt = 1;
+ }
+
+ if (nego_fw_version)
+ *nego_fw_version = (icframe_major << 16) | icframe_minor;
+
+ if (nego_srv_version)
+ *nego_srv_version = (icmsg_major << 16) | icmsg_minor;
+
+ negop->icversion_data[0].major = icframe_major;
+ negop->icversion_data[0].minor = icframe_minor;
+ negop->icversion_data[1].major = icmsg_major;
+ negop->icversion_data[1].minor = icmsg_minor;
+
+ return found_match;
+}
+
+static void wcstoutf8(char *dest, const __u16 *src, size_t dest_size)
+{
+ size_t len = 0;
+
+ while (len < dest_size) {
+ if (src[len] < 0x80)
+ dest[len++] = (char)(*src++);
+ else
+ dest[len++] = 'X';
+ }
+
+ dest[len] = '\0';
+}
+
+static int hv_fcopy_start(struct hv_start_fcopy *smsg_in)
+{
+ setlocale(LC_ALL, "en_US.utf8");
+ size_t file_size, path_size;
+ char *file_name, *path_name;
+ char *in_file_name = (char *)smsg_in->file_name;
+ char *in_path_name = (char *)smsg_in->path_name;
+
+ file_size = wcstombs(NULL, (const wchar_t *restrict)in_file_name, 0) + 1;
+ path_size = wcstombs(NULL, (const wchar_t *restrict)in_path_name, 0) + 1;
+
+ file_name = (char *)malloc(file_size * sizeof(char));
+ path_name = (char *)malloc(path_size * sizeof(char));
+
+ wcstoutf8(file_name, (__u16 *)in_file_name, file_size);
+ wcstoutf8(path_name, (__u16 *)in_path_name, path_size);
+
+ return hv_fcopy_create_file(file_name, path_name, smsg_in->copy_flags);
+}
+
+static int hv_fcopy_send_data(struct hv_fcopy_hdr *fcopy_msg, int recvlen)
+{
+ int operation = fcopy_msg->operation;
+
+ /*
+ * The strings sent from the host are encoded in
+ * utf16; convert it to utf8 strings.
+ * The host assures us that the utf16 strings will not exceed
+ * the max lengths specified. We will however, reserve room
+ * for the string terminating character - in the utf16s_utf8s()
+ * function we limit the size of the buffer where the converted
+ * string is placed to W_MAX_PATH -1 to guarantee
+ * that the strings can be properly terminated!
+ */
+
+ switch (operation) {
+ case START_FILE_COPY:
+ return hv_fcopy_start((struct hv_start_fcopy *)fcopy_msg);
+ case WRITE_TO_FILE:
+ return hv_copy_data((struct hv_do_fcopy *)fcopy_msg);
+ case COMPLETE_FCOPY:
+ return hv_copy_finished();
+ }
+
+ return HV_E_FAIL;
+}
+
+/* process the packet recv from host */
+static int fcopy_pkt_process(struct vmbus_br *txbr)
+{
+ int ret, offset, pktlen;
+ int fcopy_srv_version;
+ const struct vmbus_chanpkt_hdr *pkt;
+ struct hv_fcopy_hdr *fcopy_msg;
+ struct icmsg_hdr *icmsghdr;
+
+ pkt = (const struct vmbus_chanpkt_hdr *)desc;
+ offset = pkt->hlen << 3;
+ pktlen = (pkt->tlen << 3) - offset;
+ icmsghdr = (struct icmsg_hdr *)&desc[offset + sizeof(struct vmbuspipe_hdr)];
+ icmsghdr->status = HV_E_FAIL;
+
+ if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) {
+ if (vmbus_prep_negotiate_resp(icmsghdr, desc + offset, pktlen, fw_versions,
+ FW_VER_COUNT, fcopy_versions, FCOPY_VER_COUNT,
+ NULL, &fcopy_srv_version)) {
+ syslog(LOG_INFO, "FCopy IC version %d.%d",
+ fcopy_srv_version >> 16, fcopy_srv_version & 0xFFFF);
+ icmsghdr->status = 0;
+ }
+ } else if (icmsghdr->icmsgtype == ICMSGTYPE_FCOPY) {
+ /* Ensure recvlen is big enough to contain hv_fcopy_hdr */
+ if (pktlen < ICMSG_HDR + sizeof(struct hv_fcopy_hdr)) {
+ syslog(LOG_ERR, "Invalid Fcopy hdr. Packet length too small: %u",
+ pktlen);
+ return -ENOBUFS;
+ }
+
+ fcopy_msg = (struct hv_fcopy_hdr *)&desc[offset + ICMSG_HDR];
+ icmsghdr->status = hv_fcopy_send_data(fcopy_msg, pktlen);
+ }
+
+ icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE;
+ ret = rte_vmbus_chan_send(txbr, 0x6, desc + offset, pktlen, 0);
+ if (ret) {
+ syslog(LOG_ERR, "Write to ringbuffer failed err: %d", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void fcopy_get_first_folder(char *path, char *chan_no)
+{
+ DIR *dir = opendir(path);
+ struct dirent *entry;
+
+ if (!dir) {
+ syslog(LOG_ERR, "Failed to open directory (errno=%s).\n", strerror(errno));
+ return;
+ }
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_type == DT_DIR && strcmp(entry->d_name, ".") != 0 &&
+ strcmp(entry->d_name, "..") != 0) {
+ strcpy(chan_no, entry->d_name);
+ break;
+ }
+ }
+
+ closedir(dir);
+}
+
+int main(int argc, char *argv[])
+{
+ int fcopy_fd = -1, tmp = 1;
+ int daemonize = 1, long_index = 0, opt, ret = -EINVAL;
+ struct vmbus_br txbr, rxbr;
+ void *ring;
+ uint32_t len = HV_RING_SIZE;
+ char uio_name[MAX_FOLDER_NAME] = {0};
+ char uio_dev_path[MAX_PATH_LEN] = {0};
+
+ static struct option long_options[] = {
+ {"help", no_argument, 0, 'h' },
+ {"no-daemon", no_argument, 0, 'n' },
+ {0, 0, 0, 0 }
+ };
+
+ while ((opt = getopt_long(argc, argv, "hn", long_options,
+ &long_index)) != -1) {
+ switch (opt) {
+ case 'n':
+ daemonize = 0;
+ break;
+ case 'h':
+ default:
+ print_usage(argv);
+ goto exit;
+ }
+ }
+
+ if (daemonize && daemon(1, 0)) {
+ syslog(LOG_ERR, "daemon() failed; error: %s", strerror(errno));
+ goto exit;
+ }
+
+ openlog("HV_UIO_FCOPY", 0, LOG_USER);
+ syslog(LOG_INFO, "starting; pid is:%d", getpid());
+
+ fcopy_get_first_folder(FCOPY_UIO, uio_name);
+ snprintf(uio_dev_path, sizeof(uio_dev_path), "/dev/%s", uio_name);
+ fcopy_fd = open(uio_dev_path, O_RDWR);
+
+ if (fcopy_fd < 0) {
+ syslog(LOG_ERR, "open %s failed; error: %d %s",
+ uio_dev_path, errno, strerror(errno));
+ ret = fcopy_fd;
+ goto exit;
+ }
+
+ ring = vmbus_uio_map(&fcopy_fd, HV_RING_SIZE);
+ if (!ring) {
+ ret = errno;
+ syslog(LOG_ERR, "mmap ringbuffer failed; error: %d %s", ret, strerror(ret));
+ goto close;
+ }
+ vmbus_br_setup(&txbr, ring, HV_RING_SIZE);
+ vmbus_br_setup(&rxbr, (char *)ring + HV_RING_SIZE, HV_RING_SIZE);
+
+ rxbr.vbr->imask = 0;
+
+ while (1) {
+ /*
+ * In this loop we process fcopy messages after the
+ * handshake is complete.
+ */
+ ret = pread(fcopy_fd, &tmp, sizeof(int), 0);
+ if (ret < 0) {
+ syslog(LOG_ERR, "pread failed: %s", strerror(errno));
+ continue;
+ }
+
+ len = HV_RING_SIZE;
+ ret = rte_vmbus_chan_recv_raw(&rxbr, desc, &len);
+ if (unlikely(ret <= 0)) {
+ /* This indicates a failure to communicate (or worse) */
+ syslog(LOG_ERR, "VMBus channel recv error: %d", ret);
+ } else {
+ ret = fcopy_pkt_process(&txbr);
+ if (ret < 0)
+ goto close;
+
+ /* Signal host */
+ if ((write(fcopy_fd, &tmp, sizeof(int))) != sizeof(int)) {
+ ret = errno;
+ syslog(LOG_ERR, "Signal to host failed: %s\n", strerror(ret));
+ goto close;
+ }
+ }
+ }
+close:
+ close(fcopy_fd);
+exit:
+ return ret;
+}
diff --git a/tools/hv/vmbus_bufring.c b/tools/hv/vmbus_bufring.c
new file mode 100644
index 00000000000000..bac32c1109df86
--- /dev/null
+++ b/tools/hv/vmbus_bufring.c
@@ -0,0 +1,318 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2009-2012,2016,2023 Microsoft Corp.
+ * Copyright (c) 2012 NetApp Inc.
+ * Copyright (c) 2012 Citrix Inc.
+ * All rights reserved.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <emmintrin.h>
+#include <linux/limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include "vmbus_bufring.h"
+
+/**
+ * Compiler barrier.
+ *
+ * Guarantees that operation reordering does not occur at compile time
+ * for operations directly before and after the barrier.
+ */
+#define rte_compiler_barrier() ({ asm volatile ("" : : : "memory"); })
+
+#define VMBUS_RQST_ERROR 0xFFFFFFFFFFFFFFFF
+#define ALIGN(val, align) ((typeof(val))((val) & (~((typeof(val))((align) - 1)))))
+
+void *vmbus_uio_map(int *fd, int size)
+{
+ void *map;
+
+ map = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0);
+ if (map == MAP_FAILED)
+ return NULL;
+
+ return map;
+}
+
+/* Increase bufring index by inc with wraparound */
+static inline uint32_t vmbus_br_idxinc(uint32_t idx, uint32_t inc, uint32_t sz)
+{
+ idx += inc;
+ if (idx >= sz)
+ idx -= sz;
+
+ return idx;
+}
+
+void vmbus_br_setup(struct vmbus_br *br, void *buf, unsigned int blen)
+{
+ br->vbr = buf;
+ br->windex = br->vbr->windex;
+ br->dsize = blen - sizeof(struct vmbus_bufring);
+}
+
+static inline __always_inline void
+rte_smp_mb(void)
+{
+ asm volatile("lock addl $0, -128(%%rsp); " ::: "memory");
+}
+
+static inline int
+rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)
+{
+ uint8_t res;
+
+ asm volatile("lock ; "
+ "cmpxchgl %[src], %[dst];"
+ "sete %[res];"
+ : [res] "=a" (res), /* output */
+ [dst] "=m" (*dst)
+ : [src] "r" (src), /* input */
+ "a" (exp),
+ "m" (*dst)
+ : "memory"); /* no-clobber list */
+ return res;
+}
+
+static inline uint32_t
+vmbus_txbr_copyto(const struct vmbus_br *tbr, uint32_t windex,
+ const void *src0, uint32_t cplen)
+{
+ uint8_t *br_data = tbr->vbr->data;
+ uint32_t br_dsize = tbr->dsize;
+ const uint8_t *src = src0;
+
+ /* XXX use double mapping like Linux kernel? */
+ if (cplen > br_dsize - windex) {
+ uint32_t fraglen = br_dsize - windex;
+
+ /* Wrap-around detected */
+ memcpy(br_data + windex, src, fraglen);
+ memcpy(br_data, src + fraglen, cplen - fraglen);
+ } else {
+ memcpy(br_data + windex, src, cplen);
+ }
+
+ return vmbus_br_idxinc(windex, cplen, br_dsize);
+}
+
+/*
+ * Write scattered channel packet to TX bufring.
+ *
+ * The offset of this channel packet is written as a 64bits value
+ * immediately after this channel packet.
+ *
+ * The write goes through three stages:
+ * 1. Reserve space in ring buffer for the new data.
+ * Writer atomically moves priv_write_index.
+ * 2. Copy the new data into the ring.
+ * 3. Update the tail of the ring (visible to host) that indicates
+ * next read location. Writer updates write_index
+ */
+static int
+vmbus_txbr_write(struct vmbus_br *tbr, const struct iovec iov[], int iovlen)
+{
+ struct vmbus_bufring *vbr = tbr->vbr;
+ uint32_t ring_size = tbr->dsize;
+ uint32_t old_windex, next_windex, windex, total;
+ uint64_t save_windex;
+ int i;
+
+ total = 0;
+ for (i = 0; i < iovlen; i++)
+ total += iov[i].iov_len;
+ total += sizeof(save_windex);
+
+ /* Reserve space in ring */
+ do {
+ uint32_t avail;
+
+ /* Get current free location */
+ old_windex = tbr->windex;
+
+ /* Prevent compiler reordering this with calculation */
+ rte_compiler_barrier();
+
+ avail = vmbus_br_availwrite(tbr, old_windex);
+
+ /* If not enough space in ring, then tell caller. */
+ if (avail <= total)
+ return -EAGAIN;
+
+ next_windex = vmbus_br_idxinc(old_windex, total, ring_size);
+
+ /* Atomic update of next write_index for other threads */
+ } while (!rte_atomic32_cmpset(&tbr->windex, old_windex, next_windex));
+
+ /* Space from old..new is now reserved */
+ windex = old_windex;
+ for (i = 0; i < iovlen; i++)
+ windex = vmbus_txbr_copyto(tbr, windex, iov[i].iov_base, iov[i].iov_len);
+
+ /* Set the offset of the current channel packet. */
+ save_windex = ((uint64_t)old_windex) << 32;
+ windex = vmbus_txbr_copyto(tbr, windex, &save_windex,
+ sizeof(save_windex));
+
+ /* The region reserved should match region used */
+ if (windex != next_windex)
+ return -EINVAL;
+
+ /* Ensure that data is available before updating host index */
+ rte_compiler_barrier();
+
+ /* Checkin for our reservation. wait for our turn to update host */
+ while (!rte_atomic32_cmpset(&vbr->windex, old_windex, next_windex))
+ _mm_pause();
+
+ return 0;
+}
+
+int rte_vmbus_chan_send(struct vmbus_br *txbr, uint16_t type, void *data,
+ uint32_t dlen, uint32_t flags)
+{
+ struct vmbus_chanpkt pkt;
+ unsigned int pktlen, pad_pktlen;
+ const uint32_t hlen = sizeof(pkt);
+ uint64_t pad = 0;
+ struct iovec iov[3];
+ int error;
+
+ pktlen = hlen + dlen;
+ pad_pktlen = ALIGN(pktlen, sizeof(uint64_t));
+
+ pkt.hdr.type = type;
+ pkt.hdr.flags = flags;
+ pkt.hdr.hlen = hlen >> VMBUS_CHANPKT_SIZE_SHIFT;
+ pkt.hdr.tlen = pad_pktlen >> VMBUS_CHANPKT_SIZE_SHIFT;
+ pkt.hdr.xactid = VMBUS_RQST_ERROR;
+
+ iov[0].iov_base = &pkt;
+ iov[0].iov_len = hlen;
+ iov[1].iov_base = data;
+ iov[1].iov_len = dlen;
+ iov[2].iov_base = &pad;
+ iov[2].iov_len = pad_pktlen - pktlen;
+
+ error = vmbus_txbr_write(txbr, iov, 3);
+
+ return error;
+}
+
+static inline uint32_t
+vmbus_rxbr_copyfrom(const struct vmbus_br *rbr, uint32_t rindex,
+ void *dst0, size_t cplen)
+{
+ const uint8_t *br_data = rbr->vbr->data;
+ uint32_t br_dsize = rbr->dsize;
+ uint8_t *dst = dst0;
+
+ if (cplen > br_dsize - rindex) {
+ uint32_t fraglen = br_dsize - rindex;
+
+ /* Wrap-around detected. */
+ memcpy(dst, br_data + rindex, fraglen);
+ memcpy(dst + fraglen, br_data, cplen - fraglen);
+ } else {
+ memcpy(dst, br_data + rindex, cplen);
+ }
+
+ return vmbus_br_idxinc(rindex, cplen, br_dsize);
+}
+
+/* Copy data from receive ring but don't change index */
+static int
+vmbus_rxbr_peek(const struct vmbus_br *rbr, void *data, size_t dlen)
+{
+ uint32_t avail;
+
+ /*
+ * The requested data and the 64bits channel packet
+ * offset should be there at least.
+ */
+ avail = vmbus_br_availread(rbr);
+ if (avail < dlen + sizeof(uint64_t))
+ return -EAGAIN;
+
+ vmbus_rxbr_copyfrom(rbr, rbr->vbr->rindex, data, dlen);
+ return 0;
+}
+
+/*
+ * Copy data from receive ring and change index
+ * NOTE:
+ * We assume (dlen + skip) == sizeof(channel packet).
+ */
+static int
+vmbus_rxbr_read(struct vmbus_br *rbr, void *data, size_t dlen, size_t skip)
+{
+ struct vmbus_bufring *vbr = rbr->vbr;
+ uint32_t br_dsize = rbr->dsize;
+ uint32_t rindex;
+
+ if (vmbus_br_availread(rbr) < dlen + skip + sizeof(uint64_t))
+ return -EAGAIN;
+
+ /* Record where host was when we started read (for debug) */
+ rbr->windex = rbr->vbr->windex;
+
+ /*
+ * Copy channel packet from RX bufring.
+ */
+ rindex = vmbus_br_idxinc(rbr->vbr->rindex, skip, br_dsize);
+ rindex = vmbus_rxbr_copyfrom(rbr, rindex, data, dlen);
+
+ /*
+ * Discard this channel packet's 64bits offset, which is useless to us.
+ */
+ rindex = vmbus_br_idxinc(rindex, sizeof(uint64_t), br_dsize);
+
+ /* Update the read index _after_ the channel packet is fetched. */
+ rte_compiler_barrier();
+
+ vbr->rindex = rindex;
+
+ return 0;
+}
+
+int rte_vmbus_chan_recv_raw(struct vmbus_br *rxbr,
+ void *data, uint32_t *len)
+{
+ struct vmbus_chanpkt_hdr pkt;
+ uint32_t dlen, bufferlen = *len;
+ int error;
+
+ error = vmbus_rxbr_peek(rxbr, &pkt, sizeof(pkt));
+ if (error)
+ return error;
+
+ if (unlikely(pkt.hlen < VMBUS_CHANPKT_HLEN_MIN))
+ /* XXX this channel is dead actually. */
+ return -EIO;
+
+ if (unlikely(pkt.hlen > pkt.tlen))
+ return -EIO;
+
+ /* Length are in quad words */
+ dlen = pkt.tlen << VMBUS_CHANPKT_SIZE_SHIFT;
+ *len = dlen;
+
+ /* If caller buffer is not large enough */
+ if (unlikely(dlen > bufferlen))
+ return -ENOBUFS;
+
+ /* Read data and skip packet header */
+ error = vmbus_rxbr_read(rxbr, data, dlen, 0);
+ if (error)
+ return error;
+
+ /* Return the number of bytes read */
+ return dlen + sizeof(uint64_t);
+}
diff --git a/tools/hv/vmbus_bufring.h b/tools/hv/vmbus_bufring.h
new file mode 100644
index 00000000000000..6e7caacfff5744
--- /dev/null
+++ b/tools/hv/vmbus_bufring.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#ifndef _VMBUS_BUF_H_
+#define _VMBUS_BUF_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#define __packed __attribute__((__packed__))
+#define unlikely(x) __builtin_expect(!!(x), 0)
+
+#define ICMSGHDRFLAG_TRANSACTION 1
+#define ICMSGHDRFLAG_REQUEST 2
+#define ICMSGHDRFLAG_RESPONSE 4
+
+#define IC_VERSION_NEGOTIATION_MAX_VER_COUNT 100
+#define ICMSG_HDR (sizeof(struct vmbuspipe_hdr) + sizeof(struct icmsg_hdr))
+#define ICMSG_NEGOTIATE_PKT_SIZE(icframe_vercnt, icmsg_vercnt) \
+ (ICMSG_HDR + sizeof(struct icmsg_negotiate) + \
+ (((icframe_vercnt) + (icmsg_vercnt)) * sizeof(struct ic_version)))
+
+/*
+ * Channel packets
+ */
+
+/* Channel packet flags */
+#define VMBUS_CHANPKT_TYPE_INBAND 0x0006
+#define VMBUS_CHANPKT_TYPE_RXBUF 0x0007
+#define VMBUS_CHANPKT_TYPE_GPA 0x0009
+#define VMBUS_CHANPKT_TYPE_COMP 0x000b
+
+#define VMBUS_CHANPKT_FLAG_NONE 0
+#define VMBUS_CHANPKT_FLAG_RC 0x0001 /* report completion */
+
+#define VMBUS_CHANPKT_SIZE_SHIFT 3
+#define VMBUS_CHANPKT_SIZE_ALIGN BIT(VMBUS_CHANPKT_SIZE_SHIFT)
+#define VMBUS_CHANPKT_HLEN_MIN \
+ (sizeof(struct vmbus_chanpkt_hdr) >> VMBUS_CHANPKT_SIZE_SHIFT)
+
+/*
+ * Buffer ring
+ */
+struct vmbus_bufring {
+ volatile uint32_t windex;
+ volatile uint32_t rindex;
+
+ /*
+ * Interrupt mask {0,1}
+ *
+ * For TX bufring, host set this to 1, when it is processing
+ * the TX bufring, so that we can safely skip the TX event
+ * notification to host.
+ *
+ * For RX bufring, once this is set to 1 by us, host will not
+ * further dispatch interrupts to us, even if there are data
+ * pending on the RX bufring. This effectively disables the
+ * interrupt of the channel to which this RX bufring is attached.
+ */
+ volatile uint32_t imask;
+
+ /*
+ * Win8 uses some of the reserved bits to implement
+ * interrupt driven flow management. On the send side
+ * we can request that the receiver interrupt the sender
+ * when the ring transitions from being full to being able
+ * to handle a message of size "pending_send_sz".
+ *
+ * Add necessary state for this enhancement.
+ */
+ volatile uint32_t pending_send;
+ uint32_t reserved1[12];
+
+ union {
+ struct {
+ uint32_t feat_pending_send_sz:1;
+ };
+ uint32_t value;
+ } feature_bits;
+
+ /* Pad it to rte_mem_page_size() so that data starts on page boundary */
+ uint8_t reserved2[4028];
+
+ /*
+ * Ring data starts here + RingDataStartOffset
+ * !!! DO NOT place any fields below this !!!
+ */
+ uint8_t data[];
+} __packed;
+
+struct vmbus_br {
+ struct vmbus_bufring *vbr;
+ uint32_t dsize;
+ uint32_t windex; /* next available location */
+};
+
+struct vmbus_chanpkt_hdr {
+ uint16_t type; /* VMBUS_CHANPKT_TYPE_ */
+ uint16_t hlen; /* header len, in 8 bytes */
+ uint16_t tlen; /* total len, in 8 bytes */
+ uint16_t flags; /* VMBUS_CHANPKT_FLAG_ */
+ uint64_t xactid;
+} __packed;
+
+struct vmbus_chanpkt {
+ struct vmbus_chanpkt_hdr hdr;
+} __packed;
+
+struct vmbuspipe_hdr {
+ unsigned int flags;
+ unsigned int msgsize;
+} __packed;
+
+struct ic_version {
+ unsigned short major;
+ unsigned short minor;
+} __packed;
+
+struct icmsg_negotiate {
+ unsigned short icframe_vercnt;
+ unsigned short icmsg_vercnt;
+ unsigned int reserved;
+ struct ic_version icversion_data[]; /* any size array */
+} __packed;
+
+struct icmsg_hdr {
+ struct ic_version icverframe;
+ unsigned short icmsgtype;
+ struct ic_version icvermsg;
+ unsigned short icmsgsize;
+ unsigned int status;
+ unsigned char ictransaction_id;
+ unsigned char icflags;
+ unsigned char reserved[2];
+} __packed;
+
+int rte_vmbus_chan_recv_raw(struct vmbus_br *rxbr, void *data, uint32_t *len);
+int rte_vmbus_chan_send(struct vmbus_br *txbr, uint16_t type, void *data,
+ uint32_t dlen, uint32_t flags);
+void vmbus_br_setup(struct vmbus_br *br, void *buf, unsigned int blen);
+void *vmbus_uio_map(int *fd, int size);
+
+/* Amount of space available for write */
+static inline uint32_t vmbus_br_availwrite(const struct vmbus_br *br, uint32_t windex)
+{
+ uint32_t rindex = br->vbr->rindex;
+
+ if (windex >= rindex)
+ return br->dsize - (windex - rindex);
+ else
+ return rindex - windex;
+}
+
+static inline uint32_t vmbus_br_availread(const struct vmbus_br *br)
+{
+ return br->dsize - vmbus_br_availwrite(br, br->vbr->windex);
+}
+
+#endif /* !_VMBUS_BUF_H_ */
diff --git a/tools/testing/nvdimm/test/ndtest.c b/tools/testing/nvdimm/test/ndtest.c
index b8419f460368a4..2c6285aae852d1 100644
--- a/tools/testing/nvdimm/test/ndtest.c
+++ b/tools/testing/nvdimm/test/ndtest.c
@@ -830,12 +830,11 @@ static int ndtest_bus_register(struct ndtest_priv *p)
return 0;
}
-static int ndtest_remove(struct platform_device *pdev)
+static void ndtest_remove(struct platform_device *pdev)
{
struct ndtest_priv *p = to_ndtest_priv(&pdev->dev);
nvdimm_bus_unregister(p->bus);
- return 0;
}
static int ndtest_probe(struct platform_device *pdev)
@@ -882,7 +881,7 @@ static const struct platform_device_id ndtest_id[] = {
static struct platform_driver ndtest_driver = {
.probe = ndtest_probe,
- .remove = ndtest_remove,
+ .remove_new = ndtest_remove,
.driver = {
.name = KBUILD_MODNAME,
},