First set of IIO new device and feature support for the 5.16 cycle
Counter subsystem changes now sent separately. This has been a busy cycle, so lots here and a few more stragglers to come next week. Big new feature in this cycle is probably output buffer support. This has been in the works for a very long time so it's great to see Mihail pick up the challenge and build upon his predecessors work to finally bring this feature to mainline. New device support ------------------ * adi,adxl313 - New driver and dt bindings for this low power accelerometer. * adi,adxl355 - New driver and dt bindings for this accelerometer. - Later series adds buffer support. * asahi-kasei,ak8975 - Minor additions to driver to support ak09916 * aspeed,aspeed-adc - Substantial rework plus feature additions to add support for the ast2600 including a new dt bindings doc. * atmel,at91_sama5d2 - Rework and support introduced for the sama7g5 parts. * maxim,max31865 - New driver and bindings for this RTD temperature sensor chip. * nxp,imx8qxp - New driver and bindings for the ADC found on the i.MX 8QuadXPlus Soc. * senseair,sunrise - New driver and bindings for this family of carbon dioxide gas sensors. * sensiron,scd4x - New driver and bindings for this carbon dioxide gas sensor. New features ------------ * Output buffer support. Works in a similar fashion to input buffers, but in this case userspace pushes data into the kfifo which is then drained to the device when a trigger occurs. Support added to the ad5766 DAC driver. * Core, devm_iio_map_array_register() to avoid need for devm_add_action_or_reset() based cleanup in fully managed allocation drivers. * Core iio_push_to_buffers_with_ts_unaligned() function to safely handle a few drivers where it really hard to ensure the correct data alignment in an iio_push_to_buffers_with_timestamp() call. Note this uses a bounce buffer so should be avoided whenever possible. Used in the ti,adc108s102, invense,mpu3050 and adi,adis16400. This closes the last known set of drivers with alignment issues at this interface. * maxim,max1027 - Substantial rework to this driver main target of which was supporting use of other triggers than it's own EOC interrupt. - Transfer optimization. * nxp,fxls8962af - Threshold even support including using it as a wakeup source. Cleanups, minor fixes etc ------------------------- Chances of a common type to multiple drivers: * devm_ conversion and drop of .remove() callbacks in: - adi,ad5064 - adi,ad7291 - adi,ad7303 - adi,ad7746 - adi,ad9832 - adi,adis16080 - dialog,da9150-gpadc - intel,mrfld_adc - marvell,berlin2 - maxim,max1363 - maxim,max44000 - nuvoton,nau7802 - st_sensors (includes a lot of rework!) - ti,ads8344 - ti,lp8788 * devm_platform_ioremap_resource() used to reduce boilerplate - cirrus,ep93xx - rockchip,saradc - stm,stm32-dac * Use dev_err_probe() in more places to both not print on deferred probe and ensure a reason for the deferral is available for debug purposes. - adi,ad8801 - capella,cm36651 - linear,ltc1660 - maxim,ds4424 - maxim,max5821 - microchip,mcp4922 - nxp,lpc18xx - onnn,noa1305 - st,lsm9ds0 - st,st_sensors - st,stm32-dac - ti,afe4403 - ti,afe4404 - ti,dac7311 * Drop error returns in SPI and I2C remove() functions as they are ignored and long term plan is to change these all over to returning void. In some cases these patches just make it 'obvious' they always return 0 where it was the case before but not easy to tell. - adi,ad5380 - adi,ad5446 - adi,ad5686 - adi,ad5592r - bosch,bma400 - bosch,bmc150 - fsl,mma7455 - honeywell,hmc5843 - kionix,kxsd9 - maxim,max5487 - meas,ms5611 - ti,afe4403 Driver specific changes * adi,ad5770r - Bring driver inline with documented bindings. * adi,ad7746 - Trivial style fix * adi,ad7949 - Express some magic values as the underlying parts via new #defines. - Make it work with SPI controllers that don't support 14 or 16 bit messages - Support selection of voltage reference from dt including expanding the dt-bindings to cover this new functionality. * adi,ad799x - Implement selection of external reference voltage on AD7991, AD7995 and AD7999. - Add missing dt-bindings doc for devices supported by this driver. * adi,adislib - Move interrupt startup to better location in startup flow. - Handle devices that cannot mask/unmask the drdy pin and must instead mask at the interrupt controller. Applies to the adis16460 and adis16475 from which we then drop equivalent code. * adi,ltc2983 - Add support for optional reset pin. - Fail to probe if no channels specified in dt binding. * asahi-kasei,ak8975 - dt-binding additions of missing vid-supply regulator. * aspeed,aspeed-adc - Typo fix. * fsl,mma7660 - Mark acpi_device_id table __maybe_unused to avoid build warning. * fsl,imx25-gcq - Avoid initializing regulators that aren't used. * invensense,mpu3050 - Drop a dead protection against a clash with the old input driver. * invensense,mpu6050 - Rework code to not use strcpy() and hence avoid possibility of wrong sized buffers. Note this wasn't a bug, but the new code is a lot more readable. - Mark acpi_device_id table __maybe_unused to avoid build warning. * kionix,kxcjk1013 - dt-binding addition to note it supports interrupts. * marvell,berlin2-adc - Enable COMPILE_TEST building. * maxim,max1027 - Avoid returning success in an error path. * nxp,imx8qxp - Fix warning when runtime pm not enabled via __maybe_unused. * ricoh,rn5t618 - Use the new devm_iio_map_array_register() instead of open coding the same. * samsung,exynos_adc - Improve kconfig help text. * st,lsm6dsx - Move max_fifo_size into the fifo_ops structure where the other configuration parameters are found. * st,st_sensors: - Reorder to ensure we turn the power off after removing userspace interfaces. * senseair,sunrise - Add missing I2C dependency. * ti,twl6030 - Small code tidy up. -----BEGIN PGP SIGNATURE----- iQJFBAABCAAvFiEEbilms4eEBlKRJoGxVIU0mcT0FogFAmFudVERHGppYzIzQGtl cm5lbC5vcmcACgkQVIU0mcT0Foiepg//R5DRm1gMFGP/dl2P8u6KD2oXNmbqc3J0 +EPRTkyTyawQzc21wdxUh3oObgxmDS6P7Cr17z9viGse2PsUjza4Sptic3Zqi3Kv FzeXZaP0GY9nC+hAyz+dSkMKNw19mkCYYEIWhDwYFpXMiOV8QlT+6pFgOiffofeR GYMUoV2XeuxR6OH0zMOuYKTzjOCYC+VJTP4BfeUetpRZQyllgFOA+5KX4ZWTiJMV eGCaq3Hhn6IQa7ATgYUBYJyMkAXQDrKDaMxiaCIuh/k3bgjAkfhkV48mu2cHTsX9 v0q21JkgmBenwNP/OQ6mxmWR9SFY4eB22Ptkd1foTPjAsqoST7qmqEBhhLrRb/fB HkL0I7M4tWDqSTJKgdn+UHYrKFW9oBHIt5SK1tCS+zTYIn1wEmkY/w/9MuZllBFO VKu5q2ypf/yGgCqPIZilK69mrZd7/72rE6tr3oefUF2iUVLdh9fI1TjGGyoT6GZ5 ZlEIfGHRpcRGHFiA4e+6tTx9VLr8m1B5YVvBWWPlruYe8ep7MmD3KjUcwINiRyNs 5rdBl2RI8VAUBbQ+60VWn03e/0kuRESpFC3Gf/ng+6hRSUbRFe4lZw60GbyFDXwV m1odTlkXA7l6oRG9f5COhE/oNO7dsfMO74W6+yzhWICnxjgJFosaaZTLw8F9QJdS m3z9tSU84js= =htmG -----END PGP SIGNATURE----- Merge tag 'iio-for-5.16a-split-take4' of https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio into char-misc-next Jonathan writes: First set of IIO new device and feature support for the 5.16 cycle Counter subsystem changes now sent separately. This has been a busy cycle, so lots here and a few more stragglers to come next week. Big new feature in this cycle is probably output buffer support. This has been in the works for a very long time so it's great to see Mihail pick up the challenge and build upon his predecessors work to finally bring this feature to mainline. New device support ------------------ * adi,adxl313 - New driver and dt bindings for this low power accelerometer. * adi,adxl355 - New driver and dt bindings for this accelerometer. - Later series adds buffer support. * asahi-kasei,ak8975 - Minor additions to driver to support ak09916 * aspeed,aspeed-adc - Substantial rework plus feature additions to add support for the ast2600 including a new dt bindings doc. * atmel,at91_sama5d2 - Rework and support introduced for the sama7g5 parts. * maxim,max31865 - New driver and bindings for this RTD temperature sensor chip. * nxp,imx8qxp - New driver and bindings for the ADC found on the i.MX 8QuadXPlus Soc. * senseair,sunrise - New driver and bindings for this family of carbon dioxide gas sensors. * sensiron,scd4x - New driver and bindings for this carbon dioxide gas sensor. New features ------------ * Output buffer support. Works in a similar fashion to input buffers, but in this case userspace pushes data into the kfifo which is then drained to the device when a trigger occurs. Support added to the ad5766 DAC driver. * Core, devm_iio_map_array_register() to avoid need for devm_add_action_or_reset() based cleanup in fully managed allocation drivers. * Core iio_push_to_buffers_with_ts_unaligned() function to safely handle a few drivers where it really hard to ensure the correct data alignment in an iio_push_to_buffers_with_timestamp() call. Note this uses a bounce buffer so should be avoided whenever possible. Used in the ti,adc108s102, invense,mpu3050 and adi,adis16400. This closes the last known set of drivers with alignment issues at this interface. * maxim,max1027 - Substantial rework to this driver main target of which was supporting use of other triggers than it's own EOC interrupt. - Transfer optimization. * nxp,fxls8962af - Threshold even support including using it as a wakeup source. Cleanups, minor fixes etc ------------------------- Chances of a common type to multiple drivers: * devm_ conversion and drop of .remove() callbacks in: - adi,ad5064 - adi,ad7291 - adi,ad7303 - adi,ad7746 - adi,ad9832 - adi,adis16080 - dialog,da9150-gpadc - intel,mrfld_adc - marvell,berlin2 - maxim,max1363 - maxim,max44000 - nuvoton,nau7802 - st_sensors (includes a lot of rework!) - ti,ads8344 - ti,lp8788 * devm_platform_ioremap_resource() used to reduce boilerplate - cirrus,ep93xx - rockchip,saradc - stm,stm32-dac * Use dev_err_probe() in more places to both not print on deferred probe and ensure a reason for the deferral is available for debug purposes. - adi,ad8801 - capella,cm36651 - linear,ltc1660 - maxim,ds4424 - maxim,max5821 - microchip,mcp4922 - nxp,lpc18xx - onnn,noa1305 - st,lsm9ds0 - st,st_sensors - st,stm32-dac - ti,afe4403 - ti,afe4404 - ti,dac7311 * Drop error returns in SPI and I2C remove() functions as they are ignored and long term plan is to change these all over to returning void. In some cases these patches just make it 'obvious' they always return 0 where it was the case before but not easy to tell. - adi,ad5380 - adi,ad5446 - adi,ad5686 - adi,ad5592r - bosch,bma400 - bosch,bmc150 - fsl,mma7455 - honeywell,hmc5843 - kionix,kxsd9 - maxim,max5487 - meas,ms5611 - ti,afe4403 Driver specific changes * adi,ad5770r - Bring driver inline with documented bindings. * adi,ad7746 - Trivial style fix * adi,ad7949 - Express some magic values as the underlying parts via new #defines. - Make it work with SPI controllers that don't support 14 or 16 bit messages - Support selection of voltage reference from dt including expanding the dt-bindings to cover this new functionality. * adi,ad799x - Implement selection of external reference voltage on AD7991, AD7995 and AD7999. - Add missing dt-bindings doc for devices supported by this driver. * adi,adislib - Move interrupt startup to better location in startup flow. - Handle devices that cannot mask/unmask the drdy pin and must instead mask at the interrupt controller. Applies to the adis16460 and adis16475 from which we then drop equivalent code. * adi,ltc2983 - Add support for optional reset pin. - Fail to probe if no channels specified in dt binding. * asahi-kasei,ak8975 - dt-binding additions of missing vid-supply regulator. * aspeed,aspeed-adc - Typo fix. * fsl,mma7660 - Mark acpi_device_id table __maybe_unused to avoid build warning. * fsl,imx25-gcq - Avoid initializing regulators that aren't used. * invensense,mpu3050 - Drop a dead protection against a clash with the old input driver. * invensense,mpu6050 - Rework code to not use strcpy() and hence avoid possibility of wrong sized buffers. Note this wasn't a bug, but the new code is a lot more readable. - Mark acpi_device_id table __maybe_unused to avoid build warning. * kionix,kxcjk1013 - dt-binding addition to note it supports interrupts. * marvell,berlin2-adc - Enable COMPILE_TEST building. * maxim,max1027 - Avoid returning success in an error path. * nxp,imx8qxp - Fix warning when runtime pm not enabled via __maybe_unused. * ricoh,rn5t618 - Use the new devm_iio_map_array_register() instead of open coding the same. * samsung,exynos_adc - Improve kconfig help text. * st,lsm6dsx - Move max_fifo_size into the fifo_ops structure where the other configuration parameters are found. * st,st_sensors: - Reorder to ensure we turn the power off after removing userspace interfaces. * senseair,sunrise - Add missing I2C dependency. * ti,twl6030 - Small code tidy up. * tag 'iio-for-5.16a-split-take4' of https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio: (148 commits) iio: imx8qxp-adc: mark PM functions as __maybe_unused iio: pressure: ms5611: Make ms5611_remove() return void iio: potentiometer: max5487: Don't return an error in .remove() iio: magn: hmc5843: Make hmc5843_common_remove() return void iio: health: afe4403: Don't return an error in .remove() iio: dac: ad5686: Make ad5686_remove() return void iio: dac: ad5592r: Make ad5592r_remove() return void iio: dac: ad5446: Make ad5446_remove() return void iio: dac: ad5380: Make ad5380_remove() return void iio: accel: mma7455: Make mma7455_core_remove() return void iio: accel: kxsd9: Make kxsd9_common_remove() return void iio: accel: bmi088: Make bmi088_accel_core_remove() return void iio: accel: bmc150: Make bmc150_accel_core_remove() return void iio: accel: bma400: Make bma400_remove() return void drivers:iio:dac:ad5766.c: Add trigger buffer iio: triggered-buffer: extend support to configure output buffers iio: kfifo-buffer: Add output buffer support iio: Add output buffer support iio: documentation: Document scd4x calibration use drivers: iio: chemical: Add support for Sensirion SCD4x CO2 sensor ...
This commit is contained in:
Коммит
6bce28cb49
|
@ -429,6 +429,7 @@ What: /sys/bus/iio/devices/iio:deviceX/in_angl_scale
|
|||
What: /sys/bus/iio/devices/iio:deviceX/in_intensity_x_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_intensity_y_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_intensity_z_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_concentration_co2_scale
|
||||
KernelVersion: 2.6.35
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
|
@ -1957,3 +1958,44 @@ Description:
|
|||
Specify the percent for light sensor relative to the channel
|
||||
absolute value that a data field should change before an event
|
||||
is generated. Units are a percentage of the prior reading.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/calibration_auto_enable
|
||||
Date: June 2020
|
||||
KernelVersion: 5.8
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Some sensors have the ability to apply auto calibration at
|
||||
runtime. For example, it may be necessary to compensate for
|
||||
contaminant build-up in a measurement chamber or optical
|
||||
element deterioration that would otherwise lead to sensor drift.
|
||||
|
||||
Writing 1 or 0 to this attribute will respectively activate or
|
||||
deactivate this auto calibration function.
|
||||
|
||||
Upon reading, the current status is returned.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/calibration_forced_value
|
||||
Date: June 2020
|
||||
KernelVersion: 5.8
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Some sensors have the ability to apply a manual calibration using
|
||||
a known measurement value, perhaps obtained from an external
|
||||
reference device.
|
||||
|
||||
Writing a value to this function will force such a calibration
|
||||
change. For the scd30 the value should be from the range
|
||||
[400 1 2000].
|
||||
|
||||
Note for the scd30 that a valid value may only be obtained once
|
||||
it is has been written. Until then any read back of this value
|
||||
should be ignored. As for the scd4x an error will be returned
|
||||
immediately if the manual calibration has failed.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/calibration_forced_value_available
|
||||
KernelVersion: 5.15
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Available range for the forced calibration value, expressed as:
|
||||
|
||||
- a range specified as "[min step max]"
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
What: /sys/bus/iio/devices/iio:deviceX/in_concentration_co2_calibration_factory
|
||||
Date: August 2021
|
||||
KernelVersion: 5.16
|
||||
Contact: Jacopo Mondi <jacopo@jmondi.org>
|
||||
Description:
|
||||
Writing '1' triggers a 'Factory' calibration cycle.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_concentration_co2_calibration_background
|
||||
Date: August 2021
|
||||
KernelVersion: 5.16
|
||||
Contact: Jacopo Mondi <jacopo@jmondi.org>
|
||||
Description:
|
||||
Writing '1' triggers a 'Background' calibration cycle.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/error_status_available
|
||||
Date: August 2021
|
||||
KernelVersion: 5.16
|
||||
Contact: Jacopo Mondi <jacopo@jmondi.org>
|
||||
Description:
|
||||
Reading returns the list of possible chip error status.
|
||||
Available options are:
|
||||
- 'error_fatal': Analog front-end initialization error
|
||||
- 'error_i2c': Read/write to non-existing register
|
||||
- 'error_algorithm': Corrupted parameters
|
||||
- 'error_calibration': Calibration has failed
|
||||
- 'error_self_diagnostic': Internal interface failure
|
||||
- 'error_out_of_range': Measured concentration out of scale
|
||||
- 'error_memory': Error during memory operations
|
||||
- 'error_no_measurement': Cleared at first measurement
|
||||
- 'error_low_voltage': Sensor regulated voltage too low
|
||||
- 'error_measurement_timeout': Unable to complete measurement
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/error_status
|
||||
Date: August 2021
|
||||
KernelVersion: 5.16
|
||||
Contact: Jacopo Mondi <jacopo@jmondi.org>
|
||||
Description:
|
||||
Reading returns the current chip error status.
|
|
@ -1,34 +0,0 @@
|
|||
What: /sys/bus/iio/devices/iio:deviceX/calibration_auto_enable
|
||||
Date: June 2020
|
||||
KernelVersion: 5.8
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Contaminants build-up in the measurement chamber or optical
|
||||
elements deterioration leads to sensor drift.
|
||||
|
||||
One can compensate for sensor drift by using automatic self
|
||||
calibration procedure (asc).
|
||||
|
||||
Writing 1 or 0 to this attribute will respectively activate or
|
||||
deactivate asc.
|
||||
|
||||
Upon reading current asc status is returned.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/calibration_forced_value
|
||||
Date: June 2020
|
||||
KernelVersion: 5.8
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Contaminants build-up in the measurement chamber or optical
|
||||
elements deterioration leads to sensor drift.
|
||||
|
||||
One can compensate for sensor drift by using forced
|
||||
recalibration (frc). This is useful in case there's known
|
||||
co2 reference available nearby the sensor.
|
||||
|
||||
Picking value from the range [400 1 2000] and writing it to the
|
||||
sensor will set frc.
|
||||
|
||||
Upon reading current frc value is returned. Note that after
|
||||
power cycling default value (i.e 400) is returned even though
|
||||
internally sensor had recalibrated itself.
|
|
@ -0,0 +1,20 @@
|
|||
What: /sys/bus/iio/devices/iio:deviceX/fault_ovuv
|
||||
KernelVersion: 5.11
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Overvoltage or Undervoltage Input fault. The internal circuitry
|
||||
is protected from excessive voltages applied to the thermocouple
|
||||
cables at FORCE+, FORCE2, RTDIN+ & RTDIN-. This circuitry turn
|
||||
off when the input voltage is negative or greater than VDD.
|
||||
|
||||
Reading returns '1' if input voltage is negative or greater
|
||||
than VDD, otherwise '0'.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_filter_notch_center_frequency
|
||||
KernelVersion: 5.11
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Notch frequency in Hz for a noise rejection filter. Used i.e for
|
||||
line noise rejection.
|
||||
|
||||
Valid notch filter values are 50 Hz and 60 Hz.
|
|
@ -0,0 +1,86 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/accel/adi,adxl313.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices ADXL313 3-Axis Digital Accelerometer
|
||||
|
||||
maintainers:
|
||||
- Lucas Stankus <lucas.p.stankus@gmail.com>
|
||||
|
||||
description: |
|
||||
Analog Devices ADXL313 3-Axis Digital Accelerometer that supports
|
||||
both I2C & SPI interfaces.
|
||||
https://www.analog.com/en/products/adxl313.html
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,adxl313
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
spi-3wire: true
|
||||
|
||||
spi-max-frequency: true
|
||||
|
||||
vs-supply:
|
||||
description: Regulator that supplies power to the accelerometer
|
||||
|
||||
vdd-supply:
|
||||
description: Regulator that supplies the digital interface supply voltage
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
interrupt-names:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
items:
|
||||
enum:
|
||||
- INT1
|
||||
- INT2
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
i2c0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
/* Example for a I2C device node */
|
||||
accelerometer@53 {
|
||||
compatible = "adi,adxl313";
|
||||
reg = <0x53>;
|
||||
interrupt-parent = <&gpio0>;
|
||||
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "INT1";
|
||||
};
|
||||
};
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
/* Example for a SPI device node */
|
||||
accelerometer@0 {
|
||||
compatible = "adi,adxl313";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <5000000>;
|
||||
interrupt-parent = <&gpio0>;
|
||||
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "INT1";
|
||||
};
|
||||
};
|
|
@ -0,0 +1,88 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/accel/adi,adxl355.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices ADXL355 3-Axis, Low noise MEMS Accelerometer
|
||||
|
||||
maintainers:
|
||||
- Puranjay Mohan <puranjay12@gmail.com>
|
||||
|
||||
description: |
|
||||
Analog Devices ADXL355 3-Axis, Low noise MEMS Accelerometer that supports
|
||||
both I2C & SPI interfaces
|
||||
https://www.analog.com/en/products/adxl355.html
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,adxl355
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
maxItems: 3
|
||||
description: |
|
||||
Type for DRDY should be IRQ_TYPE_EDGE_RISING.
|
||||
Three configurable interrupt lines exist.
|
||||
|
||||
interrupt-names:
|
||||
description: Specify which interrupt line is in use.
|
||||
items:
|
||||
enum:
|
||||
- INT1
|
||||
- INT2
|
||||
- DRDY
|
||||
minItems: 1
|
||||
maxItems: 3
|
||||
|
||||
vdd-supply:
|
||||
description: Regulator that provides power to the sensor
|
||||
|
||||
vddio-supply:
|
||||
description: Regulator that provides power to the bus
|
||||
|
||||
spi-max-frequency: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
/* Example for a I2C device node */
|
||||
accelerometer@1d {
|
||||
compatible = "adi,adxl355";
|
||||
reg = <0x1d>;
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <25 IRQ_TYPE_EDGE_RISING>;
|
||||
interrupt-names = "DRDY";
|
||||
};
|
||||
};
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
accelerometer@0 {
|
||||
compatible = "adi,adxl355";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <1000000>;
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <25 IRQ_TYPE_EDGE_RISING>;
|
||||
interrupt-names = "DRDY";
|
||||
};
|
||||
};
|
|
@ -21,6 +21,9 @@ properties:
|
|||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
vdd-supply: true
|
||||
vddio-supply: true
|
||||
|
||||
|
|
|
@ -26,19 +26,43 @@ properties:
|
|||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vrefin-supply:
|
||||
description:
|
||||
Buffered ADC reference voltage supply.
|
||||
|
||||
vref-supply:
|
||||
description:
|
||||
ADC reference voltage supply
|
||||
Unbuffered ADC reference voltage supply.
|
||||
|
||||
adi,internal-ref-microvolt:
|
||||
description: |
|
||||
Internal reference voltage selection in microvolts.
|
||||
|
||||
If no internal reference is specified, the channel will default to the
|
||||
external reference defined by vrefin-supply (or vref-supply).
|
||||
vrefin-supply will take precedence over vref-supply if both are defined.
|
||||
|
||||
If no supplies are defined, the reference selection will default to
|
||||
4096mV internal reference.
|
||||
|
||||
enum: [2500000, 4096000]
|
||||
default: 4096000
|
||||
|
||||
|
||||
spi-max-frequency: true
|
||||
|
||||
"#io-channel-cells":
|
||||
'#io-channel-cells':
|
||||
const: 1
|
||||
|
||||
'#address-cells':
|
||||
const: 1
|
||||
|
||||
'#size-cells':
|
||||
const: 0
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- vref-supply
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
|
@ -49,9 +73,30 @@ examples:
|
|||
#size-cells = <0>;
|
||||
|
||||
adc@0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
compatible = "adi,ad7949";
|
||||
reg = <0>;
|
||||
vref-supply = <&vdd_supply>;
|
||||
};
|
||||
|
||||
adc@1 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
compatible = "adi,ad7949";
|
||||
reg = <1>;
|
||||
vrefin-supply = <&vdd_supply>;
|
||||
};
|
||||
|
||||
adc@2 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
compatible = "adi,ad7949";
|
||||
reg = <2>;
|
||||
adi,internal-ref-microvolt = <4096000>;
|
||||
};
|
||||
};
|
||||
...
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/adc/adi,ad799x.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices AD799x analog to digital converters
|
||||
|
||||
maintainers:
|
||||
- Michael Hennerich <Michael.Hennerich@analog.com>
|
||||
|
||||
description: |
|
||||
Support for Analog Devices AD7991, AD7992, AD7993, AD7994, AD7995, AD7997, AD7998,
|
||||
AD7999 and similar analog to digital converters.
|
||||
Specifications on the converters can be found at:
|
||||
AD7991, AD7995, AD7999:
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/AD7991_7995_7999.pdf
|
||||
AD7992:
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/AD7992.pdf
|
||||
AD7993, AD7994:
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/AD7993_7994.pdf
|
||||
AD7997, AD7998:
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/AD7997_7998.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,ad7991
|
||||
- adi,ad7992
|
||||
- adi,ad7993
|
||||
- adi,ad7994
|
||||
- adi,ad7995
|
||||
- adi,ad7997
|
||||
- adi,ad7998
|
||||
- adi,ad7999
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
vcc-supply:
|
||||
description:
|
||||
ADC power supply
|
||||
|
||||
vref-supply:
|
||||
description:
|
||||
ADC reference voltage supply, optional for AD7991, AD7995 and AD7999
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
adc1: adc@28 {
|
||||
reg = <0x28>;
|
||||
compatible = "adi,ad7991";
|
||||
interrupts = <13 2>;
|
||||
interrupt-parent = <&gpio6>;
|
||||
|
||||
vcc-supply = <&vcc_3v3>;
|
||||
vref-supply = <&adc_vref>;
|
||||
};
|
||||
};
|
||||
...
|
|
@ -0,0 +1,100 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/adc/aspeed,ast2600-adc.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: ADC that forms part of an ASPEED server management processor.
|
||||
|
||||
maintainers:
|
||||
- Billy Tsai <billy_tsai@aspeedtech.com>
|
||||
|
||||
description: |
|
||||
• 10-bits resolution for 16 voltage channels.
|
||||
• The device split into two individual engine and each contains 8 voltage
|
||||
channels.
|
||||
• Channel scanning can be non-continuous.
|
||||
• Programmable ADC clock frequency.
|
||||
• Programmable upper and lower threshold for each channels.
|
||||
• Interrupt when larger or less than threshold for each channels.
|
||||
• Support hysteresis for each channels.
|
||||
• Built-in a compensating method.
|
||||
• Built-in a register to trim internal reference voltage.
|
||||
• Internal or External reference voltage.
|
||||
• Support 2 Internal reference voltage 1.2v or 2.5v.
|
||||
• Integrate dividing circuit for battery sensing.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- aspeed,ast2600-adc0
|
||||
- aspeed,ast2600-adc1
|
||||
description:
|
||||
Their trimming data, which is used to calibrate internal reference volage,
|
||||
locates in different address of OTP.
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
description:
|
||||
Input clock used to derive the sample clock. Expected to be the
|
||||
SoC's APB clock.
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
"#io-channel-cells":
|
||||
const: 1
|
||||
|
||||
vref-supply:
|
||||
description:
|
||||
The external regulator supply ADC reference voltage.
|
||||
|
||||
aspeed,int-vref-microvolt:
|
||||
enum: [1200000, 2500000]
|
||||
description:
|
||||
ADC internal reference voltage in microvolts.
|
||||
|
||||
aspeed,battery-sensing:
|
||||
type: boolean
|
||||
description:
|
||||
Inform the driver that last channel will be used to sensor battery.
|
||||
|
||||
aspeed,trim-data-valid:
|
||||
type: boolean
|
||||
description: |
|
||||
The ADC reference voltage can be calibrated to obtain the trimming
|
||||
data which will be stored in otp. This property informs the driver that
|
||||
the data store in the otp is valid.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- clocks
|
||||
- resets
|
||||
- "#io-channel-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/ast2600-clock.h>
|
||||
adc0: adc@1e6e9000 {
|
||||
compatible = "aspeed,ast2600-adc0";
|
||||
reg = <0x1e6e9000 0x100>;
|
||||
clocks = <&syscon ASPEED_CLK_APB2>;
|
||||
resets = <&syscon ASPEED_RESET_ADC>;
|
||||
#io-channel-cells = <1>;
|
||||
aspeed,int-vref-microvolt = <2500000>;
|
||||
};
|
||||
adc1: adc@1e6e9100 {
|
||||
compatible = "aspeed,ast2600-adc1";
|
||||
reg = <0x1e6e9100 0x100>;
|
||||
clocks = <&syscon ASPEED_CLK_APB2>;
|
||||
resets = <&syscon ASPEED_RESET_ADC>;
|
||||
#io-channel-cells = <1>;
|
||||
aspeed,int-vref-microvolt = <2500000>;
|
||||
};
|
||||
...
|
|
@ -15,6 +15,7 @@ properties:
|
|||
enum:
|
||||
- atmel,sama5d2-adc
|
||||
- microchip,sam9x60-adc
|
||||
- microchip,sama7g5-adc
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/adc/nxp,imx8qxp-adc.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: NXP IMX8QXP ADC bindings
|
||||
|
||||
maintainers:
|
||||
- Cai Huoqing <caihuoqing@baidu.com>
|
||||
|
||||
description:
|
||||
Supports the ADC found on the IMX8QXP SoC.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: nxp,imx8qxp-adc
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 2
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: per
|
||||
- const: ipg
|
||||
|
||||
assigned-clocks:
|
||||
maxItems: 1
|
||||
|
||||
assigned-clock-rates:
|
||||
maxItems: 1
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
"#io-channel-cells":
|
||||
const: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
- assigned-clocks
|
||||
- assigned-clock-rates
|
||||
- power-domains
|
||||
- "#io-channel-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
#include <dt-bindings/firmware/imx/rsrc.h>
|
||||
soc {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
adc@5a880000 {
|
||||
compatible = "nxp,imx8qxp-adc";
|
||||
reg = <0x0 0x5a880000 0x0 0x10000>;
|
||||
interrupts = <GIC_SPI 240 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&clk IMX_SC_R_ADC_0>,
|
||||
<&clk IMX_SC_R_ADC_0>;
|
||||
clock-names = "per", "ipg";
|
||||
assigned-clocks = <&clk IMX_SC_R_ADC_0>;
|
||||
assigned-clock-rates = <24000000>;
|
||||
power-domains = <&pd IMX_SC_R_ADC_0>;
|
||||
#io-channel-cells = <1>;
|
||||
};
|
||||
};
|
||||
...
|
|
@ -0,0 +1,55 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/chemical/senseair,sunrise.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Senseair Sunrise 006-0-0007 CO2 Sensor
|
||||
|
||||
maintainers:
|
||||
- Jacopo Mondi <jacopo@jmondi.org>
|
||||
|
||||
description: |
|
||||
Senseair Sunrise 006-0-0007 is a NDIR CO2 sensor. It supports I2C or UART buses
|
||||
for communications and control.
|
||||
|
||||
Datasheets:
|
||||
https://rmtplusstoragesenseair.blob.core.windows.net/docs/Dev/publicerat/PSP11704.pdf
|
||||
https://rmtplusstoragesenseair.blob.core.windows.net/docs/Dev/publicerat/PSH11649.pdf
|
||||
https://rmtplusstoragesenseair.blob.core.windows.net/docs/Dev/publicerat/TDE5531.pdf
|
||||
https://rmtplusstoragesenseair.blob.core.windows.net/docs/Market/publicerat/TDE7318.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: senseair,sunrise-006-0-0007
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
ndry-gpios:
|
||||
maxItems: 1
|
||||
description:
|
||||
Phandle to the GPIO line connected to the nDRY pin. Typically active low.
|
||||
|
||||
en-gpios:
|
||||
maxItems: 1
|
||||
description:
|
||||
Phandle to the GPIO line connected to the EN pin. Typically active high.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
co2-sensor@68 {
|
||||
compatible = "senseair,sunrise-006-0-0007";
|
||||
reg = <0x68>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/chemical/sensirion,scd4x.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Sensirion SCD4X carbon dioxide sensor
|
||||
|
||||
maintainers:
|
||||
- Roan van Dijk <roan@protonic.nl>
|
||||
|
||||
description: |
|
||||
Air quality sensor capable of measuring co2 concentration, temperature
|
||||
and relative humidity.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- sensirion,scd40
|
||||
- sensirion,scd41
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
vdd-supply: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
co2-sensor@62 {
|
||||
compatible = "sensirion,scd41";
|
||||
reg = <0x62>;
|
||||
};
|
||||
};
|
|
@ -17,11 +17,13 @@ properties:
|
|||
- asahi-kasei,ak8963
|
||||
- asahi-kasei,ak09911
|
||||
- asahi-kasei,ak09912
|
||||
- asahi-kasei,ak09916
|
||||
- enum:
|
||||
- ak8975
|
||||
- ak8963
|
||||
- ak09911
|
||||
- ak09912
|
||||
- ak09916
|
||||
deprecated: true
|
||||
|
||||
reg:
|
||||
|
@ -43,6 +45,11 @@ properties:
|
|||
an optional regulator that needs to be on to provide VDD power to
|
||||
the sensor.
|
||||
|
||||
vid-supply:
|
||||
description: |
|
||||
an optional regulator that needs to be on to provide VID power to
|
||||
the sensor.
|
||||
|
||||
mount-matrix:
|
||||
description: an optional 3x3 mounting rotation matrix.
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/temperature/maxim,max31865.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Maxim MAX31865 Resistance Temperature Detector.
|
||||
|
||||
maintainers:
|
||||
- Navin Sankar Velliangiri <navin@linumiz.com>
|
||||
|
||||
description: |
|
||||
https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: maxim,max31865
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
maxim,3-wire:
|
||||
description:
|
||||
Identifies the number of wires used by the RTD. Setting this property
|
||||
enables 3-wire RTD connection. Else 2-wire or 4-wire RTD connection.
|
||||
type: boolean
|
||||
|
||||
spi-max-frequency: true
|
||||
spi-cpha: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- spi-cpha
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
temp_sensor@0 {
|
||||
compatible = "maxim,max31865";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <400000>;
|
||||
spi-cpha;
|
||||
maxim,3-wire;
|
||||
};
|
||||
};
|
||||
...
|
|
@ -1018,6 +1018,8 @@ patternProperties:
|
|||
description: Shenzhen SEI Robotics Co., Ltd
|
||||
"^semtech,.*":
|
||||
description: Semtech Corporation
|
||||
"^senseair,.*":
|
||||
description: Senseair AB
|
||||
"^sensirion,.*":
|
||||
description: Sensirion AG
|
||||
"^sensortek,.*":
|
||||
|
|
|
@ -287,6 +287,7 @@ IIO
|
|||
devm_iio_device_register()
|
||||
devm_iio_dmaengine_buffer_setup()
|
||||
devm_iio_kfifo_buffer_setup()
|
||||
devm_iio_map_array_register()
|
||||
devm_iio_triggered_buffer_setup()
|
||||
devm_iio_trigger_alloc()
|
||||
devm_iio_trigger_register()
|
||||
|
|
36
MAINTAINERS
36
MAINTAINERS
|
@ -591,6 +591,12 @@ L: platform-driver-x86@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/platform/x86/adv_swbutton.c
|
||||
|
||||
ADXL313 THREE-AXIS DIGITAL ACCELEROMETER DRIVER
|
||||
M: Lucas Stankus <lucas.p.stankus@gmail.com>
|
||||
S: Supported
|
||||
F: Documentation/devicetree/bindings/iio/accel/adi,adxl313.yaml
|
||||
F: drivers/iio/accel/adxl313*
|
||||
|
||||
ADXL34X THREE-AXIS DIGITAL ACCELEROMETER DRIVER (ADXL345/ADXL346)
|
||||
M: Michael Hennerich <michael.hennerich@analog.com>
|
||||
S: Supported
|
||||
|
@ -599,6 +605,16 @@ W: http://ez.analog.com/community/linux-device-drivers
|
|||
F: Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml
|
||||
F: drivers/input/misc/adxl34x.c
|
||||
|
||||
ADXL355 THREE-AXIS DIGITAL ACCELEROMETER DRIVER
|
||||
M: Puranjay Mohan <puranjay12@gmail.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Supported
|
||||
F: Documentation/devicetree/bindings/iio/accel/adi,adxl355.yaml
|
||||
F: drivers/iio/accel/adxl355.h
|
||||
F: drivers/iio/accel/adxl355_core.c
|
||||
F: drivers/iio/accel/adxl355_i2c.c
|
||||
F: drivers/iio/accel/adxl355_spi.c
|
||||
|
||||
ADXL372 THREE-AXIS DIGITAL ACCELEROMETER DRIVER
|
||||
M: Michael Hennerich <michael.hennerich@analog.com>
|
||||
S: Supported
|
||||
|
@ -13472,6 +13488,13 @@ S: Maintained
|
|||
F: Documentation/devicetree/bindings/display/imx/nxp,imx8mq-dcss.yaml
|
||||
F: drivers/gpu/drm/imx/dcss/
|
||||
|
||||
NXP i.MX 8QXP ADC DRIVER
|
||||
M: Cai Huoqing <caihuoqing@baidu.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Supported
|
||||
F: Documentation/devicetree/bindings/iio/adc/nxp,imx8qxp-adc.yaml
|
||||
F: drivers/iio/adc/imx8qxp-adc.c
|
||||
|
||||
NXP PF8100/PF8121A/PF8200 PMIC REGULATOR DEVICE DRIVER
|
||||
M: Jagan Teki <jagan@amarulasolutions.com>
|
||||
S: Maintained
|
||||
|
@ -16872,6 +16895,13 @@ S: Maintained
|
|||
F: drivers/misc/phantom.c
|
||||
F: include/uapi/linux/phantom.h
|
||||
|
||||
SENSEAIR SUNRISE 006-0-0007
|
||||
M: Jacopo Mondi <jacopo@jmondi.org>
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-bus-iio-chemical-sunrise-co2
|
||||
F: Documentation/devicetree/bindings/iio/chemical/senseair,sunrise.yaml
|
||||
F: drivers/iio/chemical/sunrise_co2.c
|
||||
|
||||
SENSIRION SCD30 CARBON DIOXIDE SENSOR DRIVER
|
||||
M: Tomasz Duszynski <tomasz.duszynski@octakon.com>
|
||||
S: Maintained
|
||||
|
@ -16881,6 +16911,12 @@ F: drivers/iio/chemical/scd30_core.c
|
|||
F: drivers/iio/chemical/scd30_i2c.c
|
||||
F: drivers/iio/chemical/scd30_serial.c
|
||||
|
||||
SENSIRION SCD4X CARBON DIOXIDE SENSOR DRIVER
|
||||
M: Roan van Dijk <roan@protonic.nl>
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/iio/chemical/sensirion,scd4x.yaml
|
||||
F: drivers/iio/chemical/scd4x.c
|
||||
|
||||
SENSIRION SGP40 GAS SENSOR DRIVER
|
||||
M: Andreas Klinger <ak@it-klinger.de>
|
||||
S: Maintained
|
||||
|
|
|
@ -30,6 +30,35 @@ config ADIS16209
|
|||
To compile this driver as a module, say M here: the module will be
|
||||
called adis16209.
|
||||
|
||||
config ADXL313
|
||||
tristate
|
||||
|
||||
config ADXL313_I2C
|
||||
tristate "Analog Devices ADXL313 3-Axis Digital Accelerometer I2C Driver"
|
||||
depends on I2C
|
||||
select ADXL313
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say Y here if you want to build i2c support for the Analog Devices
|
||||
ADXL313 3-axis digital accelerometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called adxl313_i2c and you will also get adxl313_core
|
||||
for the core module.
|
||||
|
||||
config ADXL313_SPI
|
||||
tristate "Analog Devices ADXL313 3-Axis Digital Accelerometer SPI Driver"
|
||||
depends on SPI
|
||||
select ADXL313
|
||||
select REGMAP_SPI
|
||||
help
|
||||
Say Y here if you want to build spi support for the Analog Devices
|
||||
ADXL313 3-axis digital accelerometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called adxl313_spi and you will also get adxl313_core
|
||||
for the core module.
|
||||
|
||||
config ADXL345
|
||||
tristate
|
||||
|
||||
|
@ -61,6 +90,39 @@ config ADXL345_SPI
|
|||
will be called adxl345_spi and you will also get adxl345_core
|
||||
for the core module.
|
||||
|
||||
config ADXL355
|
||||
tristate
|
||||
|
||||
config ADXL355_I2C
|
||||
tristate "Analog Devices ADXL355 3-Axis Digital Accelerometer I2C Driver"
|
||||
depends on I2C
|
||||
select ADXL355
|
||||
select REGMAP_I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
Say Y here if you want to build i2c support for the Analog Devices
|
||||
ADXL355 3-axis digital accelerometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called adxl355_i2c and you will also get adxl355_core
|
||||
for the core module.
|
||||
|
||||
config ADXL355_SPI
|
||||
tristate "Analog Devices ADXL355 3-Axis Digital Accelerometer SPI Driver"
|
||||
depends on SPI
|
||||
select ADXL355
|
||||
select REGMAP_SPI
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
Say Y here if you want to build spi support for the Analog Devices
|
||||
ADXL355 3-axis digital accelerometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called adxl355_spi and you will also get adxl355_core
|
||||
for the core module.
|
||||
|
||||
config ADXL372
|
||||
tristate
|
||||
select IIO_BUFFER
|
||||
|
|
|
@ -6,9 +6,15 @@
|
|||
# When adding new entries keep the list in alphabetical order
|
||||
obj-$(CONFIG_ADIS16201) += adis16201.o
|
||||
obj-$(CONFIG_ADIS16209) += adis16209.o
|
||||
obj-$(CONFIG_ADXL313) += adxl313_core.o
|
||||
obj-$(CONFIG_ADXL313_I2C) += adxl313_i2c.o
|
||||
obj-$(CONFIG_ADXL313_SPI) += adxl313_spi.o
|
||||
obj-$(CONFIG_ADXL345) += adxl345_core.o
|
||||
obj-$(CONFIG_ADXL345_I2C) += adxl345_i2c.o
|
||||
obj-$(CONFIG_ADXL345_SPI) += adxl345_spi.o
|
||||
obj-$(CONFIG_ADXL355) += adxl355_core.o
|
||||
obj-$(CONFIG_ADXL355_I2C) += adxl355_i2c.o
|
||||
obj-$(CONFIG_ADXL355_SPI) += adxl355_spi.o
|
||||
obj-$(CONFIG_ADXL372) += adxl372.o
|
||||
obj-$(CONFIG_ADXL372_I2C) += adxl372_i2c.o
|
||||
obj-$(CONFIG_ADXL372_SPI) += adxl372_spi.o
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* ADXL313 3-Axis Digital Accelerometer
|
||||
*
|
||||
* Copyright (c) 2021 Lucas Stankus <lucas.p.stankus@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _ADXL313_H_
|
||||
#define _ADXL313_H_
|
||||
|
||||
/* ADXL313 register definitions */
|
||||
#define ADXL313_REG_DEVID0 0x00
|
||||
#define ADXL313_REG_DEVID1 0x01
|
||||
#define ADXL313_REG_PARTID 0x02
|
||||
#define ADXL313_REG_XID 0x04
|
||||
#define ADXL313_REG_SOFT_RESET 0x18
|
||||
#define ADXL313_REG_OFS_AXIS(index) (0x1E + (index))
|
||||
#define ADXL313_REG_THRESH_ACT 0x24
|
||||
#define ADXL313_REG_ACT_INACT_CTL 0x27
|
||||
#define ADXL313_REG_BW_RATE 0x2C
|
||||
#define ADXL313_REG_POWER_CTL 0x2D
|
||||
#define ADXL313_REG_INT_MAP 0x2F
|
||||
#define ADXL313_REG_DATA_FORMAT 0x31
|
||||
#define ADXL313_REG_DATA_AXIS(index) (0x32 + ((index) * 2))
|
||||
#define ADXL313_REG_FIFO_CTL 0x38
|
||||
#define ADXL313_REG_FIFO_STATUS 0x39
|
||||
|
||||
#define ADXL313_DEVID0 0xAD
|
||||
#define ADXL313_DEVID1 0x1D
|
||||
#define ADXL313_PARTID 0xCB
|
||||
#define ADXL313_SOFT_RESET 0x52
|
||||
|
||||
#define ADXL313_RATE_MSK GENMASK(3, 0)
|
||||
#define ADXL313_RATE_BASE 6
|
||||
|
||||
#define ADXL313_POWER_CTL_MSK GENMASK(3, 2)
|
||||
#define ADXL313_MEASUREMENT_MODE BIT(3)
|
||||
|
||||
#define ADXL313_RANGE_MSK GENMASK(1, 0)
|
||||
#define ADXL313_RANGE_4G 3
|
||||
|
||||
#define ADXL313_FULL_RES BIT(3)
|
||||
#define ADXL313_SPI_3WIRE BIT(6)
|
||||
#define ADXL313_I2C_DISABLE BIT(6)
|
||||
|
||||
extern const struct regmap_access_table adxl313_readable_regs_table;
|
||||
|
||||
extern const struct regmap_access_table adxl313_writable_regs_table;
|
||||
|
||||
int adxl313_core_probe(struct device *dev,
|
||||
struct regmap *regmap,
|
||||
const char *name,
|
||||
int (*setup)(struct device *, struct regmap *));
|
||||
#endif /* _ADXL313_H_ */
|
|
@ -0,0 +1,332 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* ADXL313 3-Axis Digital Accelerometer
|
||||
*
|
||||
* Copyright (c) 2021 Lucas Stankus <lucas.p.stankus@gmail.com>
|
||||
*
|
||||
* Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "adxl313.h"
|
||||
|
||||
static const struct regmap_range adxl313_readable_reg_range[] = {
|
||||
regmap_reg_range(ADXL313_REG_DEVID0, ADXL313_REG_XID),
|
||||
regmap_reg_range(ADXL313_REG_SOFT_RESET, ADXL313_REG_SOFT_RESET),
|
||||
regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)),
|
||||
regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL),
|
||||
regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_FIFO_STATUS),
|
||||
};
|
||||
|
||||
const struct regmap_access_table adxl313_readable_regs_table = {
|
||||
.yes_ranges = adxl313_readable_reg_range,
|
||||
.n_yes_ranges = ARRAY_SIZE(adxl313_readable_reg_range),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(adxl313_readable_regs_table);
|
||||
|
||||
static const struct regmap_range adxl313_writable_reg_range[] = {
|
||||
regmap_reg_range(ADXL313_REG_SOFT_RESET, ADXL313_REG_SOFT_RESET),
|
||||
regmap_reg_range(ADXL313_REG_OFS_AXIS(0), ADXL313_REG_OFS_AXIS(2)),
|
||||
regmap_reg_range(ADXL313_REG_THRESH_ACT, ADXL313_REG_ACT_INACT_CTL),
|
||||
regmap_reg_range(ADXL313_REG_BW_RATE, ADXL313_REG_INT_MAP),
|
||||
regmap_reg_range(ADXL313_REG_DATA_FORMAT, ADXL313_REG_DATA_FORMAT),
|
||||
regmap_reg_range(ADXL313_REG_FIFO_CTL, ADXL313_REG_FIFO_CTL),
|
||||
};
|
||||
|
||||
const struct regmap_access_table adxl313_writable_regs_table = {
|
||||
.yes_ranges = adxl313_writable_reg_range,
|
||||
.n_yes_ranges = ARRAY_SIZE(adxl313_writable_reg_range),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(adxl313_writable_regs_table);
|
||||
|
||||
struct adxl313_data {
|
||||
struct regmap *regmap;
|
||||
struct mutex lock; /* lock to protect transf_buf */
|
||||
__le16 transf_buf ____cacheline_aligned;
|
||||
};
|
||||
|
||||
static const int adxl313_odr_freqs[][2] = {
|
||||
[0] = { 6, 250000 },
|
||||
[1] = { 12, 500000 },
|
||||
[2] = { 25, 0 },
|
||||
[3] = { 50, 0 },
|
||||
[4] = { 100, 0 },
|
||||
[5] = { 200, 0 },
|
||||
[6] = { 400, 0 },
|
||||
[7] = { 800, 0 },
|
||||
[8] = { 1600, 0 },
|
||||
[9] = { 3200, 0 },
|
||||
};
|
||||
|
||||
#define ADXL313_ACCEL_CHANNEL(index, axis) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.address = index, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_CALIBBIAS), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.info_mask_shared_by_type_available = \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.scan_type = { \
|
||||
.realbits = 13, \
|
||||
}, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec adxl313_channels[] = {
|
||||
ADXL313_ACCEL_CHANNEL(0, X),
|
||||
ADXL313_ACCEL_CHANNEL(1, Y),
|
||||
ADXL313_ACCEL_CHANNEL(2, Z),
|
||||
};
|
||||
|
||||
static int adxl313_set_odr(struct adxl313_data *data,
|
||||
unsigned int freq1, unsigned int freq2)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(adxl313_odr_freqs); i++) {
|
||||
if (adxl313_odr_freqs[i][0] == freq1 &&
|
||||
adxl313_odr_freqs[i][1] == freq2)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(adxl313_odr_freqs))
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_update_bits(data->regmap, ADXL313_REG_BW_RATE,
|
||||
ADXL313_RATE_MSK,
|
||||
FIELD_PREP(ADXL313_RATE_MSK, ADXL313_RATE_BASE + i));
|
||||
}
|
||||
|
||||
static int adxl313_read_axis(struct adxl313_data *data,
|
||||
struct iio_chan_spec const *chan)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
ret = regmap_bulk_read(data->regmap,
|
||||
ADXL313_REG_DATA_AXIS(chan->address),
|
||||
&data->transf_buf, sizeof(data->transf_buf));
|
||||
if (ret)
|
||||
goto unlock_ret;
|
||||
|
||||
ret = le16_to_cpu(data->transf_buf);
|
||||
|
||||
unlock_ret:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adxl313_read_freq_avail(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
const int **vals, int *type, int *length,
|
||||
long mask)
|
||||
{
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*vals = (const int *)adxl313_odr_freqs;
|
||||
*length = ARRAY_SIZE(adxl313_odr_freqs) * 2;
|
||||
*type = IIO_VAL_INT_PLUS_MICRO;
|
||||
return IIO_AVAIL_LIST;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int adxl313_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct adxl313_data *data = iio_priv(indio_dev);
|
||||
unsigned int regval;
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = adxl313_read_axis(data, chan);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = sign_extend32(ret, chan->scan_type.realbits - 1);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
/*
|
||||
* Scale for any g range is given in datasheet as
|
||||
* 1024 LSB/g = 0.0009765625 * 9.80665 = 0.009576806640625 m/s^2
|
||||
*/
|
||||
*val = 0;
|
||||
*val2 = 9576806;
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
ret = regmap_read(data->regmap,
|
||||
ADXL313_REG_OFS_AXIS(chan->address), ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* 8-bit resolution at +/- 0.5g, that is 4x accel data scale
|
||||
* factor at full resolution
|
||||
*/
|
||||
*val = sign_extend32(regval, 7) * 4;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret = regmap_read(data->regmap, ADXL313_REG_BW_RATE, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = FIELD_GET(ADXL313_RATE_MSK, regval) - ADXL313_RATE_BASE;
|
||||
*val = adxl313_odr_freqs[ret][0];
|
||||
*val2 = adxl313_odr_freqs[ret][1];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int adxl313_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct adxl313_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
/*
|
||||
* 8-bit resolution at +/- 0.5g, that is 4x accel data scale
|
||||
* factor at full resolution
|
||||
*/
|
||||
if (clamp_val(val, -128 * 4, 127 * 4) != val)
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_write(data->regmap,
|
||||
ADXL313_REG_OFS_AXIS(chan->address),
|
||||
val / 4);
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return adxl313_set_odr(data, val, val2);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info adxl313_info = {
|
||||
.read_raw = adxl313_read_raw,
|
||||
.write_raw = adxl313_write_raw,
|
||||
.read_avail = adxl313_read_freq_avail,
|
||||
};
|
||||
|
||||
static int adxl313_setup(struct device *dev, struct adxl313_data *data,
|
||||
int (*setup)(struct device *, struct regmap *))
|
||||
{
|
||||
unsigned int regval;
|
||||
int ret;
|
||||
|
||||
/* Ensures the device is in a consistent state after start up */
|
||||
ret = regmap_write(data->regmap, ADXL313_REG_SOFT_RESET,
|
||||
ADXL313_SOFT_RESET);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (setup) {
|
||||
ret = setup(dev, data->regmap);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_read(data->regmap, ADXL313_REG_DEVID0, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (regval != ADXL313_DEVID0) {
|
||||
dev_err(dev, "Invalid manufacturer ID: 0x%02x\n", regval);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = regmap_read(data->regmap, ADXL313_REG_DEVID1, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (regval != ADXL313_DEVID1) {
|
||||
dev_err(dev, "Invalid mems ID: 0x%02x\n", regval);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = regmap_read(data->regmap, ADXL313_REG_PARTID, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (regval != ADXL313_PARTID) {
|
||||
dev_err(dev, "Invalid device ID: 0x%02x\n", regval);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Sets the range to +/- 4g */
|
||||
ret = regmap_update_bits(data->regmap, ADXL313_REG_DATA_FORMAT,
|
||||
ADXL313_RANGE_MSK,
|
||||
FIELD_PREP(ADXL313_RANGE_MSK, ADXL313_RANGE_4G));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enables full resolution */
|
||||
ret = regmap_update_bits(data->regmap, ADXL313_REG_DATA_FORMAT,
|
||||
ADXL313_FULL_RES, ADXL313_FULL_RES);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enables measurement mode */
|
||||
return regmap_update_bits(data->regmap, ADXL313_REG_POWER_CTL,
|
||||
ADXL313_POWER_CTL_MSK,
|
||||
ADXL313_MEASUREMENT_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* adxl313_core_probe() - probe and setup for adxl313 accelerometer
|
||||
* @dev: Driver model representation of the device
|
||||
* @regmap: Register map of the device
|
||||
* @name: Device name buffer reference
|
||||
* @setup: Setup routine to be executed right before the standard device
|
||||
* setup, can also be set to NULL if not required
|
||||
*
|
||||
* Return: 0 on success, negative errno on error cases
|
||||
*/
|
||||
int adxl313_core_probe(struct device *dev,
|
||||
struct regmap *regmap,
|
||||
const char *name,
|
||||
int (*setup)(struct device *, struct regmap *))
|
||||
{
|
||||
struct adxl313_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);
|
||||
data->regmap = regmap;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
indio_dev->name = name;
|
||||
indio_dev->info = &adxl313_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = adxl313_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(adxl313_channels);
|
||||
|
||||
ret = adxl313_setup(dev, data, setup);
|
||||
if (ret) {
|
||||
dev_err(dev, "ADXL313 setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(adxl313_core_probe);
|
||||
|
||||
MODULE_AUTHOR("Lucas Stankus <lucas.p.stankus@gmail.com>");
|
||||
MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer core driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,66 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* ADXL313 3-Axis Digital Accelerometer
|
||||
*
|
||||
* Copyright (c) 2021 Lucas Stankus <lucas.p.stankus@gmail.com>
|
||||
*
|
||||
* Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "adxl313.h"
|
||||
|
||||
static const struct regmap_config adxl313_i2c_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.rd_table = &adxl313_readable_regs_table,
|
||||
.wr_table = &adxl313_writable_regs_table,
|
||||
.max_register = 0x39,
|
||||
};
|
||||
|
||||
static int adxl313_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &adxl313_i2c_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&client->dev, "Error initializing i2c regmap: %ld\n",
|
||||
PTR_ERR(regmap));
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
return adxl313_core_probe(&client->dev, regmap, client->name, NULL);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id adxl313_i2c_id[] = {
|
||||
{ "adxl313" },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, adxl313_i2c_id);
|
||||
|
||||
static const struct of_device_id adxl313_of_match[] = {
|
||||
{ .compatible = "adi,adxl313" },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, adxl313_of_match);
|
||||
|
||||
static struct i2c_driver adxl313_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "adxl313_i2c",
|
||||
.of_match_table = adxl313_of_match,
|
||||
},
|
||||
.probe_new = adxl313_i2c_probe,
|
||||
.id_table = adxl313_i2c_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(adxl313_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Lucas Stankus <lucas.p.stankus@gmail.com>");
|
||||
MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer I2C driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,92 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* ADXL313 3-Axis Digital Accelerometer
|
||||
*
|
||||
* Copyright (c) 2021 Lucas Stankus <lucas.p.stankus@gmail.com>
|
||||
*
|
||||
* Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL313.pdf
|
||||
*/
|
||||
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include "adxl313.h"
|
||||
|
||||
static const struct regmap_config adxl313_spi_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.rd_table = &adxl313_readable_regs_table,
|
||||
.wr_table = &adxl313_writable_regs_table,
|
||||
.max_register = 0x39,
|
||||
/* Setting bits 7 and 6 enables multiple-byte read */
|
||||
.read_flag_mask = BIT(7) | BIT(6),
|
||||
};
|
||||
|
||||
static int adxl313_spi_setup(struct device *dev, struct regmap *regmap)
|
||||
{
|
||||
struct spi_device *spi = container_of(dev, struct spi_device, dev);
|
||||
int ret;
|
||||
|
||||
if (spi->mode & SPI_3WIRE) {
|
||||
ret = regmap_write(regmap, ADXL313_REG_DATA_FORMAT,
|
||||
ADXL313_SPI_3WIRE);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return regmap_update_bits(regmap, ADXL313_REG_POWER_CTL,
|
||||
ADXL313_I2C_DISABLE, ADXL313_I2C_DISABLE);
|
||||
}
|
||||
|
||||
static int adxl313_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct spi_device_id *id = spi_get_device_id(spi);
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
spi->mode |= SPI_MODE_3;
|
||||
ret = spi_setup(spi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
regmap = devm_regmap_init_spi(spi, &adxl313_spi_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&spi->dev, "Error initializing spi regmap: %ld\n",
|
||||
PTR_ERR(regmap));
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
return adxl313_core_probe(&spi->dev, regmap, id->name,
|
||||
&adxl313_spi_setup);
|
||||
}
|
||||
|
||||
static const struct spi_device_id adxl313_spi_id[] = {
|
||||
{ "adxl313" },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(spi, adxl313_spi_id);
|
||||
|
||||
static const struct of_device_id adxl313_of_match[] = {
|
||||
{ .compatible = "adi,adxl313" },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, adxl313_of_match);
|
||||
|
||||
static struct spi_driver adxl313_spi_driver = {
|
||||
.driver = {
|
||||
.name = "adxl313_spi",
|
||||
.of_match_table = adxl313_of_match,
|
||||
},
|
||||
.probe = adxl313_spi_probe,
|
||||
.id_table = adxl313_spi_id,
|
||||
};
|
||||
|
||||
module_spi_driver(adxl313_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Lucas Stankus <lucas.p.stankus@gmail.com>");
|
||||
MODULE_DESCRIPTION("ADXL313 3-Axis Digital Accelerometer SPI driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,21 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* ADXL355 3-Axis Digital Accelerometer
|
||||
*
|
||||
* Copyright (c) 2021 Puranjay Mohan <puranjay12@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _ADXL355_H_
|
||||
#define _ADXL355_H_
|
||||
|
||||
#include <linux/regmap.h>
|
||||
|
||||
struct device;
|
||||
|
||||
extern const struct regmap_access_table adxl355_readable_regs_tbl;
|
||||
extern const struct regmap_access_table adxl355_writeable_regs_tbl;
|
||||
|
||||
int adxl355_core_probe(struct device *dev, struct regmap *regmap,
|
||||
const char *name);
|
||||
|
||||
#endif /* _ADXL355_H_ */
|
|
@ -0,0 +1,765 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* ADXL355 3-Axis Digital Accelerometer IIO core driver
|
||||
*
|
||||
* Copyright (c) 2021 Puranjay Mohan <puranjay12@gmail.com>
|
||||
*
|
||||
* Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/adxl354_adxl355.pdf
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#include "adxl355.h"
|
||||
|
||||
/* ADXL355 Register Definitions */
|
||||
#define ADXL355_DEVID_AD_REG 0x00
|
||||
#define ADXL355_DEVID_MST_REG 0x01
|
||||
#define ADXL355_PARTID_REG 0x02
|
||||
#define ADXL355_STATUS_REG 0x04
|
||||
#define ADXL355_FIFO_ENTRIES_REG 0x05
|
||||
#define ADXL355_TEMP2_REG 0x06
|
||||
#define ADXL355_XDATA3_REG 0x08
|
||||
#define ADXL355_YDATA3_REG 0x0B
|
||||
#define ADXL355_ZDATA3_REG 0x0E
|
||||
#define ADXL355_FIFO_DATA_REG 0x11
|
||||
#define ADXL355_OFFSET_X_H_REG 0x1E
|
||||
#define ADXL355_OFFSET_Y_H_REG 0x20
|
||||
#define ADXL355_OFFSET_Z_H_REG 0x22
|
||||
#define ADXL355_ACT_EN_REG 0x24
|
||||
#define ADXL355_ACT_THRESH_H_REG 0x25
|
||||
#define ADXL355_ACT_THRESH_L_REG 0x26
|
||||
#define ADXL355_ACT_COUNT_REG 0x27
|
||||
#define ADXL355_FILTER_REG 0x28
|
||||
#define ADXL355_FILTER_ODR_MSK GENMASK(3, 0)
|
||||
#define ADXL355_FILTER_HPF_MSK GENMASK(6, 4)
|
||||
#define ADXL355_FIFO_SAMPLES_REG 0x29
|
||||
#define ADXL355_INT_MAP_REG 0x2A
|
||||
#define ADXL355_SYNC_REG 0x2B
|
||||
#define ADXL355_RANGE_REG 0x2C
|
||||
#define ADXL355_POWER_CTL_REG 0x2D
|
||||
#define ADXL355_POWER_CTL_MODE_MSK GENMASK(1, 0)
|
||||
#define ADXL355_POWER_CTL_DRDY_MSK BIT(2)
|
||||
#define ADXL355_SELF_TEST_REG 0x2E
|
||||
#define ADXL355_RESET_REG 0x2F
|
||||
|
||||
#define ADXL355_DEVID_AD_VAL 0xAD
|
||||
#define ADXL355_DEVID_MST_VAL 0x1D
|
||||
#define ADXL355_PARTID_VAL 0xED
|
||||
#define ADXL355_RESET_CODE 0x52
|
||||
|
||||
#define MEGA 1000000UL
|
||||
#define TERA 1000000000000ULL
|
||||
|
||||
static const struct regmap_range adxl355_read_reg_range[] = {
|
||||
regmap_reg_range(ADXL355_DEVID_AD_REG, ADXL355_FIFO_DATA_REG),
|
||||
regmap_reg_range(ADXL355_OFFSET_X_H_REG, ADXL355_SELF_TEST_REG),
|
||||
};
|
||||
|
||||
const struct regmap_access_table adxl355_readable_regs_tbl = {
|
||||
.yes_ranges = adxl355_read_reg_range,
|
||||
.n_yes_ranges = ARRAY_SIZE(adxl355_read_reg_range),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(adxl355_readable_regs_tbl);
|
||||
|
||||
static const struct regmap_range adxl355_write_reg_range[] = {
|
||||
regmap_reg_range(ADXL355_OFFSET_X_H_REG, ADXL355_RESET_REG),
|
||||
};
|
||||
|
||||
const struct regmap_access_table adxl355_writeable_regs_tbl = {
|
||||
.yes_ranges = adxl355_write_reg_range,
|
||||
.n_yes_ranges = ARRAY_SIZE(adxl355_write_reg_range),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(adxl355_writeable_regs_tbl);
|
||||
|
||||
enum adxl355_op_mode {
|
||||
ADXL355_MEASUREMENT,
|
||||
ADXL355_STANDBY,
|
||||
ADXL355_TEMP_OFF,
|
||||
};
|
||||
|
||||
enum adxl355_odr {
|
||||
ADXL355_ODR_4000HZ,
|
||||
ADXL355_ODR_2000HZ,
|
||||
ADXL355_ODR_1000HZ,
|
||||
ADXL355_ODR_500HZ,
|
||||
ADXL355_ODR_250HZ,
|
||||
ADXL355_ODR_125HZ,
|
||||
ADXL355_ODR_62_5HZ,
|
||||
ADXL355_ODR_31_25HZ,
|
||||
ADXL355_ODR_15_625HZ,
|
||||
ADXL355_ODR_7_813HZ,
|
||||
ADXL355_ODR_3_906HZ,
|
||||
};
|
||||
|
||||
enum adxl355_hpf_3db {
|
||||
ADXL355_HPF_OFF,
|
||||
ADXL355_HPF_24_7,
|
||||
ADXL355_HPF_6_2084,
|
||||
ADXL355_HPF_1_5545,
|
||||
ADXL355_HPF_0_3862,
|
||||
ADXL355_HPF_0_0954,
|
||||
ADXL355_HPF_0_0238,
|
||||
};
|
||||
|
||||
static const int adxl355_odr_table[][2] = {
|
||||
[0] = {4000, 0},
|
||||
[1] = {2000, 0},
|
||||
[2] = {1000, 0},
|
||||
[3] = {500, 0},
|
||||
[4] = {250, 0},
|
||||
[5] = {125, 0},
|
||||
[6] = {62, 500000},
|
||||
[7] = {31, 250000},
|
||||
[8] = {15, 625000},
|
||||
[9] = {7, 813000},
|
||||
[10] = {3, 906000},
|
||||
};
|
||||
|
||||
static const int adxl355_hpf_3db_multipliers[] = {
|
||||
0,
|
||||
247000,
|
||||
62084,
|
||||
15545,
|
||||
3862,
|
||||
954,
|
||||
238,
|
||||
};
|
||||
|
||||
enum adxl355_chans {
|
||||
chan_x, chan_y, chan_z,
|
||||
};
|
||||
|
||||
struct adxl355_chan_info {
|
||||
u8 data_reg;
|
||||
u8 offset_reg;
|
||||
};
|
||||
|
||||
static const struct adxl355_chan_info adxl355_chans[] = {
|
||||
[chan_x] = {
|
||||
.data_reg = ADXL355_XDATA3_REG,
|
||||
.offset_reg = ADXL355_OFFSET_X_H_REG
|
||||
},
|
||||
[chan_y] = {
|
||||
.data_reg = ADXL355_YDATA3_REG,
|
||||
.offset_reg = ADXL355_OFFSET_Y_H_REG
|
||||
},
|
||||
[chan_z] = {
|
||||
.data_reg = ADXL355_ZDATA3_REG,
|
||||
.offset_reg = ADXL355_OFFSET_Z_H_REG
|
||||
},
|
||||
};
|
||||
|
||||
struct adxl355_data {
|
||||
struct regmap *regmap;
|
||||
struct device *dev;
|
||||
struct mutex lock; /* lock to protect op_mode */
|
||||
enum adxl355_op_mode op_mode;
|
||||
enum adxl355_odr odr;
|
||||
enum adxl355_hpf_3db hpf_3db;
|
||||
int calibbias[3];
|
||||
int adxl355_hpf_3db_table[7][2];
|
||||
struct iio_trigger *dready_trig;
|
||||
union {
|
||||
u8 transf_buf[3];
|
||||
struct {
|
||||
u8 buf[14];
|
||||
s64 ts;
|
||||
} buffer;
|
||||
} ____cacheline_aligned;
|
||||
};
|
||||
|
||||
static int adxl355_set_op_mode(struct adxl355_data *data,
|
||||
enum adxl355_op_mode op_mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (data->op_mode == op_mode)
|
||||
return 0;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, ADXL355_POWER_CTL_REG,
|
||||
ADXL355_POWER_CTL_MODE_MSK, op_mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data->op_mode = op_mode;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adxl355_data_rdy_trigger_set_state(struct iio_trigger *trig,
|
||||
bool state)
|
||||
{
|
||||
struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
|
||||
struct adxl355_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
ret = regmap_update_bits(data->regmap, ADXL355_POWER_CTL_REG,
|
||||
ADXL355_POWER_CTL_DRDY_MSK,
|
||||
FIELD_PREP(ADXL355_POWER_CTL_DRDY_MSK,
|
||||
state ? 0 : 1));
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void adxl355_fill_3db_frequency_table(struct adxl355_data *data)
|
||||
{
|
||||
u32 multiplier;
|
||||
u64 div, rem;
|
||||
u64 odr;
|
||||
int i;
|
||||
|
||||
odr = mul_u64_u32_shr(adxl355_odr_table[data->odr][0], MEGA, 0) +
|
||||
adxl355_odr_table[data->odr][1];
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(adxl355_hpf_3db_multipliers); i++) {
|
||||
multiplier = adxl355_hpf_3db_multipliers[i];
|
||||
div = div64_u64_rem(mul_u64_u32_shr(odr, multiplier, 0),
|
||||
TERA * 100, &rem);
|
||||
|
||||
data->adxl355_hpf_3db_table[i][0] = div;
|
||||
data->adxl355_hpf_3db_table[i][1] = div_u64(rem, MEGA * 100);
|
||||
}
|
||||
}
|
||||
|
||||
static int adxl355_setup(struct adxl355_data *data)
|
||||
{
|
||||
unsigned int regval;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(data->regmap, ADXL355_DEVID_AD_REG, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (regval != ADXL355_DEVID_AD_VAL) {
|
||||
dev_err(data->dev, "Invalid ADI ID 0x%02x\n", regval);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = regmap_read(data->regmap, ADXL355_DEVID_MST_REG, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (regval != ADXL355_DEVID_MST_VAL) {
|
||||
dev_err(data->dev, "Invalid MEMS ID 0x%02x\n", regval);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = regmap_read(data->regmap, ADXL355_PARTID_REG, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (regval != ADXL355_PARTID_VAL) {
|
||||
dev_err(data->dev, "Invalid DEV ID 0x%02x\n", regval);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform a software reset to make sure the device is in a consistent
|
||||
* state after start-up.
|
||||
*/
|
||||
ret = regmap_write(data->regmap, ADXL355_RESET_REG, ADXL355_RESET_CODE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, ADXL355_POWER_CTL_REG,
|
||||
ADXL355_POWER_CTL_DRDY_MSK,
|
||||
FIELD_PREP(ADXL355_POWER_CTL_DRDY_MSK, 1));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
adxl355_fill_3db_frequency_table(data);
|
||||
|
||||
return adxl355_set_op_mode(data, ADXL355_MEASUREMENT);
|
||||
}
|
||||
|
||||
static int adxl355_get_temp_data(struct adxl355_data *data, u8 addr)
|
||||
{
|
||||
return regmap_bulk_read(data->regmap, addr, data->transf_buf, 2);
|
||||
}
|
||||
|
||||
static int adxl355_read_axis(struct adxl355_data *data, u8 addr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, addr, data->transf_buf,
|
||||
ARRAY_SIZE(data->transf_buf));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return get_unaligned_be24(data->transf_buf);
|
||||
}
|
||||
|
||||
static int adxl355_find_match(const int (*freq_tbl)[2], const int n,
|
||||
const int val, const int val2)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (freq_tbl[i][0] == val && freq_tbl[i][1] == val2)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int adxl355_set_odr(struct adxl355_data *data,
|
||||
enum adxl355_odr odr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
if (data->odr == odr) {
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = adxl355_set_op_mode(data, ADXL355_STANDBY);
|
||||
if (ret)
|
||||
goto err_unlock;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, ADXL355_FILTER_REG,
|
||||
ADXL355_FILTER_ODR_MSK,
|
||||
FIELD_PREP(ADXL355_FILTER_ODR_MSK, odr));
|
||||
if (ret)
|
||||
goto err_set_opmode;
|
||||
|
||||
data->odr = odr;
|
||||
adxl355_fill_3db_frequency_table(data);
|
||||
|
||||
ret = adxl355_set_op_mode(data, ADXL355_MEASUREMENT);
|
||||
if (ret)
|
||||
goto err_set_opmode;
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
|
||||
err_set_opmode:
|
||||
adxl355_set_op_mode(data, ADXL355_MEASUREMENT);
|
||||
err_unlock:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adxl355_set_hpf_3db(struct adxl355_data *data,
|
||||
enum adxl355_hpf_3db hpf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
if (data->hpf_3db == hpf) {
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = adxl355_set_op_mode(data, ADXL355_STANDBY);
|
||||
if (ret)
|
||||
goto err_unlock;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, ADXL355_FILTER_REG,
|
||||
ADXL355_FILTER_HPF_MSK,
|
||||
FIELD_PREP(ADXL355_FILTER_HPF_MSK, hpf));
|
||||
if (ret)
|
||||
goto err_set_opmode;
|
||||
|
||||
data->hpf_3db = hpf;
|
||||
|
||||
ret = adxl355_set_op_mode(data, ADXL355_MEASUREMENT);
|
||||
if (ret)
|
||||
goto err_set_opmode;
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
|
||||
err_set_opmode:
|
||||
adxl355_set_op_mode(data, ADXL355_MEASUREMENT);
|
||||
err_unlock:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adxl355_set_calibbias(struct adxl355_data *data,
|
||||
enum adxl355_chans chan, int calibbias)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
ret = adxl355_set_op_mode(data, ADXL355_STANDBY);
|
||||
if (ret)
|
||||
goto err_unlock;
|
||||
|
||||
put_unaligned_be16(calibbias, data->transf_buf);
|
||||
ret = regmap_bulk_write(data->regmap,
|
||||
adxl355_chans[chan].offset_reg,
|
||||
data->transf_buf, 2);
|
||||
if (ret)
|
||||
goto err_set_opmode;
|
||||
|
||||
data->calibbias[chan] = calibbias;
|
||||
|
||||
ret = adxl355_set_op_mode(data, ADXL355_MEASUREMENT);
|
||||
if (ret)
|
||||
goto err_set_opmode;
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
|
||||
err_set_opmode:
|
||||
adxl355_set_op_mode(data, ADXL355_MEASUREMENT);
|
||||
err_unlock:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adxl355_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct adxl355_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
switch (chan->type) {
|
||||
case IIO_TEMP:
|
||||
ret = adxl355_get_temp_data(data, chan->address);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = get_unaligned_be16(data->transf_buf);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
case IIO_ACCEL:
|
||||
ret = adxl355_read_axis(data, adxl355_chans[
|
||||
chan->address].data_reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = sign_extend32(ret >> chan->scan_type.shift,
|
||||
chan->scan_type.realbits - 1);
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
switch (chan->type) {
|
||||
/*
|
||||
* The datasheet defines an intercept of 1885 LSB at 25 degC
|
||||
* and a slope of -9.05 LSB/C. The following formula can be used
|
||||
* to find the temperature:
|
||||
* Temp = ((RAW - 1885)/(-9.05)) + 25 but this doesn't follow
|
||||
* the format of the IIO which is Temp = (RAW + OFFSET) * SCALE.
|
||||
* Hence using some rearranging we get the scale as -110.497238
|
||||
* and offset as -2111.25.
|
||||
*/
|
||||
case IIO_TEMP:
|
||||
*val = -110;
|
||||
*val2 = 497238;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
/*
|
||||
* At +/- 2g with 20-bit resolution, scale is given in datasheet
|
||||
* as 3.9ug/LSB = 0.0000039 * 9.80665 = 0.00003824593 m/s^2.
|
||||
*/
|
||||
case IIO_ACCEL:
|
||||
*val = 0;
|
||||
*val2 = 38245;
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
*val = -2111;
|
||||
*val2 = 250000;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
*val = sign_extend32(data->calibbias[chan->address], 15);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*val = adxl355_odr_table[data->odr][0];
|
||||
*val2 = adxl355_odr_table[data->odr][1];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
|
||||
*val = data->adxl355_hpf_3db_table[data->hpf_3db][0];
|
||||
*val2 = data->adxl355_hpf_3db_table[data->hpf_3db][1];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int adxl355_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct adxl355_data *data = iio_priv(indio_dev);
|
||||
int odr_idx, hpf_idx, calibbias;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
odr_idx = adxl355_find_match(adxl355_odr_table,
|
||||
ARRAY_SIZE(adxl355_odr_table),
|
||||
val, val2);
|
||||
if (odr_idx < 0)
|
||||
return odr_idx;
|
||||
|
||||
return adxl355_set_odr(data, odr_idx);
|
||||
case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
|
||||
hpf_idx = adxl355_find_match(data->adxl355_hpf_3db_table,
|
||||
ARRAY_SIZE(data->adxl355_hpf_3db_table),
|
||||
val, val2);
|
||||
if (hpf_idx < 0)
|
||||
return hpf_idx;
|
||||
|
||||
return adxl355_set_hpf_3db(data, hpf_idx);
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
calibbias = clamp_t(int, val, S16_MIN, S16_MAX);
|
||||
|
||||
return adxl355_set_calibbias(data, chan->address, calibbias);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int adxl355_read_avail(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
const int **vals, int *type, int *length,
|
||||
long mask)
|
||||
{
|
||||
struct adxl355_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*vals = (const int *)adxl355_odr_table;
|
||||
*type = IIO_VAL_INT_PLUS_MICRO;
|
||||
/* Values are stored in a 2D matrix */
|
||||
*length = ARRAY_SIZE(adxl355_odr_table) * 2;
|
||||
|
||||
return IIO_AVAIL_LIST;
|
||||
case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY:
|
||||
*vals = (const int *)data->adxl355_hpf_3db_table;
|
||||
*type = IIO_VAL_INT_PLUS_MICRO;
|
||||
/* Values are stored in a 2D matrix */
|
||||
*length = ARRAY_SIZE(data->adxl355_hpf_3db_table) * 2;
|
||||
|
||||
return IIO_AVAIL_LIST;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const unsigned long adxl355_avail_scan_masks[] = {
|
||||
GENMASK(3, 0),
|
||||
0
|
||||
};
|
||||
|
||||
static const struct iio_info adxl355_info = {
|
||||
.read_raw = adxl355_read_raw,
|
||||
.write_raw = adxl355_write_raw,
|
||||
.read_avail = &adxl355_read_avail,
|
||||
};
|
||||
|
||||
static const struct iio_trigger_ops adxl355_trigger_ops = {
|
||||
.set_trigger_state = &adxl355_data_rdy_trigger_set_state,
|
||||
.validate_device = &iio_trigger_validate_own_device,
|
||||
};
|
||||
|
||||
static irqreturn_t adxl355_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct adxl355_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
/*
|
||||
* data->buffer is used both for triggered buffer support
|
||||
* and read/write_raw(), hence, it has to be zeroed here before usage.
|
||||
*/
|
||||
data->buffer.buf[0] = 0;
|
||||
|
||||
/*
|
||||
* The acceleration data is 24 bits and big endian. It has to be saved
|
||||
* in 32 bits, hence, it is saved in the 2nd byte of the 4 byte buffer.
|
||||
* The buf array is 14 bytes as it includes 3x4=12 bytes for
|
||||
* accelaration data of x, y, and z axis. It also includes 2 bytes for
|
||||
* temperature data.
|
||||
*/
|
||||
ret = regmap_bulk_read(data->regmap, ADXL355_XDATA3_REG,
|
||||
&data->buffer.buf[1], 3);
|
||||
if (ret)
|
||||
goto out_unlock_notify;
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, ADXL355_YDATA3_REG,
|
||||
&data->buffer.buf[5], 3);
|
||||
if (ret)
|
||||
goto out_unlock_notify;
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, ADXL355_ZDATA3_REG,
|
||||
&data->buffer.buf[9], 3);
|
||||
if (ret)
|
||||
goto out_unlock_notify;
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, ADXL355_TEMP2_REG,
|
||||
&data->buffer.buf[12], 2);
|
||||
if (ret)
|
||||
goto out_unlock_notify;
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer,
|
||||
pf->timestamp);
|
||||
|
||||
out_unlock_notify:
|
||||
mutex_unlock(&data->lock);
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
#define ADXL355_ACCEL_CHANNEL(index, reg, axis) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.address = reg, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_CALIBBIAS), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
|
||||
BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \
|
||||
.info_mask_shared_by_type_available = \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
|
||||
BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY), \
|
||||
.scan_index = index, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 20, \
|
||||
.storagebits = 32, \
|
||||
.shift = 4, \
|
||||
.endianness = IIO_BE, \
|
||||
} \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec adxl355_channels[] = {
|
||||
ADXL355_ACCEL_CHANNEL(0, chan_x, X),
|
||||
ADXL355_ACCEL_CHANNEL(1, chan_y, Y),
|
||||
ADXL355_ACCEL_CHANNEL(2, chan_z, Z),
|
||||
{
|
||||
.type = IIO_TEMP,
|
||||
.address = ADXL355_TEMP2_REG,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_OFFSET),
|
||||
.scan_index = 3,
|
||||
.scan_type = {
|
||||
.sign = 's',
|
||||
.realbits = 12,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_BE,
|
||||
},
|
||||
},
|
||||
IIO_CHAN_SOFT_TIMESTAMP(4),
|
||||
};
|
||||
|
||||
static int adxl355_probe_trigger(struct iio_dev *indio_dev, int irq)
|
||||
{
|
||||
struct adxl355_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
data->dready_trig = devm_iio_trigger_alloc(data->dev, "%s-dev%d",
|
||||
indio_dev->name,
|
||||
iio_device_id(indio_dev));
|
||||
if (!data->dready_trig)
|
||||
return -ENOMEM;
|
||||
|
||||
data->dready_trig->ops = &adxl355_trigger_ops;
|
||||
iio_trigger_set_drvdata(data->dready_trig, indio_dev);
|
||||
|
||||
ret = devm_request_irq(data->dev, irq,
|
||||
&iio_trigger_generic_data_rdy_poll,
|
||||
IRQF_ONESHOT, "adxl355_irq", data->dready_trig);
|
||||
if (ret)
|
||||
return dev_err_probe(data->dev, ret, "request irq %d failed\n",
|
||||
irq);
|
||||
|
||||
ret = devm_iio_trigger_register(data->dev, data->dready_trig);
|
||||
if (ret) {
|
||||
dev_err(data->dev, "iio trigger register failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
indio_dev->trig = iio_trigger_get(data->dready_trig);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int adxl355_core_probe(struct device *dev, struct regmap *regmap,
|
||||
const char *name)
|
||||
{
|
||||
struct adxl355_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
int irq;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->regmap = regmap;
|
||||
data->dev = dev;
|
||||
data->op_mode = ADXL355_STANDBY;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
indio_dev->name = name;
|
||||
indio_dev->info = &adxl355_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = adxl355_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(adxl355_channels);
|
||||
indio_dev->available_scan_masks = adxl355_avail_scan_masks;
|
||||
|
||||
ret = adxl355_setup(data);
|
||||
if (ret) {
|
||||
dev_err(dev, "ADXL355 setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
|
||||
&iio_pollfunc_store_time,
|
||||
&adxl355_trigger_handler, NULL);
|
||||
if (ret) {
|
||||
dev_err(dev, "iio triggered buffer setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Would be good to move it to the generic version.
|
||||
*/
|
||||
irq = of_irq_get_byname(dev->of_node, "DRDY");
|
||||
if (irq > 0) {
|
||||
ret = adxl355_probe_trigger(indio_dev, irq);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(adxl355_core_probe);
|
||||
|
||||
MODULE_AUTHOR("Puranjay Mohan <puranjay12@gmail.com>");
|
||||
MODULE_DESCRIPTION("ADXL355 3-Axis Digital Accelerometer core driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,62 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* ADXL355 3-Axis Digital Accelerometer I2C driver
|
||||
*
|
||||
* Copyright (c) 2021 Puranjay Mohan <puranjay12@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "adxl355.h"
|
||||
|
||||
static const struct regmap_config adxl355_i2c_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = 0x2F,
|
||||
.rd_table = &adxl355_readable_regs_tbl,
|
||||
.wr_table = &adxl355_writeable_regs_tbl,
|
||||
};
|
||||
|
||||
static int adxl355_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &adxl355_i2c_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&client->dev, "Error initializing i2c regmap: %ld\n",
|
||||
PTR_ERR(regmap));
|
||||
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
return adxl355_core_probe(&client->dev, regmap, client->name);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id adxl355_i2c_id[] = {
|
||||
{ "adxl355", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, adxl355_i2c_id);
|
||||
|
||||
static const struct of_device_id adxl355_of_match[] = {
|
||||
{ .compatible = "adi,adxl355" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adxl355_of_match);
|
||||
|
||||
static struct i2c_driver adxl355_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "adxl355_i2c",
|
||||
.of_match_table = adxl355_of_match,
|
||||
},
|
||||
.probe_new = adxl355_i2c_probe,
|
||||
.id_table = adxl355_i2c_id,
|
||||
};
|
||||
module_i2c_driver(adxl355_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Puranjay Mohan <puranjay12@gmail.com>");
|
||||
MODULE_DESCRIPTION("ADXL355 3-Axis Digital Accelerometer I2C driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,65 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* ADXL355 3-Axis Digital Accelerometer SPI driver
|
||||
*
|
||||
* Copyright (c) 2021 Puranjay Mohan <puranjay12@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include "adxl355.h"
|
||||
|
||||
static const struct regmap_config adxl355_spi_regmap_config = {
|
||||
.reg_bits = 7,
|
||||
.pad_bits = 1,
|
||||
.val_bits = 8,
|
||||
.read_flag_mask = BIT(0),
|
||||
.max_register = 0x2F,
|
||||
.rd_table = &adxl355_readable_regs_tbl,
|
||||
.wr_table = &adxl355_writeable_regs_tbl,
|
||||
};
|
||||
|
||||
static int adxl355_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct spi_device_id *id = spi_get_device_id(spi);
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = devm_regmap_init_spi(spi, &adxl355_spi_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&spi->dev, "Error initializing spi regmap: %ld\n",
|
||||
PTR_ERR(regmap));
|
||||
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
return adxl355_core_probe(&spi->dev, regmap, id->name);
|
||||
}
|
||||
|
||||
static const struct spi_device_id adxl355_spi_id[] = {
|
||||
{ "adxl355", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, adxl355_spi_id);
|
||||
|
||||
static const struct of_device_id adxl355_of_match[] = {
|
||||
{ .compatible = "adi,adxl355" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adxl355_of_match);
|
||||
|
||||
static struct spi_driver adxl355_spi_driver = {
|
||||
.driver = {
|
||||
.name = "adxl355_spi",
|
||||
.of_match_table = adxl355_of_match,
|
||||
},
|
||||
.probe = adxl355_spi_probe,
|
||||
.id_table = adxl355_spi_id,
|
||||
};
|
||||
module_spi_driver(adxl355_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Puranjay Mohan <puranjay12@gmail.com>");
|
||||
MODULE_DESCRIPTION("ADXL355 3-Axis Digital Accelerometer SPI driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -1214,6 +1214,7 @@ int adxl372_probe(struct device *dev, struct regmap *regmap,
|
|||
ret = devm_iio_triggered_buffer_setup_ext(dev,
|
||||
indio_dev, NULL,
|
||||
adxl372_trigger_handler,
|
||||
IIO_BUFFER_DIRECTION_IN,
|
||||
&adxl372_buffer_ops,
|
||||
adxl372_fifo_attributes);
|
||||
if (ret < 0)
|
||||
|
|
|
@ -94,6 +94,6 @@ extern const struct regmap_config bma400_regmap_config;
|
|||
|
||||
int bma400_probe(struct device *dev, struct regmap *regmap, const char *name);
|
||||
|
||||
int bma400_remove(struct device *dev);
|
||||
void bma400_remove(struct device *dev);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -828,7 +828,7 @@ int bma400_probe(struct device *dev, struct regmap *regmap, const char *name)
|
|||
}
|
||||
EXPORT_SYMBOL(bma400_probe);
|
||||
|
||||
int bma400_remove(struct device *dev)
|
||||
void bma400_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct bma400_data *data = iio_priv(indio_dev);
|
||||
|
@ -838,12 +838,13 @@ int bma400_remove(struct device *dev)
|
|||
ret = bma400_set_power_mode(data, POWER_MODE_SLEEP);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
if (ret)
|
||||
dev_warn(dev, "Failed to put device into sleep mode (%pe)\n", ERR_PTR(ret));
|
||||
|
||||
regulator_bulk_disable(ARRAY_SIZE(data->regulators),
|
||||
data->regulators);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(bma400_remove);
|
||||
|
||||
|
|
|
@ -29,7 +29,9 @@ static int bma400_i2c_probe(struct i2c_client *client,
|
|||
|
||||
static int bma400_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
return bma400_remove(&client->dev);
|
||||
bma400_remove(&client->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id bma400_i2c_ids[] = {
|
||||
|
|
|
@ -89,7 +89,9 @@ static int bma400_spi_probe(struct spi_device *spi)
|
|||
|
||||
static int bma400_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
return bma400_remove(&spi->dev);
|
||||
bma400_remove(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id bma400_spi_ids[] = {
|
||||
|
|
|
@ -1734,6 +1734,7 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq,
|
|||
ret = iio_triggered_buffer_setup_ext(indio_dev,
|
||||
&iio_pollfunc_store_time,
|
||||
bmc150_accel_trigger_handler,
|
||||
IIO_BUFFER_DIRECTION_IN,
|
||||
&bmc150_accel_buffer_ops,
|
||||
fifo_attrs);
|
||||
if (ret < 0) {
|
||||
|
@ -1799,7 +1800,7 @@ err_disable_regulators:
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(bmc150_accel_core_probe);
|
||||
|
||||
int bmc150_accel_core_remove(struct device *dev)
|
||||
void bmc150_accel_core_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct bmc150_accel_data *data = iio_priv(indio_dev);
|
||||
|
@ -1819,8 +1820,6 @@ int bmc150_accel_core_remove(struct device *dev)
|
|||
|
||||
regulator_bulk_disable(ARRAY_SIZE(data->regulators),
|
||||
data->regulators);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bmc150_accel_core_remove);
|
||||
|
||||
|
|
|
@ -213,7 +213,9 @@ static int bmc150_accel_remove(struct i2c_client *client)
|
|||
{
|
||||
bmc150_acpi_dual_accel_remove(client);
|
||||
|
||||
return bmc150_accel_core_remove(&client->dev);
|
||||
bmc150_accel_core_remove(&client->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id bmc150_accel_acpi_match[] = {
|
||||
|
|
|
@ -37,7 +37,9 @@ static int bmc150_accel_probe(struct spi_device *spi)
|
|||
|
||||
static int bmc150_accel_remove(struct spi_device *spi)
|
||||
{
|
||||
return bmc150_accel_core_remove(&spi->dev);
|
||||
bmc150_accel_core_remove(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id bmc150_accel_acpi_match[] = {
|
||||
|
|
|
@ -88,7 +88,7 @@ struct bmc150_accel_data {
|
|||
int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq,
|
||||
enum bmc150_type type, const char *name,
|
||||
bool block_supported);
|
||||
int bmc150_accel_core_remove(struct device *dev);
|
||||
void bmc150_accel_core_remove(struct device *dev);
|
||||
extern const struct dev_pm_ops bmc150_accel_pm_ops;
|
||||
extern const struct regmap_config bmc150_regmap_conf;
|
||||
|
||||
|
|
|
@ -536,7 +536,7 @@ int bmi088_accel_core_probe(struct device *dev, struct regmap *regmap,
|
|||
EXPORT_SYMBOL_GPL(bmi088_accel_core_probe);
|
||||
|
||||
|
||||
int bmi088_accel_core_remove(struct device *dev)
|
||||
void bmi088_accel_core_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct bmi088_accel_data *data = iio_priv(indio_dev);
|
||||
|
@ -546,8 +546,6 @@ int bmi088_accel_core_remove(struct device *dev)
|
|||
pm_runtime_disable(dev);
|
||||
pm_runtime_set_suspended(dev);
|
||||
bmi088_accel_power_down(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bmi088_accel_core_remove);
|
||||
|
||||
|
|
|
@ -58,7 +58,9 @@ static int bmi088_accel_probe(struct spi_device *spi)
|
|||
|
||||
static int bmi088_accel_remove(struct spi_device *spi)
|
||||
{
|
||||
return bmi088_accel_core_remove(&spi->dev);
|
||||
bmi088_accel_core_remove(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id bmi088_accel_id[] = {
|
||||
|
|
|
@ -13,6 +13,6 @@ extern const struct dev_pm_ops bmi088_accel_pm_ops;
|
|||
|
||||
int bmi088_accel_core_probe(struct device *dev, struct regmap *regmap, int irq,
|
||||
const char *name, bool block_supported);
|
||||
int bmi088_accel_core_remove(struct device *dev);
|
||||
void bmi088_accel_core_remove(struct device *dev);
|
||||
|
||||
#endif /* BMI088_ACCEL_H */
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <linux/regmap.h>
|
||||
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/events.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
@ -30,6 +31,7 @@
|
|||
|
||||
#define FXLS8962AF_INT_STATUS 0x00
|
||||
#define FXLS8962AF_INT_STATUS_SRC_BOOT BIT(0)
|
||||
#define FXLS8962AF_INT_STATUS_SRC_SDCD_OT BIT(4)
|
||||
#define FXLS8962AF_INT_STATUS_SRC_BUF BIT(5)
|
||||
#define FXLS8962AF_INT_STATUS_SRC_DRDY BIT(7)
|
||||
#define FXLS8962AF_TEMP_OUT 0x01
|
||||
|
@ -73,6 +75,7 @@
|
|||
#define FXLS8962AF_ASLP_COUNT_LSB 0x1e
|
||||
|
||||
#define FXLS8962AF_INT_EN 0x20
|
||||
#define FXLS8962AF_INT_EN_SDCD_OT_EN BIT(5)
|
||||
#define FXLS8962AF_INT_EN_BUF_EN BIT(6)
|
||||
#define FXLS8962AF_INT_PIN_SEL 0x21
|
||||
#define FXLS8962AF_INT_PIN_SEL_MASK GENMASK(7, 0)
|
||||
|
@ -96,9 +99,21 @@
|
|||
#define FXLS8962AF_ORIENT_THS_REG 0x2c
|
||||
|
||||
#define FXLS8962AF_SDCD_INT_SRC1 0x2d
|
||||
#define FXLS8962AF_SDCD_INT_SRC1_X_OT BIT(5)
|
||||
#define FXLS8962AF_SDCD_INT_SRC1_X_POL BIT(4)
|
||||
#define FXLS8962AF_SDCD_INT_SRC1_Y_OT BIT(3)
|
||||
#define FXLS8962AF_SDCD_INT_SRC1_Y_POL BIT(2)
|
||||
#define FXLS8962AF_SDCD_INT_SRC1_Z_OT BIT(1)
|
||||
#define FXLS8962AF_SDCD_INT_SRC1_Z_POL BIT(0)
|
||||
#define FXLS8962AF_SDCD_INT_SRC2 0x2e
|
||||
#define FXLS8962AF_SDCD_CONFIG1 0x2f
|
||||
#define FXLS8962AF_SDCD_CONFIG1_Z_OT_EN BIT(3)
|
||||
#define FXLS8962AF_SDCD_CONFIG1_Y_OT_EN BIT(4)
|
||||
#define FXLS8962AF_SDCD_CONFIG1_X_OT_EN BIT(5)
|
||||
#define FXLS8962AF_SDCD_CONFIG1_OT_ELE BIT(7)
|
||||
#define FXLS8962AF_SDCD_CONFIG2 0x30
|
||||
#define FXLS8962AF_SDCD_CONFIG2_SDCD_EN BIT(7)
|
||||
#define FXLS8962AF_SC2_REF_UPDM_AC GENMASK(6, 5)
|
||||
#define FXLS8962AF_SDCD_OT_DBCNT 0x31
|
||||
#define FXLS8962AF_SDCD_WT_DBCNT 0x32
|
||||
#define FXLS8962AF_SDCD_LTHS_LSB 0x33
|
||||
|
@ -151,7 +166,11 @@ struct fxls8962af_data {
|
|||
} scan;
|
||||
int64_t timestamp, old_timestamp; /* Only used in hw fifo mode. */
|
||||
struct iio_mount_matrix orientation;
|
||||
int irq;
|
||||
u8 watermark;
|
||||
u8 enable_event;
|
||||
u16 lower_thres;
|
||||
u16 upper_thres;
|
||||
};
|
||||
|
||||
const struct regmap_config fxls8962af_regmap_conf = {
|
||||
|
@ -238,7 +257,7 @@ static int fxls8962af_get_out(struct fxls8962af_data *data,
|
|||
}
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, chan->address,
|
||||
&raw_val, (chan->scan_type.storagebits / 8));
|
||||
&raw_val, sizeof(data->lower_thres));
|
||||
|
||||
if (!is_active)
|
||||
fxls8962af_power_off(data);
|
||||
|
@ -451,6 +470,15 @@ static int fxls8962af_write_raw(struct iio_dev *indio_dev,
|
|||
}
|
||||
}
|
||||
|
||||
static int fxls8962af_event_setup(struct fxls8962af_data *data, int state)
|
||||
{
|
||||
/* Enable wakeup interrupt */
|
||||
int mask = FXLS8962AF_INT_EN_SDCD_OT_EN;
|
||||
int value = state ? mask : 0;
|
||||
|
||||
return regmap_update_bits(data->regmap, FXLS8962AF_INT_EN, mask, value);
|
||||
}
|
||||
|
||||
static int fxls8962af_set_watermark(struct iio_dev *indio_dev, unsigned val)
|
||||
{
|
||||
struct fxls8962af_data *data = iio_priv(indio_dev);
|
||||
|
@ -463,6 +491,217 @@ static int fxls8962af_set_watermark(struct iio_dev *indio_dev, unsigned val)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int __fxls8962af_set_thresholds(struct fxls8962af_data *data,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_direction dir,
|
||||
int val)
|
||||
{
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_FALLING:
|
||||
data->lower_thres = val;
|
||||
return regmap_bulk_write(data->regmap, FXLS8962AF_SDCD_LTHS_LSB,
|
||||
&data->lower_thres, sizeof(data->lower_thres));
|
||||
case IIO_EV_DIR_RISING:
|
||||
data->upper_thres = val;
|
||||
return regmap_bulk_write(data->regmap, FXLS8962AF_SDCD_UTHS_LSB,
|
||||
&data->upper_thres, sizeof(data->upper_thres));
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int fxls8962af_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 fxls8962af_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (type != IIO_EV_TYPE_THRESH)
|
||||
return -EINVAL;
|
||||
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_FALLING:
|
||||
ret = regmap_bulk_read(data->regmap, FXLS8962AF_SDCD_LTHS_LSB,
|
||||
&data->lower_thres, sizeof(data->lower_thres));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = sign_extend32(data->lower_thres, chan->scan_type.realbits - 1);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_EV_DIR_RISING:
|
||||
ret = regmap_bulk_read(data->regmap, FXLS8962AF_SDCD_UTHS_LSB,
|
||||
&data->upper_thres, sizeof(data->upper_thres));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = sign_extend32(data->upper_thres, chan->scan_type.realbits - 1);
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int fxls8962af_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 fxls8962af_data *data = iio_priv(indio_dev);
|
||||
int ret, val_masked;
|
||||
|
||||
if (type != IIO_EV_TYPE_THRESH)
|
||||
return -EINVAL;
|
||||
|
||||
if (val < -2048 || val > 2047)
|
||||
return -EINVAL;
|
||||
|
||||
if (data->enable_event)
|
||||
return -EBUSY;
|
||||
|
||||
val_masked = val & GENMASK(11, 0);
|
||||
if (fxls8962af_is_active(data)) {
|
||||
ret = fxls8962af_standby(data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = __fxls8962af_set_thresholds(data, chan, dir, val_masked);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return fxls8962af_active(data);
|
||||
} else {
|
||||
return __fxls8962af_set_thresholds(data, chan, dir, val_masked);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
fxls8962af_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 fxls8962af_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (type != IIO_EV_TYPE_THRESH)
|
||||
return -EINVAL;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
return !!(FXLS8962AF_SDCD_CONFIG1_X_OT_EN & data->enable_event);
|
||||
case IIO_MOD_Y:
|
||||
return !!(FXLS8962AF_SDCD_CONFIG1_Y_OT_EN & data->enable_event);
|
||||
case IIO_MOD_Z:
|
||||
return !!(FXLS8962AF_SDCD_CONFIG1_Z_OT_EN & data->enable_event);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
fxls8962af_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 fxls8962af_data *data = iio_priv(indio_dev);
|
||||
u8 enable_event, enable_bits;
|
||||
int ret, value;
|
||||
|
||||
if (type != IIO_EV_TYPE_THRESH)
|
||||
return -EINVAL;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
enable_bits = FXLS8962AF_SDCD_CONFIG1_X_OT_EN;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
enable_bits = FXLS8962AF_SDCD_CONFIG1_Y_OT_EN;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
enable_bits = FXLS8962AF_SDCD_CONFIG1_Z_OT_EN;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (state)
|
||||
enable_event = data->enable_event | enable_bits;
|
||||
else
|
||||
enable_event = data->enable_event & ~enable_bits;
|
||||
|
||||
if (data->enable_event == enable_event)
|
||||
return 0;
|
||||
|
||||
ret = fxls8962af_standby(data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable events */
|
||||
value = enable_event | FXLS8962AF_SDCD_CONFIG1_OT_ELE;
|
||||
ret = regmap_write(data->regmap, FXLS8962AF_SDCD_CONFIG1, value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Enable update of SDCD_REF_X/Y/Z values with the current decimated and
|
||||
* trimmed X/Y/Z acceleration input data. This allows for acceleration
|
||||
* slope detection with Data(n) to Data(n–1) always used as the input
|
||||
* to the window comparator.
|
||||
*/
|
||||
value = enable_event ?
|
||||
FXLS8962AF_SDCD_CONFIG2_SDCD_EN | FXLS8962AF_SC2_REF_UPDM_AC :
|
||||
0x00;
|
||||
ret = regmap_write(data->regmap, FXLS8962AF_SDCD_CONFIG2, value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = fxls8962af_event_setup(data, state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data->enable_event = enable_event;
|
||||
|
||||
if (data->enable_event) {
|
||||
fxls8962af_active(data);
|
||||
ret = fxls8962af_power_on(data);
|
||||
} else {
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Not in buffered mode so disable power */
|
||||
ret = fxls8962af_power_off(data);
|
||||
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_event_spec fxls8962af_event[] = {
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_EITHER,
|
||||
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
|
||||
},
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_FALLING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE),
|
||||
},
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_RISING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_VALUE),
|
||||
},
|
||||
};
|
||||
|
||||
#define FXLS8962AF_CHANNEL(axis, reg, idx) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.address = reg, \
|
||||
|
@ -481,6 +720,8 @@ static int fxls8962af_set_watermark(struct iio_dev *indio_dev, unsigned val)
|
|||
.shift = 4, \
|
||||
.endianness = IIO_BE, \
|
||||
}, \
|
||||
.event_spec = fxls8962af_event, \
|
||||
.num_event_specs = ARRAY_SIZE(fxls8962af_event), \
|
||||
}
|
||||
|
||||
#define FXLS8962AF_TEMP_CHANNEL { \
|
||||
|
@ -522,6 +763,10 @@ static const struct iio_info fxls8962af_info = {
|
|||
.read_raw = &fxls8962af_read_raw,
|
||||
.write_raw = &fxls8962af_write_raw,
|
||||
.write_raw_get_fmt = fxls8962af_write_raw_get_fmt,
|
||||
.read_event_value = fxls8962af_read_event,
|
||||
.write_event_value = fxls8962af_write_event,
|
||||
.read_event_config = fxls8962af_read_event_config,
|
||||
.write_event_config = fxls8962af_write_event_config,
|
||||
.read_avail = fxls8962af_read_avail,
|
||||
.hwfifo_set_watermark = fxls8962af_set_watermark,
|
||||
};
|
||||
|
@ -605,7 +850,8 @@ static int fxls8962af_buffer_predisable(struct iio_dev *indio_dev)
|
|||
|
||||
ret = __fxls8962af_fifo_set_mode(data, false);
|
||||
|
||||
fxls8962af_active(data);
|
||||
if (data->enable_event)
|
||||
fxls8962af_active(data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -614,7 +860,10 @@ static int fxls8962af_buffer_postdisable(struct iio_dev *indio_dev)
|
|||
{
|
||||
struct fxls8962af_data *data = iio_priv(indio_dev);
|
||||
|
||||
return fxls8962af_power_off(data);
|
||||
if (!data->enable_event)
|
||||
fxls8962af_power_off(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops fxls8962af_buffer_ops = {
|
||||
|
@ -725,6 +974,45 @@ static int fxls8962af_fifo_flush(struct iio_dev *indio_dev)
|
|||
return count;
|
||||
}
|
||||
|
||||
static int fxls8962af_event_interrupt(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct fxls8962af_data *data = iio_priv(indio_dev);
|
||||
s64 ts = iio_get_time_ns(indio_dev);
|
||||
unsigned int reg;
|
||||
u64 ev_code;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(data->regmap, FXLS8962AF_SDCD_INT_SRC1, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (reg & FXLS8962AF_SDCD_INT_SRC1_X_OT) {
|
||||
ev_code = reg & FXLS8962AF_SDCD_INT_SRC1_X_POL ?
|
||||
IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
|
||||
iio_push_event(indio_dev,
|
||||
IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X,
|
||||
IIO_EV_TYPE_THRESH, ev_code), ts);
|
||||
}
|
||||
|
||||
if (reg & FXLS8962AF_SDCD_INT_SRC1_Y_OT) {
|
||||
ev_code = reg & FXLS8962AF_SDCD_INT_SRC1_Y_POL ?
|
||||
IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
|
||||
iio_push_event(indio_dev,
|
||||
IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X,
|
||||
IIO_EV_TYPE_THRESH, ev_code), ts);
|
||||
}
|
||||
|
||||
if (reg & FXLS8962AF_SDCD_INT_SRC1_Z_OT) {
|
||||
ev_code = reg & FXLS8962AF_SDCD_INT_SRC1_Z_POL ?
|
||||
IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING;
|
||||
iio_push_event(indio_dev,
|
||||
IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X,
|
||||
IIO_EV_TYPE_THRESH, ev_code), ts);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t fxls8962af_interrupt(int irq, void *p)
|
||||
{
|
||||
struct iio_dev *indio_dev = p;
|
||||
|
@ -744,6 +1032,14 @@ static irqreturn_t fxls8962af_interrupt(int irq, void *p)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (reg & FXLS8962AF_INT_STATUS_SRC_SDCD_OT) {
|
||||
ret = fxls8962af_event_interrupt(indio_dev);
|
||||
if (ret < 0)
|
||||
return IRQ_NONE;
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
|
@ -861,6 +1157,7 @@ int fxls8962af_core_probe(struct device *dev, struct regmap *regmap, int irq)
|
|||
data = iio_priv(indio_dev);
|
||||
dev_set_drvdata(dev, indio_dev);
|
||||
data->regmap = regmap;
|
||||
data->irq = irq;
|
||||
|
||||
ret = iio_read_mount_matrix(dev, &data->orientation);
|
||||
if (ret)
|
||||
|
@ -930,6 +1227,9 @@ int fxls8962af_core_probe(struct device *dev, struct regmap *regmap, int irq)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (device_property_read_bool(dev, "wakeup-source"))
|
||||
device_init_wakeup(dev, true);
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fxls8962af_core_probe);
|
||||
|
@ -955,9 +1255,46 @@ static int __maybe_unused fxls8962af_runtime_resume(struct device *dev)
|
|||
return fxls8962af_active(data);
|
||||
}
|
||||
|
||||
static int __maybe_unused fxls8962af_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct fxls8962af_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (device_may_wakeup(dev) && data->enable_event) {
|
||||
enable_irq_wake(data->irq);
|
||||
|
||||
/*
|
||||
* Disable buffer, as the buffer is so small the device will wake
|
||||
* almost immediately.
|
||||
*/
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
fxls8962af_buffer_predisable(indio_dev);
|
||||
} else {
|
||||
fxls8962af_runtime_suspend(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused fxls8962af_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct fxls8962af_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (device_may_wakeup(dev) && data->enable_event) {
|
||||
disable_irq_wake(data->irq);
|
||||
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
fxls8962af_buffer_postenable(indio_dev);
|
||||
} else {
|
||||
fxls8962af_runtime_resume(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct dev_pm_ops fxls8962af_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume)
|
||||
SET_SYSTEM_SLEEP_PM_OPS(fxls8962af_suspend, fxls8962af_resume)
|
||||
SET_RUNTIME_PM_OPS(fxls8962af_runtime_suspend,
|
||||
fxls8962af_runtime_resume, NULL)
|
||||
};
|
||||
|
|
|
@ -34,7 +34,9 @@ static int kxsd9_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
static int kxsd9_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
return kxsd9_common_remove(&client->dev);
|
||||
kxsd9_common_remove(&client->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id kxsd9_of_match[] = {
|
||||
|
|
|
@ -34,7 +34,9 @@ static int kxsd9_spi_probe(struct spi_device *spi)
|
|||
|
||||
static int kxsd9_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
return kxsd9_common_remove(&spi->dev);
|
||||
kxsd9_common_remove(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id kxsd9_spi_id[] = {
|
||||
|
|
|
@ -478,7 +478,7 @@ err_power_down:
|
|||
}
|
||||
EXPORT_SYMBOL(kxsd9_common_probe);
|
||||
|
||||
int kxsd9_common_remove(struct device *dev)
|
||||
void kxsd9_common_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct kxsd9_state *st = iio_priv(indio_dev);
|
||||
|
@ -489,8 +489,6 @@ int kxsd9_common_remove(struct device *dev)
|
|||
pm_runtime_put_noidle(dev);
|
||||
pm_runtime_disable(dev);
|
||||
kxsd9_power_down(st);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(kxsd9_common_remove);
|
||||
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
int kxsd9_common_probe(struct device *dev,
|
||||
struct regmap *map,
|
||||
const char *name);
|
||||
int kxsd9_common_remove(struct device *dev);
|
||||
void kxsd9_common_remove(struct device *dev);
|
||||
|
||||
extern const struct dev_pm_ops kxsd9_dev_pm_ops;
|
||||
|
|
|
@ -11,6 +11,6 @@ extern const struct regmap_config mma7455_core_regmap;
|
|||
|
||||
int mma7455_core_probe(struct device *dev, struct regmap *regmap,
|
||||
const char *name);
|
||||
int mma7455_core_remove(struct device *dev);
|
||||
void mma7455_core_remove(struct device *dev);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -294,7 +294,7 @@ int mma7455_core_probe(struct device *dev, struct regmap *regmap,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(mma7455_core_probe);
|
||||
|
||||
int mma7455_core_remove(struct device *dev)
|
||||
void mma7455_core_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct mma7455_data *mma7455 = iio_priv(indio_dev);
|
||||
|
@ -304,8 +304,6 @@ int mma7455_core_remove(struct device *dev)
|
|||
|
||||
regmap_write(mma7455->regmap, MMA7455_REG_MCTL,
|
||||
MMA7455_MCTL_MODE_STANDBY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mma7455_core_remove);
|
||||
|
||||
|
|
|
@ -28,7 +28,9 @@ static int mma7455_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
static int mma7455_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
return mma7455_core_remove(&i2c->dev);
|
||||
mma7455_core_remove(&i2c->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id mma7455_i2c_ids[] = {
|
||||
|
|
|
@ -24,7 +24,9 @@ static int mma7455_spi_probe(struct spi_device *spi)
|
|||
|
||||
static int mma7455_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
return mma7455_core_remove(&spi->dev);
|
||||
mma7455_core_remove(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id mma7455_spi_ids[] = {
|
||||
|
|
|
@ -254,7 +254,7 @@ static const struct of_device_id mma7660_of_match[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(of, mma7660_of_match);
|
||||
|
||||
static const struct acpi_device_id mma7660_acpi_id[] = {
|
||||
static const struct acpi_device_id __maybe_unused mma7660_acpi_id[] = {
|
||||
{"MMA7660", 0},
|
||||
{}
|
||||
};
|
||||
|
|
|
@ -1210,7 +1210,7 @@ static int apply_acpi_orientation(struct iio_dev *indio_dev)
|
|||
};
|
||||
|
||||
|
||||
adev = ACPI_COMPANION(adata->dev);
|
||||
adev = ACPI_COMPANION(indio_dev->dev.parent);
|
||||
if (!adev)
|
||||
return 0;
|
||||
|
||||
|
@ -1334,7 +1334,8 @@ EXPORT_SYMBOL(st_accel_get_settings);
|
|||
int st_accel_common_probe(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct st_sensor_data *adata = iio_priv(indio_dev);
|
||||
struct st_sensors_platform_data *pdata = dev_get_platdata(adata->dev);
|
||||
struct device *parent = indio_dev->dev.parent;
|
||||
struct st_sensors_platform_data *pdata = dev_get_platdata(parent);
|
||||
int err;
|
||||
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
@ -1354,7 +1355,7 @@ int st_accel_common_probe(struct iio_dev *indio_dev)
|
|||
*/
|
||||
err = apply_acpi_orientation(indio_dev);
|
||||
if (err) {
|
||||
err = iio_read_mount_matrix(adata->dev, &adata->mount_matrix);
|
||||
err = iio_read_mount_matrix(parent, &adata->mount_matrix);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
@ -1380,32 +1381,10 @@ int st_accel_common_probe(struct iio_dev *indio_dev)
|
|||
return err;
|
||||
}
|
||||
|
||||
err = iio_device_register(indio_dev);
|
||||
if (err)
|
||||
goto st_accel_device_register_error;
|
||||
|
||||
dev_info(&indio_dev->dev, "registered accelerometer %s\n",
|
||||
indio_dev->name);
|
||||
|
||||
return 0;
|
||||
|
||||
st_accel_device_register_error:
|
||||
if (adata->irq > 0)
|
||||
st_sensors_deallocate_trigger(indio_dev);
|
||||
return err;
|
||||
return devm_iio_device_register(parent, indio_dev);
|
||||
}
|
||||
EXPORT_SYMBOL(st_accel_common_probe);
|
||||
|
||||
void st_accel_common_remove(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct st_sensor_data *adata = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
if (adata->irq > 0)
|
||||
st_sensors_deallocate_trigger(indio_dev);
|
||||
}
|
||||
EXPORT_SYMBOL(st_accel_common_remove);
|
||||
|
||||
MODULE_AUTHOR("Denis Ciocca <denis.ciocca@st.com>");
|
||||
MODULE_DESCRIPTION("STMicroelectronics accelerometers driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
|
@ -177,27 +177,7 @@ static int st_accel_i2c_probe(struct i2c_client *client)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = st_accel_common_probe(indio_dev);
|
||||
if (ret < 0)
|
||||
goto st_accel_power_off;
|
||||
|
||||
return 0;
|
||||
|
||||
st_accel_power_off:
|
||||
st_sensors_power_disable(indio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int st_accel_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
st_sensors_power_disable(indio_dev);
|
||||
|
||||
st_accel_common_remove(indio_dev);
|
||||
|
||||
return 0;
|
||||
return st_accel_common_probe(indio_dev);
|
||||
}
|
||||
|
||||
static struct i2c_driver st_accel_driver = {
|
||||
|
@ -207,7 +187,6 @@ static struct i2c_driver st_accel_driver = {
|
|||
.acpi_match_table = ACPI_PTR(st_accel_acpi_match),
|
||||
},
|
||||
.probe_new = st_accel_i2c_probe,
|
||||
.remove = st_accel_i2c_remove,
|
||||
.id_table = st_accel_id_table,
|
||||
};
|
||||
module_i2c_driver(st_accel_driver);
|
||||
|
|
|
@ -127,27 +127,7 @@ static int st_accel_spi_probe(struct spi_device *spi)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
err = st_accel_common_probe(indio_dev);
|
||||
if (err < 0)
|
||||
goto st_accel_power_off;
|
||||
|
||||
return 0;
|
||||
|
||||
st_accel_power_off:
|
||||
st_sensors_power_disable(indio_dev);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int st_accel_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev = spi_get_drvdata(spi);
|
||||
|
||||
st_sensors_power_disable(indio_dev);
|
||||
|
||||
st_accel_common_remove(indio_dev);
|
||||
|
||||
return 0;
|
||||
return st_accel_common_probe(indio_dev);
|
||||
}
|
||||
|
||||
static const struct spi_device_id st_accel_id_table[] = {
|
||||
|
@ -177,7 +157,6 @@ static struct spi_driver st_accel_driver = {
|
|||
.of_match_table = st_accel_of_match,
|
||||
},
|
||||
.probe = st_accel_spi_probe,
|
||||
.remove = st_accel_spi_remove,
|
||||
.id_table = st_accel_id_table,
|
||||
};
|
||||
module_spi_driver(st_accel_driver);
|
||||
|
|
|
@ -354,7 +354,7 @@ config BCM_IPROC_ADC
|
|||
|
||||
config BERLIN2_ADC
|
||||
tristate "Marvell Berlin2 ADC driver"
|
||||
depends on ARCH_BERLIN
|
||||
depends on ARCH_BERLIN || COMPILE_TEST
|
||||
help
|
||||
Marvell Berlin2 ADC driver. This ADC has 8 channels, with one used for
|
||||
temperature measurement.
|
||||
|
@ -430,9 +430,9 @@ config EXYNOS_ADC
|
|||
depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || ARCH_S5PV210 || (OF && COMPILE_TEST)
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Core support for the ADC block found in the Samsung EXYNOS series
|
||||
of SoCs for drivers such as the touchscreen and hwmon to use to share
|
||||
this resource.
|
||||
Driver for the ADC block found in the Samsung S3C (S3C2410, S3C2416,
|
||||
S3C2440, S3C2443, S3C6410), S5Pv210 and Exynos SoCs.
|
||||
Choose Y here only if you build for such Samsung SoC.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called exynos_adc.
|
||||
|
@ -530,6 +530,16 @@ config IMX7D_ADC
|
|||
This driver can also be built as a module. If so, the module will be
|
||||
called imx7d_adc.
|
||||
|
||||
config IMX8QXP_ADC
|
||||
tristate "NXP IMX8QXP ADC driver"
|
||||
depends on ARCH_MXC_ARM64 || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Say yes here to build support for IMX8QXP ADC.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called imx8qxp-adc.
|
||||
|
||||
config LP8788_ADC
|
||||
tristate "LP8788 ADC driver"
|
||||
depends on MFD_LP8788
|
||||
|
|
|
@ -46,6 +46,7 @@ obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o
|
|||
obj-$(CONFIG_HI8435) += hi8435.o
|
||||
obj-$(CONFIG_HX711) += hx711.o
|
||||
obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o
|
||||
obj-$(CONFIG_IMX8QXP_ADC) += imx8qxp-adc.o
|
||||
obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o
|
||||
obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o
|
||||
obj-$(CONFIG_INTEL_MRFLD_ADC) += intel_mrfld_adc.o
|
||||
|
|
|
@ -460,6 +460,11 @@ static const struct iio_info ad7291_info = {
|
|||
.write_event_value = &ad7291_write_event_value,
|
||||
};
|
||||
|
||||
static void ad7291_reg_disable(void *reg)
|
||||
{
|
||||
regulator_disable(reg);
|
||||
}
|
||||
|
||||
static int ad7291_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
|
@ -473,8 +478,6 @@ static int ad7291_probe(struct i2c_client *client,
|
|||
chip = iio_priv(indio_dev);
|
||||
|
||||
mutex_init(&chip->state_lock);
|
||||
/* this is only used for device removal purposes */
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
chip->client = client;
|
||||
|
||||
|
@ -495,6 +498,11 @@ static int ad7291_probe(struct i2c_client *client,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(&client->dev, ad7291_reg_disable,
|
||||
chip->reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
chip->command |= AD7291_EXT_REF;
|
||||
}
|
||||
|
||||
|
@ -506,58 +514,25 @@ static int ad7291_probe(struct i2c_client *client,
|
|||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = ad7291_i2c_write(chip, AD7291_COMMAND, AD7291_RESET);
|
||||
if (ret) {
|
||||
ret = -EIO;
|
||||
goto error_disable_reg;
|
||||
}
|
||||
if (ret)
|
||||
return -EIO;
|
||||
|
||||
ret = ad7291_i2c_write(chip, AD7291_COMMAND, chip->command);
|
||||
if (ret) {
|
||||
ret = -EIO;
|
||||
goto error_disable_reg;
|
||||
}
|
||||
if (ret)
|
||||
return -EIO;
|
||||
|
||||
if (client->irq > 0) {
|
||||
ret = request_threaded_irq(client->irq,
|
||||
NULL,
|
||||
&ad7291_event_handler,
|
||||
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
||||
id->name,
|
||||
indio_dev);
|
||||
ret = devm_request_threaded_irq(&client->dev, client->irq,
|
||||
NULL,
|
||||
&ad7291_event_handler,
|
||||
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
||||
id->name,
|
||||
indio_dev);
|
||||
if (ret)
|
||||
goto error_disable_reg;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret)
|
||||
goto error_unreg_irq;
|
||||
|
||||
return 0;
|
||||
|
||||
error_unreg_irq:
|
||||
if (client->irq)
|
||||
free_irq(client->irq, indio_dev);
|
||||
error_disable_reg:
|
||||
if (chip->reg)
|
||||
regulator_disable(chip->reg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7291_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct ad7291_chip_info *chip = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
if (client->irq)
|
||||
free_irq(client->irq, indio_dev);
|
||||
|
||||
if (chip->reg)
|
||||
regulator_disable(chip->reg);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(&client->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ad7291_id[] = {
|
||||
|
@ -579,7 +554,6 @@ static struct i2c_driver ad7291_driver = {
|
|||
.of_match_table = ad7291_of_match,
|
||||
},
|
||||
.probe = ad7291_probe,
|
||||
.remove = ad7291_remove,
|
||||
.id_table = ad7291_id,
|
||||
};
|
||||
module_i2c_driver(ad7291_driver);
|
||||
|
|
|
@ -11,12 +11,41 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
#define AD7949_MASK_CHANNEL_SEL GENMASK(9, 7)
|
||||
#define AD7949_MASK_TOTAL GENMASK(13, 0)
|
||||
#define AD7949_OFFSET_CHANNEL_SEL 7
|
||||
#define AD7949_CFG_READ_BACK 0x1
|
||||
#define AD7949_CFG_REG_SIZE_BITS 14
|
||||
#define AD7949_CFG_MASK_TOTAL GENMASK(13, 0)
|
||||
|
||||
/* CFG: Configuration Update */
|
||||
#define AD7949_CFG_MASK_OVERWRITE BIT(13)
|
||||
|
||||
/* INCC: Input Channel Configuration */
|
||||
#define AD7949_CFG_MASK_INCC GENMASK(12, 10)
|
||||
#define AD7949_CFG_VAL_INCC_UNIPOLAR_GND 7
|
||||
#define AD7949_CFG_VAL_INCC_UNIPOLAR_COMM 6
|
||||
#define AD7949_CFG_VAL_INCC_UNIPOLAR_DIFF 4
|
||||
#define AD7949_CFG_VAL_INCC_TEMP 3
|
||||
#define AD7949_CFG_VAL_INCC_BIPOLAR 2
|
||||
#define AD7949_CFG_VAL_INCC_BIPOLAR_DIFF 0
|
||||
|
||||
/* INX: Input channel Selection in a binary fashion */
|
||||
#define AD7949_CFG_MASK_INX GENMASK(9, 7)
|
||||
|
||||
/* BW: select bandwidth for low-pass filter. Full or Quarter */
|
||||
#define AD7949_CFG_MASK_BW_FULL BIT(6)
|
||||
|
||||
/* REF: reference/buffer selection */
|
||||
#define AD7949_CFG_MASK_REF GENMASK(5, 3)
|
||||
#define AD7949_CFG_VAL_REF_EXT_TEMP_BUF 3
|
||||
#define AD7949_CFG_VAL_REF_EXT_TEMP 2
|
||||
#define AD7949_CFG_VAL_REF_INT_4096 1
|
||||
#define AD7949_CFG_VAL_REF_INT_2500 0
|
||||
#define AD7949_CFG_VAL_REF_EXTERNAL BIT(1)
|
||||
|
||||
/* SEQ: channel sequencer. Allows for scanning channels */
|
||||
#define AD7949_CFG_MASK_SEQ GENMASK(2, 1)
|
||||
|
||||
/* RB: Read back the CFG register */
|
||||
#define AD7949_CFG_MASK_RBN BIT(0)
|
||||
|
||||
enum {
|
||||
ID_AD7949 = 0,
|
||||
|
@ -41,41 +70,51 @@ static const struct ad7949_adc_spec ad7949_adc_spec[] = {
|
|||
* @vref: regulator generating Vref
|
||||
* @indio_dev: reference to iio structure
|
||||
* @spi: reference to spi structure
|
||||
* @refsel: reference selection
|
||||
* @resolution: resolution of the chip
|
||||
* @cfg: copy of the configuration register
|
||||
* @current_channel: current channel in use
|
||||
* @buffer: buffer to send / receive data to / from device
|
||||
* @buf8b: be16 buffer to exchange data with the device in 8-bit transfers
|
||||
*/
|
||||
struct ad7949_adc_chip {
|
||||
struct mutex lock;
|
||||
struct regulator *vref;
|
||||
struct iio_dev *indio_dev;
|
||||
struct spi_device *spi;
|
||||
u32 refsel;
|
||||
u8 resolution;
|
||||
u16 cfg;
|
||||
unsigned int current_channel;
|
||||
u16 buffer ____cacheline_aligned;
|
||||
__be16 buf8b;
|
||||
};
|
||||
|
||||
static int ad7949_spi_write_cfg(struct ad7949_adc_chip *ad7949_adc, u16 val,
|
||||
u16 mask)
|
||||
{
|
||||
int ret;
|
||||
int bits_per_word = ad7949_adc->resolution;
|
||||
int shift = bits_per_word - AD7949_CFG_REG_SIZE_BITS;
|
||||
struct spi_message msg;
|
||||
struct spi_transfer tx[] = {
|
||||
{
|
||||
.tx_buf = &ad7949_adc->buffer,
|
||||
.len = 2,
|
||||
.bits_per_word = bits_per_word,
|
||||
},
|
||||
};
|
||||
|
||||
ad7949_adc->cfg = (val & mask) | (ad7949_adc->cfg & ~mask);
|
||||
ad7949_adc->buffer = ad7949_adc->cfg << shift;
|
||||
spi_message_init_with_transfers(&msg, tx, 1);
|
||||
ret = spi_sync(ad7949_adc->spi, &msg);
|
||||
|
||||
switch (ad7949_adc->spi->bits_per_word) {
|
||||
case 16:
|
||||
ad7949_adc->buffer = ad7949_adc->cfg << 2;
|
||||
ret = spi_write(ad7949_adc->spi, &ad7949_adc->buffer, 2);
|
||||
break;
|
||||
case 14:
|
||||
ad7949_adc->buffer = ad7949_adc->cfg;
|
||||
ret = spi_write(ad7949_adc->spi, &ad7949_adc->buffer, 2);
|
||||
break;
|
||||
case 8:
|
||||
/* Here, type is big endian as it must be sent in two transfers */
|
||||
ad7949_adc->buf8b = cpu_to_be16(ad7949_adc->cfg << 2);
|
||||
ret = spi_write(ad7949_adc->spi, &ad7949_adc->buf8b, 2);
|
||||
break;
|
||||
default:
|
||||
dev_err(&ad7949_adc->indio_dev->dev, "unsupported BPW\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* This delay is to avoid a new request before the required time to
|
||||
|
@ -90,16 +129,6 @@ static int ad7949_spi_read_channel(struct ad7949_adc_chip *ad7949_adc, int *val,
|
|||
{
|
||||
int ret;
|
||||
int i;
|
||||
int bits_per_word = ad7949_adc->resolution;
|
||||
int mask = GENMASK(ad7949_adc->resolution - 1, 0);
|
||||
struct spi_message msg;
|
||||
struct spi_transfer tx[] = {
|
||||
{
|
||||
.rx_buf = &ad7949_adc->buffer,
|
||||
.len = 2,
|
||||
.bits_per_word = bits_per_word,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* 1: write CFG for sample N and read old data (sample N-2)
|
||||
|
@ -109,8 +138,8 @@ static int ad7949_spi_read_channel(struct ad7949_adc_chip *ad7949_adc, int *val,
|
|||
*/
|
||||
for (i = 0; i < 2; i++) {
|
||||
ret = ad7949_spi_write_cfg(ad7949_adc,
|
||||
channel << AD7949_OFFSET_CHANNEL_SEL,
|
||||
AD7949_MASK_CHANNEL_SEL);
|
||||
FIELD_PREP(AD7949_CFG_MASK_INX, channel),
|
||||
AD7949_CFG_MASK_INX);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (channel == ad7949_adc->current_channel)
|
||||
|
@ -118,9 +147,11 @@ static int ad7949_spi_read_channel(struct ad7949_adc_chip *ad7949_adc, int *val,
|
|||
}
|
||||
|
||||
/* 3: write something and read actual data */
|
||||
ad7949_adc->buffer = 0;
|
||||
spi_message_init_with_transfers(&msg, tx, 1);
|
||||
ret = spi_sync(ad7949_adc->spi, &msg);
|
||||
if (ad7949_adc->spi->bits_per_word == 8)
|
||||
ret = spi_read(ad7949_adc->spi, &ad7949_adc->buf8b, 2);
|
||||
else
|
||||
ret = spi_read(ad7949_adc->spi, &ad7949_adc->buffer, 2);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -132,7 +163,25 @@ static int ad7949_spi_read_channel(struct ad7949_adc_chip *ad7949_adc, int *val,
|
|||
|
||||
ad7949_adc->current_channel = channel;
|
||||
|
||||
*val = ad7949_adc->buffer & mask;
|
||||
switch (ad7949_adc->spi->bits_per_word) {
|
||||
case 16:
|
||||
*val = ad7949_adc->buffer;
|
||||
/* Shift-out padding bits */
|
||||
*val >>= 16 - ad7949_adc->resolution;
|
||||
break;
|
||||
case 14:
|
||||
*val = ad7949_adc->buffer & GENMASK(13, 0);
|
||||
break;
|
||||
case 8:
|
||||
/* Here, type is big endian as data was sent in two transfers */
|
||||
*val = be16_to_cpu(ad7949_adc->buf8b);
|
||||
/* Shift-out padding bits */
|
||||
*val >>= 16 - ad7949_adc->resolution;
|
||||
break;
|
||||
default:
|
||||
dev_err(&ad7949_adc->indio_dev->dev, "unsupported BPW\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -178,12 +227,26 @@ static int ad7949_spi_read_raw(struct iio_dev *indio_dev,
|
|||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = regulator_get_voltage(ad7949_adc->vref);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
switch (ad7949_adc->refsel) {
|
||||
case AD7949_CFG_VAL_REF_INT_2500:
|
||||
*val = 2500;
|
||||
break;
|
||||
case AD7949_CFG_VAL_REF_INT_4096:
|
||||
*val = 4096;
|
||||
break;
|
||||
case AD7949_CFG_VAL_REF_EXT_TEMP:
|
||||
case AD7949_CFG_VAL_REF_EXT_TEMP_BUF:
|
||||
ret = regulator_get_voltage(ad7949_adc->vref);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret / 5000;
|
||||
return IIO_VAL_INT;
|
||||
/* convert value back to mV */
|
||||
*val = ret / 1000;
|
||||
break;
|
||||
}
|
||||
|
||||
*val2 = (1 << ad7949_adc->resolution) - 1;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
|
@ -199,8 +262,8 @@ static int ad7949_spi_reg_access(struct iio_dev *indio_dev,
|
|||
if (readval)
|
||||
*readval = ad7949_adc->cfg;
|
||||
else
|
||||
ret = ad7949_spi_write_cfg(ad7949_adc,
|
||||
writeval & AD7949_MASK_TOTAL, AD7949_MASK_TOTAL);
|
||||
ret = ad7949_spi_write_cfg(ad7949_adc, writeval,
|
||||
AD7949_CFG_MASK_TOTAL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -214,10 +277,19 @@ static int ad7949_spi_init(struct ad7949_adc_chip *ad7949_adc)
|
|||
{
|
||||
int ret;
|
||||
int val;
|
||||
u16 cfg;
|
||||
|
||||
/* Sequencer disabled, CFG readback disabled, IN0 as default channel */
|
||||
ad7949_adc->current_channel = 0;
|
||||
ret = ad7949_spi_write_cfg(ad7949_adc, 0x3C79, AD7949_MASK_TOTAL);
|
||||
|
||||
cfg = FIELD_PREP(AD7949_CFG_MASK_OVERWRITE, 1) |
|
||||
FIELD_PREP(AD7949_CFG_MASK_INCC, AD7949_CFG_VAL_INCC_UNIPOLAR_GND) |
|
||||
FIELD_PREP(AD7949_CFG_MASK_INX, ad7949_adc->current_channel) |
|
||||
FIELD_PREP(AD7949_CFG_MASK_BW_FULL, 1) |
|
||||
FIELD_PREP(AD7949_CFG_MASK_REF, ad7949_adc->refsel) |
|
||||
FIELD_PREP(AD7949_CFG_MASK_SEQ, 0x0) |
|
||||
FIELD_PREP(AD7949_CFG_MASK_RBN, 1);
|
||||
|
||||
ret = ad7949_spi_write_cfg(ad7949_adc, cfg, AD7949_CFG_MASK_TOTAL);
|
||||
|
||||
/*
|
||||
* Do two dummy conversions to apply the first configuration setting.
|
||||
|
@ -229,12 +301,19 @@ static int ad7949_spi_init(struct ad7949_adc_chip *ad7949_adc)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void ad7949_disable_reg(void *reg)
|
||||
{
|
||||
regulator_disable(reg);
|
||||
}
|
||||
|
||||
static int ad7949_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
u32 spi_ctrl_mask = spi->controller->bits_per_word_mask;
|
||||
struct device *dev = &spi->dev;
|
||||
const struct ad7949_adc_spec *spec;
|
||||
struct ad7949_adc_chip *ad7949_adc;
|
||||
struct iio_dev *indio_dev;
|
||||
u32 tmp;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*ad7949_adc));
|
||||
|
@ -257,16 +336,64 @@ static int ad7949_spi_probe(struct spi_device *spi)
|
|||
indio_dev->num_channels = spec->num_channels;
|
||||
ad7949_adc->resolution = spec->resolution;
|
||||
|
||||
ad7949_adc->vref = devm_regulator_get(dev, "vref");
|
||||
if (IS_ERR(ad7949_adc->vref)) {
|
||||
dev_err(dev, "fail to request regulator\n");
|
||||
return PTR_ERR(ad7949_adc->vref);
|
||||
/* Set SPI bits per word */
|
||||
if (spi_ctrl_mask & SPI_BPW_MASK(ad7949_adc->resolution)) {
|
||||
spi->bits_per_word = ad7949_adc->resolution;
|
||||
} else if (spi_ctrl_mask == SPI_BPW_MASK(16)) {
|
||||
spi->bits_per_word = 16;
|
||||
} else if (spi_ctrl_mask == SPI_BPW_MASK(8)) {
|
||||
spi->bits_per_word = 8;
|
||||
} else {
|
||||
dev_err(dev, "unable to find common BPW with spi controller\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = regulator_enable(ad7949_adc->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "fail to enable regulator\n");
|
||||
return ret;
|
||||
/* Setup internal voltage reference */
|
||||
tmp = 4096000;
|
||||
device_property_read_u32(dev, "adi,internal-ref-microvolt", &tmp);
|
||||
|
||||
switch (tmp) {
|
||||
case 2500000:
|
||||
ad7949_adc->refsel = AD7949_CFG_VAL_REF_INT_2500;
|
||||
break;
|
||||
case 4096000:
|
||||
ad7949_adc->refsel = AD7949_CFG_VAL_REF_INT_4096;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "unsupported internal voltage reference\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Setup external voltage reference, buffered? */
|
||||
ad7949_adc->vref = devm_regulator_get_optional(dev, "vrefin");
|
||||
if (IS_ERR(ad7949_adc->vref)) {
|
||||
ret = PTR_ERR(ad7949_adc->vref);
|
||||
if (ret != -ENODEV)
|
||||
return ret;
|
||||
/* unbuffered? */
|
||||
ad7949_adc->vref = devm_regulator_get_optional(dev, "vref");
|
||||
if (IS_ERR(ad7949_adc->vref)) {
|
||||
ret = PTR_ERR(ad7949_adc->vref);
|
||||
if (ret != -ENODEV)
|
||||
return ret;
|
||||
} else {
|
||||
ad7949_adc->refsel = AD7949_CFG_VAL_REF_EXT_TEMP;
|
||||
}
|
||||
} else {
|
||||
ad7949_adc->refsel = AD7949_CFG_VAL_REF_EXT_TEMP_BUF;
|
||||
}
|
||||
|
||||
if (ad7949_adc->refsel & AD7949_CFG_VAL_REF_EXTERNAL) {
|
||||
ret = regulator_enable(ad7949_adc->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "fail to enable regulator\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(dev, ad7949_disable_reg,
|
||||
ad7949_adc->vref);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
mutex_init(&ad7949_adc->lock);
|
||||
|
@ -274,36 +401,16 @@ static int ad7949_spi_probe(struct spi_device *spi)
|
|||
ret = ad7949_spi_init(ad7949_adc);
|
||||
if (ret) {
|
||||
dev_err(dev, "enable to init this device: %d\n", ret);
|
||||
goto err;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
ret = devm_iio_device_register(dev, indio_dev);
|
||||
if (ret)
|
||||
dev_err(dev, "fail to register iio device: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
mutex_destroy(&ad7949_adc->lock);
|
||||
regulator_disable(ad7949_adc->vref);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad7949_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev = spi_get_drvdata(spi);
|
||||
struct ad7949_adc_chip *ad7949_adc = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
mutex_destroy(&ad7949_adc->lock);
|
||||
regulator_disable(ad7949_adc->vref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ad7949_spi_of_id[] = {
|
||||
{ .compatible = "adi,ad7949" },
|
||||
{ .compatible = "adi,ad7682" },
|
||||
|
@ -326,7 +433,6 @@ static struct spi_driver ad7949_spi_driver = {
|
|||
.of_match_table = ad7949_spi_of_id,
|
||||
},
|
||||
.probe = ad7949_spi_probe,
|
||||
.remove = ad7949_spi_remove,
|
||||
.id_table = ad7949_spi_id,
|
||||
};
|
||||
module_spi_driver(ad7949_spi_driver);
|
||||
|
|
|
@ -299,7 +299,11 @@ static int ad799x_read_raw(struct iio_dev *indio_dev,
|
|||
GENMASK(chan->scan_type.realbits - 1, 0);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = regulator_get_voltage(st->vref);
|
||||
if (st->vref)
|
||||
ret = regulator_get_voltage(st->vref);
|
||||
else
|
||||
ret = regulator_get_voltage(st->reg);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret / 1000;
|
||||
|
@ -770,6 +774,7 @@ static int ad799x_probe(struct i2c_client *client,
|
|||
const struct i2c_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
int extra_config = 0;
|
||||
struct ad799x_state *st;
|
||||
struct iio_dev *indio_dev;
|
||||
const struct ad799x_chip_info *chip_info =
|
||||
|
@ -797,14 +802,36 @@ static int ad799x_probe(struct i2c_client *client,
|
|||
ret = regulator_enable(st->reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
st->vref = devm_regulator_get(&client->dev, "vref");
|
||||
|
||||
/* check if an external reference is supplied */
|
||||
st->vref = devm_regulator_get_optional(&client->dev, "vref");
|
||||
|
||||
if (IS_ERR(st->vref)) {
|
||||
ret = PTR_ERR(st->vref);
|
||||
goto error_disable_reg;
|
||||
if (PTR_ERR(st->vref) == -ENODEV) {
|
||||
st->vref = NULL;
|
||||
dev_info(&client->dev, "Using VCC reference voltage\n");
|
||||
} else {
|
||||
ret = PTR_ERR(st->vref);
|
||||
goto error_disable_reg;
|
||||
}
|
||||
}
|
||||
|
||||
if (st->vref) {
|
||||
/*
|
||||
* Use external reference voltage if supported by hardware.
|
||||
* This is optional if voltage / regulator present, use VCC otherwise.
|
||||
*/
|
||||
if ((st->id == ad7991) || (st->id == ad7995) || (st->id == ad7999)) {
|
||||
dev_info(&client->dev, "Using external reference voltage\n");
|
||||
extra_config |= AD7991_REF_SEL;
|
||||
ret = regulator_enable(st->vref);
|
||||
if (ret)
|
||||
goto error_disable_reg;
|
||||
} else {
|
||||
st->vref = NULL;
|
||||
dev_warn(&client->dev, "Supplied reference not supported\n");
|
||||
}
|
||||
}
|
||||
ret = regulator_enable(st->vref);
|
||||
if (ret)
|
||||
goto error_disable_reg;
|
||||
|
||||
st->client = client;
|
||||
|
||||
|
@ -815,7 +842,7 @@ static int ad799x_probe(struct i2c_client *client,
|
|||
indio_dev->channels = st->chip_config->channel;
|
||||
indio_dev->num_channels = chip_info->num_channels;
|
||||
|
||||
ret = ad799x_update_config(st, st->chip_config->default_config);
|
||||
ret = ad799x_update_config(st, st->chip_config->default_config | extra_config);
|
||||
if (ret)
|
||||
goto error_disable_vref;
|
||||
|
||||
|
@ -845,7 +872,8 @@ static int ad799x_probe(struct i2c_client *client,
|
|||
error_cleanup_ring:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
error_disable_vref:
|
||||
regulator_disable(st->vref);
|
||||
if (st->vref)
|
||||
regulator_disable(st->vref);
|
||||
error_disable_reg:
|
||||
regulator_disable(st->reg);
|
||||
|
||||
|
@ -860,7 +888,8 @@ static int ad799x_remove(struct i2c_client *client)
|
|||
iio_device_unregister(indio_dev);
|
||||
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
regulator_disable(st->vref);
|
||||
if (st->vref)
|
||||
regulator_disable(st->vref);
|
||||
regulator_disable(st->reg);
|
||||
kfree(st->rx_buf);
|
||||
|
||||
|
@ -872,7 +901,8 @@ static int __maybe_unused ad799x_suspend(struct device *dev)
|
|||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct ad799x_state *st = iio_priv(indio_dev);
|
||||
|
||||
regulator_disable(st->vref);
|
||||
if (st->vref)
|
||||
regulator_disable(st->vref);
|
||||
regulator_disable(st->reg);
|
||||
|
||||
return 0;
|
||||
|
@ -889,17 +919,21 @@ static int __maybe_unused ad799x_resume(struct device *dev)
|
|||
dev_err(dev, "Unable to enable vcc regulator\n");
|
||||
return ret;
|
||||
}
|
||||
ret = regulator_enable(st->vref);
|
||||
if (ret) {
|
||||
regulator_disable(st->reg);
|
||||
dev_err(dev, "Unable to enable vref regulator\n");
|
||||
return ret;
|
||||
|
||||
if (st->vref) {
|
||||
ret = regulator_enable(st->vref);
|
||||
if (ret) {
|
||||
regulator_disable(st->reg);
|
||||
dev_err(dev, "Unable to enable vref regulator\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* resync config */
|
||||
ret = ad799x_update_config(st, st->config);
|
||||
if (ret) {
|
||||
regulator_disable(st->vref);
|
||||
if (st->vref)
|
||||
regulator_disable(st->vref);
|
||||
regulator_disable(st->reg);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Aspeed AST2400/2500 ADC
|
||||
* Aspeed AST2400/2500/2600 ADC
|
||||
*
|
||||
* Copyright (C) 2017 Google, Inc.
|
||||
* Copyright (C) 2021 Aspeed Technology Inc.
|
||||
*
|
||||
* ADC clock formula:
|
||||
* Ast2400/Ast2500:
|
||||
* clock period = period of PCLK * 2 * (ADC0C[31:17] + 1) * (ADC0C[9:0] + 1)
|
||||
* Ast2600:
|
||||
* clock period = period of PCLK * 2 * (ADC0C[15:0] + 1)
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
|
@ -13,9 +20,13 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/driver.h>
|
||||
|
@ -28,34 +39,87 @@
|
|||
#define ASPEED_REG_INTERRUPT_CONTROL 0x04
|
||||
#define ASPEED_REG_VGA_DETECT_CONTROL 0x08
|
||||
#define ASPEED_REG_CLOCK_CONTROL 0x0C
|
||||
#define ASPEED_REG_MAX 0xC0
|
||||
#define ASPEED_REG_COMPENSATION_TRIM 0xC4
|
||||
/*
|
||||
* The register offset between 0xC8~0xCC can be read and won't affect the
|
||||
* hardware logic in each version of ADC.
|
||||
*/
|
||||
#define ASPEED_REG_MAX 0xD0
|
||||
|
||||
#define ASPEED_OPERATION_MODE_POWER_DOWN (0x0 << 1)
|
||||
#define ASPEED_OPERATION_MODE_STANDBY (0x1 << 1)
|
||||
#define ASPEED_OPERATION_MODE_NORMAL (0x7 << 1)
|
||||
|
||||
#define ASPEED_ENGINE_ENABLE BIT(0)
|
||||
|
||||
#define ASPEED_ADC_CTRL_INIT_RDY BIT(8)
|
||||
#define ASPEED_ADC_ENGINE_ENABLE BIT(0)
|
||||
#define ASPEED_ADC_OP_MODE GENMASK(3, 1)
|
||||
#define ASPEED_ADC_OP_MODE_PWR_DOWN 0
|
||||
#define ASPEED_ADC_OP_MODE_STANDBY 1
|
||||
#define ASPEED_ADC_OP_MODE_NORMAL 7
|
||||
#define ASPEED_ADC_CTRL_COMPENSATION BIT(4)
|
||||
#define ASPEED_ADC_AUTO_COMPENSATION BIT(5)
|
||||
/*
|
||||
* Bit 6 determines not only the reference voltage range but also the dividing
|
||||
* circuit for battery sensing.
|
||||
*/
|
||||
#define ASPEED_ADC_REF_VOLTAGE GENMASK(7, 6)
|
||||
#define ASPEED_ADC_REF_VOLTAGE_2500mV 0
|
||||
#define ASPEED_ADC_REF_VOLTAGE_1200mV 1
|
||||
#define ASPEED_ADC_REF_VOLTAGE_EXT_HIGH 2
|
||||
#define ASPEED_ADC_REF_VOLTAGE_EXT_LOW 3
|
||||
#define ASPEED_ADC_BAT_SENSING_DIV BIT(6)
|
||||
#define ASPEED_ADC_BAT_SENSING_DIV_2_3 0
|
||||
#define ASPEED_ADC_BAT_SENSING_DIV_1_3 1
|
||||
#define ASPEED_ADC_CTRL_INIT_RDY BIT(8)
|
||||
#define ASPEED_ADC_CH7_MODE BIT(12)
|
||||
#define ASPEED_ADC_CH7_NORMAL 0
|
||||
#define ASPEED_ADC_CH7_BAT 1
|
||||
#define ASPEED_ADC_BAT_SENSING_ENABLE BIT(13)
|
||||
#define ASPEED_ADC_CTRL_CHANNEL GENMASK(31, 16)
|
||||
#define ASPEED_ADC_CTRL_CHANNEL_ENABLE(ch) FIELD_PREP(ASPEED_ADC_CTRL_CHANNEL, BIT(ch))
|
||||
|
||||
#define ASPEED_ADC_INIT_POLLING_TIME 500
|
||||
#define ASPEED_ADC_INIT_TIMEOUT 500000
|
||||
/*
|
||||
* When the sampling rate is too high, the ADC may not have enough charging
|
||||
* time, resulting in a low voltage value. Thus, the default uses a slow
|
||||
* sampling rate for most use cases.
|
||||
*/
|
||||
#define ASPEED_ADC_DEF_SAMPLING_RATE 65000
|
||||
|
||||
struct aspeed_adc_trim_locate {
|
||||
const unsigned int offset;
|
||||
const unsigned int field;
|
||||
};
|
||||
|
||||
struct aspeed_adc_model_data {
|
||||
const char *model_name;
|
||||
unsigned int min_sampling_rate; // Hz
|
||||
unsigned int max_sampling_rate; // Hz
|
||||
unsigned int vref_voltage; // mV
|
||||
unsigned int vref_fixed_mv;
|
||||
bool wait_init_sequence;
|
||||
bool need_prescaler;
|
||||
bool bat_sense_sup;
|
||||
u8 scaler_bit_width;
|
||||
unsigned int num_channels;
|
||||
const struct aspeed_adc_trim_locate *trim_locate;
|
||||
};
|
||||
|
||||
struct adc_gain {
|
||||
u8 mult;
|
||||
u8 div;
|
||||
};
|
||||
|
||||
struct aspeed_adc_data {
|
||||
struct device *dev;
|
||||
const struct aspeed_adc_model_data *model_data;
|
||||
struct regulator *regulator;
|
||||
void __iomem *base;
|
||||
spinlock_t clk_lock;
|
||||
struct clk_hw *fixed_div_clk;
|
||||
struct clk_hw *clk_prescaler;
|
||||
struct clk_hw *clk_scaler;
|
||||
struct reset_control *rst;
|
||||
int vref_mv;
|
||||
u32 sample_period_ns;
|
||||
int cv;
|
||||
bool battery_sensing;
|
||||
struct adc_gain battery_mode_gain;
|
||||
};
|
||||
|
||||
#define ASPEED_CHAN(_idx, _data_reg_addr) { \
|
||||
|
@ -65,7 +129,8 @@ struct aspeed_adc_data {
|
|||
.address = (_data_reg_addr), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
|
||||
BIT(IIO_CHAN_INFO_OFFSET), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec aspeed_adc_iio_channels[] = {
|
||||
|
@ -87,21 +152,170 @@ static const struct iio_chan_spec aspeed_adc_iio_channels[] = {
|
|||
ASPEED_CHAN(15, 0x2E),
|
||||
};
|
||||
|
||||
#define ASPEED_BAT_CHAN(_idx, _data_reg_addr) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.channel = (_idx), \
|
||||
.address = (_data_reg_addr), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_OFFSET), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
}
|
||||
static const struct iio_chan_spec aspeed_adc_iio_bat_channels[] = {
|
||||
ASPEED_CHAN(0, 0x10),
|
||||
ASPEED_CHAN(1, 0x12),
|
||||
ASPEED_CHAN(2, 0x14),
|
||||
ASPEED_CHAN(3, 0x16),
|
||||
ASPEED_CHAN(4, 0x18),
|
||||
ASPEED_CHAN(5, 0x1A),
|
||||
ASPEED_CHAN(6, 0x1C),
|
||||
ASPEED_BAT_CHAN(7, 0x1E),
|
||||
};
|
||||
|
||||
static int aspeed_adc_set_trim_data(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct device_node *syscon;
|
||||
struct regmap *scu;
|
||||
u32 scu_otp, trimming_val;
|
||||
struct aspeed_adc_data *data = iio_priv(indio_dev);
|
||||
|
||||
syscon = of_find_node_by_name(NULL, "syscon");
|
||||
if (syscon == NULL) {
|
||||
dev_warn(data->dev, "Couldn't find syscon node\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
scu = syscon_node_to_regmap(syscon);
|
||||
if (IS_ERR(scu)) {
|
||||
dev_warn(data->dev, "Failed to get syscon regmap\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
if (data->model_data->trim_locate) {
|
||||
if (regmap_read(scu, data->model_data->trim_locate->offset,
|
||||
&scu_otp)) {
|
||||
dev_warn(data->dev,
|
||||
"Failed to get adc trimming data\n");
|
||||
trimming_val = 0x8;
|
||||
} else {
|
||||
trimming_val =
|
||||
((scu_otp) &
|
||||
(data->model_data->trim_locate->field)) >>
|
||||
__ffs(data->model_data->trim_locate->field);
|
||||
}
|
||||
dev_dbg(data->dev,
|
||||
"trimming val = %d, offset = %08x, fields = %08x\n",
|
||||
trimming_val, data->model_data->trim_locate->offset,
|
||||
data->model_data->trim_locate->field);
|
||||
writel(trimming_val, data->base + ASPEED_REG_COMPENSATION_TRIM);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aspeed_adc_compensation(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct aspeed_adc_data *data = iio_priv(indio_dev);
|
||||
u32 index, adc_raw = 0;
|
||||
u32 adc_engine_control_reg_val;
|
||||
|
||||
adc_engine_control_reg_val =
|
||||
readl(data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
adc_engine_control_reg_val &= ~ASPEED_ADC_OP_MODE;
|
||||
adc_engine_control_reg_val |=
|
||||
(FIELD_PREP(ASPEED_ADC_OP_MODE, ASPEED_ADC_OP_MODE_NORMAL) |
|
||||
ASPEED_ADC_ENGINE_ENABLE);
|
||||
/*
|
||||
* Enable compensating sensing:
|
||||
* After that, the input voltage of ADC will force to half of the reference
|
||||
* voltage. So the expected reading raw data will become half of the max
|
||||
* value. We can get compensating value = 0x200 - ADC read raw value.
|
||||
* It is recommended to average at least 10 samples to get a final CV.
|
||||
*/
|
||||
writel(adc_engine_control_reg_val | ASPEED_ADC_CTRL_COMPENSATION |
|
||||
ASPEED_ADC_CTRL_CHANNEL_ENABLE(0),
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
/*
|
||||
* After enable compensating sensing mode need to wait some time for ADC stable
|
||||
* Experiment result is 1ms.
|
||||
*/
|
||||
mdelay(1);
|
||||
|
||||
for (index = 0; index < 16; index++) {
|
||||
/*
|
||||
* Waiting for the sampling period ensures that the value acquired
|
||||
* is fresh each time.
|
||||
*/
|
||||
ndelay(data->sample_period_ns);
|
||||
adc_raw += readw(data->base + aspeed_adc_iio_channels[0].address);
|
||||
}
|
||||
adc_raw >>= 4;
|
||||
data->cv = BIT(ASPEED_RESOLUTION_BITS - 1) - adc_raw;
|
||||
writel(adc_engine_control_reg_val,
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
dev_dbg(data->dev, "Compensating value = %d\n", data->cv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aspeed_adc_set_sampling_rate(struct iio_dev *indio_dev, u32 rate)
|
||||
{
|
||||
struct aspeed_adc_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (rate < data->model_data->min_sampling_rate ||
|
||||
rate > data->model_data->max_sampling_rate)
|
||||
return -EINVAL;
|
||||
/* Each sampling needs 12 clocks to convert.*/
|
||||
clk_set_rate(data->clk_scaler->clk, rate * ASPEED_CLOCKS_PER_SAMPLE);
|
||||
rate = clk_get_rate(data->clk_scaler->clk);
|
||||
data->sample_period_ns = DIV_ROUND_UP_ULL(
|
||||
(u64)NSEC_PER_SEC * ASPEED_CLOCKS_PER_SAMPLE, rate);
|
||||
dev_dbg(data->dev, "Adc clock = %d sample period = %d ns", rate,
|
||||
data->sample_period_ns);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aspeed_adc_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct aspeed_adc_data *data = iio_priv(indio_dev);
|
||||
const struct aspeed_adc_model_data *model_data =
|
||||
of_device_get_match_data(data->dev);
|
||||
u32 adc_engine_control_reg_val;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
*val = readw(data->base + chan->address);
|
||||
if (data->battery_sensing && chan->channel == 7) {
|
||||
adc_engine_control_reg_val =
|
||||
readl(data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
writel(adc_engine_control_reg_val |
|
||||
FIELD_PREP(ASPEED_ADC_CH7_MODE,
|
||||
ASPEED_ADC_CH7_BAT) |
|
||||
ASPEED_ADC_BAT_SENSING_ENABLE,
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
/*
|
||||
* After enable battery sensing mode need to wait some time for adc stable
|
||||
* Experiment result is 1ms.
|
||||
*/
|
||||
mdelay(1);
|
||||
*val = readw(data->base + chan->address);
|
||||
*val = (*val * data->battery_mode_gain.mult) /
|
||||
data->battery_mode_gain.div;
|
||||
/* Restore control register value */
|
||||
writel(adc_engine_control_reg_val,
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
} else
|
||||
*val = readw(data->base + chan->address);
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
if (data->battery_sensing && chan->channel == 7)
|
||||
*val = (data->cv * data->battery_mode_gain.mult) /
|
||||
data->battery_mode_gain.div;
|
||||
else
|
||||
*val = data->cv;
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = model_data->vref_voltage;
|
||||
*val = data->vref_mv;
|
||||
*val2 = ASPEED_RESOLUTION_BITS;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
|
||||
|
@ -119,19 +333,9 @@ static int aspeed_adc_write_raw(struct iio_dev *indio_dev,
|
|||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct aspeed_adc_data *data = iio_priv(indio_dev);
|
||||
const struct aspeed_adc_model_data *model_data =
|
||||
of_device_get_match_data(data->dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
if (val < model_data->min_sampling_rate ||
|
||||
val > model_data->max_sampling_rate)
|
||||
return -EINVAL;
|
||||
|
||||
clk_set_rate(data->clk_scaler->clk,
|
||||
val * ASPEED_CLOCKS_PER_SAMPLE);
|
||||
return 0;
|
||||
return aspeed_adc_set_sampling_rate(indio_dev, val);
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
|
@ -168,14 +372,119 @@ static const struct iio_info aspeed_adc_iio_info = {
|
|||
.debugfs_reg_access = aspeed_adc_reg_access,
|
||||
};
|
||||
|
||||
static void aspeed_adc_unregister_fixed_divider(void *data)
|
||||
{
|
||||
struct clk_hw *clk = data;
|
||||
|
||||
clk_hw_unregister_fixed_factor(clk);
|
||||
}
|
||||
|
||||
static void aspeed_adc_reset_assert(void *data)
|
||||
{
|
||||
struct reset_control *rst = data;
|
||||
|
||||
reset_control_assert(rst);
|
||||
}
|
||||
|
||||
static void aspeed_adc_clk_disable_unprepare(void *data)
|
||||
{
|
||||
struct clk *clk = data;
|
||||
|
||||
clk_disable_unprepare(clk);
|
||||
}
|
||||
|
||||
static void aspeed_adc_power_down(void *data)
|
||||
{
|
||||
struct aspeed_adc_data *priv_data = data;
|
||||
|
||||
writel(FIELD_PREP(ASPEED_ADC_OP_MODE, ASPEED_ADC_OP_MODE_PWR_DOWN),
|
||||
priv_data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
}
|
||||
|
||||
static void aspeed_adc_reg_disable(void *data)
|
||||
{
|
||||
struct regulator *reg = data;
|
||||
|
||||
regulator_disable(reg);
|
||||
}
|
||||
|
||||
static int aspeed_adc_vref_config(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct aspeed_adc_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
u32 adc_engine_control_reg_val;
|
||||
|
||||
if (data->model_data->vref_fixed_mv) {
|
||||
data->vref_mv = data->model_data->vref_fixed_mv;
|
||||
return 0;
|
||||
}
|
||||
adc_engine_control_reg_val =
|
||||
readl(data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
data->regulator = devm_regulator_get_optional(data->dev, "vref");
|
||||
if (!IS_ERR(data->regulator)) {
|
||||
ret = regulator_enable(data->regulator);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = devm_add_action_or_reset(
|
||||
data->dev, aspeed_adc_reg_disable, data->regulator);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->vref_mv = regulator_get_voltage(data->regulator);
|
||||
/* Conversion from uV to mV */
|
||||
data->vref_mv /= 1000;
|
||||
if ((data->vref_mv >= 1550) && (data->vref_mv <= 2700))
|
||||
writel(adc_engine_control_reg_val |
|
||||
FIELD_PREP(
|
||||
ASPEED_ADC_REF_VOLTAGE,
|
||||
ASPEED_ADC_REF_VOLTAGE_EXT_HIGH),
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
else if ((data->vref_mv >= 900) && (data->vref_mv <= 1650))
|
||||
writel(adc_engine_control_reg_val |
|
||||
FIELD_PREP(
|
||||
ASPEED_ADC_REF_VOLTAGE,
|
||||
ASPEED_ADC_REF_VOLTAGE_EXT_LOW),
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
else {
|
||||
dev_err(data->dev, "Regulator voltage %d not support",
|
||||
data->vref_mv);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
} else {
|
||||
if (PTR_ERR(data->regulator) != -ENODEV)
|
||||
return PTR_ERR(data->regulator);
|
||||
data->vref_mv = 2500000;
|
||||
of_property_read_u32(data->dev->of_node,
|
||||
"aspeed,int-vref-microvolt",
|
||||
&data->vref_mv);
|
||||
/* Conversion from uV to mV */
|
||||
data->vref_mv /= 1000;
|
||||
if (data->vref_mv == 2500)
|
||||
writel(adc_engine_control_reg_val |
|
||||
FIELD_PREP(ASPEED_ADC_REF_VOLTAGE,
|
||||
ASPEED_ADC_REF_VOLTAGE_2500mV),
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
else if (data->vref_mv == 1200)
|
||||
writel(adc_engine_control_reg_val |
|
||||
FIELD_PREP(ASPEED_ADC_REF_VOLTAGE,
|
||||
ASPEED_ADC_REF_VOLTAGE_1200mV),
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
else {
|
||||
dev_err(data->dev, "Voltage %d not support", data->vref_mv);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aspeed_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
struct aspeed_adc_data *data;
|
||||
const struct aspeed_adc_model_data *model_data;
|
||||
const char *clk_parent_name;
|
||||
int ret;
|
||||
u32 adc_engine_control_reg_val;
|
||||
unsigned long scaler_flags = 0;
|
||||
char clk_name[32], clk_parent_name[32];
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
|
@ -183,6 +492,7 @@ static int aspeed_adc_probe(struct platform_device *pdev)
|
|||
|
||||
data = iio_priv(indio_dev);
|
||||
data->dev = &pdev->dev;
|
||||
data->model_data = of_device_get_match_data(&pdev->dev);
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
data->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
|
@ -191,45 +501,117 @@ static int aspeed_adc_probe(struct platform_device *pdev)
|
|||
|
||||
/* Register ADC clock prescaler with source specified by device tree. */
|
||||
spin_lock_init(&data->clk_lock);
|
||||
clk_parent_name = of_clk_get_parent_name(pdev->dev.of_node, 0);
|
||||
snprintf(clk_parent_name, ARRAY_SIZE(clk_parent_name), "%s",
|
||||
of_clk_get_parent_name(pdev->dev.of_node, 0));
|
||||
snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-fixed-div",
|
||||
data->model_data->model_name);
|
||||
data->fixed_div_clk = clk_hw_register_fixed_factor(
|
||||
&pdev->dev, clk_name, clk_parent_name, 0, 1, 2);
|
||||
if (IS_ERR(data->fixed_div_clk))
|
||||
return PTR_ERR(data->fixed_div_clk);
|
||||
|
||||
data->clk_prescaler = clk_hw_register_divider(
|
||||
&pdev->dev, "prescaler", clk_parent_name, 0,
|
||||
data->base + ASPEED_REG_CLOCK_CONTROL,
|
||||
17, 15, 0, &data->clk_lock);
|
||||
if (IS_ERR(data->clk_prescaler))
|
||||
return PTR_ERR(data->clk_prescaler);
|
||||
ret = devm_add_action_or_reset(data->dev,
|
||||
aspeed_adc_unregister_fixed_divider,
|
||||
data->fixed_div_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
snprintf(clk_parent_name, ARRAY_SIZE(clk_parent_name), clk_name);
|
||||
|
||||
if (data->model_data->need_prescaler) {
|
||||
snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-prescaler",
|
||||
data->model_data->model_name);
|
||||
data->clk_prescaler = devm_clk_hw_register_divider(
|
||||
&pdev->dev, clk_name, clk_parent_name, 0,
|
||||
data->base + ASPEED_REG_CLOCK_CONTROL, 17, 15, 0,
|
||||
&data->clk_lock);
|
||||
if (IS_ERR(data->clk_prescaler))
|
||||
return PTR_ERR(data->clk_prescaler);
|
||||
snprintf(clk_parent_name, ARRAY_SIZE(clk_parent_name),
|
||||
clk_name);
|
||||
scaler_flags = CLK_SET_RATE_PARENT;
|
||||
}
|
||||
/*
|
||||
* Register ADC clock scaler downstream from the prescaler. Allow rate
|
||||
* setting to adjust the prescaler as well.
|
||||
*/
|
||||
data->clk_scaler = clk_hw_register_divider(
|
||||
&pdev->dev, "scaler", "prescaler",
|
||||
CLK_SET_RATE_PARENT,
|
||||
data->base + ASPEED_REG_CLOCK_CONTROL,
|
||||
0, 10, 0, &data->clk_lock);
|
||||
if (IS_ERR(data->clk_scaler)) {
|
||||
ret = PTR_ERR(data->clk_scaler);
|
||||
goto scaler_error;
|
||||
}
|
||||
snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-scaler",
|
||||
data->model_data->model_name);
|
||||
data->clk_scaler = devm_clk_hw_register_divider(
|
||||
&pdev->dev, clk_name, clk_parent_name, scaler_flags,
|
||||
data->base + ASPEED_REG_CLOCK_CONTROL, 0,
|
||||
data->model_data->scaler_bit_width, 0, &data->clk_lock);
|
||||
if (IS_ERR(data->clk_scaler))
|
||||
return PTR_ERR(data->clk_scaler);
|
||||
|
||||
data->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL);
|
||||
data->rst = devm_reset_control_get_shared(&pdev->dev, NULL);
|
||||
if (IS_ERR(data->rst)) {
|
||||
dev_err(&pdev->dev,
|
||||
"invalid or missing reset controller device tree entry");
|
||||
ret = PTR_ERR(data->rst);
|
||||
goto reset_error;
|
||||
return PTR_ERR(data->rst);
|
||||
}
|
||||
reset_control_deassert(data->rst);
|
||||
|
||||
model_data = of_device_get_match_data(&pdev->dev);
|
||||
ret = devm_add_action_or_reset(data->dev, aspeed_adc_reset_assert,
|
||||
data->rst);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (model_data->wait_init_sequence) {
|
||||
/* Enable engine in normal mode. */
|
||||
writel(ASPEED_OPERATION_MODE_NORMAL | ASPEED_ENGINE_ENABLE,
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
ret = aspeed_adc_vref_config(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (of_find_property(data->dev->of_node, "aspeed,trim-data-valid",
|
||||
NULL)) {
|
||||
ret = aspeed_adc_set_trim_data(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (of_find_property(data->dev->of_node, "aspeed,battery-sensing",
|
||||
NULL)) {
|
||||
if (data->model_data->bat_sense_sup) {
|
||||
data->battery_sensing = 1;
|
||||
if (readl(data->base + ASPEED_REG_ENGINE_CONTROL) &
|
||||
ASPEED_ADC_BAT_SENSING_DIV) {
|
||||
data->battery_mode_gain.mult = 3;
|
||||
data->battery_mode_gain.div = 1;
|
||||
} else {
|
||||
data->battery_mode_gain.mult = 3;
|
||||
data->battery_mode_gain.div = 2;
|
||||
}
|
||||
} else
|
||||
dev_warn(&pdev->dev,
|
||||
"Failed to enable battery-sensing mode\n");
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(data->clk_scaler->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = devm_add_action_or_reset(data->dev,
|
||||
aspeed_adc_clk_disable_unprepare,
|
||||
data->clk_scaler->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = aspeed_adc_set_sampling_rate(indio_dev,
|
||||
ASPEED_ADC_DEF_SAMPLING_RATE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
adc_engine_control_reg_val =
|
||||
readl(data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
adc_engine_control_reg_val |=
|
||||
FIELD_PREP(ASPEED_ADC_OP_MODE, ASPEED_ADC_OP_MODE_NORMAL) |
|
||||
ASPEED_ADC_ENGINE_ENABLE;
|
||||
/* Enable engine in normal mode. */
|
||||
writel(adc_engine_control_reg_val,
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
|
||||
ret = devm_add_action_or_reset(data->dev, aspeed_adc_power_down,
|
||||
data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (data->model_data->wait_init_sequence) {
|
||||
/* Wait for initial sequence complete. */
|
||||
ret = readl_poll_timeout(data->base + ASPEED_REG_ENGINE_CONTROL,
|
||||
adc_engine_control_reg_val,
|
||||
|
@ -238,87 +620,99 @@ static int aspeed_adc_probe(struct platform_device *pdev)
|
|||
ASPEED_ADC_INIT_POLLING_TIME,
|
||||
ASPEED_ADC_INIT_TIMEOUT);
|
||||
if (ret)
|
||||
goto poll_timeout_error;
|
||||
return ret;
|
||||
}
|
||||
|
||||
aspeed_adc_compensation(indio_dev);
|
||||
/* Start all channels in normal mode. */
|
||||
ret = clk_prepare_enable(data->clk_scaler->clk);
|
||||
if (ret)
|
||||
goto clk_enable_error;
|
||||
|
||||
adc_engine_control_reg_val = GENMASK(31, 16) |
|
||||
ASPEED_OPERATION_MODE_NORMAL | ASPEED_ENGINE_ENABLE;
|
||||
adc_engine_control_reg_val =
|
||||
readl(data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
adc_engine_control_reg_val |= ASPEED_ADC_CTRL_CHANNEL;
|
||||
writel(adc_engine_control_reg_val,
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
|
||||
model_data = of_device_get_match_data(&pdev->dev);
|
||||
indio_dev->name = model_data->model_name;
|
||||
indio_dev->name = data->model_data->model_name;
|
||||
indio_dev->info = &aspeed_adc_iio_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = aspeed_adc_iio_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(aspeed_adc_iio_channels);
|
||||
indio_dev->channels = data->battery_sensing ?
|
||||
aspeed_adc_iio_bat_channels :
|
||||
aspeed_adc_iio_channels;
|
||||
indio_dev->num_channels = data->model_data->num_channels;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret)
|
||||
goto iio_register_error;
|
||||
|
||||
return 0;
|
||||
|
||||
iio_register_error:
|
||||
writel(ASPEED_OPERATION_MODE_POWER_DOWN,
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
clk_disable_unprepare(data->clk_scaler->clk);
|
||||
clk_enable_error:
|
||||
poll_timeout_error:
|
||||
reset_control_assert(data->rst);
|
||||
reset_error:
|
||||
clk_hw_unregister_divider(data->clk_scaler);
|
||||
scaler_error:
|
||||
clk_hw_unregister_divider(data->clk_prescaler);
|
||||
ret = devm_iio_device_register(data->dev, indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aspeed_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct aspeed_adc_data *data = iio_priv(indio_dev);
|
||||
static const struct aspeed_adc_trim_locate ast2500_adc_trim = {
|
||||
.offset = 0x154,
|
||||
.field = GENMASK(31, 28),
|
||||
};
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
writel(ASPEED_OPERATION_MODE_POWER_DOWN,
|
||||
data->base + ASPEED_REG_ENGINE_CONTROL);
|
||||
clk_disable_unprepare(data->clk_scaler->clk);
|
||||
reset_control_assert(data->rst);
|
||||
clk_hw_unregister_divider(data->clk_scaler);
|
||||
clk_hw_unregister_divider(data->clk_prescaler);
|
||||
static const struct aspeed_adc_trim_locate ast2600_adc0_trim = {
|
||||
.offset = 0x5d0,
|
||||
.field = GENMASK(3, 0),
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
static const struct aspeed_adc_trim_locate ast2600_adc1_trim = {
|
||||
.offset = 0x5d0,
|
||||
.field = GENMASK(7, 4),
|
||||
};
|
||||
|
||||
static const struct aspeed_adc_model_data ast2400_model_data = {
|
||||
.model_name = "ast2400-adc",
|
||||
.vref_voltage = 2500, // mV
|
||||
.vref_fixed_mv = 2500,
|
||||
.min_sampling_rate = 10000,
|
||||
.max_sampling_rate = 500000,
|
||||
.need_prescaler = true,
|
||||
.scaler_bit_width = 10,
|
||||
.num_channels = 16,
|
||||
};
|
||||
|
||||
static const struct aspeed_adc_model_data ast2500_model_data = {
|
||||
.model_name = "ast2500-adc",
|
||||
.vref_voltage = 1800, // mV
|
||||
.vref_fixed_mv = 1800,
|
||||
.min_sampling_rate = 1,
|
||||
.max_sampling_rate = 1000000,
|
||||
.wait_init_sequence = true,
|
||||
.need_prescaler = true,
|
||||
.scaler_bit_width = 10,
|
||||
.num_channels = 16,
|
||||
.trim_locate = &ast2500_adc_trim,
|
||||
};
|
||||
|
||||
static const struct aspeed_adc_model_data ast2600_adc0_model_data = {
|
||||
.model_name = "ast2600-adc0",
|
||||
.min_sampling_rate = 10000,
|
||||
.max_sampling_rate = 500000,
|
||||
.wait_init_sequence = true,
|
||||
.bat_sense_sup = true,
|
||||
.scaler_bit_width = 16,
|
||||
.num_channels = 8,
|
||||
.trim_locate = &ast2600_adc0_trim,
|
||||
};
|
||||
|
||||
static const struct aspeed_adc_model_data ast2600_adc1_model_data = {
|
||||
.model_name = "ast2600-adc1",
|
||||
.min_sampling_rate = 10000,
|
||||
.max_sampling_rate = 500000,
|
||||
.wait_init_sequence = true,
|
||||
.bat_sense_sup = true,
|
||||
.scaler_bit_width = 16,
|
||||
.num_channels = 8,
|
||||
.trim_locate = &ast2600_adc1_trim,
|
||||
};
|
||||
|
||||
static const struct of_device_id aspeed_adc_matches[] = {
|
||||
{ .compatible = "aspeed,ast2400-adc", .data = &ast2400_model_data },
|
||||
{ .compatible = "aspeed,ast2500-adc", .data = &ast2500_model_data },
|
||||
{ .compatible = "aspeed,ast2600-adc0", .data = &ast2600_adc0_model_data },
|
||||
{ .compatible = "aspeed,ast2600-adc1", .data = &ast2600_adc1_model_data },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, aspeed_adc_matches);
|
||||
|
||||
static struct platform_driver aspeed_adc_driver = {
|
||||
.probe = aspeed_adc_probe,
|
||||
.remove = aspeed_adc_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.of_match_table = aspeed_adc_matches,
|
||||
|
@ -328,5 +722,5 @@ static struct platform_driver aspeed_adc_driver = {
|
|||
module_platform_driver(aspeed_adc_driver);
|
||||
|
||||
MODULE_AUTHOR("Rick Altherr <raltherr@google.com>");
|
||||
MODULE_DESCRIPTION("Aspeed AST2400/2500 ADC Driver");
|
||||
MODULE_DESCRIPTION("Aspeed AST2400/2500/2600 ADC Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -259,7 +259,7 @@ static int axp288_adc_probe(struct platform_device *pdev)
|
|||
info->irq = platform_get_irq(pdev, 0);
|
||||
if (info->irq < 0)
|
||||
return info->irq;
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
info->regmap = axp20x->regmap;
|
||||
/*
|
||||
* Set ADC to enabled state at all time, including system suspend.
|
||||
|
@ -276,31 +276,12 @@ static int axp288_adc_probe(struct platform_device *pdev)
|
|||
indio_dev->num_channels = ARRAY_SIZE(axp288_adc_channels);
|
||||
indio_dev->info = &axp288_adc_iio_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
ret = iio_map_array_register(indio_dev, axp288_adc_default_maps);
|
||||
|
||||
ret = devm_iio_map_array_register(&pdev->dev, indio_dev, axp288_adc_default_maps);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "unable to register iio device\n");
|
||||
goto err_array_unregister;
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_array_unregister:
|
||||
iio_map_array_unregister(indio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int axp288_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_map_array_unregister(indio_dev);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(&pdev->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct platform_device_id axp288_adc_id_table[] = {
|
||||
|
@ -310,7 +291,6 @@ static const struct platform_device_id axp288_adc_id_table[] = {
|
|||
|
||||
static struct platform_driver axp288_adc_driver = {
|
||||
.probe = axp288_adc_probe,
|
||||
.remove = axp288_adc_remove,
|
||||
.id_table = axp288_adc_id_table,
|
||||
.driver = {
|
||||
.name = "axp288_adc",
|
||||
|
|
|
@ -280,6 +280,13 @@ static const struct iio_info berlin2_adc_info = {
|
|||
.read_raw = berlin2_adc_read_raw,
|
||||
};
|
||||
|
||||
static void berlin2_adc_powerdown(void *regmap)
|
||||
{
|
||||
regmap_update_bits(regmap, BERLIN2_SM_CTRL,
|
||||
BERLIN2_SM_CTRL_ADC_POWER, 0);
|
||||
|
||||
}
|
||||
|
||||
static int berlin2_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
|
@ -293,7 +300,6 @@ static int berlin2_adc_probe(struct platform_device *pdev)
|
|||
return -ENOMEM;
|
||||
|
||||
priv = iio_priv(indio_dev);
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
priv->regmap = syscon_node_to_regmap(parent_np);
|
||||
of_node_put(parent_np);
|
||||
|
@ -333,29 +339,12 @@ static int berlin2_adc_probe(struct platform_device *pdev)
|
|||
BERLIN2_SM_CTRL_ADC_POWER,
|
||||
BERLIN2_SM_CTRL_ADC_POWER);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
/* Power down the ADC */
|
||||
regmap_update_bits(priv->regmap, BERLIN2_SM_CTRL,
|
||||
BERLIN2_SM_CTRL_ADC_POWER, 0);
|
||||
ret = devm_add_action_or_reset(&pdev->dev, berlin2_adc_powerdown,
|
||||
priv->regmap);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int berlin2_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct berlin2_adc_priv *priv = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
/* Power down the ADC */
|
||||
regmap_update_bits(priv->regmap, BERLIN2_SM_CTRL,
|
||||
BERLIN2_SM_CTRL_ADC_POWER, 0);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(&pdev->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id berlin2_adc_match[] = {
|
||||
|
@ -370,7 +359,6 @@ static struct platform_driver berlin2_adc_driver = {
|
|||
.of_match_table = berlin2_adc_match,
|
||||
},
|
||||
.probe = berlin2_adc_probe,
|
||||
.remove = berlin2_adc_remove,
|
||||
};
|
||||
module_platform_driver(berlin2_adc_driver);
|
||||
|
||||
|
|
|
@ -330,7 +330,6 @@ static int da9150_gpadc_probe(struct platform_device *pdev)
|
|||
}
|
||||
gpadc = iio_priv(indio_dev);
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
gpadc->da9150 = da9150;
|
||||
gpadc->dev = dev;
|
||||
mutex_init(&gpadc->lock);
|
||||
|
@ -347,7 +346,7 @@ static int da9150_gpadc_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_map_array_register(indio_dev, da9150_gpadc_default_maps);
|
||||
ret = devm_iio_map_array_register(&pdev->dev, indio_dev, da9150_gpadc_default_maps);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register IIO maps: %d\n", ret);
|
||||
return ret;
|
||||
|
@ -359,28 +358,7 @@ static int da9150_gpadc_probe(struct platform_device *pdev)
|
|||
indio_dev->channels = da9150_gpadc_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(da9150_gpadc_channels);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register IIO device: %d\n", ret);
|
||||
goto iio_map_unreg;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
iio_map_unreg:
|
||||
iio_map_array_unregister(indio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da9150_gpadc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_map_array_unregister(indio_dev);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(&pdev->dev, indio_dev);
|
||||
}
|
||||
|
||||
static struct platform_driver da9150_gpadc_driver = {
|
||||
|
@ -388,7 +366,6 @@ static struct platform_driver da9150_gpadc_driver = {
|
|||
.name = "da9150-gpadc",
|
||||
},
|
||||
.probe = da9150_gpadc_probe,
|
||||
.remove = da9150_gpadc_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(da9150_gpadc_driver);
|
||||
|
|
|
@ -156,15 +156,13 @@ static int ep93xx_adc_probe(struct platform_device *pdev)
|
|||
struct iio_dev *iiodev;
|
||||
struct ep93xx_adc_priv *priv;
|
||||
struct clk *pclk;
|
||||
struct resource *res;
|
||||
|
||||
iiodev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
|
||||
if (!iiodev)
|
||||
return -ENOMEM;
|
||||
priv = iio_priv(iiodev);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
priv->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
priv->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(priv->base))
|
||||
return PTR_ERR(priv->base);
|
||||
|
||||
|
|
|
@ -172,13 +172,35 @@ static const struct regmap_config mx25_gcq_regconfig = {
|
|||
.reg_stride = 4,
|
||||
};
|
||||
|
||||
static int mx25_gcq_ext_regulator_setup(struct device *dev,
|
||||
struct mx25_gcq_priv *priv, u32 refp)
|
||||
{
|
||||
char reg_name[12];
|
||||
int ret;
|
||||
|
||||
if (priv->vref[refp])
|
||||
return 0;
|
||||
|
||||
ret = snprintf(reg_name, sizeof(reg_name), "vref-%s",
|
||||
mx25_gcq_refp_names[refp]);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->vref[refp] = devm_regulator_get_optional(dev, reg_name);
|
||||
if (IS_ERR(priv->vref[refp]))
|
||||
return dev_err_probe(dev, PTR_ERR(priv->vref[refp]),
|
||||
"Error, trying to use external voltage reference without a %s regulator.",
|
||||
reg_name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
unsigned int refp_used[4] = {};
|
||||
int ret, i;
|
||||
|
||||
/*
|
||||
|
@ -194,19 +216,6 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
|
|||
MX25_ADCQ_CFG_IN(i) |
|
||||
MX25_ADCQ_CFG_REFN_NGND2);
|
||||
|
||||
/*
|
||||
* First get all regulators to store them in channel_vref_mv if
|
||||
* necessary. Later we use that information for proper IIO scale
|
||||
* information.
|
||||
*/
|
||||
priv->vref[MX25_ADC_REFP_INT] = NULL;
|
||||
priv->vref[MX25_ADC_REFP_EXT] =
|
||||
devm_regulator_get_optional(dev, "vref-ext");
|
||||
priv->vref[MX25_ADC_REFP_XP] =
|
||||
devm_regulator_get_optional(dev, "vref-xp");
|
||||
priv->vref[MX25_ADC_REFP_YP] =
|
||||
devm_regulator_get_optional(dev, "vref-yp");
|
||||
|
||||
for_each_child_of_node(np, child) {
|
||||
u32 reg;
|
||||
u32 refp = MX25_ADCQ_CFG_REFP_INT;
|
||||
|
@ -233,11 +242,10 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
|
|||
case MX25_ADC_REFP_EXT:
|
||||
case MX25_ADC_REFP_XP:
|
||||
case MX25_ADC_REFP_YP:
|
||||
if (IS_ERR(priv->vref[refp])) {
|
||||
dev_err(dev, "Error, trying to use external voltage reference without a vref-%s regulator.",
|
||||
mx25_gcq_refp_names[refp]);
|
||||
ret = mx25_gcq_ext_regulator_setup(&pdev->dev, priv, refp);
|
||||
if (ret) {
|
||||
of_node_put(child);
|
||||
return PTR_ERR(priv->vref[refp]);
|
||||
return ret;
|
||||
}
|
||||
priv->channel_vref_mv[reg] =
|
||||
regulator_get_voltage(priv->vref[refp]);
|
||||
|
@ -253,8 +261,6 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
++refp_used[refp];
|
||||
|
||||
/*
|
||||
* Shift the read values to the correct positions within the
|
||||
* register.
|
||||
|
@ -285,15 +291,6 @@ static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
|
|||
regmap_write(priv->regs, MX25_ADCQ_CR,
|
||||
MX25_ADCQ_CR_PDMSK | MX25_ADCQ_CR_QSM_FQS);
|
||||
|
||||
/* Remove unused regulators */
|
||||
for (i = 0; i != 4; ++i) {
|
||||
if (!refp_used[i]) {
|
||||
if (!IS_ERR_OR_NULL(priv->vref[i]))
|
||||
devm_regulator_put(priv->vref[i]);
|
||||
priv->vref[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,494 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* NXP i.MX8QXP ADC driver
|
||||
*
|
||||
* Based on the work of Haibo Chen <haibo.chen@nxp.com>
|
||||
* The initial developer of the original code is Haibo Chen.
|
||||
* Portions created by Haibo Chen are Copyright (C) 2018 NXP.
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Copyright (C) 2018 NXP
|
||||
* Copyright (C) 2021 Cai Huoqing
|
||||
*/
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
#define ADC_DRIVER_NAME "imx8qxp-adc"
|
||||
|
||||
/* Register map definition */
|
||||
#define IMX8QXP_ADR_ADC_CTRL 0x10
|
||||
#define IMX8QXP_ADR_ADC_STAT 0x14
|
||||
#define IMX8QXP_ADR_ADC_IE 0x18
|
||||
#define IMX8QXP_ADR_ADC_DE 0x1c
|
||||
#define IMX8QXP_ADR_ADC_CFG 0x20
|
||||
#define IMX8QXP_ADR_ADC_FCTRL 0x30
|
||||
#define IMX8QXP_ADR_ADC_SWTRIG 0x34
|
||||
#define IMX8QXP_ADR_ADC_TCTRL(tid) (0xc0 + (tid) * 4)
|
||||
#define IMX8QXP_ADR_ADC_CMDH(cid) (0x100 + (cid) * 8)
|
||||
#define IMX8QXP_ADR_ADC_CMDL(cid) (0x104 + (cid) * 8)
|
||||
#define IMX8QXP_ADR_ADC_RESFIFO 0x300
|
||||
#define IMX8QXP_ADR_ADC_TST 0xffc
|
||||
|
||||
/* ADC bit shift */
|
||||
#define IMX8QXP_ADC_IE_FWMIE_MASK GENMASK(1, 0)
|
||||
#define IMX8QXP_ADC_CTRL_FIFO_RESET_MASK BIT(8)
|
||||
#define IMX8QXP_ADC_CTRL_SOFTWARE_RESET_MASK BIT(1)
|
||||
#define IMX8QXP_ADC_CTRL_ADC_EN_MASK BIT(0)
|
||||
#define IMX8QXP_ADC_TCTRL_TCMD_MASK GENMASK(31, 24)
|
||||
#define IMX8QXP_ADC_TCTRL_TDLY_MASK GENMASK(23, 16)
|
||||
#define IMX8QXP_ADC_TCTRL_TPRI_MASK GENMASK(15, 8)
|
||||
#define IMX8QXP_ADC_TCTRL_HTEN_MASK GENMASK(7, 0)
|
||||
#define IMX8QXP_ADC_CMDL_CSCALE_MASK GENMASK(13, 8)
|
||||
#define IMX8QXP_ADC_CMDL_MODE_MASK BIT(7)
|
||||
#define IMX8QXP_ADC_CMDL_DIFF_MASK BIT(6)
|
||||
#define IMX8QXP_ADC_CMDL_ABSEL_MASK BIT(5)
|
||||
#define IMX8QXP_ADC_CMDL_ADCH_MASK GENMASK(2, 0)
|
||||
#define IMX8QXP_ADC_CMDH_NEXT_MASK GENMASK(31, 24)
|
||||
#define IMX8QXP_ADC_CMDH_LOOP_MASK GENMASK(23, 16)
|
||||
#define IMX8QXP_ADC_CMDH_AVGS_MASK GENMASK(15, 12)
|
||||
#define IMX8QXP_ADC_CMDH_STS_MASK BIT(8)
|
||||
#define IMX8QXP_ADC_CMDH_LWI_MASK GENMASK(7, 7)
|
||||
#define IMX8QXP_ADC_CMDH_CMPEN_MASK GENMASK(0, 0)
|
||||
#define IMX8QXP_ADC_CFG_PWREN_MASK BIT(28)
|
||||
#define IMX8QXP_ADC_CFG_PUDLY_MASK GENMASK(23, 16)
|
||||
#define IMX8QXP_ADC_CFG_REFSEL_MASK GENMASK(7, 6)
|
||||
#define IMX8QXP_ADC_CFG_PWRSEL_MASK GENMASK(5, 4)
|
||||
#define IMX8QXP_ADC_CFG_TPRICTRL_MASK GENMASK(3, 0)
|
||||
#define IMX8QXP_ADC_FCTRL_FWMARK_MASK GENMASK(20, 16)
|
||||
#define IMX8QXP_ADC_FCTRL_FCOUNT_MASK GENMASK(4, 0)
|
||||
#define IMX8QXP_ADC_RESFIFO_VAL_MASK GENMASK(18, 3)
|
||||
|
||||
/* ADC PARAMETER*/
|
||||
#define IMX8QXP_ADC_CMDL_CHANNEL_SCALE_FULL GENMASK(5, 0)
|
||||
#define IMX8QXP_ADC_CMDL_SEL_A_A_B_CHANNEL 0
|
||||
#define IMX8QXP_ADC_CMDL_STANDARD_RESOLUTION 0
|
||||
#define IMX8QXP_ADC_CMDL_MODE_SINGLE 0
|
||||
#define IMX8QXP_ADC_CMDH_LWI_INCREMENT_DIS 0
|
||||
#define IMX8QXP_ADC_CMDH_CMPEN_DIS 0
|
||||
#define IMX8QXP_ADC_PAUSE_EN BIT(31)
|
||||
#define IMX8QXP_ADC_TCTRL_TPRI_PRIORITY_HIGH 0
|
||||
|
||||
#define IMX8QXP_ADC_TCTRL_HTEN_HW_TIRG_DIS 0
|
||||
|
||||
#define IMX8QXP_ADC_TIMEOUT msecs_to_jiffies(100)
|
||||
|
||||
struct imx8qxp_adc {
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
struct clk *clk;
|
||||
struct clk *ipg_clk;
|
||||
struct regulator *vref;
|
||||
/* Serialise ADC channel reads */
|
||||
struct mutex lock;
|
||||
struct completion completion;
|
||||
};
|
||||
|
||||
#define IMX8QXP_ADC_CHAN(_idx) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.channel = (_idx), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec imx8qxp_adc_iio_channels[] = {
|
||||
IMX8QXP_ADC_CHAN(0),
|
||||
IMX8QXP_ADC_CHAN(1),
|
||||
IMX8QXP_ADC_CHAN(2),
|
||||
IMX8QXP_ADC_CHAN(3),
|
||||
IMX8QXP_ADC_CHAN(4),
|
||||
IMX8QXP_ADC_CHAN(5),
|
||||
IMX8QXP_ADC_CHAN(6),
|
||||
IMX8QXP_ADC_CHAN(7),
|
||||
};
|
||||
|
||||
static void imx8qxp_adc_reset(struct imx8qxp_adc *adc)
|
||||
{
|
||||
u32 ctrl;
|
||||
|
||||
/*software reset, need to clear the set bit*/
|
||||
ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_CTRL);
|
||||
ctrl |= FIELD_PREP(IMX8QXP_ADC_CTRL_SOFTWARE_RESET_MASK, 1);
|
||||
writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL);
|
||||
udelay(10);
|
||||
ctrl &= ~FIELD_PREP(IMX8QXP_ADC_CTRL_SOFTWARE_RESET_MASK, 1);
|
||||
writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL);
|
||||
|
||||
/* reset the fifo */
|
||||
ctrl |= FIELD_PREP(IMX8QXP_ADC_CTRL_FIFO_RESET_MASK, 1);
|
||||
writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL);
|
||||
}
|
||||
|
||||
static void imx8qxp_adc_reg_config(struct imx8qxp_adc *adc, int channel)
|
||||
{
|
||||
u32 adc_cfg, adc_tctrl, adc_cmdl, adc_cmdh;
|
||||
|
||||
/* ADC configuration */
|
||||
adc_cfg = FIELD_PREP(IMX8QXP_ADC_CFG_PWREN_MASK, 1) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CFG_PUDLY_MASK, 0x80)|
|
||||
FIELD_PREP(IMX8QXP_ADC_CFG_REFSEL_MASK, 0) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CFG_PWRSEL_MASK, 3) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CFG_TPRICTRL_MASK, 0);
|
||||
writel(adc_cfg, adc->regs + IMX8QXP_ADR_ADC_CFG);
|
||||
|
||||
/* config the trigger control */
|
||||
adc_tctrl = FIELD_PREP(IMX8QXP_ADC_TCTRL_TCMD_MASK, 1) |
|
||||
FIELD_PREP(IMX8QXP_ADC_TCTRL_TDLY_MASK, 0) |
|
||||
FIELD_PREP(IMX8QXP_ADC_TCTRL_TPRI_MASK, IMX8QXP_ADC_TCTRL_TPRI_PRIORITY_HIGH) |
|
||||
FIELD_PREP(IMX8QXP_ADC_TCTRL_HTEN_MASK, IMX8QXP_ADC_TCTRL_HTEN_HW_TIRG_DIS);
|
||||
writel(adc_tctrl, adc->regs + IMX8QXP_ADR_ADC_TCTRL(0));
|
||||
|
||||
/* config the cmd */
|
||||
adc_cmdl = FIELD_PREP(IMX8QXP_ADC_CMDL_CSCALE_MASK, IMX8QXP_ADC_CMDL_CHANNEL_SCALE_FULL) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CMDL_MODE_MASK, IMX8QXP_ADC_CMDL_STANDARD_RESOLUTION) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CMDL_DIFF_MASK, IMX8QXP_ADC_CMDL_MODE_SINGLE) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CMDL_ABSEL_MASK, IMX8QXP_ADC_CMDL_SEL_A_A_B_CHANNEL) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CMDL_ADCH_MASK, channel);
|
||||
writel(adc_cmdl, adc->regs + IMX8QXP_ADR_ADC_CMDL(0));
|
||||
|
||||
adc_cmdh = FIELD_PREP(IMX8QXP_ADC_CMDH_NEXT_MASK, 0) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CMDH_LOOP_MASK, 0) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CMDH_AVGS_MASK, 7) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CMDH_STS_MASK, 0) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CMDH_LWI_MASK, IMX8QXP_ADC_CMDH_LWI_INCREMENT_DIS) |
|
||||
FIELD_PREP(IMX8QXP_ADC_CMDH_CMPEN_MASK, IMX8QXP_ADC_CMDH_CMPEN_DIS);
|
||||
writel(adc_cmdh, adc->regs + IMX8QXP_ADR_ADC_CMDH(0));
|
||||
}
|
||||
|
||||
static void imx8qxp_adc_fifo_config(struct imx8qxp_adc *adc)
|
||||
{
|
||||
u32 fifo_ctrl, interrupt_en;
|
||||
|
||||
fifo_ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_FCTRL);
|
||||
fifo_ctrl &= ~IMX8QXP_ADC_FCTRL_FWMARK_MASK;
|
||||
/* set the watermark level to 1 */
|
||||
fifo_ctrl |= FIELD_PREP(IMX8QXP_ADC_FCTRL_FWMARK_MASK, 0);
|
||||
writel(fifo_ctrl, adc->regs + IMX8QXP_ADR_ADC_FCTRL);
|
||||
|
||||
/* FIFO Watermark Interrupt Enable */
|
||||
interrupt_en = readl(adc->regs + IMX8QXP_ADR_ADC_IE);
|
||||
interrupt_en |= FIELD_PREP(IMX8QXP_ADC_IE_FWMIE_MASK, 1);
|
||||
writel(interrupt_en, adc->regs + IMX8QXP_ADR_ADC_IE);
|
||||
}
|
||||
|
||||
static void imx8qxp_adc_disable(struct imx8qxp_adc *adc)
|
||||
{
|
||||
u32 ctrl;
|
||||
|
||||
ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_CTRL);
|
||||
ctrl &= ~FIELD_PREP(IMX8QXP_ADC_CTRL_ADC_EN_MASK, 1);
|
||||
writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL);
|
||||
}
|
||||
|
||||
static int imx8qxp_adc_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct imx8qxp_adc *adc = iio_priv(indio_dev);
|
||||
struct device *dev = adc->dev;
|
||||
|
||||
u32 ctrl, vref_uv;
|
||||
long ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
pm_runtime_get_sync(dev);
|
||||
|
||||
mutex_lock(&adc->lock);
|
||||
reinit_completion(&adc->completion);
|
||||
|
||||
imx8qxp_adc_reg_config(adc, chan->channel);
|
||||
|
||||
imx8qxp_adc_fifo_config(adc);
|
||||
|
||||
/* adc enable */
|
||||
ctrl = readl(adc->regs + IMX8QXP_ADR_ADC_CTRL);
|
||||
ctrl |= FIELD_PREP(IMX8QXP_ADC_CTRL_ADC_EN_MASK, 1);
|
||||
writel(ctrl, adc->regs + IMX8QXP_ADR_ADC_CTRL);
|
||||
/* adc start */
|
||||
writel(1, adc->regs + IMX8QXP_ADR_ADC_SWTRIG);
|
||||
|
||||
ret = wait_for_completion_interruptible_timeout(&adc->completion,
|
||||
IMX8QXP_ADC_TIMEOUT);
|
||||
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_sync_autosuspend(dev);
|
||||
|
||||
if (ret == 0) {
|
||||
mutex_unlock(&adc->lock);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
if (ret < 0) {
|
||||
mutex_unlock(&adc->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*val = FIELD_GET(IMX8QXP_ADC_RESFIFO_VAL_MASK,
|
||||
readl(adc->regs + IMX8QXP_ADR_ADC_RESFIFO));
|
||||
|
||||
mutex_unlock(&adc->lock);
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
vref_uv = regulator_get_voltage(adc->vref);
|
||||
*val = vref_uv / 1000;
|
||||
*val2 = 12;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*val = clk_get_rate(adc->clk) / 3;
|
||||
return IIO_VAL_INT;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t imx8qxp_adc_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct imx8qxp_adc *adc = dev_id;
|
||||
u32 fifo_count;
|
||||
|
||||
fifo_count = FIELD_GET(IMX8QXP_ADC_FCTRL_FCOUNT_MASK,
|
||||
readl(adc->regs + IMX8QXP_ADR_ADC_FCTRL));
|
||||
|
||||
if (fifo_count)
|
||||
complete(&adc->completion);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int imx8qxp_adc_reg_access(struct iio_dev *indio_dev, unsigned int reg,
|
||||
unsigned int writeval, unsigned int *readval)
|
||||
{
|
||||
struct imx8qxp_adc *adc = iio_priv(indio_dev);
|
||||
struct device *dev = adc->dev;
|
||||
|
||||
if (!readval || reg % 4 || reg > IMX8QXP_ADR_ADC_TST)
|
||||
return -EINVAL;
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
|
||||
*readval = readl(adc->regs + reg);
|
||||
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_sync_autosuspend(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_info imx8qxp_adc_iio_info = {
|
||||
.read_raw = &imx8qxp_adc_read_raw,
|
||||
.debugfs_reg_access = &imx8qxp_adc_reg_access,
|
||||
};
|
||||
|
||||
static int imx8qxp_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct imx8qxp_adc *adc;
|
||||
struct iio_dev *indio_dev;
|
||||
struct device *dev = &pdev->dev;
|
||||
int irq;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
|
||||
if (!indio_dev) {
|
||||
dev_err(dev, "Failed allocating iio device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
adc = iio_priv(indio_dev);
|
||||
adc->dev = dev;
|
||||
|
||||
mutex_init(&adc->lock);
|
||||
adc->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(adc->regs))
|
||||
return PTR_ERR(adc->regs);
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
adc->clk = devm_clk_get(dev, "per");
|
||||
if (IS_ERR(adc->clk))
|
||||
return dev_err_probe(dev, PTR_ERR(adc->clk), "Failed getting clock\n");
|
||||
|
||||
adc->ipg_clk = devm_clk_get(dev, "ipg");
|
||||
if (IS_ERR(adc->ipg_clk))
|
||||
return dev_err_probe(dev, PTR_ERR(adc->ipg_clk), "Failed getting clock\n");
|
||||
|
||||
adc->vref = devm_regulator_get(dev, "vref");
|
||||
if (IS_ERR(adc->vref))
|
||||
return dev_err_probe(dev, PTR_ERR(adc->vref), "Failed getting reference voltage\n");
|
||||
|
||||
ret = regulator_enable(adc->vref);
|
||||
if (ret) {
|
||||
dev_err(dev, "Can't enable adc reference top voltage\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
init_completion(&adc->completion);
|
||||
|
||||
indio_dev->name = ADC_DRIVER_NAME;
|
||||
indio_dev->info = &imx8qxp_adc_iio_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = imx8qxp_adc_iio_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(imx8qxp_adc_iio_channels);
|
||||
|
||||
ret = clk_prepare_enable(adc->clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Could not prepare or enable the clock.\n");
|
||||
goto error_regulator_disable;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(adc->ipg_clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Could not prepare or enable the clock.\n");
|
||||
goto error_adc_clk_disable;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, irq, imx8qxp_adc_isr, 0, ADC_DRIVER_NAME, adc);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed requesting irq, irq = %d\n", irq);
|
||||
goto error_ipg_clk_disable;
|
||||
}
|
||||
|
||||
imx8qxp_adc_reset(adc);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
imx8qxp_adc_disable(adc);
|
||||
dev_err(dev, "Couldn't register the device.\n");
|
||||
goto error_ipg_clk_disable;
|
||||
}
|
||||
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_set_autosuspend_delay(dev, 50);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
error_ipg_clk_disable:
|
||||
clk_disable_unprepare(adc->ipg_clk);
|
||||
error_adc_clk_disable:
|
||||
clk_disable_unprepare(adc->clk);
|
||||
error_regulator_disable:
|
||||
regulator_disable(adc->vref);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx8qxp_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct imx8qxp_adc *adc = iio_priv(indio_dev);
|
||||
struct device *dev = adc->dev;
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
imx8qxp_adc_disable(adc);
|
||||
|
||||
clk_disable_unprepare(adc->clk);
|
||||
clk_disable_unprepare(adc->ipg_clk);
|
||||
regulator_disable(adc->vref);
|
||||
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_put_noidle(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __maybe_unused int imx8qxp_adc_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct imx8qxp_adc *adc = iio_priv(indio_dev);
|
||||
|
||||
imx8qxp_adc_disable(adc);
|
||||
|
||||
clk_disable_unprepare(adc->clk);
|
||||
clk_disable_unprepare(adc->ipg_clk);
|
||||
regulator_disable(adc->vref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __maybe_unused int imx8qxp_adc_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct imx8qxp_adc *adc = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = regulator_enable(adc->vref);
|
||||
if (ret) {
|
||||
dev_err(dev, "Can't enable adc reference top voltage, err = %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(adc->clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Could not prepare or enable clock.\n");
|
||||
goto err_disable_reg;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(adc->ipg_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Could not prepare or enable clock.\n");
|
||||
goto err_unprepare_clk;
|
||||
}
|
||||
|
||||
imx8qxp_adc_reset(adc);
|
||||
|
||||
return 0;
|
||||
|
||||
err_unprepare_clk:
|
||||
clk_disable_unprepare(adc->clk);
|
||||
|
||||
err_disable_reg:
|
||||
regulator_disable(adc->vref);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops imx8qxp_adc_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
|
||||
SET_RUNTIME_PM_OPS(imx8qxp_adc_runtime_suspend, imx8qxp_adc_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct of_device_id imx8qxp_adc_match[] = {
|
||||
{ .compatible = "nxp,imx8qxp-adc", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx8qxp_adc_match);
|
||||
|
||||
static struct platform_driver imx8qxp_adc_driver = {
|
||||
.probe = imx8qxp_adc_probe,
|
||||
.remove = imx8qxp_adc_remove,
|
||||
.driver = {
|
||||
.name = ADC_DRIVER_NAME,
|
||||
.of_match_table = imx8qxp_adc_match,
|
||||
.pm = &imx8qxp_adc_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(imx8qxp_adc_driver);
|
||||
|
||||
MODULE_DESCRIPTION("i.MX8QuadXPlus ADC driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -205,8 +205,6 @@ static int mrfld_adc_probe(struct platform_device *pdev)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
indio_dev->name = pdev->name;
|
||||
|
||||
indio_dev->channels = mrfld_adc_channels;
|
||||
|
@ -214,28 +212,11 @@ static int mrfld_adc_probe(struct platform_device *pdev)
|
|||
indio_dev->info = &mrfld_adc_iio_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = iio_map_array_register(indio_dev, iio_maps);
|
||||
ret = devm_iio_map_array_register(dev, indio_dev, iio_maps);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_iio_device_register(dev, indio_dev);
|
||||
if (ret < 0)
|
||||
goto err_array_unregister;
|
||||
|
||||
return 0;
|
||||
|
||||
err_array_unregister:
|
||||
iio_map_array_unregister(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mrfld_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
|
||||
iio_map_array_unregister(indio_dev);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct platform_device_id mrfld_adc_id_table[] = {
|
||||
|
@ -249,7 +230,6 @@ static struct platform_driver mrfld_adc_driver = {
|
|||
.name = "mrfld_bcove_adc",
|
||||
},
|
||||
.probe = mrfld_adc_probe,
|
||||
.remove = mrfld_adc_remove,
|
||||
.id_table = mrfld_adc_id_table,
|
||||
};
|
||||
module_platform_driver(mrfld_adc_driver);
|
||||
|
|
|
@ -163,7 +163,8 @@ static struct iio_map lp8788_default_iio_maps[] = {
|
|||
{ }
|
||||
};
|
||||
|
||||
static int lp8788_iio_map_register(struct iio_dev *indio_dev,
|
||||
static int lp8788_iio_map_register(struct device *dev,
|
||||
struct iio_dev *indio_dev,
|
||||
struct lp8788_platform_data *pdata,
|
||||
struct lp8788_adc *adc)
|
||||
{
|
||||
|
@ -173,7 +174,7 @@ static int lp8788_iio_map_register(struct iio_dev *indio_dev,
|
|||
map = (!pdata || !pdata->adc_pdata) ?
|
||||
lp8788_default_iio_maps : pdata->adc_pdata;
|
||||
|
||||
ret = iio_map_array_register(indio_dev, map);
|
||||
ret = devm_iio_map_array_register(dev, indio_dev, map);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "iio map err: %d\n", ret);
|
||||
return ret;
|
||||
|
@ -196,9 +197,8 @@ static int lp8788_adc_probe(struct platform_device *pdev)
|
|||
|
||||
adc = iio_priv(indio_dev);
|
||||
adc->lp = lp;
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
ret = lp8788_iio_map_register(indio_dev, lp->pdata, adc);
|
||||
ret = lp8788_iio_map_register(&pdev->dev, indio_dev, lp->pdata, adc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -210,32 +210,11 @@ static int lp8788_adc_probe(struct platform_device *pdev)
|
|||
indio_dev->channels = lp8788_adc_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(lp8788_adc_channels);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "iio dev register err: %d\n", ret);
|
||||
goto err_iio_device;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_iio_device:
|
||||
iio_map_array_unregister(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lp8788_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_map_array_unregister(indio_dev);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(&pdev->dev, indio_dev);
|
||||
}
|
||||
|
||||
static struct platform_driver lp8788_adc_driver = {
|
||||
.probe = lp8788_adc_probe,
|
||||
.remove = lp8788_adc_remove,
|
||||
.driver = {
|
||||
.name = LP8788_DEV_ADC,
|
||||
},
|
||||
|
|
|
@ -60,6 +60,9 @@
|
|||
#define MAX1027_NAVG_32 (0x03 << 2)
|
||||
#define MAX1027_AVG_EN BIT(4)
|
||||
|
||||
/* Device can achieve 300ksps so we assume a 3.33us conversion delay */
|
||||
#define MAX1027_CONVERSION_UDELAY 4
|
||||
|
||||
enum max1027_id {
|
||||
max1027,
|
||||
max1029,
|
||||
|
@ -172,18 +175,53 @@ static const struct iio_chan_spec max1231_channels[] = {
|
|||
MAX1X31_CHANNELS(12),
|
||||
};
|
||||
|
||||
/*
|
||||
* These devices are able to scan from 0 to N, N being the highest voltage
|
||||
* channel requested by the user. The temperature can be included or not,
|
||||
* but cannot be retrieved alone. Based on the below
|
||||
* ->available_scan_masks, the core will select the most appropriate
|
||||
* ->active_scan_mask and the "minimum" number of channels will be
|
||||
* scanned and pushed to the buffers.
|
||||
*
|
||||
* For example, if the user wants channels 1, 4 and 5, all channels from
|
||||
* 0 to 5 will be scanned and pushed to the IIO buffers. The core will then
|
||||
* filter out the unneeded samples based on the ->active_scan_mask that has
|
||||
* been selected and only channels 1, 4 and 5 will be available to the user
|
||||
* in the shared buffer.
|
||||
*/
|
||||
#define MAX1X27_SCAN_MASK_TEMP BIT(0)
|
||||
|
||||
#define MAX1X27_SCAN_MASKS(temp) \
|
||||
GENMASK(1, 1 - (temp)), GENMASK(2, 1 - (temp)), \
|
||||
GENMASK(3, 1 - (temp)), GENMASK(4, 1 - (temp)), \
|
||||
GENMASK(5, 1 - (temp)), GENMASK(6, 1 - (temp)), \
|
||||
GENMASK(7, 1 - (temp)), GENMASK(8, 1 - (temp))
|
||||
|
||||
#define MAX1X29_SCAN_MASKS(temp) \
|
||||
MAX1X27_SCAN_MASKS(temp), \
|
||||
GENMASK(9, 1 - (temp)), GENMASK(10, 1 - (temp)), \
|
||||
GENMASK(11, 1 - (temp)), GENMASK(12, 1 - (temp))
|
||||
|
||||
#define MAX1X31_SCAN_MASKS(temp) \
|
||||
MAX1X29_SCAN_MASKS(temp), \
|
||||
GENMASK(13, 1 - (temp)), GENMASK(14, 1 - (temp)), \
|
||||
GENMASK(15, 1 - (temp)), GENMASK(16, 1 - (temp))
|
||||
|
||||
static const unsigned long max1027_available_scan_masks[] = {
|
||||
0x000001ff,
|
||||
MAX1X27_SCAN_MASKS(0),
|
||||
MAX1X27_SCAN_MASKS(1),
|
||||
0x00000000,
|
||||
};
|
||||
|
||||
static const unsigned long max1029_available_scan_masks[] = {
|
||||
0x00001fff,
|
||||
MAX1X29_SCAN_MASKS(0),
|
||||
MAX1X29_SCAN_MASKS(1),
|
||||
0x00000000,
|
||||
};
|
||||
|
||||
static const unsigned long max1031_available_scan_masks[] = {
|
||||
0x0001ffff,
|
||||
MAX1X31_SCAN_MASKS(0),
|
||||
MAX1X31_SCAN_MASKS(1),
|
||||
0x00000000,
|
||||
};
|
||||
|
||||
|
@ -232,10 +270,65 @@ struct max1027_state {
|
|||
struct iio_trigger *trig;
|
||||
__be16 *buffer;
|
||||
struct mutex lock;
|
||||
struct completion complete;
|
||||
|
||||
u8 reg ____cacheline_aligned;
|
||||
};
|
||||
|
||||
static int max1027_wait_eoc(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct max1027_state *st = iio_priv(indio_dev);
|
||||
unsigned int conversion_time = MAX1027_CONVERSION_UDELAY;
|
||||
int ret;
|
||||
|
||||
if (st->spi->irq) {
|
||||
ret = wait_for_completion_timeout(&st->complete,
|
||||
msecs_to_jiffies(1000));
|
||||
reinit_completion(&st->complete);
|
||||
if (!ret)
|
||||
return -ETIMEDOUT;
|
||||
} else {
|
||||
if (indio_dev->active_scan_mask)
|
||||
conversion_time *= hweight32(*indio_dev->active_scan_mask);
|
||||
|
||||
usleep_range(conversion_time, conversion_time * 2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Scan from chan 0 to the highest requested channel. Include temperature on demand. */
|
||||
static int max1027_configure_chans_and_start(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct max1027_state *st = iio_priv(indio_dev);
|
||||
|
||||
st->reg = MAX1027_CONV_REG | MAX1027_SCAN_0_N;
|
||||
st->reg |= MAX1027_CHAN(fls(*indio_dev->active_scan_mask) - 2);
|
||||
if (*indio_dev->active_scan_mask & MAX1X27_SCAN_MASK_TEMP)
|
||||
st->reg |= MAX1027_TEMP;
|
||||
|
||||
return spi_write(st->spi, &st->reg, 1);
|
||||
}
|
||||
|
||||
static int max1027_enable_trigger(struct iio_dev *indio_dev, bool enable)
|
||||
{
|
||||
struct max1027_state *st = iio_priv(indio_dev);
|
||||
|
||||
st->reg = MAX1027_SETUP_REG | MAX1027_REF_MODE2;
|
||||
|
||||
/*
|
||||
* Start acquisition on:
|
||||
* MODE0: external hardware trigger wired to the cnvst input pin
|
||||
* MODE2: conversion register write
|
||||
*/
|
||||
if (enable)
|
||||
st->reg |= MAX1027_CKS_MODE0;
|
||||
else
|
||||
st->reg |= MAX1027_CKS_MODE2;
|
||||
|
||||
return spi_write(st->spi, &st->reg, 1);
|
||||
}
|
||||
|
||||
static int max1027_read_single_value(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val)
|
||||
|
@ -243,19 +336,9 @@ static int max1027_read_single_value(struct iio_dev *indio_dev,
|
|||
int ret;
|
||||
struct max1027_state *st = iio_priv(indio_dev);
|
||||
|
||||
if (iio_buffer_enabled(indio_dev)) {
|
||||
dev_warn(&indio_dev->dev, "trigger mode already enabled");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* Start acquisition on conversion register write */
|
||||
st->reg = MAX1027_SETUP_REG | MAX1027_REF_MODE2 | MAX1027_CKS_MODE2;
|
||||
ret = spi_write(st->spi, &st->reg, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(&indio_dev->dev,
|
||||
"Failed to configure setup register\n");
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Configure conversion register with the requested chan */
|
||||
st->reg = MAX1027_CONV_REG | MAX1027_CHAN(chan->channel) |
|
||||
|
@ -266,18 +349,24 @@ static int max1027_read_single_value(struct iio_dev *indio_dev,
|
|||
if (ret < 0) {
|
||||
dev_err(&indio_dev->dev,
|
||||
"Failed to configure conversion register\n");
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* For an unknown reason, when we use the mode "10" (write
|
||||
* conversion register), the interrupt doesn't occur every time.
|
||||
* So we just wait 1 ms.
|
||||
* So we just wait the maximum conversion time and deliver the value.
|
||||
*/
|
||||
mdelay(1);
|
||||
ret = max1027_wait_eoc(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read result */
|
||||
ret = spi_read(st->spi, st->buffer, (chan->type == IIO_TEMP) ? 4 : 2);
|
||||
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -327,8 +416,8 @@ static int max1027_read_raw(struct iio_dev *indio_dev,
|
|||
}
|
||||
|
||||
static int max1027_debugfs_reg_access(struct iio_dev *indio_dev,
|
||||
unsigned reg, unsigned writeval,
|
||||
unsigned *readval)
|
||||
unsigned int reg, unsigned int writeval,
|
||||
unsigned int *readval)
|
||||
{
|
||||
struct max1027_state *st = iio_priv(indio_dev);
|
||||
u8 *val = (u8 *)st->buffer;
|
||||
|
@ -343,61 +432,96 @@ static int max1027_debugfs_reg_access(struct iio_dev *indio_dev,
|
|||
return spi_write(st->spi, val, 1);
|
||||
}
|
||||
|
||||
static int max1027_validate_trigger(struct iio_dev *indio_dev,
|
||||
struct iio_trigger *trig)
|
||||
{
|
||||
struct max1027_state *st = iio_priv(indio_dev);
|
||||
|
||||
if (st->trig != trig)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max1027_set_trigger_state(struct iio_trigger *trig, bool state)
|
||||
static int max1027_set_cnvst_trigger_state(struct iio_trigger *trig, bool state)
|
||||
{
|
||||
struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
|
||||
struct max1027_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (state) {
|
||||
/* Start acquisition on cnvst */
|
||||
st->reg = MAX1027_SETUP_REG | MAX1027_CKS_MODE0 |
|
||||
MAX1027_REF_MODE2;
|
||||
ret = spi_write(st->spi, &st->reg, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
/*
|
||||
* In order to disable the convst trigger, start acquisition on
|
||||
* conversion register write, which basically disables triggering
|
||||
* conversions upon cnvst changes and thus has the effect of disabling
|
||||
* the external hardware trigger.
|
||||
*/
|
||||
ret = max1027_enable_trigger(indio_dev, state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Scan from 0 to max */
|
||||
st->reg = MAX1027_CONV_REG | MAX1027_CHAN(0) |
|
||||
MAX1027_SCAN_N_M | MAX1027_TEMP;
|
||||
ret = spi_write(st->spi, &st->reg, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
/* Start acquisition on conversion register write */
|
||||
st->reg = MAX1027_SETUP_REG | MAX1027_CKS_MODE2 |
|
||||
MAX1027_REF_MODE2;
|
||||
ret = spi_write(st->spi, &st->reg, 1);
|
||||
if (ret < 0)
|
||||
if (state) {
|
||||
ret = max1027_configure_chans_and_start(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max1027_read_scan(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct max1027_state *st = iio_priv(indio_dev);
|
||||
unsigned int scanned_chans;
|
||||
int ret;
|
||||
|
||||
scanned_chans = fls(*indio_dev->active_scan_mask) - 1;
|
||||
if (*indio_dev->active_scan_mask & MAX1X27_SCAN_MASK_TEMP)
|
||||
scanned_chans++;
|
||||
|
||||
/* fill buffer with all channel */
|
||||
ret = spi_read(st->spi, st->buffer, scanned_chans * 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
iio_push_to_buffers(indio_dev, st->buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t max1027_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_dev *indio_dev = private;
|
||||
struct max1027_state *st = iio_priv(indio_dev);
|
||||
|
||||
/*
|
||||
* If buffers are disabled (raw read) or when using external triggers,
|
||||
* we just need to unlock the waiters which will then handle the data.
|
||||
*
|
||||
* When using the internal trigger, we must hand-off the choice of the
|
||||
* handler to the core which will then lookup through the interrupt tree
|
||||
* for the right handler registered with iio_triggered_buffer_setup()
|
||||
* to execute, as this trigger might very well be used in conjunction
|
||||
* with another device. The core will then call the relevant handler to
|
||||
* perform the data processing step.
|
||||
*/
|
||||
if (!iio_buffer_enabled(indio_dev))
|
||||
complete(&st->complete);
|
||||
else
|
||||
iio_trigger_poll(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t max1027_trigger_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_poll_func *pf = private;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct max1027_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
pr_debug("%s(irq=%d, private=0x%p)\n", __func__, irq, private);
|
||||
if (!iio_trigger_using_own(indio_dev)) {
|
||||
ret = max1027_configure_chans_and_start(indio_dev);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* fill buffer with all channel */
|
||||
spi_read(st->spi, st->buffer, indio_dev->masklength * 2);
|
||||
/* This is a threaded handler, it is fine to wait for an IRQ */
|
||||
ret = max1027_wait_eoc(indio_dev);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
iio_push_to_buffers(indio_dev, st->buffer);
|
||||
ret = max1027_read_scan(indio_dev);
|
||||
out:
|
||||
if (ret)
|
||||
dev_err(&indio_dev->dev,
|
||||
"Cannot read scanned values (%d)\n", ret);
|
||||
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
|
@ -406,12 +530,11 @@ static irqreturn_t max1027_trigger_handler(int irq, void *private)
|
|||
|
||||
static const struct iio_trigger_ops max1027_trigger_ops = {
|
||||
.validate_device = &iio_trigger_validate_own_device,
|
||||
.set_trigger_state = &max1027_set_trigger_state,
|
||||
.set_trigger_state = &max1027_set_cnvst_trigger_state,
|
||||
};
|
||||
|
||||
static const struct iio_info max1027_info = {
|
||||
.read_raw = &max1027_read_raw,
|
||||
.validate_trigger = &max1027_validate_trigger,
|
||||
.debugfs_reg_access = &max1027_debugfs_reg_access,
|
||||
};
|
||||
|
||||
|
@ -421,10 +544,8 @@ static int max1027_probe(struct spi_device *spi)
|
|||
struct iio_dev *indio_dev;
|
||||
struct max1027_state *st;
|
||||
|
||||
pr_debug("%s: probe(spi = 0x%p)\n", __func__, spi);
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
|
||||
if (indio_dev == NULL) {
|
||||
if (!indio_dev) {
|
||||
pr_err("Can't allocate iio device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
@ -434,6 +555,7 @@ static int max1027_probe(struct spi_device *spi)
|
|||
st->info = &max1027_chip_info_tbl[spi_get_device_id(spi)->driver_data];
|
||||
|
||||
mutex_init(&st->lock);
|
||||
init_completion(&st->complete);
|
||||
|
||||
indio_dev->name = spi_get_device_id(spi)->name;
|
||||
indio_dev->info = &max1027_info;
|
||||
|
@ -443,26 +565,26 @@ static int max1027_probe(struct spi_device *spi)
|
|||
indio_dev->available_scan_masks = st->info->available_scan_masks;
|
||||
|
||||
st->buffer = devm_kmalloc_array(&indio_dev->dev,
|
||||
indio_dev->num_channels, 2,
|
||||
GFP_KERNEL);
|
||||
if (st->buffer == NULL) {
|
||||
dev_err(&indio_dev->dev, "Can't allocate buffer\n");
|
||||
indio_dev->num_channels, 2,
|
||||
GFP_KERNEL);
|
||||
if (!st->buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Enable triggered buffers */
|
||||
ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
|
||||
&iio_pollfunc_store_time,
|
||||
&max1027_trigger_handler,
|
||||
NULL);
|
||||
if (ret < 0) {
|
||||
dev_err(&indio_dev->dev, "Failed to setup buffer\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* If there is an EOC interrupt, register the cnvst hardware trigger */
|
||||
if (spi->irq) {
|
||||
ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
|
||||
&iio_pollfunc_store_time,
|
||||
&max1027_trigger_handler,
|
||||
NULL);
|
||||
if (ret < 0) {
|
||||
dev_err(&indio_dev->dev, "Failed to setup buffer\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
st->trig = devm_iio_trigger_alloc(&spi->dev, "%s-trigger",
|
||||
indio_dev->name);
|
||||
if (st->trig == NULL) {
|
||||
if (!st->trig) {
|
||||
ret = -ENOMEM;
|
||||
dev_err(&indio_dev->dev,
|
||||
"Failed to allocate iio trigger\n");
|
||||
|
@ -479,12 +601,9 @@ static int max1027_probe(struct spi_device *spi)
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(&spi->dev, spi->irq,
|
||||
iio_trigger_generic_data_rdy_poll,
|
||||
NULL,
|
||||
IRQF_TRIGGER_FALLING,
|
||||
spi->dev.driver->name,
|
||||
st->trig);
|
||||
ret = devm_request_irq(&spi->dev, spi->irq, max1027_handler,
|
||||
IRQF_TRIGGER_FALLING,
|
||||
spi->dev.driver->name, indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&indio_dev->dev, "Failed to allocate IRQ.\n");
|
||||
return ret;
|
||||
|
@ -507,6 +626,11 @@ static int max1027_probe(struct spi_device *spi)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Assume conversion on register write for now */
|
||||
ret = max1027_enable_trigger(indio_dev, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_iio_device_register(&spi->dev, indio_dev);
|
||||
}
|
||||
|
||||
|
|
|
@ -1577,6 +1577,11 @@ static const struct of_device_id max1363_of_match[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(of, max1363_of_match);
|
||||
|
||||
static void max1363_reg_disable(void *reg)
|
||||
{
|
||||
regulator_disable(reg);
|
||||
}
|
||||
|
||||
static int max1363_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
|
@ -1590,7 +1595,8 @@ static int max1363_probe(struct i2c_client *client,
|
|||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = iio_map_array_register(indio_dev, client->dev.platform_data);
|
||||
ret = devm_iio_map_array_register(&client->dev, indio_dev,
|
||||
client->dev.platform_data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -1598,17 +1604,16 @@ static int max1363_probe(struct i2c_client *client,
|
|||
|
||||
mutex_init(&st->lock);
|
||||
st->reg = devm_regulator_get(&client->dev, "vcc");
|
||||
if (IS_ERR(st->reg)) {
|
||||
ret = PTR_ERR(st->reg);
|
||||
goto error_unregister_map;
|
||||
}
|
||||
if (IS_ERR(st->reg))
|
||||
return PTR_ERR(st->reg);
|
||||
|
||||
ret = regulator_enable(st->reg);
|
||||
if (ret)
|
||||
goto error_unregister_map;
|
||||
return ret;
|
||||
|
||||
/* this is only used for device removal purposes */
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
ret = devm_add_action_or_reset(&client->dev, max1363_reg_disable, st->reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->chip_info = device_get_match_data(&client->dev);
|
||||
if (!st->chip_info)
|
||||
|
@ -1622,13 +1627,17 @@ static int max1363_probe(struct i2c_client *client,
|
|||
|
||||
ret = regulator_enable(vref);
|
||||
if (ret)
|
||||
goto error_disable_reg;
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(&client->dev, max1363_reg_disable, vref);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->vref = vref;
|
||||
vref_uv = regulator_get_voltage(vref);
|
||||
if (vref_uv <= 0) {
|
||||
ret = -EINVAL;
|
||||
goto error_disable_reg;
|
||||
}
|
||||
if (vref_uv <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
st->vref_uv = vref_uv;
|
||||
}
|
||||
|
||||
|
@ -1640,13 +1649,12 @@ static int max1363_probe(struct i2c_client *client,
|
|||
st->send = max1363_smbus_send;
|
||||
st->recv = max1363_smbus_recv;
|
||||
} else {
|
||||
ret = -EOPNOTSUPP;
|
||||
goto error_disable_reg;
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
ret = max1363_alloc_scan_masks(indio_dev);
|
||||
if (ret)
|
||||
goto error_disable_reg;
|
||||
return ret;
|
||||
|
||||
indio_dev->name = id->name;
|
||||
indio_dev->channels = st->chip_info->channels;
|
||||
|
@ -1655,12 +1663,12 @@ static int max1363_probe(struct i2c_client *client,
|
|||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
ret = max1363_initial_setup(st);
|
||||
if (ret < 0)
|
||||
goto error_disable_reg;
|
||||
return ret;
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
&max1363_trigger_handler, NULL);
|
||||
ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, NULL,
|
||||
&max1363_trigger_handler, NULL);
|
||||
if (ret)
|
||||
goto error_disable_reg;
|
||||
return ret;
|
||||
|
||||
if (client->irq) {
|
||||
ret = devm_request_threaded_irq(&client->dev, st->client->irq,
|
||||
|
@ -1671,39 +1679,10 @@ static int max1363_probe(struct i2c_client *client,
|
|||
indio_dev);
|
||||
|
||||
if (ret)
|
||||
goto error_uninit_buffer;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
goto error_uninit_buffer;
|
||||
|
||||
return 0;
|
||||
|
||||
error_uninit_buffer:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
error_disable_reg:
|
||||
if (st->vref)
|
||||
regulator_disable(st->vref);
|
||||
regulator_disable(st->reg);
|
||||
error_unregister_map:
|
||||
iio_map_array_unregister(indio_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max1363_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct max1363_state *st = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
if (st->vref)
|
||||
regulator_disable(st->vref);
|
||||
regulator_disable(st->reg);
|
||||
iio_map_array_unregister(indio_dev);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(&client->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max1363_id[] = {
|
||||
|
@ -1756,7 +1735,6 @@ static struct i2c_driver max1363_driver = {
|
|||
.of_match_table = max1363_of_match,
|
||||
},
|
||||
.probe = max1363_probe,
|
||||
.remove = max1363_remove,
|
||||
.id_table = max1363_id,
|
||||
};
|
||||
module_i2c_driver(max1363_driver);
|
||||
|
|
|
@ -428,8 +428,6 @@ static int nau7802_probe(struct i2c_client *client,
|
|||
|
||||
st = iio_priv(indio_dev);
|
||||
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
indio_dev->name = dev_name(&client->dev);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->info = &nau7802_info;
|
||||
|
@ -495,13 +493,13 @@ static int nau7802_probe(struct i2c_client *client,
|
|||
* will enable them back when we will need them..
|
||||
*/
|
||||
if (client->irq) {
|
||||
ret = request_threaded_irq(client->irq,
|
||||
NULL,
|
||||
nau7802_eoc_trigger,
|
||||
IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
|
||||
IRQF_NO_AUTOEN,
|
||||
client->dev.driver->name,
|
||||
indio_dev);
|
||||
ret = devm_request_threaded_irq(&client->dev, client->irq,
|
||||
NULL,
|
||||
nau7802_eoc_trigger,
|
||||
IRQF_TRIGGER_HIGH | IRQF_ONESHOT |
|
||||
IRQF_NO_AUTOEN,
|
||||
client->dev.driver->name,
|
||||
indio_dev);
|
||||
if (ret) {
|
||||
/*
|
||||
* What may happen here is that our IRQ controller is
|
||||
|
@ -526,7 +524,7 @@ static int nau7802_probe(struct i2c_client *client,
|
|||
ret = i2c_smbus_write_byte_data(st->client, NAU7802_REG_CTRL2,
|
||||
NAU7802_CTRL2_CRS(st->sample_rate));
|
||||
if (ret)
|
||||
goto error_free_irq;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Setup the ADC channels available on the board */
|
||||
|
@ -536,36 +534,7 @@ static int nau7802_probe(struct i2c_client *client,
|
|||
mutex_init(&st->lock);
|
||||
mutex_init(&st->data_lock);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Couldn't register the device.\n");
|
||||
goto error_device_register;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error_device_register:
|
||||
mutex_destroy(&st->lock);
|
||||
mutex_destroy(&st->data_lock);
|
||||
error_free_irq:
|
||||
if (client->irq)
|
||||
free_irq(client->irq, indio_dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nau7802_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct nau7802_state *st = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
mutex_destroy(&st->lock);
|
||||
mutex_destroy(&st->data_lock);
|
||||
if (client->irq)
|
||||
free_irq(client->irq, indio_dev);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(&client->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id nau7802_i2c_id[] = {
|
||||
|
@ -582,7 +551,6 @@ MODULE_DEVICE_TABLE(of, nau7802_dt_ids);
|
|||
|
||||
static struct i2c_driver nau7802_driver = {
|
||||
.probe = nau7802_probe,
|
||||
.remove = nau7802_remove,
|
||||
.id_table = nau7802_i2c_id,
|
||||
.driver = {
|
||||
.name = "nau7802",
|
||||
|
|
|
@ -197,13 +197,6 @@ static struct iio_map rn5t618_maps[] = {
|
|||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static void unregister_map(void *data)
|
||||
{
|
||||
struct iio_dev *iio_dev = (struct iio_dev *) data;
|
||||
|
||||
iio_map_array_unregister(iio_dev);
|
||||
}
|
||||
|
||||
static int rn5t618_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
@ -254,11 +247,7 @@ static int rn5t618_adc_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_map_array_register(iio_dev, rn5t618_maps);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(adc->dev, unregister_map, iio_dev);
|
||||
ret = devm_iio_map_array_register(adc->dev, iio_dev, rn5t618_maps);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
|
|
@ -319,7 +319,6 @@ static int rockchip_saradc_probe(struct platform_device *pdev)
|
|||
struct rockchip_saradc *info = NULL;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct iio_dev *indio_dev = NULL;
|
||||
struct resource *mem;
|
||||
const struct of_device_id *match;
|
||||
int ret;
|
||||
int irq;
|
||||
|
@ -348,8 +347,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
info->regs = devm_ioremap_resource(&pdev->dev, mem);
|
||||
info->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(info->regs))
|
||||
return PTR_ERR(info->regs);
|
||||
|
||||
|
|
|
@ -75,9 +75,9 @@ struct adc108s102_state {
|
|||
* rx_buf: |XX|R0|R1|R2|R3|R4|R5|R6|R7|tt|tt|tt|tt|
|
||||
*
|
||||
* tx_buf: 8 channel read commands, plus 1 dummy command
|
||||
* rx_buf: 1 dummy response, 8 channel responses, plus 64-bit timestamp
|
||||
* rx_buf: 1 dummy response, 8 channel responses
|
||||
*/
|
||||
__be16 rx_buf[13] ____cacheline_aligned;
|
||||
__be16 rx_buf[9] ____cacheline_aligned;
|
||||
__be16 tx_buf[9] ____cacheline_aligned;
|
||||
};
|
||||
|
||||
|
@ -149,9 +149,10 @@ static irqreturn_t adc108s102_trigger_handler(int irq, void *p)
|
|||
goto out_notify;
|
||||
|
||||
/* Skip the dummy response in the first slot */
|
||||
iio_push_to_buffers_with_timestamp(indio_dev,
|
||||
(u8 *)&st->rx_buf[1],
|
||||
iio_get_time_ns(indio_dev));
|
||||
iio_push_to_buffers_with_ts_unaligned(indio_dev,
|
||||
&st->rx_buf[1],
|
||||
st->ring_xfer.len - sizeof(st->rx_buf[1]),
|
||||
iio_get_time_ns(indio_dev));
|
||||
|
||||
out_notify:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
|
|
@ -133,6 +133,11 @@ static const struct iio_info ads8344_info = {
|
|||
.read_raw = ads8344_read_raw,
|
||||
};
|
||||
|
||||
static void ads8344_reg_disable(void *data)
|
||||
{
|
||||
regulator_disable(data);
|
||||
}
|
||||
|
||||
static int ads8344_probe(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
|
@ -161,26 +166,11 @@ static int ads8344_probe(struct spi_device *spi)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
spi_set_drvdata(spi, indio_dev);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret) {
|
||||
regulator_disable(adc->reg);
|
||||
ret = devm_add_action_or_reset(&spi->dev, ads8344_reg_disable, adc->reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ads8344_remove(struct spi_device *spi)
|
||||
{
|
||||
struct iio_dev *indio_dev = spi_get_drvdata(spi);
|
||||
struct ads8344 *adc = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
regulator_disable(adc->reg);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(&spi->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id ads8344_of_match[] = {
|
||||
|
@ -195,7 +185,6 @@ static struct spi_driver ads8344_driver = {
|
|||
.of_match_table = ads8344_of_match,
|
||||
},
|
||||
.probe = ads8344_probe,
|
||||
.remove = ads8344_remove,
|
||||
};
|
||||
module_spi_driver(ads8344_driver);
|
||||
|
||||
|
|
|
@ -900,7 +900,7 @@ static int twl6030_gpadc_probe(struct platform_device *pdev)
|
|||
|
||||
ret = pdata->calibrate(gpadc);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to read calibration registers\n");
|
||||
dev_err(dev, "failed to read calibration registers\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -914,14 +914,14 @@ static int twl6030_gpadc_probe(struct platform_device *pdev)
|
|||
|
||||
ret = twl6030_gpadc_enable_irq(TWL6030_GPADC_RT_SW1_EOC_MASK);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to enable GPADC interrupt\n");
|
||||
dev_err(dev, "failed to enable GPADC interrupt\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = twl_i2c_write_u8(TWL6030_MODULE_ID1, TWL6030_GPADCS,
|
||||
TWL6030_REG_TOGGLE1);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to enable GPADC module\n");
|
||||
dev_err(dev, "failed to enable GPADC module\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* @indio_dev: IIO device structure
|
||||
* @h: Function which will be used as pollfunc top half
|
||||
* @thread: Function which will be used as pollfunc bottom half
|
||||
* @direction: Direction of the data stream (in/out).
|
||||
* @setup_ops: Buffer setup functions to use for this device.
|
||||
* If NULL the default setup functions for triggered
|
||||
* buffers will be used.
|
||||
|
@ -38,6 +39,7 @@
|
|||
int iio_triggered_buffer_setup_ext(struct iio_dev *indio_dev,
|
||||
irqreturn_t (*h)(int irq, void *p),
|
||||
irqreturn_t (*thread)(int irq, void *p),
|
||||
enum iio_buffer_direction direction,
|
||||
const struct iio_buffer_setup_ops *setup_ops,
|
||||
const struct attribute **buffer_attrs)
|
||||
{
|
||||
|
@ -68,6 +70,7 @@ int iio_triggered_buffer_setup_ext(struct iio_dev *indio_dev,
|
|||
/* Flag that polled ring buffering is possible */
|
||||
indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
|
||||
|
||||
buffer->direction = direction;
|
||||
buffer->attrs = buffer_attrs;
|
||||
|
||||
ret = iio_device_attach_buffer(indio_dev, buffer);
|
||||
|
@ -105,13 +108,14 @@ int devm_iio_triggered_buffer_setup_ext(struct device *dev,
|
|||
struct iio_dev *indio_dev,
|
||||
irqreturn_t (*h)(int irq, void *p),
|
||||
irqreturn_t (*thread)(int irq, void *p),
|
||||
enum iio_buffer_direction direction,
|
||||
const struct iio_buffer_setup_ops *ops,
|
||||
const struct attribute **buffer_attrs)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = iio_triggered_buffer_setup_ext(indio_dev, h, thread, ops,
|
||||
buffer_attrs);
|
||||
ret = iio_triggered_buffer_setup_ext(indio_dev, h, thread, direction,
|
||||
ops, buffer_attrs);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
|
@ -138,10 +138,60 @@ static void iio_kfifo_buffer_release(struct iio_buffer *buffer)
|
|||
kfree(kf);
|
||||
}
|
||||
|
||||
static size_t iio_kfifo_buf_space_available(struct iio_buffer *r)
|
||||
{
|
||||
struct iio_kfifo *kf = iio_to_kfifo(r);
|
||||
size_t avail;
|
||||
|
||||
mutex_lock(&kf->user_lock);
|
||||
avail = kfifo_avail(&kf->kf);
|
||||
mutex_unlock(&kf->user_lock);
|
||||
|
||||
return avail;
|
||||
}
|
||||
|
||||
static int iio_kfifo_remove_from(struct iio_buffer *r, void *data)
|
||||
{
|
||||
int ret;
|
||||
struct iio_kfifo *kf = iio_to_kfifo(r);
|
||||
|
||||
if (kfifo_size(&kf->kf) < 1)
|
||||
return -EBUSY;
|
||||
|
||||
ret = kfifo_out(&kf->kf, data, 1);
|
||||
if (ret != 1)
|
||||
return -EBUSY;
|
||||
|
||||
wake_up_interruptible_poll(&r->pollq, EPOLLOUT | EPOLLWRNORM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int iio_kfifo_write(struct iio_buffer *r, size_t n,
|
||||
const char __user *buf)
|
||||
{
|
||||
struct iio_kfifo *kf = iio_to_kfifo(r);
|
||||
int ret, copied;
|
||||
|
||||
mutex_lock(&kf->user_lock);
|
||||
if (!kfifo_initialized(&kf->kf) || n < kfifo_esize(&kf->kf))
|
||||
ret = -EINVAL;
|
||||
else
|
||||
ret = kfifo_from_user(&kf->kf, buf, n, &copied);
|
||||
mutex_unlock(&kf->user_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
static const struct iio_buffer_access_funcs kfifo_access_funcs = {
|
||||
.store_to = &iio_store_to_kfifo,
|
||||
.read = &iio_read_kfifo,
|
||||
.data_available = iio_kfifo_buf_data_available,
|
||||
.remove_from = &iio_kfifo_remove_from,
|
||||
.write = &iio_kfifo_write,
|
||||
.space_available = &iio_kfifo_buf_space_available,
|
||||
.request_update = &iio_request_update_kfifo,
|
||||
.set_bytes_per_datum = &iio_set_bytes_per_datum_kfifo,
|
||||
.set_length = &iio_set_length_kfifo,
|
||||
|
|
|
@ -118,6 +118,19 @@ config SCD30_SERIAL
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called scd30_serial.
|
||||
|
||||
config SCD4X
|
||||
tristate "SCD4X carbon dioxide sensor driver"
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
depends on I2C
|
||||
select CRC8
|
||||
help
|
||||
Say Y here to build support for the Sensirion SCD4X sensor with carbon
|
||||
dioxide, relative humidity and temperature sensing capabilities.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called scd4x.
|
||||
|
||||
config SENSIRION_SGP30
|
||||
tristate "Sensirion SGPxx gas sensors"
|
||||
depends on I2C
|
||||
|
@ -170,6 +183,17 @@ config SPS30_SERIAL
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called sps30_serial.
|
||||
|
||||
config SENSEAIR_SUNRISE_CO2
|
||||
tristate "Senseair Sunrise 006-0-0007 CO2 sensor"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say yes here to build support for Senseair Sunrise 006-0-0007 CO2
|
||||
sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called sunrise_co2.
|
||||
|
||||
config VZ89X
|
||||
tristate "SGX Sensortech MiCS VZ89X VOC sensor"
|
||||
depends on I2C
|
||||
|
|
|
@ -15,6 +15,8 @@ obj-$(CONFIG_PMS7003) += pms7003.o
|
|||
obj-$(CONFIG_SCD30_CORE) += scd30_core.o
|
||||
obj-$(CONFIG_SCD30_I2C) += scd30_i2c.o
|
||||
obj-$(CONFIG_SCD30_SERIAL) += scd30_serial.o
|
||||
obj-$(CONFIG_SCD4X) += scd4x.o
|
||||
obj-$(CONFIG_SENSEAIR_SUNRISE_CO2) += sunrise_co2.o
|
||||
obj-$(CONFIG_SENSIRION_SGP30) += sgp30.o
|
||||
obj-$(CONFIG_SENSIRION_SGP40) += sgp40.o
|
||||
obj-$(CONFIG_SPS30) += sps30.o
|
||||
|
|
|
@ -0,0 +1,691 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Sensirion SCD4X carbon dioxide sensor i2c driver
|
||||
*
|
||||
* Copyright (C) 2021 Protonic Holland
|
||||
* Author: Roan van Dijk <roan@protonic.nl>
|
||||
*
|
||||
* I2C slave address: 0x62
|
||||
*
|
||||
* Datasheets:
|
||||
* https://www.sensirion.com/file/datasheet_scd4x
|
||||
*/
|
||||
|
||||
#include <asm/unaligned.h>
|
||||
#include <linux/crc8.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/iio/types.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define SCD4X_CRC8_POLYNOMIAL 0x31
|
||||
#define SCD4X_TIMEOUT_ERR 1000
|
||||
#define SCD4X_READ_BUF_SIZE 9
|
||||
#define SCD4X_COMMAND_BUF_SIZE 2
|
||||
#define SCD4X_WRITE_BUF_SIZE 5
|
||||
#define SCD4X_FRC_MIN_PPM 0
|
||||
#define SCD4X_FRC_MAX_PPM 2000
|
||||
#define SCD4X_READY_MASK 0x01
|
||||
|
||||
/*Commands SCD4X*/
|
||||
enum scd4x_cmd {
|
||||
CMD_START_MEAS = 0x21b1,
|
||||
CMD_READ_MEAS = 0xec05,
|
||||
CMD_STOP_MEAS = 0x3f86,
|
||||
CMD_SET_TEMP_OFFSET = 0x241d,
|
||||
CMD_GET_TEMP_OFFSET = 0x2318,
|
||||
CMD_FRC = 0x362f,
|
||||
CMD_SET_ASC = 0x2416,
|
||||
CMD_GET_ASC = 0x2313,
|
||||
CMD_GET_DATA_READY = 0xe4b8,
|
||||
};
|
||||
|
||||
enum scd4x_channel_idx {
|
||||
SCD4X_CO2,
|
||||
SCD4X_TEMP,
|
||||
SCD4X_HR,
|
||||
};
|
||||
|
||||
struct scd4x_state {
|
||||
struct i2c_client *client;
|
||||
/* maintain access to device, to prevent concurrent reads/writes */
|
||||
struct mutex lock;
|
||||
struct regulator *vdd;
|
||||
};
|
||||
|
||||
DECLARE_CRC8_TABLE(scd4x_crc8_table);
|
||||
|
||||
static int scd4x_i2c_xfer(struct scd4x_state *state, char *txbuf, int txsize,
|
||||
char *rxbuf, int rxsize)
|
||||
{
|
||||
struct i2c_client *client = state->client;
|
||||
int ret;
|
||||
|
||||
ret = i2c_master_send(client, txbuf, txsize);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != txsize)
|
||||
return -EIO;
|
||||
|
||||
if (rxsize == 0)
|
||||
return 0;
|
||||
|
||||
ret = i2c_master_recv(client, rxbuf, rxsize);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != rxsize)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scd4x_send_command(struct scd4x_state *state, enum scd4x_cmd cmd)
|
||||
{
|
||||
char buf[SCD4X_COMMAND_BUF_SIZE];
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Measurement needs to be stopped before sending commands.
|
||||
* Except stop and start command.
|
||||
*/
|
||||
if ((cmd != CMD_STOP_MEAS) && (cmd != CMD_START_MEAS)) {
|
||||
|
||||
ret = scd4x_send_command(state, CMD_STOP_MEAS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* execution time for stopping measurement */
|
||||
msleep_interruptible(500);
|
||||
}
|
||||
|
||||
put_unaligned_be16(cmd, buf);
|
||||
ret = scd4x_i2c_xfer(state, buf, 2, buf, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if ((cmd != CMD_STOP_MEAS) && (cmd != CMD_START_MEAS)) {
|
||||
ret = scd4x_send_command(state, CMD_START_MEAS);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scd4x_read(struct scd4x_state *state, enum scd4x_cmd cmd,
|
||||
void *response, int response_sz)
|
||||
{
|
||||
struct i2c_client *client = state->client;
|
||||
char buf[SCD4X_READ_BUF_SIZE];
|
||||
char *rsp = response;
|
||||
int i, ret;
|
||||
char crc;
|
||||
|
||||
/*
|
||||
* Measurement needs to be stopped before sending commands.
|
||||
* Except for reading measurement and data ready command.
|
||||
*/
|
||||
if ((cmd != CMD_GET_DATA_READY) && (cmd != CMD_READ_MEAS)) {
|
||||
ret = scd4x_send_command(state, CMD_STOP_MEAS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* execution time for stopping measurement */
|
||||
msleep_interruptible(500);
|
||||
}
|
||||
|
||||
/* CRC byte for every 2 bytes of data */
|
||||
response_sz += response_sz / 2;
|
||||
|
||||
put_unaligned_be16(cmd, buf);
|
||||
ret = scd4x_i2c_xfer(state, buf, 2, buf, response_sz);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < response_sz; i += 3) {
|
||||
crc = crc8(scd4x_crc8_table, buf + i, 2, CRC8_INIT_VALUE);
|
||||
if (crc != buf[i + 2]) {
|
||||
dev_err(&client->dev, "CRC error\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
*rsp++ = buf[i];
|
||||
*rsp++ = buf[i + 1];
|
||||
}
|
||||
|
||||
/* start measurement */
|
||||
if ((cmd != CMD_GET_DATA_READY) && (cmd != CMD_READ_MEAS)) {
|
||||
ret = scd4x_send_command(state, CMD_START_MEAS);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scd4x_write(struct scd4x_state *state, enum scd4x_cmd cmd, uint16_t arg)
|
||||
{
|
||||
char buf[SCD4X_WRITE_BUF_SIZE];
|
||||
int ret;
|
||||
char crc;
|
||||
|
||||
put_unaligned_be16(cmd, buf);
|
||||
put_unaligned_be16(arg, buf + 2);
|
||||
|
||||
crc = crc8(scd4x_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
|
||||
buf[4] = crc;
|
||||
|
||||
/* measurement needs to be stopped before sending commands */
|
||||
ret = scd4x_send_command(state, CMD_STOP_MEAS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* execution time */
|
||||
msleep_interruptible(500);
|
||||
|
||||
ret = scd4x_i2c_xfer(state, buf, SCD4X_WRITE_BUF_SIZE, buf, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* start measurement, except for forced calibration command */
|
||||
if (cmd != CMD_FRC) {
|
||||
ret = scd4x_send_command(state, CMD_START_MEAS);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scd4x_write_and_fetch(struct scd4x_state *state, enum scd4x_cmd cmd,
|
||||
uint16_t arg, void *response, int response_sz)
|
||||
{
|
||||
struct i2c_client *client = state->client;
|
||||
char buf[SCD4X_READ_BUF_SIZE];
|
||||
char *rsp = response;
|
||||
int i, ret;
|
||||
char crc;
|
||||
|
||||
ret = scd4x_write(state, CMD_FRC, arg);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* execution time */
|
||||
msleep_interruptible(400);
|
||||
|
||||
/* CRC byte for every 2 bytes of data */
|
||||
response_sz += response_sz / 2;
|
||||
|
||||
ret = i2c_master_recv(client, buf, response_sz);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
if (ret != response_sz) {
|
||||
ret = -EIO;
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (i = 0; i < response_sz; i += 3) {
|
||||
crc = crc8(scd4x_crc8_table, buf + i, 2, CRC8_INIT_VALUE);
|
||||
if (crc != buf[i + 2]) {
|
||||
dev_err(&client->dev, "CRC error\n");
|
||||
ret = -EIO;
|
||||
goto err;
|
||||
}
|
||||
|
||||
*rsp++ = buf[i];
|
||||
*rsp++ = buf[i + 1];
|
||||
}
|
||||
|
||||
return scd4x_send_command(state, CMD_START_MEAS);
|
||||
|
||||
err:
|
||||
/*
|
||||
* on error try to start the measurement,
|
||||
* puts sensor back into continuous measurement
|
||||
*/
|
||||
scd4x_send_command(state, CMD_START_MEAS);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int scd4x_read_meas(struct scd4x_state *state, uint16_t *meas)
|
||||
{
|
||||
int i, ret;
|
||||
__be16 buf[3];
|
||||
|
||||
ret = scd4x_read(state, CMD_READ_MEAS, buf, sizeof(buf));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(buf); i++)
|
||||
meas[i] = be16_to_cpu(buf[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scd4x_wait_meas_poll(struct scd4x_state *state)
|
||||
{
|
||||
struct i2c_client *client = state->client;
|
||||
int tries = 6;
|
||||
int ret;
|
||||
|
||||
do {
|
||||
__be16 bval;
|
||||
uint16_t val;
|
||||
|
||||
ret = scd4x_read(state, CMD_GET_DATA_READY, &bval, sizeof(bval));
|
||||
if (ret)
|
||||
return -EIO;
|
||||
val = be16_to_cpu(bval);
|
||||
|
||||
/* new measurement available */
|
||||
if (val & 0x7FF)
|
||||
return 0;
|
||||
|
||||
msleep_interruptible(1000);
|
||||
} while (--tries);
|
||||
|
||||
/* try to start sensor on timeout */
|
||||
ret = scd4x_send_command(state, CMD_START_MEAS);
|
||||
if (ret)
|
||||
dev_err(&client->dev, "failed to start measurement: %d\n", ret);
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int scd4x_read_poll(struct scd4x_state *state, uint16_t *buf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = scd4x_wait_meas_poll(state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return scd4x_read_meas(state, buf);
|
||||
}
|
||||
|
||||
static int scd4x_read_channel(struct scd4x_state *state, int chan)
|
||||
{
|
||||
int ret;
|
||||
uint16_t buf[3];
|
||||
|
||||
ret = scd4x_read_poll(state, buf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return buf[chan];
|
||||
}
|
||||
|
||||
static int scd4x_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val,
|
||||
int *val2, long mask)
|
||||
{
|
||||
struct scd4x_state *state = iio_priv(indio_dev);
|
||||
int ret;
|
||||
__be16 tmp;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = iio_device_claim_direct_mode(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&state->lock);
|
||||
ret = scd4x_read_channel(state, chan->address);
|
||||
mutex_unlock(&state->lock);
|
||||
|
||||
iio_device_release_direct_mode(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
if (chan->type == IIO_TEMP) {
|
||||
*val = 175000;
|
||||
*val2 = 65536;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
} else if (chan->type == IIO_HUMIDITYRELATIVE) {
|
||||
*val = 100000;
|
||||
*val2 = 65536;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
}
|
||||
return -EINVAL;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
*val = -16852;
|
||||
*val2 = 114286;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
mutex_lock(&state->lock);
|
||||
ret = scd4x_read(state, CMD_GET_TEMP_OFFSET, &tmp, sizeof(tmp));
|
||||
mutex_unlock(&state->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = be16_to_cpu(tmp);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int scd4x_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct scd4x_state *state = iio_priv(indio_dev);
|
||||
int ret = 0;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
mutex_lock(&state->lock);
|
||||
ret = scd4x_write(state, CMD_SET_TEMP_OFFSET, val);
|
||||
mutex_unlock(&state->lock);
|
||||
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t calibration_auto_enable_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct scd4x_state *state = iio_priv(indio_dev);
|
||||
int ret;
|
||||
__be16 bval;
|
||||
u16 val;
|
||||
|
||||
mutex_lock(&state->lock);
|
||||
ret = scd4x_read(state, CMD_GET_ASC, &bval, sizeof(bval));
|
||||
mutex_unlock(&state->lock);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to read automatic calibration");
|
||||
return ret;
|
||||
}
|
||||
|
||||
val = (be16_to_cpu(bval) & SCD4X_READY_MASK) ? 1 : 0;
|
||||
|
||||
return sprintf(buf, "%d\n", val);
|
||||
}
|
||||
|
||||
static ssize_t calibration_auto_enable_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct scd4x_state *state = iio_priv(indio_dev);
|
||||
bool val;
|
||||
int ret;
|
||||
uint16_t value;
|
||||
|
||||
ret = kstrtobool(buf, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
value = val;
|
||||
|
||||
mutex_lock(&state->lock);
|
||||
ret = scd4x_write(state, CMD_SET_ASC, value);
|
||||
mutex_unlock(&state->lock);
|
||||
if (ret)
|
||||
dev_err(dev, "failed to set automatic calibration");
|
||||
|
||||
return ret ?: len;
|
||||
}
|
||||
|
||||
static ssize_t calibration_forced_value_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct scd4x_state *state = iio_priv(indio_dev);
|
||||
uint16_t val, arg;
|
||||
int ret;
|
||||
|
||||
ret = kstrtou16(buf, 0, &arg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (arg < SCD4X_FRC_MIN_PPM || arg > SCD4X_FRC_MAX_PPM)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&state->lock);
|
||||
ret = scd4x_write_and_fetch(state, CMD_FRC, arg, &val, sizeof(val));
|
||||
mutex_unlock(&state->lock);
|
||||
|
||||
if (val == 0xff) {
|
||||
dev_err(dev, "forced calibration has failed");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret ?: len;
|
||||
}
|
||||
|
||||
static IIO_DEVICE_ATTR_RW(calibration_auto_enable, 0);
|
||||
static IIO_DEVICE_ATTR_WO(calibration_forced_value, 0);
|
||||
|
||||
static IIO_CONST_ATTR(calibration_forced_value_available,
|
||||
__stringify([SCD4X_FRC_MIN_PPM 1 SCD4X_FRC_MAX_PPM]));
|
||||
|
||||
static struct attribute *scd4x_attrs[] = {
|
||||
&iio_dev_attr_calibration_auto_enable.dev_attr.attr,
|
||||
&iio_dev_attr_calibration_forced_value.dev_attr.attr,
|
||||
&iio_const_attr_calibration_forced_value_available.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group scd4x_attr_group = {
|
||||
.attrs = scd4x_attrs,
|
||||
};
|
||||
|
||||
static const struct iio_info scd4x_info = {
|
||||
.attrs = &scd4x_attr_group,
|
||||
.read_raw = scd4x_read_raw,
|
||||
.write_raw = scd4x_write_raw,
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec scd4x_channels[] = {
|
||||
{
|
||||
.type = IIO_CONCENTRATION,
|
||||
.channel2 = IIO_MOD_CO2,
|
||||
.modified = 1,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
.address = SCD4X_CO2,
|
||||
.scan_index = SCD4X_CO2,
|
||||
.scan_type = {
|
||||
.sign = 'u',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_BE,
|
||||
},
|
||||
},
|
||||
{
|
||||
.type = IIO_TEMP,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE) |
|
||||
BIT(IIO_CHAN_INFO_OFFSET) |
|
||||
BIT(IIO_CHAN_INFO_CALIBBIAS),
|
||||
.address = SCD4X_TEMP,
|
||||
.scan_index = SCD4X_TEMP,
|
||||
.scan_type = {
|
||||
.sign = 'u',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_BE,
|
||||
},
|
||||
},
|
||||
{
|
||||
.type = IIO_HUMIDITYRELATIVE,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
.address = SCD4X_HR,
|
||||
.scan_index = SCD4X_HR,
|
||||
.scan_type = {
|
||||
.sign = 'u',
|
||||
.realbits = 16,
|
||||
.storagebits = 16,
|
||||
.endianness = IIO_BE,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static int __maybe_unused scd4x_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct scd4x_state *state = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = scd4x_send_command(state, CMD_STOP_MEAS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return regulator_disable(state->vdd);
|
||||
}
|
||||
|
||||
static int __maybe_unused scd4x_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct scd4x_state *state = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = regulator_enable(state->vdd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return scd4x_send_command(state, CMD_START_MEAS);
|
||||
}
|
||||
|
||||
static __maybe_unused SIMPLE_DEV_PM_OPS(scd4x_pm_ops, scd4x_suspend, scd4x_resume);
|
||||
|
||||
static void scd4x_stop_meas(void *state)
|
||||
{
|
||||
scd4x_send_command(state, CMD_STOP_MEAS);
|
||||
}
|
||||
|
||||
static void scd4x_disable_regulator(void *data)
|
||||
{
|
||||
struct scd4x_state *state = data;
|
||||
|
||||
regulator_disable(state->vdd);
|
||||
}
|
||||
|
||||
static irqreturn_t scd4x_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct scd4x_state *state = iio_priv(indio_dev);
|
||||
struct {
|
||||
uint16_t data[3];
|
||||
int64_t ts __aligned(8);
|
||||
} scan;
|
||||
int ret;
|
||||
|
||||
memset(&scan, 0, sizeof(scan));
|
||||
mutex_lock(&state->lock);
|
||||
ret = scd4x_read_poll(state, scan.data);
|
||||
mutex_unlock(&state->lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev));
|
||||
out:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int scd4x_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
{
|
||||
static const unsigned long scd4x_scan_masks[] = { 0x07, 0x00 };
|
||||
struct device *dev = &client->dev;
|
||||
struct iio_dev *indio_dev;
|
||||
struct scd4x_state *state;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
state = iio_priv(indio_dev);
|
||||
mutex_init(&state->lock);
|
||||
state->client = client;
|
||||
crc8_populate_msb(scd4x_crc8_table, SCD4X_CRC8_POLYNOMIAL);
|
||||
|
||||
indio_dev->info = &scd4x_info;
|
||||
indio_dev->name = client->name;
|
||||
indio_dev->channels = scd4x_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(scd4x_channels);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->available_scan_masks = scd4x_scan_masks;
|
||||
|
||||
state->vdd = devm_regulator_get(dev, "vdd");
|
||||
if (IS_ERR(state->vdd))
|
||||
return dev_err_probe(dev, PTR_ERR(state->vdd), "failed to get regulator\n");
|
||||
|
||||
ret = regulator_enable(state->vdd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, scd4x_disable_regulator, state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = scd4x_send_command(state, CMD_STOP_MEAS);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to stop measurement: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* execution time */
|
||||
msleep_interruptible(500);
|
||||
|
||||
ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, scd4x_trigger_handler, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = scd4x_send_command(state, CMD_START_MEAS);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to start measurement: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(dev, scd4x_stop_meas, state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id scd4x_dt_ids[] = {
|
||||
{ .compatible = "sensirion,scd40" },
|
||||
{ .compatible = "sensirion,scd41" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, scd4x_dt_ids);
|
||||
|
||||
static struct i2c_driver scd4x_i2c_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.of_match_table = scd4x_dt_ids,
|
||||
.pm = &scd4x_pm_ops
|
||||
},
|
||||
.probe = scd4x_probe,
|
||||
};
|
||||
module_i2c_driver(scd4x_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Roan van Dijk <roan@protonic.nl>");
|
||||
MODULE_DESCRIPTION("Sensirion SCD4X carbon dioxide sensor core driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,537 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Senseair Sunrise 006-0-0007 CO2 sensor driver.
|
||||
*
|
||||
* Copyright (C) 2021 Jacopo Mondi
|
||||
*
|
||||
* List of features not yet supported by the driver:
|
||||
* - controllable EN pin
|
||||
* - single-shot operations using the nDRY pin.
|
||||
* - ABC/target calibration
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/time64.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
#define DRIVER_NAME "sunrise_co2"
|
||||
|
||||
#define SUNRISE_ERROR_STATUS_REG 0x00
|
||||
#define SUNRISE_CO2_FILTERED_COMP_REG 0x06
|
||||
#define SUNRISE_CHIP_TEMPERATURE_REG 0x08
|
||||
#define SUNRISE_CALIBRATION_STATUS_REG 0x81
|
||||
#define SUNRISE_CALIBRATION_COMMAND_REG 0x82
|
||||
#define SUNRISE_CALIBRATION_FACTORY_CMD 0x7c02
|
||||
#define SUNRISE_CALIBRATION_BACKGROUND_CMD 0x7c06
|
||||
/*
|
||||
* The calibration timeout is not characterized in the datasheet.
|
||||
* Use 30 seconds as a reasonable upper limit.
|
||||
*/
|
||||
#define SUNRISE_CALIBRATION_TIMEOUT_US (30 * USEC_PER_SEC)
|
||||
|
||||
struct sunrise_dev {
|
||||
struct i2c_client *client;
|
||||
struct regmap *regmap;
|
||||
/* Protects access to IIO attributes. */
|
||||
struct mutex lock;
|
||||
bool ignore_nak;
|
||||
};
|
||||
|
||||
/* Custom regmap read/write operations: perform unlocked access to the i2c bus. */
|
||||
|
||||
static int sunrise_regmap_read(void *context, const void *reg_buf,
|
||||
size_t reg_size, void *val_buf, size_t val_size)
|
||||
{
|
||||
struct i2c_client *client = context;
|
||||
struct sunrise_dev *sunrise = i2c_get_clientdata(client);
|
||||
union i2c_smbus_data data;
|
||||
int ret;
|
||||
|
||||
if (reg_size != 1 || !val_size)
|
||||
return -EINVAL;
|
||||
|
||||
memset(&data, 0, sizeof(data));
|
||||
data.block[0] = val_size;
|
||||
|
||||
/*
|
||||
* Wake up sensor by sending sensor address: START, sensor address,
|
||||
* STOP. Sensor will not ACK this byte.
|
||||
*
|
||||
* The chip enters a low power state after 15ms without
|
||||
* communications or after a complete read/write sequence.
|
||||
*/
|
||||
__i2c_smbus_xfer(client->adapter, client->addr,
|
||||
sunrise->ignore_nak ? I2C_M_IGNORE_NAK : 0,
|
||||
I2C_SMBUS_WRITE, 0, I2C_SMBUS_BYTE_DATA, &data);
|
||||
|
||||
usleep_range(500, 1500);
|
||||
|
||||
ret = __i2c_smbus_xfer(client->adapter, client->addr, client->flags,
|
||||
I2C_SMBUS_READ, ((u8 *)reg_buf)[0],
|
||||
I2C_SMBUS_I2C_BLOCK_DATA, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
memcpy(val_buf, &data.block[1], data.block[0]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sunrise_regmap_write(void *context, const void *val_buf, size_t count)
|
||||
{
|
||||
struct i2c_client *client = context;
|
||||
struct sunrise_dev *sunrise = i2c_get_clientdata(client);
|
||||
union i2c_smbus_data data;
|
||||
|
||||
/* Discard reg address from values count. */
|
||||
if (!count)
|
||||
return -EINVAL;
|
||||
count--;
|
||||
|
||||
memset(&data, 0, sizeof(data));
|
||||
data.block[0] = count;
|
||||
memcpy(&data.block[1], (u8 *)val_buf + 1, count);
|
||||
|
||||
__i2c_smbus_xfer(client->adapter, client->addr,
|
||||
sunrise->ignore_nak ? I2C_M_IGNORE_NAK : 0,
|
||||
I2C_SMBUS_WRITE, 0, I2C_SMBUS_BYTE_DATA, &data);
|
||||
|
||||
usleep_range(500, 1500);
|
||||
|
||||
return __i2c_smbus_xfer(client->adapter, client->addr, client->flags,
|
||||
I2C_SMBUS_WRITE, ((u8 *)val_buf)[0],
|
||||
I2C_SMBUS_I2C_BLOCK_DATA, &data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sunrise i2c read/write operations: lock the i2c segment to avoid losing the
|
||||
* wake up session. Use custom regmap operations that perform unlocked access to
|
||||
* the i2c bus.
|
||||
*/
|
||||
static int sunrise_read_byte(struct sunrise_dev *sunrise, u8 reg)
|
||||
{
|
||||
const struct i2c_client *client = sunrise->client;
|
||||
const struct device *dev = &client->dev;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
|
||||
ret = regmap_read(sunrise->regmap, reg, &val);
|
||||
i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
|
||||
if (ret) {
|
||||
dev_err(dev, "Read byte failed: reg 0x%02x (%d)\n", reg, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int sunrise_read_word(struct sunrise_dev *sunrise, u8 reg, u16 *val)
|
||||
{
|
||||
const struct i2c_client *client = sunrise->client;
|
||||
const struct device *dev = &client->dev;
|
||||
__be16 be_val;
|
||||
int ret;
|
||||
|
||||
i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
|
||||
ret = regmap_bulk_read(sunrise->regmap, reg, &be_val, sizeof(be_val));
|
||||
i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
|
||||
if (ret) {
|
||||
dev_err(dev, "Read word failed: reg 0x%02x (%d)\n", reg, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*val = be16_to_cpu(be_val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sunrise_write_byte(struct sunrise_dev *sunrise, u8 reg, u8 val)
|
||||
{
|
||||
const struct i2c_client *client = sunrise->client;
|
||||
const struct device *dev = &client->dev;
|
||||
int ret;
|
||||
|
||||
i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
|
||||
ret = regmap_write(sunrise->regmap, reg, val);
|
||||
i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
|
||||
if (ret)
|
||||
dev_err(dev, "Write byte failed: reg 0x%02x (%d)\n", reg, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sunrise_write_word(struct sunrise_dev *sunrise, u8 reg, u16 data)
|
||||
{
|
||||
const struct i2c_client *client = sunrise->client;
|
||||
const struct device *dev = &client->dev;
|
||||
__be16 be_data = cpu_to_be16(data);
|
||||
int ret;
|
||||
|
||||
i2c_lock_bus(client->adapter, I2C_LOCK_SEGMENT);
|
||||
ret = regmap_bulk_write(sunrise->regmap, reg, &be_data, sizeof(be_data));
|
||||
i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
|
||||
if (ret)
|
||||
dev_err(dev, "Write word failed: reg 0x%02x (%d)\n", reg, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Trigger a calibration cycle. */
|
||||
|
||||
enum {
|
||||
SUNRISE_CALIBRATION_FACTORY,
|
||||
SUNRISE_CALIBRATION_BACKGROUND,
|
||||
};
|
||||
|
||||
static const struct sunrise_calib_data {
|
||||
u16 cmd;
|
||||
u8 bit;
|
||||
const char * const name;
|
||||
} calib_data[] = {
|
||||
[SUNRISE_CALIBRATION_FACTORY] = {
|
||||
SUNRISE_CALIBRATION_FACTORY_CMD,
|
||||
BIT(2),
|
||||
"factory_calibration",
|
||||
},
|
||||
[SUNRISE_CALIBRATION_BACKGROUND] = {
|
||||
SUNRISE_CALIBRATION_BACKGROUND_CMD,
|
||||
BIT(5),
|
||||
"background_calibration",
|
||||
},
|
||||
};
|
||||
|
||||
static int sunrise_calibrate(struct sunrise_dev *sunrise,
|
||||
const struct sunrise_calib_data *data)
|
||||
{
|
||||
unsigned int status;
|
||||
int ret;
|
||||
|
||||
/* Reset the calibration status reg. */
|
||||
ret = sunrise_write_byte(sunrise, SUNRISE_CALIBRATION_STATUS_REG, 0x00);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Write a calibration command and poll the calibration status bit. */
|
||||
ret = sunrise_write_word(sunrise, SUNRISE_CALIBRATION_COMMAND_REG, data->cmd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_dbg(&sunrise->client->dev, "%s in progress\n", data->name);
|
||||
|
||||
/*
|
||||
* Calibration takes several seconds, so the sleep time between reads
|
||||
* can be pretty relaxed.
|
||||
*/
|
||||
return read_poll_timeout(sunrise_read_byte, status, status & data->bit,
|
||||
200000, SUNRISE_CALIBRATION_TIMEOUT_US, false,
|
||||
sunrise, SUNRISE_CALIBRATION_STATUS_REG);
|
||||
}
|
||||
|
||||
static ssize_t sunrise_cal_factory_write(struct iio_dev *iiodev,
|
||||
uintptr_t private,
|
||||
const struct iio_chan_spec *chan,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct sunrise_dev *sunrise = iio_priv(iiodev);
|
||||
bool enable;
|
||||
int ret;
|
||||
|
||||
ret = kstrtobool(buf, &enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!enable)
|
||||
return len;
|
||||
|
||||
mutex_lock(&sunrise->lock);
|
||||
ret = sunrise_calibrate(sunrise, &calib_data[SUNRISE_CALIBRATION_FACTORY]);
|
||||
mutex_unlock(&sunrise->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t sunrise_cal_background_write(struct iio_dev *iiodev,
|
||||
uintptr_t private,
|
||||
const struct iio_chan_spec *chan,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct sunrise_dev *sunrise = iio_priv(iiodev);
|
||||
bool enable;
|
||||
int ret;
|
||||
|
||||
ret = kstrtobool(buf, &enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!enable)
|
||||
return len;
|
||||
|
||||
mutex_lock(&sunrise->lock);
|
||||
ret = sunrise_calibrate(sunrise, &calib_data[SUNRISE_CALIBRATION_BACKGROUND]);
|
||||
mutex_unlock(&sunrise->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Enumerate and retrieve the chip error status. */
|
||||
enum {
|
||||
SUNRISE_ERROR_FATAL,
|
||||
SUNRISE_ERROR_I2C,
|
||||
SUNRISE_ERROR_ALGORITHM,
|
||||
SUNRISE_ERROR_CALIBRATION,
|
||||
SUNRISE_ERROR_SELF_DIAGNOSTIC,
|
||||
SUNRISE_ERROR_OUT_OF_RANGE,
|
||||
SUNRISE_ERROR_MEMORY,
|
||||
SUNRISE_ERROR_NO_MEASUREMENT,
|
||||
SUNRISE_ERROR_LOW_VOLTAGE,
|
||||
SUNRISE_ERROR_MEASUREMENT_TIMEOUT,
|
||||
};
|
||||
|
||||
static const char * const sunrise_error_statuses[] = {
|
||||
[SUNRISE_ERROR_FATAL] = "error_fatal",
|
||||
[SUNRISE_ERROR_I2C] = "error_i2c",
|
||||
[SUNRISE_ERROR_ALGORITHM] = "error_algorithm",
|
||||
[SUNRISE_ERROR_CALIBRATION] = "error_calibration",
|
||||
[SUNRISE_ERROR_SELF_DIAGNOSTIC] = "error_self_diagnostic",
|
||||
[SUNRISE_ERROR_OUT_OF_RANGE] = "error_out_of_range",
|
||||
[SUNRISE_ERROR_MEMORY] = "error_memory",
|
||||
[SUNRISE_ERROR_NO_MEASUREMENT] = "error_no_measurement",
|
||||
[SUNRISE_ERROR_LOW_VOLTAGE] = "error_low_voltage",
|
||||
[SUNRISE_ERROR_MEASUREMENT_TIMEOUT] = "error_measurement_timeout",
|
||||
};
|
||||
|
||||
static const struct iio_enum sunrise_error_statuses_enum = {
|
||||
.items = sunrise_error_statuses,
|
||||
.num_items = ARRAY_SIZE(sunrise_error_statuses),
|
||||
};
|
||||
|
||||
static ssize_t sunrise_error_status_read(struct iio_dev *iiodev,
|
||||
uintptr_t private,
|
||||
const struct iio_chan_spec *chan,
|
||||
char *buf)
|
||||
{
|
||||
struct sunrise_dev *sunrise = iio_priv(iiodev);
|
||||
unsigned long errors;
|
||||
ssize_t len = 0;
|
||||
u16 value;
|
||||
int ret;
|
||||
u8 i;
|
||||
|
||||
mutex_lock(&sunrise->lock);
|
||||
ret = sunrise_read_word(sunrise, SUNRISE_ERROR_STATUS_REG, &value);
|
||||
if (ret) {
|
||||
mutex_unlock(&sunrise->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
errors = value;
|
||||
for_each_set_bit(i, &errors, ARRAY_SIZE(sunrise_error_statuses))
|
||||
len += sysfs_emit_at(buf, len, "%s ", sunrise_error_statuses[i]);
|
||||
|
||||
if (len)
|
||||
buf[len - 1] = '\n';
|
||||
|
||||
mutex_unlock(&sunrise->lock);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec_ext_info sunrise_concentration_ext_info[] = {
|
||||
/* Calibration triggers. */
|
||||
{
|
||||
.name = "calibration_factory",
|
||||
.write = sunrise_cal_factory_write,
|
||||
.shared = IIO_SEPARATE,
|
||||
},
|
||||
{
|
||||
.name = "calibration_background",
|
||||
.write = sunrise_cal_background_write,
|
||||
.shared = IIO_SEPARATE,
|
||||
},
|
||||
|
||||
/* Error statuses. */
|
||||
{
|
||||
.name = "error_status",
|
||||
.read = sunrise_error_status_read,
|
||||
.shared = IIO_SHARED_BY_ALL,
|
||||
},
|
||||
{
|
||||
.name = "error_status_available",
|
||||
.shared = IIO_SHARED_BY_ALL,
|
||||
.read = iio_enum_available_read,
|
||||
.private = (uintptr_t)&sunrise_error_statuses_enum,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec sunrise_channels[] = {
|
||||
{
|
||||
.type = IIO_CONCENTRATION,
|
||||
.modified = 1,
|
||||
.channel2 = IIO_MOD_CO2,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
.ext_info = sunrise_concentration_ext_info,
|
||||
},
|
||||
{
|
||||
.type = IIO_TEMP,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
},
|
||||
};
|
||||
|
||||
static int sunrise_read_raw(struct iio_dev *iio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct sunrise_dev *sunrise = iio_priv(iio_dev);
|
||||
u16 value;
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
switch (chan->type) {
|
||||
case IIO_CONCENTRATION:
|
||||
mutex_lock(&sunrise->lock);
|
||||
ret = sunrise_read_word(sunrise, SUNRISE_CO2_FILTERED_COMP_REG,
|
||||
&value);
|
||||
*val = value;
|
||||
mutex_unlock(&sunrise->lock);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_TEMP:
|
||||
mutex_lock(&sunrise->lock);
|
||||
ret = sunrise_read_word(sunrise, SUNRISE_CHIP_TEMPERATURE_REG,
|
||||
&value);
|
||||
*val = value;
|
||||
mutex_unlock(&sunrise->lock);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
switch (chan->type) {
|
||||
case IIO_CONCENTRATION:
|
||||
/*
|
||||
* 1 / 10^4 to comply with IIO scale for CO2
|
||||
* (percentage). The chip CO2 reading range is [400 -
|
||||
* 5000] ppm which corresponds to [0,004 - 0,5] %.
|
||||
*/
|
||||
*val = 1;
|
||||
*val2 = 10000;
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
|
||||
case IIO_TEMP:
|
||||
/* x10 to comply with IIO scale (millidegrees celsius). */
|
||||
*val = 10;
|
||||
return IIO_VAL_INT;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info sunrise_info = {
|
||||
.read_raw = sunrise_read_raw,
|
||||
};
|
||||
|
||||
static const struct regmap_bus sunrise_regmap_bus = {
|
||||
.read = sunrise_regmap_read,
|
||||
.write = sunrise_regmap_write,
|
||||
};
|
||||
|
||||
static const struct regmap_config sunrise_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static int sunrise_probe(struct i2c_client *client)
|
||||
{
|
||||
struct sunrise_dev *sunrise;
|
||||
struct iio_dev *iio_dev;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_BLOCK_DATA)) {
|
||||
dev_err(&client->dev,
|
||||
"Adapter does not support required functionalities\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*sunrise));
|
||||
if (!iio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
sunrise = iio_priv(iio_dev);
|
||||
sunrise->client = client;
|
||||
mutex_init(&sunrise->lock);
|
||||
|
||||
i2c_set_clientdata(client, sunrise);
|
||||
|
||||
sunrise->regmap = devm_regmap_init(&client->dev, &sunrise_regmap_bus,
|
||||
client, &sunrise_regmap_config);
|
||||
if (IS_ERR(sunrise->regmap)) {
|
||||
dev_err(&client->dev, "Failed to initialize regmap\n");
|
||||
return PTR_ERR(sunrise->regmap);
|
||||
}
|
||||
|
||||
/*
|
||||
* The chip nacks the wake up message. If the adapter does not support
|
||||
* protocol mangling do not set the I2C_M_IGNORE_NAK flag at the expense
|
||||
* of possible cruft in the logs.
|
||||
*/
|
||||
if (i2c_check_functionality(client->adapter, I2C_FUNC_PROTOCOL_MANGLING))
|
||||
sunrise->ignore_nak = true;
|
||||
|
||||
iio_dev->info = &sunrise_info;
|
||||
iio_dev->name = DRIVER_NAME;
|
||||
iio_dev->channels = sunrise_channels;
|
||||
iio_dev->num_channels = ARRAY_SIZE(sunrise_channels);
|
||||
iio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
return devm_iio_device_register(&client->dev, iio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id sunrise_of_match[] = {
|
||||
{ .compatible = "senseair,sunrise-006-0-0007" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sunrise_of_match);
|
||||
|
||||
static struct i2c_driver sunrise_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.of_match_table = sunrise_of_match,
|
||||
},
|
||||
.probe_new = sunrise_probe,
|
||||
};
|
||||
module_i2c_driver(sunrise_driver);
|
||||
|
||||
MODULE_AUTHOR("Jacopo Mondi <jacopo@jmondi.org>");
|
||||
MODULE_DESCRIPTION("Senseair Sunrise 006-0-0007 CO2 sensor IIO driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -241,8 +241,9 @@ int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name,
|
|||
fifo_attrs = NULL;
|
||||
|
||||
ret = iio_triggered_buffer_setup_ext(indio_dev,
|
||||
&iio_pollfunc_store_time,
|
||||
NULL, NULL, fifo_attrs);
|
||||
&iio_pollfunc_store_time, NULL,
|
||||
IIO_BUFFER_DIRECTION_IN,
|
||||
NULL, fifo_attrs);
|
||||
if (ret) {
|
||||
dev_err(&indio_dev->dev, "Triggered Buffer Setup Failed\n");
|
||||
return ret;
|
||||
|
|
|
@ -215,17 +215,23 @@ int st_sensors_set_axis_enable(struct iio_dev *indio_dev, u8 axis_enable)
|
|||
}
|
||||
EXPORT_SYMBOL(st_sensors_set_axis_enable);
|
||||
|
||||
static void st_reg_disable(void *reg)
|
||||
{
|
||||
regulator_disable(reg);
|
||||
}
|
||||
|
||||
int st_sensors_power_enable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct st_sensor_data *pdata = iio_priv(indio_dev);
|
||||
struct device *parent = indio_dev->dev.parent;
|
||||
int err;
|
||||
|
||||
/* Regulators not mandatory, but if requested we should enable them. */
|
||||
pdata->vdd = devm_regulator_get(indio_dev->dev.parent, "vdd");
|
||||
if (IS_ERR(pdata->vdd)) {
|
||||
dev_err(&indio_dev->dev, "unable to get Vdd supply\n");
|
||||
return PTR_ERR(pdata->vdd);
|
||||
}
|
||||
pdata->vdd = devm_regulator_get(parent, "vdd");
|
||||
if (IS_ERR(pdata->vdd))
|
||||
return dev_err_probe(&indio_dev->dev, PTR_ERR(pdata->vdd),
|
||||
"unable to get Vdd supply\n");
|
||||
|
||||
err = regulator_enable(pdata->vdd);
|
||||
if (err != 0) {
|
||||
dev_warn(&indio_dev->dev,
|
||||
|
@ -233,36 +239,26 @@ int st_sensors_power_enable(struct iio_dev *indio_dev)
|
|||
return err;
|
||||
}
|
||||
|
||||
pdata->vdd_io = devm_regulator_get(indio_dev->dev.parent, "vddio");
|
||||
if (IS_ERR(pdata->vdd_io)) {
|
||||
dev_err(&indio_dev->dev, "unable to get Vdd_IO supply\n");
|
||||
err = PTR_ERR(pdata->vdd_io);
|
||||
goto st_sensors_disable_vdd;
|
||||
}
|
||||
err = devm_add_action_or_reset(parent, st_reg_disable, pdata->vdd);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
pdata->vdd_io = devm_regulator_get(parent, "vddio");
|
||||
if (IS_ERR(pdata->vdd_io))
|
||||
return dev_err_probe(&indio_dev->dev, PTR_ERR(pdata->vdd_io),
|
||||
"unable to get Vdd_IO supply\n");
|
||||
|
||||
err = regulator_enable(pdata->vdd_io);
|
||||
if (err != 0) {
|
||||
dev_warn(&indio_dev->dev,
|
||||
"Failed to enable specified Vdd_IO supply\n");
|
||||
goto st_sensors_disable_vdd;
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
st_sensors_disable_vdd:
|
||||
regulator_disable(pdata->vdd);
|
||||
return err;
|
||||
return devm_add_action_or_reset(parent, st_reg_disable, pdata->vdd_io);
|
||||
}
|
||||
EXPORT_SYMBOL(st_sensors_power_enable);
|
||||
|
||||
void st_sensors_power_disable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct st_sensor_data *pdata = iio_priv(indio_dev);
|
||||
|
||||
regulator_disable(pdata->vdd);
|
||||
regulator_disable(pdata->vdd_io);
|
||||
}
|
||||
EXPORT_SYMBOL(st_sensors_power_disable);
|
||||
|
||||
static int st_sensors_set_drdy_int_pin(struct iio_dev *indio_dev,
|
||||
struct st_sensors_platform_data *pdata)
|
||||
{
|
||||
|
|
|
@ -57,7 +57,6 @@ int st_sensors_i2c_configure(struct iio_dev *indio_dev,
|
|||
|
||||
indio_dev->name = client->name;
|
||||
|
||||
sdata->dev = &client->dev;
|
||||
sdata->irq = client->irq;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -109,7 +109,6 @@ int st_sensors_spi_configure(struct iio_dev *indio_dev,
|
|||
|
||||
indio_dev->name = spi->modalias;
|
||||
|
||||
sdata->dev = &spi->dev;
|
||||
sdata->irq = spi->irq;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -42,7 +42,8 @@ static bool st_sensors_new_samples_available(struct iio_dev *indio_dev,
|
|||
sdata->sensor_settings->drdy_irq.stat_drdy.addr,
|
||||
&status);
|
||||
if (ret < 0) {
|
||||
dev_err(sdata->dev, "error checking samples available\n");
|
||||
dev_err(indio_dev->dev.parent,
|
||||
"error checking samples available\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -87,7 +88,7 @@ static irqreturn_t st_sensors_irq_thread(int irq, void *p)
|
|||
st_sensors_new_samples_available(indio_dev, sdata)) {
|
||||
iio_trigger_poll_chained(p);
|
||||
} else {
|
||||
dev_dbg(sdata->dev, "spurious IRQ\n");
|
||||
dev_dbg(indio_dev->dev.parent, "spurious IRQ\n");
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
|
@ -107,7 +108,8 @@ static irqreturn_t st_sensors_irq_thread(int irq, void *p)
|
|||
*/
|
||||
while (sdata->hw_irq_trigger &&
|
||||
st_sensors_new_samples_available(indio_dev, sdata)) {
|
||||
dev_dbg(sdata->dev, "more samples came in during polling\n");
|
||||
dev_dbg(indio_dev->dev.parent,
|
||||
"more samples came in during polling\n");
|
||||
sdata->hw_timestamp = iio_get_time_ns(indio_dev);
|
||||
iio_trigger_poll_chained(p);
|
||||
}
|
||||
|
@ -119,11 +121,12 @@ int st_sensors_allocate_trigger(struct iio_dev *indio_dev,
|
|||
const struct iio_trigger_ops *trigger_ops)
|
||||
{
|
||||
struct st_sensor_data *sdata = iio_priv(indio_dev);
|
||||
struct device *parent = indio_dev->dev.parent;
|
||||
unsigned long irq_trig;
|
||||
int err;
|
||||
|
||||
sdata->trig = iio_trigger_alloc(sdata->dev, "%s-trigger",
|
||||
indio_dev->name);
|
||||
sdata->trig = devm_iio_trigger_alloc(parent, "%s-trigger",
|
||||
indio_dev->name);
|
||||
if (sdata->trig == NULL) {
|
||||
dev_err(&indio_dev->dev, "failed to allocate iio trigger.\n");
|
||||
return -ENOMEM;
|
||||
|
@ -153,7 +156,7 @@ int st_sensors_allocate_trigger(struct iio_dev *indio_dev,
|
|||
sdata->sensor_settings->drdy_irq.addr_ihl,
|
||||
sdata->sensor_settings->drdy_irq.mask_ihl, 1);
|
||||
if (err < 0)
|
||||
goto iio_trigger_free;
|
||||
return err;
|
||||
dev_info(&indio_dev->dev,
|
||||
"interrupts on the falling edge or active low level\n");
|
||||
}
|
||||
|
@ -179,8 +182,7 @@ int st_sensors_allocate_trigger(struct iio_dev *indio_dev,
|
|||
if (!sdata->sensor_settings->drdy_irq.stat_drdy.addr) {
|
||||
dev_err(&indio_dev->dev,
|
||||
"edge IRQ not supported w/o stat register.\n");
|
||||
err = -EOPNOTSUPP;
|
||||
goto iio_trigger_free;
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
sdata->edge_irq = true;
|
||||
} else {
|
||||
|
@ -205,44 +207,29 @@ int st_sensors_allocate_trigger(struct iio_dev *indio_dev,
|
|||
sdata->sensor_settings->drdy_irq.stat_drdy.addr)
|
||||
irq_trig |= IRQF_SHARED;
|
||||
|
||||
err = request_threaded_irq(sdata->irq,
|
||||
st_sensors_irq_handler,
|
||||
st_sensors_irq_thread,
|
||||
irq_trig,
|
||||
sdata->trig->name,
|
||||
sdata->trig);
|
||||
err = devm_request_threaded_irq(parent,
|
||||
sdata->irq,
|
||||
st_sensors_irq_handler,
|
||||
st_sensors_irq_thread,
|
||||
irq_trig,
|
||||
sdata->trig->name,
|
||||
sdata->trig);
|
||||
if (err) {
|
||||
dev_err(&indio_dev->dev, "failed to request trigger IRQ.\n");
|
||||
goto iio_trigger_free;
|
||||
return err;
|
||||
}
|
||||
|
||||
err = iio_trigger_register(sdata->trig);
|
||||
err = devm_iio_trigger_register(parent, sdata->trig);
|
||||
if (err < 0) {
|
||||
dev_err(&indio_dev->dev, "failed to register iio trigger.\n");
|
||||
goto iio_trigger_register_error;
|
||||
return err;
|
||||
}
|
||||
indio_dev->trig = iio_trigger_get(sdata->trig);
|
||||
|
||||
return 0;
|
||||
|
||||
iio_trigger_register_error:
|
||||
free_irq(sdata->irq, sdata->trig);
|
||||
iio_trigger_free:
|
||||
iio_trigger_free(sdata->trig);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(st_sensors_allocate_trigger);
|
||||
|
||||
void st_sensors_deallocate_trigger(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct st_sensor_data *sdata = iio_priv(indio_dev);
|
||||
|
||||
iio_trigger_unregister(sdata->trig);
|
||||
free_irq(sdata->irq, sdata->trig);
|
||||
iio_trigger_free(sdata->trig);
|
||||
}
|
||||
EXPORT_SYMBOL(st_sensors_deallocate_trigger);
|
||||
|
||||
int st_sensors_validate_device(struct iio_trigger *trig,
|
||||
struct iio_dev *indio_dev)
|
||||
{
|
||||
|
|
|
@ -843,6 +843,13 @@ static int ad5064_request_vref(struct ad5064_state *st, struct device *dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void ad5064_bulk_reg_disable(void *data)
|
||||
{
|
||||
struct ad5064_state *st = data;
|
||||
|
||||
regulator_bulk_disable(ad5064_num_vref(st), st->vref_reg);
|
||||
}
|
||||
|
||||
static int ad5064_probe(struct device *dev, enum ad5064_type type,
|
||||
const char *name, ad5064_write_func write)
|
||||
{
|
||||
|
@ -858,7 +865,6 @@ static int ad5064_probe(struct device *dev, enum ad5064_type type,
|
|||
|
||||
st = iio_priv(indio_dev);
|
||||
mutex_init(&st->lock);
|
||||
dev_set_drvdata(dev, indio_dev);
|
||||
|
||||
st->chip_info = &ad5064_chip_info_tbl[type];
|
||||
st->dev = dev;
|
||||
|
@ -872,6 +878,10 @@ static int ad5064_probe(struct device *dev, enum ad5064_type type,
|
|||
ret = regulator_bulk_enable(ad5064_num_vref(st), st->vref_reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, ad5064_bulk_reg_disable, st);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
indio_dev->name = name;
|
||||
|
@ -887,30 +897,7 @@ static int ad5064_probe(struct device *dev, enum ad5064_type type,
|
|||
st->dac_cache[i] = midscale;
|
||||
}
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret)
|
||||
goto error_disable_reg;
|
||||
|
||||
return 0;
|
||||
|
||||
error_disable_reg:
|
||||
if (!st->use_internal_vref)
|
||||
regulator_bulk_disable(ad5064_num_vref(st), st->vref_reg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ad5064_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct ad5064_state *st = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
if (!st->use_internal_vref)
|
||||
regulator_bulk_disable(ad5064_num_vref(st), st->vref_reg);
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SPI_MASTER)
|
||||
|
@ -932,11 +919,6 @@ static int ad5064_spi_probe(struct spi_device *spi)
|
|||
ad5064_spi_write);
|
||||
}
|
||||
|
||||
static int ad5064_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
return ad5064_remove(&spi->dev);
|
||||
}
|
||||
|
||||
static const struct spi_device_id ad5064_spi_ids[] = {
|
||||
{"ad5024", ID_AD5024},
|
||||
{"ad5025", ID_AD5025},
|
||||
|
@ -963,7 +945,6 @@ static struct spi_driver ad5064_spi_driver = {
|
|||
.name = "ad5064",
|
||||
},
|
||||
.probe = ad5064_spi_probe,
|
||||
.remove = ad5064_spi_remove,
|
||||
.id_table = ad5064_spi_ids,
|
||||
};
|
||||
|
||||
|
@ -1019,11 +1000,6 @@ static int ad5064_i2c_probe(struct i2c_client *i2c,
|
|||
ad5064_i2c_write);
|
||||
}
|
||||
|
||||
static int ad5064_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
return ad5064_remove(&i2c->dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ad5064_i2c_ids[] = {
|
||||
{"ad5625", ID_AD5625 },
|
||||
{"ad5625r-1v25", ID_AD5625R_1V25 },
|
||||
|
@ -1081,7 +1057,6 @@ static struct i2c_driver ad5064_i2c_driver = {
|
|||
.name = "ad5064",
|
||||
},
|
||||
.probe = ad5064_i2c_probe,
|
||||
.remove = ad5064_i2c_remove,
|
||||
.id_table = ad5064_i2c_ids,
|
||||
};
|
||||
|
||||
|
|
|
@ -444,7 +444,7 @@ error_free_reg:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int ad5380_remove(struct device *dev)
|
||||
static void ad5380_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct ad5380_state *st = iio_priv(indio_dev);
|
||||
|
@ -453,11 +453,8 @@ static int ad5380_remove(struct device *dev)
|
|||
|
||||
kfree(indio_dev->channels);
|
||||
|
||||
if (!IS_ERR(st->vref_reg)) {
|
||||
if (!IS_ERR(st->vref_reg))
|
||||
regulator_disable(st->vref_reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool ad5380_reg_false(struct device *dev, unsigned int reg)
|
||||
|
@ -493,7 +490,9 @@ static int ad5380_spi_probe(struct spi_device *spi)
|
|||
|
||||
static int ad5380_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
return ad5380_remove(&spi->dev);
|
||||
ad5380_remove(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id ad5380_spi_ids[] = {
|
||||
|
@ -566,7 +565,9 @@ static int ad5380_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
static int ad5380_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
return ad5380_remove(&i2c->dev);
|
||||
ad5380_remove(&i2c->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ad5380_i2c_ids[] = {
|
||||
|
|
|
@ -283,7 +283,7 @@ error_disable_reg:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int ad5446_remove(struct device *dev)
|
||||
static void ad5446_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct ad5446_state *st = iio_priv(indio_dev);
|
||||
|
@ -291,8 +291,6 @@ static int ad5446_remove(struct device *dev)
|
|||
iio_device_unregister(indio_dev);
|
||||
if (!IS_ERR(st->reg))
|
||||
regulator_disable(st->reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SPI_MASTER)
|
||||
|
@ -495,7 +493,9 @@ static int ad5446_spi_probe(struct spi_device *spi)
|
|||
|
||||
static int ad5446_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
return ad5446_remove(&spi->dev);
|
||||
ad5446_remove(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver ad5446_spi_driver = {
|
||||
|
@ -572,7 +572,9 @@ static int ad5446_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
static int ad5446_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
return ad5446_remove(&i2c->dev);
|
||||
ad5446_remove(&i2c->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ad5446_i2c_ids[] = {
|
||||
|
|
|
@ -663,7 +663,7 @@ error_disable_reg:
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(ad5592r_probe);
|
||||
|
||||
int ad5592r_remove(struct device *dev)
|
||||
void ad5592r_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *iio_dev = dev_get_drvdata(dev);
|
||||
struct ad5592r_state *st = iio_priv(iio_dev);
|
||||
|
@ -674,8 +674,6 @@ int ad5592r_remove(struct device *dev)
|
|||
|
||||
if (st->reg)
|
||||
regulator_disable(st->reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ad5592r_remove);
|
||||
|
||||
|
|
|
@ -71,6 +71,6 @@ struct ad5592r_state {
|
|||
|
||||
int ad5592r_probe(struct device *dev, const char *name,
|
||||
const struct ad5592r_rw_ops *ops);
|
||||
int ad5592r_remove(struct device *dev);
|
||||
void ad5592r_remove(struct device *dev);
|
||||
|
||||
#endif /* __DRIVERS_IIO_DAC_AD5592R_BASE_H__ */
|
||||
|
|
|
@ -132,7 +132,9 @@ static int ad5592r_spi_probe(struct spi_device *spi)
|
|||
|
||||
static int ad5592r_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
return ad5592r_remove(&spi->dev);
|
||||
ad5592r_remove(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id ad5592r_spi_ids[] = {
|
||||
|
|
|
@ -99,7 +99,9 @@ static int ad5593r_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
static int ad5593r_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
return ad5592r_remove(&i2c->dev);
|
||||
ad5592r_remove(&i2c->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ad5593r_i2c_ids[] = {
|
||||
|
|
|
@ -97,7 +97,9 @@ static int ad5686_spi_probe(struct spi_device *spi)
|
|||
|
||||
static int ad5686_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
return ad5686_remove(&spi->dev);
|
||||
ad5686_remove(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id ad5686_spi_id[] = {
|
||||
|
|
|
@ -538,7 +538,7 @@ error_disable_reg:
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(ad5686_probe);
|
||||
|
||||
int ad5686_remove(struct device *dev)
|
||||
void ad5686_remove(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct ad5686_state *st = iio_priv(indio_dev);
|
||||
|
@ -546,8 +546,6 @@ int ad5686_remove(struct device *dev)
|
|||
iio_device_unregister(indio_dev);
|
||||
if (!IS_ERR(st->reg))
|
||||
regulator_disable(st->reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ad5686_remove);
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ int ad5686_probe(struct device *dev,
|
|||
const char *name, ad5686_write_func write,
|
||||
ad5686_read_func read);
|
||||
|
||||
int ad5686_remove(struct device *dev);
|
||||
void ad5686_remove(struct device *dev);
|
||||
|
||||
|
||||
#endif /* __DRIVERS_IIO_DAC_AD5686_H__ */
|
||||
|
|
|
@ -67,7 +67,9 @@ static int ad5686_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
static int ad5686_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
return ad5686_remove(&i2c->dev);
|
||||
ad5686_remove(&i2c->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ad5686_i2c_id[] = {
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
* Copyright 2019-2020 Analog Devices Inc.
|
||||
*/
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
@ -455,6 +458,7 @@ static const struct iio_chan_spec_ext_info ad5766_ext_info[] = {
|
|||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_OFFSET) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.scan_index = (_chan), \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = (_bits), \
|
||||
|
@ -576,6 +580,35 @@ static int ad5766_default_setup(struct ad5766_state *st)
|
|||
return __ad5766_spi_write(st, AD5766_CMD_SPAN_REG, st->crt_range);
|
||||
}
|
||||
|
||||
static irqreturn_t ad5766_trigger_handler(int irq, void *p)
|
||||
{
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct iio_buffer *buffer = indio_dev->buffer;
|
||||
struct ad5766_state *st = iio_priv(indio_dev);
|
||||
int ret, ch, i;
|
||||
u16 data[ARRAY_SIZE(ad5766_channels)];
|
||||
|
||||
ret = iio_pop_from_buffer(buffer, data);
|
||||
if (ret)
|
||||
goto done;
|
||||
|
||||
i = 0;
|
||||
mutex_lock(&st->lock);
|
||||
for_each_set_bit(ch, indio_dev->active_scan_mask,
|
||||
st->chip_info->num_channels - 1)
|
||||
__ad5766_spi_write(st, AD5766_CMD_WR_IN_REG(ch), data[i++]);
|
||||
|
||||
__ad5766_spi_write(st, AD5766_CMD_SW_LDAC,
|
||||
*indio_dev->active_scan_mask);
|
||||
mutex_unlock(&st->lock);
|
||||
|
||||
done:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ad5766_probe(struct spi_device *spi)
|
||||
{
|
||||
enum ad5766_type type;
|
||||
|
@ -609,6 +642,15 @@ static int ad5766_probe(struct spi_device *spi)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Configure trigger buffer */
|
||||
ret = devm_iio_triggered_buffer_setup_ext(&spi->dev, indio_dev, NULL,
|
||||
ad5766_trigger_handler,
|
||||
IIO_BUFFER_DIRECTION_OUT,
|
||||
NULL,
|
||||
NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_iio_device_register(&spi->dev, indio_dev);
|
||||
}
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче