mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 12:21:22 -05:00
Merge tag 'iio-for-6.19a' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/jic23/iio into char-misc-next
Jonathan writes:
IIO: New device support, features and cleanup for 6.19
The usual bunch of new device support, but also quite a bit of cleanup
of the core and older drivers which is always good to see.
New device support
------------------
adi,ad4080
- Add support for AD4081, AD4083, AD4084, AD4086 and AD4087 ADCs with
slightly different features to existing supported parts (max CNV clock
count, resolution etc)
adi,adxl380
- Add support for ADXL318 and ADXL319 which have reduced functionality
compared to other supported parts, particularly around event detection.
aosong,adp810
- New driver for this differential pressure and temperature sensor.
aspeed,adc
- Add support for the AST2700 SoC ADCs which differ in small ways from
already supported parts.
bosch,sm330
- New driver for this IMU (accelerometer + gyroscop) with I2C and SPI bus
support.
invensense,icm45600
- New driver for this family of IMUs with sub drivers for accelerometer
and gyroscope elements. I2C, I3C and SPI busses all supported.
* Supports ICM45605, ICM45606, ICM45608, ICM45634, ICM45686, ICM45687,
ICM45688P, ICM45689.
* Support basic features and FIFO.
maxim,max14001
- New driver for the MAX14001 and MAX14002 ADCs.
renesas,rzt2h
- New driver supporting the RZ/T2H and RZ/N2H ADCs found in various SoCs.
renesas,rznl
- New driver supporting the RZ/NL ADC found in various SoCs.
Features
--------
adi,ad5446
- Add a DT binding doc for the 29 variants currently covered by the driver.
- Add adi,ad5542 which is compatiable with the adi,ad5542a which was
already supported.
bosch,bma220
- I2C support including an I2C bus watchdog.
- Power supply control
- Data ready trigger.
- Low pass filter control.
- Debugfs register access.
- Add Petre Rodan as a maintainer of this driver (thanks!)
bosch,bmi270
- Add support for motion events.
fsl,mpl3115
- Add a dataready trigger and related sampling frequency control.
- Add threshold events.
infineon,dps310
- Add a specific device tree binding.
maxim,max30100
- Allow control of LED pulse-width in dt-binding. Optimum value depends
on physical characteristics of the device which contains this sensor.
mediatek,mt2701
- Add dt compatible for the mt8189.
rockchip,saradc
- Add rk3506 compatible which is functionally the same as the already
supported rk3528 (which is therefore the fallback)
st,lsm6dsx
- Make sampling more flexible when both fifo and events are of interest
by decoupling the FIFO fill rate from actual sampling.
Cleanup and minor fixes
-----------------------
core
- Document and add might_sleep() to iio_push_to_buffers_with_ts_unaligned()
as it allocates a buffer, typically just on 1st call.
- Add documentation for iio_push_to_buffers_with_ts() which is being used
to replace iio_push_to_buffers_with_timestamp() in new code as it
validates the buffer size. Make the deprecation of the old function
clear.
- Document that the store_to() callback in struct iio_buffer_access_funcs
may be called from contexts that cannot sleep.
- Document that the cb() provided to a callback buffer may be called
from contexts that cannot sleep.
- Cleanup up industrialio-backend.c comments.
- Call mutex_destroy() in cleanup of buffers.
- Call device_initialize() later to avoid having to call device_put()
before configuration is otherwise complete.
- Use mutex_init_with_key() to replace opencoded version.
- Use dma_buf_unmap_attachment_unlocked() to replace opencoded version.
- Reorder Makefile for pressure sensors.
various
- Uses sysfs_emit() to replace sprintf() in read_label() and other
callbacks that typically are used to write data to sysfs buffers.
- Switch to REGCACHE_MAPLE in various drivers.
adi,docs
- Fix up formatting of cross references and other kernel-doc issues.
adi,ad4080
- Fix wrong masking of product IDs.
adi,ad5446
- Use DMA safe buffers as needed for SPI.
- Drop a duplicate device chip specific data structure where two parts
are functionally identical.
- Fail probe if reference is not available.
- Split up the massive array of chip type specific structures into
separate structures as this tends to be easier to read and maintain.
- Add explicit of_device_id entries for all supported parts.
- Split I2C and SPI parts away from core to avoid ifdef complexity.
- Switch to devm_mutex_init().
- Make use of guard() to simplify code.
- Applying IWYU principles and reorder headers.
- Various other minor cleanup.
adi,ad7124
- Add debugfs to support single cycle mode, typically only used for cases
such as validate performance of the ADC.
- Various other minor cleanup including removing some layers of indirection
that weren't necessary.
- Add extended attributes to the temperature channel which follows the same
signal path as other channels.
- Replace the setup register allocation strategy with a simpler more
predictable one (a fix for OOB from this code follows later in this pull
request).
adi,adxl345
- Ensure dt-binding allows for both interrupt wired at the same time.
arm,scmi
- Replace const_ilog2() with the resulting value which ends up simpler
to read.
bosch,bma220
- Add correct SPI mode specification to the device tree binding.
- Fix up interrupt type in dt binding example to match that the driver
expects.
- Relax hard constraint on matching chip ID with a message only so as to
enable fallback DT compatibles to work.
- Use local struct device *dev to replaces lots of indirect look ups.
- Improve includes on approximate IWYU basis.
- Explicit of_match_table.
- Reset some registers during probe.
- Move to regmap.
- Ensure a timestamp is available when filling the buffer by using a
locally acquired one rather than relying on trigger top half running.
- Add a utility function to search value pair tables for a match.
- Various other minor improvements.
- Move code to avoid a false dependency of the core code on the I2C module.
bosch,bma400
- Improve register and field naming + organization. Use with FIELD_GET()
and FIELD_PREP() to allow dropping of shift defines.
- Use macros to define event related fields.
- Switch to an address lookup based on an index variable to replace lots of
very similar register macros.
- Rename activity_event_en() to generic_event_en() to better reflect what
it does.
- Improve comments around interrupt register handling.
fsl,mpl3115
- Factor out code for triggered buffer data collection.
- Use more consistent register field naming style.
- Use get_unaligned_be24() to get the pressure.
invensense,mpu6050
- Drop false requirement in DT binding for the interrupt. The driver will
be able to do less if one is not provided, but some features are still
available.
invensense,icm45600_i3c
- Fix missing return on failure to match part.
linear,ltc2688
- Use devm_mutex_init() so mutex_destroy() is called in tear down path.
- Use guard() to simplify lock handling in error return paths.
qcom,vadc
- Fix up some kernel-doc related warnings.
rohm,bd79112 and bd79124
- Use regmap_reg_range() helper to set the ranges.
st,lsm6dsx
- Fix units of ODR in structure documentation.
ti,am335x
- Add range checks to avoid a compiler warning.
ti,pac1934
- Switch to system_percpu_wq.
Various other minor typo fixes etc.
* tag 'iio-for-6.19a' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/jic23/iio: (150 commits)
staging: iio: adt7316: replace sprintf() with sysfs_emit()
iio: pressure: Arrange Makefile alphabetically
iio: ABI: document pressure event attributes
iio: mpl3115: add threshold events support
iio: mpl3115: use get_unaligned_be24() to retrieve pressure data
iio: buffer: use dma_buf_unmap_attachment_unlocked() helper
iio: core: Replace lockdep_set_class() + mutex_init() by combined call
iio: core: Clean up device correctly on iio_device_alloc() failure
iio: core: add missing mutex_destroy in iio_dev_release()
iio: accel: adxl380: add support for ADXL318 and ADXL319
dt-bindings: iio: accel: adxl380: add new supported parts
iio: imu: inv_icm45600: Initializes inv_icm45600_buffer_postdisable() sleep
iio: adc: pac1934: replace use of system_wq with system_percpu_wq
iio: dac: ad5446: Add AD5542 to the spi id table
iio: dac: ad5446: Fix coding style issues
iio: dac: ad5446: Refactor header inclusion
iio: dac: ad5446: Make use of the cleanup helpers
iio: dac: ad5446: Make use of devm_mutex_init()
iio: dac: ad5446: Separate I2C/SPI into different drivers
iio: dac: ad5456: Add missing DT compatibles
...
This commit is contained in:
@@ -898,6 +898,7 @@ What: /sys/.../iio:deviceX/events/in_tempY_thresh_rising_en
|
||||
What: /sys/.../iio:deviceX/events/in_tempY_thresh_falling_en
|
||||
What: /sys/.../iio:deviceX/events/in_capacitanceY_thresh_rising_en
|
||||
What: /sys/.../iio:deviceX/events/in_capacitanceY_thresh_falling_en
|
||||
What: /sys/.../iio:deviceX/events/in_pressure_thresh_rising_en
|
||||
KernelVersion: 2.6.37
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
@@ -926,6 +927,7 @@ What: /sys/.../iio:deviceX/events/in_accel_y_roc_rising_en
|
||||
What: /sys/.../iio:deviceX/events/in_accel_y_roc_falling_en
|
||||
What: /sys/.../iio:deviceX/events/in_accel_z_roc_rising_en
|
||||
What: /sys/.../iio:deviceX/events/in_accel_z_roc_falling_en
|
||||
What: /sys/.../iio:deviceX/events/in_accel_x&y&z_roc_rising_en
|
||||
What: /sys/.../iio:deviceX/events/in_anglvel_x_roc_rising_en
|
||||
What: /sys/.../iio:deviceX/events/in_anglvel_x_roc_falling_en
|
||||
What: /sys/.../iio:deviceX/events/in_anglvel_y_roc_rising_en
|
||||
@@ -1001,6 +1003,7 @@ Description:
|
||||
to the raw signal, allowing slow tracking to resume and the
|
||||
adaptive threshold event detection to function as expected.
|
||||
|
||||
What: /sys/.../events/in_accel_mag_adaptive_rising_value
|
||||
What: /sys/.../events/in_accel_thresh_rising_value
|
||||
What: /sys/.../events/in_accel_thresh_falling_value
|
||||
What: /sys/.../events/in_accel_x_raw_thresh_rising_value
|
||||
@@ -1045,6 +1048,7 @@ What: /sys/.../events/in_capacitanceY_thresh_rising_value
|
||||
What: /sys/.../events/in_capacitanceY_thresh_falling_value
|
||||
What: /sys/.../events/in_capacitanceY_thresh_adaptive_rising_value
|
||||
What: /sys/.../events/in_capacitanceY_thresh_falling_rising_value
|
||||
What: /sys/.../events/in_pressure_thresh_rising_value
|
||||
KernelVersion: 2.6.37
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
@@ -1147,6 +1151,7 @@ Description:
|
||||
will get activated once in_voltage0_raw goes above 1200 and will become
|
||||
deactivated again once the value falls below 1150.
|
||||
|
||||
What: /sys/.../events/in_accel_roc_rising_value
|
||||
What: /sys/.../events/in_accel_x_raw_roc_rising_value
|
||||
What: /sys/.../events/in_accel_x_raw_roc_falling_value
|
||||
What: /sys/.../events/in_accel_y_raw_roc_rising_value
|
||||
@@ -1193,6 +1198,8 @@ Description:
|
||||
value is in raw device units or in processed units (as _raw
|
||||
and _input do on sysfs direct channel read attributes).
|
||||
|
||||
What: /sys/.../events/in_accel_mag_adaptive_rising_period
|
||||
What: /sys/.../events/in_accel_roc_rising_period
|
||||
What: /sys/.../events/in_accel_x_thresh_rising_period
|
||||
What: /sys/.../events/in_accel_x_thresh_falling_period
|
||||
What: /sys/.../events/in_accel_x_roc_rising_period
|
||||
@@ -1362,6 +1369,15 @@ Description:
|
||||
number or direction is not specified, applies to all channels of
|
||||
this type.
|
||||
|
||||
What: /sys/.../iio:deviceX/events/in_accel_x_mag_adaptive_rising_en
|
||||
What: /sys/.../iio:deviceX/events/in_accel_y_mag_adaptive_rising_en
|
||||
What: /sys/.../iio:deviceX/events/in_accel_z_mag_adaptive_rising_en
|
||||
KernelVersion: 2.6.37
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Similar to in_accel_x_thresh[_rising|_falling]_en, but here the
|
||||
magnitude of the channel is compared to the adaptive threshold.
|
||||
|
||||
What: /sys/.../iio:deviceX/events/in_accel_mag_referenced_en
|
||||
What: /sys/.../iio:deviceX/events/in_accel_mag_referenced_rising_en
|
||||
What: /sys/.../iio:deviceX/events/in_accel_mag_referenced_falling_en
|
||||
@@ -2422,3 +2438,23 @@ Description:
|
||||
Value representing the user's attention to the system expressed
|
||||
in units as percentage. This usually means if the user is
|
||||
looking at the screen or not.
|
||||
|
||||
What: /sys/.../events/in_accel_value_available
|
||||
KernelVersion: 6.18
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
List of available threshold values for acceleration event
|
||||
generation. Applies to all event types on in_accel channels.
|
||||
Units after application of scale and offset are m/s^2.
|
||||
Expressed as:
|
||||
|
||||
- a range specified as "[min step max]"
|
||||
|
||||
What: /sys/.../events/in_accel_period_available
|
||||
KernelVersion: 6.18
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
List of available periods for accelerometer event detection in
|
||||
seconds, expressed as:
|
||||
|
||||
- a range specified as "[min step max]"
|
||||
|
||||
@@ -35,15 +35,17 @@ properties:
|
||||
spi-3wire: true
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
interrupt-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- enum: [INT1, INT2]
|
||||
- const: INT2
|
||||
|
||||
dependencies:
|
||||
interrupts: [ interrupt-names ]
|
||||
interrupt-names: [ interrupts ]
|
||||
|
||||
required:
|
||||
- compatible
|
||||
@@ -84,7 +86,8 @@ examples:
|
||||
spi-cpol;
|
||||
spi-cpha;
|
||||
interrupt-parent = <&gpio0>;
|
||||
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "INT2";
|
||||
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<1 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "INT1", "INT2";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,16 +11,19 @@ maintainers:
|
||||
- Antoniu Miclaus <antoniu.miclaus@analog.com>
|
||||
|
||||
description: |
|
||||
The ADXL380/ADXL382 is a low noise density, low power, 3-axis
|
||||
accelerometer with selectable measurement ranges. The ADXL380
|
||||
supports the ±4 g, ±8 g, and ±16 g ranges, and the ADXL382 supports
|
||||
±15 g, ±30 g, and ±60 g ranges.
|
||||
The ADXL380/ADXL382 and ADXL318/ADXL319 are low noise density,
|
||||
low power, 3-axis accelerometers with selectable measurement ranges.
|
||||
The ADXL380 and ADXL318 support the ±4 g, ±8 g, and ±16 g ranges,
|
||||
while the ADXL382 and ADXL319 support ±15 g, ±30 g, and ±60 g ranges.
|
||||
|
||||
https://www.analog.com/en/products/adxl318.html
|
||||
https://www.analog.com/en/products/adxl380.html
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,adxl318
|
||||
- adi,adxl319
|
||||
- adi,adxl380
|
||||
- adi,adxl382
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
$id: http://devicetree.org/schemas/iio/accel/bosch,bma220.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Bosch BMA220 Trixial Acceleration Sensor
|
||||
title: Bosch BMA220 Triaxial Acceleration Sensor
|
||||
|
||||
maintainers:
|
||||
- Jonathan Cameron <Jonathan.Cameron@huawei.com>
|
||||
@@ -20,6 +20,9 @@ properties:
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
spi-cpha: true
|
||||
spi-cpol: true
|
||||
|
||||
vdda-supply: true
|
||||
vddd-supply: true
|
||||
vddio-supply: true
|
||||
@@ -44,8 +47,10 @@ examples:
|
||||
compatible = "bosch,bma220";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <2500000>;
|
||||
spi-cpol;
|
||||
spi-cpha;
|
||||
interrupt-parent = <&gpio0>;
|
||||
interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupts = <0 IRQ_TYPE_EDGE_RISING>;
|
||||
};
|
||||
};
|
||||
...
|
||||
|
||||
@@ -26,6 +26,11 @@ properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,ad4080
|
||||
- adi,ad4081
|
||||
- adi,ad4083
|
||||
- adi,ad4084
|
||||
- adi,ad4086
|
||||
- adi,ad4087
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
89
Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml
Normal file
89
Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml
Normal file
@@ -0,0 +1,89 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
# Copyright 2023-2025 Analog Devices Inc.
|
||||
# Copyright 2023 Kim Seer Paller
|
||||
# Copyright 2025 Marilene Andrade Garcia
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/adc/adi,max14001.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices MAX14001-MAX14002 ADC
|
||||
|
||||
maintainers:
|
||||
- Kim Seer Paller <kimseer.paller@analog.com>
|
||||
- Marilene Andrade Garcia <marilene.agarcia@gmail.com>
|
||||
|
||||
description: |
|
||||
Single channel 10 bit ADC with SPI interface.
|
||||
Datasheet can be found here
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/MAX14001-MAX14002.pdf
|
||||
|
||||
$ref: /schemas/spi/spi-peripheral-props.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- const: adi,max14002
|
||||
- items:
|
||||
- const: adi,max14001
|
||||
- const: adi,max14002
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
spi-max-frequency:
|
||||
maximum: 5000000
|
||||
|
||||
vdd-supply:
|
||||
description:
|
||||
Isolated DC-DC power supply input voltage.
|
||||
|
||||
vddl-supply:
|
||||
description:
|
||||
Logic power supply.
|
||||
|
||||
refin-supply:
|
||||
description:
|
||||
ADC voltage reference supply.
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
items:
|
||||
- description: |
|
||||
cout: comparator output signal that asserts high on the COUT pin
|
||||
when ADC readings exceed the upper threshold and low when readings
|
||||
fall below the lower threshold.
|
||||
- description: |
|
||||
fault: when fault reporting is enabled, the FAULT pin is asserted
|
||||
low whenever one of the monitored fault conditions occurs.
|
||||
|
||||
interrupt-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: cout
|
||||
- const: fault
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- vdd-supply
|
||||
- vddl-supply
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
adc@0 {
|
||||
compatible = "adi,max14001", "adi,max14002";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <5000000>;
|
||||
spi-lsb-first;
|
||||
vdd-supply = <&vdd>;
|
||||
vddl-supply = <&vddl>;
|
||||
};
|
||||
};
|
||||
...
|
||||
@@ -29,6 +29,8 @@ properties:
|
||||
enum:
|
||||
- aspeed,ast2600-adc0
|
||||
- aspeed,ast2600-adc1
|
||||
- aspeed,ast2700-adc0
|
||||
- aspeed,ast2700-adc1
|
||||
description:
|
||||
Their trimming data, which is used to calibrate internal reference volage,
|
||||
locates in different address of OTP.
|
||||
|
||||
@@ -42,6 +42,7 @@ properties:
|
||||
- mediatek,mt8183-auxadc
|
||||
- mediatek,mt8186-auxadc
|
||||
- mediatek,mt8188-auxadc
|
||||
- mediatek,mt8189-auxadc
|
||||
- mediatek,mt8195-auxadc
|
||||
- mediatek,mt8516-auxadc
|
||||
- const: mediatek,mt8173-auxadc
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/adc/renesas,r9a09g077-adc.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Renesas RZ/T2H / RZ/N2H ADC12
|
||||
|
||||
maintainers:
|
||||
- Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
|
||||
|
||||
description: |
|
||||
A/D Converter block is a successive approximation analog-to-digital converter
|
||||
with a 12-bit accuracy. Up to 16 analog input channels can be selected.
|
||||
Conversions can be performed in single or continuous mode. Result of the ADC
|
||||
is stored in a 16-bit data register corresponding to each channel.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- items:
|
||||
- const: renesas,r9a09g087-adc # RZ/N2H
|
||||
- const: renesas,r9a09g077-adc # RZ/T2H
|
||||
- items:
|
||||
- const: renesas,r9a09g077-adc # RZ/T2H
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
items:
|
||||
- description: A/D scan end interrupt
|
||||
- description: A/D scan end interrupt for Group B
|
||||
- description: A/D scan end interrupt for Group C
|
||||
- description: Window A compare match
|
||||
- description: Window B compare match
|
||||
- description: Compare match
|
||||
- description: Compare mismatch
|
||||
|
||||
interrupt-names:
|
||||
items:
|
||||
- const: adi
|
||||
- const: gbadi
|
||||
- const: gcadi
|
||||
- const: cmpai
|
||||
- const: cmpbi
|
||||
- const: wcmpm
|
||||
- const: wcmpum
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: Converter clock
|
||||
- description: Peripheral clock
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: adclk
|
||||
- const: pclk
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
'#address-cells':
|
||||
const: 1
|
||||
|
||||
'#size-cells':
|
||||
const: 0
|
||||
|
||||
"#io-channel-cells":
|
||||
const: 1
|
||||
|
||||
patternProperties:
|
||||
"^channel@[0-9a-f]$":
|
||||
$ref: adc.yaml
|
||||
type: object
|
||||
description: The external channels which are connected to the ADC.
|
||||
|
||||
properties:
|
||||
reg:
|
||||
description: The channel number.
|
||||
maximum: 15
|
||||
|
||||
required:
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
- power-domains
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/renesas,r9a09g077-cpg-mssr.h>
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
adc@80008000 {
|
||||
compatible = "renesas,r9a09g077-adc";
|
||||
reg = <0x80008000 0x400>;
|
||||
interrupts = <GIC_SPI 708 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 709 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 710 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 711 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 712 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 855 IRQ_TYPE_EDGE_RISING>,
|
||||
<GIC_SPI 856 IRQ_TYPE_EDGE_RISING>;
|
||||
interrupt-names = "adi", "gbadi", "gcadi",
|
||||
"cmpai", "cmpbi", "wcmpm", "wcmpum";
|
||||
clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>,
|
||||
<&cpg CPG_MOD 225>;
|
||||
clock-names = "adclk", "pclk";
|
||||
power-domains = <&cpg>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
#io-channel-cells = <1>;
|
||||
|
||||
channel@0 {
|
||||
reg = <0x0>;
|
||||
};
|
||||
channel@1 {
|
||||
reg = <0x1>;
|
||||
};
|
||||
channel@2 {
|
||||
reg = <0x2>;
|
||||
};
|
||||
channel@3 {
|
||||
reg = <0x3>;
|
||||
};
|
||||
};
|
||||
111
Documentation/devicetree/bindings/iio/adc/renesas,rzn1-adc.yaml
Normal file
111
Documentation/devicetree/bindings/iio/adc/renesas,rzn1-adc.yaml
Normal file
@@ -0,0 +1,111 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/adc/renesas,rzn1-adc.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Renesas RZ/N1 Analog to Digital Converter (ADC)
|
||||
|
||||
maintainers:
|
||||
- Herve Codina <herve.codina@bootlin.com>
|
||||
|
||||
description:
|
||||
The Renesas RZ/N1 ADC controller available in the Renesas RZ/N1 SoCs family
|
||||
can use up to two internal ADC cores (ADC1 and ADC2) those internal cores are
|
||||
handled through ADC controller virtual channels.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- const: renesas,r9a06g032-adc # RZ/N1D
|
||||
- const: renesas,rzn1-adc
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: APB internal bus clock
|
||||
- description: ADC clock
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: pclk
|
||||
- const: adc
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
adc1-avdd-supply:
|
||||
description:
|
||||
ADC1 analog power supply.
|
||||
|
||||
adc1-vref-supply:
|
||||
description:
|
||||
ADC1 reference voltage supply.
|
||||
|
||||
adc2-avdd-supply:
|
||||
description:
|
||||
ADC2 analog power supply.
|
||||
|
||||
adc2-vref-supply:
|
||||
description:
|
||||
ADC2 reference voltage supply.
|
||||
|
||||
'#io-channel-cells':
|
||||
const: 1
|
||||
description: |
|
||||
Channels numbers available:
|
||||
if ADC1 is used (i.e. adc1-{avdd,vref}-supply present):
|
||||
- 0: ADC1 IN0
|
||||
- 1: ADC1 IN1
|
||||
- 2: ADC1 IN2
|
||||
- 3: ADC1 IN3
|
||||
- 4: ADC1 IN4
|
||||
- 5: ADC1 IN6
|
||||
- 6: ADC1 IN7
|
||||
- 7: ADC1 IN8
|
||||
if ADC2 is used (i.e. adc2-{avdd,vref}-supply present):
|
||||
- 8: ADC2 IN0
|
||||
- 9: ADC2 IN1
|
||||
- 10: ADC2 IN2
|
||||
- 11: ADC2 IN3
|
||||
- 12: ADC2 IN4
|
||||
- 13: ADC2 IN6
|
||||
- 14: ADC2 IN7
|
||||
- 15: ADC2 IN8
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- clocks
|
||||
- clock-names
|
||||
- power-domains
|
||||
- '#io-channel-cells'
|
||||
|
||||
# At least one of avvd/vref supplies
|
||||
anyOf:
|
||||
- required:
|
||||
- adc1-vref-supply
|
||||
- adc1-avdd-supply
|
||||
- required:
|
||||
- adc2-vref-supply
|
||||
- adc2-avdd-supply
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/r9a06g032-sysctrl.h>
|
||||
|
||||
adc: adc@40065000 {
|
||||
compatible = "renesas,r9a06g032-adc", "renesas,rzn1-adc";
|
||||
reg = <0x40065000 0x200>;
|
||||
clocks = <&sysctrl R9A06G032_HCLK_ADC>, <&sysctrl R9A06G032_CLK_ADC>;
|
||||
clock-names = "pclk", "adc";
|
||||
power-domains = <&sysctrl>;
|
||||
adc1-avdd-supply = <&adc1_avdd>;
|
||||
adc1-vref-supply = <&adc1_vref>;
|
||||
#io-channel-cells = <1>;
|
||||
};
|
||||
...
|
||||
@@ -16,6 +16,9 @@ properties:
|
||||
- const: rockchip,rk3066-tsadc
|
||||
- const: rockchip,rk3399-saradc
|
||||
- const: rockchip,rk3528-saradc
|
||||
- items:
|
||||
- const: rockchip,rk3506-saradc
|
||||
- const: rockchip,rk3528-saradc
|
||||
- const: rockchip,rk3562-saradc
|
||||
- const: rockchip,rk3588-saradc
|
||||
- items:
|
||||
|
||||
138
Documentation/devicetree/bindings/iio/dac/adi,ad5446.yaml
Normal file
138
Documentation/devicetree/bindings/iio/dac/adi,ad5446.yaml
Normal file
@@ -0,0 +1,138 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/dac/adi,ad5446.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices AD5446 and similar DACs
|
||||
|
||||
maintainers:
|
||||
- Michael Hennerich <michael.hennerich@analog.com>
|
||||
- Nuno Sá <nuno.sa@analog.com>
|
||||
|
||||
description:
|
||||
Digital to Analog Converter devices supporting both SPI and I2C interfaces.
|
||||
These devices feature a range of resolutions from 8-bit to 16-bit.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- description: SPI DACs
|
||||
enum:
|
||||
- adi,ad5300
|
||||
- adi,ad5310
|
||||
- adi,ad5320
|
||||
- adi,ad5444
|
||||
- adi,ad5446
|
||||
- adi,ad5450
|
||||
- adi,ad5451
|
||||
- adi,ad5452
|
||||
- adi,ad5453
|
||||
- adi,ad5512a
|
||||
- adi,ad5541a
|
||||
- adi,ad5542
|
||||
- adi,ad5542a
|
||||
- adi,ad5543
|
||||
- adi,ad5553
|
||||
- adi,ad5600
|
||||
- adi,ad5601
|
||||
- adi,ad5611
|
||||
- adi,ad5621
|
||||
- adi,ad5641
|
||||
- adi,ad5620-2500
|
||||
- adi,ad5620-1250
|
||||
- adi,ad5640-2500
|
||||
- adi,ad5640-1250
|
||||
- adi,ad5660-2500
|
||||
- adi,ad5660-1250
|
||||
- adi,ad5662
|
||||
- ti,dac081s101
|
||||
- ti,dac101s101
|
||||
- ti,dac121s101
|
||||
- description: I2C DACs
|
||||
enum:
|
||||
- adi,ad5301
|
||||
- adi,ad5311
|
||||
- adi,ad5321
|
||||
- adi,ad5602
|
||||
- adi,ad5612
|
||||
- adi,ad5622
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vcc-supply:
|
||||
description:
|
||||
Reference voltage supply. If not supplied, devices with internal
|
||||
voltage reference will use that.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- adi,ad5300
|
||||
- adi,ad5310
|
||||
- adi,ad5320
|
||||
- adi,ad5444
|
||||
- adi,ad5446
|
||||
- adi,ad5450
|
||||
- adi,ad5451
|
||||
- adi,ad5452
|
||||
- adi,ad5453
|
||||
- adi,ad5512a
|
||||
- adi,ad5541a
|
||||
- adi,ad5542
|
||||
- adi,ad5542a
|
||||
- adi,ad5543
|
||||
- adi,ad5553
|
||||
- adi,ad5600
|
||||
- adi,ad5601
|
||||
- adi,ad5611
|
||||
- adi,ad5621
|
||||
- adi,ad5641
|
||||
- adi,ad5620-2500
|
||||
- adi,ad5620-1250
|
||||
- adi,ad5640-2500
|
||||
- adi,ad5640-1250
|
||||
- adi,ad5660-2500
|
||||
- adi,ad5660-1250
|
||||
- adi,ad5662
|
||||
- ti,dac081s101
|
||||
- ti,dac101s101
|
||||
- ti,dac121s101
|
||||
then:
|
||||
allOf:
|
||||
- $ref: /schemas/spi/spi-peripheral-props.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
dac@0 {
|
||||
compatible = "adi,ad5446";
|
||||
reg = <0>;
|
||||
vcc-supply = <&dac_vref>;
|
||||
};
|
||||
};
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
dac@42 {
|
||||
compatible = "adi,ad5622";
|
||||
reg = <0x42>;
|
||||
vcc-supply = <&dac_vref>;
|
||||
};
|
||||
};
|
||||
...
|
||||
@@ -27,6 +27,14 @@ properties:
|
||||
LED current whilst the engine is running. First indexed value is
|
||||
the configuration for the RED LED, and second value is for the IR LED.
|
||||
|
||||
maxim,pulse-width-us:
|
||||
description: |
|
||||
LED pulse width in microseconds. Appropriate pulse width depends on
|
||||
factors such as optical window absorption, LED-to-sensor distance,
|
||||
and expected reflectivity of the skin or contact surface.
|
||||
enum: [200, 400, 800, 1600]
|
||||
default: 1600
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
|
||||
90
Documentation/devicetree/bindings/iio/imu/bosch,smi330.yaml
Normal file
90
Documentation/devicetree/bindings/iio/imu/bosch,smi330.yaml
Normal file
@@ -0,0 +1,90 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/imu/bosch,smi330.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Bosch SMI330 6-Axis IMU
|
||||
|
||||
maintainers:
|
||||
- Stefan Gutmann <stefam.gutmann@de.bosch.com>
|
||||
|
||||
description:
|
||||
SMI330 is a 6-axis inertial measurement unit that supports acceleration and
|
||||
gyroscopic measurements with hardware fifo buffering. Sensor also provides
|
||||
events information such as motion, no-motion and tilt detection.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: bosch,smi330
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vdd-supply:
|
||||
description: provide VDD power to the sensor.
|
||||
|
||||
vddio-supply:
|
||||
description: provide VDD IO power to the sensor.
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
interrupt-names:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
items:
|
||||
enum:
|
||||
- INT1
|
||||
- INT2
|
||||
|
||||
drive-open-drain:
|
||||
type: boolean
|
||||
description:
|
||||
set if the interrupt pin(s) should be configured as
|
||||
open drain. If not set, defaults to push-pull.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/spi/spi-peripheral-props.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
// Example for I2C
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
imu@68 {
|
||||
compatible = "bosch,smi330";
|
||||
reg = <0x68>;
|
||||
vddio-supply = <&vddio>;
|
||||
vdd-supply = <&vdd>;
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <26 IRQ_TYPE_EDGE_RISING>;
|
||||
interrupt-names = "INT1";
|
||||
};
|
||||
};
|
||||
|
||||
// Example for SPI
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
imu@0 {
|
||||
compatible = "bosch,smi330";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <10000000>;
|
||||
interrupt-parent = <&gpio>;
|
||||
interrupts = <26 IRQ_TYPE_EDGE_RISING>;
|
||||
interrupt-names = "INT1";
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/imu/invensense,icm45600.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: InvenSense ICM-45600 Inertial Measurement Unit
|
||||
|
||||
maintainers:
|
||||
- Remi Buisson <remi.buisson@tdk.com>
|
||||
|
||||
description: |
|
||||
6-axis MotionTracking device that combines a 3-axis gyroscope and a 3-axis
|
||||
accelerometer.
|
||||
|
||||
It has a configurable host interface that supports I3C, I2C and SPI serial
|
||||
communication, features up to 8kB FIFO and 2 programmable interrupts with
|
||||
ultra-low-power wake-on-motion support to minimize system power consumption.
|
||||
|
||||
Other industry-leading features include InvenSense on-chip APEX Motion
|
||||
Processing engine for gesture recognition, activity classification, and
|
||||
pedometer, along with programmable digital filters, and an embedded
|
||||
temperature sensor.
|
||||
|
||||
https://invensense.tdk.com/wp-content/uploads/documentation/DS-000576_ICM-45605.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- invensense,icm45605
|
||||
- invensense,icm45606
|
||||
- invensense,icm45608
|
||||
- invensense,icm45634
|
||||
- invensense,icm45686
|
||||
- invensense,icm45687
|
||||
- invensense,icm45688p
|
||||
- invensense,icm45689
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
interrupt-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- enum: [int1, int2]
|
||||
- const: int2
|
||||
description: Choose chip interrupt pin to be used as interrupt input.
|
||||
|
||||
drive-open-drain:
|
||||
type: boolean
|
||||
|
||||
vdd-supply: true
|
||||
|
||||
vddio-supply: true
|
||||
|
||||
mount-matrix: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- vdd-supply
|
||||
- vddio-supply
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
imu@68 {
|
||||
compatible = "invensense,icm45605";
|
||||
reg = <0x68>;
|
||||
interrupt-parent = <&gpio2>;
|
||||
interrupt-names = "int1";
|
||||
interrupts = <7 IRQ_TYPE_EDGE_RISING>;
|
||||
vdd-supply = <&vdd>;
|
||||
vddio-supply = <&vddio>;
|
||||
mount-matrix = "0", "-1", "0",
|
||||
"1", "0", "0",
|
||||
"0", "0", "1";
|
||||
};
|
||||
};
|
||||
@@ -86,7 +86,6 @@ unevaluatedProperties: false
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
|
||||
examples:
|
||||
- |
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/pressure/aosong,adp810.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: aosong adp810 differential pressure sensor
|
||||
|
||||
maintainers:
|
||||
- Akhilesh Patil <akhilesh@ee.iitb.ac.in>
|
||||
|
||||
description:
|
||||
ADP810 is differential pressure and temperature sensor. It has I2C bus
|
||||
interface with fixed address of 0x25. This sensor supports 8 bit CRC for
|
||||
reliable data transfer. It can measure differential pressure in the
|
||||
range -500 to 500Pa and temperate in the range -40 to +85 degree celsius.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- aosong,adp810
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vdd-supply: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- vdd-supply
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
pressure-sensor@25 {
|
||||
compatible = "aosong,adp810";
|
||||
reg = <0x25>;
|
||||
vdd-supply = <&vdd_regulator>;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/pressure/fsl,mpl3115.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: MPL3115 precision pressure sensor with altimetry
|
||||
|
||||
maintainers:
|
||||
- Antoni Pokusinski <apokusinski01@gmail.com>
|
||||
|
||||
description: |
|
||||
MPL3115 is a pressure/altitude and temperature sensor with I2C interface.
|
||||
It features two programmable interrupt lines which indicate events such as
|
||||
data ready or pressure/temperature threshold reached.
|
||||
https://www.nxp.com/docs/en/data-sheet/MPL3115A2.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: fsl,mpl3115
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vdd-supply: true
|
||||
|
||||
vddio-supply: true
|
||||
|
||||
interrupts:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
|
||||
interrupt-names:
|
||||
minItems: 1
|
||||
maxItems: 2
|
||||
items:
|
||||
enum:
|
||||
- INT1
|
||||
- INT2
|
||||
|
||||
drive-open-drain:
|
||||
type: boolean
|
||||
description:
|
||||
set if the specified interrupt pins should be configured as
|
||||
open drain. If not set, defaults to push-pull.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- vdd-supply
|
||||
- vddio-supply
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pressure@60 {
|
||||
compatible = "fsl,mpl3115";
|
||||
reg = <0x60>;
|
||||
vdd-supply = <&vdd>;
|
||||
vddio-supply = <&vddio>;
|
||||
interrupt-parent = <&gpio1>;
|
||||
interrupts = <4 IRQ_TYPE_EDGE_FALLING>;
|
||||
interrupt-names = "INT2";
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/iio/pressure/infineon,dps310.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Infineon DPS310 barometric pressure and temperature sensor
|
||||
|
||||
maintainers:
|
||||
- Eddie James <eajames@linux.ibm.com>
|
||||
|
||||
description:
|
||||
The DPS310 is a barometric pressure and temperature sensor with an I2C
|
||||
interface.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- infineon,dps310
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
"#io-channel-cells":
|
||||
const: 0
|
||||
|
||||
vdd-supply:
|
||||
description:
|
||||
Voltage supply for the chip's analog blocks.
|
||||
|
||||
vddio-supply:
|
||||
description:
|
||||
Digital voltage supply for the chip's digital blocks and I/O interface.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
dps: pressure-sensor@76 {
|
||||
compatible = "infineon,dps310";
|
||||
reg = <0x76>;
|
||||
#io-channel-cells = <0>;
|
||||
vdd-supply = <&vref1>;
|
||||
vddio-supply = <&vref2>;
|
||||
};
|
||||
};
|
||||
@@ -113,8 +113,6 @@ properties:
|
||||
- fsl,mma7660
|
||||
# MMA8450Q: Xtrinsic Low-power, 3-axis Xtrinsic Accelerometer
|
||||
- fsl,mma8450
|
||||
# MPL3115: Absolute Digital Pressure Sensor
|
||||
- fsl,mpl3115
|
||||
# MPR121: Proximity Capacitive Touch Sensor Controller
|
||||
- fsl,mpr121
|
||||
# Honeywell Humidicon HIH-6130 humidity/temperature sensor
|
||||
@@ -127,8 +125,6 @@ properties:
|
||||
- ibm,cffps2
|
||||
# IBM On-Chip Controller hwmon device
|
||||
- ibm,p8-occ-hwmon
|
||||
# Infineon barometric pressure and temperature sensor
|
||||
- infineon,dps310
|
||||
# Infineon IR36021 digital POL buck controller
|
||||
- infineon,ir36021
|
||||
# Infineon IRPS5401 Voltage Regulator (PMIC)
|
||||
|
||||
@@ -264,5 +264,5 @@ Configure RMS voltage event thresholds (requires interrupts):
|
||||
8. IIO Interfacing Tools
|
||||
========================
|
||||
|
||||
See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO
|
||||
See Documentation/iio/iio_tools.rst for the description of the available IIO
|
||||
interfacing tools.
|
||||
|
||||
@@ -374,11 +374,11 @@ Obtain buffered data:
|
||||
00001740 01 1a 00 00 ff ff fe 31 00 00 46 aa 00 03 37 f7 |.......1..F...7.|
|
||||
...
|
||||
|
||||
See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered
|
||||
See Documentation/iio/iio_devbuf.rst for more information about how buffered
|
||||
data is structured.
|
||||
|
||||
4. IIO Interfacing Tools
|
||||
========================
|
||||
|
||||
See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO
|
||||
See Documentation/iio/iio_tools.rst for the description of the available IIO
|
||||
interfacing tools.
|
||||
|
||||
@@ -436,11 +436,11 @@ Obtain buffered data::
|
||||
00006b60 09 63 00 00 00 00 1b 13 00 00 22 2f 00 03 23 91 |.c........"/..#.|
|
||||
...
|
||||
|
||||
See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered
|
||||
See Documentation/iio/iio_devbuf.rst for more information about how buffered
|
||||
data is structured.
|
||||
|
||||
4. IIO Interfacing Tools
|
||||
========================
|
||||
|
||||
See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO
|
||||
See Documentation/iio/iio_tools.rst for the description of the available IIO
|
||||
interfacing tools.
|
||||
|
||||
@@ -366,11 +366,11 @@ Obtain buffered data:
|
||||
0000ceb0 00 00 0d 2f 00 00 05 25 00 00 07 8d 00 00 a2 ce |.../...%........|
|
||||
...
|
||||
|
||||
See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered
|
||||
See Documentation/iio/iio_devbuf.rst for more information about how buffered
|
||||
data is structured.
|
||||
|
||||
4. IIO Interfacing Tools
|
||||
========================
|
||||
|
||||
See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO
|
||||
See Documentation/iio/iio_tools.rst for the description of the available IIO
|
||||
interfacing tools.
|
||||
|
||||
@@ -433,11 +433,11 @@ Obtain buffered data:
|
||||
00000f0 00004 00014 00015 00005 00012 00011 00005 00012
|
||||
...
|
||||
|
||||
See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered
|
||||
See Documentation/iio/iio_devbuf.rst for more information about how buffered
|
||||
data is structured.
|
||||
|
||||
4. IIO Interfacing Tools
|
||||
========================
|
||||
|
||||
See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO
|
||||
See Documentation/iio/iio_tools.rst for the description of the available IIO
|
||||
interfacing tools.
|
||||
|
||||
@@ -223,11 +223,11 @@ Obtain buffered data:
|
||||
002bc3c0 f7 fd 00 cb fb 94 24 80 f7 e3 00 f2 fb b8 24 80 |......$.......$.|
|
||||
...
|
||||
|
||||
See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered
|
||||
See Documentation/iio/iio_devbuf.rst for more information about how buffered
|
||||
data is structured.
|
||||
|
||||
4. IIO Interfacing Tools
|
||||
========================
|
||||
|
||||
See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO
|
||||
See Documentation/iio/iio_tools.rst for the description of the available IIO
|
||||
interfacing tools.
|
||||
|
||||
59
MAINTAINERS
59
MAINTAINERS
@@ -440,6 +440,18 @@ W: http://wiki.analog.com/AD5398
|
||||
W: https://ez.analog.com/linux-software-drivers
|
||||
F: drivers/regulator/ad5398.c
|
||||
|
||||
AD5446 ANALOG DEVICES INC AD5446 DAC DRIVER
|
||||
M: Michael Hennerich <michael.hennerich@analog.com>
|
||||
M: Nuno Sá <nuno.sa@analog.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Supported
|
||||
W: https://ez.analog.com/linux-software-drivers
|
||||
F: Documentation/devicetree/bindings/iio/dac/adi,ad5446.yaml
|
||||
F: drivers/iio/dac/ad5446-i2c.c
|
||||
F: drivers/iio/dac/ad5446-spi.c
|
||||
F: drivers/iio/dac/ad5446.c
|
||||
F: drivers/iio/dac/ad5446.h
|
||||
|
||||
AD714X CAPACITANCE TOUCH SENSOR DRIVER (AD7142/3/7/8/7A)
|
||||
M: Michael Hennerich <michael.hennerich@analog.com>
|
||||
S: Supported
|
||||
@@ -3749,6 +3761,13 @@ S: Maintained
|
||||
F: Documentation/devicetree/bindings/iio/chemical/aosong,ags02ma.yaml
|
||||
F: drivers/iio/chemical/ags02ma.c
|
||||
|
||||
AOSONG ADP810 DIFFERENTIAL PRESSURE SENSOR DRIVER
|
||||
M: Akhilesh Patil <akhilesh@ee.iitb.ac.in>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/iio/pressure/aosong,adp810.yaml
|
||||
F: drivers/iio/pressure/adp810.c
|
||||
|
||||
ASC7621 HARDWARE MONITOR DRIVER
|
||||
M: George Joseph <george.joseph@fairview5.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
@@ -4472,6 +4491,13 @@ F: include/net/bond*
|
||||
F: include/uapi/linux/if_bonding.h
|
||||
F: tools/testing/selftests/drivers/net/bonding/
|
||||
|
||||
BOSCH SENSORTEC BMA220 ACCELEROMETER IIO DRIVER
|
||||
M: Petre Rodan <petre.rodan@subdimension.ro>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml
|
||||
F: drivers/iio/accel/bma220*
|
||||
|
||||
BOSCH SENSORTEC BMA400 ACCELEROMETER IIO DRIVER
|
||||
M: Dan Robertson <dan@dlrobertson.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
@@ -12228,6 +12254,7 @@ INFINEON DPS310 Driver
|
||||
M: Eddie James <eajames@linux.ibm.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/iio/pressure/infineon,dps310.yaml
|
||||
F: drivers/iio/pressure/dps310.c
|
||||
|
||||
INFINEON PEB2466 ASoC CODEC
|
||||
@@ -13039,6 +13066,14 @@ F: Documentation/ABI/testing/sysfs-bus-iio-inv_icm42600
|
||||
F: Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml
|
||||
F: drivers/iio/imu/inv_icm42600/
|
||||
|
||||
INVENSENSE ICM-456xx IMU DRIVER
|
||||
M: Remi Buisson <remi.buisson@tdk.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
W: https://invensense.tdk.com/
|
||||
F: Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml
|
||||
F: drivers/iio/imu/inv_icm45600/
|
||||
|
||||
INVENSENSE MPU-3050 GYROSCOPE DRIVER
|
||||
M: Linus Walleij <linus.walleij@linaro.org>
|
||||
L: linux-iio@vger.kernel.org
|
||||
@@ -15175,6 +15210,15 @@ S: Orphan
|
||||
F: drivers/video/fbdev/matrox/matroxfb_*
|
||||
F: include/uapi/linux/matroxfb.h
|
||||
|
||||
MAX14001/MAX14002 IIO ADC DRIVER
|
||||
M: Kim Seer Paller <kimseer.paller@analog.com>
|
||||
M: Marilene Andrade Garcia <marilene.agarcia@gmail.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
S: Maintained
|
||||
W: https://ez.analog.com/linux-software-drivers
|
||||
F: Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml
|
||||
F: drivers/iio/adc/max14001.c
|
||||
|
||||
MAX15301 DRIVER
|
||||
M: Daniel Nilsson <daniel.nilsson@flex.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
@@ -21880,6 +21924,14 @@ S: Supported
|
||||
F: Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml
|
||||
F: drivers/counter/rz-mtu3-cnt.c
|
||||
|
||||
RENESAS RZ/T2H / RZ/N2H A/D DRIVER
|
||||
M: Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>
|
||||
L: linux-iio@vger.kernel.org
|
||||
L: linux-renesas-soc@vger.kernel.org
|
||||
S: Supported
|
||||
F: Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml
|
||||
F: drivers/iio/adc/rzt2h_adc.c
|
||||
|
||||
RENESAS RTCA-3 RTC DRIVER
|
||||
M: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
|
||||
L: linux-rtc@vger.kernel.org
|
||||
@@ -21901,6 +21953,13 @@ F: include/dt-bindings/net/pcs-rzn1-miic.h
|
||||
F: include/linux/pcs-rzn1-miic.h
|
||||
F: net/dsa/tag_rzn1_a5psw.c
|
||||
|
||||
RENESAS RZ/N1 ADC DRIVER
|
||||
M: Herve Codina <herve.codina@bootlin.com>
|
||||
L: linux-renesas-soc@vger.kernel.org
|
||||
S: Supported
|
||||
F: Documentation/devicetree/bindings/iio/adc/renesas,rzn1-adc.yaml
|
||||
F: drivers/iio/adc/rzn1-adc.c
|
||||
|
||||
RENESAS RZ/N1 DWMAC GLUE LAYER
|
||||
M: Romain Gantois <romain.gantois@bootlin.com>
|
||||
S: Maintained
|
||||
|
||||
@@ -218,15 +218,30 @@ config BMA180
|
||||
|
||||
config BMA220
|
||||
tristate "Bosch BMA220 3-Axis Accelerometer Driver"
|
||||
depends on SPI
|
||||
depends on I2C || SPI
|
||||
select REGMAP
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
select BMA220_I2C if I2C
|
||||
select BMA220_SPI if SPI
|
||||
help
|
||||
Say yes here to add support for the Bosch BMA220 triaxial
|
||||
acceleration sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called bma220_spi.
|
||||
module will be called bma220_core and you will also get
|
||||
bma220_i2c if I2C is enabled and bma220_spi if SPI is
|
||||
enabled.
|
||||
|
||||
config BMA220_I2C
|
||||
tristate
|
||||
select REGMAP_I2C
|
||||
depends on BMA220
|
||||
|
||||
config BMA220_SPI
|
||||
tristate
|
||||
select REGMAP_SPI
|
||||
depends on BMA220
|
||||
|
||||
config BMA400
|
||||
tristate "Bosch BMA400 3-Axis Accelerometer Driver"
|
||||
|
||||
@@ -25,7 +25,9 @@ obj-$(CONFIG_ADXL380) += adxl380.o
|
||||
obj-$(CONFIG_ADXL380_I2C) += adxl380_i2c.o
|
||||
obj-$(CONFIG_ADXL380_SPI) += adxl380_spi.o
|
||||
obj-$(CONFIG_BMA180) += bma180.o
|
||||
obj-$(CONFIG_BMA220) += bma220_spi.o
|
||||
obj-$(CONFIG_BMA220) += bma220_core.o
|
||||
obj-$(CONFIG_BMA220_I2C) += bma220_i2c.o
|
||||
obj-$(CONFIG_BMA220_SPI) += bma220_spi.o
|
||||
obj-$(CONFIG_BMA400) += bma400_core.o
|
||||
obj-$(CONFIG_BMA400_I2C) += bma400_i2c.o
|
||||
obj-$(CONFIG_BMA400_SPI) += bma400_spi.o
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
#include "adxl380.h"
|
||||
|
||||
#define ADXL380_ID_VAL 380
|
||||
#define ADXL318_ID_VAL 380
|
||||
#define ADXL382_ID_VAL 382
|
||||
#define ADXL319_ID_VAL 382
|
||||
|
||||
#define ADXL380_DEVID_AD_REG 0x00
|
||||
#define ADLX380_PART_ID_REG 0x02
|
||||
@@ -178,41 +180,6 @@ enum adxl380_tap_time_type {
|
||||
|
||||
static const int adxl380_range_scale_factor_tbl[] = { 1, 2, 4 };
|
||||
|
||||
const struct adxl380_chip_info adxl380_chip_info = {
|
||||
.name = "adxl380",
|
||||
.chip_id = ADXL380_ID_VAL,
|
||||
.scale_tbl = {
|
||||
[ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 },
|
||||
[ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 },
|
||||
[ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 },
|
||||
},
|
||||
.samp_freq_tbl = { 8000, 16000, 32000 },
|
||||
/*
|
||||
* The datasheet defines an intercept of 470 LSB at 25 degC
|
||||
* and a sensitivity of 10.2 LSB/C.
|
||||
*/
|
||||
.temp_offset = 25 * 102 / 10 - 470,
|
||||
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(adxl380_chip_info, "IIO_ADXL380");
|
||||
|
||||
const struct adxl380_chip_info adxl382_chip_info = {
|
||||
.name = "adxl382",
|
||||
.chip_id = ADXL382_ID_VAL,
|
||||
.scale_tbl = {
|
||||
[ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 },
|
||||
[ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 },
|
||||
[ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 },
|
||||
},
|
||||
.samp_freq_tbl = { 16000, 32000, 64000 },
|
||||
/*
|
||||
* The datasheet defines an intercept of 570 LSB at 25 degC
|
||||
* and a sensitivity of 10.2 LSB/C.
|
||||
*/
|
||||
.temp_offset = 25 * 102 / 10 - 570,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(adxl382_chip_info, "IIO_ADXL380");
|
||||
|
||||
static const unsigned int adxl380_th_reg_high_addr[2] = {
|
||||
[ADXL380_ACTIVITY] = ADXL380_THRESH_ACT_H_REG,
|
||||
[ADXL380_INACTIVITY] = ADXL380_THRESH_INACT_H_REG,
|
||||
@@ -276,9 +243,14 @@ static int adxl380_set_measure_en(struct adxl380_state *st, bool en)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Activity/ Inactivity detection available only in VLP/ULP mode */
|
||||
if (FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) ||
|
||||
FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl))
|
||||
/*
|
||||
* Activity/Inactivity detection available only in VLP/ULP
|
||||
* mode and for devices that support low power modes. Otherwise
|
||||
* go straight to measure mode (same bits as ADXL380_OP_MODE_HP).
|
||||
*/
|
||||
if (st->chip_info->has_low_power &&
|
||||
(FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) ||
|
||||
FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl)))
|
||||
op_mode = ADXL380_OP_MODE_VLP;
|
||||
else
|
||||
op_mode = ADXL380_OP_MODE_HP;
|
||||
@@ -1618,6 +1590,15 @@ static int adxl380_set_watermark(struct iio_dev *indio_dev, unsigned int val)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_info adxl318_info = {
|
||||
.read_raw = adxl380_read_raw,
|
||||
.read_avail = &adxl380_read_avail,
|
||||
.write_raw = adxl380_write_raw,
|
||||
.write_raw_get_fmt = adxl380_write_raw_get_fmt,
|
||||
.debugfs_reg_access = &adxl380_reg_access,
|
||||
.hwfifo_set_watermark = adxl380_set_watermark,
|
||||
};
|
||||
|
||||
static const struct iio_info adxl380_info = {
|
||||
.read_raw = adxl380_read_raw,
|
||||
.read_avail = &adxl380_read_avail,
|
||||
@@ -1632,6 +1613,81 @@ static const struct iio_info adxl380_info = {
|
||||
.hwfifo_set_watermark = adxl380_set_watermark,
|
||||
};
|
||||
|
||||
const struct adxl380_chip_info adxl318_chip_info = {
|
||||
.name = "adxl318",
|
||||
.chip_id = ADXL318_ID_VAL,
|
||||
.scale_tbl = {
|
||||
[ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 },
|
||||
[ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 },
|
||||
[ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 },
|
||||
},
|
||||
.samp_freq_tbl = { 8000, 16000, 32000 },
|
||||
/*
|
||||
* The datasheet defines an intercept of 550 LSB at 25 degC
|
||||
* and a sensitivity of 10.2 LSB/C.
|
||||
*/
|
||||
.temp_offset = 25 * 102 / 10 - 550,
|
||||
.info = &adxl318_info,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(adxl318_chip_info, "IIO_ADXL380");
|
||||
|
||||
const struct adxl380_chip_info adxl319_chip_info = {
|
||||
.name = "adxl319",
|
||||
.chip_id = ADXL319_ID_VAL,
|
||||
.scale_tbl = {
|
||||
[ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 },
|
||||
[ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 },
|
||||
[ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 },
|
||||
},
|
||||
.samp_freq_tbl = { 16000, 32000, 64000 },
|
||||
/*
|
||||
* The datasheet defines an intercept of 550 LSB at 25 degC
|
||||
* and a sensitivity of 10.2 LSB/C.
|
||||
*/
|
||||
.temp_offset = 25 * 102 / 10 - 550,
|
||||
.info = &adxl318_info,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(adxl319_chip_info, "IIO_ADXL380");
|
||||
|
||||
const struct adxl380_chip_info adxl380_chip_info = {
|
||||
.name = "adxl380",
|
||||
.chip_id = ADXL380_ID_VAL,
|
||||
.scale_tbl = {
|
||||
[ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 },
|
||||
[ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 },
|
||||
[ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 },
|
||||
},
|
||||
.samp_freq_tbl = { 8000, 16000, 32000 },
|
||||
/*
|
||||
* The datasheet defines an intercept of 470 LSB at 25 degC
|
||||
* and a sensitivity of 10.2 LSB/C.
|
||||
*/
|
||||
.temp_offset = 25 * 102 / 10 - 470,
|
||||
.has_low_power = true,
|
||||
.info = &adxl380_info,
|
||||
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(adxl380_chip_info, "IIO_ADXL380");
|
||||
|
||||
const struct adxl380_chip_info adxl382_chip_info = {
|
||||
.name = "adxl382",
|
||||
.chip_id = ADXL382_ID_VAL,
|
||||
.scale_tbl = {
|
||||
[ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 },
|
||||
[ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 },
|
||||
[ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 },
|
||||
},
|
||||
.samp_freq_tbl = { 16000, 32000, 64000 },
|
||||
/*
|
||||
* The datasheet defines an intercept of 570 LSB at 25 degC
|
||||
* and a sensitivity of 10.2 LSB/C.
|
||||
*/
|
||||
.temp_offset = 25 * 102 / 10 - 570,
|
||||
.has_low_power = true,
|
||||
.info = &adxl380_info,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(adxl382_chip_info, "IIO_ADXL380");
|
||||
|
||||
static const struct iio_event_spec adxl380_events[] = {
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
@@ -1866,7 +1922,7 @@ int adxl380_probe(struct device *dev, struct regmap *regmap,
|
||||
indio_dev->channels = adxl380_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(adxl380_channels);
|
||||
indio_dev->name = chip_info->name;
|
||||
indio_dev->info = &adxl380_info;
|
||||
indio_dev->info = chip_info->info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = devm_regulator_get_enable(dev, "vddio");
|
||||
|
||||
@@ -12,10 +12,14 @@ struct adxl380_chip_info {
|
||||
const char *name;
|
||||
const int scale_tbl[3][2];
|
||||
const int samp_freq_tbl[3];
|
||||
const struct iio_info *info;
|
||||
const int temp_offset;
|
||||
const u16 chip_id;
|
||||
const bool has_low_power;
|
||||
};
|
||||
|
||||
extern const struct adxl380_chip_info adxl318_chip_info;
|
||||
extern const struct adxl380_chip_info adxl319_chip_info;
|
||||
extern const struct adxl380_chip_info adxl380_chip_info;
|
||||
extern const struct adxl380_chip_info adxl382_chip_info;
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ static int adxl380_i2c_probe(struct i2c_client *client)
|
||||
}
|
||||
|
||||
static const struct i2c_device_id adxl380_i2c_id[] = {
|
||||
{ "adxl318", (kernel_ulong_t)&adxl318_chip_info },
|
||||
{ "adxl319", (kernel_ulong_t)&adxl319_chip_info },
|
||||
{ "adxl380", (kernel_ulong_t)&adxl380_chip_info },
|
||||
{ "adxl382", (kernel_ulong_t)&adxl382_chip_info },
|
||||
{ }
|
||||
@@ -40,6 +42,8 @@ static const struct i2c_device_id adxl380_i2c_id[] = {
|
||||
MODULE_DEVICE_TABLE(i2c, adxl380_i2c_id);
|
||||
|
||||
static const struct of_device_id adxl380_of_match[] = {
|
||||
{ .compatible = "adi,adxl318", .data = &adxl318_chip_info },
|
||||
{ .compatible = "adi,adxl319", .data = &adxl319_chip_info },
|
||||
{ .compatible = "adi,adxl380", .data = &adxl380_chip_info },
|
||||
{ .compatible = "adi,adxl382", .data = &adxl382_chip_info },
|
||||
{ }
|
||||
|
||||
@@ -35,6 +35,8 @@ static int adxl380_spi_probe(struct spi_device *spi)
|
||||
}
|
||||
|
||||
static const struct spi_device_id adxl380_spi_id[] = {
|
||||
{ "adxl318", (kernel_ulong_t)&adxl318_chip_info },
|
||||
{ "adxl319", (kernel_ulong_t)&adxl319_chip_info },
|
||||
{ "adxl380", (kernel_ulong_t)&adxl380_chip_info },
|
||||
{ "adxl382", (kernel_ulong_t)&adxl382_chip_info },
|
||||
{ }
|
||||
@@ -42,6 +44,8 @@ static const struct spi_device_id adxl380_spi_id[] = {
|
||||
MODULE_DEVICE_TABLE(spi, adxl380_spi_id);
|
||||
|
||||
static const struct of_device_id adxl380_of_match[] = {
|
||||
{ .compatible = "adi,adxl318", .data = &adxl318_chip_info },
|
||||
{ .compatible = "adi,adxl319", .data = &adxl319_chip_info },
|
||||
{ .compatible = "adi,adxl380", .data = &adxl380_chip_info },
|
||||
{ .compatible = "adi,adxl382", .data = &adxl382_chip_info },
|
||||
{ }
|
||||
|
||||
28
drivers/iio/accel/bma220.h
Normal file
28
drivers/iio/accel/bma220.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Forward declarations needed by the bma220 sources.
|
||||
*
|
||||
* Copyright 2025 Petre Rodan <petre.rodan@subdimension.ro>
|
||||
*/
|
||||
|
||||
#ifndef _BMA220_H
|
||||
#define _BMA220_H
|
||||
|
||||
#include <linux/pm.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define BMA220_REG_WDT 0x17
|
||||
#define BMA220_WDT_MASK GENMASK(2, 1)
|
||||
#define BMA220_WDT_OFF 0x0
|
||||
#define BMA220_WDT_1MS 0x2
|
||||
#define BMA220_WDT_10MS 0x3
|
||||
|
||||
struct device;
|
||||
|
||||
extern const struct regmap_config bma220_i2c_regmap_config;
|
||||
extern const struct regmap_config bma220_spi_regmap_config;
|
||||
extern const struct dev_pm_ops bma220_pm_ops;
|
||||
|
||||
int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq);
|
||||
|
||||
#endif
|
||||
585
drivers/iio/accel/bma220_core.c
Normal file
585
drivers/iio/accel/bma220_core.c
Normal file
@@ -0,0 +1,585 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* BMA220 Digital triaxial acceleration sensor driver
|
||||
*
|
||||
* Copyright (c) 2016,2020 Intel Corporation.
|
||||
* Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro>
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/types.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 "bma220.h"
|
||||
|
||||
#define BMA220_REG_ID 0x00
|
||||
#define BMA220_REG_REVISION_ID 0x01
|
||||
#define BMA220_REG_ACCEL_X 0x02
|
||||
#define BMA220_REG_ACCEL_Y 0x03
|
||||
#define BMA220_REG_ACCEL_Z 0x04
|
||||
#define BMA220_REG_CONF0 0x05
|
||||
#define BMA220_HIGH_DUR_MSK GENMASK(5, 0)
|
||||
#define BMA220_HIGH_HY_MSK GENMASK(7, 6)
|
||||
#define BMA220_REG_CONF1 0x06
|
||||
#define BMA220_HIGH_TH_MSK GENMASK(3, 0)
|
||||
#define BMA220_LOW_TH_MSK GENMASK(7, 4)
|
||||
#define BMA220_REG_CONF2 0x07
|
||||
#define BMA220_LOW_DUR_MSK GENMASK(5, 0)
|
||||
#define BMA220_LOW_HY_MSK GENMASK(7, 6)
|
||||
#define BMA220_REG_CONF3 0x08
|
||||
#define BMA220_TT_DUR_MSK GENMASK(2, 0)
|
||||
#define BMA220_TT_TH_MSK GENMASK(6, 3)
|
||||
#define BMA220_REG_CONF4 0x09
|
||||
#define BMA220_SLOPE_DUR_MSK GENMASK(1, 0)
|
||||
#define BMA220_SLOPE_TH_MSK GENMASK(5, 2)
|
||||
#define BMA220_REG_CONF5 0x0a
|
||||
#define BMA220_TIP_EN_MSK BIT(4)
|
||||
#define BMA220_REG_IF0 0x0b
|
||||
#define BMA220_REG_IF1 0x0c
|
||||
#define BMA220_IF_SLOPE BIT(0)
|
||||
#define BMA220_IF_DRDY BIT(1)
|
||||
#define BMA220_IF_HIGH BIT(2)
|
||||
#define BMA220_IF_LOW BIT(3)
|
||||
#define BMA220_IF_TT BIT(4)
|
||||
#define BMA220_REG_IE0 0x0d
|
||||
#define BMA220_INT_EN_TAP_Z_MSK BIT(0)
|
||||
#define BMA220_INT_EN_TAP_Y_MSK BIT(1)
|
||||
#define BMA220_INT_EN_TAP_X_MSK BIT(2)
|
||||
#define BMA220_INT_EN_SLOPE_Z_MSK BIT(3)
|
||||
#define BMA220_INT_EN_SLOPE_Y_MSK BIT(4)
|
||||
#define BMA220_INT_EN_SLOPE_X_MSK BIT(5)
|
||||
#define BMA220_INT_EN_DRDY_MSK BIT(7)
|
||||
#define BMA220_REG_IE1 0x0e
|
||||
#define BMA220_INT_EN_HIGH_Z_MSK BIT(0)
|
||||
#define BMA220_INT_EN_HIGH_Y_MSK BIT(1)
|
||||
#define BMA220_INT_EN_HIGH_X_MSK BIT(2)
|
||||
#define BMA220_INT_EN_LOW_MSK BIT(3)
|
||||
#define BMA220_INT_LATCH_MSK GENMASK(6, 4)
|
||||
#define BMA220_INT_RST_MSK BIT(7)
|
||||
#define BMA220_REG_IE2 0x0f
|
||||
#define BMA220_REG_FILTER 0x10
|
||||
#define BMA220_FILTER_MASK GENMASK(3, 0)
|
||||
#define BMA220_REG_RANGE 0x11
|
||||
#define BMA220_RANGE_MASK GENMASK(1, 0)
|
||||
#define BMA220_REG_SUSPEND 0x18
|
||||
#define BMA220_REG_SOFTRESET 0x19
|
||||
|
||||
#define BMA220_CHIP_ID 0xDD
|
||||
#define BMA220_SUSPEND_SLEEP 0xFF
|
||||
#define BMA220_SUSPEND_WAKE 0x00
|
||||
#define BMA220_RESET_MODE 0xFF
|
||||
#define BMA220_NONRESET_MODE 0x00
|
||||
|
||||
#define BMA220_DEVICE_NAME "bma220"
|
||||
|
||||
#define BMA220_COF_1000Hz 0x0
|
||||
#define BMA220_COF_500Hz 0x1
|
||||
#define BMA220_COF_250Hz 0x2
|
||||
#define BMA220_COF_125Hz 0x3
|
||||
#define BMA220_COF_64Hz 0x4
|
||||
#define BMA220_COF_32Hz 0x5
|
||||
|
||||
#define BMA220_ACCEL_CHANNEL(index, reg, axis) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.address = reg, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
|
||||
.info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) |\
|
||||
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
|
||||
.scan_index = index, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 6, \
|
||||
.storagebits = 8, \
|
||||
.shift = 2, \
|
||||
.endianness = IIO_CPU, \
|
||||
}, \
|
||||
}
|
||||
|
||||
enum bma220_axis {
|
||||
AXIS_X,
|
||||
AXIS_Y,
|
||||
AXIS_Z,
|
||||
};
|
||||
|
||||
static const int bma220_scale_table[][2] = {
|
||||
{ 0, 623000 }, { 1, 248000 }, { 2, 491000 }, { 4, 983000 },
|
||||
};
|
||||
|
||||
struct bma220_data {
|
||||
struct regmap *regmap;
|
||||
struct mutex lock;
|
||||
u8 lpf_3dB_freq_idx;
|
||||
u8 range_idx;
|
||||
struct iio_trigger *trig;
|
||||
struct {
|
||||
s8 chans[3];
|
||||
/* Ensure timestamp is naturally aligned. */
|
||||
aligned_s64 timestamp;
|
||||
} scan __aligned(IIO_DMA_MINALIGN);
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec bma220_channels[] = {
|
||||
BMA220_ACCEL_CHANNEL(0, BMA220_REG_ACCEL_X, X),
|
||||
BMA220_ACCEL_CHANNEL(1, BMA220_REG_ACCEL_Y, Y),
|
||||
BMA220_ACCEL_CHANNEL(2, BMA220_REG_ACCEL_Z, Z),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
};
|
||||
|
||||
/* Available cut-off frequencies of the low pass filter in Hz. */
|
||||
static const int bma220_lpf_3dB_freq_Hz_table[] = {
|
||||
[BMA220_COF_1000Hz] = 1000,
|
||||
[BMA220_COF_500Hz] = 500,
|
||||
[BMA220_COF_250Hz] = 250,
|
||||
[BMA220_COF_125Hz] = 125,
|
||||
[BMA220_COF_64Hz] = 64,
|
||||
[BMA220_COF_32Hz] = 32,
|
||||
};
|
||||
|
||||
static const unsigned long bma220_accel_scan_masks[] = {
|
||||
BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z),
|
||||
0
|
||||
};
|
||||
|
||||
static bool bma220_is_writable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case BMA220_REG_CONF0:
|
||||
case BMA220_REG_CONF1:
|
||||
case BMA220_REG_CONF2:
|
||||
case BMA220_REG_CONF3:
|
||||
case BMA220_REG_CONF4:
|
||||
case BMA220_REG_CONF5:
|
||||
case BMA220_REG_IE0:
|
||||
case BMA220_REG_IE1:
|
||||
case BMA220_REG_IE2:
|
||||
case BMA220_REG_FILTER:
|
||||
case BMA220_REG_RANGE:
|
||||
case BMA220_REG_WDT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const struct regmap_config bma220_spi_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.read_flag_mask = BIT(7),
|
||||
.max_register = BMA220_REG_SOFTRESET,
|
||||
.cache_type = REGCACHE_NONE,
|
||||
.writeable_reg = bma220_is_writable_reg,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(bma220_spi_regmap_config, "IIO_BOSCH_BMA220");
|
||||
|
||||
/*
|
||||
* Based on the datasheet the memory map differs between the SPI and the I2C
|
||||
* implementations. I2C register addresses are simply shifted to the left
|
||||
* by 1 bit yet the register size remains unchanged.
|
||||
* This driver employs the SPI memory map to correlate register names to
|
||||
* addresses regardless of the bus type.
|
||||
*/
|
||||
|
||||
const struct regmap_config bma220_i2c_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.reg_shift = -1,
|
||||
.max_register = BMA220_REG_SOFTRESET,
|
||||
.cache_type = REGCACHE_NONE,
|
||||
.writeable_reg = bma220_is_writable_reg,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(bma220_i2c_regmap_config, "IIO_BOSCH_BMA220");
|
||||
|
||||
static int bma220_data_rdy_trigger_set_state(struct iio_trigger *trig,
|
||||
bool state)
|
||||
{
|
||||
struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
|
||||
return regmap_update_bits(data->regmap, BMA220_REG_IE0,
|
||||
BMA220_INT_EN_DRDY_MSK,
|
||||
FIELD_PREP(BMA220_INT_EN_DRDY_MSK, state));
|
||||
}
|
||||
|
||||
static const struct iio_trigger_ops bma220_trigger_ops = {
|
||||
.set_trigger_state = &bma220_data_rdy_trigger_set_state,
|
||||
.validate_device = &iio_trigger_validate_own_device,
|
||||
};
|
||||
|
||||
static irqreturn_t bma220_trigger_handler(int irq, void *p)
|
||||
{
|
||||
int ret;
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, BMA220_REG_ACCEL_X,
|
||||
&data->scan.chans,
|
||||
sizeof(data->scan.chans));
|
||||
if (ret < 0)
|
||||
return IRQ_NONE;
|
||||
|
||||
iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
|
||||
iio_get_time_ns(indio_dev));
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int bma220_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
u8 index;
|
||||
unsigned int reg;
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
|
||||
guard(mutex)(&data->lock);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = regmap_read(data->regmap, chan->address, ®);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
*val = sign_extend32(reg >> chan->scan_type.shift,
|
||||
chan->scan_type.realbits - 1);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
index = data->range_idx;
|
||||
*val = bma220_scale_table[index][0];
|
||||
*val2 = bma220_scale_table[index][1];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
|
||||
index = data->lpf_3dB_freq_idx;
|
||||
*val = bma220_lpf_3dB_freq_Hz_table[index];
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int bma220_find_match_2dt(const int (*tbl)[2], const int n,
|
||||
const int val, const int val2)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (tbl[i][0] == val && tbl[i][1] == val2)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int bma220_find_match(const int *arr, const int n, const int val)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (arr[i] == val)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int bma220_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
int index = -1;
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
|
||||
guard(mutex)(&data->lock);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
index = bma220_find_match_2dt(bma220_scale_table,
|
||||
ARRAY_SIZE(bma220_scale_table),
|
||||
val, val2);
|
||||
if (index < 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, BMA220_REG_RANGE,
|
||||
BMA220_RANGE_MASK,
|
||||
FIELD_PREP(BMA220_RANGE_MASK, index));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->range_idx = index;
|
||||
|
||||
return 0;
|
||||
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
|
||||
index = bma220_find_match(bma220_lpf_3dB_freq_Hz_table,
|
||||
ARRAY_SIZE(bma220_lpf_3dB_freq_Hz_table),
|
||||
val);
|
||||
if (index < 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, BMA220_REG_FILTER,
|
||||
BMA220_FILTER_MASK,
|
||||
FIELD_PREP(BMA220_FILTER_MASK, index));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
data->lpf_3dB_freq_idx = index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int bma220_read_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_SCALE:
|
||||
*vals = (int *)bma220_scale_table;
|
||||
*type = IIO_VAL_INT_PLUS_MICRO;
|
||||
*length = ARRAY_SIZE(bma220_scale_table) * 2;
|
||||
return IIO_AVAIL_LIST;
|
||||
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
|
||||
*vals = (const int *)bma220_lpf_3dB_freq_Hz_table;
|
||||
*type = IIO_VAL_INT;
|
||||
*length = ARRAY_SIZE(bma220_lpf_3dB_freq_Hz_table);
|
||||
return IIO_AVAIL_LIST;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int bma220_reg_access(struct iio_dev *indio_dev, unsigned int reg,
|
||||
unsigned int writeval, unsigned int *readval)
|
||||
{
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (readval)
|
||||
return regmap_read(data->regmap, reg, readval);
|
||||
return regmap_write(data->regmap, reg, writeval);
|
||||
}
|
||||
|
||||
static const struct iio_info bma220_info = {
|
||||
.read_raw = bma220_read_raw,
|
||||
.write_raw = bma220_write_raw,
|
||||
.read_avail = bma220_read_avail,
|
||||
.debugfs_reg_access = &bma220_reg_access,
|
||||
};
|
||||
|
||||
static int bma220_reset(struct bma220_data *data, bool up)
|
||||
{
|
||||
int ret;
|
||||
unsigned int i, val;
|
||||
|
||||
/*
|
||||
* The chip can be reset by a simple register read.
|
||||
* We need up to 2 register reads of the softreset register
|
||||
* to make sure that the device is in the desired state.
|
||||
*/
|
||||
for (i = 0; i < 2; i++) {
|
||||
ret = regmap_read(data->regmap, BMA220_REG_SOFTRESET, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (up && val == BMA220_RESET_MODE)
|
||||
return 0;
|
||||
|
||||
if (!up && val == BMA220_NONRESET_MODE)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static int bma220_power(struct bma220_data *data, bool up)
|
||||
{
|
||||
int ret;
|
||||
unsigned int i, val;
|
||||
|
||||
/*
|
||||
* The chip can be suspended/woken up by a simple register read.
|
||||
* So, we need up to 2 register reads of the suspend register
|
||||
* to make sure that the device is in the desired state.
|
||||
*/
|
||||
for (i = 0; i < 2; i++) {
|
||||
ret = regmap_read(data->regmap, BMA220_REG_SUSPEND, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (up && val == BMA220_SUSPEND_SLEEP)
|
||||
return 0;
|
||||
|
||||
if (!up && val == BMA220_SUSPEND_WAKE)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static int bma220_init(struct device *dev, struct bma220_data *data)
|
||||
{
|
||||
int ret;
|
||||
unsigned int val;
|
||||
static const char * const regulator_names[] = { "vddd", "vddio", "vdda" };
|
||||
|
||||
ret = devm_regulator_bulk_get_enable(dev,
|
||||
ARRAY_SIZE(regulator_names),
|
||||
regulator_names);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to get regulators\n");
|
||||
|
||||
ret = regmap_read(data->regmap, BMA220_REG_ID, &val);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to read chip id register\n");
|
||||
|
||||
if (val != BMA220_CHIP_ID)
|
||||
dev_info(dev, "Unknown chip found: 0x%02x\n", val);
|
||||
|
||||
ret = bma220_power(data, true);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to power-on chip\n");
|
||||
|
||||
ret = bma220_reset(data, true);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to soft reset chip\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bma220_deinit(void *data_ptr)
|
||||
{
|
||||
struct bma220_data *data = data_ptr;
|
||||
int ret;
|
||||
struct device *dev = regmap_get_device(data->regmap);
|
||||
|
||||
ret = bma220_power(data, false);
|
||||
if (ret)
|
||||
dev_warn(dev,
|
||||
"Failed to put device into suspend mode (%pe)\n",
|
||||
ERR_PTR(ret));
|
||||
}
|
||||
|
||||
static irqreturn_t bma220_irq_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_dev *indio_dev = private;
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
unsigned int bma220_reg_if1;
|
||||
|
||||
ret = regmap_read(data->regmap, BMA220_REG_IF1, &bma220_reg_if1);
|
||||
if (ret)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (FIELD_GET(BMA220_IF_DRDY, bma220_reg_if1))
|
||||
iio_trigger_poll_nested(data->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq)
|
||||
{
|
||||
int ret;
|
||||
struct iio_dev *indio_dev;
|
||||
struct bma220_data *data;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->regmap = regmap;
|
||||
|
||||
ret = bma220_init(dev, data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_mutex_init(dev, &data->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
indio_dev->info = &bma220_info;
|
||||
indio_dev->name = BMA220_DEVICE_NAME;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = bma220_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(bma220_channels);
|
||||
indio_dev->available_scan_masks = bma220_accel_scan_masks;
|
||||
|
||||
if (irq > 0) {
|
||||
data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
|
||||
indio_dev->name,
|
||||
iio_device_id(indio_dev));
|
||||
if (!data->trig)
|
||||
return -ENOMEM;
|
||||
|
||||
data->trig->ops = &bma220_trigger_ops;
|
||||
iio_trigger_set_drvdata(data->trig, indio_dev);
|
||||
|
||||
ret = devm_iio_trigger_register(dev, data->trig);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"iio trigger register fail\n");
|
||||
indio_dev->trig = iio_trigger_get(data->trig);
|
||||
ret = devm_request_threaded_irq(dev, irq, NULL,
|
||||
&bma220_irq_handler, IRQF_ONESHOT,
|
||||
indio_dev->name, indio_dev);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"request irq %d failed\n", irq);
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(dev, bma220_deinit, data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
|
||||
bma220_trigger_handler, NULL);
|
||||
if (ret < 0)
|
||||
dev_err_probe(dev, ret, "iio triggered buffer setup failed\n");
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(bma220_common_probe, "IIO_BOSCH_BMA220");
|
||||
|
||||
static int bma220_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
|
||||
return bma220_power(data, false);
|
||||
}
|
||||
|
||||
static int bma220_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
|
||||
return bma220_power(data, true);
|
||||
}
|
||||
EXPORT_NS_SIMPLE_DEV_PM_OPS(bma220_pm_ops, bma220_suspend, bma220_resume,
|
||||
IIO_BOSCH_BMA220);
|
||||
|
||||
MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>");
|
||||
MODULE_DESCRIPTION("BMA220 acceleration sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
69
drivers/iio/accel/bma220_i2c.c
Normal file
69
drivers/iio/accel/bma220_i2c.c
Normal file
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Bosch triaxial acceleration sensor
|
||||
*
|
||||
* Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro>
|
||||
*
|
||||
* Datasheet: https://media.digikey.com/pdf/Data%20Sheets/Bosch/BMA220.pdf
|
||||
* I2C address is either 0x0b or 0x0a depending on CSB (pin 10)
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "bma220.h"
|
||||
|
||||
static int bma220_set_wdt(struct regmap *regmap, const u8 val)
|
||||
{
|
||||
return regmap_update_bits(regmap, BMA220_REG_WDT, BMA220_WDT_MASK,
|
||||
FIELD_PREP(BMA220_WDT_MASK, val));
|
||||
}
|
||||
|
||||
static int bma220_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &bma220_i2c_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(&client->dev, PTR_ERR(regmap),
|
||||
"failed to create regmap\n");
|
||||
|
||||
ret = bma220_common_probe(&client->dev, regmap, client->irq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return bma220_set_wdt(regmap, BMA220_WDT_1MS);
|
||||
}
|
||||
|
||||
static const struct of_device_id bma220_i2c_match[] = {
|
||||
{ .compatible = "bosch,bma220" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, bma220_i2c_match);
|
||||
|
||||
static const struct i2c_device_id bma220_i2c_id[] = {
|
||||
{ "bma220" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, bma220_i2c_id);
|
||||
|
||||
static struct i2c_driver bma220_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "bma220_i2c",
|
||||
.pm = pm_sleep_ptr(&bma220_pm_ops),
|
||||
.of_match_table = bma220_i2c_match,
|
||||
},
|
||||
.probe = bma220_i2c_probe,
|
||||
.id_table = bma220_i2c_id,
|
||||
};
|
||||
module_i2c_driver(bma220_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>");
|
||||
MODULE_DESCRIPTION("Bosch triaxial acceleration sensor i2c driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("IIO_BOSCH_BMA220");
|
||||
@@ -5,326 +5,56 @@
|
||||
* Copyright (c) 2016,2020 Intel Corporation.
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include "bma220.h"
|
||||
|
||||
#define BMA220_REG_ID 0x00
|
||||
#define BMA220_REG_ACCEL_X 0x02
|
||||
#define BMA220_REG_ACCEL_Y 0x03
|
||||
#define BMA220_REG_ACCEL_Z 0x04
|
||||
#define BMA220_REG_RANGE 0x11
|
||||
#define BMA220_REG_SUSPEND 0x18
|
||||
|
||||
#define BMA220_CHIP_ID 0xDD
|
||||
#define BMA220_READ_MASK BIT(7)
|
||||
#define BMA220_RANGE_MASK GENMASK(1, 0)
|
||||
#define BMA220_SUSPEND_SLEEP 0xFF
|
||||
#define BMA220_SUSPEND_WAKE 0x00
|
||||
|
||||
#define BMA220_DEVICE_NAME "bma220"
|
||||
|
||||
#define BMA220_ACCEL_CHANNEL(index, reg, axis) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.address = reg, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.scan_index = index, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 6, \
|
||||
.storagebits = 8, \
|
||||
.shift = 2, \
|
||||
.endianness = IIO_CPU, \
|
||||
}, \
|
||||
}
|
||||
|
||||
enum bma220_axis {
|
||||
AXIS_X,
|
||||
AXIS_Y,
|
||||
AXIS_Z,
|
||||
};
|
||||
|
||||
static const int bma220_scale_table[][2] = {
|
||||
{0, 623000}, {1, 248000}, {2, 491000}, {4, 983000},
|
||||
};
|
||||
|
||||
struct bma220_data {
|
||||
struct spi_device *spi_device;
|
||||
struct mutex lock;
|
||||
struct {
|
||||
s8 chans[3];
|
||||
/* Ensure timestamp is naturally aligned. */
|
||||
aligned_s64 timestamp;
|
||||
} scan;
|
||||
u8 tx_buf[2] __aligned(IIO_DMA_MINALIGN);
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec bma220_channels[] = {
|
||||
BMA220_ACCEL_CHANNEL(0, BMA220_REG_ACCEL_X, X),
|
||||
BMA220_ACCEL_CHANNEL(1, BMA220_REG_ACCEL_Y, Y),
|
||||
BMA220_ACCEL_CHANNEL(2, BMA220_REG_ACCEL_Z, Z),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(3),
|
||||
};
|
||||
|
||||
static inline int bma220_read_reg(struct spi_device *spi, u8 reg)
|
||||
static int bma220_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
return spi_w8r8(spi, reg | BMA220_READ_MASK);
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = devm_regmap_init_spi(spi, &bma220_spi_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(&spi->dev, PTR_ERR(regmap),
|
||||
"failed to create regmap\n");
|
||||
|
||||
return bma220_common_probe(&spi->dev, regmap, spi->irq);
|
||||
}
|
||||
|
||||
static const unsigned long bma220_accel_scan_masks[] = {
|
||||
BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z),
|
||||
0
|
||||
};
|
||||
|
||||
static irqreturn_t bma220_trigger_handler(int irq, void *p)
|
||||
{
|
||||
int ret;
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
struct spi_device *spi = data->spi_device;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->tx_buf[0] = BMA220_REG_ACCEL_X | BMA220_READ_MASK;
|
||||
ret = spi_write_then_read(spi, data->tx_buf, 1, &data->scan.chans,
|
||||
ARRAY_SIZE(bma220_channels) - 1);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
|
||||
pf->timestamp);
|
||||
err:
|
||||
mutex_unlock(&data->lock);
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int bma220_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
u8 range_idx;
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = bma220_read_reg(data->spi_device, chan->address);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
*val = sign_extend32(ret >> chan->scan_type.shift,
|
||||
chan->scan_type.realbits - 1);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = bma220_read_reg(data->spi_device, BMA220_REG_RANGE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
range_idx = ret & BMA220_RANGE_MASK;
|
||||
*val = bma220_scale_table[range_idx][0];
|
||||
*val2 = bma220_scale_table[range_idx][1];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int bma220_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
int index = -1;
|
||||
struct bma220_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
for (i = 0; i < ARRAY_SIZE(bma220_scale_table); i++)
|
||||
if (val == bma220_scale_table[i][0] &&
|
||||
val2 == bma220_scale_table[i][1]) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
if (index < 0)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->tx_buf[0] = BMA220_REG_RANGE;
|
||||
data->tx_buf[1] = index;
|
||||
ret = spi_write(data->spi_device, data->tx_buf,
|
||||
sizeof(data->tx_buf));
|
||||
if (ret < 0)
|
||||
dev_err(&data->spi_device->dev,
|
||||
"failed to set measurement range\n");
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int bma220_read_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_SCALE:
|
||||
*vals = (int *)bma220_scale_table;
|
||||
*type = IIO_VAL_INT_PLUS_MICRO;
|
||||
*length = ARRAY_SIZE(bma220_scale_table) * 2;
|
||||
return IIO_AVAIL_LIST;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info bma220_info = {
|
||||
.read_raw = bma220_read_raw,
|
||||
.write_raw = bma220_write_raw,
|
||||
.read_avail = bma220_read_avail,
|
||||
};
|
||||
|
||||
static int bma220_init(struct spi_device *spi)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = bma220_read_reg(spi, BMA220_REG_ID);
|
||||
if (ret != BMA220_CHIP_ID)
|
||||
return -ENODEV;
|
||||
|
||||
/* Make sure the chip is powered on */
|
||||
ret = bma220_read_reg(spi, BMA220_REG_SUSPEND);
|
||||
if (ret == BMA220_SUSPEND_WAKE)
|
||||
ret = bma220_read_reg(spi, BMA220_REG_SUSPEND);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret == BMA220_SUSPEND_WAKE)
|
||||
return -EBUSY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bma220_power(struct spi_device *spi, bool up)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
/**
|
||||
* The chip can be suspended/woken up by a simple register read.
|
||||
* So, we need up to 2 register reads of the suspend register
|
||||
* to make sure that the device is in the desired state.
|
||||
*/
|
||||
for (i = 0; i < 2; i++) {
|
||||
ret = bma220_read_reg(spi, BMA220_REG_SUSPEND);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (up && ret == BMA220_SUSPEND_SLEEP)
|
||||
return 0;
|
||||
|
||||
if (!up && ret == BMA220_SUSPEND_WAKE)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static void bma220_deinit(void *spi)
|
||||
{
|
||||
bma220_power(spi, false);
|
||||
}
|
||||
|
||||
static int bma220_probe(struct spi_device *spi)
|
||||
{
|
||||
int ret;
|
||||
struct iio_dev *indio_dev;
|
||||
struct bma220_data *data;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->spi_device = spi;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
indio_dev->info = &bma220_info;
|
||||
indio_dev->name = BMA220_DEVICE_NAME;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = bma220_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(bma220_channels);
|
||||
indio_dev->available_scan_masks = bma220_accel_scan_masks;
|
||||
|
||||
ret = bma220_init(data->spi_device);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(&spi->dev, bma220_deinit, spi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
|
||||
iio_pollfunc_store_time,
|
||||
bma220_trigger_handler, NULL);
|
||||
if (ret < 0) {
|
||||
dev_err(&spi->dev, "iio triggered buffer setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return devm_iio_device_register(&spi->dev, indio_dev);
|
||||
}
|
||||
|
||||
static int bma220_suspend(struct device *dev)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
|
||||
return bma220_power(spi, false);
|
||||
}
|
||||
|
||||
static int bma220_resume(struct device *dev)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
|
||||
return bma220_power(spi, true);
|
||||
}
|
||||
static DEFINE_SIMPLE_DEV_PM_OPS(bma220_pm_ops, bma220_suspend, bma220_resume);
|
||||
|
||||
static const struct spi_device_id bma220_spi_id[] = {
|
||||
{"bma220", 0},
|
||||
{ "bma220", 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct acpi_device_id bma220_acpi_id[] = {
|
||||
{"BMA0220", 0},
|
||||
{ "BMA0220", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, bma220_spi_id);
|
||||
|
||||
static struct spi_driver bma220_driver = {
|
||||
static const struct of_device_id bma220_of_spi_match[] = {
|
||||
{ .compatible = "bosch,bma220" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, bma220_of_spi_match);
|
||||
|
||||
static struct spi_driver bma220_spi_driver = {
|
||||
.driver = {
|
||||
.name = "bma220_spi",
|
||||
.pm = pm_sleep_ptr(&bma220_pm_ops),
|
||||
.of_match_table = bma220_of_spi_match,
|
||||
.acpi_match_table = bma220_acpi_id,
|
||||
},
|
||||
.probe = bma220_probe,
|
||||
.probe = bma220_spi_probe,
|
||||
.id_table = bma220_spi_id,
|
||||
};
|
||||
module_spi_driver(bma220_driver);
|
||||
module_spi_driver(bma220_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>");
|
||||
MODULE_DESCRIPTION("BMA220 acceleration sensor driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("BMA220 triaxial acceleration sensor spi driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("IIO_BOSCH_BMA220");
|
||||
|
||||
@@ -16,31 +16,44 @@
|
||||
* Read-Only Registers
|
||||
*/
|
||||
|
||||
/* Chip ID of BMA 400 devices found in the chip ID register. */
|
||||
#define BMA400_ID_REG_VAL 0x90
|
||||
|
||||
/* Status and ID registers */
|
||||
#define BMA400_CHIP_ID_REG 0x00
|
||||
#define BMA400_ERR_REG 0x02
|
||||
#define BMA400_STATUS_REG 0x03
|
||||
|
||||
/* Acceleration registers */
|
||||
#define BMA400_X_AXIS_LSB_REG 0x04
|
||||
#define BMA400_X_AXIS_MSB_REG 0x05
|
||||
#define BMA400_Y_AXIS_LSB_REG 0x06
|
||||
#define BMA400_Y_AXIS_MSB_REG 0x07
|
||||
#define BMA400_Z_AXIS_LSB_REG 0x08
|
||||
#define BMA400_Z_AXIS_MSB_REG 0x09
|
||||
#define BMA400_ACC_X_LSB_REG 0x04
|
||||
#define BMA400_ACC_X_MSB_REG 0x05
|
||||
#define BMA400_ACC_Y_LSB_REG 0x06
|
||||
#define BMA400_ACC_Y_MSB_REG 0x07
|
||||
#define BMA400_ACC_Z_LSB_REG 0x08
|
||||
#define BMA400_ACC_Z_MSB_REG 0x09
|
||||
|
||||
/* Sensor time registers */
|
||||
#define BMA400_SENSOR_TIME0 0x0a
|
||||
#define BMA400_SENSOR_TIME1 0x0b
|
||||
#define BMA400_SENSOR_TIME2 0x0c
|
||||
#define BMA400_SENSOR_TIME0_REG 0x0a
|
||||
#define BMA400_SENSOR_TIME1_REG 0x0b
|
||||
#define BMA400_SENSOR_TIME2_REG 0x0c
|
||||
|
||||
/* Event and interrupt registers */
|
||||
#define BMA400_EVENT_REG 0x0d
|
||||
|
||||
#define BMA400_INT_STAT0_REG 0x0e
|
||||
#define BMA400_INT_STAT0_GEN1_MASK BIT(2)
|
||||
#define BMA400_INT_STAT0_GEN2_MASK BIT(3)
|
||||
#define BMA400_INT_STAT0_DRDY_MASK BIT(7)
|
||||
|
||||
#define BMA400_INT_STAT1_REG 0x0f
|
||||
#define BMA400_INT_STAT1_STEP_INT_MASK GENMASK(9, 8)
|
||||
#define BMA400_INT_STAT1_S_TAP_MASK BIT(10)
|
||||
#define BMA400_INT_STAT1_D_TAP_MASK BIT(11)
|
||||
|
||||
#define BMA400_INT_STAT2_REG 0x10
|
||||
#define BMA400_INT12_MAP_REG 0x23
|
||||
#define BMA400_INT_ENG_OVRUN_MSK BIT(4)
|
||||
|
||||
/* Bit present in all INT_STAT registers */
|
||||
#define BMA400_INT_STAT_ENG_OVRRUN_MASK BIT(4)
|
||||
|
||||
/* Temperature register */
|
||||
#define BMA400_TEMP_DATA_REG 0x11
|
||||
@@ -55,70 +68,100 @@
|
||||
#define BMA400_STEP_CNT1_REG 0x16
|
||||
#define BMA400_STEP_CNT3_REG 0x17
|
||||
#define BMA400_STEP_STAT_REG 0x18
|
||||
#define BMA400_STEP_INT_MSK BIT(0)
|
||||
#define BMA400_STEP_RAW_LEN 0x03
|
||||
#define BMA400_STEP_STAT_MASK GENMASK(9, 8)
|
||||
|
||||
/*
|
||||
* Read-write configuration registers
|
||||
*/
|
||||
#define BMA400_ACC_CONFIG0_REG 0x19
|
||||
#define BMA400_ACC_CONFIG1_REG 0x1a
|
||||
#define BMA400_ACC_CONFIG0_REG 0x19
|
||||
#define BMA400_ACC_CONFIG0_LP_OSR_MASK GENMASK(6, 5)
|
||||
|
||||
#define BMA400_ACC_CONFIG1_REG 0x1a
|
||||
#define BMA400_ACC_CONFIG1_ODR_MASK GENMASK(3, 0)
|
||||
#define BMA400_ACC_CONFIG1_ODR_MIN_RAW 0x05
|
||||
#define BMA400_ACC_CONFIG1_ODR_LP_RAW 0x06
|
||||
#define BMA400_ACC_CONFIG1_ODR_MAX_RAW 0x0b
|
||||
#define BMA400_ACC_CONFIG1_ODR_MAX_HZ 800
|
||||
#define BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ 25
|
||||
#define BMA400_ACC_CONFIG1_ODR_MIN_HZ 12
|
||||
#define BMA400_ACC_CONFIG1_NP_OSR_MASK GENMASK(5, 4)
|
||||
#define BMA400_ACC_CONFIG1_ACC_RANGE_MASK GENMASK(7, 6)
|
||||
|
||||
#define BMA400_ACC_CONFIG2_REG 0x1b
|
||||
#define BMA400_CMD_REG 0x7e
|
||||
|
||||
/* Interrupt registers */
|
||||
#define BMA400_INT_CONFIG0_REG 0x1f
|
||||
#define BMA400_INT_CONFIG0_GEN1_MASK BIT(2)
|
||||
#define BMA400_INT_CONFIG0_GEN2_MASK BIT(3)
|
||||
#define BMA400_INT_CONFIG0_DRDY_MASK BIT(7)
|
||||
|
||||
enum bma400_generic_intr {
|
||||
BMA400_GEN1_INTR = 0x1,
|
||||
BMA400_GEN2_INTR = 0x2,
|
||||
};
|
||||
|
||||
#define BMA400_INT_CONFIG1_REG 0x20
|
||||
#define BMA400_INT_CONFIG1_STEP_INT_MASK BIT(0)
|
||||
#define BMA400_INT_CONFIG1_S_TAP_MASK BIT(2)
|
||||
#define BMA400_INT_CONFIG1_D_TAP_MASK BIT(3)
|
||||
|
||||
#define BMA400_INT1_MAP_REG 0x21
|
||||
#define BMA400_INT12_MAP_REG 0x23
|
||||
#define BMA400_INT_IO_CTRL_REG 0x24
|
||||
#define BMA400_INT_DRDY_MSK BIT(7)
|
||||
|
||||
/* Chip ID of BMA 400 devices found in the chip ID register. */
|
||||
#define BMA400_ID_REG_VAL 0x90
|
||||
|
||||
#define BMA400_LP_OSR_SHIFT 5
|
||||
#define BMA400_NP_OSR_SHIFT 4
|
||||
#define BMA400_SCALE_SHIFT 6
|
||||
|
||||
#define BMA400_TWO_BITS_MASK GENMASK(1, 0)
|
||||
#define BMA400_LP_OSR_MASK GENMASK(6, 5)
|
||||
#define BMA400_NP_OSR_MASK GENMASK(5, 4)
|
||||
#define BMA400_ACC_ODR_MASK GENMASK(3, 0)
|
||||
#define BMA400_ACC_SCALE_MASK GENMASK(7, 6)
|
||||
|
||||
#define BMA400_ACC_ODR_MIN_RAW 0x05
|
||||
#define BMA400_ACC_ODR_LP_RAW 0x06
|
||||
#define BMA400_ACC_ODR_MAX_RAW 0x0b
|
||||
|
||||
#define BMA400_ACC_ODR_MAX_HZ 800
|
||||
#define BMA400_ACC_ODR_MIN_WHOLE_HZ 25
|
||||
#define BMA400_ACC_ODR_MIN_HZ 12
|
||||
|
||||
/* Generic interrupts register */
|
||||
#define BMA400_GEN1INT_CONFIG0 0x3f
|
||||
#define BMA400_GEN2INT_CONFIG0 0x4A
|
||||
#define BMA400_GENINT_CONFIG_REG_BASE 0x3f
|
||||
#define BMA400_NUM_GENINT_CONFIG_REGS 11
|
||||
#define BMA400_GENINT_CONFIG_REG(gen_intr, config_idx) \
|
||||
(BMA400_GENINT_CONFIG_REG_BASE + \
|
||||
(gen_intr - 1) * BMA400_NUM_GENINT_CONFIG_REGS + \
|
||||
(config_idx))
|
||||
#define BMA400_GENINT_CONFIG0_HYST_MASK GENMASK(1, 0)
|
||||
#define BMA400_GENINT_CONFIG0_REF_UPD_MODE_MASK GENMASK(3, 2)
|
||||
#define BMA400_GENINT_CONFIG0_DATA_SRC_MASK BIT(4)
|
||||
#define BMA400_GENINT_CONFIG0_X_EN_MASK BIT(5)
|
||||
#define BMA400_GENINT_CONFIG0_Y_EN_MASK BIT(6)
|
||||
#define BMA400_GENINT_CONFIG0_Z_EN_MASK BIT(7)
|
||||
|
||||
enum bma400_accel_data_src {
|
||||
ACCEL_FILT1 = 0x0,
|
||||
ACCEL_FILT2 = 0x1,
|
||||
};
|
||||
|
||||
enum bma400_ref_updt_mode {
|
||||
BMA400_REF_MANUAL_UPDT_MODE = 0x0,
|
||||
BMA400_REF_ONETIME_UPDT_MODE = 0x1,
|
||||
BMA400_REF_EVERYTIME_UPDT_MODE = 0x2,
|
||||
BMA400_REF_EVERYTIME_LP_UPDT_MODE = 0x3,
|
||||
};
|
||||
|
||||
#define BMA400_GEN_CONFIG1_OFF 0x01
|
||||
#define BMA400_GEN_CONFIG2_OFF 0x02
|
||||
#define BMA400_GEN_CONFIG3_OFF 0x03
|
||||
#define BMA400_GEN_CONFIG31_OFF 0x04
|
||||
#define BMA400_INT_GEN1_MSK BIT(2)
|
||||
#define BMA400_INT_GEN2_MSK BIT(3)
|
||||
#define BMA400_GEN_HYST_MSK GENMASK(1, 0)
|
||||
#define BMA400_GENINT_CONFIG1_AXES_COMB_MASK BIT(0)
|
||||
#define BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK BIT(1)
|
||||
|
||||
enum bma400_genintr_acceleval_axescomb {
|
||||
BMA400_EVAL_X_OR_Y_OR_Z = 0x0,
|
||||
BMA400_EVAL_X_AND_Y_AND_Z = 0x1,
|
||||
};
|
||||
|
||||
enum bma400_detect_criterion {
|
||||
BMA400_DETECT_INACTIVITY = 0x0,
|
||||
BMA400_DETECT_ACTIVITY = 0x1,
|
||||
};
|
||||
|
||||
/* TAP config registers */
|
||||
#define BMA400_TAP_CONFIG 0x57
|
||||
#define BMA400_TAP_CONFIG1 0x58
|
||||
#define BMA400_S_TAP_MSK BIT(2)
|
||||
#define BMA400_D_TAP_MSK BIT(3)
|
||||
#define BMA400_INT_S_TAP_MSK BIT(10)
|
||||
#define BMA400_INT_D_TAP_MSK BIT(11)
|
||||
#define BMA400_TAP_SEN_MSK GENMASK(2, 0)
|
||||
#define BMA400_TAP_TICSTH_MSK GENMASK(1, 0)
|
||||
#define BMA400_TAP_QUIET_MSK GENMASK(3, 2)
|
||||
#define BMA400_TAP_QUIETDT_MSK GENMASK(5, 4)
|
||||
#define BMA400_TAP_CONFIG_REG 0x57
|
||||
#define BMA400_TAP_CONFIG_SEN_MASK GENMASK(2, 0)
|
||||
|
||||
#define BMA400_TAP_CONFIG1_REG 0x58
|
||||
#define BMA400_TAP_CONFIG1_TICSTH_MASK GENMASK(1, 0)
|
||||
#define BMA400_TAP_CONFIG1_QUIET_MASK GENMASK(3, 2)
|
||||
#define BMA400_TAP_CONFIG1_QUIETDT_MASK GENMASK(5, 4)
|
||||
#define BMA400_TAP_TIM_LIST_LEN 4
|
||||
|
||||
#define BMA400_CMD_REG 0x7e
|
||||
/*
|
||||
* BMA400_SCALE_MIN macro value represents m/s^2 for 1 LSB before
|
||||
* converting to micro values for +-2g range.
|
||||
@@ -138,8 +181,8 @@
|
||||
* To select +-8g = 9577 << 2 = raw value to write is 2.
|
||||
* To select +-16g = 9577 << 3 = raw value to write is 3.
|
||||
*/
|
||||
#define BMA400_SCALE_MIN 9577
|
||||
#define BMA400_SCALE_MAX 76617
|
||||
#define BMA400_ACC_SCALE_MIN 9577
|
||||
#define BMA400_ACC_SCALE_MAX 76617
|
||||
|
||||
extern const struct regmap_config bma400_regmap_config;
|
||||
|
||||
|
||||
@@ -121,21 +121,56 @@ struct bma400_data {
|
||||
__be16 duration;
|
||||
};
|
||||
|
||||
struct bma400_genintr_info {
|
||||
enum bma400_generic_intr genintr;
|
||||
unsigned int intrmask;
|
||||
enum iio_event_direction dir;
|
||||
enum bma400_detect_criterion detect_mode;
|
||||
};
|
||||
|
||||
/* Lookup struct for determining GEN1/GEN2 based on dir */
|
||||
static const struct bma400_genintr_info bma400_genintrs[] = {
|
||||
[IIO_EV_DIR_RISING] = {
|
||||
.genintr = BMA400_GEN1_INTR,
|
||||
.intrmask = BMA400_INT_CONFIG0_GEN1_MASK,
|
||||
.dir = IIO_EV_DIR_RISING,
|
||||
.detect_mode = BMA400_DETECT_ACTIVITY,
|
||||
},
|
||||
[IIO_EV_DIR_FALLING] = {
|
||||
.genintr = BMA400_GEN2_INTR,
|
||||
.intrmask = BMA400_INT_CONFIG0_GEN2_MASK,
|
||||
.dir = IIO_EV_DIR_FALLING,
|
||||
.detect_mode = BMA400_DETECT_INACTIVITY,
|
||||
}
|
||||
};
|
||||
|
||||
static inline const struct bma400_genintr_info *
|
||||
get_bma400_genintr_info(enum iio_event_direction dir)
|
||||
{
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_RISING:
|
||||
case IIO_EV_DIR_FALLING:
|
||||
return &bma400_genintrs[dir];
|
||||
default:
|
||||
return NULL;
|
||||
};
|
||||
}
|
||||
|
||||
static bool bma400_is_writable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case BMA400_CHIP_ID_REG:
|
||||
case BMA400_ERR_REG:
|
||||
case BMA400_STATUS_REG:
|
||||
case BMA400_X_AXIS_LSB_REG:
|
||||
case BMA400_X_AXIS_MSB_REG:
|
||||
case BMA400_Y_AXIS_LSB_REG:
|
||||
case BMA400_Y_AXIS_MSB_REG:
|
||||
case BMA400_Z_AXIS_LSB_REG:
|
||||
case BMA400_Z_AXIS_MSB_REG:
|
||||
case BMA400_SENSOR_TIME0:
|
||||
case BMA400_SENSOR_TIME1:
|
||||
case BMA400_SENSOR_TIME2:
|
||||
case BMA400_ACC_X_LSB_REG:
|
||||
case BMA400_ACC_X_MSB_REG:
|
||||
case BMA400_ACC_Y_LSB_REG:
|
||||
case BMA400_ACC_Y_MSB_REG:
|
||||
case BMA400_ACC_Z_LSB_REG:
|
||||
case BMA400_ACC_Z_MSB_REG:
|
||||
case BMA400_SENSOR_TIME0_REG:
|
||||
case BMA400_SENSOR_TIME1_REG:
|
||||
case BMA400_SENSOR_TIME2_REG:
|
||||
case BMA400_EVENT_REG:
|
||||
case BMA400_INT_STAT0_REG:
|
||||
case BMA400_INT_STAT1_REG:
|
||||
@@ -159,15 +194,15 @@ static bool bma400_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||
switch (reg) {
|
||||
case BMA400_ERR_REG:
|
||||
case BMA400_STATUS_REG:
|
||||
case BMA400_X_AXIS_LSB_REG:
|
||||
case BMA400_X_AXIS_MSB_REG:
|
||||
case BMA400_Y_AXIS_LSB_REG:
|
||||
case BMA400_Y_AXIS_MSB_REG:
|
||||
case BMA400_Z_AXIS_LSB_REG:
|
||||
case BMA400_Z_AXIS_MSB_REG:
|
||||
case BMA400_SENSOR_TIME0:
|
||||
case BMA400_SENSOR_TIME1:
|
||||
case BMA400_SENSOR_TIME2:
|
||||
case BMA400_ACC_X_LSB_REG:
|
||||
case BMA400_ACC_X_MSB_REG:
|
||||
case BMA400_ACC_Y_LSB_REG:
|
||||
case BMA400_ACC_Y_MSB_REG:
|
||||
case BMA400_ACC_Z_LSB_REG:
|
||||
case BMA400_ACC_Z_MSB_REG:
|
||||
case BMA400_SENSOR_TIME0_REG:
|
||||
case BMA400_SENSOR_TIME1_REG:
|
||||
case BMA400_SENSOR_TIME2_REG:
|
||||
case BMA400_EVENT_REG:
|
||||
case BMA400_INT_STAT0_REG:
|
||||
case BMA400_INT_STAT1_REG:
|
||||
@@ -275,11 +310,11 @@ static ssize_t in_accel_gesture_tap_maxtomin_time_show(struct device *dev,
|
||||
struct bma400_data *data = iio_priv(indio_dev);
|
||||
int ret, reg_val, raw, vals[2];
|
||||
|
||||
ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1, ®_val);
|
||||
ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG, ®_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
raw = FIELD_GET(BMA400_TAP_TICSTH_MSK, reg_val);
|
||||
raw = FIELD_GET(BMA400_TAP_CONFIG1_TICSTH_MASK, reg_val);
|
||||
vals[0] = 0;
|
||||
vals[1] = tap_max2min_time[raw];
|
||||
|
||||
@@ -302,9 +337,9 @@ static ssize_t in_accel_gesture_tap_maxtomin_time_store(struct device *dev,
|
||||
if (raw < 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, BMA400_TAP_CONFIG1,
|
||||
BMA400_TAP_TICSTH_MSK,
|
||||
FIELD_PREP(BMA400_TAP_TICSTH_MSK, raw));
|
||||
ret = regmap_update_bits(data->regmap, BMA400_TAP_CONFIG1_REG,
|
||||
BMA400_TAP_CONFIG1_TICSTH_MASK,
|
||||
FIELD_PREP(BMA400_TAP_CONFIG1_TICSTH_MASK, raw));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -449,13 +484,13 @@ static int bma400_get_accel_reg(struct bma400_data *data,
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
lsb_reg = BMA400_X_AXIS_LSB_REG;
|
||||
lsb_reg = BMA400_ACC_X_LSB_REG;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
lsb_reg = BMA400_Y_AXIS_LSB_REG;
|
||||
lsb_reg = BMA400_ACC_Y_LSB_REG;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
lsb_reg = BMA400_Z_AXIS_LSB_REG;
|
||||
lsb_reg = BMA400_ACC_Z_LSB_REG;
|
||||
break;
|
||||
default:
|
||||
dev_err(data->dev, "invalid axis channel modifier\n");
|
||||
@@ -475,8 +510,8 @@ static int bma400_get_accel_reg(struct bma400_data *data,
|
||||
static void bma400_output_data_rate_from_raw(int raw, unsigned int *val,
|
||||
unsigned int *val2)
|
||||
{
|
||||
*val = BMA400_ACC_ODR_MAX_HZ >> (BMA400_ACC_ODR_MAX_RAW - raw);
|
||||
if (raw > BMA400_ACC_ODR_MIN_RAW)
|
||||
*val = BMA400_ACC_CONFIG1_ODR_MAX_HZ >> (BMA400_ACC_CONFIG1_ODR_MAX_RAW - raw);
|
||||
if (raw > BMA400_ACC_CONFIG1_ODR_MIN_RAW)
|
||||
*val2 = 0;
|
||||
else
|
||||
*val2 = 500000;
|
||||
@@ -494,7 +529,7 @@ static int bma400_get_accel_output_data_rate(struct bma400_data *data)
|
||||
* Runs at a fixed rate in low-power mode. See section 4.3
|
||||
* in the datasheet.
|
||||
*/
|
||||
bma400_output_data_rate_from_raw(BMA400_ACC_ODR_LP_RAW,
|
||||
bma400_output_data_rate_from_raw(BMA400_ACC_CONFIG1_ODR_LP_RAW,
|
||||
&data->sample_freq.hz,
|
||||
&data->sample_freq.uhz);
|
||||
return 0;
|
||||
@@ -507,9 +542,9 @@ static int bma400_get_accel_output_data_rate(struct bma400_data *data)
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
odr = val & BMA400_ACC_ODR_MASK;
|
||||
if (odr < BMA400_ACC_ODR_MIN_RAW ||
|
||||
odr > BMA400_ACC_ODR_MAX_RAW) {
|
||||
odr = val & BMA400_ACC_CONFIG1_ODR_MASK;
|
||||
if (odr < BMA400_ACC_CONFIG1_ODR_MIN_RAW ||
|
||||
odr > BMA400_ACC_CONFIG1_ODR_MAX_RAW) {
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
@@ -539,19 +574,19 @@ static int bma400_set_accel_output_data_rate(struct bma400_data *data,
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
if (hz >= BMA400_ACC_ODR_MIN_WHOLE_HZ) {
|
||||
if (uhz || hz > BMA400_ACC_ODR_MAX_HZ)
|
||||
if (hz >= BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ) {
|
||||
if (uhz || hz > BMA400_ACC_CONFIG1_ODR_MAX_HZ)
|
||||
return -EINVAL;
|
||||
|
||||
/* Note this works because MIN_WHOLE_HZ is odd */
|
||||
idx = __ffs(hz);
|
||||
|
||||
if (hz >> idx != BMA400_ACC_ODR_MIN_WHOLE_HZ)
|
||||
if (hz >> idx != BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ)
|
||||
return -EINVAL;
|
||||
|
||||
idx += BMA400_ACC_ODR_MIN_RAW + 1;
|
||||
} else if (hz == BMA400_ACC_ODR_MIN_HZ && uhz == 500000) {
|
||||
idx = BMA400_ACC_ODR_MIN_RAW;
|
||||
idx += BMA400_ACC_CONFIG1_ODR_MIN_RAW + 1;
|
||||
} else if (hz == BMA400_ACC_CONFIG1_ODR_MIN_HZ && uhz == 500000) {
|
||||
idx = BMA400_ACC_CONFIG1_ODR_MIN_RAW;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -561,7 +596,7 @@ static int bma400_set_accel_output_data_rate(struct bma400_data *data,
|
||||
return ret;
|
||||
|
||||
/* preserve the range and normal mode osr */
|
||||
odr = (~BMA400_ACC_ODR_MASK & val) | idx;
|
||||
odr = (~BMA400_ACC_CONFIG1_ODR_MASK & val) | idx;
|
||||
|
||||
ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG, odr);
|
||||
if (ret)
|
||||
@@ -592,7 +627,7 @@ static int bma400_get_accel_oversampling_ratio(struct bma400_data *data)
|
||||
return ret;
|
||||
}
|
||||
|
||||
osr = (val & BMA400_LP_OSR_MASK) >> BMA400_LP_OSR_SHIFT;
|
||||
osr = FIELD_GET(BMA400_ACC_CONFIG0_LP_OSR_MASK, val);
|
||||
|
||||
data->oversampling_ratio = osr;
|
||||
return 0;
|
||||
@@ -603,7 +638,7 @@ static int bma400_get_accel_oversampling_ratio(struct bma400_data *data)
|
||||
return ret;
|
||||
}
|
||||
|
||||
osr = (val & BMA400_NP_OSR_MASK) >> BMA400_NP_OSR_SHIFT;
|
||||
osr = FIELD_GET(BMA400_ACC_CONFIG1_NP_OSR_MASK, val);
|
||||
|
||||
data->oversampling_ratio = osr;
|
||||
return 0;
|
||||
@@ -637,8 +672,8 @@ static int bma400_set_accel_oversampling_ratio(struct bma400_data *data,
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(data->regmap, BMA400_ACC_CONFIG0_REG,
|
||||
(acc_config & ~BMA400_LP_OSR_MASK) |
|
||||
(val << BMA400_LP_OSR_SHIFT));
|
||||
(acc_config & ~BMA400_ACC_CONFIG0_LP_OSR_MASK) |
|
||||
FIELD_PREP(BMA400_ACC_CONFIG0_LP_OSR_MASK, val));
|
||||
if (ret) {
|
||||
dev_err(data->dev, "Failed to write out OSR\n");
|
||||
return ret;
|
||||
@@ -653,8 +688,8 @@ static int bma400_set_accel_oversampling_ratio(struct bma400_data *data,
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG,
|
||||
(acc_config & ~BMA400_NP_OSR_MASK) |
|
||||
(val << BMA400_NP_OSR_SHIFT));
|
||||
(acc_config & ~BMA400_ACC_CONFIG1_NP_OSR_MASK) |
|
||||
FIELD_PREP(BMA400_ACC_CONFIG1_NP_OSR_MASK, val));
|
||||
if (ret) {
|
||||
dev_err(data->dev, "Failed to write out OSR\n");
|
||||
return ret;
|
||||
@@ -679,7 +714,7 @@ static int bma400_accel_scale_to_raw(struct bma400_data *data,
|
||||
/* Note this works because BMA400_SCALE_MIN is odd */
|
||||
raw = __ffs(val);
|
||||
|
||||
if (val >> raw != BMA400_SCALE_MIN)
|
||||
if (val >> raw != BMA400_ACC_SCALE_MIN)
|
||||
return -EINVAL;
|
||||
|
||||
return raw;
|
||||
@@ -695,11 +730,11 @@ static int bma400_get_accel_scale(struct bma400_data *data)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
raw_scale = (val & BMA400_ACC_SCALE_MASK) >> BMA400_SCALE_SHIFT;
|
||||
raw_scale = FIELD_GET(BMA400_ACC_CONFIG1_ACC_RANGE_MASK, val);
|
||||
if (raw_scale > BMA400_TWO_BITS_MASK)
|
||||
return -EINVAL;
|
||||
|
||||
data->scale = BMA400_SCALE_MIN << raw_scale;
|
||||
data->scale = BMA400_ACC_SCALE_MIN << raw_scale;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -719,8 +754,8 @@ static int bma400_set_accel_scale(struct bma400_data *data, unsigned int val)
|
||||
return raw;
|
||||
|
||||
ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG,
|
||||
(acc_config & ~BMA400_ACC_SCALE_MASK) |
|
||||
(raw << BMA400_SCALE_SHIFT));
|
||||
(acc_config & ~BMA400_ACC_CONFIG1_ACC_RANGE_MASK) |
|
||||
FIELD_PREP(BMA400_ACC_CONFIG1_ACC_RANGE_MASK, raw));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -786,8 +821,8 @@ static int bma400_enable_steps(struct bma400_data *data, int val)
|
||||
return 0;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG1_REG,
|
||||
BMA400_STEP_INT_MSK,
|
||||
FIELD_PREP(BMA400_STEP_INT_MSK, val ? 1 : 0));
|
||||
BMA400_INT_CONFIG1_STEP_INT_MASK,
|
||||
FIELD_PREP(BMA400_INT_CONFIG1_STEP_INT_MASK, val ? 1 : 0));
|
||||
if (ret)
|
||||
return ret;
|
||||
data->steps_enabled = val;
|
||||
@@ -826,7 +861,7 @@ static void bma400_init_tables(void)
|
||||
for (i = 0; i + 1 < ARRAY_SIZE(bma400_scales); i += 2) {
|
||||
raw = i / 2;
|
||||
bma400_scales[i] = 0;
|
||||
bma400_scales[i + 1] = BMA400_SCALE_MIN << raw;
|
||||
bma400_scales[i + 1] = BMA400_ACC_SCALE_MIN << raw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1063,7 +1098,7 @@ static int bma400_write_raw(struct iio_dev *indio_dev,
|
||||
return ret;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
if (val != 0 ||
|
||||
val2 < BMA400_SCALE_MIN || val2 > BMA400_SCALE_MAX)
|
||||
val2 < BMA400_ACC_SCALE_MIN || val2 > BMA400_ACC_SCALE_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
@@ -1114,16 +1149,16 @@ static int bma400_read_event_config(struct iio_dev *indio_dev,
|
||||
case IIO_ACCEL:
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_RISING:
|
||||
return FIELD_GET(BMA400_INT_GEN1_MSK,
|
||||
return FIELD_GET(BMA400_INT_CONFIG0_GEN1_MASK,
|
||||
data->generic_event_en);
|
||||
case IIO_EV_DIR_FALLING:
|
||||
return FIELD_GET(BMA400_INT_GEN2_MSK,
|
||||
return FIELD_GET(BMA400_INT_CONFIG0_GEN2_MASK,
|
||||
data->generic_event_en);
|
||||
case IIO_EV_DIR_SINGLETAP:
|
||||
return FIELD_GET(BMA400_S_TAP_MSK,
|
||||
return FIELD_GET(BMA400_INT_CONFIG1_S_TAP_MASK,
|
||||
data->tap_event_en_bitmask);
|
||||
case IIO_EV_DIR_DOUBLETAP:
|
||||
return FIELD_GET(BMA400_D_TAP_MSK,
|
||||
return FIELD_GET(BMA400_INT_CONFIG1_D_TAP_MASK,
|
||||
data->tap_event_en_bitmask);
|
||||
default:
|
||||
return -EINVAL;
|
||||
@@ -1146,8 +1181,8 @@ static int bma400_steps_event_enable(struct bma400_data *data, int state)
|
||||
return ret;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, BMA400_INT12_MAP_REG,
|
||||
BMA400_STEP_INT_MSK,
|
||||
FIELD_PREP(BMA400_STEP_INT_MSK,
|
||||
BMA400_INT_CONFIG1_STEP_INT_MASK,
|
||||
FIELD_PREP(BMA400_INT_CONFIG1_STEP_INT_MASK,
|
||||
state));
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -1155,63 +1190,68 @@ static int bma400_steps_event_enable(struct bma400_data *data, int state)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bma400_activity_event_en(struct bma400_data *data,
|
||||
enum iio_event_direction dir,
|
||||
int state)
|
||||
static int bma400_generic_event_en(struct bma400_data *data,
|
||||
enum iio_event_direction dir,
|
||||
int state)
|
||||
{
|
||||
int ret, reg, msk, value;
|
||||
int field_value = 0;
|
||||
int ret;
|
||||
unsigned int intrmask, regval;
|
||||
enum bma400_generic_intr genintr;
|
||||
enum bma400_detect_criterion detect_criterion;
|
||||
const struct bma400_genintr_info *bma400_genintr;
|
||||
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_RISING:
|
||||
reg = BMA400_GEN1INT_CONFIG0;
|
||||
msk = BMA400_INT_GEN1_MSK;
|
||||
value = 2;
|
||||
set_mask_bits(&field_value, BMA400_INT_GEN1_MSK,
|
||||
FIELD_PREP(BMA400_INT_GEN1_MSK, state));
|
||||
break;
|
||||
case IIO_EV_DIR_FALLING:
|
||||
reg = BMA400_GEN2INT_CONFIG0;
|
||||
msk = BMA400_INT_GEN2_MSK;
|
||||
value = 0;
|
||||
set_mask_bits(&field_value, BMA400_INT_GEN2_MSK,
|
||||
FIELD_PREP(BMA400_INT_GEN2_MSK, state));
|
||||
break;
|
||||
default:
|
||||
bma400_genintr = get_bma400_genintr_info(dir);
|
||||
if (!bma400_genintr)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Enabling all axis for interrupt evaluation */
|
||||
ret = regmap_write(data->regmap, reg, 0xF8);
|
||||
genintr = bma400_genintr->genintr;
|
||||
detect_criterion = bma400_genintr->detect_mode;
|
||||
intrmask = bma400_genintr->intrmask;
|
||||
|
||||
/*
|
||||
* Enabling all axis for interrupt evaluation
|
||||
* Acc_filt2 is recommended as data source in datasheet (Section 4.7)
|
||||
*/
|
||||
ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 0),
|
||||
BMA400_GENINT_CONFIG0_X_EN_MASK |
|
||||
BMA400_GENINT_CONFIG0_Y_EN_MASK |
|
||||
BMA400_GENINT_CONFIG0_Z_EN_MASK|
|
||||
FIELD_PREP(BMA400_GENINT_CONFIG0_DATA_SRC_MASK, ACCEL_FILT2)|
|
||||
FIELD_PREP(BMA400_GENINT_CONFIG0_REF_UPD_MODE_MASK,
|
||||
BMA400_REF_EVERYTIME_UPDT_MODE));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* OR combination of all axis for interrupt evaluation */
|
||||
ret = regmap_write(data->regmap, reg + BMA400_GEN_CONFIG1_OFF, value);
|
||||
regval = FIELD_PREP(BMA400_GENINT_CONFIG1_AXES_COMB_MASK, BMA400_EVAL_X_OR_Y_OR_Z) |
|
||||
FIELD_PREP(BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK, detect_criterion);
|
||||
ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 1), regval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Initial value to avoid interrupts while enabling*/
|
||||
ret = regmap_write(data->regmap, reg + BMA400_GEN_CONFIG2_OFF, 0x0A);
|
||||
/*
|
||||
* Initial value to avoid interrupts while enabling
|
||||
* Value is in units of 8mg/lsb, i.e. effective val is val * 8mg/lsb
|
||||
*/
|
||||
ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 2), 0x0A);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Initial duration value to avoid interrupts while enabling*/
|
||||
ret = regmap_write(data->regmap, reg + BMA400_GEN_CONFIG31_OFF, 0x0F);
|
||||
ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 4), 0x0F);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, BMA400_INT1_MAP_REG, msk,
|
||||
field_value);
|
||||
regval = state ? intrmask : 0;
|
||||
ret = regmap_update_bits(data->regmap, BMA400_INT1_MAP_REG, intrmask, regval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG0_REG, msk,
|
||||
field_value);
|
||||
ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG0_REG, intrmask, regval);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
set_mask_bits(&data->generic_event_en, msk, field_value);
|
||||
set_mask_bits(&data->generic_event_en, intrmask, regval);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1240,21 +1280,21 @@ static int bma400_tap_event_en(struct bma400_data *data,
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(data->regmap, BMA400_INT12_MAP_REG,
|
||||
BMA400_S_TAP_MSK,
|
||||
FIELD_PREP(BMA400_S_TAP_MSK, state));
|
||||
BMA400_INT_CONFIG1_S_TAP_MASK,
|
||||
FIELD_PREP(BMA400_INT_CONFIG1_S_TAP_MASK, state));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_SINGLETAP:
|
||||
mask = BMA400_S_TAP_MSK;
|
||||
set_mask_bits(&field_value, BMA400_S_TAP_MSK,
|
||||
FIELD_PREP(BMA400_S_TAP_MSK, state));
|
||||
mask = BMA400_INT_CONFIG1_S_TAP_MASK;
|
||||
set_mask_bits(&field_value, BMA400_INT_CONFIG1_S_TAP_MASK,
|
||||
FIELD_PREP(BMA400_INT_CONFIG1_S_TAP_MASK, state));
|
||||
break;
|
||||
case IIO_EV_DIR_DOUBLETAP:
|
||||
mask = BMA400_D_TAP_MSK;
|
||||
set_mask_bits(&field_value, BMA400_D_TAP_MSK,
|
||||
FIELD_PREP(BMA400_D_TAP_MSK, state));
|
||||
mask = BMA400_INT_CONFIG1_D_TAP_MASK;
|
||||
set_mask_bits(&field_value, BMA400_INT_CONFIG1_D_TAP_MASK,
|
||||
FIELD_PREP(BMA400_INT_CONFIG1_D_TAP_MASK, state));
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
@@ -1303,7 +1343,7 @@ static int bma400_write_event_config(struct iio_dev *indio_dev,
|
||||
switch (type) {
|
||||
case IIO_EV_TYPE_MAG:
|
||||
mutex_lock(&data->mutex);
|
||||
ret = bma400_activity_event_en(data, dir, state);
|
||||
ret = bma400_generic_event_en(data, dir, state);
|
||||
mutex_unlock(&data->mutex);
|
||||
return ret;
|
||||
case IIO_EV_TYPE_GESTURE:
|
||||
@@ -1336,18 +1376,6 @@ static int bma400_write_event_config(struct iio_dev *indio_dev,
|
||||
}
|
||||
}
|
||||
|
||||
static int get_gen_config_reg(enum iio_event_direction dir)
|
||||
{
|
||||
switch (dir) {
|
||||
case IIO_EV_DIR_FALLING:
|
||||
return BMA400_GEN2INT_CONFIG0;
|
||||
case IIO_EV_DIR_RISING:
|
||||
return BMA400_GEN1INT_CONFIG0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int bma400_read_event_value(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
@@ -1356,22 +1384,25 @@ static int bma400_read_event_value(struct iio_dev *indio_dev,
|
||||
int *val, int *val2)
|
||||
{
|
||||
struct bma400_data *data = iio_priv(indio_dev);
|
||||
int ret, reg, reg_val, raw;
|
||||
int ret, reg_val, raw;
|
||||
enum bma400_generic_intr genintr;
|
||||
const struct bma400_genintr_info *bma400_genintr;
|
||||
|
||||
if (chan->type != IIO_ACCEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (type) {
|
||||
case IIO_EV_TYPE_MAG:
|
||||
reg = get_gen_config_reg(dir);
|
||||
if (reg < 0)
|
||||
bma400_genintr = get_bma400_genintr_info(dir);
|
||||
if (!bma400_genintr)
|
||||
return -EINVAL;
|
||||
genintr = bma400_genintr->genintr;
|
||||
|
||||
*val2 = 0;
|
||||
switch (info) {
|
||||
case IIO_EV_INFO_VALUE:
|
||||
ret = regmap_read(data->regmap,
|
||||
reg + BMA400_GEN_CONFIG2_OFF,
|
||||
BMA400_GENINT_CONFIG_REG(genintr, 2),
|
||||
val);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -1379,7 +1410,7 @@ static int bma400_read_event_value(struct iio_dev *indio_dev,
|
||||
case IIO_EV_INFO_PERIOD:
|
||||
mutex_lock(&data->mutex);
|
||||
ret = regmap_bulk_read(data->regmap,
|
||||
reg + BMA400_GEN_CONFIG3_OFF,
|
||||
BMA400_GENINT_CONFIG_REG(genintr, 3),
|
||||
&data->duration,
|
||||
sizeof(data->duration));
|
||||
if (ret) {
|
||||
@@ -1390,10 +1421,12 @@ static int bma400_read_event_value(struct iio_dev *indio_dev,
|
||||
mutex_unlock(&data->mutex);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_EV_INFO_HYSTERESIS:
|
||||
ret = regmap_read(data->regmap, reg, val);
|
||||
ret = regmap_read(data->regmap,
|
||||
BMA400_GENINT_CONFIG_REG(genintr, 0),
|
||||
val);
|
||||
if (ret)
|
||||
return ret;
|
||||
*val = FIELD_GET(BMA400_GEN_HYST_MSK, *val);
|
||||
*val = FIELD_GET(BMA400_GENINT_CONFIG0_HYST_MASK, *val);
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
@@ -1401,30 +1434,30 @@ static int bma400_read_event_value(struct iio_dev *indio_dev,
|
||||
case IIO_EV_TYPE_GESTURE:
|
||||
switch (info) {
|
||||
case IIO_EV_INFO_VALUE:
|
||||
ret = regmap_read(data->regmap, BMA400_TAP_CONFIG,
|
||||
ret = regmap_read(data->regmap, BMA400_TAP_CONFIG_REG,
|
||||
®_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = FIELD_GET(BMA400_TAP_SEN_MSK, reg_val);
|
||||
*val = FIELD_GET(BMA400_TAP_CONFIG_SEN_MASK, reg_val);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_EV_INFO_RESET_TIMEOUT:
|
||||
ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1,
|
||||
ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG,
|
||||
®_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
raw = FIELD_GET(BMA400_TAP_QUIET_MSK, reg_val);
|
||||
raw = FIELD_GET(BMA400_TAP_CONFIG1_QUIET_MASK, reg_val);
|
||||
*val = 0;
|
||||
*val2 = tap_reset_timeout[raw];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_EV_INFO_TAP2_MIN_DELAY:
|
||||
ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1,
|
||||
ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG,
|
||||
®_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
raw = FIELD_GET(BMA400_TAP_QUIETDT_MSK, reg_val);
|
||||
raw = FIELD_GET(BMA400_TAP_CONFIG1_QUIETDT_MASK, reg_val);
|
||||
*val = 0;
|
||||
*val2 = double_tap2_min_delay[raw];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
@@ -1444,16 +1477,19 @@ static int bma400_write_event_value(struct iio_dev *indio_dev,
|
||||
int val, int val2)
|
||||
{
|
||||
struct bma400_data *data = iio_priv(indio_dev);
|
||||
int reg, ret, raw;
|
||||
int ret, raw;
|
||||
enum bma400_generic_intr genintr;
|
||||
const struct bma400_genintr_info *bma400_genintr;
|
||||
|
||||
if (chan->type != IIO_ACCEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (type) {
|
||||
case IIO_EV_TYPE_MAG:
|
||||
reg = get_gen_config_reg(dir);
|
||||
if (reg < 0)
|
||||
bma400_genintr = get_bma400_genintr_info(dir);
|
||||
if (!bma400_genintr)
|
||||
return -EINVAL;
|
||||
genintr = bma400_genintr->genintr;
|
||||
|
||||
switch (info) {
|
||||
case IIO_EV_INFO_VALUE:
|
||||
@@ -1461,7 +1497,7 @@ static int bma400_write_event_value(struct iio_dev *indio_dev,
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_write(data->regmap,
|
||||
reg + BMA400_GEN_CONFIG2_OFF,
|
||||
BMA400_GENINT_CONFIG_REG(genintr, 2),
|
||||
val);
|
||||
case IIO_EV_INFO_PERIOD:
|
||||
if (val < 1 || val > 65535)
|
||||
@@ -1470,7 +1506,7 @@ static int bma400_write_event_value(struct iio_dev *indio_dev,
|
||||
mutex_lock(&data->mutex);
|
||||
put_unaligned_be16(val, &data->duration);
|
||||
ret = regmap_bulk_write(data->regmap,
|
||||
reg + BMA400_GEN_CONFIG3_OFF,
|
||||
BMA400_GENINT_CONFIG_REG(genintr, 3),
|
||||
&data->duration,
|
||||
sizeof(data->duration));
|
||||
mutex_unlock(&data->mutex);
|
||||
@@ -1479,9 +1515,10 @@ static int bma400_write_event_value(struct iio_dev *indio_dev,
|
||||
if (val < 0 || val > 3)
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_update_bits(data->regmap, reg,
|
||||
BMA400_GEN_HYST_MSK,
|
||||
FIELD_PREP(BMA400_GEN_HYST_MSK,
|
||||
return regmap_update_bits(data->regmap,
|
||||
BMA400_GENINT_CONFIG_REG(genintr, 0),
|
||||
BMA400_GENINT_CONFIG0_HYST_MASK,
|
||||
FIELD_PREP(BMA400_GENINT_CONFIG0_HYST_MASK,
|
||||
val));
|
||||
default:
|
||||
return -EINVAL;
|
||||
@@ -1493,9 +1530,9 @@ static int bma400_write_event_value(struct iio_dev *indio_dev,
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_update_bits(data->regmap,
|
||||
BMA400_TAP_CONFIG,
|
||||
BMA400_TAP_SEN_MSK,
|
||||
FIELD_PREP(BMA400_TAP_SEN_MSK,
|
||||
BMA400_TAP_CONFIG_REG,
|
||||
BMA400_TAP_CONFIG_SEN_MASK,
|
||||
FIELD_PREP(BMA400_TAP_CONFIG_SEN_MASK,
|
||||
val));
|
||||
case IIO_EV_INFO_RESET_TIMEOUT:
|
||||
raw = usec_to_tapreg_raw(val2, tap_reset_timeout);
|
||||
@@ -1503,9 +1540,9 @@ static int bma400_write_event_value(struct iio_dev *indio_dev,
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_update_bits(data->regmap,
|
||||
BMA400_TAP_CONFIG1,
|
||||
BMA400_TAP_QUIET_MSK,
|
||||
FIELD_PREP(BMA400_TAP_QUIET_MSK,
|
||||
BMA400_TAP_CONFIG1_REG,
|
||||
BMA400_TAP_CONFIG1_QUIET_MASK,
|
||||
FIELD_PREP(BMA400_TAP_CONFIG1_QUIET_MASK,
|
||||
raw));
|
||||
case IIO_EV_INFO_TAP2_MIN_DELAY:
|
||||
raw = usec_to_tapreg_raw(val2, double_tap2_min_delay);
|
||||
@@ -1513,9 +1550,9 @@ static int bma400_write_event_value(struct iio_dev *indio_dev,
|
||||
return -EINVAL;
|
||||
|
||||
return regmap_update_bits(data->regmap,
|
||||
BMA400_TAP_CONFIG1,
|
||||
BMA400_TAP_QUIETDT_MSK,
|
||||
FIELD_PREP(BMA400_TAP_QUIETDT_MSK,
|
||||
BMA400_TAP_CONFIG1_REG,
|
||||
BMA400_TAP_CONFIG1_QUIETDT_MASK,
|
||||
FIELD_PREP(BMA400_TAP_CONFIG1_QUIETDT_MASK,
|
||||
raw));
|
||||
default:
|
||||
return -EINVAL;
|
||||
@@ -1533,14 +1570,14 @@ static int bma400_data_rdy_trigger_set_state(struct iio_trigger *trig,
|
||||
int ret;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG0_REG,
|
||||
BMA400_INT_DRDY_MSK,
|
||||
FIELD_PREP(BMA400_INT_DRDY_MSK, state));
|
||||
BMA400_INT_CONFIG0_DRDY_MASK,
|
||||
FIELD_PREP(BMA400_INT_CONFIG0_DRDY_MASK, state));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return regmap_update_bits(data->regmap, BMA400_INT1_MAP_REG,
|
||||
BMA400_INT_DRDY_MSK,
|
||||
FIELD_PREP(BMA400_INT_DRDY_MSK, state));
|
||||
BMA400_INT_CONFIG0_DRDY_MASK,
|
||||
FIELD_PREP(BMA400_INT_CONFIG0_DRDY_MASK, state));
|
||||
}
|
||||
|
||||
static const unsigned long bma400_avail_scan_masks[] = {
|
||||
@@ -1578,7 +1615,7 @@ static irqreturn_t bma400_trigger_handler(int irq, void *p)
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
/* bulk read six registers, with the base being the LSB register */
|
||||
ret = regmap_bulk_read(data->regmap, BMA400_X_AXIS_LSB_REG,
|
||||
ret = regmap_bulk_read(data->regmap, BMA400_ACC_X_LSB_REG,
|
||||
&data->buffer.buff, sizeof(data->buffer.buff));
|
||||
if (ret)
|
||||
goto unlock_err;
|
||||
@@ -1628,13 +1665,13 @@ static irqreturn_t bma400_interrupt(int irq, void *private)
|
||||
* Disable all advance interrupts if interrupt engine overrun occurs.
|
||||
* See section 4.7 "Interrupt engine overrun" in datasheet v1.2.
|
||||
*/
|
||||
if (FIELD_GET(BMA400_INT_ENG_OVRUN_MSK, le16_to_cpu(data->status))) {
|
||||
if (FIELD_GET(BMA400_INT_STAT_ENG_OVRRUN_MASK, le16_to_cpu(data->status))) {
|
||||
bma400_disable_adv_interrupt(data);
|
||||
dev_err(data->dev, "Interrupt engine overrun\n");
|
||||
goto unlock_err;
|
||||
}
|
||||
|
||||
if (FIELD_GET(BMA400_INT_S_TAP_MSK, le16_to_cpu(data->status)))
|
||||
if (FIELD_GET(BMA400_INT_STAT1_S_TAP_MASK, le16_to_cpu(data->status)))
|
||||
iio_push_event(indio_dev,
|
||||
IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
|
||||
IIO_MOD_X_OR_Y_OR_Z,
|
||||
@@ -1642,7 +1679,7 @@ static irqreturn_t bma400_interrupt(int irq, void *private)
|
||||
IIO_EV_DIR_SINGLETAP),
|
||||
timestamp);
|
||||
|
||||
if (FIELD_GET(BMA400_INT_D_TAP_MSK, le16_to_cpu(data->status)))
|
||||
if (FIELD_GET(BMA400_INT_STAT1_D_TAP_MASK, le16_to_cpu(data->status)))
|
||||
iio_push_event(indio_dev,
|
||||
IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
|
||||
IIO_MOD_X_OR_Y_OR_Z,
|
||||
@@ -1650,10 +1687,10 @@ static irqreturn_t bma400_interrupt(int irq, void *private)
|
||||
IIO_EV_DIR_DOUBLETAP),
|
||||
timestamp);
|
||||
|
||||
if (FIELD_GET(BMA400_INT_GEN1_MSK, le16_to_cpu(data->status)))
|
||||
if (FIELD_GET(BMA400_INT_STAT0_GEN1_MASK, le16_to_cpu(data->status)))
|
||||
ev_dir = IIO_EV_DIR_RISING;
|
||||
|
||||
if (FIELD_GET(BMA400_INT_GEN2_MSK, le16_to_cpu(data->status)))
|
||||
if (FIELD_GET(BMA400_INT_STAT0_GEN2_MASK, le16_to_cpu(data->status)))
|
||||
ev_dir = IIO_EV_DIR_FALLING;
|
||||
|
||||
if (ev_dir != IIO_EV_DIR_NONE) {
|
||||
@@ -1664,7 +1701,7 @@ static irqreturn_t bma400_interrupt(int irq, void *private)
|
||||
timestamp);
|
||||
}
|
||||
|
||||
if (FIELD_GET(BMA400_STEP_STAT_MASK, le16_to_cpu(data->status))) {
|
||||
if (FIELD_GET(BMA400_INT_STAT1_STEP_INT_MASK, le16_to_cpu(data->status))) {
|
||||
iio_push_event(indio_dev,
|
||||
IIO_MOD_EVENT_CODE(IIO_STEPS, 0, IIO_NO_MOD,
|
||||
IIO_EV_TYPE_CHANGE,
|
||||
@@ -1686,7 +1723,7 @@ static irqreturn_t bma400_interrupt(int irq, void *private)
|
||||
}
|
||||
}
|
||||
|
||||
if (FIELD_GET(BMA400_INT_DRDY_MSK, le16_to_cpu(data->status))) {
|
||||
if (FIELD_GET(BMA400_INT_STAT0_DRDY_MASK, le16_to_cpu(data->status))) {
|
||||
mutex_unlock(&data->mutex);
|
||||
iio_trigger_poll_nested(data->trig);
|
||||
return IRQ_HANDLED;
|
||||
|
||||
@@ -1020,6 +1020,16 @@ config MAX1363
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called max1363.
|
||||
|
||||
config MAX14001
|
||||
tristate "Analog Devices MAX14001/MAX14002 ADC driver"
|
||||
depends on SPI
|
||||
help
|
||||
Say yes here to build support for Analog Devices MAX14001/MAX14002
|
||||
Configurable, Isolated 10-bit ADCs for Multi-Range Binary Inputs.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called max14001.
|
||||
|
||||
config MAX34408
|
||||
tristate "Maxim max34408/max344089 ADC driver"
|
||||
depends on I2C
|
||||
@@ -1403,6 +1413,27 @@ config RZG2L_ADC
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called rzg2l_adc.
|
||||
|
||||
config RZN1_ADC
|
||||
tristate "Renesas RZ/N1 ADC driver"
|
||||
depends on ARCH_RZN1 || COMPILE_TEST
|
||||
help
|
||||
Say yes here to build support for the ADC found in Renesas
|
||||
RZ/N1 family.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called rzn1-adc.
|
||||
|
||||
config RZT2H_ADC
|
||||
tristate "Renesas RZ/T2H / RZ/N2H ADC driver"
|
||||
depends on ARCH_RENESAS || COMPILE_TEST
|
||||
select IIO_ADC_HELPER
|
||||
help
|
||||
Say yes here to build support for the ADC found in Renesas
|
||||
RZ/T2H / RZ/N2H SoCs.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called rzt2h_adc.
|
||||
|
||||
config SC27XX_ADC
|
||||
tristate "Spreadtrum SC27xx series PMICs ADC"
|
||||
depends on MFD_SC27XX_PMIC || COMPILE_TEST
|
||||
|
||||
@@ -89,6 +89,7 @@ obj-$(CONFIG_MAX11205) += max11205.o
|
||||
obj-$(CONFIG_MAX11410) += max11410.o
|
||||
obj-$(CONFIG_MAX1241) += max1241.o
|
||||
obj-$(CONFIG_MAX1363) += max1363.o
|
||||
obj-$(CONFIG_MAX14001) += max14001.o
|
||||
obj-$(CONFIG_MAX34408) += max34408.o
|
||||
obj-$(CONFIG_MAX77541_ADC) += max77541-adc.o
|
||||
obj-$(CONFIG_MAX9611) += max9611.o
|
||||
@@ -123,6 +124,8 @@ obj-$(CONFIG_ROHM_BD79112) += rohm-bd79112.o
|
||||
obj-$(CONFIG_ROHM_BD79124) += rohm-bd79124.o
|
||||
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
|
||||
obj-$(CONFIG_RZG2L_ADC) += rzg2l_adc.o
|
||||
obj-$(CONFIG_RZN1_ADC) += rzn1-adc.o
|
||||
obj-$(CONFIG_RZT2H_ADC) += rzt2h_adc.o
|
||||
obj-$(CONFIG_SC27XX_ADC) += sc27xx_adc.o
|
||||
obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o
|
||||
obj-$(CONFIG_SOPHGO_CV1800B_ADC) += sophgo-cv1800b-adc.o
|
||||
|
||||
@@ -852,8 +852,8 @@ static int ad4030_read_label(struct iio_dev *indio_dev,
|
||||
char *label)
|
||||
{
|
||||
if (chan->differential)
|
||||
return sprintf(label, "differential%lu\n", chan->address);
|
||||
return sprintf(label, "common-mode%lu\n", chan->address);
|
||||
return sysfs_emit(label, "differential%lu\n", chan->address);
|
||||
return sysfs_emit(label, "common-mode%lu\n", chan->address);
|
||||
}
|
||||
|
||||
static int ad4030_get_current_scan_type(const struct iio_dev *indio_dev,
|
||||
|
||||
@@ -125,7 +125,12 @@
|
||||
|
||||
/* Miscellaneous Definitions */
|
||||
#define AD4080_SPI_READ BIT(7)
|
||||
#define AD4080_CHIP_ID GENMASK(2, 0)
|
||||
#define AD4080_CHIP_ID 0x0050
|
||||
#define AD4081_CHIP_ID 0x0051
|
||||
#define AD4083_CHIP_ID 0x0053
|
||||
#define AD4084_CHIP_ID 0x0054
|
||||
#define AD4086_CHIP_ID 0x0056
|
||||
#define AD4087_CHIP_ID 0x0057
|
||||
|
||||
#define AD4080_LVDS_CNV_CLK_CNT_MAX 7
|
||||
|
||||
@@ -167,6 +172,7 @@ struct ad4080_chip_info {
|
||||
const unsigned int (*scale_table)[2];
|
||||
const struct iio_chan_spec *channels;
|
||||
unsigned int num_channels;
|
||||
unsigned int lvds_cnv_clk_cnt_max;
|
||||
};
|
||||
|
||||
struct ad4080_state {
|
||||
@@ -414,23 +420,35 @@ static struct iio_chan_spec_ext_info ad4080_ext_info[] = {
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec ad4080_channel = {
|
||||
.type = IIO_VOLTAGE,
|
||||
.indexed = 1,
|
||||
.channel = 0,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE),
|
||||
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
|
||||
.info_mask_shared_by_all_available =
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
|
||||
.ext_info = ad4080_ext_info,
|
||||
.scan_index = 0,
|
||||
.scan_type = {
|
||||
.sign = 's',
|
||||
.realbits = 20,
|
||||
.storagebits = 32,
|
||||
},
|
||||
};
|
||||
#define AD4080_CHANNEL_DEFINE(bits, storage) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.channel = 0, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
|
||||
.info_mask_shared_by_all_available = \
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
|
||||
.ext_info = ad4080_ext_info, \
|
||||
.scan_index = 0, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = (bits), \
|
||||
.storagebits = (storage), \
|
||||
}, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32);
|
||||
|
||||
static const struct iio_chan_spec ad4081_channel = AD4080_CHANNEL_DEFINE(20, 32);
|
||||
|
||||
static const struct iio_chan_spec ad4083_channel = AD4080_CHANNEL_DEFINE(16, 16);
|
||||
|
||||
static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16);
|
||||
|
||||
static const struct iio_chan_spec ad4086_channel = AD4080_CHANNEL_DEFINE(14, 16);
|
||||
|
||||
static const struct iio_chan_spec ad4087_channel = AD4080_CHANNEL_DEFINE(14, 16);
|
||||
|
||||
static const struct ad4080_chip_info ad4080_chip_info = {
|
||||
.name = "ad4080",
|
||||
@@ -439,13 +457,65 @@ static const struct ad4080_chip_info ad4080_chip_info = {
|
||||
.num_scales = ARRAY_SIZE(ad4080_scale_table),
|
||||
.num_channels = 1,
|
||||
.channels = &ad4080_channel,
|
||||
.lvds_cnv_clk_cnt_max = AD4080_LVDS_CNV_CLK_CNT_MAX,
|
||||
};
|
||||
|
||||
static const struct ad4080_chip_info ad4081_chip_info = {
|
||||
.name = "ad4081",
|
||||
.product_id = AD4081_CHIP_ID,
|
||||
.scale_table = ad4080_scale_table,
|
||||
.num_scales = ARRAY_SIZE(ad4080_scale_table),
|
||||
.num_channels = 1,
|
||||
.channels = &ad4081_channel,
|
||||
.lvds_cnv_clk_cnt_max = 2,
|
||||
};
|
||||
|
||||
static const struct ad4080_chip_info ad4083_chip_info = {
|
||||
.name = "ad4083",
|
||||
.product_id = AD4083_CHIP_ID,
|
||||
.scale_table = ad4080_scale_table,
|
||||
.num_scales = ARRAY_SIZE(ad4080_scale_table),
|
||||
.num_channels = 1,
|
||||
.channels = &ad4083_channel,
|
||||
.lvds_cnv_clk_cnt_max = 5,
|
||||
};
|
||||
|
||||
static const struct ad4080_chip_info ad4084_chip_info = {
|
||||
.name = "ad4084",
|
||||
.product_id = AD4084_CHIP_ID,
|
||||
.scale_table = ad4080_scale_table,
|
||||
.num_scales = ARRAY_SIZE(ad4080_scale_table),
|
||||
.num_channels = 1,
|
||||
.channels = &ad4084_channel,
|
||||
.lvds_cnv_clk_cnt_max = 2,
|
||||
};
|
||||
|
||||
static const struct ad4080_chip_info ad4086_chip_info = {
|
||||
.name = "ad4086",
|
||||
.product_id = AD4086_CHIP_ID,
|
||||
.scale_table = ad4080_scale_table,
|
||||
.num_scales = ARRAY_SIZE(ad4080_scale_table),
|
||||
.num_channels = 1,
|
||||
.channels = &ad4086_channel,
|
||||
.lvds_cnv_clk_cnt_max = 4,
|
||||
};
|
||||
|
||||
static const struct ad4080_chip_info ad4087_chip_info = {
|
||||
.name = "ad4087",
|
||||
.product_id = AD4087_CHIP_ID,
|
||||
.scale_table = ad4080_scale_table,
|
||||
.num_scales = ARRAY_SIZE(ad4080_scale_table),
|
||||
.num_channels = 1,
|
||||
.channels = &ad4087_channel,
|
||||
.lvds_cnv_clk_cnt_max = 1,
|
||||
};
|
||||
|
||||
static int ad4080_setup(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct ad4080_state *st = iio_priv(indio_dev);
|
||||
struct device *dev = regmap_get_device(st->regmap);
|
||||
unsigned int id;
|
||||
__le16 id_le;
|
||||
u16 id;
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(st->regmap, AD4080_REG_INTERFACE_CONFIG_A,
|
||||
@@ -458,11 +528,13 @@ static int ad4080_setup(struct iio_dev *indio_dev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(st->regmap, AD4080_REG_CHIP_TYPE, &id);
|
||||
ret = regmap_bulk_read(st->regmap, AD4080_REG_PRODUCT_ID_L, &id_le,
|
||||
sizeof(id_le));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (id != AD4080_CHIP_ID)
|
||||
id = le16_to_cpu(id_le);
|
||||
if (id != st->info->product_id)
|
||||
dev_info(dev, "Unrecognized CHIP_ID 0x%X\n", id);
|
||||
|
||||
ret = regmap_set_bits(st->regmap, AD4080_REG_GPIO_CONFIG_A,
|
||||
@@ -488,7 +560,7 @@ static int ad4080_setup(struct iio_dev *indio_dev)
|
||||
AD4080_REG_ADC_DATA_INTF_CONFIG_B,
|
||||
AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK,
|
||||
FIELD_PREP(AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK,
|
||||
AD4080_LVDS_CNV_CLK_CNT_MAX));
|
||||
st->info->lvds_cnv_clk_cnt_max));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -593,12 +665,22 @@ static int ad4080_probe(struct spi_device *spi)
|
||||
|
||||
static const struct spi_device_id ad4080_id[] = {
|
||||
{ "ad4080", (kernel_ulong_t)&ad4080_chip_info },
|
||||
{ "ad4081", (kernel_ulong_t)&ad4081_chip_info },
|
||||
{ "ad4083", (kernel_ulong_t)&ad4083_chip_info },
|
||||
{ "ad4084", (kernel_ulong_t)&ad4084_chip_info },
|
||||
{ "ad4086", (kernel_ulong_t)&ad4086_chip_info },
|
||||
{ "ad4087", (kernel_ulong_t)&ad4087_chip_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ad4080_id);
|
||||
|
||||
static const struct of_device_id ad4080_of_match[] = {
|
||||
{ .compatible = "adi,ad4080", &ad4080_chip_info },
|
||||
{ .compatible = "adi,ad4081", &ad4081_chip_info },
|
||||
{ .compatible = "adi,ad4083", &ad4083_chip_info },
|
||||
{ .compatible = "adi,ad4084", &ad4084_chip_info },
|
||||
{ .compatible = "adi,ad4086", &ad4086_chip_info },
|
||||
{ .compatible = "adi,ad4087", &ad4087_chip_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ad4080_of_match);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
@@ -110,6 +111,8 @@
|
||||
#define AD7124_FILTER_SINGLE_CYCLE BIT(16)
|
||||
#define AD7124_FILTER_FS GENMASK(10, 0)
|
||||
|
||||
#define AD7124_CFG_SLOT_UNASSIGNED ~0U
|
||||
|
||||
#define AD7124_MAX_CONFIGS 8
|
||||
#define AD7124_MAX_CHANNELS 16
|
||||
|
||||
@@ -175,14 +178,13 @@ enum ad7124_filter_type {
|
||||
};
|
||||
|
||||
struct ad7124_channel_config {
|
||||
bool live;
|
||||
unsigned int cfg_slot;
|
||||
unsigned int requested_odr;
|
||||
unsigned int requested_odr_micro;
|
||||
/*
|
||||
* Following fields are used to compare for equality. If you
|
||||
* make adaptations in it, you most likely also have to adapt
|
||||
* ad7124_find_similar_live_cfg(), too.
|
||||
* ad7124_config_equal(), too.
|
||||
*/
|
||||
struct_group(config_props,
|
||||
enum ad7124_ref_sel refsel;
|
||||
@@ -199,7 +201,6 @@ struct ad7124_channel_config {
|
||||
};
|
||||
|
||||
struct ad7124_channel {
|
||||
unsigned int nr;
|
||||
struct ad7124_channel_config cfg;
|
||||
unsigned int ain;
|
||||
unsigned int slot;
|
||||
@@ -215,14 +216,14 @@ struct ad7124_state {
|
||||
unsigned int adc_control;
|
||||
unsigned int num_channels;
|
||||
struct mutex cfgs_lock; /* lock for configs access */
|
||||
unsigned long cfg_slots_status; /* bitmap with slot status (1 means it is used) */
|
||||
u8 cfg_slot_use_count[AD7124_MAX_CONFIGS];
|
||||
|
||||
/*
|
||||
* Stores the power-on reset value for the GAIN(x) registers which are
|
||||
* needed for measurements at gain 1 (i.e. CONFIG(x).PGA == 0)
|
||||
*/
|
||||
unsigned int gain_default;
|
||||
DECLARE_KFIFO(live_cfgs_fifo, struct ad7124_channel_config *, AD7124_MAX_CONFIGS);
|
||||
bool enable_single_cycle;
|
||||
};
|
||||
|
||||
static const struct ad7124_chip_info ad7124_4_chip_info = {
|
||||
@@ -366,9 +367,6 @@ static void ad7124_set_channel_odr(struct ad7124_state *st, unsigned int channel
|
||||
cfg->requested_odr_micro * factor / MICRO;
|
||||
odr_sel_bits = clamp(DIV_ROUND_CLOSEST(fclk, divisor), 1, 2047);
|
||||
|
||||
if (odr_sel_bits != st->channels[channel].cfg.odr_sel_bits)
|
||||
st->channels[channel].cfg.live = false;
|
||||
|
||||
st->channels[channel].cfg.odr_sel_bits = odr_sel_bits;
|
||||
}
|
||||
|
||||
@@ -403,61 +401,6 @@ static int ad7124_get_3db_filter_factor(struct ad7124_state *st,
|
||||
}
|
||||
}
|
||||
|
||||
static struct ad7124_channel_config *ad7124_find_similar_live_cfg(struct ad7124_state *st,
|
||||
struct ad7124_channel_config *cfg)
|
||||
{
|
||||
struct ad7124_channel_config *cfg_aux;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* This is just to make sure that the comparison is adapted after
|
||||
* struct ad7124_channel_config was changed.
|
||||
*/
|
||||
static_assert(sizeof_field(struct ad7124_channel_config, config_props) ==
|
||||
sizeof(struct {
|
||||
enum ad7124_ref_sel refsel;
|
||||
bool bipolar;
|
||||
bool buf_positive;
|
||||
bool buf_negative;
|
||||
unsigned int vref_mv;
|
||||
unsigned int pga_bits;
|
||||
unsigned int odr_sel_bits;
|
||||
enum ad7124_filter_type filter_type;
|
||||
unsigned int calibration_offset;
|
||||
unsigned int calibration_gain;
|
||||
}));
|
||||
|
||||
for (i = 0; i < st->num_channels; i++) {
|
||||
cfg_aux = &st->channels[i].cfg;
|
||||
|
||||
if (cfg_aux->live &&
|
||||
cfg->refsel == cfg_aux->refsel &&
|
||||
cfg->bipolar == cfg_aux->bipolar &&
|
||||
cfg->buf_positive == cfg_aux->buf_positive &&
|
||||
cfg->buf_negative == cfg_aux->buf_negative &&
|
||||
cfg->vref_mv == cfg_aux->vref_mv &&
|
||||
cfg->pga_bits == cfg_aux->pga_bits &&
|
||||
cfg->odr_sel_bits == cfg_aux->odr_sel_bits &&
|
||||
cfg->filter_type == cfg_aux->filter_type &&
|
||||
cfg->calibration_offset == cfg_aux->calibration_offset &&
|
||||
cfg->calibration_gain == cfg_aux->calibration_gain)
|
||||
return cfg_aux;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ad7124_find_free_config_slot(struct ad7124_state *st)
|
||||
{
|
||||
unsigned int free_cfg_slot;
|
||||
|
||||
free_cfg_slot = find_first_zero_bit(&st->cfg_slots_status, AD7124_MAX_CONFIGS);
|
||||
if (free_cfg_slot == AD7124_MAX_CONFIGS)
|
||||
return -1;
|
||||
|
||||
return free_cfg_slot;
|
||||
}
|
||||
|
||||
/* Only called during probe, so dev_err_probe() can be used */
|
||||
static int ad7124_init_config_vref(struct ad7124_state *st, struct ad7124_channel_config *cfg)
|
||||
{
|
||||
@@ -486,6 +429,21 @@ static int ad7124_init_config_vref(struct ad7124_state *st, struct ad7124_channe
|
||||
}
|
||||
}
|
||||
|
||||
static bool ad7124_config_equal(struct ad7124_channel_config *a,
|
||||
struct ad7124_channel_config *b)
|
||||
{
|
||||
return a->refsel == b->refsel &&
|
||||
a->bipolar == b->bipolar &&
|
||||
a->buf_positive == b->buf_positive &&
|
||||
a->buf_negative == b->buf_negative &&
|
||||
a->vref_mv == b->vref_mv &&
|
||||
a->pga_bits == b->pga_bits &&
|
||||
a->odr_sel_bits == b->odr_sel_bits &&
|
||||
a->filter_type == b->filter_type &&
|
||||
a->calibration_offset == b->calibration_offset &&
|
||||
a->calibration_gain == b->calibration_gain;
|
||||
}
|
||||
|
||||
static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_config *cfg,
|
||||
unsigned int cfg_slot)
|
||||
{
|
||||
@@ -494,13 +452,13 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co
|
||||
unsigned int post = 0;
|
||||
int ret;
|
||||
|
||||
cfg->cfg_slot = cfg_slot;
|
||||
|
||||
ret = ad_sd_write_reg(&st->sd, AD7124_OFFSET(cfg->cfg_slot), 3, cfg->calibration_offset);
|
||||
ret = ad_sd_write_reg(&st->sd, AD7124_OFFSET(cfg_slot), 3,
|
||||
cfg->calibration_offset);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ad_sd_write_reg(&st->sd, AD7124_GAIN(cfg->cfg_slot), 3, cfg->calibration_gain);
|
||||
ret = ad_sd_write_reg(&st->sd, AD7124_GAIN(cfg_slot), 3,
|
||||
cfg->calibration_gain);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -510,7 +468,7 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co
|
||||
(cfg->buf_negative ? AD7124_CONFIG_AIN_BUFM : 0) |
|
||||
FIELD_PREP(AD7124_CONFIG_PGA, cfg->pga_bits);
|
||||
|
||||
ret = ad_sd_write_reg(&st->sd, AD7124_CONFIG(cfg->cfg_slot), 2, val);
|
||||
ret = ad_sd_write_reg(&st->sd, AD7124_CONFIG(cfg_slot), 2, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
@@ -560,108 +518,107 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co
|
||||
* sampling frequency even when only one channel is enabled in a
|
||||
* buffered read. If it was not set, the N in ad7124_set_channel_odr()
|
||||
* would be 1 and we would get a faster sampling frequency than what
|
||||
* was requested.
|
||||
* was requested. It may only be disabled through debugfs for testing
|
||||
* purposes.
|
||||
*/
|
||||
return ad_sd_write_reg(&st->sd, AD7124_FILTER(cfg->cfg_slot), 3,
|
||||
return ad_sd_write_reg(&st->sd, AD7124_FILTER(cfg_slot), 3,
|
||||
FIELD_PREP(AD7124_FILTER_FILTER, filter) |
|
||||
FIELD_PREP(AD7124_FILTER_REJ60, rej60) |
|
||||
FIELD_PREP(AD7124_FILTER_POST_FILTER, post) |
|
||||
AD7124_FILTER_SINGLE_CYCLE |
|
||||
FIELD_PREP(AD7124_FILTER_SINGLE_CYCLE,
|
||||
st->enable_single_cycle) |
|
||||
FIELD_PREP(AD7124_FILTER_FS, cfg->odr_sel_bits));
|
||||
}
|
||||
|
||||
static struct ad7124_channel_config *ad7124_pop_config(struct ad7124_state *st)
|
||||
/**
|
||||
* ad7124_request_config_slot() - Request a config slot for a given config
|
||||
* @st: Driver instance
|
||||
* @channel: Channel to request a slot for
|
||||
*
|
||||
* Tries to find a matching config already in use, otherwise finds a free
|
||||
* slot. If this function returns successfully, the use count for the slot is
|
||||
* increased and the slot number is stored in cfg->cfg_slot.
|
||||
*
|
||||
* The slot must be released again with ad7124_release_config_slot() when no
|
||||
* longer needed.
|
||||
*
|
||||
* Returns: 0 if a slot was successfully assigned, -EUSERS if no slot is
|
||||
* available or other error if SPI communication fails.
|
||||
*/
|
||||
static int ad7124_request_config_slot(struct ad7124_state *st, u8 channel)
|
||||
{
|
||||
struct ad7124_channel_config *lru_cfg;
|
||||
struct ad7124_channel_config *cfg;
|
||||
int ret;
|
||||
int i;
|
||||
unsigned int other, slot;
|
||||
int last_used_slot = -1;
|
||||
|
||||
/* Find another channel with a matching config, if any. */
|
||||
for (other = 0; other < st->num_channels; other++) {
|
||||
if (other == channel)
|
||||
continue;
|
||||
|
||||
if (st->channels[other].cfg.cfg_slot == AD7124_CFG_SLOT_UNASSIGNED)
|
||||
continue;
|
||||
|
||||
last_used_slot = max_t(int, last_used_slot,
|
||||
st->channels[other].cfg.cfg_slot);
|
||||
|
||||
if (!ad7124_config_equal(&st->channels[other].cfg,
|
||||
&st->channels[channel].cfg))
|
||||
continue;
|
||||
|
||||
/* Found a match, re-use that slot. */
|
||||
slot = st->channels[other].cfg.cfg_slot;
|
||||
st->cfg_slot_use_count[slot]++;
|
||||
st->channels[channel].cfg.cfg_slot = slot;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* No match, use next free slot. */
|
||||
slot = last_used_slot + 1;
|
||||
if (slot >= AD7124_MAX_CONFIGS)
|
||||
return -EUSERS;
|
||||
|
||||
st->cfg_slot_use_count[slot]++;
|
||||
st->channels[channel].cfg.cfg_slot = slot;
|
||||
|
||||
return ad7124_write_config(st, &st->channels[channel].cfg, slot);
|
||||
}
|
||||
|
||||
static void ad7124_release_config_slot(struct ad7124_state *st, u8 channel)
|
||||
{
|
||||
unsigned int slot;
|
||||
|
||||
/*
|
||||
* Pop least recently used config from the fifo
|
||||
* in order to make room for the new one
|
||||
* All of these early return conditions can happen at probe when all
|
||||
* channels are disabled. Otherwise, they should not happen normally.
|
||||
*/
|
||||
ret = kfifo_get(&st->live_cfgs_fifo, &lru_cfg);
|
||||
if (ret <= 0)
|
||||
return NULL;
|
||||
if (channel >= st->num_channels)
|
||||
return;
|
||||
|
||||
lru_cfg->live = false;
|
||||
slot = st->channels[channel].cfg.cfg_slot;
|
||||
|
||||
/* mark slot as free */
|
||||
assign_bit(lru_cfg->cfg_slot, &st->cfg_slots_status, 0);
|
||||
if (slot == AD7124_CFG_SLOT_UNASSIGNED ||
|
||||
st->cfg_slot_use_count[slot] == 0)
|
||||
return;
|
||||
|
||||
/* invalidate all other configs that pointed to this one */
|
||||
for (i = 0; i < st->num_channels; i++) {
|
||||
cfg = &st->channels[i].cfg;
|
||||
|
||||
if (cfg->cfg_slot == lru_cfg->cfg_slot)
|
||||
cfg->live = false;
|
||||
}
|
||||
|
||||
return lru_cfg;
|
||||
}
|
||||
|
||||
static int ad7124_push_config(struct ad7124_state *st, struct ad7124_channel_config *cfg)
|
||||
{
|
||||
struct ad7124_channel_config *lru_cfg;
|
||||
int free_cfg_slot;
|
||||
|
||||
free_cfg_slot = ad7124_find_free_config_slot(st);
|
||||
if (free_cfg_slot >= 0) {
|
||||
/* push the new config in configs queue */
|
||||
kfifo_put(&st->live_cfgs_fifo, cfg);
|
||||
} else {
|
||||
/* pop one config to make room for the new one */
|
||||
lru_cfg = ad7124_pop_config(st);
|
||||
if (!lru_cfg)
|
||||
return -EINVAL;
|
||||
|
||||
/* push the new config in configs queue */
|
||||
free_cfg_slot = lru_cfg->cfg_slot;
|
||||
kfifo_put(&st->live_cfgs_fifo, cfg);
|
||||
}
|
||||
|
||||
/* mark slot as used */
|
||||
assign_bit(free_cfg_slot, &st->cfg_slots_status, 1);
|
||||
|
||||
return ad7124_write_config(st, cfg, free_cfg_slot);
|
||||
}
|
||||
|
||||
static int ad7124_enable_channel(struct ad7124_state *st, struct ad7124_channel *ch)
|
||||
{
|
||||
ch->cfg.live = true;
|
||||
return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(ch->nr), 2, ch->ain |
|
||||
FIELD_PREP(AD7124_CHANNEL_SETUP, ch->cfg.cfg_slot) |
|
||||
AD7124_CHANNEL_ENABLE);
|
||||
st->cfg_slot_use_count[slot]--;
|
||||
st->channels[channel].cfg.cfg_slot = AD7124_CFG_SLOT_UNASSIGNED;
|
||||
}
|
||||
|
||||
static int ad7124_prepare_read(struct ad7124_state *st, int address)
|
||||
{
|
||||
struct ad7124_channel_config *cfg = &st->channels[address].cfg;
|
||||
struct ad7124_channel_config *live_cfg;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Before doing any reads assign the channel a configuration.
|
||||
* Check if channel's config is on the device
|
||||
*/
|
||||
if (!cfg->live) {
|
||||
/* check if config matches another one */
|
||||
live_cfg = ad7124_find_similar_live_cfg(st, cfg);
|
||||
if (!live_cfg)
|
||||
ad7124_push_config(st, cfg);
|
||||
else
|
||||
cfg->cfg_slot = live_cfg->cfg_slot;
|
||||
}
|
||||
ret = ad7124_request_config_slot(st, address);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* point channel to the config slot and enable */
|
||||
return ad7124_enable_channel(st, &st->channels[address]);
|
||||
}
|
||||
|
||||
static int __ad7124_set_channel(struct ad_sigma_delta *sd, unsigned int channel)
|
||||
{
|
||||
struct ad7124_state *st = container_of(sd, struct ad7124_state, sd);
|
||||
|
||||
return ad7124_prepare_read(st, channel);
|
||||
return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(address), 2,
|
||||
st->channels[address].ain |
|
||||
FIELD_PREP(AD7124_CHANNEL_SETUP, cfg->cfg_slot) |
|
||||
AD7124_CHANNEL_ENABLE);
|
||||
}
|
||||
|
||||
static int ad7124_set_channel(struct ad_sigma_delta *sd, unsigned int channel)
|
||||
@@ -670,7 +627,7 @@ static int ad7124_set_channel(struct ad_sigma_delta *sd, unsigned int channel)
|
||||
int ret;
|
||||
|
||||
mutex_lock(&st->cfgs_lock);
|
||||
ret = __ad7124_set_channel(sd, channel);
|
||||
ret = ad7124_prepare_read(st, channel);
|
||||
mutex_unlock(&st->cfgs_lock);
|
||||
|
||||
return ret;
|
||||
@@ -700,6 +657,8 @@ static int ad7124_disable_one(struct ad_sigma_delta *sd, unsigned int chan)
|
||||
{
|
||||
struct ad7124_state *st = container_of(sd, struct ad7124_state, sd);
|
||||
|
||||
ad7124_release_config_slot(st, chan);
|
||||
|
||||
/* The relevant thing here is that AD7124_CHANNEL_ENABLE is cleared. */
|
||||
return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(chan), 2, 0);
|
||||
}
|
||||
@@ -709,7 +668,7 @@ static int ad7124_disable_all(struct ad_sigma_delta *sd)
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
for (i = 0; i < AD7124_MAX_CHANNELS; i++) {
|
||||
ret = ad7124_disable_one(sd, i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -921,9 +880,6 @@ static int ad7124_write_raw(struct iio_dev *indio_dev,
|
||||
gain = DIV_ROUND_CLOSEST(res, val2);
|
||||
res = ad7124_find_closest_match(ad7124_gain, ARRAY_SIZE(ad7124_gain), gain);
|
||||
|
||||
if (st->channels[chan->address].cfg.pga_bits != res)
|
||||
st->channels[chan->address].cfg.live = false;
|
||||
|
||||
st->channels[chan->address].cfg.pga_bits = res;
|
||||
return 0;
|
||||
default:
|
||||
@@ -965,7 +921,7 @@ static int ad7124_update_scan_mode(struct iio_dev *indio_dev,
|
||||
for (i = 0; i < st->num_channels; i++) {
|
||||
bit_set = test_bit(i, scan_mask);
|
||||
if (bit_set)
|
||||
ret = __ad7124_set_channel(&st->sd, i);
|
||||
ret = ad7124_prepare_read(st, i);
|
||||
else
|
||||
ret = ad7124_spi_write_mask(st, AD7124_CHANNEL(i), AD7124_CHANNEL_ENABLE,
|
||||
0, 2);
|
||||
@@ -1066,7 +1022,11 @@ static int ad7124_syscalib_locked(struct ad7124_state *st, const struct iio_chan
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(ch->cfg.cfg_slot), 3,
|
||||
/*
|
||||
* Making the assumption that a single conversion will always
|
||||
* use configuration slot 0 for the OFFSET/GAIN registers.
|
||||
*/
|
||||
ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(0), 3,
|
||||
&ch->cfg.calibration_offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -1081,7 +1041,7 @@ static int ad7124_syscalib_locked(struct ad7124_state *st, const struct iio_chan
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(ch->cfg.cfg_slot), 3,
|
||||
ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(0), 3,
|
||||
&ch->cfg.calibration_gain);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -1172,7 +1132,6 @@ static int ad7124_set_filter_type_attr(struct iio_dev *dev,
|
||||
|
||||
guard(mutex)(&st->cfgs_lock);
|
||||
|
||||
cfg->live = false;
|
||||
cfg->filter_type = value;
|
||||
ad7124_set_channel_odr(st, chan->address);
|
||||
|
||||
@@ -1305,7 +1264,6 @@ static int ad7124_parse_channel_config(struct iio_dev *indio_dev,
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"diff-channels property of %pfwP contains invalid data\n", child);
|
||||
|
||||
st->channels[channel].nr = channel;
|
||||
st->channels[channel].ain = FIELD_PREP(AD7124_CHANNEL_AINP, ain[0]) |
|
||||
FIELD_PREP(AD7124_CHANNEL_AINM, ain[1]);
|
||||
|
||||
@@ -1332,7 +1290,6 @@ static int ad7124_parse_channel_config(struct iio_dev *indio_dev,
|
||||
|
||||
if (num_channels < AD7124_MAX_CHANNELS) {
|
||||
st->channels[num_channels] = (struct ad7124_channel) {
|
||||
.nr = num_channels,
|
||||
.ain = FIELD_PREP(AD7124_CHANNEL_AINP, AD7124_CHANNEL_AINx_TEMPSENSOR) |
|
||||
FIELD_PREP(AD7124_CHANNEL_AINM, AD7124_CHANNEL_AINx_AVSS),
|
||||
.cfg = {
|
||||
@@ -1358,6 +1315,7 @@ static int ad7124_parse_channel_config(struct iio_dev *indio_dev,
|
||||
},
|
||||
.address = num_channels,
|
||||
.scan_index = num_channels,
|
||||
.ext_info = ad7124_calibsys_ext_info,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1489,8 +1447,10 @@ static int ad7124_setup(struct ad7124_state *st)
|
||||
st->adc_control &= ~AD7124_ADC_CONTROL_MODE;
|
||||
st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_MODE, AD_SD_MODE_IDLE);
|
||||
|
||||
mutex_init(&st->cfgs_lock);
|
||||
INIT_KFIFO(st->live_cfgs_fifo);
|
||||
ret = devm_mutex_init(dev, &st->cfgs_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < st->num_channels; i++) {
|
||||
struct ad7124_channel_config *cfg = &st->channels[i].cfg;
|
||||
|
||||
@@ -1498,6 +1458,8 @@ static int ad7124_setup(struct ad7124_state *st)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
cfg->cfg_slot = AD7124_CFG_SLOT_UNASSIGNED;
|
||||
|
||||
/* Default filter type on the ADC after reset. */
|
||||
cfg->filter_type = AD7124_FILTER_TYPE_SINC4;
|
||||
|
||||
@@ -1555,9 +1517,9 @@ static int __ad7124_calibrate_all(struct ad7124_state *st, struct iio_dev *indio
|
||||
* after full-scale calibration because the next
|
||||
* ad_sd_calibrate() call overwrites this via
|
||||
* ad_sigma_delta_set_channel() -> ad7124_set_channel()
|
||||
* ... -> ad7124_enable_channel().
|
||||
* -> ad7124_prepare_read().
|
||||
*/
|
||||
ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(st->channels[i].cfg.cfg_slot), 3,
|
||||
ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(0), 3,
|
||||
&st->channels[i].cfg.calibration_gain);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -1567,7 +1529,11 @@ static int __ad7124_calibrate_all(struct ad7124_state *st, struct iio_dev *indio
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(st->channels[i].cfg.cfg_slot), 3,
|
||||
/*
|
||||
* Making the assumption that a single conversion will always
|
||||
* use configuration slot 0 for the OFFSET/GAIN registers.
|
||||
*/
|
||||
ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(0), 3,
|
||||
&st->channels[i].cfg.calibration_offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -1609,6 +1575,18 @@ static void ad7124_reg_disable(void *r)
|
||||
regulator_disable(r);
|
||||
}
|
||||
|
||||
static void ad7124_debugfs_init(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct dentry *dentry = iio_get_debugfs_dentry(indio_dev);
|
||||
struct ad7124_state *st = iio_priv(indio_dev);
|
||||
|
||||
if (!IS_ENABLED(CONFIG_DEBUG_FS))
|
||||
return;
|
||||
|
||||
debugfs_create_bool("enable_single_cycle", 0644, dentry,
|
||||
&st->enable_single_cycle);
|
||||
}
|
||||
|
||||
static int ad7124_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct ad7124_chip_info *info;
|
||||
@@ -1629,6 +1607,9 @@ static int ad7124_probe(struct spi_device *spi)
|
||||
|
||||
st->chip_info = info;
|
||||
|
||||
/* Only disabled for debug/testing purposes. */
|
||||
st->enable_single_cycle = true;
|
||||
|
||||
indio_dev->name = st->chip_info->name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->info = &ad7124_info;
|
||||
@@ -1686,6 +1667,8 @@ static int ad7124_probe(struct spi_device *spi)
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to register iio device\n");
|
||||
|
||||
ad7124_debugfs_init(indio_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -899,7 +899,7 @@ static int ad7768_read_label(struct iio_dev *indio_dev,
|
||||
{
|
||||
struct ad7768_state *st = iio_priv(indio_dev);
|
||||
|
||||
return sprintf(label, "%s\n", st->labels[chan->channel]);
|
||||
return sysfs_emit(label, "%s\n", st->labels[chan->channel]);
|
||||
}
|
||||
|
||||
static int ad7768_get_current_scan_type(const struct iio_dev *indio_dev,
|
||||
|
||||
@@ -1629,7 +1629,7 @@ static const struct regmap_config ade9000_regmap_config = {
|
||||
.val_bits = 32,
|
||||
.max_register = 0x6bc,
|
||||
.zero_flag_mask = true,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.cache_type = REGCACHE_MAPLE,
|
||||
.reg_read = ade9000_spi_read_reg,
|
||||
.reg_write = ade9000_spi_write_reg,
|
||||
.volatile_reg = ade9000_is_volatile_reg,
|
||||
|
||||
@@ -645,6 +645,16 @@ static const struct aspeed_adc_trim_locate ast2600_adc1_trim = {
|
||||
.field = GENMASK(7, 4),
|
||||
};
|
||||
|
||||
static const struct aspeed_adc_trim_locate ast2700_adc0_trim = {
|
||||
.offset = 0x820,
|
||||
.field = GENMASK(3, 0),
|
||||
};
|
||||
|
||||
static const struct aspeed_adc_trim_locate ast2700_adc1_trim = {
|
||||
.offset = 0x820,
|
||||
.field = GENMASK(7, 4),
|
||||
};
|
||||
|
||||
static const struct aspeed_adc_model_data ast2400_model_data = {
|
||||
.model_name = "ast2400-adc",
|
||||
.vref_fixed_mv = 2500,
|
||||
@@ -689,11 +699,35 @@ static const struct aspeed_adc_model_data ast2600_adc1_model_data = {
|
||||
.trim_locate = &ast2600_adc1_trim,
|
||||
};
|
||||
|
||||
static const struct aspeed_adc_model_data ast2700_adc0_model_data = {
|
||||
.model_name = "ast2700-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 = &ast2700_adc0_trim,
|
||||
};
|
||||
|
||||
static const struct aspeed_adc_model_data ast2700_adc1_model_data = {
|
||||
.model_name = "ast2700-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 = &ast2700_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 },
|
||||
{ .compatible = "aspeed,ast2700-adc0", .data = &ast2700_adc0_model_data },
|
||||
{ .compatible = "aspeed,ast2700-adc1", .data = &ast2700_adc1_model_data },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, aspeed_adc_matches);
|
||||
|
||||
391
drivers/iio/adc/max14001.c
Normal file
391
drivers/iio/adc/max14001.c
Normal file
@@ -0,0 +1,391 @@
|
||||
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
|
||||
/*
|
||||
* Analog Devices MAX14001/MAX14002 ADC driver
|
||||
*
|
||||
* Copyright (C) 2023-2025 Analog Devices Inc.
|
||||
* Copyright (C) 2023 Kim Seer Paller <kimseer.paller@analog.com>
|
||||
* Copyright (c) 2025 Marilene Andrade Garcia <marilene.agarcia@gmail.com>
|
||||
*
|
||||
* Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX14001-MAX14002.pdf
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitrev.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/units.h>
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/types.h>
|
||||
|
||||
/* MAX14001 Registers Address */
|
||||
#define MAX14001_REG_ADC 0x00
|
||||
#define MAX14001_REG_FADC 0x01
|
||||
#define MAX14001_REG_FLAGS 0x02
|
||||
#define MAX14001_REG_FLTEN 0x03
|
||||
#define MAX14001_REG_THL 0x04
|
||||
#define MAX14001_REG_THU 0x05
|
||||
#define MAX14001_REG_INRR 0x06
|
||||
#define MAX14001_REG_INRT 0x07
|
||||
#define MAX14001_REG_INRP 0x08
|
||||
#define MAX14001_REG_CFG 0x09
|
||||
#define MAX14001_REG_ENBL 0x0A
|
||||
#define MAX14001_REG_ACT 0x0B
|
||||
#define MAX14001_REG_WEN 0x0C
|
||||
|
||||
#define MAX14001_REG_VERIFICATION(x) ((x) + 0x10)
|
||||
|
||||
#define MAX14001_REG_CFG_BIT_EXRF BIT(5)
|
||||
|
||||
#define MAX14001_REG_WEN_VALUE_WRITE 0x294
|
||||
|
||||
#define MAX14001_MASK_ADDR GENMASK(15, 11)
|
||||
#define MAX14001_MASK_WR BIT(10)
|
||||
#define MAX14001_MASK_DATA GENMASK(9, 0)
|
||||
|
||||
struct max14001_state {
|
||||
const struct max14001_chip_info *chip_info;
|
||||
struct spi_device *spi;
|
||||
struct regmap *regmap;
|
||||
int vref_mV;
|
||||
bool spi_hw_has_lsb_first;
|
||||
|
||||
/*
|
||||
* The following buffers will be bit-reversed during device
|
||||
* communication, because the device transmits and receives data
|
||||
* LSB-first.
|
||||
* DMA (thus cache coherency maintenance) requires the transfer
|
||||
* buffers to live in their own cache lines.
|
||||
*/
|
||||
union {
|
||||
__be16 be;
|
||||
__le16 le;
|
||||
} spi_tx_buffer __aligned(IIO_DMA_MINALIGN);
|
||||
|
||||
union {
|
||||
__be16 be;
|
||||
__le16 le;
|
||||
} spi_rx_buffer;
|
||||
};
|
||||
|
||||
struct max14001_chip_info {
|
||||
const char *name;
|
||||
};
|
||||
|
||||
static int max14001_read(void *context, unsigned int reg, unsigned int *val)
|
||||
{
|
||||
struct max14001_state *st = context;
|
||||
struct spi_transfer xfers[] = {
|
||||
{
|
||||
.tx_buf = &st->spi_tx_buffer,
|
||||
.len = sizeof(st->spi_tx_buffer),
|
||||
.cs_change = 1,
|
||||
}, {
|
||||
.rx_buf = &st->spi_rx_buffer,
|
||||
.len = sizeof(st->spi_rx_buffer),
|
||||
},
|
||||
};
|
||||
int ret;
|
||||
unsigned int addr, data;
|
||||
|
||||
/*
|
||||
* Prepare SPI transmit buffer 16 bit-value and reverse bit order
|
||||
* to align with the LSB-first input on SDI port in order to meet
|
||||
* the device communication requirements. If the controller supports
|
||||
* SPI_LSB_FIRST, this step will be handled by the SPI controller.
|
||||
*/
|
||||
addr = FIELD_PREP(MAX14001_MASK_ADDR, reg);
|
||||
|
||||
if (st->spi_hw_has_lsb_first)
|
||||
st->spi_tx_buffer.le = cpu_to_le16(addr);
|
||||
else
|
||||
st->spi_tx_buffer.be = cpu_to_be16(bitrev16(addr));
|
||||
|
||||
ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Convert received 16-bit value to cpu-endian format and reverse
|
||||
* bit order. If the controller supports SPI_LSB_FIRST, this step
|
||||
* will be handled by the SPI controller.
|
||||
*/
|
||||
if (st->spi_hw_has_lsb_first)
|
||||
data = le16_to_cpu(st->spi_rx_buffer.le);
|
||||
else
|
||||
data = bitrev16(be16_to_cpu(st->spi_rx_buffer.be));
|
||||
|
||||
*val = FIELD_GET(MAX14001_MASK_DATA, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max14001_write(struct max14001_state *st, unsigned int reg, unsigned int val)
|
||||
{
|
||||
unsigned int addr;
|
||||
|
||||
/*
|
||||
* Prepare SPI transmit buffer 16 bit-value and reverse bit order
|
||||
* to align with the LSB-first input on SDI port in order to meet
|
||||
* the device communication requirements. If the controller supports
|
||||
* SPI_LSB_FIRST, this step will be handled by the SPI controller.
|
||||
*/
|
||||
addr = FIELD_PREP(MAX14001_MASK_ADDR, reg) |
|
||||
FIELD_PREP(MAX14001_MASK_WR, 1) |
|
||||
FIELD_PREP(MAX14001_MASK_DATA, val);
|
||||
|
||||
if (st->spi_hw_has_lsb_first)
|
||||
st->spi_tx_buffer.le = cpu_to_le16(addr);
|
||||
else
|
||||
st->spi_tx_buffer.be = cpu_to_be16(bitrev16(addr));
|
||||
|
||||
return spi_write(st->spi, &st->spi_tx_buffer, sizeof(st->spi_tx_buffer));
|
||||
}
|
||||
|
||||
static int max14001_write_single_reg(void *context, unsigned int reg, unsigned int val)
|
||||
{
|
||||
struct max14001_state *st = context;
|
||||
int ret;
|
||||
|
||||
/* Enable writing to the SPI register. */
|
||||
ret = max14001_write(st, MAX14001_REG_WEN, MAX14001_REG_WEN_VALUE_WRITE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Writing data into SPI register. */
|
||||
ret = max14001_write(st, reg, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Disable writing to the SPI register. */
|
||||
return max14001_write(st, MAX14001_REG_WEN, 0);
|
||||
}
|
||||
|
||||
static int max14001_write_verification_reg(struct max14001_state *st, unsigned int reg)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(st->regmap, reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return max14001_write(st, MAX14001_REG_VERIFICATION(reg), val);
|
||||
}
|
||||
|
||||
static int max14001_disable_mv_fault(struct max14001_state *st)
|
||||
{
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
/* Enable writing to the SPI registers. */
|
||||
ret = max14001_write(st, MAX14001_REG_WEN, MAX14001_REG_WEN_VALUE_WRITE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Reads all registers and writes the values to their appropriate
|
||||
* verification registers to clear the Memory Validation fault.
|
||||
*/
|
||||
for (reg = MAX14001_REG_FLTEN; reg <= MAX14001_REG_ENBL; reg++) {
|
||||
ret = max14001_write_verification_reg(st, reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Disable writing to the SPI registers. */
|
||||
return max14001_write(st, MAX14001_REG_WEN, 0);
|
||||
}
|
||||
|
||||
static int max14001_debugfs_reg_access(struct iio_dev *indio_dev,
|
||||
unsigned int reg, unsigned int writeval,
|
||||
unsigned int *readval)
|
||||
{
|
||||
struct max14001_state *st = iio_priv(indio_dev);
|
||||
|
||||
if (readval)
|
||||
return regmap_read(st->regmap, reg, readval);
|
||||
|
||||
return regmap_write(st->regmap, reg, writeval);
|
||||
}
|
||||
|
||||
static int max14001_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct max14001_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = regmap_read(st->regmap, MAX14001_REG_ADC, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = st->vref_mV;
|
||||
*val2 = 10;
|
||||
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct regmap_range max14001_regmap_rd_range[] = {
|
||||
regmap_reg_range(MAX14001_REG_ADC, MAX14001_REG_ENBL),
|
||||
regmap_reg_range(MAX14001_REG_WEN, MAX14001_REG_WEN),
|
||||
regmap_reg_range(MAX14001_REG_VERIFICATION(MAX14001_REG_FLTEN),
|
||||
MAX14001_REG_VERIFICATION(MAX14001_REG_ENBL)),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max14001_regmap_rd_table = {
|
||||
.yes_ranges = max14001_regmap_rd_range,
|
||||
.n_yes_ranges = ARRAY_SIZE(max14001_regmap_rd_range),
|
||||
};
|
||||
|
||||
static const struct regmap_range max14001_regmap_wr_range[] = {
|
||||
regmap_reg_range(MAX14001_REG_FLTEN, MAX14001_REG_WEN),
|
||||
regmap_reg_range(MAX14001_REG_VERIFICATION(MAX14001_REG_FLTEN),
|
||||
MAX14001_REG_VERIFICATION(MAX14001_REG_ENBL)),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table max14001_regmap_wr_table = {
|
||||
.yes_ranges = max14001_regmap_wr_range,
|
||||
.n_yes_ranges = ARRAY_SIZE(max14001_regmap_wr_range),
|
||||
};
|
||||
|
||||
static const struct regmap_config max14001_regmap_config = {
|
||||
.reg_read = max14001_read,
|
||||
.reg_write = max14001_write_single_reg,
|
||||
.max_register = MAX14001_REG_VERIFICATION(MAX14001_REG_ENBL),
|
||||
.rd_table = &max14001_regmap_rd_table,
|
||||
.wr_table = &max14001_regmap_wr_table,
|
||||
};
|
||||
|
||||
static const struct iio_info max14001_info = {
|
||||
.read_raw = max14001_read_raw,
|
||||
.debugfs_reg_access = max14001_debugfs_reg_access,
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec max14001_channel[] = {
|
||||
{
|
||||
.type = IIO_VOLTAGE,
|
||||
.indexed = 1,
|
||||
.channel = 0,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
},
|
||||
};
|
||||
|
||||
static int max14001_probe(struct spi_device *spi)
|
||||
{
|
||||
struct device *dev = &spi->dev;
|
||||
struct iio_dev *indio_dev;
|
||||
struct max14001_state *st;
|
||||
int ret;
|
||||
bool use_ext_vrefin = false;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
st = iio_priv(indio_dev);
|
||||
st->spi = spi;
|
||||
st->spi_hw_has_lsb_first = spi->mode & SPI_LSB_FIRST;
|
||||
st->chip_info = spi_get_device_match_data(spi);
|
||||
if (!st->chip_info)
|
||||
return -EINVAL;
|
||||
|
||||
indio_dev->name = st->chip_info->name;
|
||||
indio_dev->info = &max14001_info;
|
||||
indio_dev->channels = max14001_channel;
|
||||
indio_dev->num_channels = ARRAY_SIZE(max14001_channel);
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
st->regmap = devm_regmap_init(dev, NULL, st, &max14001_regmap_config);
|
||||
if (IS_ERR(st->regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(st->regmap), "Failed to initialize regmap\n");
|
||||
|
||||
ret = devm_regulator_get_enable(dev, "vdd");
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to enable Vdd supply\n");
|
||||
|
||||
ret = devm_regulator_get_enable(dev, "vddl");
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to enable Vddl supply\n");
|
||||
|
||||
ret = devm_regulator_get_enable_read_voltage(dev, "refin");
|
||||
if (ret < 0 && ret != -ENODEV)
|
||||
return dev_err_probe(dev, ret, "Failed to get REFIN voltage\n");
|
||||
|
||||
if (ret == -ENODEV)
|
||||
ret = 1250000;
|
||||
else
|
||||
use_ext_vrefin = true;
|
||||
st->vref_mV = ret / (MICRO / MILLI);
|
||||
|
||||
if (use_ext_vrefin) {
|
||||
/*
|
||||
* Configure the MAX14001/MAX14002 to use an external voltage
|
||||
* reference source by setting the bit 5 of the configuration register.
|
||||
*/
|
||||
ret = regmap_set_bits(st->regmap, MAX14001_REG_CFG,
|
||||
MAX14001_REG_CFG_BIT_EXRF);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Failed to set External REFIN in Configuration Register\n");
|
||||
}
|
||||
|
||||
ret = max14001_disable_mv_fault(st);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to disable MV Fault\n");
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
static struct max14001_chip_info max14001_chip_info = {
|
||||
.name = "max14001",
|
||||
};
|
||||
|
||||
static struct max14001_chip_info max14002_chip_info = {
|
||||
.name = "max14002",
|
||||
};
|
||||
|
||||
static const struct spi_device_id max14001_id_table[] = {
|
||||
{ "max14001", (kernel_ulong_t)&max14001_chip_info },
|
||||
{ "max14002", (kernel_ulong_t)&max14002_chip_info },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct of_device_id max14001_of_match[] = {
|
||||
{ .compatible = "adi,max14001", .data = &max14001_chip_info },
|
||||
{ .compatible = "adi,max14002", .data = &max14002_chip_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, max14001_of_match);
|
||||
|
||||
static struct spi_driver max14001_driver = {
|
||||
.driver = {
|
||||
.name = "max14001",
|
||||
.of_match_table = max14001_of_match,
|
||||
},
|
||||
.probe = max14001_probe,
|
||||
.id_table = max14001_id_table,
|
||||
};
|
||||
module_spi_driver(max14001_driver);
|
||||
|
||||
MODULE_AUTHOR("Kim Seer Paller <kimseer.paller@analog.com>");
|
||||
MODULE_AUTHOR("Marilene Andrade Garcia <marilene.agarcia@gmail.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices MAX14001/MAX14002 ADCs driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -987,7 +987,7 @@ static int mcp3564_read_label(struct iio_dev *indio_dev,
|
||||
{
|
||||
struct mcp3564_state *adc = iio_priv(indio_dev);
|
||||
|
||||
return sprintf(label, "%s\n", adc->labels[chan->scan_index]);
|
||||
return sysfs_emit(label, "%s\n", adc->labels[chan->scan_index]);
|
||||
}
|
||||
|
||||
static int mcp3564_parse_fw_children(struct iio_dev *indio_dev)
|
||||
|
||||
@@ -1181,12 +1181,12 @@ static int read_label(struct iio_dev *indio_dev,
|
||||
char *label)
|
||||
{
|
||||
if (chan->type == IIO_TEMP)
|
||||
return sprintf(label, "temp-sensor\n");
|
||||
return sysfs_emit(label, "temp-sensor\n");
|
||||
if (chan->type == IIO_VOLTAGE && chan->channel >= NUM_MUX_0_VSS)
|
||||
return sprintf(label, "%s\n",
|
||||
return sysfs_emit(label, "%s\n",
|
||||
chan7_mux_names[chan->channel - NUM_MUX_0_VSS]);
|
||||
if (chan->type == IIO_VOLTAGE)
|
||||
return sprintf(label, "channel-%d\n", chan->channel);
|
||||
return sysfs_emit(label, "channel-%d\n", chan->channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ static const char *mt6360_channel_labels[MT6360_CHAN_MAX] = {
|
||||
static int mt6360_adc_read_label(struct iio_dev *iio_dev, const struct iio_chan_spec *chan,
|
||||
char *label)
|
||||
{
|
||||
return snprintf(label, PAGE_SIZE, "%s\n", mt6360_channel_labels[chan->channel]);
|
||||
return sysfs_emit(label, "%s\n", mt6360_channel_labels[chan->channel]);
|
||||
}
|
||||
|
||||
static const struct iio_info mt6360_adc_iio_info = {
|
||||
|
||||
@@ -672,13 +672,13 @@ static int pac1921_read_label(struct iio_dev *indio_dev,
|
||||
{
|
||||
switch (chan->channel) {
|
||||
case PAC1921_CHAN_VBUS:
|
||||
return sprintf(label, "vbus\n");
|
||||
return sysfs_emit(label, "vbus\n");
|
||||
case PAC1921_CHAN_VSENSE:
|
||||
return sprintf(label, "vsense\n");
|
||||
return sysfs_emit(label, "vsense\n");
|
||||
case PAC1921_CHAN_CURRENT:
|
||||
return sprintf(label, "current\n");
|
||||
return sysfs_emit(label, "current\n");
|
||||
case PAC1921_CHAN_POWER:
|
||||
return sprintf(label, "power\n");
|
||||
return sysfs_emit(label, "power\n");
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -768,7 +768,7 @@ static int pac1934_retrieve_data(struct pac1934_chip_info *info,
|
||||
* Re-schedule the work for the read registers on timeout
|
||||
* (to prevent chip registers saturation)
|
||||
*/
|
||||
mod_delayed_work(system_wq, &info->work_chip_rfsh,
|
||||
mod_delayed_work(system_percpu_wq, &info->work_chip_rfsh,
|
||||
msecs_to_jiffies(PAC1934_MAX_RFSH_LIMIT_MS));
|
||||
}
|
||||
|
||||
|
||||
@@ -769,7 +769,7 @@ static int rradc_read_raw(struct iio_dev *indio_dev,
|
||||
static int rradc_read_label(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, char *label)
|
||||
{
|
||||
return snprintf(label, PAGE_SIZE, "%s\n",
|
||||
return sysfs_emit(label, "%s\n",
|
||||
rradc_chans[chan->address].label);
|
||||
}
|
||||
|
||||
|
||||
@@ -168,15 +168,10 @@ static int _get_gpio_reg(unsigned int offset, unsigned int base)
|
||||
#define GET_GPI_VAL_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPI_VALUE_A0_A7)
|
||||
|
||||
static const struct regmap_range bd71815_volatile_ro_ranges[] = {
|
||||
{
|
||||
/* Read ADC data */
|
||||
.range_min = BD79112_REG_AGIO0A,
|
||||
.range_max = BD79112_REG_AGIO15B,
|
||||
}, {
|
||||
/* GPI state */
|
||||
.range_min = BD79112_REG_GPI_VALUE_B8_15,
|
||||
.range_max = BD79112_REG_GPI_VALUE_A0_A7,
|
||||
},
|
||||
/* Read ADC data */
|
||||
regmap_reg_range(BD79112_REG_AGIO0A, BD79112_REG_AGIO15B),
|
||||
/* GPI state */
|
||||
regmap_reg_range(BD79112_REG_GPI_VALUE_B8_15, BD79112_REG_GPI_VALUE_A0_A7),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table bd79112_volatile_regs = {
|
||||
|
||||
@@ -126,13 +126,8 @@ struct bd79124_data {
|
||||
};
|
||||
|
||||
static const struct regmap_range bd79124_ro_ranges[] = {
|
||||
{
|
||||
.range_min = BD79124_REG_EVENT_FLAG,
|
||||
.range_max = BD79124_REG_EVENT_FLAG,
|
||||
}, {
|
||||
.range_min = BD79124_REG_RECENT_CH0_LSB,
|
||||
.range_max = BD79124_REG_RECENT_CH7_MSB,
|
||||
},
|
||||
regmap_reg_range(BD79124_REG_EVENT_FLAG, BD79124_REG_EVENT_FLAG),
|
||||
regmap_reg_range(BD79124_REG_RECENT_CH0_LSB, BD79124_REG_RECENT_CH7_MSB),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table bd79124_ro_regs = {
|
||||
@@ -141,22 +136,11 @@ static const struct regmap_access_table bd79124_ro_regs = {
|
||||
};
|
||||
|
||||
static const struct regmap_range bd79124_volatile_ranges[] = {
|
||||
{
|
||||
.range_min = BD79124_REG_RECENT_CH0_LSB,
|
||||
.range_max = BD79124_REG_RECENT_CH7_MSB,
|
||||
}, {
|
||||
.range_min = BD79124_REG_EVENT_FLAG,
|
||||
.range_max = BD79124_REG_EVENT_FLAG,
|
||||
}, {
|
||||
.range_min = BD79124_REG_EVENT_FLAG_HI,
|
||||
.range_max = BD79124_REG_EVENT_FLAG_HI,
|
||||
}, {
|
||||
.range_min = BD79124_REG_EVENT_FLAG_LO,
|
||||
.range_max = BD79124_REG_EVENT_FLAG_LO,
|
||||
}, {
|
||||
.range_min = BD79124_REG_SYSTEM_STATUS,
|
||||
.range_max = BD79124_REG_SYSTEM_STATUS,
|
||||
},
|
||||
regmap_reg_range(BD79124_REG_RECENT_CH0_LSB, BD79124_REG_RECENT_CH7_MSB),
|
||||
regmap_reg_range(BD79124_REG_EVENT_FLAG, BD79124_REG_EVENT_FLAG),
|
||||
regmap_reg_range(BD79124_REG_EVENT_FLAG_HI, BD79124_REG_EVENT_FLAG_HI),
|
||||
regmap_reg_range(BD79124_REG_EVENT_FLAG_LO, BD79124_REG_EVENT_FLAG_LO),
|
||||
regmap_reg_range(BD79124_REG_SYSTEM_STATUS, BD79124_REG_SYSTEM_STATUS),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table bd79124_volatile_regs = {
|
||||
@@ -165,13 +149,8 @@ static const struct regmap_access_table bd79124_volatile_regs = {
|
||||
};
|
||||
|
||||
static const struct regmap_range bd79124_precious_ranges[] = {
|
||||
{
|
||||
.range_min = BD79124_REG_EVENT_FLAG_HI,
|
||||
.range_max = BD79124_REG_EVENT_FLAG_HI,
|
||||
}, {
|
||||
.range_min = BD79124_REG_EVENT_FLAG_LO,
|
||||
.range_max = BD79124_REG_EVENT_FLAG_LO,
|
||||
},
|
||||
regmap_reg_range(BD79124_REG_EVENT_FLAG_HI, BD79124_REG_EVENT_FLAG_HI),
|
||||
regmap_reg_range(BD79124_REG_EVENT_FLAG_LO, BD79124_REG_EVENT_FLAG_LO),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table bd79124_precious_regs = {
|
||||
|
||||
490
drivers/iio/adc/rzn1-adc.c
Normal file
490
drivers/iio/adc/rzn1-adc.c
Normal file
@@ -0,0 +1,490 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Renesas RZ/N1 ADC driver
|
||||
*
|
||||
* Copyright (C) 2025 Schneider-Electric
|
||||
*
|
||||
* Author: Herve Codina <herve.codina@bootlin.com>
|
||||
*
|
||||
* The RZ/N1 ADC controller can handle channels from its internal ADC1 and/or
|
||||
* ADC2 cores. The driver use ADC1 and/or ADC2 cores depending on the presence
|
||||
* of the related power supplies (AVDD and VREF) description in the device-tree.
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define RZN1_ADC_CONTROL_REG 0x02c
|
||||
#define RZN1_ADC_CONTROL_ADC_BUSY BIT(6)
|
||||
|
||||
#define RZN1_ADC_FORCE_REG 0x030
|
||||
#define RZN1_ADC_SET_FORCE_REG 0x034
|
||||
#define RZN1_ADC_CLEAR_FORCE_REG 0x038
|
||||
#define RZN1_ADC_FORCE_VC(_n) BIT(_n)
|
||||
|
||||
#define RZN1_ADC_CONFIG_REG 0x040
|
||||
#define RZN1_ADC_CONFIG_ADC_POWER_DOWN BIT(3)
|
||||
|
||||
#define RZN1_ADC_VC_REG(_n) (0x0c0 + 4 * (_n))
|
||||
#define RZN1_ADC_VC_ADC2_ENABLE BIT(16)
|
||||
#define RZN1_ADC_VC_ADC1_ENABLE BIT(15)
|
||||
#define RZN1_ADC_VC_ADC2_CHANNEL_SEL_MASK GENMASK(5, 3)
|
||||
#define RZN1_ADC_VC_ADC1_CHANNEL_SEL_MASK GENMASK(2, 0)
|
||||
|
||||
#define RZN1_ADC_ADC1_DATA_REG(_n) (0x100 + 4 * (_n))
|
||||
#define RZN1_ADC_ADC2_DATA_REG(_n) (0x140 + 4 * (_n))
|
||||
#define RZN1_ADC_ADCX_DATA_DATA_MASK GENMASK(11, 0)
|
||||
|
||||
#define RZN1_ADC_NO_CHANNEL -1
|
||||
|
||||
#define RZN1_ADC_CHANNEL_SHARED_SCALE(_ch, _ds_name) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.channel = (_ch), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.datasheet_name = (_ds_name), \
|
||||
}
|
||||
|
||||
#define RZN1_ADC_CHANNEL_SEPARATED_SCALE(_ch, _ds_name) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.channel = (_ch), \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.datasheet_name = (_ds_name), \
|
||||
}
|
||||
|
||||
/*
|
||||
* 8 ADC1_IN signals existed numbered 0..4, 6..8
|
||||
* ADCx_IN5 doesn't exist in RZ/N1 datasheet
|
||||
*/
|
||||
static struct iio_chan_spec rzn1_adc1_channels[] = {
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(0, "ADC1_IN0"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(1, "ADC1_IN1"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(2, "ADC1_IN2"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(3, "ADC1_IN3"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(4, "ADC1_IN4"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(5, "ADC1_IN6"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(6, "ADC1_IN7"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(7, "ADC1_IN8"),
|
||||
};
|
||||
|
||||
static struct iio_chan_spec rzn1_adc2_channels[] = {
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(8, "ADC2_IN0"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(9, "ADC2_IN1"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(10, "ADC2_IN2"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(11, "ADC2_IN3"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(12, "ADC2_IN4"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(13, "ADC2_IN6"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(14, "ADC2_IN7"),
|
||||
RZN1_ADC_CHANNEL_SHARED_SCALE(15, "ADC2_IN8"),
|
||||
};
|
||||
|
||||
/*
|
||||
* If both ADCs core are used, scale cannot be common. Indeed, scale is
|
||||
* based on Vref connected on each ADC core.
|
||||
*/
|
||||
static struct iio_chan_spec rzn1_adc1_adc2_channels[] = {
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(0, "ADC1_IN0"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(1, "ADC1_IN1"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(2, "ADC1_IN2"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(3, "ADC1_IN3"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(4, "ADC1_IN4"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(5, "ADC1_IN6"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(6, "ADC1_IN7"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(7, "ADC1_IN8"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(8, "ADC2_IN0"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(9, "ADC2_IN1"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(10, "ADC2_IN2"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(11, "ADC2_IN3"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(12, "ADC2_IN4"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(13, "ADC2_IN6"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(14, "ADC2_IN7"),
|
||||
RZN1_ADC_CHANNEL_SEPARATED_SCALE(15, "ADC2_IN8"),
|
||||
};
|
||||
|
||||
struct rzn1_adc {
|
||||
struct device *dev;
|
||||
void __iomem *regs;
|
||||
struct mutex lock; /* ADC lock */
|
||||
int adc1_vref_mV; /* ADC1 Vref in mV. Negative if ADC1 is not used */
|
||||
int adc2_vref_mV; /* ADC2 Vref in mV. Negative if ADC2 is not used */
|
||||
};
|
||||
|
||||
static int rzn1_adc_power(struct rzn1_adc *rzn1_adc, bool power)
|
||||
{
|
||||
u32 v;
|
||||
|
||||
writel(power ? 0 : RZN1_ADC_CONFIG_ADC_POWER_DOWN,
|
||||
rzn1_adc->regs + RZN1_ADC_CONFIG_REG);
|
||||
|
||||
/* Wait for the ADC_BUSY to clear */
|
||||
return readl_poll_timeout_atomic(rzn1_adc->regs + RZN1_ADC_CONTROL_REG,
|
||||
v, !(v & RZN1_ADC_CONTROL_ADC_BUSY),
|
||||
0, 500);
|
||||
}
|
||||
|
||||
static void rzn1_adc_vc_setup_conversion(struct rzn1_adc *rzn1_adc, u32 ch,
|
||||
int adc1_ch, int adc2_ch)
|
||||
{
|
||||
u32 vc = 0;
|
||||
|
||||
if (adc1_ch != RZN1_ADC_NO_CHANNEL)
|
||||
vc |= RZN1_ADC_VC_ADC1_ENABLE |
|
||||
FIELD_PREP(RZN1_ADC_VC_ADC1_CHANNEL_SEL_MASK, adc1_ch);
|
||||
|
||||
if (adc2_ch != RZN1_ADC_NO_CHANNEL)
|
||||
vc |= RZN1_ADC_VC_ADC2_ENABLE |
|
||||
FIELD_PREP(RZN1_ADC_VC_ADC2_CHANNEL_SEL_MASK, adc2_ch);
|
||||
|
||||
writel(vc, rzn1_adc->regs + RZN1_ADC_VC_REG(ch));
|
||||
}
|
||||
|
||||
static int rzn1_adc_vc_start_conversion(struct rzn1_adc *rzn1_adc, u32 ch)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(rzn1_adc->regs + RZN1_ADC_FORCE_REG);
|
||||
if (val & RZN1_ADC_FORCE_VC(ch))
|
||||
return -EBUSY;
|
||||
|
||||
writel(RZN1_ADC_FORCE_VC(ch), rzn1_adc->regs + RZN1_ADC_SET_FORCE_REG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rzn1_adc_vc_stop_conversion(struct rzn1_adc *rzn1_adc, u32 ch)
|
||||
{
|
||||
writel(RZN1_ADC_FORCE_VC(ch), rzn1_adc->regs + RZN1_ADC_CLEAR_FORCE_REG);
|
||||
}
|
||||
|
||||
static int rzn1_adc_vc_wait_conversion(struct rzn1_adc *rzn1_adc, u32 ch,
|
||||
u32 *adc1_data, u32 *adc2_data)
|
||||
{
|
||||
u32 data_reg;
|
||||
int ret;
|
||||
u32 v;
|
||||
|
||||
/*
|
||||
* When a VC is selected, it needs 20 ADC clocks to perform the
|
||||
* conversion.
|
||||
*
|
||||
* The worst case is when the 16 VCs need to perform a conversion and
|
||||
* our VC is the lowest in term of priority.
|
||||
*
|
||||
* In that case, the conversion is performed in 16 * 20 ADC clocks.
|
||||
*
|
||||
* The ADC clock can be set from 4MHz to 20MHz. This leads to a worst
|
||||
* case of 16 * 20 * 1/4Mhz = 80us.
|
||||
*
|
||||
* Round it up to 100us.
|
||||
*/
|
||||
|
||||
/* Wait for the ADC_FORCE_VC(n) to clear */
|
||||
ret = readl_poll_timeout_atomic(rzn1_adc->regs + RZN1_ADC_FORCE_REG,
|
||||
v, !(v & RZN1_ADC_FORCE_VC(ch)),
|
||||
0, 100);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (adc1_data) {
|
||||
data_reg = readl(rzn1_adc->regs + RZN1_ADC_ADC1_DATA_REG(ch));
|
||||
*adc1_data = FIELD_GET(RZN1_ADC_ADCX_DATA_DATA_MASK, data_reg);
|
||||
}
|
||||
|
||||
if (adc2_data) {
|
||||
data_reg = readl(rzn1_adc->regs + RZN1_ADC_ADC2_DATA_REG(ch));
|
||||
*adc2_data = FIELD_GET(RZN1_ADC_ADCX_DATA_DATA_MASK, data_reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rzn1_adc_read_raw_ch(struct rzn1_adc *rzn1_adc, unsigned int chan, int *val)
|
||||
{
|
||||
u32 *adc1_data, *adc2_data;
|
||||
int adc1_ch, adc2_ch;
|
||||
u32 adc_data;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* IIO chan are decoupled from chans used in rzn1_adc_vc_*() functions.
|
||||
* The RZ/N1 ADC VC controller can handle on a single VC chan one
|
||||
* channel from the ADC1 core and one channel from the ADC2 core.
|
||||
*
|
||||
* Even if IIO chans are mapped 1:1 to ADC core chans and so uses only
|
||||
* a chan from ADC1 or a chan from ADC2, future improvements can define
|
||||
* an IIO chan that uses one chan from ADC1 and one chan from ADC2.
|
||||
*/
|
||||
|
||||
if (chan < 8) {
|
||||
/* chan 0..7 used to get ADC1 ch 0..7 */
|
||||
adc1_ch = chan;
|
||||
adc1_data = &adc_data;
|
||||
adc2_ch = RZN1_ADC_NO_CHANNEL;
|
||||
adc2_data = NULL;
|
||||
} else if (chan < 16) {
|
||||
/* chan 8..15 used to get ADC2 ch 0..7 */
|
||||
adc1_ch = RZN1_ADC_NO_CHANNEL;
|
||||
adc1_data = NULL;
|
||||
adc2_ch = chan - 8;
|
||||
adc2_data = &adc_data;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ACQUIRE(pm_runtime_active_auto_try_enabled, pm)(rzn1_adc->dev);
|
||||
ret = ACQUIRE_ERR(pm_runtime_active_auto_try_enabled, &pm);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &rzn1_adc->lock) {
|
||||
rzn1_adc_vc_setup_conversion(rzn1_adc, chan, adc1_ch, adc2_ch);
|
||||
|
||||
ret = rzn1_adc_vc_start_conversion(rzn1_adc, chan);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = rzn1_adc_vc_wait_conversion(rzn1_adc, chan, adc1_data, adc2_data);
|
||||
if (ret) {
|
||||
rzn1_adc_vc_stop_conversion(rzn1_adc, chan);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
*val = adc_data;
|
||||
ret = IIO_VAL_INT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rzn1_adc_get_vref_mV(struct rzn1_adc *rzn1_adc, unsigned int chan)
|
||||
{
|
||||
/* chan 0..7 use ADC1 ch 0..7. Vref related to ADC1 core */
|
||||
if (chan < 8)
|
||||
return rzn1_adc->adc1_vref_mV;
|
||||
|
||||
/* chan 8..15 use ADC2 ch 0..7. Vref related to ADC2 core */
|
||||
if (chan < 16)
|
||||
return rzn1_adc->adc2_vref_mV;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int rzn1_adc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct rzn1_adc *rzn1_adc = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = rzn1_adc_read_raw_ch(rzn1_adc, chan->channel, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = rzn1_adc_get_vref_mV(rzn1_adc, chan->channel);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
*val2 = 12;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info rzn1_adc_info = {
|
||||
.read_raw = &rzn1_adc_read_raw,
|
||||
};
|
||||
|
||||
static int rzn1_adc_set_iio_dev_channels(struct rzn1_adc *rzn1_adc,
|
||||
struct iio_dev *indio_dev)
|
||||
{
|
||||
/*
|
||||
* When an ADC core is not used, its related vref_mV is set to a
|
||||
* negative error code. Use the correct IIO channels table based on
|
||||
* those vref_mV values.
|
||||
*/
|
||||
if (rzn1_adc->adc1_vref_mV >= 0) {
|
||||
if (rzn1_adc->adc2_vref_mV >= 0) {
|
||||
indio_dev->channels = rzn1_adc1_adc2_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(rzn1_adc1_adc2_channels);
|
||||
} else {
|
||||
indio_dev->channels = rzn1_adc1_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(rzn1_adc1_channels);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rzn1_adc->adc2_vref_mV >= 0) {
|
||||
indio_dev->channels = rzn1_adc2_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(rzn1_adc2_channels);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return dev_err_probe(rzn1_adc->dev, -ENODEV,
|
||||
"Failed to set IIO channels, no ADC core used\n");
|
||||
}
|
||||
|
||||
static int rzn1_adc_core_get_regulators(struct rzn1_adc *rzn1_adc,
|
||||
int *adc_vref_mV,
|
||||
const char *avdd_name, const char *vref_name)
|
||||
{
|
||||
struct device *dev = rzn1_adc->dev;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* For a given ADC core (ADC1 or ADC2), both regulators (AVDD and VREF)
|
||||
* must be available in order to have the ADC core used.
|
||||
*
|
||||
* We use the regulators presence to check the usage of the related
|
||||
* ADC core. If both regulators are available, the ADC core is used.
|
||||
* Otherwise, the ADC core is not used.
|
||||
*
|
||||
* The adc_vref_mV value is set to a negative error code (-ENODEV) when
|
||||
* the ADC core is not used. Otherwise it is set to the VRef mV value.
|
||||
*/
|
||||
|
||||
*adc_vref_mV = -ENODEV;
|
||||
|
||||
ret = devm_regulator_get_enable_optional(dev, avdd_name);
|
||||
if (ret == -ENODEV)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to get '%s' regulator\n",
|
||||
avdd_name);
|
||||
|
||||
ret = devm_regulator_get_enable_read_voltage(dev, vref_name);
|
||||
if (ret == -ENODEV)
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to get '%s' regulator\n",
|
||||
vref_name);
|
||||
|
||||
/*
|
||||
* Both regulators are available.
|
||||
* Set adc_vref_mV to the Vref value in mV. This, as the value set is
|
||||
* positive, also signals that the ADC is used.
|
||||
*/
|
||||
*adc_vref_mV = ret / 1000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rzn1_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct iio_dev *indio_dev;
|
||||
struct rzn1_adc *rzn1_adc;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*rzn1_adc));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
rzn1_adc = iio_priv(indio_dev);
|
||||
rzn1_adc->dev = dev;
|
||||
|
||||
ret = devm_mutex_init(dev, &rzn1_adc->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rzn1_adc->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(rzn1_adc->regs))
|
||||
return PTR_ERR(rzn1_adc->regs);
|
||||
|
||||
clk = devm_clk_get_enabled(dev, "pclk");
|
||||
if (IS_ERR(clk))
|
||||
return dev_err_probe(dev, PTR_ERR(clk), "Failed to get pclk\n");
|
||||
|
||||
clk = devm_clk_get_enabled(dev, "adc");
|
||||
if (IS_ERR(clk))
|
||||
return dev_err_probe(dev, PTR_ERR(clk), "Failed to get adc clk\n");
|
||||
|
||||
ret = rzn1_adc_core_get_regulators(rzn1_adc, &rzn1_adc->adc1_vref_mV,
|
||||
"adc1-avdd", "adc1-vref");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = rzn1_adc_core_get_regulators(rzn1_adc, &rzn1_adc->adc2_vref_mV,
|
||||
"adc2-avdd", "adc2-vref");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
platform_set_drvdata(pdev, rzn1_adc);
|
||||
|
||||
indio_dev->name = "rzn1-adc";
|
||||
indio_dev->info = &rzn1_adc_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
ret = rzn1_adc_set_iio_dev_channels(rzn1_adc, indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pm_runtime_set_autosuspend_delay(dev, 500);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
ret = devm_pm_runtime_enable(dev);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to enable runtime PM\n");
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
static int rzn1_adc_pm_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct rzn1_adc *rzn1_adc = dev_get_drvdata(dev);
|
||||
|
||||
return rzn1_adc_power(rzn1_adc, false);
|
||||
}
|
||||
|
||||
static int rzn1_adc_pm_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct rzn1_adc *rzn1_adc = dev_get_drvdata(dev);
|
||||
|
||||
return rzn1_adc_power(rzn1_adc, true);
|
||||
}
|
||||
|
||||
static DEFINE_RUNTIME_DEV_PM_OPS(rzn1_adc_pm_ops,
|
||||
rzn1_adc_pm_runtime_suspend,
|
||||
rzn1_adc_pm_runtime_resume,
|
||||
NULL);
|
||||
|
||||
static const struct of_device_id rzn1_adc_of_match[] = {
|
||||
{ .compatible = "renesas,rzn1-adc" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rzn1_adc_of_match);
|
||||
|
||||
static struct platform_driver rzn1_adc_driver = {
|
||||
.probe = rzn1_adc_probe,
|
||||
.driver = {
|
||||
.name = "rzn1-adc",
|
||||
.of_match_table = rzn1_adc_of_match,
|
||||
.pm = pm_ptr(&rzn1_adc_pm_ops),
|
||||
},
|
||||
};
|
||||
module_platform_driver(rzn1_adc_driver);
|
||||
|
||||
MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
|
||||
MODULE_DESCRIPTION("Renesas RZ/N1 ADC Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
304
drivers/iio/adc/rzt2h_adc.c
Normal file
304
drivers/iio/adc/rzt2h_adc.c
Normal file
@@ -0,0 +1,304 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iio/adc-helpers.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/property.h>
|
||||
|
||||
#define RZT2H_ADCSR_REG 0x00
|
||||
#define RZT2H_ADCSR_ADIE_MASK BIT(12)
|
||||
#define RZT2H_ADCSR_ADCS_MASK GENMASK(14, 13)
|
||||
#define RZT2H_ADCSR_ADCS_SINGLE 0b00
|
||||
#define RZT2H_ADCSR_ADST_MASK BIT(15)
|
||||
|
||||
#define RZT2H_ADANSA0_REG 0x04
|
||||
#define RZT2H_ADANSA0_CH_MASK(x) BIT(x)
|
||||
|
||||
#define RZT2H_ADDR_REG(x) (0x20 + 0x2 * (x))
|
||||
|
||||
#define RZT2H_ADCALCTL_REG 0x1f0
|
||||
#define RZT2H_ADCALCTL_CAL_MASK BIT(0)
|
||||
#define RZT2H_ADCALCTL_CAL_RDY_MASK BIT(1)
|
||||
#define RZT2H_ADCALCTL_CAL_ERR_MASK BIT(2)
|
||||
|
||||
#define RZT2H_ADC_MAX_CHANNELS 16
|
||||
|
||||
struct rzt2h_adc {
|
||||
void __iomem *base;
|
||||
struct device *dev;
|
||||
|
||||
struct completion completion;
|
||||
/* lock to protect against multiple access to the device */
|
||||
struct mutex lock;
|
||||
|
||||
const struct iio_chan_spec *channels;
|
||||
unsigned int num_channels;
|
||||
unsigned int max_channels;
|
||||
};
|
||||
|
||||
static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion_type)
|
||||
{
|
||||
u16 reg;
|
||||
|
||||
reg = readw(adc->base + RZT2H_ADCSR_REG);
|
||||
|
||||
/* Set conversion type */
|
||||
FIELD_MODIFY(RZT2H_ADCSR_ADCS_MASK, ®, conversion_type);
|
||||
|
||||
/* Set end of conversion interrupt and start bit. */
|
||||
reg |= RZT2H_ADCSR_ADIE_MASK | RZT2H_ADCSR_ADST_MASK;
|
||||
|
||||
writew(reg, adc->base + RZT2H_ADCSR_REG);
|
||||
}
|
||||
|
||||
static void rzt2h_adc_stop(struct rzt2h_adc *adc)
|
||||
{
|
||||
u16 reg;
|
||||
|
||||
reg = readw(adc->base + RZT2H_ADCSR_REG);
|
||||
|
||||
/* Clear end of conversion interrupt and start bit. */
|
||||
reg &= ~(RZT2H_ADCSR_ADIE_MASK | RZT2H_ADCSR_ADST_MASK);
|
||||
|
||||
writew(reg, adc->base + RZT2H_ADCSR_REG);
|
||||
}
|
||||
|
||||
static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_resume_and_get(adc->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&adc->lock);
|
||||
|
||||
reinit_completion(&adc->completion);
|
||||
|
||||
/* Enable a single channel */
|
||||
writew(RZT2H_ADANSA0_CH_MASK(ch), adc->base + RZT2H_ADANSA0_REG);
|
||||
|
||||
rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_SINGLE);
|
||||
|
||||
/*
|
||||
* Datasheet Page 2770, Table 41.1:
|
||||
* 0.32us per channel when sample-and-hold circuits are not in use.
|
||||
*/
|
||||
ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(1));
|
||||
if (!ret) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto disable;
|
||||
}
|
||||
|
||||
*val = readw(adc->base + RZT2H_ADDR_REG(ch));
|
||||
ret = IIO_VAL_INT;
|
||||
|
||||
disable:
|
||||
rzt2h_adc_stop(adc);
|
||||
|
||||
mutex_unlock(&adc->lock);
|
||||
|
||||
pm_runtime_put_autosuspend(adc->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rzt2h_adc_set_cal(struct rzt2h_adc *adc, bool cal)
|
||||
{
|
||||
u16 val;
|
||||
|
||||
val = readw(adc->base + RZT2H_ADCALCTL_REG);
|
||||
if (cal)
|
||||
val |= RZT2H_ADCALCTL_CAL_MASK;
|
||||
else
|
||||
val &= ~RZT2H_ADCALCTL_CAL_MASK;
|
||||
|
||||
writew(val, adc->base + RZT2H_ADCALCTL_REG);
|
||||
}
|
||||
|
||||
static int rzt2h_adc_calibrate(struct rzt2h_adc *adc)
|
||||
{
|
||||
u16 val;
|
||||
int ret;
|
||||
|
||||
rzt2h_adc_set_cal(adc, true);
|
||||
|
||||
ret = read_poll_timeout(readw, val, val & RZT2H_ADCALCTL_CAL_RDY_MASK,
|
||||
200, 1000, true, adc->base + RZT2H_ADCALCTL_REG);
|
||||
if (ret) {
|
||||
dev_err(adc->dev, "Calibration timed out: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rzt2h_adc_set_cal(adc, false);
|
||||
|
||||
if (val & RZT2H_ADCALCTL_CAL_ERR_MASK) {
|
||||
dev_err(adc->dev, "Calibration failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rzt2h_adc_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct rzt2h_adc *adc = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
return rzt2h_adc_read_single(adc, chan->channel, val);
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 1800;
|
||||
*val2 = 12;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_info rzt2h_adc_iio_info = {
|
||||
.read_raw = rzt2h_adc_read_raw,
|
||||
};
|
||||
|
||||
static irqreturn_t rzt2h_adc_isr(int irq, void *private)
|
||||
{
|
||||
struct rzt2h_adc *adc = private;
|
||||
|
||||
complete(&adc->completion);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec rzt2h_adc_chan_template = {
|
||||
.indexed = 1,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
.type = IIO_VOLTAGE,
|
||||
};
|
||||
|
||||
static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc)
|
||||
{
|
||||
struct iio_chan_spec *chan_array;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
ret = devm_iio_adc_device_alloc_chaninfo_se(adc->dev,
|
||||
&rzt2h_adc_chan_template,
|
||||
RZT2H_ADC_MAX_CHANNELS - 1,
|
||||
&chan_array);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(adc->dev, ret, "Failed to read channel info");
|
||||
|
||||
adc->num_channels = ret;
|
||||
adc->channels = chan_array;
|
||||
|
||||
for (i = 0; i < adc->num_channels; i++)
|
||||
if (chan_array[i].channel + 1 > adc->max_channels)
|
||||
adc->max_channels = chan_array[i].channel + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rzt2h_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct iio_dev *indio_dev;
|
||||
struct rzt2h_adc *adc;
|
||||
int ret, irq;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
adc = iio_priv(indio_dev);
|
||||
adc->dev = dev;
|
||||
init_completion(&adc->completion);
|
||||
|
||||
ret = devm_mutex_init(dev, &adc->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
platform_set_drvdata(pdev, adc);
|
||||
|
||||
ret = rzt2h_adc_parse_properties(adc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
adc->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(adc->base))
|
||||
return PTR_ERR(adc->base);
|
||||
|
||||
pm_runtime_set_autosuspend_delay(dev, 300);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
ret = devm_pm_runtime_enable(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
irq = platform_get_irq_byname(pdev, "adi");
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
ret = devm_request_irq(dev, irq, rzt2h_adc_isr, 0, dev_name(dev), adc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
indio_dev->name = "rzt2h-adc";
|
||||
indio_dev->info = &rzt2h_adc_iio_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = adc->channels;
|
||||
indio_dev->num_channels = adc->num_channels;
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id rzt2h_adc_match[] = {
|
||||
{ .compatible = "renesas,r9a09g077-adc" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rzt2h_adc_match);
|
||||
|
||||
static int rzt2h_adc_pm_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct rzt2h_adc *adc = dev_get_drvdata(dev);
|
||||
|
||||
/*
|
||||
* Datasheet Page 2810, Section 41.5.6:
|
||||
* After release from the module-stop state, wait for at least
|
||||
* 0.5 µs before starting A/D conversion.
|
||||
*/
|
||||
fsleep(1);
|
||||
|
||||
return rzt2h_adc_calibrate(adc);
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops rzt2h_adc_pm_ops = {
|
||||
RUNTIME_PM_OPS(NULL, rzt2h_adc_pm_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static struct platform_driver rzt2h_adc_driver = {
|
||||
.probe = rzt2h_adc_probe,
|
||||
.driver = {
|
||||
.name = "rzt2h-adc",
|
||||
.of_match_table = rzt2h_adc_match,
|
||||
.pm = pm_ptr(&rzt2h_adc_pm_ops),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(rzt2h_adc_driver);
|
||||
|
||||
MODULE_AUTHOR("Cosmin Tanislav <cosmin-gabriel.tanislav.xa@renesas.com>");
|
||||
MODULE_DESCRIPTION("Renesas RZ/T2H / RZ/N2H ADC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("IIO_DRIVER");
|
||||
@@ -848,7 +848,7 @@ static int ads131e08_probe(struct spi_device *spi)
|
||||
ret = devm_iio_trigger_register(&spi->dev, st->trig);
|
||||
if (ret) {
|
||||
dev_err(&spi->dev, "failed to register IIO trigger\n");
|
||||
return -ENOMEM;
|
||||
return ret;
|
||||
}
|
||||
|
||||
indio_dev->trig = iio_trigger_get(st->trig);
|
||||
|
||||
@@ -123,7 +123,7 @@ static void tiadc_step_config(struct iio_dev *indio_dev)
|
||||
|
||||
chan = adc_dev->channel_line[i];
|
||||
|
||||
if (adc_dev->step_avg[i])
|
||||
if (adc_dev->step_avg[i] && adc_dev->step_avg[i] <= STEPCONFIG_AVG_16)
|
||||
stepconfig = STEPCONFIG_AVG(ffs(adc_dev->step_avg[i]) - 1) |
|
||||
STEPCONFIG_FIFO1;
|
||||
else
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
struct iio_cb_buffer {
|
||||
struct iio_buffer buffer;
|
||||
/* Must be safe to call from any context (e.g. must not sleep). */
|
||||
int (*cb)(const void *data, void *private);
|
||||
void *private;
|
||||
struct iio_channel *channels;
|
||||
|
||||
@@ -66,10 +66,9 @@ static int scmi_iio_sensor_update_cb(struct notifier_block *nb,
|
||||
/*
|
||||
* Timestamp returned by SCMI is in seconds and is equal to
|
||||
* time * power-of-10 multiplier(tstamp_scale) seconds.
|
||||
* Converting the timestamp to nanoseconds below.
|
||||
* Converting the timestamp to nanoseconds (10⁹) below.
|
||||
*/
|
||||
tstamp_scale = sensor->sensor_info->tstamp_scale +
|
||||
const_ilog2(NSEC_PER_SEC) / const_ilog2(10);
|
||||
tstamp_scale = sensor->sensor_info->tstamp_scale + 9;
|
||||
if (tstamp_scale < 0) {
|
||||
do_div(time, int_pow(10, abs(tstamp_scale)));
|
||||
time_ns = time;
|
||||
|
||||
@@ -97,17 +97,32 @@ config AD5421
|
||||
ad5421.
|
||||
|
||||
config AD5446
|
||||
tristate "Analog Devices AD5446 and similar single channel DACs driver"
|
||||
depends on (SPI_MASTER && I2C!=m) || I2C
|
||||
tristate
|
||||
|
||||
config AD5446_SPI
|
||||
tristate "Analog Devices AD5446 and similar single channel DACs driver (SPI)"
|
||||
depends on SPI
|
||||
select AD5446
|
||||
help
|
||||
Say yes here to build support for Analog Devices AD5300, AD5301, AD5310,
|
||||
AD5311, AD5320, AD5321, AD5444, AD5446, AD5450, AD5451, AD5452, AD5453,
|
||||
AD5512A, AD5541A, AD5542A, AD5543, AD5553, AD5600, AD5601, AD5602, AD5611,
|
||||
AD5612, AD5620, AD5621, AD5622, AD5640, AD5641, AD5660, AD5662 DACs
|
||||
as well as Texas Instruments DAC081S101, DAC101S101, DAC121S101.
|
||||
Say yes here to build support for Analog Devices AD5300, AD5310,
|
||||
AD5320, AD5444, AD5446, AD5450, AD5451, AD5452, AD5453, AD5512A,
|
||||
AD5541A, AD5542A, AD5543, AD5553, AD5600, AD5601, AD5611, AD5620,
|
||||
AD5621, AD5640, AD5641, AD5660, AD5662 DACs as well as
|
||||
Texas Instruments DAC081S101, DAC101S101, DAC121S101.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called ad5446.
|
||||
module will be called ad5446-spi.
|
||||
|
||||
config AD5446_I2C
|
||||
tristate "Analog Devices AD5446 and similar single channel DACs driver (I2C)"
|
||||
depends on I2C
|
||||
select AD5446
|
||||
help
|
||||
Say yes here to build support for Analog Devices AD5301, AD5311, AD5321,
|
||||
AD5602, AD5612, AD5622 DACs.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called ad5446-i2c.
|
||||
|
||||
config AD5449
|
||||
tristate "Analog Devices AD5449 and similar DACs driver"
|
||||
|
||||
@@ -15,6 +15,8 @@ obj-$(CONFIG_AD5624R_SPI) += ad5624r_spi.o
|
||||
obj-$(CONFIG_AD5064) += ad5064.o
|
||||
obj-$(CONFIG_AD5504) += ad5504.o
|
||||
obj-$(CONFIG_AD5446) += ad5446.o
|
||||
obj-$(CONFIG_AD5446_SPI) += ad5446-spi.o
|
||||
obj-$(CONFIG_AD5446_I2C) += ad5446-i2c.o
|
||||
obj-$(CONFIG_AD5449) += ad5449.o
|
||||
obj-$(CONFIG_AD5592R_BASE) += ad5592r-base.o
|
||||
obj-$(CONFIG_AD5592R) += ad5592r.o
|
||||
|
||||
102
drivers/iio/dac/ad5446-i2c.c
Normal file
102
drivers/iio/dac/ad5446-i2c.c
Normal file
@@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* AD5446 SPI I2C driver
|
||||
*
|
||||
* Copyright 2025 Analog Devices Inc.
|
||||
*/
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#include "ad5446.h"
|
||||
|
||||
static int ad5622_write(struct ad5446_state *st, unsigned int val)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(st->dev);
|
||||
int ret;
|
||||
|
||||
st->d16 = cpu_to_be16(val);
|
||||
|
||||
ret = i2c_master_send_dmasafe(client, (char *)&st->d16, sizeof(st->d16));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != sizeof(st->d16))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ad5446_i2c_probe(struct i2c_client *i2c)
|
||||
{
|
||||
const struct i2c_device_id *id = i2c_client_get_device_id(i2c);
|
||||
const struct ad5446_chip_info *chip_info;
|
||||
|
||||
chip_info = i2c_get_match_data(i2c);
|
||||
if (!chip_info)
|
||||
return -ENODEV;
|
||||
|
||||
return ad5446_probe(&i2c->dev, id->name, chip_info);
|
||||
}
|
||||
|
||||
/*
|
||||
* ad5446_supported_i2c_device_ids:
|
||||
* The AD5620/40/60 parts are available in different fixed internal reference
|
||||
* voltage options. The actual part numbers may look differently
|
||||
* (and a bit cryptic), however this style is used to make clear which
|
||||
* parts are supported here.
|
||||
*/
|
||||
|
||||
static const struct ad5446_chip_info ad5602_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4),
|
||||
.write = ad5622_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5612_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2),
|
||||
.write = ad5622_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5622_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0),
|
||||
.write = ad5622_write,
|
||||
};
|
||||
|
||||
static const struct i2c_device_id ad5446_i2c_ids[] = {
|
||||
{"ad5301", (kernel_ulong_t)&ad5602_chip_info},
|
||||
{"ad5311", (kernel_ulong_t)&ad5612_chip_info},
|
||||
{"ad5321", (kernel_ulong_t)&ad5622_chip_info},
|
||||
{"ad5602", (kernel_ulong_t)&ad5602_chip_info},
|
||||
{"ad5612", (kernel_ulong_t)&ad5612_chip_info},
|
||||
{"ad5622", (kernel_ulong_t)&ad5622_chip_info},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ad5446_i2c_ids);
|
||||
|
||||
static const struct of_device_id ad5446_i2c_of_ids[] = {
|
||||
{ .compatible = "adi,ad5301", .data = &ad5602_chip_info },
|
||||
{ .compatible = "adi,ad5311", .data = &ad5612_chip_info },
|
||||
{ .compatible = "adi,ad5321", .data = &ad5622_chip_info },
|
||||
{ .compatible = "adi,ad5602", .data = &ad5602_chip_info },
|
||||
{ .compatible = "adi,ad5612", .data = &ad5612_chip_info },
|
||||
{ .compatible = "adi,ad5622", .data = &ad5622_chip_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(OF, ad5446_i2c_of_ids);
|
||||
|
||||
static struct i2c_driver ad5446_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "ad5446",
|
||||
.of_match_table = ad5446_i2c_of_ids,
|
||||
},
|
||||
.probe = ad5446_i2c_probe,
|
||||
.id_table = ad5446_i2c_ids,
|
||||
};
|
||||
module_i2c_driver(ad5446_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices AD5622 and similar I2C DACs");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("IIO_AD5446");
|
||||
252
drivers/iio/dac/ad5446-spi.c
Normal file
252
drivers/iio/dac/ad5446-spi.c
Normal file
@@ -0,0 +1,252 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* AD5446 SPI DAC driver
|
||||
*
|
||||
* Copyright 2025 Analog Devices Inc.
|
||||
*/
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#include "ad5446.h"
|
||||
|
||||
static int ad5446_write(struct ad5446_state *st, unsigned int val)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(st->dev);
|
||||
|
||||
st->d16 = cpu_to_be16(val);
|
||||
|
||||
return spi_write(spi, &st->d16, sizeof(st->d16));
|
||||
}
|
||||
|
||||
static int ad5660_write(struct ad5446_state *st, unsigned int val)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(st->dev);
|
||||
|
||||
put_unaligned_be24(val, st->d24);
|
||||
|
||||
return spi_write(spi, st->d24, sizeof(st->d24));
|
||||
}
|
||||
|
||||
static int ad5446_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct spi_device_id *id = spi_get_device_id(spi);
|
||||
const struct ad5446_chip_info *chip_info;
|
||||
|
||||
chip_info = spi_get_device_match_data(spi);
|
||||
if (!chip_info)
|
||||
return -ENODEV;
|
||||
|
||||
return ad5446_probe(&spi->dev, id->name, chip_info);
|
||||
}
|
||||
|
||||
/*
|
||||
* ad5446_supported_spi_device_ids:
|
||||
* The AD5620/40/60 parts are available in different fixed internal reference
|
||||
* voltage options. The actual part numbers may look differently
|
||||
* (and a bit cryptic), however this style is used to make clear which
|
||||
* parts are supported here.
|
||||
*/
|
||||
|
||||
static const struct ad5446_chip_info ad5300_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5310_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5320_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5444_chip_info = {
|
||||
.channel = AD5446_CHANNEL(12, 16, 2),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5446_chip_info = {
|
||||
.channel = AD5446_CHANNEL(14, 16, 0),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5450_chip_info = {
|
||||
.channel = AD5446_CHANNEL(8, 16, 6),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5451_chip_info = {
|
||||
.channel = AD5446_CHANNEL(10, 16, 4),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5541a_chip_info = {
|
||||
.channel = AD5446_CHANNEL(16, 16, 0),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5512a_chip_info = {
|
||||
.channel = AD5446_CHANNEL(12, 16, 4),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5553_chip_info = {
|
||||
.channel = AD5446_CHANNEL(14, 16, 0),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5601_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(8, 16, 6),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5611_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(10, 16, 4),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5621_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5641_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0),
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5620_2500_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2),
|
||||
.int_vref_mv = 2500,
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5620_1250_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2),
|
||||
.int_vref_mv = 1250,
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5640_2500_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0),
|
||||
.int_vref_mv = 2500,
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5640_1250_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0),
|
||||
.int_vref_mv = 1250,
|
||||
.write = ad5446_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5660_2500_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0),
|
||||
.int_vref_mv = 2500,
|
||||
.write = ad5660_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5660_1250_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0),
|
||||
.int_vref_mv = 1250,
|
||||
.write = ad5660_write,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5662_chip_info = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0),
|
||||
.write = ad5660_write,
|
||||
};
|
||||
|
||||
static const struct spi_device_id ad5446_spi_ids[] = {
|
||||
{"ad5300", (kernel_ulong_t)&ad5300_chip_info},
|
||||
{"ad5310", (kernel_ulong_t)&ad5310_chip_info},
|
||||
{"ad5320", (kernel_ulong_t)&ad5320_chip_info},
|
||||
{"ad5444", (kernel_ulong_t)&ad5444_chip_info},
|
||||
{"ad5446", (kernel_ulong_t)&ad5446_chip_info},
|
||||
{"ad5450", (kernel_ulong_t)&ad5450_chip_info},
|
||||
{"ad5451", (kernel_ulong_t)&ad5451_chip_info},
|
||||
{"ad5452", (kernel_ulong_t)&ad5444_chip_info}, /* ad5452 is compatible to the ad5444 */
|
||||
{"ad5453", (kernel_ulong_t)&ad5446_chip_info}, /* ad5453 is compatible to the ad5446 */
|
||||
{"ad5512a", (kernel_ulong_t)&ad5512a_chip_info},
|
||||
{"ad5541a", (kernel_ulong_t)&ad5541a_chip_info},
|
||||
{"ad5542", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5542 are compatible */
|
||||
{"ad5542a", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5542a are compatible */
|
||||
{"ad5543", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5543 are compatible */
|
||||
{"ad5553", (kernel_ulong_t)&ad5553_chip_info},
|
||||
{"ad5600", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5600 are compatible */
|
||||
{"ad5601", (kernel_ulong_t)&ad5601_chip_info},
|
||||
{"ad5611", (kernel_ulong_t)&ad5611_chip_info},
|
||||
{"ad5621", (kernel_ulong_t)&ad5621_chip_info},
|
||||
{"ad5641", (kernel_ulong_t)&ad5641_chip_info},
|
||||
{"ad5620-2500", (kernel_ulong_t)&ad5620_2500_chip_info}, /* AD5620/40/60: */
|
||||
/* part numbers may look differently */
|
||||
{"ad5620-1250", (kernel_ulong_t)&ad5620_1250_chip_info},
|
||||
{"ad5640-2500", (kernel_ulong_t)&ad5640_2500_chip_info},
|
||||
{"ad5640-1250", (kernel_ulong_t)&ad5640_1250_chip_info},
|
||||
{"ad5660-2500", (kernel_ulong_t)&ad5660_2500_chip_info},
|
||||
{"ad5660-1250", (kernel_ulong_t)&ad5660_1250_chip_info},
|
||||
{"ad5662", (kernel_ulong_t)&ad5662_chip_info},
|
||||
{"dac081s101", (kernel_ulong_t)&ad5300_chip_info}, /* compatible Texas Instruments chips */
|
||||
{"dac101s101", (kernel_ulong_t)&ad5310_chip_info},
|
||||
{"dac121s101", (kernel_ulong_t)&ad5320_chip_info},
|
||||
{"dac7512", (kernel_ulong_t)&ad5320_chip_info},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ad5446_spi_ids);
|
||||
|
||||
static const struct of_device_id ad5446_of_ids[] = {
|
||||
{ .compatible = "adi,ad5300", .data = &ad5300_chip_info },
|
||||
{ .compatible = "adi,ad5310", .data = &ad5310_chip_info },
|
||||
{ .compatible = "adi,ad5320", .data = &ad5320_chip_info },
|
||||
{ .compatible = "adi,ad5444", .data = &ad5444_chip_info },
|
||||
{ .compatible = "adi,ad5446", .data = &ad5446_chip_info },
|
||||
{ .compatible = "adi,ad5450", .data = &ad5450_chip_info },
|
||||
{ .compatible = "adi,ad5451", .data = &ad5451_chip_info },
|
||||
{ .compatible = "adi,ad5452", .data = &ad5444_chip_info },
|
||||
{ .compatible = "adi,ad5453", .data = &ad5446_chip_info },
|
||||
{ .compatible = "adi,ad5512a", .data = &ad5512a_chip_info },
|
||||
{ .compatible = "adi,ad5541a", .data = &ad5541a_chip_info },
|
||||
{ .compatible = "adi,ad5542", .data = &ad5541a_chip_info },
|
||||
{ .compatible = "adi,ad5542a", .data = &ad5541a_chip_info },
|
||||
{ .compatible = "adi,ad5543", .data = &ad5541a_chip_info },
|
||||
{ .compatible = "adi,ad5553", .data = &ad5553_chip_info },
|
||||
{ .compatible = "adi,ad5600", .data = &ad5541a_chip_info },
|
||||
{ .compatible = "adi,ad5601", .data = &ad5601_chip_info },
|
||||
{ .compatible = "adi,ad5611", .data = &ad5611_chip_info },
|
||||
{ .compatible = "adi,ad5621", .data = &ad5621_chip_info },
|
||||
{ .compatible = "adi,ad5641", .data = &ad5641_chip_info },
|
||||
{ .compatible = "adi,ad5620-2500", .data = &ad5620_2500_chip_info },
|
||||
{ .compatible = "adi,ad5620-1250", .data = &ad5620_1250_chip_info },
|
||||
{ .compatible = "adi,ad5640-2500", .data = &ad5640_2500_chip_info },
|
||||
{ .compatible = "adi,ad5640-1250", .data = &ad5640_1250_chip_info },
|
||||
{ .compatible = "adi,ad5660-2500", .data = &ad5660_2500_chip_info },
|
||||
{ .compatible = "adi,ad5660-1250", .data = &ad5660_1250_chip_info },
|
||||
{ .compatible = "adi,ad5662", .data = &ad5662_chip_info },
|
||||
{ .compatible = "ti,dac081s101", .data = &ad5300_chip_info },
|
||||
{ .compatible = "ti,dac101s101", .data = &ad5310_chip_info },
|
||||
{ .compatible = "ti,dac121s101", .data = &ad5320_chip_info },
|
||||
{ .compatible = "ti,dac7512", .data = &ad5320_chip_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ad5446_of_ids);
|
||||
|
||||
static struct spi_driver ad5446_spi_driver = {
|
||||
.driver = {
|
||||
.name = "ad5446",
|
||||
.of_match_table = ad5446_of_ids,
|
||||
},
|
||||
.probe = ad5446_spi_probe,
|
||||
.id_table = ad5446_spi_ids,
|
||||
};
|
||||
module_spi_driver(ad5446_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices AD5446 and similar SPI DACs");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("IIO_AD5446");
|
||||
@@ -1,73 +1,35 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* AD5446 SPI DAC driver
|
||||
* AD5446 CORE DAC driver
|
||||
*
|
||||
* Copyright 2010 Analog Devices Inc.
|
||||
*/
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/kstrtox.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#include <linux/unaligned.h>
|
||||
#include "ad5446.h"
|
||||
|
||||
#define MODE_PWRDWN_1k 0x1
|
||||
#define MODE_PWRDWN_100k 0x2
|
||||
#define MODE_PWRDWN_TRISTATE 0x3
|
||||
|
||||
/**
|
||||
* struct ad5446_state - driver instance specific data
|
||||
* @dev: this device
|
||||
* @chip_info: chip model specific constants, available modes etc
|
||||
* @vref_mv: actual reference voltage used
|
||||
* @cached_val: store/retrieve values during power down
|
||||
* @pwr_down_mode: power down mode (1k, 100k or tristate)
|
||||
* @pwr_down: true if the device is in power down
|
||||
* @lock: lock to protect the data buffer during write ops
|
||||
*/
|
||||
|
||||
struct ad5446_state {
|
||||
struct device *dev;
|
||||
const struct ad5446_chip_info *chip_info;
|
||||
unsigned short vref_mv;
|
||||
unsigned cached_val;
|
||||
unsigned pwr_down_mode;
|
||||
unsigned pwr_down;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ad5446_chip_info - chip specific information
|
||||
* @channel: channel spec for the DAC
|
||||
* @int_vref_mv: AD5620/40/60: the internal reference voltage
|
||||
* @write: chip specific helper function to write to the register
|
||||
*/
|
||||
|
||||
struct ad5446_chip_info {
|
||||
struct iio_chan_spec channel;
|
||||
u16 int_vref_mv;
|
||||
int (*write)(struct ad5446_state *st, unsigned val);
|
||||
};
|
||||
|
||||
static const char * const ad5446_powerdown_modes[] = {
|
||||
"1kohm_to_gnd", "100kohm_to_gnd", "three_state"
|
||||
};
|
||||
|
||||
static int ad5446_set_powerdown_mode(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan, unsigned int mode)
|
||||
const struct iio_chan_spec *chan,
|
||||
unsigned int mode)
|
||||
{
|
||||
struct ad5446_state *st = iio_priv(indio_dev);
|
||||
|
||||
@@ -77,7 +39,7 @@ static int ad5446_set_powerdown_mode(struct iio_dev *indio_dev,
|
||||
}
|
||||
|
||||
static int ad5446_get_powerdown_mode(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan)
|
||||
const struct iio_chan_spec *chan)
|
||||
{
|
||||
struct ad5446_state *st = iio_priv(indio_dev);
|
||||
|
||||
@@ -92,9 +54,9 @@ static const struct iio_enum ad5446_powerdown_mode_enum = {
|
||||
};
|
||||
|
||||
static ssize_t ad5446_read_dac_powerdown(struct iio_dev *indio_dev,
|
||||
uintptr_t private,
|
||||
const struct iio_chan_spec *chan,
|
||||
char *buf)
|
||||
uintptr_t private,
|
||||
const struct iio_chan_spec *chan,
|
||||
char *buf)
|
||||
{
|
||||
struct ad5446_state *st = iio_priv(indio_dev);
|
||||
|
||||
@@ -102,9 +64,9 @@ static ssize_t ad5446_read_dac_powerdown(struct iio_dev *indio_dev,
|
||||
}
|
||||
|
||||
static ssize_t ad5446_write_dac_powerdown(struct iio_dev *indio_dev,
|
||||
uintptr_t private,
|
||||
const struct iio_chan_spec *chan,
|
||||
const char *buf, size_t len)
|
||||
uintptr_t private,
|
||||
const struct iio_chan_spec *chan,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct ad5446_state *st = iio_priv(indio_dev);
|
||||
unsigned int shift;
|
||||
@@ -116,7 +78,7 @@ static ssize_t ad5446_write_dac_powerdown(struct iio_dev *indio_dev,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&st->lock);
|
||||
guard(mutex)(&st->lock);
|
||||
st->pwr_down = powerdown;
|
||||
|
||||
if (st->pwr_down) {
|
||||
@@ -127,12 +89,13 @@ static ssize_t ad5446_write_dac_powerdown(struct iio_dev *indio_dev,
|
||||
}
|
||||
|
||||
ret = st->chip_info->write(st, val);
|
||||
mutex_unlock(&st->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ret ? ret : len;
|
||||
return len;
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[] = {
|
||||
const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[] = {
|
||||
{
|
||||
.name = "powerdown",
|
||||
.read = ad5446_read_dac_powerdown,
|
||||
@@ -143,28 +106,7 @@ static const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[] = {
|
||||
IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ad5446_powerdown_mode_enum),
|
||||
{ }
|
||||
};
|
||||
|
||||
#define _AD5446_CHANNEL(bits, storage, _shift, ext) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.output = 1, \
|
||||
.channel = 0, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = (bits), \
|
||||
.storagebits = (storage), \
|
||||
.shift = (_shift), \
|
||||
}, \
|
||||
.ext_info = (ext), \
|
||||
}
|
||||
|
||||
#define AD5446_CHANNEL(bits, storage, shift) \
|
||||
_AD5446_CHANNEL(bits, storage, shift, NULL)
|
||||
|
||||
#define AD5446_CHANNEL_POWERDOWN(bits, storage, shift) \
|
||||
_AD5446_CHANNEL(bits, storage, shift, ad5446_ext_info_powerdown)
|
||||
EXPORT_SYMBOL_NS_GPL(ad5446_ext_info_powerdown, "IIO_AD5446");
|
||||
|
||||
static int ad5446_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
@@ -186,32 +128,35 @@ static int ad5446_read_raw(struct iio_dev *indio_dev,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ad5446_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val,
|
||||
int val2,
|
||||
long mask)
|
||||
static int ad5446_write_dac_raw(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
int val)
|
||||
{
|
||||
struct ad5446_state *st = iio_priv(indio_dev);
|
||||
int ret = 0;
|
||||
|
||||
if (val >= (1 << chan->scan_type.realbits) || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
val <<= chan->scan_type.shift;
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
st->cached_val = val;
|
||||
if (st->pwr_down)
|
||||
return 0;
|
||||
|
||||
return st->chip_info->write(st, val);
|
||||
}
|
||||
|
||||
static int ad5446_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int val,
|
||||
int val2, long mask)
|
||||
{
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (val >= (1 << chan->scan_type.realbits) || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
val <<= chan->scan_type.shift;
|
||||
mutex_lock(&st->lock);
|
||||
st->cached_val = val;
|
||||
if (!st->pwr_down)
|
||||
ret = st->chip_info->write(st, val);
|
||||
mutex_unlock(&st->lock);
|
||||
break;
|
||||
return ad5446_write_dac_raw(indio_dev, chan, val);
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_info ad5446_info = {
|
||||
@@ -219,8 +164,8 @@ static const struct iio_info ad5446_info = {
|
||||
.write_raw = ad5446_write_raw,
|
||||
};
|
||||
|
||||
static int ad5446_probe(struct device *dev, const char *name,
|
||||
const struct ad5446_chip_info *chip_info)
|
||||
int ad5446_probe(struct device *dev, const char *name,
|
||||
const struct ad5446_chip_info *chip_info)
|
||||
{
|
||||
struct ad5446_state *st;
|
||||
struct iio_dev *indio_dev;
|
||||
@@ -241,7 +186,9 @@ static int ad5446_probe(struct device *dev, const char *name,
|
||||
indio_dev->channels = &st->chip_info->channel;
|
||||
indio_dev->num_channels = 1;
|
||||
|
||||
mutex_init(&st->lock);
|
||||
ret = devm_mutex_init(dev, &st->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->pwr_down_mode = MODE_PWRDWN_1k;
|
||||
|
||||
@@ -249,354 +196,19 @@ static int ad5446_probe(struct device *dev, const char *name,
|
||||
if (ret < 0 && ret != -ENODEV)
|
||||
return ret;
|
||||
if (ret == -ENODEV) {
|
||||
if (chip_info->int_vref_mv)
|
||||
st->vref_mv = chip_info->int_vref_mv;
|
||||
else
|
||||
dev_warn(dev, "reference voltage unspecified\n");
|
||||
if (!chip_info->int_vref_mv)
|
||||
return dev_err_probe(dev, ret,
|
||||
"reference voltage unspecified\n");
|
||||
|
||||
st->vref_mv = chip_info->int_vref_mv;
|
||||
} else {
|
||||
st->vref_mv = ret / 1000;
|
||||
}
|
||||
|
||||
return devm_iio_device_register(dev, indio_dev);
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SPI_MASTER)
|
||||
|
||||
static int ad5446_write(struct ad5446_state *st, unsigned val)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(st->dev);
|
||||
__be16 data = cpu_to_be16(val);
|
||||
|
||||
return spi_write(spi, &data, sizeof(data));
|
||||
}
|
||||
|
||||
static int ad5660_write(struct ad5446_state *st, unsigned val)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(st->dev);
|
||||
uint8_t data[3];
|
||||
|
||||
put_unaligned_be24(val, &data[0]);
|
||||
|
||||
return spi_write(spi, data, sizeof(data));
|
||||
}
|
||||
|
||||
/*
|
||||
* ad5446_supported_spi_device_ids:
|
||||
* The AD5620/40/60 parts are available in different fixed internal reference
|
||||
* voltage options. The actual part numbers may look differently
|
||||
* (and a bit cryptic), however this style is used to make clear which
|
||||
* parts are supported here.
|
||||
*/
|
||||
enum ad5446_supported_spi_device_ids {
|
||||
ID_AD5300,
|
||||
ID_AD5310,
|
||||
ID_AD5320,
|
||||
ID_AD5444,
|
||||
ID_AD5446,
|
||||
ID_AD5450,
|
||||
ID_AD5451,
|
||||
ID_AD5541A,
|
||||
ID_AD5512A,
|
||||
ID_AD5553,
|
||||
ID_AD5600,
|
||||
ID_AD5601,
|
||||
ID_AD5611,
|
||||
ID_AD5621,
|
||||
ID_AD5641,
|
||||
ID_AD5620_2500,
|
||||
ID_AD5620_1250,
|
||||
ID_AD5640_2500,
|
||||
ID_AD5640_1250,
|
||||
ID_AD5660_2500,
|
||||
ID_AD5660_1250,
|
||||
ID_AD5662,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5446_spi_chip_info[] = {
|
||||
[ID_AD5300] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5310] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5320] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5444] = {
|
||||
.channel = AD5446_CHANNEL(12, 16, 2),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5446] = {
|
||||
.channel = AD5446_CHANNEL(14, 16, 0),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5450] = {
|
||||
.channel = AD5446_CHANNEL(8, 16, 6),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5451] = {
|
||||
.channel = AD5446_CHANNEL(10, 16, 4),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5541A] = {
|
||||
.channel = AD5446_CHANNEL(16, 16, 0),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5512A] = {
|
||||
.channel = AD5446_CHANNEL(12, 16, 4),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5553] = {
|
||||
.channel = AD5446_CHANNEL(14, 16, 0),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5600] = {
|
||||
.channel = AD5446_CHANNEL(16, 16, 0),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5601] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(8, 16, 6),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5611] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(10, 16, 4),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5621] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5641] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0),
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5620_2500] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2),
|
||||
.int_vref_mv = 2500,
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5620_1250] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2),
|
||||
.int_vref_mv = 1250,
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5640_2500] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0),
|
||||
.int_vref_mv = 2500,
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5640_1250] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0),
|
||||
.int_vref_mv = 1250,
|
||||
.write = ad5446_write,
|
||||
},
|
||||
[ID_AD5660_2500] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0),
|
||||
.int_vref_mv = 2500,
|
||||
.write = ad5660_write,
|
||||
},
|
||||
[ID_AD5660_1250] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0),
|
||||
.int_vref_mv = 1250,
|
||||
.write = ad5660_write,
|
||||
},
|
||||
[ID_AD5662] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0),
|
||||
.write = ad5660_write,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct spi_device_id ad5446_spi_ids[] = {
|
||||
{"ad5300", ID_AD5300},
|
||||
{"ad5310", ID_AD5310},
|
||||
{"ad5320", ID_AD5320},
|
||||
{"ad5444", ID_AD5444},
|
||||
{"ad5446", ID_AD5446},
|
||||
{"ad5450", ID_AD5450},
|
||||
{"ad5451", ID_AD5451},
|
||||
{"ad5452", ID_AD5444}, /* ad5452 is compatible to the ad5444 */
|
||||
{"ad5453", ID_AD5446}, /* ad5453 is compatible to the ad5446 */
|
||||
{"ad5512a", ID_AD5512A},
|
||||
{"ad5541a", ID_AD5541A},
|
||||
{"ad5542a", ID_AD5541A}, /* ad5541a and ad5542a are compatible */
|
||||
{"ad5543", ID_AD5541A}, /* ad5541a and ad5543 are compatible */
|
||||
{"ad5553", ID_AD5553},
|
||||
{"ad5600", ID_AD5600},
|
||||
{"ad5601", ID_AD5601},
|
||||
{"ad5611", ID_AD5611},
|
||||
{"ad5621", ID_AD5621},
|
||||
{"ad5641", ID_AD5641},
|
||||
{"ad5620-2500", ID_AD5620_2500}, /* AD5620/40/60: */
|
||||
{"ad5620-1250", ID_AD5620_1250}, /* part numbers may look differently */
|
||||
{"ad5640-2500", ID_AD5640_2500},
|
||||
{"ad5640-1250", ID_AD5640_1250},
|
||||
{"ad5660-2500", ID_AD5660_2500},
|
||||
{"ad5660-1250", ID_AD5660_1250},
|
||||
{"ad5662", ID_AD5662},
|
||||
{"dac081s101", ID_AD5300}, /* compatible Texas Instruments chips */
|
||||
{"dac101s101", ID_AD5310},
|
||||
{"dac121s101", ID_AD5320},
|
||||
{"dac7512", ID_AD5320},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ad5446_spi_ids);
|
||||
|
||||
static const struct of_device_id ad5446_of_ids[] = {
|
||||
{ .compatible = "ti,dac7512" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ad5446_of_ids);
|
||||
|
||||
static int ad5446_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct spi_device_id *id = spi_get_device_id(spi);
|
||||
|
||||
return ad5446_probe(&spi->dev, id->name,
|
||||
&ad5446_spi_chip_info[id->driver_data]);
|
||||
}
|
||||
|
||||
static struct spi_driver ad5446_spi_driver = {
|
||||
.driver = {
|
||||
.name = "ad5446",
|
||||
.of_match_table = ad5446_of_ids,
|
||||
},
|
||||
.probe = ad5446_spi_probe,
|
||||
.id_table = ad5446_spi_ids,
|
||||
};
|
||||
|
||||
static int __init ad5446_spi_register_driver(void)
|
||||
{
|
||||
return spi_register_driver(&ad5446_spi_driver);
|
||||
}
|
||||
|
||||
static void ad5446_spi_unregister_driver(void)
|
||||
{
|
||||
spi_unregister_driver(&ad5446_spi_driver);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline int ad5446_spi_register_driver(void) { return 0; }
|
||||
static inline void ad5446_spi_unregister_driver(void) { }
|
||||
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_I2C)
|
||||
|
||||
static int ad5622_write(struct ad5446_state *st, unsigned val)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(st->dev);
|
||||
__be16 data = cpu_to_be16(val);
|
||||
int ret;
|
||||
|
||||
ret = i2c_master_send(client, (char *)&data, sizeof(data));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret != sizeof(data))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* ad5446_supported_i2c_device_ids:
|
||||
* The AD5620/40/60 parts are available in different fixed internal reference
|
||||
* voltage options. The actual part numbers may look differently
|
||||
* (and a bit cryptic), however this style is used to make clear which
|
||||
* parts are supported here.
|
||||
*/
|
||||
enum ad5446_supported_i2c_device_ids {
|
||||
ID_AD5602,
|
||||
ID_AD5612,
|
||||
ID_AD5622,
|
||||
};
|
||||
|
||||
static const struct ad5446_chip_info ad5446_i2c_chip_info[] = {
|
||||
[ID_AD5602] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4),
|
||||
.write = ad5622_write,
|
||||
},
|
||||
[ID_AD5612] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2),
|
||||
.write = ad5622_write,
|
||||
},
|
||||
[ID_AD5622] = {
|
||||
.channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0),
|
||||
.write = ad5622_write,
|
||||
},
|
||||
};
|
||||
|
||||
static int ad5446_i2c_probe(struct i2c_client *i2c)
|
||||
{
|
||||
const struct i2c_device_id *id = i2c_client_get_device_id(i2c);
|
||||
return ad5446_probe(&i2c->dev, id->name,
|
||||
&ad5446_i2c_chip_info[id->driver_data]);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ad5446_i2c_ids[] = {
|
||||
{"ad5301", ID_AD5602},
|
||||
{"ad5311", ID_AD5612},
|
||||
{"ad5321", ID_AD5622},
|
||||
{"ad5602", ID_AD5602},
|
||||
{"ad5612", ID_AD5612},
|
||||
{"ad5622", ID_AD5622},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ad5446_i2c_ids);
|
||||
|
||||
static struct i2c_driver ad5446_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "ad5446",
|
||||
},
|
||||
.probe = ad5446_i2c_probe,
|
||||
.id_table = ad5446_i2c_ids,
|
||||
};
|
||||
|
||||
static int __init ad5446_i2c_register_driver(void)
|
||||
{
|
||||
return i2c_add_driver(&ad5446_i2c_driver);
|
||||
}
|
||||
|
||||
static void __exit ad5446_i2c_unregister_driver(void)
|
||||
{
|
||||
i2c_del_driver(&ad5446_i2c_driver);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline int ad5446_i2c_register_driver(void) { return 0; }
|
||||
static inline void ad5446_i2c_unregister_driver(void) { }
|
||||
|
||||
#endif
|
||||
|
||||
static int __init ad5446_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ad5446_spi_register_driver();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ad5446_i2c_register_driver();
|
||||
if (ret) {
|
||||
ad5446_spi_unregister_driver();
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(ad5446_init);
|
||||
|
||||
static void __exit ad5446_exit(void)
|
||||
{
|
||||
ad5446_i2c_unregister_driver();
|
||||
ad5446_spi_unregister_driver();
|
||||
}
|
||||
module_exit(ad5446_exit);
|
||||
EXPORT_SYMBOL_NS_GPL(ad5446_probe, "IIO_AD5446");
|
||||
|
||||
MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices AD5444/AD5446 DAC");
|
||||
MODULE_DESCRIPTION("Analog Devices CORE AD5446 DAC and similar devices");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
77
drivers/iio/dac/ad5446.h
Normal file
77
drivers/iio/dac/ad5446.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _LINUX_AD5446_H
|
||||
#define _LINUX_AD5446_H
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
struct device;
|
||||
|
||||
extern const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[];
|
||||
|
||||
#define _AD5446_CHANNEL(bits, storage, _shift, ext) { \
|
||||
.type = IIO_VOLTAGE, \
|
||||
.indexed = 1, \
|
||||
.output = 1, \
|
||||
.channel = 0, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = (bits), \
|
||||
.storagebits = (storage), \
|
||||
.shift = (_shift), \
|
||||
}, \
|
||||
.ext_info = (ext), \
|
||||
}
|
||||
|
||||
#define AD5446_CHANNEL(bits, storage, shift) \
|
||||
_AD5446_CHANNEL(bits, storage, shift, NULL)
|
||||
|
||||
#define AD5446_CHANNEL_POWERDOWN(bits, storage, shift) \
|
||||
_AD5446_CHANNEL(bits, storage, shift, ad5446_ext_info_powerdown)
|
||||
|
||||
/**
|
||||
* struct ad5446_state - driver instance specific data
|
||||
* @dev: this device
|
||||
* @chip_info: chip model specific constants, available modes etc
|
||||
* @vref_mv: actual reference voltage used
|
||||
* @cached_val: store/retrieve values during power down
|
||||
* @pwr_down_mode: power down mode (1k, 100k or tristate)
|
||||
* @pwr_down: true if the device is in power down
|
||||
* @lock: lock to protect the data buffer during write ops
|
||||
*/
|
||||
struct ad5446_state {
|
||||
struct device *dev;
|
||||
const struct ad5446_chip_info *chip_info;
|
||||
unsigned short vref_mv;
|
||||
unsigned int cached_val;
|
||||
unsigned int pwr_down_mode;
|
||||
unsigned int pwr_down;
|
||||
/* mutex to protect device shared data */
|
||||
struct mutex lock;
|
||||
union {
|
||||
__be16 d16;
|
||||
u8 d24[3];
|
||||
} __aligned(IIO_DMA_MINALIGN);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ad5446_chip_info - chip specific information
|
||||
* @channel: channel spec for the DAC
|
||||
* @int_vref_mv: AD5620/40/60: the internal reference voltage
|
||||
* @write: chip specific helper function to write to the register
|
||||
*/
|
||||
struct ad5446_chip_info {
|
||||
struct iio_chan_spec channel;
|
||||
u16 int_vref_mv;
|
||||
int (*write)(struct ad5446_state *st, unsigned int val);
|
||||
};
|
||||
|
||||
int ad5446_probe(struct device *dev, const char *name,
|
||||
const struct ad5446_chip_info *chip_info);
|
||||
|
||||
#endif
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
@@ -208,12 +209,12 @@ static int ltc2688_dac_code_write(struct ltc2688_state *st, u32 chan, u32 input,
|
||||
code = FIELD_PREP(LTC2688_DITHER_RAW_MASK, code);
|
||||
}
|
||||
|
||||
mutex_lock(&st->lock);
|
||||
guard(mutex)(&st->lock);
|
||||
/* select the correct input register to read from */
|
||||
ret = regmap_update_bits(st->regmap, LTC2688_CMD_A_B_SELECT, BIT(chan),
|
||||
input << chan);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* If in dither/toggle mode the dac should be updated by an
|
||||
@@ -224,10 +225,7 @@ static int ltc2688_dac_code_write(struct ltc2688_state *st, u32 chan, u32 input,
|
||||
else
|
||||
reg = LTC2688_CMD_CH_CODE(chan);
|
||||
|
||||
ret = regmap_write(st->regmap, reg, code);
|
||||
out_unlock:
|
||||
mutex_unlock(&st->lock);
|
||||
return ret;
|
||||
return regmap_write(st->regmap, reg, code);
|
||||
}
|
||||
|
||||
static int ltc2688_dac_code_read(struct ltc2688_state *st, u32 chan, u32 input,
|
||||
@@ -236,20 +234,20 @@ static int ltc2688_dac_code_read(struct ltc2688_state *st, u32 chan, u32 input,
|
||||
struct ltc2688_chan *c = &st->channels[chan];
|
||||
int ret;
|
||||
|
||||
mutex_lock(&st->lock);
|
||||
guard(mutex)(&st->lock);
|
||||
ret = regmap_update_bits(st->regmap, LTC2688_CMD_A_B_SELECT, BIT(chan),
|
||||
input << chan);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(st->regmap, LTC2688_CMD_CH_CODE(chan), code);
|
||||
out_unlock:
|
||||
mutex_unlock(&st->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!c->toggle_chan && input == LTC2688_INPUT_B)
|
||||
*code = FIELD_GET(LTC2688_DITHER_RAW_MASK, *code);
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const int ltc2688_raw_range[] = {0, 1, U16_MAX};
|
||||
@@ -359,17 +357,15 @@ static ssize_t ltc2688_dither_toggle_set(struct iio_dev *indio_dev,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&st->lock);
|
||||
guard(mutex)(&st->lock);
|
||||
ret = regmap_update_bits(st->regmap, LTC2688_CMD_TOGGLE_DITHER_EN,
|
||||
BIT(chan->channel), en << chan->channel);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
return ret;
|
||||
|
||||
c->mode = en ? LTC2688_MODE_DITHER_TOGGLE : LTC2688_MODE_DEFAULT;
|
||||
out_unlock:
|
||||
mutex_unlock(&st->lock);
|
||||
|
||||
return ret ?: len;
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t ltc2688_reg_bool_get(struct iio_dev *indio_dev,
|
||||
@@ -953,7 +949,9 @@ static int ltc2688_probe(struct spi_device *spi)
|
||||
|
||||
/* Just write this once. No need to do it in every regmap read. */
|
||||
st->tx_data[3] = LTC2688_CMD_NOOP;
|
||||
mutex_init(&st->lock);
|
||||
ret = devm_mutex_init(dev, &st->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->regmap = devm_regmap_init(dev, <c2688_regmap_bus, st,
|
||||
<c2688_regmap_config);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* Copyright (C) 2015, 2018
|
||||
* Author: Matt Ranostay <matt.ranostay@konsulko.com>
|
||||
*
|
||||
* TODO: enable pulse length controls via device tree properties
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
@@ -18,6 +17,7 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
@@ -52,9 +52,13 @@
|
||||
#define MAX30100_REG_MODE_CONFIG_PWR BIT(7)
|
||||
|
||||
#define MAX30100_REG_SPO2_CONFIG 0x07
|
||||
#define MAX30100_REG_SPO2_CONFIG_PW_MASK GENMASK(1, 0)
|
||||
#define MAX30100_REG_SPO2_CONFIG_200US 0x0
|
||||
#define MAX30100_REG_SPO2_CONFIG_400US 0x1
|
||||
#define MAX30100_REG_SPO2_CONFIG_800US 0x2
|
||||
#define MAX30100_REG_SPO2_CONFIG_1600US 0x3
|
||||
#define MAX30100_REG_SPO2_CONFIG_100HZ BIT(2)
|
||||
#define MAX30100_REG_SPO2_CONFIG_HI_RES_EN BIT(6)
|
||||
#define MAX30100_REG_SPO2_CONFIG_1600US 0x3
|
||||
|
||||
#define MAX30100_REG_LED_CONFIG 0x09
|
||||
#define MAX30100_REG_LED_CONFIG_LED_MASK 0x0f
|
||||
@@ -306,19 +310,47 @@ static int max30100_led_init(struct max30100_data *data)
|
||||
MAX30100_REG_LED_CONFIG_LED_MASK, reg);
|
||||
}
|
||||
|
||||
static int max30100_get_pulse_width(unsigned int pwidth_us)
|
||||
{
|
||||
switch (pwidth_us) {
|
||||
case 200:
|
||||
return MAX30100_REG_SPO2_CONFIG_200US;
|
||||
case 400:
|
||||
return MAX30100_REG_SPO2_CONFIG_400US;
|
||||
case 800:
|
||||
return MAX30100_REG_SPO2_CONFIG_800US;
|
||||
case 1600:
|
||||
return MAX30100_REG_SPO2_CONFIG_1600US;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int max30100_chip_init(struct max30100_data *data)
|
||||
{
|
||||
int ret;
|
||||
int pulse_width;
|
||||
/* set default LED pulse-width to 1600 us */
|
||||
unsigned int pulse_us = 1600;
|
||||
struct device *dev = &data->client->dev;
|
||||
|
||||
/* setup LED current settings */
|
||||
ret = max30100_led_init(data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read LED pulse-width-us from DT */
|
||||
device_property_read_u32(dev, "maxim,pulse-width-us", &pulse_us);
|
||||
|
||||
pulse_width = max30100_get_pulse_width(pulse_us);
|
||||
if (pulse_width < 0)
|
||||
return dev_err_probe(dev, pulse_width, "invalid LED pulse-width %uus\n", pulse_us);
|
||||
|
||||
/* enable hi-res SPO2 readings at 100Hz */
|
||||
ret = regmap_write(data->regmap, MAX30100_REG_SPO2_CONFIG,
|
||||
MAX30100_REG_SPO2_CONFIG_HI_RES_EN |
|
||||
MAX30100_REG_SPO2_CONFIG_100HZ);
|
||||
MAX30100_REG_SPO2_CONFIG_100HZ |
|
||||
FIELD_PREP(MAX30100_REG_SPO2_CONFIG_PW_MASK, pulse_width));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ config KMX61
|
||||
be called kmx61.
|
||||
|
||||
source "drivers/iio/imu/inv_icm42600/Kconfig"
|
||||
source "drivers/iio/imu/inv_icm45600/Kconfig"
|
||||
source "drivers/iio/imu/inv_mpu6050/Kconfig"
|
||||
|
||||
config SMI240
|
||||
@@ -124,6 +125,7 @@ config SMI240
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called smi240.
|
||||
|
||||
source "drivers/iio/imu/smi330/Kconfig"
|
||||
source "drivers/iio/imu/st_lsm6dsx/Kconfig"
|
||||
source "drivers/iio/imu/st_lsm9ds0/Kconfig"
|
||||
|
||||
|
||||
@@ -25,11 +25,13 @@ obj-$(CONFIG_FXOS8700_I2C) += fxos8700_i2c.o
|
||||
obj-$(CONFIG_FXOS8700_SPI) += fxos8700_spi.o
|
||||
|
||||
obj-y += inv_icm42600/
|
||||
obj-y += inv_icm45600/
|
||||
obj-y += inv_mpu6050/
|
||||
|
||||
obj-$(CONFIG_KMX61) += kmx61.o
|
||||
|
||||
obj-$(CONFIG_SMI240) += smi240.o
|
||||
|
||||
obj-y += smi330/
|
||||
obj-y += st_lsm6dsx/
|
||||
obj-y += st_lsm9ds0/
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
|
||||
#define BMI270_INT_STATUS_0_REG 0x1c
|
||||
#define BMI270_INT_STATUS_0_STEP_CNT_MSK BIT(1)
|
||||
#define BMI270_INT_STATUS_0_NOMOTION_MSK BIT(5)
|
||||
#define BMI270_INT_STATUS_0_MOTION_MSK BIT(6)
|
||||
|
||||
#define BMI270_INT_STATUS_1_REG 0x1d
|
||||
#define BMI270_INT_STATUS_1_ACC_GYR_DRDY_MSK GENMASK(7, 6)
|
||||
@@ -81,6 +83,8 @@
|
||||
#define BMI270_INT1_MAP_FEAT_REG 0x56
|
||||
#define BMI270_INT2_MAP_FEAT_REG 0x57
|
||||
#define BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK BIT(1)
|
||||
#define BMI270_INT_MAP_FEAT_NOMOTION_MSK BIT(5)
|
||||
#define BMI270_INT_MAP_FEAT_ANYMOTION_MSK BIT(6)
|
||||
|
||||
#define BMI270_INT_MAP_DATA_REG 0x58
|
||||
#define BMI270_INT_MAP_DATA_DRDY_INT1_MSK BIT(2)
|
||||
@@ -106,6 +110,25 @@
|
||||
#define BMI270_STEP_SC26_RST_CNT_MSK BIT(10)
|
||||
#define BMI270_STEP_SC26_EN_CNT_MSK BIT(12)
|
||||
|
||||
#define BMI270_FEAT_MOTION_DURATION_MSK GENMASK(12, 0)
|
||||
#define BMI270_FEAT_MOTION_X_EN_MSK BIT(13)
|
||||
#define BMI270_FEAT_MOTION_Y_EN_MSK BIT(14)
|
||||
#define BMI270_FEAT_MOTION_Z_EN_MSK BIT(15)
|
||||
#define BMI270_FEAT_MOTION_XYZ_EN_MSK GENMASK(15, 13)
|
||||
#define BMI270_FEAT_MOTION_THRESHOLD_MSK GENMASK(10, 0)
|
||||
#define BMI270_FEAT_MOTION_OUT_CONF_MSK GENMASK(14, 11)
|
||||
#define BMI270_FEAT_MOTION_ENABLE_MSK BIT(15)
|
||||
|
||||
#define BMI270_MOTION_XYZ_MSK GENMASK(2, 0)
|
||||
|
||||
/* See pages 92 and 93 of the datasheet */
|
||||
#define BMI270_MOTION_THRES_FULL_SCALE GENMASK(10, 0)
|
||||
#define BMI270_MOTION_DURAT_SCALE 50
|
||||
#define BMI270_MOTION_DURAT_MAX 162
|
||||
|
||||
/* 9.81 * 1000000 m/s^2 */
|
||||
#define BMI270_G_MICRO_M_S_2 9810000
|
||||
|
||||
/* See datasheet section 4.6.14, Temperature Sensor */
|
||||
#define BMI270_TEMP_OFFSET 11776
|
||||
#define BMI270_TEMP_SCALE 1953125
|
||||
@@ -114,6 +137,11 @@
|
||||
#define BMI270_STEP_COUNTER_FACTOR 20
|
||||
#define BMI270_STEP_COUNTER_MAX 20460
|
||||
|
||||
#define BMI270_INT_MICRO_TO_RAW(val, val2, scale) \
|
||||
((val) * (scale) + ((val2) * (scale)) / MEGA)
|
||||
#define BMI270_RAW_TO_MICRO(raw, scale) \
|
||||
((((raw) % (scale)) * MEGA) / scale)
|
||||
|
||||
#define BMI260_INIT_DATA_FILE "bmi260-init-data.fw"
|
||||
#define BMI270_INIT_DATA_FILE "bmi270-init-data.fw"
|
||||
|
||||
@@ -309,6 +337,13 @@ static const struct bmi270_odr_item bmi270_odr_table[] = {
|
||||
};
|
||||
|
||||
enum bmi270_feature_reg_id {
|
||||
/* Page 1 registers */
|
||||
BMI270_ANYMO1_REG,
|
||||
BMI270_ANYMO2_REG,
|
||||
/* Page 2 registers */
|
||||
BMI270_NOMO1_REG,
|
||||
BMI270_NOMO2_REG,
|
||||
/* Page 6 registers */
|
||||
BMI270_SC_26_REG,
|
||||
};
|
||||
|
||||
@@ -318,6 +353,22 @@ struct bmi270_feature_reg {
|
||||
};
|
||||
|
||||
static const struct bmi270_feature_reg bmi270_feature_regs[] = {
|
||||
[BMI270_ANYMO1_REG] = {
|
||||
.page = 1,
|
||||
.addr = 0x3c,
|
||||
},
|
||||
[BMI270_ANYMO2_REG] = {
|
||||
.page = 1,
|
||||
.addr = 0x3e,
|
||||
},
|
||||
[BMI270_NOMO1_REG] = {
|
||||
.page = 2,
|
||||
.addr = 0x30,
|
||||
},
|
||||
[BMI270_NOMO2_REG] = {
|
||||
.page = 2,
|
||||
.addr = 0x32,
|
||||
},
|
||||
[BMI270_SC_26_REG] = {
|
||||
.page = 6,
|
||||
.addr = 0x32,
|
||||
@@ -439,6 +490,121 @@ static int bmi270_step_wtrmrk_en(struct bmi270_data *data, bool state)
|
||||
state));
|
||||
}
|
||||
|
||||
static int bmi270_motion_reg(enum iio_event_type type, enum iio_event_info info)
|
||||
{
|
||||
switch (info) {
|
||||
case IIO_EV_INFO_PERIOD:
|
||||
switch (type) {
|
||||
case IIO_EV_TYPE_MAG_ADAPTIVE:
|
||||
return BMI270_ANYMO1_REG;
|
||||
case IIO_EV_TYPE_ROC:
|
||||
return BMI270_NOMO1_REG;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
case IIO_EV_INFO_VALUE:
|
||||
switch (type) {
|
||||
case IIO_EV_TYPE_MAG_ADAPTIVE:
|
||||
return BMI270_ANYMO2_REG;
|
||||
case IIO_EV_TYPE_ROC:
|
||||
return BMI270_NOMO2_REG;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int bmi270_anymotion_event_en(struct bmi270_data *data,
|
||||
struct iio_chan_spec const *chan,
|
||||
bool state)
|
||||
{
|
||||
u16 axis_msk, axis_field_val, regval;
|
||||
int ret, irq_reg;
|
||||
bool axis_en;
|
||||
|
||||
irq_reg = bmi270_int_map_reg(data->irq_pin);
|
||||
if (irq_reg < 0)
|
||||
return irq_reg;
|
||||
|
||||
guard(mutex)(&data->mutex);
|
||||
|
||||
ret = bmi270_read_feature_reg(data, BMI270_ANYMO1_REG, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
axis_msk = BMI270_FEAT_MOTION_X_EN_MSK;
|
||||
axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_X_EN_MSK, state);
|
||||
axis_en = FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, regval) |
|
||||
FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, regval);
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
axis_msk = BMI270_FEAT_MOTION_Y_EN_MSK;
|
||||
axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_Y_EN_MSK, state);
|
||||
axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, regval) |
|
||||
FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, regval);
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
axis_msk = BMI270_FEAT_MOTION_Z_EN_MSK;
|
||||
axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_Z_EN_MSK, state);
|
||||
axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, regval) |
|
||||
FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, regval);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = bmi270_update_feature_reg(data, BMI270_ANYMO1_REG, axis_msk,
|
||||
axis_field_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = bmi270_update_feature_reg(data, BMI270_ANYMO2_REG,
|
||||
BMI270_FEAT_MOTION_ENABLE_MSK,
|
||||
FIELD_PREP(BMI270_FEAT_MOTION_ENABLE_MSK,
|
||||
state || axis_en));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return regmap_update_bits(data->regmap, irq_reg,
|
||||
BMI270_INT_MAP_FEAT_ANYMOTION_MSK,
|
||||
FIELD_PREP(BMI270_INT_MAP_FEAT_ANYMOTION_MSK,
|
||||
state || axis_en));
|
||||
}
|
||||
|
||||
static int bmi270_nomotion_event_en(struct bmi270_data *data, bool state)
|
||||
{
|
||||
int ret, irq_reg;
|
||||
|
||||
irq_reg = bmi270_int_map_reg(data->irq_pin);
|
||||
if (irq_reg < 0)
|
||||
return irq_reg;
|
||||
|
||||
guard(mutex)(&data->mutex);
|
||||
|
||||
ret = bmi270_update_feature_reg(data, BMI270_NOMO1_REG,
|
||||
BMI270_FEAT_MOTION_XYZ_EN_MSK,
|
||||
FIELD_PREP(BMI270_FEAT_MOTION_XYZ_EN_MSK,
|
||||
state ? BMI270_MOTION_XYZ_MSK : 0));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = bmi270_update_feature_reg(data, BMI270_NOMO2_REG,
|
||||
BMI270_FEAT_MOTION_ENABLE_MSK,
|
||||
FIELD_PREP(BMI270_FEAT_MOTION_ENABLE_MSK,
|
||||
state));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return regmap_update_bits(data->regmap, irq_reg,
|
||||
BMI270_INT_MAP_FEAT_NOMOTION_MSK,
|
||||
FIELD_PREP(BMI270_INT_MAP_FEAT_NOMOTION_MSK,
|
||||
state));
|
||||
}
|
||||
|
||||
static int bmi270_set_scale(struct bmi270_data *data, int chan_type, int uscale)
|
||||
{
|
||||
int i;
|
||||
@@ -479,8 +645,6 @@ static int bmi270_get_scale(struct bmi270_data *data, int chan_type, int *scale,
|
||||
unsigned int val;
|
||||
struct bmi270_scale_item bmi270_scale_item;
|
||||
|
||||
guard(mutex)(&data->mutex);
|
||||
|
||||
switch (chan_type) {
|
||||
case IIO_ACCEL:
|
||||
ret = regmap_read(data->regmap, BMI270_ACC_CONF_RANGE_REG, &val);
|
||||
@@ -614,6 +778,20 @@ static irqreturn_t bmi270_irq_thread_handler(int irq, void *private)
|
||||
if (FIELD_GET(BMI270_INT_STATUS_1_ACC_GYR_DRDY_MSK, status1))
|
||||
iio_trigger_poll_nested(data->trig);
|
||||
|
||||
if (FIELD_GET(BMI270_INT_STATUS_0_MOTION_MSK, status0))
|
||||
iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
|
||||
IIO_MOD_X_OR_Y_OR_Z,
|
||||
IIO_EV_TYPE_MAG_ADAPTIVE,
|
||||
IIO_EV_DIR_RISING),
|
||||
timestamp);
|
||||
|
||||
if (FIELD_GET(BMI270_INT_STATUS_0_NOMOTION_MSK, status0))
|
||||
iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
|
||||
IIO_MOD_X_AND_Y_AND_Z,
|
||||
IIO_EV_TYPE_ROC,
|
||||
IIO_EV_DIR_RISING),
|
||||
timestamp);
|
||||
|
||||
if (FIELD_GET(BMI270_INT_STATUS_0_STEP_CNT_MSK, status0))
|
||||
iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_STEPS, 0,
|
||||
IIO_EV_TYPE_CHANGE,
|
||||
@@ -827,6 +1005,39 @@ static int bmi270_read_avail(struct iio_dev *indio_dev,
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t in_accel_value_available_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct bmi270_data *data = iio_priv(indio_dev);
|
||||
int ret, scale, uscale;
|
||||
unsigned int step, max;
|
||||
|
||||
ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
max = BMI270_G_MICRO_M_S_2 / uscale;
|
||||
step = max / BMI270_MOTION_THRES_FULL_SCALE;
|
||||
|
||||
return sysfs_emit(buf, "[0 %u %u]\n", step, max);
|
||||
}
|
||||
|
||||
static IIO_DEVICE_ATTR_RO(in_accel_value_available, 0);
|
||||
|
||||
static IIO_CONST_ATTR(in_accel_period_available, "[0.0 0.02 162.0]");
|
||||
|
||||
static struct attribute *bmi270_event_attributes[] = {
|
||||
&iio_dev_attr_in_accel_value_available.dev_attr.attr,
|
||||
&iio_const_attr_in_accel_period_available.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group bmi270_event_attribute_group = {
|
||||
.attrs = bmi270_event_attributes,
|
||||
};
|
||||
|
||||
static int bmi270_write_event_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
@@ -835,6 +1046,10 @@ static int bmi270_write_event_config(struct iio_dev *indio_dev,
|
||||
struct bmi270_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (type) {
|
||||
case IIO_EV_TYPE_MAG_ADAPTIVE:
|
||||
return bmi270_anymotion_event_en(data, chan, state);
|
||||
case IIO_EV_TYPE_ROC:
|
||||
return bmi270_nomotion_event_en(data, state);
|
||||
case IIO_EV_TYPE_CHANGE:
|
||||
return bmi270_step_wtrmrk_en(data, state);
|
||||
default:
|
||||
@@ -848,21 +1063,55 @@ static int bmi270_read_event_config(struct iio_dev *indio_dev,
|
||||
enum iio_event_direction dir)
|
||||
{
|
||||
struct bmi270_data *data = iio_priv(indio_dev);
|
||||
bool feat_en, axis_en;
|
||||
int ret, reg, regval;
|
||||
u16 motion_reg;
|
||||
|
||||
guard(mutex)(&data->mutex);
|
||||
|
||||
reg = bmi270_int_map_reg(data->irq_pin);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
ret = regmap_read(data->regmap, reg, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_STEPS:
|
||||
reg = bmi270_int_map_reg(data->irq_pin);
|
||||
if (reg)
|
||||
return reg;
|
||||
return !!FIELD_GET(BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK, regval);
|
||||
case IIO_ACCEL:
|
||||
switch (type) {
|
||||
case IIO_EV_TYPE_ROC:
|
||||
return !!FIELD_GET(BMI270_INT_MAP_FEAT_NOMOTION_MSK, regval);
|
||||
case IIO_EV_TYPE_MAG_ADAPTIVE:
|
||||
ret = bmi270_read_feature_reg(data, BMI270_ANYMO1_REG,
|
||||
&motion_reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(data->regmap, reg, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
return FIELD_GET(BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK,
|
||||
regval) ? 1 : 0;
|
||||
feat_en = FIELD_GET(BMI270_INT_MAP_FEAT_ANYMOTION_MSK,
|
||||
regval);
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK,
|
||||
motion_reg);
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
axis_en = FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK,
|
||||
motion_reg);
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
axis_en = FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK,
|
||||
motion_reg);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return axis_en && feat_en;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -876,20 +1125,50 @@ static int bmi270_write_event_value(struct iio_dev *indio_dev,
|
||||
int val, int val2)
|
||||
{
|
||||
struct bmi270_data *data = iio_priv(indio_dev);
|
||||
unsigned int raw;
|
||||
unsigned int raw, mask, regval;
|
||||
int ret, reg, scale, uscale;
|
||||
u64 tmp;
|
||||
|
||||
guard(mutex)(&data->mutex);
|
||||
|
||||
switch (type) {
|
||||
case IIO_EV_TYPE_CHANGE:
|
||||
if (type == IIO_EV_TYPE_CHANGE) {
|
||||
if (!in_range(val, 0, BMI270_STEP_COUNTER_MAX + 1))
|
||||
return -EINVAL;
|
||||
|
||||
raw = val / BMI270_STEP_COUNTER_FACTOR;
|
||||
return bmi270_update_feature_reg(data, BMI270_SC_26_REG,
|
||||
BMI270_STEP_SC26_WTRMRK_MSK,
|
||||
FIELD_PREP(BMI270_STEP_SC26_WTRMRK_MSK,
|
||||
raw));
|
||||
mask = BMI270_STEP_SC26_WTRMRK_MSK;
|
||||
regval = FIELD_PREP(BMI270_STEP_SC26_WTRMRK_MSK, raw);
|
||||
return bmi270_update_feature_reg(data, BMI270_SC_26_REG, mask,
|
||||
regval);
|
||||
}
|
||||
|
||||
reg = bmi270_motion_reg(type, info);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
switch (info) {
|
||||
case IIO_EV_INFO_VALUE:
|
||||
ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!in_range(val, 0, (BMI270_G_MICRO_M_S_2 / uscale) + 1))
|
||||
return -EINVAL;
|
||||
|
||||
tmp = (u64)val * BMI270_MOTION_THRES_FULL_SCALE * uscale;
|
||||
raw = DIV_ROUND_CLOSEST_ULL(tmp, BMI270_G_MICRO_M_S_2);
|
||||
mask = BMI270_FEAT_MOTION_THRESHOLD_MSK;
|
||||
regval = FIELD_PREP(BMI270_FEAT_MOTION_THRESHOLD_MSK, raw);
|
||||
return bmi270_update_feature_reg(data, reg, mask, regval);
|
||||
case IIO_EV_INFO_PERIOD:
|
||||
if (!in_range(val, 0, BMI270_MOTION_DURAT_MAX + 1))
|
||||
return -EINVAL;
|
||||
|
||||
raw = BMI270_INT_MICRO_TO_RAW(val, val2,
|
||||
BMI270_MOTION_DURAT_SCALE);
|
||||
mask = BMI270_FEAT_MOTION_DURATION_MSK;
|
||||
regval = FIELD_PREP(BMI270_FEAT_MOTION_DURATION_MSK, raw);
|
||||
return bmi270_update_feature_reg(data, reg, mask, regval);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -903,14 +1182,14 @@ static int bmi270_read_event_value(struct iio_dev *indio_dev,
|
||||
int *val, int *val2)
|
||||
{
|
||||
struct bmi270_data *data = iio_priv(indio_dev);
|
||||
int ret, reg, scale, uscale;
|
||||
unsigned int raw;
|
||||
u16 regval;
|
||||
int ret;
|
||||
u64 tmp;
|
||||
|
||||
guard(mutex)(&data->mutex);
|
||||
|
||||
switch (type) {
|
||||
case IIO_EV_TYPE_CHANGE:
|
||||
if (type == IIO_EV_TYPE_CHANGE) {
|
||||
ret = bmi270_read_feature_reg(data, BMI270_SC_26_REG, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -918,6 +1197,36 @@ static int bmi270_read_event_value(struct iio_dev *indio_dev,
|
||||
raw = FIELD_GET(BMI270_STEP_SC26_WTRMRK_MSK, regval);
|
||||
*val = raw * BMI270_STEP_COUNTER_FACTOR;
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
reg = bmi270_motion_reg(type, info);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
switch (info) {
|
||||
case IIO_EV_INFO_VALUE:
|
||||
ret = bmi270_read_feature_reg(data, reg, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
raw = FIELD_GET(BMI270_FEAT_MOTION_THRESHOLD_MSK, regval);
|
||||
tmp = (u64)raw * BMI270_G_MICRO_M_S_2;
|
||||
*val = DIV_ROUND_CLOSEST_ULL(tmp,
|
||||
BMI270_MOTION_THRES_FULL_SCALE * uscale);
|
||||
return IIO_VAL_INT;
|
||||
case IIO_EV_INFO_PERIOD:
|
||||
ret = bmi270_read_feature_reg(data, reg, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
raw = FIELD_GET(BMI270_FEAT_MOTION_DURATION_MSK, regval);
|
||||
*val = raw / BMI270_MOTION_DURAT_SCALE;
|
||||
*val2 = BMI270_RAW_TO_MICRO(raw, BMI270_MOTION_DURAT_SCALE);
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -929,6 +1238,20 @@ static const struct iio_event_spec bmi270_step_wtrmrk_event = {
|
||||
.mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE),
|
||||
};
|
||||
|
||||
static const struct iio_event_spec bmi270_anymotion_event = {
|
||||
.type = IIO_EV_TYPE_MAG_ADAPTIVE,
|
||||
.dir = IIO_EV_DIR_RISING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
|
||||
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_PERIOD),
|
||||
};
|
||||
|
||||
static const struct iio_event_spec bmi270_nomotion_event = {
|
||||
.type = IIO_EV_TYPE_ROC,
|
||||
.dir = IIO_EV_DIR_RISING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
|
||||
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_PERIOD),
|
||||
};
|
||||
|
||||
static const struct iio_info bmi270_info = {
|
||||
.read_raw = bmi270_read_raw,
|
||||
.write_raw = bmi270_write_raw,
|
||||
@@ -937,6 +1260,7 @@ static const struct iio_info bmi270_info = {
|
||||
.read_event_config = bmi270_read_event_config,
|
||||
.write_event_value = bmi270_write_event_value,
|
||||
.read_event_value = bmi270_read_event_value,
|
||||
.event_attrs = &bmi270_event_attribute_group,
|
||||
};
|
||||
|
||||
#define BMI270_ACCEL_CHANNEL(_axis) { \
|
||||
@@ -956,6 +1280,8 @@ static const struct iio_info bmi270_info = {
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
.event_spec = &bmi270_anymotion_event, \
|
||||
.num_event_specs = 1, \
|
||||
}
|
||||
|
||||
#define BMI270_ANG_VEL_CHANNEL(_axis) { \
|
||||
@@ -1000,6 +1326,14 @@ static const struct iio_chan_spec bmi270_channels[] = {
|
||||
.num_event_specs = 1,
|
||||
},
|
||||
IIO_CHAN_SOFT_TIMESTAMP(BMI270_SCAN_TIMESTAMP),
|
||||
{
|
||||
.type = IIO_ACCEL,
|
||||
.modified = 1,
|
||||
.channel2 = IIO_MOD_X_AND_Y_AND_Z,
|
||||
.scan_index = -1, /* Fake channel */
|
||||
.event_spec = &bmi270_nomotion_event,
|
||||
.num_event_specs = 1,
|
||||
},
|
||||
};
|
||||
|
||||
static int bmi270_int_pin_config(struct bmi270_data *data,
|
||||
@@ -1107,6 +1441,13 @@ static int bmi270_trigger_probe(struct bmi270_data *data,
|
||||
return dev_err_probe(data->dev, ret,
|
||||
"Trigger registration failed\n");
|
||||
|
||||
/* Disable axes for motion events */
|
||||
ret = bmi270_update_feature_reg(data, BMI270_ANYMO1_REG,
|
||||
BMI270_FEAT_MOTION_XYZ_EN_MSK,
|
||||
FIELD_PREP(BMI270_FEAT_MOTION_XYZ_EN_MSK, 0));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data->irq_pin = irq_pin;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -60,7 +60,7 @@ static int bmi270_spi_probe(struct spi_device *spi)
|
||||
&bmi270_spi_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(regmap),
|
||||
"Failed to init i2c regmap");
|
||||
"Failed to init spi regmap\n");
|
||||
|
||||
return bmi270_core_probe(dev, regmap, chip_info);
|
||||
}
|
||||
|
||||
70
drivers/iio/imu/inv_icm45600/Kconfig
Normal file
70
drivers/iio/imu/inv_icm45600/Kconfig
Normal file
@@ -0,0 +1,70 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
config INV_ICM45600
|
||||
tristate
|
||||
select IIO_BUFFER
|
||||
select IIO_KFIFO_BUF
|
||||
select IIO_INV_SENSORS_TIMESTAMP
|
||||
|
||||
config INV_ICM45600_I2C
|
||||
tristate "InvenSense ICM-456xx I2C driver"
|
||||
depends on I2C
|
||||
select INV_ICM45600
|
||||
select REGMAP_I2C
|
||||
help
|
||||
This driver supports the InvenSense ICM-456xx motion tracking
|
||||
devices over I2C.
|
||||
Supported devices:
|
||||
- ICM-45605
|
||||
- ICM-45606
|
||||
- ICM-45608
|
||||
- ICM-45634
|
||||
- ICM-45686
|
||||
- ICM-45687
|
||||
- ICM-45688-P
|
||||
- ICM-45689
|
||||
|
||||
This driver can be built as a module. The module will be called
|
||||
inv-icm45600-i2c.
|
||||
|
||||
config INV_ICM45600_SPI
|
||||
tristate "InvenSense ICM-456xx SPI driver"
|
||||
depends on SPI_MASTER
|
||||
select INV_ICM45600
|
||||
select REGMAP_SPI
|
||||
help
|
||||
This driver supports the InvenSense ICM-456xx motion tracking
|
||||
devices over SPI.
|
||||
Supported devices:
|
||||
- ICM-45605
|
||||
- ICM-45606
|
||||
- ICM-45608
|
||||
- ICM-45634
|
||||
- ICM-45686
|
||||
- ICM-45687
|
||||
- ICM-45688-P
|
||||
- ICM-45689
|
||||
|
||||
This driver can be built as a module. The module will be called
|
||||
inv-icm45600-spi.
|
||||
|
||||
config INV_ICM45600_I3C
|
||||
tristate "InvenSense ICM-456xx I3C driver"
|
||||
depends on I3C
|
||||
select INV_ICM45600
|
||||
select REGMAP_I3C
|
||||
help
|
||||
This driver supports the InvenSense ICM-456xx motion tracking
|
||||
devices over I3C.
|
||||
Supported devices:
|
||||
- ICM-45605
|
||||
- ICM-45606
|
||||
- ICM-45608
|
||||
- ICM-45634
|
||||
- ICM-45686
|
||||
- ICM-45687
|
||||
- ICM-45688-P
|
||||
- ICM-45689
|
||||
|
||||
This driver can be built as a module. The module will be called
|
||||
inv-icm45600-i3c.
|
||||
16
drivers/iio/imu/inv_icm45600/Makefile
Normal file
16
drivers/iio/imu/inv_icm45600/Makefile
Normal file
@@ -0,0 +1,16 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
obj-$(CONFIG_INV_ICM45600) += inv-icm45600.o
|
||||
inv-icm45600-y += inv_icm45600_core.o
|
||||
inv-icm45600-y += inv_icm45600_buffer.o
|
||||
inv-icm45600-y += inv_icm45600_gyro.o
|
||||
inv-icm45600-y += inv_icm45600_accel.o
|
||||
|
||||
obj-$(CONFIG_INV_ICM45600_I2C) += inv-icm45600-i2c.o
|
||||
inv-icm45600-i2c-y += inv_icm45600_i2c.o
|
||||
|
||||
obj-$(CONFIG_INV_ICM45600_SPI) += inv-icm45600-spi.o
|
||||
inv-icm45600-spi-y += inv_icm45600_spi.o
|
||||
|
||||
obj-$(CONFIG_INV_ICM45600_I3C) += inv-icm45600-i3c.o
|
||||
inv-icm45600-i3c-y += inv_icm45600_i3c.o
|
||||
385
drivers/iio/imu/inv_icm45600/inv_icm45600.h
Normal file
385
drivers/iio/imu/inv_icm45600/inv_icm45600.h
Normal file
@@ -0,0 +1,385 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/* Copyright (C) 2025 Invensense, Inc. */
|
||||
|
||||
#ifndef INV_ICM45600_H_
|
||||
#define INV_ICM45600_H_
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/iio/common/inv_sensors_timestamp.h>
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
#include "inv_icm45600_buffer.h"
|
||||
|
||||
#define INV_ICM45600_REG_BANK_MASK GENMASK(15, 8)
|
||||
#define INV_ICM45600_REG_ADDR_MASK GENMASK(7, 0)
|
||||
|
||||
enum inv_icm45600_sensor_mode {
|
||||
INV_ICM45600_SENSOR_MODE_OFF,
|
||||
INV_ICM45600_SENSOR_MODE_STANDBY,
|
||||
INV_ICM45600_SENSOR_MODE_LOW_POWER,
|
||||
INV_ICM45600_SENSOR_MODE_LOW_NOISE,
|
||||
INV_ICM45600_SENSOR_MODE_MAX
|
||||
};
|
||||
|
||||
/* gyroscope fullscale values */
|
||||
enum inv_icm45600_gyro_fs {
|
||||
INV_ICM45600_GYRO_FS_2000DPS,
|
||||
INV_ICM45600_GYRO_FS_1000DPS,
|
||||
INV_ICM45600_GYRO_FS_500DPS,
|
||||
INV_ICM45600_GYRO_FS_250DPS,
|
||||
INV_ICM45600_GYRO_FS_125DPS,
|
||||
INV_ICM45600_GYRO_FS_62_5DPS,
|
||||
INV_ICM45600_GYRO_FS_31_25DPS,
|
||||
INV_ICM45600_GYRO_FS_15_625DPS,
|
||||
INV_ICM45600_GYRO_FS_MAX
|
||||
};
|
||||
|
||||
enum inv_icm45686_gyro_fs {
|
||||
INV_ICM45686_GYRO_FS_4000DPS,
|
||||
INV_ICM45686_GYRO_FS_2000DPS,
|
||||
INV_ICM45686_GYRO_FS_1000DPS,
|
||||
INV_ICM45686_GYRO_FS_500DPS,
|
||||
INV_ICM45686_GYRO_FS_250DPS,
|
||||
INV_ICM45686_GYRO_FS_125DPS,
|
||||
INV_ICM45686_GYRO_FS_62_5DPS,
|
||||
INV_ICM45686_GYRO_FS_31_25DPS,
|
||||
INV_ICM45686_GYRO_FS_15_625DPS,
|
||||
INV_ICM45686_GYRO_FS_MAX
|
||||
};
|
||||
|
||||
/* accelerometer fullscale values */
|
||||
enum inv_icm45600_accel_fs {
|
||||
INV_ICM45600_ACCEL_FS_16G,
|
||||
INV_ICM45600_ACCEL_FS_8G,
|
||||
INV_ICM45600_ACCEL_FS_4G,
|
||||
INV_ICM45600_ACCEL_FS_2G,
|
||||
INV_ICM45600_ACCEL_FS_MAX
|
||||
};
|
||||
|
||||
enum inv_icm45686_accel_fs {
|
||||
INV_ICM45686_ACCEL_FS_32G,
|
||||
INV_ICM45686_ACCEL_FS_16G,
|
||||
INV_ICM45686_ACCEL_FS_8G,
|
||||
INV_ICM45686_ACCEL_FS_4G,
|
||||
INV_ICM45686_ACCEL_FS_2G,
|
||||
INV_ICM45686_ACCEL_FS_MAX
|
||||
};
|
||||
|
||||
/* ODR suffixed by LN or LP are Low-Noise or Low-Power mode only */
|
||||
enum inv_icm45600_odr {
|
||||
INV_ICM45600_ODR_6400HZ_LN = 0x03,
|
||||
INV_ICM45600_ODR_3200HZ_LN,
|
||||
INV_ICM45600_ODR_1600HZ_LN,
|
||||
INV_ICM45600_ODR_800HZ_LN,
|
||||
INV_ICM45600_ODR_400HZ,
|
||||
INV_ICM45600_ODR_200HZ,
|
||||
INV_ICM45600_ODR_100HZ,
|
||||
INV_ICM45600_ODR_50HZ,
|
||||
INV_ICM45600_ODR_25HZ,
|
||||
INV_ICM45600_ODR_12_5HZ,
|
||||
INV_ICM45600_ODR_6_25HZ_LP,
|
||||
INV_ICM45600_ODR_3_125HZ_LP,
|
||||
INV_ICM45600_ODR_1_5625HZ_LP,
|
||||
INV_ICM45600_ODR_MAX
|
||||
};
|
||||
|
||||
struct inv_icm45600_sensor_conf {
|
||||
u8 mode;
|
||||
u8 fs;
|
||||
u8 odr;
|
||||
u8 filter;
|
||||
};
|
||||
|
||||
#define INV_ICM45600_SENSOR_CONF_KEEP_VALUES { U8_MAX, U8_MAX, U8_MAX, U8_MAX }
|
||||
|
||||
struct inv_icm45600_conf {
|
||||
struct inv_icm45600_sensor_conf gyro;
|
||||
struct inv_icm45600_sensor_conf accel;
|
||||
};
|
||||
|
||||
struct inv_icm45600_suspended {
|
||||
enum inv_icm45600_sensor_mode gyro;
|
||||
enum inv_icm45600_sensor_mode accel;
|
||||
};
|
||||
|
||||
struct inv_icm45600_chip_info {
|
||||
u8 whoami;
|
||||
const char *name;
|
||||
const struct inv_icm45600_conf *conf;
|
||||
const int *accel_scales;
|
||||
const int accel_scales_len;
|
||||
const int *gyro_scales;
|
||||
const int gyro_scales_len;
|
||||
};
|
||||
|
||||
extern const struct inv_icm45600_chip_info inv_icm45605_chip_info;
|
||||
extern const struct inv_icm45600_chip_info inv_icm45606_chip_info;
|
||||
extern const struct inv_icm45600_chip_info inv_icm45608_chip_info;
|
||||
extern const struct inv_icm45600_chip_info inv_icm45634_chip_info;
|
||||
extern const struct inv_icm45600_chip_info inv_icm45686_chip_info;
|
||||
extern const struct inv_icm45600_chip_info inv_icm45687_chip_info;
|
||||
extern const struct inv_icm45600_chip_info inv_icm45688p_chip_info;
|
||||
extern const struct inv_icm45600_chip_info inv_icm45689_chip_info;
|
||||
|
||||
extern const int inv_icm45600_accel_scale[][2];
|
||||
extern const int inv_icm45686_accel_scale[][2];
|
||||
extern const int inv_icm45600_gyro_scale[][2];
|
||||
extern const int inv_icm45686_gyro_scale[][2];
|
||||
|
||||
/**
|
||||
* struct inv_icm45600_state - driver state variables
|
||||
* @lock: lock for serializing multiple registers access.
|
||||
* @map: regmap pointer.
|
||||
* @vddio_supply: I/O voltage regulator for the chip.
|
||||
* @orientation: sensor chip orientation relative to main hardware.
|
||||
* @conf: chip sensors configurations.
|
||||
* @suspended: suspended sensors configuration.
|
||||
* @indio_gyro: gyroscope IIO device.
|
||||
* @indio_accel: accelerometer IIO device.
|
||||
* @chip_info: chip driver data.
|
||||
* @timestamp: interrupt timestamps.
|
||||
* @fifo: FIFO management structure.
|
||||
* @buffer: data transfer buffer aligned for DMA.
|
||||
*/
|
||||
struct inv_icm45600_state {
|
||||
struct mutex lock;
|
||||
struct regmap *map;
|
||||
struct regulator *vddio_supply;
|
||||
struct iio_mount_matrix orientation;
|
||||
struct inv_icm45600_conf conf;
|
||||
struct inv_icm45600_suspended suspended;
|
||||
struct iio_dev *indio_gyro;
|
||||
struct iio_dev *indio_accel;
|
||||
const struct inv_icm45600_chip_info *chip_info;
|
||||
struct {
|
||||
s64 gyro;
|
||||
s64 accel;
|
||||
} timestamp;
|
||||
struct inv_icm45600_fifo fifo;
|
||||
union {
|
||||
u8 buff[2];
|
||||
__le16 u16;
|
||||
u8 ireg[3];
|
||||
} buffer __aligned(IIO_DMA_MINALIGN);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct inv_icm45600_sensor_state - sensor state variables
|
||||
* @scales: table of scales.
|
||||
* @scales_len: length (nb of items) of the scales table.
|
||||
* @power_mode: sensor requested power mode (for common frequencies)
|
||||
* @ts: timestamp module states.
|
||||
*/
|
||||
struct inv_icm45600_sensor_state {
|
||||
const int *scales;
|
||||
size_t scales_len;
|
||||
enum inv_icm45600_sensor_mode power_mode;
|
||||
struct inv_sensors_timestamp ts;
|
||||
};
|
||||
|
||||
/* Virtual register addresses: @bank on MSB (16 bits), @address on LSB */
|
||||
|
||||
/* Indirect register access */
|
||||
#define INV_ICM45600_REG_IREG_ADDR 0x7C
|
||||
#define INV_ICM45600_REG_IREG_DATA 0x7E
|
||||
|
||||
/* Direct acces registers */
|
||||
#define INV_ICM45600_REG_MISC2 0x007F
|
||||
#define INV_ICM45600_MISC2_SOFT_RESET BIT(1)
|
||||
|
||||
#define INV_ICM45600_REG_DRIVE_CONFIG0 0x0032
|
||||
#define INV_ICM45600_DRIVE_CONFIG0_SPI_MASK GENMASK(3, 1)
|
||||
#define INV_ICM45600_SPI_SLEW_RATE_0_5NS 6
|
||||
#define INV_ICM45600_SPI_SLEW_RATE_4NS 5
|
||||
#define INV_ICM45600_SPI_SLEW_RATE_5NS 4
|
||||
#define INV_ICM45600_SPI_SLEW_RATE_7NS 3
|
||||
#define INV_ICM45600_SPI_SLEW_RATE_10NS 2
|
||||
#define INV_ICM45600_SPI_SLEW_RATE_14NS 1
|
||||
#define INV_ICM45600_SPI_SLEW_RATE_38NS 0
|
||||
|
||||
#define INV_ICM45600_REG_INT1_CONFIG2 0x0018
|
||||
#define INV_ICM45600_INT1_CONFIG2_PUSH_PULL BIT(2)
|
||||
#define INV_ICM45600_INT1_CONFIG2_LATCHED BIT(1)
|
||||
#define INV_ICM45600_INT1_CONFIG2_ACTIVE_HIGH BIT(0)
|
||||
#define INV_ICM45600_INT1_CONFIG2_ACTIVE_LOW 0x00
|
||||
|
||||
#define INV_ICM45600_REG_FIFO_CONFIG0 0x001D
|
||||
#define INV_ICM45600_FIFO_CONFIG0_MODE_MASK GENMASK(7, 6)
|
||||
#define INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS 0
|
||||
#define INV_ICM45600_FIFO_CONFIG0_MODE_STREAM 1
|
||||
#define INV_ICM45600_FIFO_CONFIG0_MODE_STOP_ON_FULL 2
|
||||
#define INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MASK GENMASK(5, 0)
|
||||
#define INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MAX 0x1F
|
||||
|
||||
#define INV_ICM45600_REG_FIFO_CONFIG2 0x0020
|
||||
#define INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH BIT(7)
|
||||
#define INV_ICM45600_REG_FIFO_CONFIG2_WM_GT_TH BIT(3)
|
||||
|
||||
#define INV_ICM45600_REG_FIFO_CONFIG3 0x0021
|
||||
#define INV_ICM45600_FIFO_CONFIG3_ES1_EN BIT(5)
|
||||
#define INV_ICM45600_FIFO_CONFIG3_ES0_EN BIT(4)
|
||||
#define INV_ICM45600_FIFO_CONFIG3_HIRES_EN BIT(3)
|
||||
#define INV_ICM45600_FIFO_CONFIG3_GYRO_EN BIT(2)
|
||||
#define INV_ICM45600_FIFO_CONFIG3_ACCEL_EN BIT(1)
|
||||
#define INV_ICM45600_FIFO_CONFIG3_IF_EN BIT(0)
|
||||
|
||||
#define INV_ICM45600_REG_FIFO_CONFIG4 0x0022
|
||||
#define INV_ICM45600_FIFO_CONFIG4_COMP_EN BIT(2)
|
||||
#define INV_ICM45600_FIFO_CONFIG4_TMST_FSYNC_EN BIT(1)
|
||||
#define INV_ICM45600_FIFO_CONFIG4_ES0_9B BIT(0)
|
||||
|
||||
/* all sensor data are 16 bits (2 registers wide) in big-endian */
|
||||
#define INV_ICM45600_REG_TEMP_DATA 0x000C
|
||||
#define INV_ICM45600_REG_ACCEL_DATA_X 0x0000
|
||||
#define INV_ICM45600_REG_ACCEL_DATA_Y 0x0002
|
||||
#define INV_ICM45600_REG_ACCEL_DATA_Z 0x0004
|
||||
#define INV_ICM45600_REG_GYRO_DATA_X 0x0006
|
||||
#define INV_ICM45600_REG_GYRO_DATA_Y 0x0008
|
||||
#define INV_ICM45600_REG_GYRO_DATA_Z 0x000A
|
||||
|
||||
#define INV_ICM45600_REG_INT_STATUS 0x0019
|
||||
#define INV_ICM45600_INT_STATUS_RESET_DONE BIT(7)
|
||||
#define INV_ICM45600_INT_STATUS_AUX1_AGC_RDY BIT(6)
|
||||
#define INV_ICM45600_INT_STATUS_AP_AGC_RDY BIT(5)
|
||||
#define INV_ICM45600_INT_STATUS_AP_FSYNC BIT(4)
|
||||
#define INV_ICM45600_INT_STATUS_AUX1_DRDY BIT(3)
|
||||
#define INV_ICM45600_INT_STATUS_DATA_RDY BIT(2)
|
||||
#define INV_ICM45600_INT_STATUS_FIFO_THS BIT(1)
|
||||
#define INV_ICM45600_INT_STATUS_FIFO_FULL BIT(0)
|
||||
|
||||
/*
|
||||
* FIFO access registers
|
||||
* FIFO count is 16 bits (2 registers)
|
||||
* FIFO data is a continuous read register to read FIFO content
|
||||
*/
|
||||
#define INV_ICM45600_REG_FIFO_COUNT 0x0012
|
||||
#define INV_ICM45600_REG_FIFO_DATA 0x0014
|
||||
|
||||
#define INV_ICM45600_REG_PWR_MGMT0 0x0010
|
||||
#define INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK GENMASK(3, 2)
|
||||
#define INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK GENMASK(1, 0)
|
||||
|
||||
#define INV_ICM45600_REG_ACCEL_CONFIG0 0x001B
|
||||
#define INV_ICM45600_ACCEL_CONFIG0_FS_MASK GENMASK(6, 4)
|
||||
#define INV_ICM45600_ACCEL_CONFIG0_ODR_MASK GENMASK(3, 0)
|
||||
#define INV_ICM45600_REG_GYRO_CONFIG0 0x001C
|
||||
#define INV_ICM45600_GYRO_CONFIG0_FS_MASK GENMASK(7, 4)
|
||||
#define INV_ICM45600_GYRO_CONFIG0_ODR_MASK GENMASK(3, 0)
|
||||
|
||||
#define INV_ICM45600_REG_SMC_CONTROL_0 0xA258
|
||||
#define INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL BIT(4)
|
||||
#define INV_ICM45600_SMC_CONTROL_0_TMST_EN BIT(0)
|
||||
|
||||
/* FIFO watermark is 16 bits (2 registers wide) in little-endian */
|
||||
#define INV_ICM45600_REG_FIFO_WATERMARK 0x001E
|
||||
|
||||
/* FIFO is configured for 8kb */
|
||||
#define INV_ICM45600_FIFO_SIZE_MAX SZ_8K
|
||||
|
||||
#define INV_ICM45600_REG_INT1_CONFIG0 0x0016
|
||||
#define INV_ICM45600_INT1_CONFIG0_RESET_DONE_EN BIT(7)
|
||||
#define INV_ICM45600_INT1_CONFIG0_AUX1_AGC_RDY_EN BIT(6)
|
||||
#define INV_ICM45600_INT1_CONFIG0_AP_AGC_RDY_EN BIT(5)
|
||||
#define INV_ICM45600_INT1_CONFIG0_AP_FSYNC_EN BIT(4)
|
||||
#define INV_ICM45600_INT1_CONFIG0_AUX1_DRDY_EN BIT(3)
|
||||
#define INV_ICM45600_INT1_CONFIG0_DRDY_EN BIT(2)
|
||||
#define INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN BIT(1)
|
||||
#define INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN BIT(0)
|
||||
|
||||
#define INV_ICM45600_REG_WHOAMI 0x0072
|
||||
#define INV_ICM45600_WHOAMI_ICM45605 0xE5
|
||||
#define INV_ICM45600_WHOAMI_ICM45686 0xE9
|
||||
#define INV_ICM45600_WHOAMI_ICM45688P 0xE7
|
||||
#define INV_ICM45600_WHOAMI_ICM45608 0x81
|
||||
#define INV_ICM45600_WHOAMI_ICM45634 0x82
|
||||
#define INV_ICM45600_WHOAMI_ICM45689 0x83
|
||||
#define INV_ICM45600_WHOAMI_ICM45606 0x84
|
||||
#define INV_ICM45600_WHOAMI_ICM45687 0x85
|
||||
|
||||
/* Gyro USER offset */
|
||||
#define INV_ICM45600_IPREG_SYS1_REG_42 0xA42A
|
||||
#define INV_ICM45600_IPREG_SYS1_REG_56 0xA438
|
||||
#define INV_ICM45600_IPREG_SYS1_REG_70 0xA446
|
||||
#define INV_ICM45600_GYRO_OFFUSER_MASK GENMASK(13, 0)
|
||||
/* Gyro Averaging filter */
|
||||
#define INV_ICM45600_IPREG_SYS1_REG_170 0xA4AA
|
||||
#define INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK GENMASK(4, 1)
|
||||
#define INV_ICM45600_GYRO_LP_AVG_SEL_8X 5
|
||||
#define INV_ICM45600_GYRO_LP_AVG_SEL_2X 1
|
||||
/* Accel USER offset */
|
||||
#define INV_ICM45600_IPREG_SYS2_REG_24 0xA518
|
||||
#define INV_ICM45600_IPREG_SYS2_REG_32 0xA520
|
||||
#define INV_ICM45600_IPREG_SYS2_REG_40 0xA528
|
||||
#define INV_ICM45600_ACCEL_OFFUSER_MASK GENMASK(13, 0)
|
||||
/* Accel averaging filter */
|
||||
#define INV_ICM45600_IPREG_SYS2_REG_129 0xA581
|
||||
#define INV_ICM45600_ACCEL_LP_AVG_SEL_1X 0x0000
|
||||
#define INV_ICM45600_ACCEL_LP_AVG_SEL_4X 0x0002
|
||||
|
||||
/* Sleep times required by the driver */
|
||||
#define INV_ICM45600_ACCEL_STARTUP_TIME_MS 60
|
||||
#define INV_ICM45600_GYRO_STARTUP_TIME_MS 60
|
||||
#define INV_ICM45600_GYRO_STOP_TIME_MS 150
|
||||
#define INV_ICM45600_IREG_DELAY_US 4
|
||||
|
||||
typedef int (*inv_icm45600_bus_setup)(struct inv_icm45600_state *);
|
||||
|
||||
extern const struct dev_pm_ops inv_icm45600_pm_ops;
|
||||
|
||||
const struct iio_mount_matrix *
|
||||
inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan);
|
||||
|
||||
#define INV_ICM45600_TEMP_CHAN(_index) \
|
||||
{ \
|
||||
.type = IIO_TEMP, \
|
||||
.info_mask_separate = \
|
||||
BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_OFFSET) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.scan_index = _index, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
}
|
||||
|
||||
int inv_icm45600_temp_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask);
|
||||
|
||||
u32 inv_icm45600_odr_to_period(enum inv_icm45600_odr odr);
|
||||
|
||||
int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st,
|
||||
struct inv_icm45600_sensor_conf *conf,
|
||||
unsigned int *sleep_ms);
|
||||
|
||||
int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st,
|
||||
struct inv_icm45600_sensor_conf *conf,
|
||||
unsigned int *sleep_ms);
|
||||
|
||||
int inv_icm45600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
|
||||
unsigned int writeval, unsigned int *readval);
|
||||
|
||||
int inv_icm45600_core_probe(struct regmap *regmap,
|
||||
const struct inv_icm45600_chip_info *chip_info,
|
||||
bool reset, inv_icm45600_bus_setup bus_setup);
|
||||
|
||||
struct iio_dev *inv_icm45600_gyro_init(struct inv_icm45600_state *st);
|
||||
|
||||
int inv_icm45600_gyro_parse_fifo(struct iio_dev *indio_dev);
|
||||
|
||||
struct iio_dev *inv_icm45600_accel_init(struct inv_icm45600_state *st);
|
||||
|
||||
int inv_icm45600_accel_parse_fifo(struct iio_dev *indio_dev);
|
||||
|
||||
#endif
|
||||
782
drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c
Normal file
782
drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c
Normal file
@@ -0,0 +1,782 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2025 Invensense, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/common/inv_sensors_timestamp.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
|
||||
#include "inv_icm45600_buffer.h"
|
||||
#include "inv_icm45600.h"
|
||||
|
||||
enum inv_icm45600_accel_scan {
|
||||
INV_ICM45600_ACCEL_SCAN_X,
|
||||
INV_ICM45600_ACCEL_SCAN_Y,
|
||||
INV_ICM45600_ACCEL_SCAN_Z,
|
||||
INV_ICM45600_ACCEL_SCAN_TEMP,
|
||||
INV_ICM45600_ACCEL_SCAN_TIMESTAMP,
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec_ext_info inv_icm45600_accel_ext_infos[] = {
|
||||
IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm45600_get_mount_matrix),
|
||||
{ }
|
||||
};
|
||||
|
||||
#define INV_ICM45600_ACCEL_CHAN(_modifier, _index, _ext_info) \
|
||||
{ \
|
||||
.type = IIO_ACCEL, \
|
||||
.modified = 1, \
|
||||
.channel2 = _modifier, \
|
||||
.info_mask_separate = \
|
||||
BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_CALIBBIAS), \
|
||||
.info_mask_shared_by_type = \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_shared_by_type_available = \
|
||||
BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_CALIBBIAS), \
|
||||
.info_mask_shared_by_all = \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.info_mask_shared_by_all_available = \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.scan_index = _index, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
.ext_info = _ext_info, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec inv_icm45600_accel_channels[] = {
|
||||
INV_ICM45600_ACCEL_CHAN(IIO_MOD_X, INV_ICM45600_ACCEL_SCAN_X,
|
||||
inv_icm45600_accel_ext_infos),
|
||||
INV_ICM45600_ACCEL_CHAN(IIO_MOD_Y, INV_ICM45600_ACCEL_SCAN_Y,
|
||||
inv_icm45600_accel_ext_infos),
|
||||
INV_ICM45600_ACCEL_CHAN(IIO_MOD_Z, INV_ICM45600_ACCEL_SCAN_Z,
|
||||
inv_icm45600_accel_ext_infos),
|
||||
INV_ICM45600_TEMP_CHAN(INV_ICM45600_ACCEL_SCAN_TEMP),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(INV_ICM45600_ACCEL_SCAN_TIMESTAMP),
|
||||
};
|
||||
|
||||
/*
|
||||
* IIO buffer data: size must be a power of 2 and timestamp aligned
|
||||
* 16 bytes: 6 bytes acceleration, 2 bytes temperature, 8 bytes timestamp
|
||||
*/
|
||||
struct inv_icm45600_accel_buffer {
|
||||
struct inv_icm45600_fifo_sensor_data accel;
|
||||
s16 temp;
|
||||
aligned_s64 timestamp;
|
||||
};
|
||||
|
||||
static const unsigned long inv_icm45600_accel_scan_masks[] = {
|
||||
/* 3-axis accel + temperature */
|
||||
BIT(INV_ICM45600_ACCEL_SCAN_X) |
|
||||
BIT(INV_ICM45600_ACCEL_SCAN_Y) |
|
||||
BIT(INV_ICM45600_ACCEL_SCAN_Z) |
|
||||
BIT(INV_ICM45600_ACCEL_SCAN_TEMP),
|
||||
0
|
||||
};
|
||||
|
||||
/* enable accelerometer sensor and FIFO write */
|
||||
static int inv_icm45600_accel_update_scan_mode(struct iio_dev *indio_dev,
|
||||
const unsigned long *scan_mask)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
|
||||
struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES;
|
||||
unsigned int fifo_en = 0;
|
||||
unsigned int sleep = 0;
|
||||
int ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock) {
|
||||
if (*scan_mask & BIT(INV_ICM45600_ACCEL_SCAN_TEMP))
|
||||
fifo_en |= INV_ICM45600_SENSOR_TEMP;
|
||||
|
||||
if (*scan_mask & (BIT(INV_ICM45600_ACCEL_SCAN_X) |
|
||||
BIT(INV_ICM45600_ACCEL_SCAN_Y) |
|
||||
BIT(INV_ICM45600_ACCEL_SCAN_Z))) {
|
||||
/* enable accel sensor */
|
||||
conf.mode = accel_st->power_mode;
|
||||
ret = inv_icm45600_set_accel_conf(st, &conf, &sleep);
|
||||
if (ret)
|
||||
return ret;
|
||||
fifo_en |= INV_ICM45600_SENSOR_ACCEL;
|
||||
}
|
||||
|
||||
/* Update data FIFO write. */
|
||||
ret = inv_icm45600_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
|
||||
}
|
||||
|
||||
/* Sleep required time. */
|
||||
if (sleep)
|
||||
msleep(sleep);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _inv_icm45600_accel_read_sensor(struct inv_icm45600_state *st,
|
||||
struct inv_icm45600_sensor_state *accel_st,
|
||||
unsigned int reg, int *val)
|
||||
{
|
||||
struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES;
|
||||
int ret;
|
||||
|
||||
/* enable accel sensor */
|
||||
conf.mode = accel_st->power_mode;
|
||||
ret = inv_icm45600_set_accel_conf(st, &conf, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* read accel register data */
|
||||
ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = sign_extend32(le16_to_cpup(&st->buffer.u16), 15);
|
||||
if (*val == INV_ICM45600_DATA_INVALID)
|
||||
return -ENODATA;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int inv_icm45600_accel_read_sensor(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
if (chan->type != IIO_ACCEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
reg = INV_ICM45600_REG_ACCEL_DATA_X;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
reg = INV_ICM45600_REG_ACCEL_DATA_Y;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
reg = INV_ICM45600_REG_ACCEL_DATA_Z;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = _inv_icm45600_accel_read_sensor(st, accel_st, reg, val);
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* IIO format int + nano */
|
||||
const int inv_icm45600_accel_scale[][2] = {
|
||||
/* +/- 16G => 0.004788403 m/s-2 */
|
||||
[INV_ICM45600_ACCEL_FS_16G] = { 0, 4788403 },
|
||||
/* +/- 8G => 0.002394202 m/s-2 */
|
||||
[INV_ICM45600_ACCEL_FS_8G] = { 0, 2394202 },
|
||||
/* +/- 4G => 0.001197101 m/s-2 */
|
||||
[INV_ICM45600_ACCEL_FS_4G] = { 0, 1197101 },
|
||||
/* +/- 2G => 0.000598550 m/s-2 */
|
||||
[INV_ICM45600_ACCEL_FS_2G] = { 0, 598550 },
|
||||
};
|
||||
|
||||
const int inv_icm45686_accel_scale[][2] = {
|
||||
/* +/- 32G => 0.009576806 m/s-2 */
|
||||
[INV_ICM45686_ACCEL_FS_32G] = { 0, 9576806 },
|
||||
/* +/- 16G => 0.004788403 m/s-2 */
|
||||
[INV_ICM45686_ACCEL_FS_16G] = { 0, 4788403 },
|
||||
/* +/- 8G => 0.002394202 m/s-2 */
|
||||
[INV_ICM45686_ACCEL_FS_8G] = { 0, 2394202 },
|
||||
/* +/- 4G => 0.001197101 m/s-2 */
|
||||
[INV_ICM45686_ACCEL_FS_4G] = { 0, 1197101 },
|
||||
/* +/- 2G => 0.000598550 m/s-2 */
|
||||
[INV_ICM45686_ACCEL_FS_2G] = { 0, 598550 },
|
||||
};
|
||||
|
||||
static int inv_icm45600_accel_read_scale(struct iio_dev *indio_dev,
|
||||
int *val, int *val2)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
|
||||
unsigned int idx;
|
||||
|
||||
idx = st->conf.accel.fs;
|
||||
|
||||
/* Full scale register starts at 1 for not High FSR parts */
|
||||
if (accel_st->scales == (const int *)&inv_icm45600_accel_scale)
|
||||
idx--;
|
||||
|
||||
*val = accel_st->scales[2 * idx];
|
||||
*val2 = accel_st->scales[2 * idx + 1];
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
}
|
||||
|
||||
static int inv_icm45600_accel_write_scale(struct iio_dev *indio_dev,
|
||||
int val, int val2)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
unsigned int idx;
|
||||
struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES;
|
||||
int ret;
|
||||
|
||||
for (idx = 0; idx < accel_st->scales_len; idx += 2) {
|
||||
if (val == accel_st->scales[idx] &&
|
||||
val2 == accel_st->scales[idx + 1])
|
||||
break;
|
||||
}
|
||||
if (idx == accel_st->scales_len)
|
||||
return -EINVAL;
|
||||
|
||||
conf.fs = idx / 2;
|
||||
|
||||
/* Full scale register starts at 1 for not High FSR parts */
|
||||
if (accel_st->scales == (const int *)&inv_icm45600_accel_scale)
|
||||
conf.fs++;
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = inv_icm45600_set_accel_conf(st, &conf, NULL);
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* IIO format int + micro */
|
||||
static const int inv_icm45600_accel_odr[] = {
|
||||
1, 562500, /* 1.5625Hz */
|
||||
3, 125000, /* 3.125Hz */
|
||||
6, 250000, /* 6.25Hz */
|
||||
12, 500000, /* 12.5Hz */
|
||||
25, 0, /* 25Hz */
|
||||
50, 0, /* 50Hz */
|
||||
100, 0, /* 100Hz */
|
||||
200, 0, /* 200Hz */
|
||||
400, 0, /* 400Hz */
|
||||
800, 0, /* 800Hz */
|
||||
1600, 0, /* 1.6kHz */
|
||||
3200, 0, /* 3.2kHz */
|
||||
6400, 0, /* 6.4kHz */
|
||||
};
|
||||
|
||||
static const int inv_icm45600_accel_odr_conv[] = {
|
||||
INV_ICM45600_ODR_1_5625HZ_LP,
|
||||
INV_ICM45600_ODR_3_125HZ_LP,
|
||||
INV_ICM45600_ODR_6_25HZ_LP,
|
||||
INV_ICM45600_ODR_12_5HZ,
|
||||
INV_ICM45600_ODR_25HZ,
|
||||
INV_ICM45600_ODR_50HZ,
|
||||
INV_ICM45600_ODR_100HZ,
|
||||
INV_ICM45600_ODR_200HZ,
|
||||
INV_ICM45600_ODR_400HZ,
|
||||
INV_ICM45600_ODR_800HZ_LN,
|
||||
INV_ICM45600_ODR_1600HZ_LN,
|
||||
INV_ICM45600_ODR_3200HZ_LN,
|
||||
INV_ICM45600_ODR_6400HZ_LN,
|
||||
};
|
||||
|
||||
static int inv_icm45600_accel_read_odr(struct inv_icm45600_state *st,
|
||||
int *val, int *val2)
|
||||
{
|
||||
unsigned int odr;
|
||||
unsigned int i;
|
||||
|
||||
odr = st->conf.accel.odr;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(inv_icm45600_accel_odr_conv); ++i) {
|
||||
if (inv_icm45600_accel_odr_conv[i] == odr)
|
||||
break;
|
||||
}
|
||||
if (i >= ARRAY_SIZE(inv_icm45600_accel_odr_conv))
|
||||
return -EINVAL;
|
||||
|
||||
*val = inv_icm45600_accel_odr[2 * i];
|
||||
*val2 = inv_icm45600_accel_odr[2 * i + 1];
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
|
||||
static int _inv_icm45600_accel_write_odr(struct iio_dev *indio_dev, int odr)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
|
||||
struct inv_sensors_timestamp *ts = &accel_st->ts;
|
||||
struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES;
|
||||
int ret;
|
||||
|
||||
conf.odr = odr;
|
||||
ret = inv_sensors_timestamp_update_odr(ts, inv_icm45600_odr_to_period(conf.odr),
|
||||
iio_buffer_enabled(indio_dev));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (st->conf.accel.mode != INV_ICM45600_SENSOR_MODE_OFF)
|
||||
conf.mode = accel_st->power_mode;
|
||||
|
||||
ret = inv_icm45600_set_accel_conf(st, &conf, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
inv_icm45600_buffer_update_fifo_period(st);
|
||||
inv_icm45600_buffer_update_watermark(st);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int inv_icm45600_accel_write_odr(struct iio_dev *indio_dev,
|
||||
int val, int val2)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
unsigned int idx;
|
||||
int odr;
|
||||
int ret;
|
||||
|
||||
for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_accel_odr); idx += 2) {
|
||||
if (val == inv_icm45600_accel_odr[idx] &&
|
||||
val2 == inv_icm45600_accel_odr[idx + 1])
|
||||
break;
|
||||
}
|
||||
if (idx >= ARRAY_SIZE(inv_icm45600_accel_odr))
|
||||
return -EINVAL;
|
||||
|
||||
odr = inv_icm45600_accel_odr_conv[idx / 2];
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = _inv_icm45600_accel_write_odr(indio_dev, odr);
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calibration bias values, IIO range format int + micro.
|
||||
* Value is limited to +/-1g coded on 14 bits signed. Step is 0.125mg.
|
||||
*/
|
||||
static int inv_icm45600_accel_calibbias[] = {
|
||||
-9, 806650, /* min: -9.806650 m/s² */
|
||||
0, 1197, /* step: 0.001197 m/s² */
|
||||
9, 805453, /* max: 9.805453 m/s² */
|
||||
};
|
||||
|
||||
static int inv_icm45600_accel_read_offset(struct inv_icm45600_state *st,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2)
|
||||
{
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
s64 val64;
|
||||
s32 bias;
|
||||
unsigned int reg;
|
||||
s16 offset;
|
||||
int ret;
|
||||
|
||||
if (chan->type != IIO_ACCEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
reg = INV_ICM45600_IPREG_SYS2_REG_24;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
reg = INV_ICM45600_IPREG_SYS2_REG_32;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
reg = INV_ICM45600_IPREG_SYS2_REG_40;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16));
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
offset = le16_to_cpup(&st->buffer.u16) & INV_ICM45600_ACCEL_OFFUSER_MASK;
|
||||
/* 14 bits signed value */
|
||||
offset = sign_extend32(offset, 13);
|
||||
|
||||
/*
|
||||
* convert raw offset to g then to m/s²
|
||||
* 14 bits signed raw step 1/8192g
|
||||
* g to m/s²: 9.806650
|
||||
* result in micro (* 1000000)
|
||||
* (offset * 9806650) / 8192
|
||||
*/
|
||||
val64 = (s64)offset * 9806650LL;
|
||||
/* for rounding, add + or - divisor (8192) divided by 2 */
|
||||
if (val64 >= 0)
|
||||
val64 += 8192LL / 2LL;
|
||||
else
|
||||
val64 -= 8192LL / 2LL;
|
||||
bias = div_s64(val64, 8192L);
|
||||
*val = bias / 1000000L;
|
||||
*val2 = bias % 1000000L;
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
|
||||
static int inv_icm45600_accel_write_offset(struct inv_icm45600_state *st,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2)
|
||||
{
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
s64 val64;
|
||||
s32 min, max;
|
||||
unsigned int reg;
|
||||
s16 offset;
|
||||
int ret;
|
||||
|
||||
if (chan->type != IIO_ACCEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
reg = INV_ICM45600_IPREG_SYS2_REG_24;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
reg = INV_ICM45600_IPREG_SYS2_REG_32;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
reg = INV_ICM45600_IPREG_SYS2_REG_40;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* inv_icm45600_accel_calibbias: min - step - max in micro */
|
||||
min = inv_icm45600_accel_calibbias[0] * 1000000L -
|
||||
inv_icm45600_accel_calibbias[1];
|
||||
max = inv_icm45600_accel_calibbias[4] * 1000000L +
|
||||
inv_icm45600_accel_calibbias[5];
|
||||
val64 = (s64)val * 1000000LL;
|
||||
if (val >= 0)
|
||||
val64 += (s64)val2;
|
||||
else
|
||||
val64 -= (s64)val2;
|
||||
if (val64 < min || val64 > max)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* convert m/s² to g then to raw value
|
||||
* m/s² to g: 1 / 9.806650
|
||||
* g to raw 14 bits signed, step 1/8192g: * 8192
|
||||
* val in micro (1000000)
|
||||
* val * 8192 / (9.806650 * 1000000)
|
||||
*/
|
||||
val64 = val64 * 8192LL;
|
||||
/* for rounding, add + or - divisor (9806650) divided by 2 */
|
||||
if (val64 >= 0)
|
||||
val64 += 9806650 / 2;
|
||||
else
|
||||
val64 -= 9806650 / 2;
|
||||
offset = div_s64(val64, 9806650);
|
||||
|
||||
/* clamp value limited to 14 bits signed */
|
||||
offset = clamp(offset, -8192, 8191);
|
||||
|
||||
st->buffer.u16 = cpu_to_le16(offset & INV_ICM45600_ACCEL_OFFUSER_MASK);
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = regmap_bulk_write(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16));
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int inv_icm45600_accel_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_ACCEL:
|
||||
break;
|
||||
case IIO_TEMP:
|
||||
return inv_icm45600_temp_read_raw(indio_dev, chan, val, val2, mask);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (!iio_device_claim_direct(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = inv_icm45600_accel_read_sensor(indio_dev, chan, val);
|
||||
iio_device_release_direct(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
return inv_icm45600_accel_read_scale(indio_dev, val, val2);
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return inv_icm45600_accel_read_odr(st, val, val2);
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
return inv_icm45600_accel_read_offset(st, chan, val, val2);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int inv_icm45600_accel_read_avail(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
const int **vals,
|
||||
int *type, int *length, long mask)
|
||||
{
|
||||
struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
|
||||
|
||||
if (chan->type != IIO_ACCEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*vals = accel_st->scales;
|
||||
*type = IIO_VAL_INT_PLUS_NANO;
|
||||
*length = accel_st->scales_len;
|
||||
return IIO_AVAIL_LIST;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*vals = inv_icm45600_accel_odr;
|
||||
*type = IIO_VAL_INT_PLUS_MICRO;
|
||||
*length = ARRAY_SIZE(inv_icm45600_accel_odr);
|
||||
return IIO_AVAIL_LIST;
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
*vals = inv_icm45600_accel_calibbias;
|
||||
*type = IIO_VAL_INT_PLUS_MICRO;
|
||||
return IIO_AVAIL_RANGE;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int inv_icm45600_accel_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (chan->type != IIO_ACCEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
if (!iio_device_claim_direct(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = inv_icm45600_accel_write_scale(indio_dev, val, val2);
|
||||
iio_device_release_direct(indio_dev);
|
||||
return ret;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return inv_icm45600_accel_write_odr(indio_dev, val, val2);
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
if (!iio_device_claim_direct(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = inv_icm45600_accel_write_offset(st, chan, val, val2);
|
||||
iio_device_release_direct(indio_dev);
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int inv_icm45600_accel_write_raw_get_fmt(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
long mask)
|
||||
{
|
||||
if (chan->type != IIO_ACCEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int inv_icm45600_accel_hwfifo_set_watermark(struct iio_dev *indio_dev,
|
||||
unsigned int val)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
st->fifo.watermark.accel = val;
|
||||
return inv_icm45600_buffer_update_watermark(st);
|
||||
}
|
||||
|
||||
static int inv_icm45600_accel_hwfifo_flush(struct iio_dev *indio_dev,
|
||||
unsigned int count)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
ret = inv_icm45600_buffer_hwfifo_flush(st, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return st->fifo.nb.accel;
|
||||
}
|
||||
|
||||
static const struct iio_info inv_icm45600_accel_info = {
|
||||
.read_raw = inv_icm45600_accel_read_raw,
|
||||
.read_avail = inv_icm45600_accel_read_avail,
|
||||
.write_raw = inv_icm45600_accel_write_raw,
|
||||
.write_raw_get_fmt = inv_icm45600_accel_write_raw_get_fmt,
|
||||
.debugfs_reg_access = inv_icm45600_debugfs_reg,
|
||||
.update_scan_mode = inv_icm45600_accel_update_scan_mode,
|
||||
.hwfifo_set_watermark = inv_icm45600_accel_hwfifo_set_watermark,
|
||||
.hwfifo_flush_to_buffer = inv_icm45600_accel_hwfifo_flush,
|
||||
};
|
||||
|
||||
struct iio_dev *inv_icm45600_accel_init(struct inv_icm45600_state *st)
|
||||
{
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
struct inv_icm45600_sensor_state *accel_st;
|
||||
struct inv_sensors_timestamp_chip ts_chip;
|
||||
struct iio_dev *indio_dev;
|
||||
const char *name;
|
||||
int ret;
|
||||
|
||||
name = devm_kasprintf(dev, GFP_KERNEL, "%s-accel", st->chip_info->name);
|
||||
if (!name)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*accel_st));
|
||||
if (!indio_dev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
accel_st = iio_priv(indio_dev);
|
||||
|
||||
accel_st->scales = st->chip_info->accel_scales;
|
||||
accel_st->scales_len = st->chip_info->accel_scales_len * 2;
|
||||
|
||||
/* low-power (LP) mode by default at init, no ULP mode */
|
||||
accel_st->power_mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
|
||||
ret = regmap_set_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0,
|
||||
INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
/*
|
||||
* clock period is 32kHz (31250ns)
|
||||
* jitter is +/- 2% (20 per mille)
|
||||
*/
|
||||
ts_chip.clock_period = 31250;
|
||||
ts_chip.jitter = 20;
|
||||
ts_chip.init_period = inv_icm45600_odr_to_period(st->conf.accel.odr);
|
||||
inv_sensors_timestamp_init(&accel_st->ts, &ts_chip);
|
||||
|
||||
iio_device_set_drvdata(indio_dev, st);
|
||||
indio_dev->name = name;
|
||||
indio_dev->info = &inv_icm45600_accel_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = inv_icm45600_accel_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(inv_icm45600_accel_channels);
|
||||
indio_dev->available_scan_masks = inv_icm45600_accel_scan_masks;
|
||||
|
||||
ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
|
||||
&inv_icm45600_buffer_ops);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
ret = devm_iio_device_register(dev, indio_dev);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
return indio_dev;
|
||||
}
|
||||
|
||||
int inv_icm45600_accel_parse_fifo(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev);
|
||||
struct inv_sensors_timestamp *ts = &accel_st->ts;
|
||||
ssize_t i, size;
|
||||
unsigned int no;
|
||||
|
||||
/* parse all fifo packets */
|
||||
for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) {
|
||||
struct inv_icm45600_accel_buffer buffer = { };
|
||||
const struct inv_icm45600_fifo_sensor_data *accel, *gyro;
|
||||
const __le16 *timestamp;
|
||||
const s8 *temp;
|
||||
unsigned int odr;
|
||||
s64 ts_val;
|
||||
|
||||
size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i],
|
||||
&accel, &gyro, &temp, ×tamp, &odr);
|
||||
/* quit if error or FIFO is empty */
|
||||
if (size <= 0)
|
||||
return size;
|
||||
|
||||
/* skip packet if no accel data or data is invalid */
|
||||
if (accel == NULL || !inv_icm45600_fifo_is_data_valid(accel))
|
||||
continue;
|
||||
|
||||
/* update odr */
|
||||
if (odr & INV_ICM45600_SENSOR_ACCEL)
|
||||
inv_sensors_timestamp_apply_odr(ts, st->fifo.period,
|
||||
st->fifo.nb.total, no);
|
||||
|
||||
memcpy(&buffer.accel, accel, sizeof(buffer.accel));
|
||||
/* convert 8 bits FIFO temperature in high resolution format */
|
||||
buffer.temp = temp ? (*temp * 64) : 0;
|
||||
ts_val = inv_sensors_timestamp_pop(ts);
|
||||
iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), ts_val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
558
drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c
Normal file
558
drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c
Normal file
@@ -0,0 +1,558 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/* Copyright (C) 2025 Invensense, Inc. */
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/common/inv_sensors_timestamp.h>
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
#include "inv_icm45600_buffer.h"
|
||||
#include "inv_icm45600.h"
|
||||
|
||||
/* FIFO header: 1 byte */
|
||||
#define INV_ICM45600_FIFO_EXT_HEADER BIT(7)
|
||||
#define INV_ICM45600_FIFO_HEADER_ACCEL BIT(6)
|
||||
#define INV_ICM45600_FIFO_HEADER_GYRO BIT(5)
|
||||
#define INV_ICM45600_FIFO_HEADER_HIGH_RES BIT(4)
|
||||
#define INV_ICM45600_FIFO_HEADER_TMST_FSYNC GENMASK(3, 2)
|
||||
#define INV_ICM45600_FIFO_HEADER_ODR_ACCEL BIT(1)
|
||||
#define INV_ICM45600_FIFO_HEADER_ODR_GYRO BIT(0)
|
||||
|
||||
struct inv_icm45600_fifo_1sensor_packet {
|
||||
u8 header;
|
||||
struct inv_icm45600_fifo_sensor_data data;
|
||||
s8 temp;
|
||||
} __packed;
|
||||
|
||||
struct inv_icm45600_fifo_2sensors_packet {
|
||||
u8 header;
|
||||
struct inv_icm45600_fifo_sensor_data accel;
|
||||
struct inv_icm45600_fifo_sensor_data gyro;
|
||||
s8 temp;
|
||||
__le16 timestamp;
|
||||
} __packed;
|
||||
|
||||
ssize_t inv_icm45600_fifo_decode_packet(const void *packet,
|
||||
const struct inv_icm45600_fifo_sensor_data **accel,
|
||||
const struct inv_icm45600_fifo_sensor_data **gyro,
|
||||
const s8 **temp,
|
||||
const __le16 **timestamp, unsigned int *odr)
|
||||
{
|
||||
const struct inv_icm45600_fifo_1sensor_packet *pack1 = packet;
|
||||
const struct inv_icm45600_fifo_2sensors_packet *pack2 = packet;
|
||||
u8 header = *((const u8 *)packet);
|
||||
|
||||
/* FIFO extended header */
|
||||
if (header & INV_ICM45600_FIFO_EXT_HEADER) {
|
||||
/* Not yet supported */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* handle odr flags. */
|
||||
*odr = 0;
|
||||
if (header & INV_ICM45600_FIFO_HEADER_ODR_GYRO)
|
||||
*odr |= INV_ICM45600_SENSOR_GYRO;
|
||||
if (header & INV_ICM45600_FIFO_HEADER_ODR_ACCEL)
|
||||
*odr |= INV_ICM45600_SENSOR_ACCEL;
|
||||
|
||||
/* Accel + Gyro data are present. */
|
||||
if ((header & INV_ICM45600_FIFO_HEADER_ACCEL) &&
|
||||
(header & INV_ICM45600_FIFO_HEADER_GYRO)) {
|
||||
*accel = &pack2->accel;
|
||||
*gyro = &pack2->gyro;
|
||||
*temp = &pack2->temp;
|
||||
*timestamp = &pack2->timestamp;
|
||||
return sizeof(*pack2);
|
||||
}
|
||||
|
||||
/* Accel data only. */
|
||||
if (header & INV_ICM45600_FIFO_HEADER_ACCEL) {
|
||||
*accel = &pack1->data;
|
||||
*gyro = NULL;
|
||||
*temp = &pack1->temp;
|
||||
*timestamp = NULL;
|
||||
return sizeof(*pack1);
|
||||
}
|
||||
|
||||
/* Gyro data only. */
|
||||
if (header & INV_ICM45600_FIFO_HEADER_GYRO) {
|
||||
*accel = NULL;
|
||||
*gyro = &pack1->data;
|
||||
*temp = &pack1->temp;
|
||||
*timestamp = NULL;
|
||||
return sizeof(*pack1);
|
||||
}
|
||||
|
||||
/* Invalid packet if here. */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
void inv_icm45600_buffer_update_fifo_period(struct inv_icm45600_state *st)
|
||||
{
|
||||
u32 period_gyro, period_accel;
|
||||
|
||||
if (st->fifo.en & INV_ICM45600_SENSOR_GYRO)
|
||||
period_gyro = inv_icm45600_odr_to_period(st->conf.gyro.odr);
|
||||
else
|
||||
period_gyro = U32_MAX;
|
||||
|
||||
if (st->fifo.en & INV_ICM45600_SENSOR_ACCEL)
|
||||
period_accel = inv_icm45600_odr_to_period(st->conf.accel.odr);
|
||||
else
|
||||
period_accel = U32_MAX;
|
||||
|
||||
st->fifo.period = min(period_gyro, period_accel);
|
||||
}
|
||||
|
||||
int inv_icm45600_buffer_set_fifo_en(struct inv_icm45600_state *st,
|
||||
unsigned int fifo_en)
|
||||
{
|
||||
unsigned int mask;
|
||||
int ret;
|
||||
|
||||
mask = INV_ICM45600_FIFO_CONFIG3_GYRO_EN |
|
||||
INV_ICM45600_FIFO_CONFIG3_ACCEL_EN;
|
||||
|
||||
ret = regmap_assign_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, mask,
|
||||
(fifo_en & INV_ICM45600_SENSOR_GYRO) ||
|
||||
(fifo_en & INV_ICM45600_SENSOR_ACCEL));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->fifo.en = fifo_en;
|
||||
inv_icm45600_buffer_update_fifo_period(st);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int inv_icm45600_wm_truncate(unsigned int watermark, size_t packet_size,
|
||||
unsigned int fifo_period)
|
||||
{
|
||||
size_t watermark_max, grace_samples;
|
||||
|
||||
/* Keep 20ms for processing FIFO.*/
|
||||
grace_samples = (20U * NSEC_PER_MSEC) / fifo_period;
|
||||
if (grace_samples < 1)
|
||||
grace_samples = 1;
|
||||
|
||||
watermark_max = INV_ICM45600_FIFO_SIZE_MAX / packet_size;
|
||||
watermark_max -= grace_samples;
|
||||
|
||||
return min(watermark, watermark_max);
|
||||
}
|
||||
|
||||
/**
|
||||
* inv_icm45600_buffer_update_watermark - update watermark FIFO threshold
|
||||
* @st: driver internal state
|
||||
*
|
||||
* FIFO watermark threshold is computed based on the required watermark values
|
||||
* set for gyro and accel sensors. Since watermark is all about acceptable data
|
||||
* latency, use the smallest setting between the 2. It means choosing the
|
||||
* smallest latency but this is not as simple as choosing the smallest watermark
|
||||
* value. Latency depends on watermark and ODR. It requires several steps:
|
||||
* 1) compute gyro and accel latencies and choose the smallest value.
|
||||
* 2) adapt the chosen latency so that it is a multiple of both gyro and accel
|
||||
* ones. Otherwise it is possible that you don't meet a requirement. (for
|
||||
* example with gyro @100Hz wm 4 and accel @100Hz with wm 6, choosing the
|
||||
* value of 4 will not meet accel latency requirement because 6 is not a
|
||||
* multiple of 4. You need to use the value 2.)
|
||||
* 3) Since all periods are multiple of each others, watermark is computed by
|
||||
* dividing this computed latency by the smallest period, which corresponds
|
||||
* to the FIFO frequency.
|
||||
*
|
||||
* Returns: 0 on success, a negative error code otherwise.
|
||||
*/
|
||||
int inv_icm45600_buffer_update_watermark(struct inv_icm45600_state *st)
|
||||
{
|
||||
const size_t packet_size = sizeof(struct inv_icm45600_fifo_2sensors_packet);
|
||||
unsigned int wm_gyro, wm_accel, watermark;
|
||||
u32 period_gyro, period_accel, period;
|
||||
u32 latency_gyro, latency_accel, latency;
|
||||
|
||||
/* Compute sensors latency, depending on sensor watermark and odr. */
|
||||
wm_gyro = inv_icm45600_wm_truncate(st->fifo.watermark.gyro, packet_size,
|
||||
st->fifo.period);
|
||||
wm_accel = inv_icm45600_wm_truncate(st->fifo.watermark.accel, packet_size,
|
||||
st->fifo.period);
|
||||
/* Use us for odr to avoid overflow using 32 bits values. */
|
||||
period_gyro = inv_icm45600_odr_to_period(st->conf.gyro.odr) / NSEC_PER_USEC;
|
||||
period_accel = inv_icm45600_odr_to_period(st->conf.accel.odr) / NSEC_PER_USEC;
|
||||
latency_gyro = period_gyro * wm_gyro;
|
||||
latency_accel = period_accel * wm_accel;
|
||||
|
||||
/* 0 value for watermark means that the sensor is turned off. */
|
||||
if (wm_gyro == 0 && wm_accel == 0)
|
||||
return 0;
|
||||
|
||||
if (latency_gyro == 0) {
|
||||
watermark = wm_accel;
|
||||
st->fifo.watermark.eff_accel = wm_accel;
|
||||
} else if (latency_accel == 0) {
|
||||
watermark = wm_gyro;
|
||||
st->fifo.watermark.eff_gyro = wm_gyro;
|
||||
} else {
|
||||
/* Compute the smallest latency that is a multiple of both. */
|
||||
if (latency_gyro <= latency_accel)
|
||||
latency = latency_gyro - (latency_accel % latency_gyro);
|
||||
else
|
||||
latency = latency_accel - (latency_gyro % latency_accel);
|
||||
/* Use the shortest period. */
|
||||
period = min(period_gyro, period_accel);
|
||||
/* All this works because periods are multiple of each others. */
|
||||
watermark = max(latency / period, 1);
|
||||
/* Update effective watermark. */
|
||||
st->fifo.watermark.eff_gyro = max(latency / period_gyro, 1);
|
||||
st->fifo.watermark.eff_accel = max(latency / period_accel, 1);
|
||||
}
|
||||
|
||||
st->buffer.u16 = cpu_to_le16(watermark);
|
||||
return regmap_bulk_write(st->map, INV_ICM45600_REG_FIFO_WATERMARK,
|
||||
&st->buffer.u16, sizeof(st->buffer.u16));
|
||||
}
|
||||
|
||||
static int inv_icm45600_buffer_preenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
struct inv_icm45600_sensor_state *sensor_st = iio_priv(indio_dev);
|
||||
struct inv_sensors_timestamp *ts = &sensor_st->ts;
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
inv_sensors_timestamp_reset(ts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update_scan_mode callback is turning sensors on and setting data FIFO enable
|
||||
* bits.
|
||||
*/
|
||||
static int inv_icm45600_buffer_postenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
/* Exit if FIFO is already on. */
|
||||
if (st->fifo.on) {
|
||||
st->fifo.on++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2,
|
||||
INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_set_bits(st->map, INV_ICM45600_REG_INT1_CONFIG0,
|
||||
INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN |
|
||||
INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK,
|
||||
INV_ICM45600_FIFO_CONFIG0_MODE_STREAM);
|
||||
ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0,
|
||||
INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable writing sensor data to FIFO. */
|
||||
ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3,
|
||||
INV_ICM45600_FIFO_CONFIG3_IF_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->fifo.on++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int inv_icm45600_buffer_predisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
/* Exit if there are several sensors using the FIFO. */
|
||||
if (st->fifo.on > 1) {
|
||||
st->fifo.on--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Disable writing sensor data to FIFO. */
|
||||
ret = regmap_clear_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3,
|
||||
INV_ICM45600_FIFO_CONFIG3_IF_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK,
|
||||
INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS);
|
||||
ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0,
|
||||
INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_clear_bits(st->map, INV_ICM45600_REG_INT1_CONFIG0,
|
||||
INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN |
|
||||
INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2,
|
||||
INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->fifo.on--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _inv_icm45600_buffer_postdisable(struct inv_icm45600_state *st,
|
||||
unsigned int sensor, unsigned int *watermark,
|
||||
unsigned int *sleep)
|
||||
{
|
||||
struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES;
|
||||
int ret;
|
||||
|
||||
ret = inv_icm45600_buffer_set_fifo_en(st, st->fifo.en & ~sensor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*watermark = 0;
|
||||
ret = inv_icm45600_buffer_update_watermark(st);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
conf.mode = INV_ICM45600_SENSOR_MODE_OFF;
|
||||
if (sensor == INV_ICM45600_SENSOR_GYRO)
|
||||
return inv_icm45600_set_gyro_conf(st, &conf, sleep);
|
||||
else
|
||||
return inv_icm45600_set_accel_conf(st, &conf, sleep);
|
||||
}
|
||||
|
||||
static int inv_icm45600_buffer_postdisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
unsigned int sensor;
|
||||
unsigned int *watermark;
|
||||
unsigned int sleep;
|
||||
int ret;
|
||||
|
||||
if (indio_dev == st->indio_gyro) {
|
||||
sensor = INV_ICM45600_SENSOR_GYRO;
|
||||
watermark = &st->fifo.watermark.gyro;
|
||||
} else if (indio_dev == st->indio_accel) {
|
||||
sensor = INV_ICM45600_SENSOR_ACCEL;
|
||||
watermark = &st->fifo.watermark.accel;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sleep = 0;
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = _inv_icm45600_buffer_postdisable(st, sensor, watermark, &sleep);
|
||||
|
||||
/* Sleep required time. */
|
||||
if (sleep)
|
||||
msleep(sleep);
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const struct iio_buffer_setup_ops inv_icm45600_buffer_ops = {
|
||||
.preenable = inv_icm45600_buffer_preenable,
|
||||
.postenable = inv_icm45600_buffer_postenable,
|
||||
.predisable = inv_icm45600_buffer_predisable,
|
||||
.postdisable = inv_icm45600_buffer_postdisable,
|
||||
};
|
||||
|
||||
int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st,
|
||||
unsigned int max)
|
||||
{
|
||||
const ssize_t packet_size = sizeof(struct inv_icm45600_fifo_2sensors_packet);
|
||||
__le16 *raw_fifo_count;
|
||||
size_t fifo_nb, i;
|
||||
ssize_t size;
|
||||
const struct inv_icm45600_fifo_sensor_data *accel, *gyro;
|
||||
const __le16 *timestamp;
|
||||
const s8 *temp;
|
||||
unsigned int odr;
|
||||
int ret;
|
||||
|
||||
/* Reset all samples counters. */
|
||||
st->fifo.count = 0;
|
||||
st->fifo.nb.gyro = 0;
|
||||
st->fifo.nb.accel = 0;
|
||||
st->fifo.nb.total = 0;
|
||||
|
||||
raw_fifo_count = &st->buffer.u16;
|
||||
ret = regmap_bulk_read(st->map, INV_ICM45600_REG_FIFO_COUNT,
|
||||
raw_fifo_count, sizeof(*raw_fifo_count));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Check and limit number of samples if requested. */
|
||||
fifo_nb = le16_to_cpup(raw_fifo_count);
|
||||
if (fifo_nb == 0)
|
||||
return 0;
|
||||
if (max > 0 && fifo_nb > max)
|
||||
fifo_nb = max;
|
||||
|
||||
/* Try to read all FIFO data in internal buffer. */
|
||||
st->fifo.count = fifo_nb * packet_size;
|
||||
ret = regmap_noinc_read(st->map, INV_ICM45600_REG_FIFO_DATA,
|
||||
st->fifo.data, st->fifo.count);
|
||||
if (ret == -ENOTSUPP || ret == -EFBIG) {
|
||||
/* Read full fifo is not supported, read samples one by one. */
|
||||
ret = 0;
|
||||
for (i = 0; i < st->fifo.count && ret == 0; i += packet_size)
|
||||
ret = regmap_noinc_read(st->map, INV_ICM45600_REG_FIFO_DATA,
|
||||
&st->fifo.data[i], packet_size);
|
||||
}
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < st->fifo.count; i += size) {
|
||||
size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i], &accel, &gyro,
|
||||
&temp, ×tamp, &odr);
|
||||
if (size <= 0)
|
||||
/* No more sample in buffer */
|
||||
break;
|
||||
if (gyro && inv_icm45600_fifo_is_data_valid(gyro))
|
||||
st->fifo.nb.gyro++;
|
||||
if (accel && inv_icm45600_fifo_is_data_valid(accel))
|
||||
st->fifo.nb.accel++;
|
||||
st->fifo.nb.total++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int inv_icm45600_buffer_fifo_parse(struct inv_icm45600_state *st)
|
||||
{
|
||||
struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro);
|
||||
struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel);
|
||||
struct inv_sensors_timestamp *ts;
|
||||
int ret;
|
||||
|
||||
if (st->fifo.nb.total == 0)
|
||||
return 0;
|
||||
|
||||
/* Handle gyroscope timestamp and FIFO data parsing. */
|
||||
if (st->fifo.nb.gyro > 0) {
|
||||
ts = &gyro_st->ts;
|
||||
inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_gyro,
|
||||
st->timestamp.gyro);
|
||||
ret = inv_icm45600_gyro_parse_fifo(st->indio_gyro);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Handle accelerometer timestamp and FIFO data parsing. */
|
||||
if (st->fifo.nb.accel > 0) {
|
||||
ts = &accel_st->ts;
|
||||
inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_accel,
|
||||
st->timestamp.accel);
|
||||
ret = inv_icm45600_accel_parse_fifo(st->indio_accel);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st,
|
||||
unsigned int count)
|
||||
{
|
||||
struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro);
|
||||
struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel);
|
||||
struct inv_sensors_timestamp *ts;
|
||||
s64 gyro_ts, accel_ts;
|
||||
int ret;
|
||||
|
||||
gyro_ts = iio_get_time_ns(st->indio_gyro);
|
||||
accel_ts = iio_get_time_ns(st->indio_accel);
|
||||
|
||||
ret = inv_icm45600_buffer_fifo_read(st, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (st->fifo.nb.total == 0)
|
||||
return 0;
|
||||
|
||||
if (st->fifo.nb.gyro > 0) {
|
||||
ts = &gyro_st->ts;
|
||||
inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts);
|
||||
ret = inv_icm45600_gyro_parse_fifo(st->indio_gyro);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (st->fifo.nb.accel > 0) {
|
||||
ts = &accel_st->ts;
|
||||
inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts);
|
||||
ret = inv_icm45600_accel_parse_fifo(st->indio_accel);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int inv_icm45600_buffer_init(struct inv_icm45600_state *st)
|
||||
{
|
||||
int ret;
|
||||
unsigned int val;
|
||||
|
||||
st->fifo.watermark.eff_gyro = 1;
|
||||
st->fifo.watermark.eff_accel = 1;
|
||||
|
||||
/* Disable all FIFO EN bits. */
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG3, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Disable FIFO and set depth. */
|
||||
val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK,
|
||||
INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS) |
|
||||
FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MASK,
|
||||
INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MAX);
|
||||
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG0, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable only timestamp in fifo, disable compression. */
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG4,
|
||||
INV_ICM45600_FIFO_CONFIG4_TMST_FSYNC_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Enable FIFO continuous watermark interrupt. */
|
||||
return regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2,
|
||||
INV_ICM45600_REG_FIFO_CONFIG2_WM_GT_TH);
|
||||
}
|
||||
101
drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h
Normal file
101
drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/* Copyright (C) 2025 Invensense, Inc. */
|
||||
|
||||
#ifndef INV_ICM45600_BUFFER_H_
|
||||
#define INV_ICM45600_BUFFER_H_
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
struct inv_icm45600_state;
|
||||
|
||||
#define INV_ICM45600_SENSOR_GYRO BIT(0)
|
||||
#define INV_ICM45600_SENSOR_ACCEL BIT(1)
|
||||
#define INV_ICM45600_SENSOR_TEMP BIT(2)
|
||||
|
||||
/**
|
||||
* struct inv_icm45600_fifo - FIFO state variables
|
||||
* @on: reference counter for FIFO on.
|
||||
* @en: bits field of INV_ICM45600_SENSOR_* for FIFO EN bits.
|
||||
* @period: FIFO internal period.
|
||||
* @watermark: watermark configuration values for accel and gyro.
|
||||
* @watermark.gyro: requested watermark for gyro.
|
||||
* @watermark.accel: requested watermark for accel.
|
||||
* @watermark.eff_gyro: effective watermark for gyro.
|
||||
* @watermark.eff_accel: effective watermark for accel.
|
||||
* @count: number of bytes in the FIFO data buffer.
|
||||
* @nb: gyro, accel and total samples in the FIFO data buffer.
|
||||
* @data: FIFO data buffer aligned for DMA (8kB)
|
||||
*/
|
||||
struct inv_icm45600_fifo {
|
||||
unsigned int on;
|
||||
unsigned int en;
|
||||
u32 period;
|
||||
struct {
|
||||
unsigned int gyro;
|
||||
unsigned int accel;
|
||||
unsigned int eff_gyro;
|
||||
unsigned int eff_accel;
|
||||
} watermark;
|
||||
size_t count;
|
||||
struct {
|
||||
size_t gyro;
|
||||
size_t accel;
|
||||
size_t total;
|
||||
} nb;
|
||||
u8 *data;
|
||||
};
|
||||
|
||||
/* FIFO data packet */
|
||||
struct inv_icm45600_fifo_sensor_data {
|
||||
__le16 x;
|
||||
__le16 y;
|
||||
__le16 z;
|
||||
} __packed;
|
||||
#define INV_ICM45600_DATA_INVALID S16_MIN
|
||||
|
||||
static inline bool
|
||||
inv_icm45600_fifo_is_data_valid(const struct inv_icm45600_fifo_sensor_data *s)
|
||||
{
|
||||
s16 x, y, z;
|
||||
|
||||
x = le16_to_cpu(s->x);
|
||||
y = le16_to_cpu(s->y);
|
||||
z = le16_to_cpu(s->z);
|
||||
|
||||
return (x != INV_ICM45600_DATA_INVALID ||
|
||||
y != INV_ICM45600_DATA_INVALID ||
|
||||
z != INV_ICM45600_DATA_INVALID);
|
||||
}
|
||||
|
||||
ssize_t inv_icm45600_fifo_decode_packet(const void *packet,
|
||||
const struct inv_icm45600_fifo_sensor_data **accel,
|
||||
const struct inv_icm45600_fifo_sensor_data **gyro,
|
||||
const s8 **temp,
|
||||
const __le16 **timestamp, unsigned int *odr);
|
||||
|
||||
extern const struct iio_buffer_setup_ops inv_icm45600_buffer_ops;
|
||||
|
||||
int inv_icm45600_buffer_init(struct inv_icm45600_state *st);
|
||||
|
||||
void inv_icm45600_buffer_update_fifo_period(struct inv_icm45600_state *st);
|
||||
|
||||
int inv_icm45600_buffer_set_fifo_en(struct inv_icm45600_state *st,
|
||||
unsigned int fifo_en);
|
||||
|
||||
int inv_icm45600_buffer_update_watermark(struct inv_icm45600_state *st);
|
||||
|
||||
int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st,
|
||||
unsigned int max);
|
||||
|
||||
int inv_icm45600_buffer_fifo_parse(struct inv_icm45600_state *st);
|
||||
|
||||
int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st,
|
||||
unsigned int count);
|
||||
|
||||
#endif
|
||||
988
drivers/iio/imu/inv_icm45600/inv_icm45600_core.c
Normal file
988
drivers/iio/imu/inv_icm45600/inv_icm45600_core.c
Normal file
@@ -0,0 +1,988 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/* Copyright (C) 2025 Invensense, Inc. */
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
#include "inv_icm45600_buffer.h"
|
||||
#include "inv_icm45600.h"
|
||||
|
||||
static int inv_icm45600_ireg_read(struct regmap *map, unsigned int reg,
|
||||
u8 *data, size_t count)
|
||||
{
|
||||
const struct device *dev = regmap_get_device(map);
|
||||
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||
unsigned int d;
|
||||
size_t i;
|
||||
int ret;
|
||||
|
||||
st->buffer.ireg[0] = FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg);
|
||||
st->buffer.ireg[1] = FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg);
|
||||
|
||||
/* Burst write address. */
|
||||
ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, st->buffer.ireg, 2);
|
||||
/*
|
||||
* Wait while the device is busy processing the address.
|
||||
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
|
||||
*/
|
||||
fsleep(INV_ICM45600_IREG_DELAY_US);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read the data. */
|
||||
for (i = 0; i < count; i++) {
|
||||
ret = regmap_read(map, INV_ICM45600_REG_IREG_DATA, &d);
|
||||
/*
|
||||
* Wait while the device is busy processing the address.
|
||||
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
|
||||
*/
|
||||
fsleep(INV_ICM45600_IREG_DELAY_US);
|
||||
if (ret)
|
||||
return ret;
|
||||
data[i] = d;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int inv_icm45600_ireg_write(struct regmap *map, unsigned int reg,
|
||||
const u8 *data, size_t count)
|
||||
{
|
||||
const struct device *dev = regmap_get_device(map);
|
||||
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||
size_t i;
|
||||
int ret;
|
||||
|
||||
st->buffer.ireg[0] = FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg);
|
||||
st->buffer.ireg[1] = FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg);
|
||||
st->buffer.ireg[2] = data[0];
|
||||
|
||||
/* Burst write address and first byte. */
|
||||
ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, st->buffer.ireg, 3);
|
||||
/*
|
||||
* Wait while the device is busy processing the address.
|
||||
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
|
||||
*/
|
||||
fsleep(INV_ICM45600_IREG_DELAY_US);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Write the remaining bytes. */
|
||||
for (i = 1; i < count; i++) {
|
||||
ret = regmap_write(map, INV_ICM45600_REG_IREG_DATA, data[i]);
|
||||
/*
|
||||
* Wait while the device is busy processing the address.
|
||||
* Datasheet: 13.3 MINIMUM WAIT TIME-GAP
|
||||
*/
|
||||
fsleep(INV_ICM45600_IREG_DELAY_US);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int inv_icm45600_read(void *context, const void *reg_buf, size_t reg_size,
|
||||
void *val_buf, size_t val_size)
|
||||
{
|
||||
unsigned int reg = be16_to_cpup(reg_buf);
|
||||
struct regmap *map = context;
|
||||
|
||||
if (FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg))
|
||||
return inv_icm45600_ireg_read(map, reg, val_buf, val_size);
|
||||
|
||||
return regmap_bulk_read(map, FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg),
|
||||
val_buf, val_size);
|
||||
}
|
||||
|
||||
static int inv_icm45600_write(void *context, const void *data, size_t count)
|
||||
{
|
||||
const u8 *d = data;
|
||||
unsigned int reg = be16_to_cpup(data);
|
||||
struct regmap *map = context;
|
||||
|
||||
if (FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg))
|
||||
return inv_icm45600_ireg_write(map, reg, d + 2, count - 2);
|
||||
|
||||
return regmap_bulk_write(map, FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg),
|
||||
d + 2, count - 2);
|
||||
}
|
||||
|
||||
static const struct regmap_bus inv_icm45600_regmap_bus = {
|
||||
.read = inv_icm45600_read,
|
||||
.write = inv_icm45600_write,
|
||||
};
|
||||
|
||||
static const struct regmap_config inv_icm45600_regmap_config = {
|
||||
.reg_bits = 16,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
/* These are the chip initial default configurations (default FS value is based on icm45686) */
|
||||
static const struct inv_icm45600_conf inv_icm45600_default_conf = {
|
||||
.gyro = {
|
||||
.mode = INV_ICM45600_SENSOR_MODE_OFF,
|
||||
.fs = INV_ICM45686_GYRO_FS_2000DPS,
|
||||
.odr = INV_ICM45600_ODR_800HZ_LN,
|
||||
.filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X,
|
||||
},
|
||||
.accel = {
|
||||
.mode = INV_ICM45600_SENSOR_MODE_OFF,
|
||||
.fs = INV_ICM45686_ACCEL_FS_16G,
|
||||
.odr = INV_ICM45600_ODR_800HZ_LN,
|
||||
.filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct inv_icm45600_conf inv_icm45686_default_conf = {
|
||||
.gyro = {
|
||||
.mode = INV_ICM45600_SENSOR_MODE_OFF,
|
||||
.fs = INV_ICM45686_GYRO_FS_4000DPS,
|
||||
.odr = INV_ICM45600_ODR_800HZ_LN,
|
||||
.filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X,
|
||||
},
|
||||
.accel = {
|
||||
.mode = INV_ICM45600_SENSOR_MODE_OFF,
|
||||
.fs = INV_ICM45686_ACCEL_FS_32G,
|
||||
.odr = INV_ICM45600_ODR_800HZ_LN,
|
||||
.filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X,
|
||||
},
|
||||
};
|
||||
|
||||
const struct inv_icm45600_chip_info inv_icm45605_chip_info = {
|
||||
.whoami = INV_ICM45600_WHOAMI_ICM45605,
|
||||
.name = "icm45605",
|
||||
.conf = &inv_icm45600_default_conf,
|
||||
.accel_scales = (const int *)inv_icm45600_accel_scale,
|
||||
.accel_scales_len = INV_ICM45600_ACCEL_FS_MAX,
|
||||
.gyro_scales = (const int *)inv_icm45600_gyro_scale,
|
||||
.gyro_scales_len = INV_ICM45600_GYRO_FS_MAX,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(inv_icm45605_chip_info, "IIO_ICM45600");
|
||||
|
||||
const struct inv_icm45600_chip_info inv_icm45606_chip_info = {
|
||||
.whoami = INV_ICM45600_WHOAMI_ICM45606,
|
||||
.name = "icm45606",
|
||||
.conf = &inv_icm45600_default_conf,
|
||||
.accel_scales = (const int *)inv_icm45600_accel_scale,
|
||||
.accel_scales_len = INV_ICM45600_ACCEL_FS_MAX,
|
||||
.gyro_scales = (const int *)inv_icm45600_gyro_scale,
|
||||
.gyro_scales_len = INV_ICM45600_GYRO_FS_MAX,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(inv_icm45606_chip_info, "IIO_ICM45600");
|
||||
|
||||
const struct inv_icm45600_chip_info inv_icm45608_chip_info = {
|
||||
.whoami = INV_ICM45600_WHOAMI_ICM45608,
|
||||
.name = "icm45608",
|
||||
.conf = &inv_icm45600_default_conf,
|
||||
.accel_scales = (const int *)inv_icm45600_accel_scale,
|
||||
.accel_scales_len = INV_ICM45600_ACCEL_FS_MAX,
|
||||
.gyro_scales = (const int *)inv_icm45600_gyro_scale,
|
||||
.gyro_scales_len = INV_ICM45600_GYRO_FS_MAX,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(inv_icm45608_chip_info, "IIO_ICM45600");
|
||||
|
||||
const struct inv_icm45600_chip_info inv_icm45634_chip_info = {
|
||||
.whoami = INV_ICM45600_WHOAMI_ICM45634,
|
||||
.name = "icm45634",
|
||||
.conf = &inv_icm45600_default_conf,
|
||||
.accel_scales = (const int *)inv_icm45600_accel_scale,
|
||||
.accel_scales_len = INV_ICM45600_ACCEL_FS_MAX,
|
||||
.gyro_scales = (const int *)inv_icm45600_gyro_scale,
|
||||
.gyro_scales_len = INV_ICM45600_GYRO_FS_MAX,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(inv_icm45634_chip_info, "IIO_ICM45600");
|
||||
|
||||
const struct inv_icm45600_chip_info inv_icm45686_chip_info = {
|
||||
.whoami = INV_ICM45600_WHOAMI_ICM45686,
|
||||
.name = "icm45686",
|
||||
.conf = &inv_icm45686_default_conf,
|
||||
.accel_scales = (const int *)inv_icm45686_accel_scale,
|
||||
.accel_scales_len = INV_ICM45686_ACCEL_FS_MAX,
|
||||
.gyro_scales = (const int *)inv_icm45686_gyro_scale,
|
||||
.gyro_scales_len = INV_ICM45686_GYRO_FS_MAX,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(inv_icm45686_chip_info, "IIO_ICM45600");
|
||||
|
||||
const struct inv_icm45600_chip_info inv_icm45687_chip_info = {
|
||||
.whoami = INV_ICM45600_WHOAMI_ICM45687,
|
||||
.name = "icm45687",
|
||||
.conf = &inv_icm45686_default_conf,
|
||||
.accel_scales = (const int *)inv_icm45686_accel_scale,
|
||||
.accel_scales_len = INV_ICM45686_ACCEL_FS_MAX,
|
||||
.gyro_scales = (const int *)inv_icm45686_gyro_scale,
|
||||
.gyro_scales_len = INV_ICM45686_GYRO_FS_MAX,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(inv_icm45687_chip_info, "IIO_ICM45600");
|
||||
|
||||
const struct inv_icm45600_chip_info inv_icm45688p_chip_info = {
|
||||
.whoami = INV_ICM45600_WHOAMI_ICM45688P,
|
||||
.name = "icm45688p",
|
||||
.conf = &inv_icm45686_default_conf,
|
||||
.accel_scales = (const int *)inv_icm45686_accel_scale,
|
||||
.accel_scales_len = INV_ICM45686_ACCEL_FS_MAX,
|
||||
.gyro_scales = (const int *)inv_icm45686_gyro_scale,
|
||||
.gyro_scales_len = INV_ICM45686_GYRO_FS_MAX,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(inv_icm45688p_chip_info, "IIO_ICM45600");
|
||||
|
||||
const struct inv_icm45600_chip_info inv_icm45689_chip_info = {
|
||||
.whoami = INV_ICM45600_WHOAMI_ICM45689,
|
||||
.name = "icm45689",
|
||||
.conf = &inv_icm45686_default_conf,
|
||||
.accel_scales = (const int *)inv_icm45686_accel_scale,
|
||||
.accel_scales_len = INV_ICM45686_ACCEL_FS_MAX,
|
||||
.gyro_scales = (const int *)inv_icm45686_gyro_scale,
|
||||
.gyro_scales_len = INV_ICM45686_GYRO_FS_MAX,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(inv_icm45689_chip_info, "IIO_ICM45600");
|
||||
|
||||
const struct iio_mount_matrix *
|
||||
inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan)
|
||||
{
|
||||
const struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
|
||||
return &st->orientation;
|
||||
}
|
||||
|
||||
u32 inv_icm45600_odr_to_period(enum inv_icm45600_odr odr)
|
||||
{
|
||||
static const u32 odr_periods[INV_ICM45600_ODR_MAX] = {
|
||||
/* 3 first values are reserved, left to 0 */
|
||||
[INV_ICM45600_ODR_6400HZ_LN] = 156250,
|
||||
[INV_ICM45600_ODR_3200HZ_LN] = 312500,
|
||||
[INV_ICM45600_ODR_1600HZ_LN] = 625000,
|
||||
[INV_ICM45600_ODR_800HZ_LN] = 1250000,
|
||||
[INV_ICM45600_ODR_400HZ] = 2500000,
|
||||
[INV_ICM45600_ODR_200HZ] = 5000000,
|
||||
[INV_ICM45600_ODR_100HZ] = 10000000,
|
||||
[INV_ICM45600_ODR_50HZ] = 20000000,
|
||||
[INV_ICM45600_ODR_25HZ] = 40000000,
|
||||
[INV_ICM45600_ODR_12_5HZ] = 80000000,
|
||||
[INV_ICM45600_ODR_6_25HZ_LP] = 160000000,
|
||||
[INV_ICM45600_ODR_3_125HZ_LP] = 320000000,
|
||||
[INV_ICM45600_ODR_1_5625HZ_LP] = 640000000,
|
||||
};
|
||||
|
||||
return odr_periods[odr];
|
||||
}
|
||||
|
||||
static int inv_icm45600_set_pwr_mgmt0(struct inv_icm45600_state *st,
|
||||
enum inv_icm45600_sensor_mode gyro,
|
||||
enum inv_icm45600_sensor_mode accel,
|
||||
unsigned int *sleep_ms)
|
||||
{
|
||||
enum inv_icm45600_sensor_mode oldgyro = st->conf.gyro.mode;
|
||||
enum inv_icm45600_sensor_mode oldaccel = st->conf.accel.mode;
|
||||
unsigned int sleepval;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
/* if nothing changed, exit */
|
||||
if (gyro == oldgyro && accel == oldaccel)
|
||||
return 0;
|
||||
|
||||
val = FIELD_PREP(INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK, gyro) |
|
||||
FIELD_PREP(INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK, accel);
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->conf.gyro.mode = gyro;
|
||||
st->conf.accel.mode = accel;
|
||||
|
||||
/* Compute the required wait time for sensors to stabilize. */
|
||||
sleepval = 0;
|
||||
if (accel != oldaccel && oldaccel == INV_ICM45600_SENSOR_MODE_OFF)
|
||||
sleepval = max(sleepval, INV_ICM45600_ACCEL_STARTUP_TIME_MS);
|
||||
|
||||
if (gyro != oldgyro) {
|
||||
if (oldgyro == INV_ICM45600_SENSOR_MODE_OFF)
|
||||
sleepval = max(sleepval, INV_ICM45600_GYRO_STARTUP_TIME_MS);
|
||||
else if (gyro == INV_ICM45600_SENSOR_MODE_OFF)
|
||||
sleepval = max(sleepval, INV_ICM45600_GYRO_STOP_TIME_MS);
|
||||
}
|
||||
|
||||
/* Deferred sleep value if sleep pointer is provided or direct sleep */
|
||||
if (sleep_ms)
|
||||
*sleep_ms = sleepval;
|
||||
else if (sleepval)
|
||||
msleep(sleepval);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void inv_icm45600_set_default_conf(struct inv_icm45600_sensor_conf *conf,
|
||||
struct inv_icm45600_sensor_conf *oldconf)
|
||||
{
|
||||
/* Sanitize missing values with current values. */
|
||||
if (conf->mode == U8_MAX)
|
||||
conf->mode = oldconf->mode;
|
||||
if (conf->fs == U8_MAX)
|
||||
conf->fs = oldconf->fs;
|
||||
if (conf->odr == U8_MAX)
|
||||
conf->odr = oldconf->odr;
|
||||
if (conf->filter == U8_MAX)
|
||||
conf->filter = oldconf->filter;
|
||||
}
|
||||
|
||||
int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st,
|
||||
struct inv_icm45600_sensor_conf *conf,
|
||||
unsigned int *sleep_ms)
|
||||
{
|
||||
struct inv_icm45600_sensor_conf *oldconf = &st->conf.accel;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
inv_icm45600_set_default_conf(conf, oldconf);
|
||||
|
||||
/* Force the power mode against the ODR when sensor is on. */
|
||||
if (conf->mode > INV_ICM45600_SENSOR_MODE_STANDBY) {
|
||||
if (conf->odr <= INV_ICM45600_ODR_800HZ_LN) {
|
||||
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
|
||||
} else {
|
||||
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
|
||||
/* sanitize averaging value depending on ODR for low-power mode */
|
||||
/* maximum 1x @400Hz */
|
||||
if (conf->odr == INV_ICM45600_ODR_400HZ)
|
||||
conf->filter = INV_ICM45600_ACCEL_LP_AVG_SEL_1X;
|
||||
else
|
||||
conf->filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set accel fullscale & odr. */
|
||||
if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
|
||||
val = FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, conf->fs) |
|
||||
FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_ODR_MASK, conf->odr);
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
oldconf->fs = conf->fs;
|
||||
oldconf->odr = conf->odr;
|
||||
}
|
||||
|
||||
/* Set accel low-power average filter. */
|
||||
if (conf->filter != oldconf->filter) {
|
||||
ret = regmap_write(st->map, INV_ICM45600_IPREG_SYS2_REG_129,
|
||||
conf->filter);
|
||||
if (ret)
|
||||
return ret;
|
||||
oldconf->filter = conf->filter;
|
||||
}
|
||||
|
||||
/* Update the sensor accel mode. */
|
||||
return inv_icm45600_set_pwr_mgmt0(st, st->conf.gyro.mode, conf->mode,
|
||||
sleep_ms);
|
||||
}
|
||||
|
||||
int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st,
|
||||
struct inv_icm45600_sensor_conf *conf,
|
||||
unsigned int *sleep_ms)
|
||||
{
|
||||
struct inv_icm45600_sensor_conf *oldconf = &st->conf.gyro;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
inv_icm45600_set_default_conf(conf, oldconf);
|
||||
|
||||
/* Force the power mode against ODR when sensor is on. */
|
||||
if (conf->mode > INV_ICM45600_SENSOR_MODE_STANDBY) {
|
||||
if (conf->odr >= INV_ICM45600_ODR_6_25HZ_LP) {
|
||||
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
|
||||
conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X;
|
||||
} else {
|
||||
conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set gyro fullscale & odr. */
|
||||
if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) {
|
||||
val = FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, conf->fs) |
|
||||
FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_ODR_MASK, conf->odr);
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
oldconf->fs = conf->fs;
|
||||
oldconf->odr = conf->odr;
|
||||
}
|
||||
|
||||
/* Set gyro low-power average filter. */
|
||||
if (conf->filter != oldconf->filter) {
|
||||
val = FIELD_PREP(INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK, conf->filter);
|
||||
ret = regmap_update_bits(st->map, INV_ICM45600_IPREG_SYS1_REG_170,
|
||||
INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
oldconf->filter = conf->filter;
|
||||
}
|
||||
|
||||
/* Update the sensor gyro mode. */
|
||||
return inv_icm45600_set_pwr_mgmt0(st, conf->mode, st->conf.accel.mode,
|
||||
sleep_ms);
|
||||
}
|
||||
|
||||
int inv_icm45600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg,
|
||||
unsigned int writeval, unsigned int *readval)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
if (readval)
|
||||
return regmap_read(st->map, reg, readval);
|
||||
else
|
||||
return regmap_write(st->map, reg, writeval);
|
||||
}
|
||||
|
||||
static int inv_icm45600_set_conf(struct inv_icm45600_state *st,
|
||||
const struct inv_icm45600_conf *conf)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
val = FIELD_PREP(INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK, conf->gyro.mode) |
|
||||
FIELD_PREP(INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK, conf->accel.mode);
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, conf->gyro.fs) |
|
||||
FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_ODR_MASK, conf->gyro.odr);
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, conf->accel.fs) |
|
||||
FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_ODR_MASK, conf->accel.odr);
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Save configuration. */
|
||||
st->conf = *conf;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* inv_icm45600_setup() - check and setup chip
|
||||
* @st: driver internal state
|
||||
* @chip_info: detected chip description
|
||||
* @reset: define whether a reset is required or not
|
||||
* @bus_setup: callback for setting up bus specific registers
|
||||
*
|
||||
* Returns: 0 on success, a negative error code otherwise.
|
||||
*/
|
||||
static int inv_icm45600_setup(struct inv_icm45600_state *st,
|
||||
const struct inv_icm45600_chip_info *chip_info,
|
||||
bool reset, inv_icm45600_bus_setup bus_setup)
|
||||
{
|
||||
const struct device *dev = regmap_get_device(st->map);
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
/* Set chip bus configuration if specified. */
|
||||
if (bus_setup) {
|
||||
ret = bus_setup(st);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Check chip self-identification value. */
|
||||
ret = regmap_read(st->map, INV_ICM45600_REG_WHOAMI, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (val != chip_info->whoami) {
|
||||
/*
|
||||
* SPI interface has no ack mechanism.
|
||||
* 0xFF or 0x00 whoami means no response from the device.
|
||||
*/
|
||||
if (val == U8_MAX || val == 0)
|
||||
return dev_err_probe(dev, -ENODEV,
|
||||
"Invalid whoami %#02x expected %#02x (%s)\n",
|
||||
val, chip_info->whoami, chip_info->name);
|
||||
|
||||
dev_warn(dev, "Unexpected whoami %#02x expected %#02x (%s)\n",
|
||||
val, chip_info->whoami, chip_info->name);
|
||||
}
|
||||
|
||||
st->chip_info = chip_info;
|
||||
|
||||
if (reset) {
|
||||
/* Reset previous state. */
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_MISC2,
|
||||
INV_ICM45600_MISC2_SOFT_RESET);
|
||||
if (ret)
|
||||
return ret;
|
||||
/*
|
||||
* IMU reset time.
|
||||
* Datasheet: 16.84 REG_MISC2
|
||||
*/
|
||||
fsleep(USEC_PER_MSEC);
|
||||
|
||||
if (bus_setup) {
|
||||
ret = bus_setup(st);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_read(st->map, INV_ICM45600_REG_INT_STATUS, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (!(val & INV_ICM45600_INT_STATUS_RESET_DONE)) {
|
||||
dev_err(dev, "reset error, reset done bit not set\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
return inv_icm45600_set_conf(st, chip_info->conf);
|
||||
}
|
||||
|
||||
static irqreturn_t inv_icm45600_irq_timestamp(int irq, void *_data)
|
||||
{
|
||||
struct inv_icm45600_state *st = _data;
|
||||
|
||||
st->timestamp.gyro = iio_get_time_ns(st->indio_gyro);
|
||||
st->timestamp.accel = iio_get_time_ns(st->indio_accel);
|
||||
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
static irqreturn_t inv_icm45600_irq_handler(int irq, void *_data)
|
||||
{
|
||||
struct inv_icm45600_state *st = _data;
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
unsigned int mask, status;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
ret = regmap_read(st->map, INV_ICM45600_REG_INT_STATUS, &status);
|
||||
if (ret)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
/* Read the FIFO data. */
|
||||
mask = INV_ICM45600_INT_STATUS_FIFO_THS | INV_ICM45600_INT_STATUS_FIFO_FULL;
|
||||
if (status & mask) {
|
||||
ret = inv_icm45600_buffer_fifo_read(st, 0);
|
||||
if (ret) {
|
||||
dev_err(dev, "FIFO read error %d\n", ret);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
ret = inv_icm45600_buffer_fifo_parse(st);
|
||||
if (ret)
|
||||
dev_err(dev, "FIFO parsing error %d\n", ret);
|
||||
}
|
||||
|
||||
/* FIFO full warning. */
|
||||
if (status & INV_ICM45600_INT_STATUS_FIFO_FULL)
|
||||
dev_warn(dev, "FIFO full possible data lost!\n");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* inv_icm45600_irq_init() - initialize int pin and interrupt handler
|
||||
* @st: driver internal state
|
||||
* @irq: irq number
|
||||
* @irq_type: irq trigger type
|
||||
* @open_drain: true if irq is open drain, false for push-pull
|
||||
*
|
||||
* Returns: 0 on success, a negative error code otherwise.
|
||||
*/
|
||||
static int inv_icm45600_irq_init(struct inv_icm45600_state *st, int irq,
|
||||
int irq_type, bool open_drain)
|
||||
{
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
/* Configure INT1 interrupt: default is active low on edge. */
|
||||
switch (irq_type) {
|
||||
case IRQF_TRIGGER_RISING:
|
||||
case IRQF_TRIGGER_HIGH:
|
||||
val = INV_ICM45600_INT1_CONFIG2_ACTIVE_HIGH;
|
||||
break;
|
||||
default:
|
||||
val = INV_ICM45600_INT1_CONFIG2_ACTIVE_LOW;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (irq_type) {
|
||||
case IRQF_TRIGGER_LOW:
|
||||
case IRQF_TRIGGER_HIGH:
|
||||
val |= INV_ICM45600_INT1_CONFIG2_LATCHED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!open_drain)
|
||||
val |= INV_ICM45600_INT1_CONFIG2_PUSH_PULL;
|
||||
|
||||
ret = regmap_write(st->map, INV_ICM45600_REG_INT1_CONFIG2, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_request_threaded_irq(dev, irq, inv_icm45600_irq_timestamp,
|
||||
inv_icm45600_irq_handler, irq_type | IRQF_ONESHOT,
|
||||
"inv_icm45600", st);
|
||||
}
|
||||
|
||||
static int inv_icm45600_timestamp_setup(struct inv_icm45600_state *st)
|
||||
{
|
||||
/* Enable timestamps. */
|
||||
return regmap_set_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0,
|
||||
INV_ICM45600_SMC_CONTROL_0_TMST_EN);
|
||||
}
|
||||
|
||||
static int inv_icm45600_enable_regulator_vddio(struct inv_icm45600_state *st)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regulator_enable(st->vddio_supply);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Wait a little for supply ramp.
|
||||
* Duration is empirically defined.
|
||||
*/
|
||||
fsleep(3 * USEC_PER_MSEC);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void inv_icm45600_disable_vddio_reg(void *_data)
|
||||
{
|
||||
struct inv_icm45600_state *st = _data;
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
|
||||
if (pm_runtime_status_suspended(dev))
|
||||
return;
|
||||
|
||||
regulator_disable(st->vddio_supply);
|
||||
}
|
||||
|
||||
int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chip_info *chip_info,
|
||||
bool reset, inv_icm45600_bus_setup bus_setup)
|
||||
{
|
||||
struct device *dev = regmap_get_device(regmap);
|
||||
struct inv_icm45600_state *st;
|
||||
struct regmap *regmap_custom;
|
||||
struct fwnode_handle *fwnode;
|
||||
int irq, irq_type;
|
||||
bool open_drain;
|
||||
int ret;
|
||||
|
||||
/* Get INT1 only supported interrupt. */
|
||||
fwnode = dev_fwnode(dev);
|
||||
irq = fwnode_irq_get_byname(fwnode, "int1");
|
||||
if (irq < 0)
|
||||
return dev_err_probe(dev, irq, "Missing int1 interrupt\n");
|
||||
|
||||
irq_type = irq_get_trigger_type(irq);
|
||||
|
||||
open_drain = device_property_read_bool(dev, "drive-open-drain");
|
||||
|
||||
regmap_custom = devm_regmap_init(dev, &inv_icm45600_regmap_bus, regmap,
|
||||
&inv_icm45600_regmap_config);
|
||||
if (IS_ERR(regmap_custom))
|
||||
return dev_err_probe(dev, PTR_ERR(regmap_custom), "Failed to register regmap\n");
|
||||
|
||||
st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
|
||||
if (!st)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, st);
|
||||
|
||||
st->fifo.data = devm_kzalloc(dev, 8192, GFP_KERNEL);
|
||||
if (!st->fifo.data)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_mutex_init(dev, &st->lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->map = regmap_custom;
|
||||
|
||||
ret = iio_read_mount_matrix(dev, &st->orientation);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to retrieve mounting matrix\n");
|
||||
|
||||
st->vddio_supply = devm_regulator_get(dev, "vddio");
|
||||
if (IS_ERR(st->vddio_supply))
|
||||
return PTR_ERR(st->vddio_supply);
|
||||
|
||||
ret = devm_regulator_get_enable(dev, "vdd");
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to get vdd regulator\n");
|
||||
|
||||
/*
|
||||
* Supply ramp time + Start-up time.
|
||||
* Datasheet: 3.3.2 A.C. Electrical Characteristics
|
||||
*/
|
||||
fsleep(5 * USEC_PER_MSEC);
|
||||
|
||||
ret = inv_icm45600_enable_regulator_vddio(st);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, inv_icm45600_disable_vddio_reg, st);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = inv_icm45600_setup(st, chip_info, reset, bus_setup);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = inv_icm45600_timestamp_setup(st);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = inv_icm45600_buffer_init(st);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
st->indio_gyro = inv_icm45600_gyro_init(st);
|
||||
if (IS_ERR(st->indio_gyro))
|
||||
return PTR_ERR(st->indio_gyro);
|
||||
|
||||
st->indio_accel = inv_icm45600_accel_init(st);
|
||||
if (IS_ERR(st->indio_accel))
|
||||
return PTR_ERR(st->indio_accel);
|
||||
|
||||
ret = inv_icm45600_irq_init(st, irq, irq_type, open_drain);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_pm_runtime_set_active_enabled(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pm_runtime_get_noresume(dev);
|
||||
pm_runtime_set_autosuspend_delay(dev, 2 * USEC_PER_MSEC);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
pm_runtime_put(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(inv_icm45600_core_probe, "IIO_ICM45600");
|
||||
|
||||
/*
|
||||
* Suspend saves sensors state and turns everything off.
|
||||
*/
|
||||
static int inv_icm45600_suspend(struct device *dev)
|
||||
{
|
||||
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock) {
|
||||
/* Disable FIFO data streaming. */
|
||||
if (st->fifo.on) {
|
||||
unsigned int val;
|
||||
|
||||
/* Clear FIFO_CONFIG3_IF_EN before changing the FIFO configuration */
|
||||
ret = regmap_clear_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3,
|
||||
INV_ICM45600_FIFO_CONFIG3_IF_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK,
|
||||
INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS);
|
||||
ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0,
|
||||
INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Save sensors states */
|
||||
st->suspended.gyro = st->conf.gyro.mode;
|
||||
st->suspended.accel = st->conf.accel.mode;
|
||||
}
|
||||
|
||||
return pm_runtime_force_suspend(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* System resume gets the system back on and restores the sensors state.
|
||||
* Manually put runtime power management in system active state.
|
||||
*/
|
||||
static int inv_icm45600_resume(struct device *dev)
|
||||
{
|
||||
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_force_resume(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock) {
|
||||
/* Restore sensors state. */
|
||||
ret = inv_icm45600_set_pwr_mgmt0(st, st->suspended.gyro,
|
||||
st->suspended.accel, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Restore FIFO data streaming. */
|
||||
if (st->fifo.on) {
|
||||
struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro);
|
||||
struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel);
|
||||
unsigned int val;
|
||||
|
||||
inv_sensors_timestamp_reset(&gyro_st->ts);
|
||||
inv_sensors_timestamp_reset(&accel_st->ts);
|
||||
val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK,
|
||||
INV_ICM45600_FIFO_CONFIG0_MODE_STREAM);
|
||||
ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0,
|
||||
INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* FIFO_CONFIG3_IF_EN must only be set at end of FIFO the configuration */
|
||||
ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3,
|
||||
INV_ICM45600_FIFO_CONFIG3_IF_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Runtime suspend will turn off sensors that are enabled by iio devices. */
|
||||
static int inv_icm45600_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
/* disable all sensors */
|
||||
ret = inv_icm45600_set_pwr_mgmt0(st, INV_ICM45600_SENSOR_MODE_OFF,
|
||||
INV_ICM45600_SENSOR_MODE_OFF, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
regulator_disable(st->vddio_supply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Sensors are enabled by iio devices, no need to turn them back on here. */
|
||||
static int inv_icm45600_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct inv_icm45600_state *st = dev_get_drvdata(dev);
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
return inv_icm45600_enable_regulator_vddio(st);
|
||||
}
|
||||
|
||||
static int _inv_icm45600_temp_read(struct inv_icm45600_state *st, s16 *temp)
|
||||
{
|
||||
struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES;
|
||||
int ret;
|
||||
|
||||
/* Make sure a sensor is on. */
|
||||
if (st->conf.gyro.mode == INV_ICM45600_SENSOR_MODE_OFF &&
|
||||
st->conf.accel.mode == INV_ICM45600_SENSOR_MODE_OFF) {
|
||||
conf.mode = INV_ICM45600_SENSOR_MODE_LOW_POWER;
|
||||
ret = inv_icm45600_set_accel_conf(st, &conf, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regmap_bulk_read(st->map, INV_ICM45600_REG_TEMP_DATA,
|
||||
&st->buffer.u16, sizeof(st->buffer.u16));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*temp = (s16)le16_to_cpup(&st->buffer.u16);
|
||||
if (*temp == INV_ICM45600_DATA_INVALID)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int inv_icm45600_temp_read(struct inv_icm45600_state *st, s16 *temp)
|
||||
{
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = _inv_icm45600_temp_read(st, temp);
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int inv_icm45600_temp_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
s16 temp;
|
||||
int ret;
|
||||
|
||||
if (chan->type != IIO_TEMP)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
ret = inv_icm45600_temp_read(st, &temp);
|
||||
if (ret)
|
||||
return ret;
|
||||
*val = temp;
|
||||
return IIO_VAL_INT;
|
||||
/*
|
||||
* T°C = (temp / 128) + 25
|
||||
* Tm°C = 1000 * ((temp * 100 / 12800) + 25)
|
||||
* scale: 100000 / 13248 = 7.8125
|
||||
* offset: 25000
|
||||
*/
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 7;
|
||||
*val2 = 812500;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
*val = 25000;
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
EXPORT_NS_GPL_DEV_PM_OPS(inv_icm45600_pm_ops, IIO_ICM45600) = {
|
||||
SYSTEM_SLEEP_PM_OPS(inv_icm45600_suspend, inv_icm45600_resume)
|
||||
RUNTIME_PM_OPS(inv_icm45600_runtime_suspend,
|
||||
inv_icm45600_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
MODULE_AUTHOR("InvenSense, Inc.");
|
||||
MODULE_DESCRIPTION("InvenSense ICM-456xx device driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("IIO_INV_SENSORS_TIMESTAMP");
|
||||
791
drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c
Normal file
791
drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c
Normal file
@@ -0,0 +1,791 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (C) 2025 Invensense, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/common/inv_sensors_timestamp.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
|
||||
#include "inv_icm45600_buffer.h"
|
||||
#include "inv_icm45600.h"
|
||||
|
||||
enum inv_icm45600_gyro_scan {
|
||||
INV_ICM45600_GYRO_SCAN_X,
|
||||
INV_ICM45600_GYRO_SCAN_Y,
|
||||
INV_ICM45600_GYRO_SCAN_Z,
|
||||
INV_ICM45600_GYRO_SCAN_TEMP,
|
||||
INV_ICM45600_GYRO_SCAN_TIMESTAMP,
|
||||
};
|
||||
|
||||
static const struct iio_chan_spec_ext_info inv_icm45600_gyro_ext_infos[] = {
|
||||
IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm45600_get_mount_matrix),
|
||||
{ }
|
||||
};
|
||||
|
||||
#define INV_ICM45600_GYRO_CHAN(_modifier, _index, _ext_info) \
|
||||
{ \
|
||||
.type = IIO_ANGL_VEL, \
|
||||
.modified = 1, \
|
||||
.channel2 = _modifier, \
|
||||
.info_mask_separate = \
|
||||
BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_CALIBBIAS), \
|
||||
.info_mask_shared_by_type = \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.info_mask_shared_by_type_available = \
|
||||
BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_CALIBBIAS), \
|
||||
.info_mask_shared_by_all = \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.info_mask_shared_by_all_available = \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.scan_index = _index, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
.ext_info = _ext_info, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec inv_icm45600_gyro_channels[] = {
|
||||
INV_ICM45600_GYRO_CHAN(IIO_MOD_X, INV_ICM45600_GYRO_SCAN_X,
|
||||
inv_icm45600_gyro_ext_infos),
|
||||
INV_ICM45600_GYRO_CHAN(IIO_MOD_Y, INV_ICM45600_GYRO_SCAN_Y,
|
||||
inv_icm45600_gyro_ext_infos),
|
||||
INV_ICM45600_GYRO_CHAN(IIO_MOD_Z, INV_ICM45600_GYRO_SCAN_Z,
|
||||
inv_icm45600_gyro_ext_infos),
|
||||
INV_ICM45600_TEMP_CHAN(INV_ICM45600_GYRO_SCAN_TEMP),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(INV_ICM45600_GYRO_SCAN_TIMESTAMP),
|
||||
};
|
||||
|
||||
/*
|
||||
* IIO buffer data: size must be a power of 2 and timestamp aligned
|
||||
* 16 bytes: 6 bytes angular velocity, 2 bytes temperature, 8 bytes timestamp
|
||||
*/
|
||||
struct inv_icm45600_gyro_buffer {
|
||||
struct inv_icm45600_fifo_sensor_data gyro;
|
||||
s16 temp;
|
||||
aligned_s64 timestamp;
|
||||
};
|
||||
|
||||
static const unsigned long inv_icm45600_gyro_scan_masks[] = {
|
||||
/* 3-axis gyro + temperature */
|
||||
BIT(INV_ICM45600_GYRO_SCAN_X) |
|
||||
BIT(INV_ICM45600_GYRO_SCAN_Y) |
|
||||
BIT(INV_ICM45600_GYRO_SCAN_Z) |
|
||||
BIT(INV_ICM45600_GYRO_SCAN_TEMP),
|
||||
0
|
||||
};
|
||||
|
||||
/* enable gyroscope sensor and FIFO write */
|
||||
static int inv_icm45600_gyro_update_scan_mode(struct iio_dev *indio_dev,
|
||||
const unsigned long *scan_mask)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
|
||||
struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES;
|
||||
unsigned int fifo_en = 0;
|
||||
unsigned int sleep = 0;
|
||||
int ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock) {
|
||||
if (*scan_mask & BIT(INV_ICM45600_GYRO_SCAN_TEMP))
|
||||
fifo_en |= INV_ICM45600_SENSOR_TEMP;
|
||||
|
||||
if (*scan_mask & (BIT(INV_ICM45600_GYRO_SCAN_X) |
|
||||
BIT(INV_ICM45600_GYRO_SCAN_Y) |
|
||||
BIT(INV_ICM45600_GYRO_SCAN_Z))) {
|
||||
/* enable gyro sensor */
|
||||
conf.mode = gyro_st->power_mode;
|
||||
ret = inv_icm45600_set_gyro_conf(st, &conf, &sleep);
|
||||
if (ret)
|
||||
return ret;
|
||||
fifo_en |= INV_ICM45600_SENSOR_GYRO;
|
||||
}
|
||||
ret = inv_icm45600_buffer_set_fifo_en(st, fifo_en | st->fifo.en);
|
||||
}
|
||||
if (sleep)
|
||||
msleep(sleep);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _inv_icm45600_gyro_read_sensor(struct inv_icm45600_state *st,
|
||||
struct inv_icm45600_sensor_state *gyro_st,
|
||||
unsigned int reg, int *val)
|
||||
{
|
||||
struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES;
|
||||
int ret;
|
||||
|
||||
/* enable gyro sensor */
|
||||
conf.mode = gyro_st->power_mode;
|
||||
ret = inv_icm45600_set_gyro_conf(st, &conf, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* read gyro register data */
|
||||
ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = sign_extend32(le16_to_cpup(&st->buffer.u16), 15);
|
||||
if (*val == INV_ICM45600_DATA_INVALID)
|
||||
return -ENODATA;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int inv_icm45600_gyro_read_sensor(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
unsigned int reg;
|
||||
int ret;
|
||||
|
||||
if (chan->type != IIO_ANGL_VEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
reg = INV_ICM45600_REG_GYRO_DATA_X;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
reg = INV_ICM45600_REG_GYRO_DATA_Y;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
reg = INV_ICM45600_REG_GYRO_DATA_Z;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = _inv_icm45600_gyro_read_sensor(st, gyro_st, reg, val);
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* IIO format int + nano */
|
||||
const int inv_icm45600_gyro_scale[][2] = {
|
||||
/* +/- 2000dps => 0.001065264 rad/s */
|
||||
[INV_ICM45600_GYRO_FS_2000DPS] = { 0, 1065264 },
|
||||
/* +/- 1000dps => 0.000532632 rad/s */
|
||||
[INV_ICM45600_GYRO_FS_1000DPS] = { 0, 532632 },
|
||||
/* +/- 500dps => 0.000266316 rad/s */
|
||||
[INV_ICM45600_GYRO_FS_500DPS] = { 0, 266316 },
|
||||
/* +/- 250dps => 0.000133158 rad/s */
|
||||
[INV_ICM45600_GYRO_FS_250DPS] = { 0, 133158 },
|
||||
/* +/- 125dps => 0.000066579 rad/s */
|
||||
[INV_ICM45600_GYRO_FS_125DPS] = { 0, 66579 },
|
||||
/* +/- 62.5dps => 0.000033290 rad/s */
|
||||
[INV_ICM45600_GYRO_FS_62_5DPS] = { 0, 33290 },
|
||||
/* +/- 31.25dps => 0.000016645 rad/s */
|
||||
[INV_ICM45600_GYRO_FS_31_25DPS] = { 0, 16645 },
|
||||
/* +/- 15.625dps => 0.000008322 rad/s */
|
||||
[INV_ICM45600_GYRO_FS_15_625DPS] = { 0, 8322 },
|
||||
};
|
||||
|
||||
/* IIO format int + nano */
|
||||
const int inv_icm45686_gyro_scale[][2] = {
|
||||
/* +/- 4000dps => 0.002130529 rad/s */
|
||||
[INV_ICM45686_GYRO_FS_4000DPS] = { 0, 2130529 },
|
||||
/* +/- 2000dps => 0.001065264 rad/s */
|
||||
[INV_ICM45686_GYRO_FS_2000DPS] = { 0, 1065264 },
|
||||
/* +/- 1000dps => 0.000532632 rad/s */
|
||||
[INV_ICM45686_GYRO_FS_1000DPS] = { 0, 532632 },
|
||||
/* +/- 500dps => 0.000266316 rad/s */
|
||||
[INV_ICM45686_GYRO_FS_500DPS] = { 0, 266316 },
|
||||
/* +/- 250dps => 0.000133158 rad/s */
|
||||
[INV_ICM45686_GYRO_FS_250DPS] = { 0, 133158 },
|
||||
/* +/- 125dps => 0.000066579 rad/s */
|
||||
[INV_ICM45686_GYRO_FS_125DPS] = { 0, 66579 },
|
||||
/* +/- 62.5dps => 0.000033290 rad/s */
|
||||
[INV_ICM45686_GYRO_FS_62_5DPS] = { 0, 33290 },
|
||||
/* +/- 31.25dps => 0.000016645 rad/s */
|
||||
[INV_ICM45686_GYRO_FS_31_25DPS] = { 0, 16645 },
|
||||
/* +/- 15.625dps => 0.000008322 rad/s */
|
||||
[INV_ICM45686_GYRO_FS_15_625DPS] = { 0, 8322 },
|
||||
};
|
||||
|
||||
static int inv_icm45600_gyro_read_scale(struct iio_dev *indio_dev,
|
||||
int *val, int *val2)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
|
||||
unsigned int idx;
|
||||
|
||||
idx = st->conf.gyro.fs;
|
||||
|
||||
/* Full scale register starts at 1 for not High FSR parts */
|
||||
if (gyro_st->scales == (const int *)&inv_icm45600_gyro_scale)
|
||||
idx--;
|
||||
|
||||
*val = gyro_st->scales[2 * idx];
|
||||
*val2 = gyro_st->scales[2 * idx + 1];
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
}
|
||||
|
||||
static int inv_icm45600_gyro_write_scale(struct iio_dev *indio_dev,
|
||||
int val, int val2)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
unsigned int idx;
|
||||
struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES;
|
||||
int ret;
|
||||
|
||||
for (idx = 0; idx < gyro_st->scales_len; idx += 2) {
|
||||
if (val == gyro_st->scales[idx] &&
|
||||
val2 == gyro_st->scales[idx + 1])
|
||||
break;
|
||||
}
|
||||
if (idx == gyro_st->scales_len)
|
||||
return -EINVAL;
|
||||
|
||||
conf.fs = idx / 2;
|
||||
|
||||
/* Full scale register starts at 1 for not High FSR parts */
|
||||
if (gyro_st->scales == (const int *)&inv_icm45600_gyro_scale)
|
||||
conf.fs++;
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = inv_icm45600_set_gyro_conf(st, &conf, NULL);
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* IIO format int + micro */
|
||||
static const int inv_icm45600_gyro_odr[] = {
|
||||
1, 562500, /* 1.5625Hz */
|
||||
3, 125000, /* 3.125Hz */
|
||||
6, 250000, /* 6.25Hz */
|
||||
12, 500000, /* 12.5Hz */
|
||||
25, 0, /* 25Hz */
|
||||
50, 0, /* 50Hz */
|
||||
100, 0, /* 100Hz */
|
||||
200, 0, /* 200Hz */
|
||||
400, 0, /* 400Hz */
|
||||
800, 0, /* 800Hz */
|
||||
1600, 0, /* 1.6kHz */
|
||||
3200, 0, /* 3.2kHz */
|
||||
6400, 0, /* 6.4kHz */
|
||||
};
|
||||
|
||||
static const int inv_icm45600_gyro_odr_conv[] = {
|
||||
INV_ICM45600_ODR_1_5625HZ_LP,
|
||||
INV_ICM45600_ODR_3_125HZ_LP,
|
||||
INV_ICM45600_ODR_6_25HZ_LP,
|
||||
INV_ICM45600_ODR_12_5HZ,
|
||||
INV_ICM45600_ODR_25HZ,
|
||||
INV_ICM45600_ODR_50HZ,
|
||||
INV_ICM45600_ODR_100HZ,
|
||||
INV_ICM45600_ODR_200HZ,
|
||||
INV_ICM45600_ODR_400HZ,
|
||||
INV_ICM45600_ODR_800HZ_LN,
|
||||
INV_ICM45600_ODR_1600HZ_LN,
|
||||
INV_ICM45600_ODR_3200HZ_LN,
|
||||
INV_ICM45600_ODR_6400HZ_LN,
|
||||
};
|
||||
|
||||
static int inv_icm45600_gyro_read_odr(struct inv_icm45600_state *st,
|
||||
int *val, int *val2)
|
||||
{
|
||||
unsigned int odr;
|
||||
unsigned int i;
|
||||
|
||||
odr = st->conf.gyro.odr;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(inv_icm45600_gyro_odr_conv); ++i) {
|
||||
if (inv_icm45600_gyro_odr_conv[i] == odr)
|
||||
break;
|
||||
}
|
||||
if (i >= ARRAY_SIZE(inv_icm45600_gyro_odr_conv))
|
||||
return -EINVAL;
|
||||
|
||||
*val = inv_icm45600_gyro_odr[2 * i];
|
||||
*val2 = inv_icm45600_gyro_odr[2 * i + 1];
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
|
||||
static int _inv_icm45600_gyro_write_odr(struct iio_dev *indio_dev, int odr)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
|
||||
struct inv_sensors_timestamp *ts = &gyro_st->ts;
|
||||
struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES;
|
||||
int ret;
|
||||
|
||||
conf.odr = odr;
|
||||
ret = inv_sensors_timestamp_update_odr(ts, inv_icm45600_odr_to_period(conf.odr),
|
||||
iio_buffer_enabled(indio_dev));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (st->conf.gyro.mode != INV_ICM45600_SENSOR_MODE_OFF)
|
||||
conf.mode = gyro_st->power_mode;
|
||||
|
||||
ret = inv_icm45600_set_gyro_conf(st, &conf, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
inv_icm45600_buffer_update_fifo_period(st);
|
||||
inv_icm45600_buffer_update_watermark(st);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int inv_icm45600_gyro_write_odr(struct iio_dev *indio_dev,
|
||||
int val, int val2)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
unsigned int idx;
|
||||
int odr;
|
||||
int ret;
|
||||
|
||||
for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_gyro_odr); idx += 2) {
|
||||
if (val == inv_icm45600_gyro_odr[idx] &&
|
||||
val2 == inv_icm45600_gyro_odr[idx + 1])
|
||||
break;
|
||||
}
|
||||
if (idx >= ARRAY_SIZE(inv_icm45600_gyro_odr))
|
||||
return -EINVAL;
|
||||
|
||||
odr = inv_icm45600_gyro_odr_conv[idx / 2];
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = _inv_icm45600_gyro_write_odr(indio_dev, odr);
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calibration bias values, IIO range format int + nano.
|
||||
* Value is limited to +/-62.5dps coded on 14 bits signed. Step is 7.5mdps.
|
||||
*/
|
||||
static int inv_icm45600_gyro_calibbias[] = {
|
||||
-1, 90830336, /* min: -1.090830336 rad/s */
|
||||
0, 133158, /* step: 0.000133158 rad/s */
|
||||
1, 90697178, /* max: 1.090697178 rad/s */
|
||||
};
|
||||
|
||||
static int inv_icm45600_gyro_read_offset(struct inv_icm45600_state *st,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2)
|
||||
{
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
s64 val64;
|
||||
s32 bias;
|
||||
unsigned int reg;
|
||||
s16 offset;
|
||||
int ret;
|
||||
|
||||
if (chan->type != IIO_ANGL_VEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
reg = INV_ICM45600_IPREG_SYS1_REG_42;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
reg = INV_ICM45600_IPREG_SYS1_REG_56;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
reg = INV_ICM45600_IPREG_SYS1_REG_70;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16));
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
offset = le16_to_cpup(&st->buffer.u16) & INV_ICM45600_GYRO_OFFUSER_MASK;
|
||||
/* 14 bits signed value */
|
||||
offset = sign_extend32(offset, 13);
|
||||
|
||||
/*
|
||||
* convert raw offset to dps then to rad/s
|
||||
* 14 bits signed raw max 62.5 to dps: 625 / 81920
|
||||
* dps to rad: Pi / 180
|
||||
* result in nano (1000000000)
|
||||
* (offset * 625 * Pi * 1000000000) / (81920 * 180)
|
||||
*/
|
||||
val64 = (s64)offset * 625LL * 3141592653LL;
|
||||
/* for rounding, add + or - divisor (81920 * 180) divided by 2 */
|
||||
if (val64 >= 0)
|
||||
val64 += 81920 * 180 / 2;
|
||||
else
|
||||
val64 -= 81920 * 180 / 2;
|
||||
bias = div_s64(val64, 81920 * 180);
|
||||
*val = bias / 1000000000L;
|
||||
*val2 = bias % 1000000000L;
|
||||
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
}
|
||||
|
||||
static int inv_icm45600_gyro_write_offset(struct inv_icm45600_state *st,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2)
|
||||
{
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
s64 val64, min, max;
|
||||
unsigned int reg;
|
||||
s16 offset;
|
||||
int ret;
|
||||
|
||||
if (chan->type != IIO_ANGL_VEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
reg = INV_ICM45600_IPREG_SYS1_REG_42;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
reg = INV_ICM45600_IPREG_SYS1_REG_56;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
reg = INV_ICM45600_IPREG_SYS1_REG_70;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* inv_icm45600_gyro_calibbias: min - step - max in nano */
|
||||
min = (s64)inv_icm45600_gyro_calibbias[0] * 1000000000LL -
|
||||
(s64)inv_icm45600_gyro_calibbias[1];
|
||||
max = (s64)inv_icm45600_gyro_calibbias[4] * 1000000000LL +
|
||||
(s64)inv_icm45600_gyro_calibbias[5];
|
||||
val64 = (s64)val * 1000000000LL;
|
||||
if (val >= 0)
|
||||
val64 += (s64)val2;
|
||||
else
|
||||
val64 -= (s64)val2;
|
||||
if (val64 < min || val64 > max)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* convert rad/s to dps then to raw value
|
||||
* rad to dps: 180 / Pi
|
||||
* dps to raw 14 bits signed, max 62.5: 8192 / 62.5
|
||||
* val in nano (1000000000)
|
||||
* val * 180 * 8192 / (Pi * 1000000000 * 62.5)
|
||||
*/
|
||||
val64 = val64 * 180LL * 8192;
|
||||
/* for rounding, add + or - divisor (314159265 * 625) divided by 2 */
|
||||
if (val64 >= 0)
|
||||
val64 += 314159265LL * 625LL / 2LL;
|
||||
else
|
||||
val64 -= 314159265LL * 625LL / 2LL;
|
||||
offset = div64_s64(val64, 314159265LL * 625LL);
|
||||
|
||||
/* clamp value limited to 14 bits signed */
|
||||
offset = clamp(offset, -8192, 8191);
|
||||
|
||||
st->buffer.u16 = cpu_to_le16(offset & INV_ICM45600_GYRO_OFFUSER_MASK);
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
scoped_guard(mutex, &st->lock)
|
||||
ret = regmap_bulk_write(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16));
|
||||
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int inv_icm45600_gyro_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_ANGL_VEL:
|
||||
break;
|
||||
case IIO_TEMP:
|
||||
return inv_icm45600_temp_read_raw(indio_dev, chan, val, val2, mask);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (!iio_device_claim_direct(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = inv_icm45600_gyro_read_sensor(indio_dev, chan, val);
|
||||
iio_device_release_direct(indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
return inv_icm45600_gyro_read_scale(indio_dev, val, val2);
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return inv_icm45600_gyro_read_odr(st, val, val2);
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
return inv_icm45600_gyro_read_offset(st, chan, val, val2);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int inv_icm45600_gyro_read_avail(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
const int **vals,
|
||||
int *type, int *length, long mask)
|
||||
{
|
||||
struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
|
||||
|
||||
if (chan->type != IIO_ANGL_VEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*vals = gyro_st->scales;
|
||||
*type = IIO_VAL_INT_PLUS_NANO;
|
||||
*length = gyro_st->scales_len;
|
||||
return IIO_AVAIL_LIST;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*vals = inv_icm45600_gyro_odr;
|
||||
*type = IIO_VAL_INT_PLUS_MICRO;
|
||||
*length = ARRAY_SIZE(inv_icm45600_gyro_odr);
|
||||
return IIO_AVAIL_LIST;
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
*vals = inv_icm45600_gyro_calibbias;
|
||||
*type = IIO_VAL_INT_PLUS_NANO;
|
||||
return IIO_AVAIL_RANGE;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int inv_icm45600_gyro_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (chan->type != IIO_ANGL_VEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
if (!iio_device_claim_direct(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = inv_icm45600_gyro_write_scale(indio_dev, val, val2);
|
||||
iio_device_release_direct(indio_dev);
|
||||
return ret;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return inv_icm45600_gyro_write_odr(indio_dev, val, val2);
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
if (!iio_device_claim_direct(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = inv_icm45600_gyro_write_offset(st, chan, val, val2);
|
||||
iio_device_release_direct(indio_dev);
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int inv_icm45600_gyro_write_raw_get_fmt(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
long mask)
|
||||
{
|
||||
if (chan->type != IIO_ANGL_VEL)
|
||||
return -EINVAL;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_CALIBBIAS:
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int inv_icm45600_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev,
|
||||
unsigned int val)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
st->fifo.watermark.gyro = val;
|
||||
return inv_icm45600_buffer_update_watermark(st);
|
||||
}
|
||||
|
||||
static int inv_icm45600_gyro_hwfifo_flush(struct iio_dev *indio_dev,
|
||||
unsigned int count)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
int ret;
|
||||
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
guard(mutex)(&st->lock);
|
||||
|
||||
ret = inv_icm45600_buffer_hwfifo_flush(st, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return st->fifo.nb.gyro;
|
||||
}
|
||||
|
||||
static const struct iio_info inv_icm45600_gyro_info = {
|
||||
.read_raw = inv_icm45600_gyro_read_raw,
|
||||
.read_avail = inv_icm45600_gyro_read_avail,
|
||||
.write_raw = inv_icm45600_gyro_write_raw,
|
||||
.write_raw_get_fmt = inv_icm45600_gyro_write_raw_get_fmt,
|
||||
.debugfs_reg_access = inv_icm45600_debugfs_reg,
|
||||
.update_scan_mode = inv_icm45600_gyro_update_scan_mode,
|
||||
.hwfifo_set_watermark = inv_icm45600_gyro_hwfifo_set_watermark,
|
||||
.hwfifo_flush_to_buffer = inv_icm45600_gyro_hwfifo_flush,
|
||||
};
|
||||
|
||||
struct iio_dev *inv_icm45600_gyro_init(struct inv_icm45600_state *st)
|
||||
{
|
||||
struct device *dev = regmap_get_device(st->map);
|
||||
struct inv_icm45600_sensor_state *gyro_st;
|
||||
struct inv_sensors_timestamp_chip ts_chip;
|
||||
struct iio_dev *indio_dev;
|
||||
const char *name;
|
||||
int ret;
|
||||
|
||||
name = devm_kasprintf(dev, GFP_KERNEL, "%s-gyro", st->chip_info->name);
|
||||
if (!name)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*gyro_st));
|
||||
if (!indio_dev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
gyro_st = iio_priv(indio_dev);
|
||||
|
||||
gyro_st->scales = st->chip_info->gyro_scales;
|
||||
gyro_st->scales_len = st->chip_info->gyro_scales_len * 2;
|
||||
|
||||
/* low-noise by default at init */
|
||||
gyro_st->power_mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE;
|
||||
|
||||
/*
|
||||
* clock period is 32kHz (31250ns)
|
||||
* jitter is +/- 2% (20 per mille)
|
||||
*/
|
||||
ts_chip.clock_period = 31250;
|
||||
ts_chip.jitter = 20;
|
||||
ts_chip.init_period = inv_icm45600_odr_to_period(st->conf.gyro.odr);
|
||||
inv_sensors_timestamp_init(&gyro_st->ts, &ts_chip);
|
||||
|
||||
iio_device_set_drvdata(indio_dev, st);
|
||||
indio_dev->name = name;
|
||||
indio_dev->info = &inv_icm45600_gyro_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->channels = inv_icm45600_gyro_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(inv_icm45600_gyro_channels);
|
||||
indio_dev->available_scan_masks = inv_icm45600_gyro_scan_masks;
|
||||
indio_dev->setup_ops = &inv_icm45600_buffer_ops;
|
||||
|
||||
ret = devm_iio_kfifo_buffer_setup(dev, indio_dev,
|
||||
&inv_icm45600_buffer_ops);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
ret = devm_iio_device_register(dev, indio_dev);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
return indio_dev;
|
||||
}
|
||||
|
||||
int inv_icm45600_gyro_parse_fifo(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev);
|
||||
struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev);
|
||||
struct inv_sensors_timestamp *ts = &gyro_st->ts;
|
||||
ssize_t i, size;
|
||||
unsigned int no;
|
||||
|
||||
/* parse all fifo packets */
|
||||
for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) {
|
||||
struct inv_icm45600_gyro_buffer buffer = { };
|
||||
const struct inv_icm45600_fifo_sensor_data *accel, *gyro;
|
||||
const __le16 *timestamp;
|
||||
const s8 *temp;
|
||||
unsigned int odr;
|
||||
s64 ts_val;
|
||||
|
||||
size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i],
|
||||
&accel, &gyro, &temp, ×tamp, &odr);
|
||||
/* quit if error or FIFO is empty */
|
||||
if (size <= 0)
|
||||
return size;
|
||||
|
||||
/* skip packet if no gyro data or data is invalid */
|
||||
if (gyro == NULL || !inv_icm45600_fifo_is_data_valid(gyro))
|
||||
continue;
|
||||
|
||||
/* update odr */
|
||||
if (odr & INV_ICM45600_SENSOR_GYRO)
|
||||
inv_sensors_timestamp_apply_odr(ts, st->fifo.period,
|
||||
st->fifo.nb.total, no);
|
||||
|
||||
memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro));
|
||||
/* convert 8 bits FIFO temperature in high resolution format */
|
||||
buffer.temp = temp ? (*temp * 64) : 0;
|
||||
ts_val = inv_sensors_timestamp_pop(ts);
|
||||
iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), ts_val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
98
drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c
Normal file
98
drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c
Normal file
@@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/* Copyright (C) 2025 InvenSense, Inc. */
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "inv_icm45600.h"
|
||||
|
||||
static const struct regmap_config inv_icm45600_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static int inv_icm45600_probe(struct i2c_client *client)
|
||||
{
|
||||
const struct inv_icm45600_chip_info *chip_info;
|
||||
struct regmap *regmap;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
|
||||
return -ENODEV;
|
||||
|
||||
chip_info = device_get_match_data(&client->dev);
|
||||
if (!chip_info)
|
||||
return -ENODEV;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &inv_icm45600_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return PTR_ERR(regmap);
|
||||
|
||||
return inv_icm45600_core_probe(regmap, chip_info, true, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* The device id table is used to identify which device is
|
||||
* supported by this driver.
|
||||
*/
|
||||
static const struct i2c_device_id inv_icm45600_id[] = {
|
||||
{ "icm45605", (kernel_ulong_t)&inv_icm45605_chip_info },
|
||||
{ "icm45606", (kernel_ulong_t)&inv_icm45606_chip_info },
|
||||
{ "icm45608", (kernel_ulong_t)&inv_icm45608_chip_info },
|
||||
{ "icm45634", (kernel_ulong_t)&inv_icm45634_chip_info },
|
||||
{ "icm45686", (kernel_ulong_t)&inv_icm45686_chip_info },
|
||||
{ "icm45687", (kernel_ulong_t)&inv_icm45687_chip_info },
|
||||
{ "icm45688p", (kernel_ulong_t)&inv_icm45688p_chip_info },
|
||||
{ "icm45689", (kernel_ulong_t)&inv_icm45689_chip_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, inv_icm45600_id);
|
||||
|
||||
static const struct of_device_id inv_icm45600_of_matches[] = {
|
||||
{
|
||||
.compatible = "invensense,icm45605",
|
||||
.data = &inv_icm45605_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45606",
|
||||
.data = &inv_icm45606_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45608",
|
||||
.data = &inv_icm45608_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45634",
|
||||
.data = &inv_icm45634_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45686",
|
||||
.data = &inv_icm45686_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45687",
|
||||
.data = &inv_icm45687_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45688p",
|
||||
.data = &inv_icm45688p_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45689",
|
||||
.data = &inv_icm45689_chip_info,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, inv_icm45600_of_matches);
|
||||
|
||||
static struct i2c_driver inv_icm45600_driver = {
|
||||
.driver = {
|
||||
.name = "inv-icm45600-i2c",
|
||||
.of_match_table = inv_icm45600_of_matches,
|
||||
.pm = pm_ptr(&inv_icm45600_pm_ops),
|
||||
},
|
||||
.id_table = inv_icm45600_id,
|
||||
.probe = inv_icm45600_probe,
|
||||
};
|
||||
module_i2c_driver(inv_icm45600_driver);
|
||||
|
||||
MODULE_AUTHOR("InvenSense, Inc.");
|
||||
MODULE_DESCRIPTION("InvenSense ICM-456xx I2C driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("IIO_ICM45600");
|
||||
79
drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c
Normal file
79
drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/* Copyright (C) 2025 InvenSense, Inc. */
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <linux/i3c/device.h>
|
||||
#include <linux/i3c/master.h>
|
||||
|
||||
#include "inv_icm45600.h"
|
||||
|
||||
static const struct regmap_config inv_icm45600_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static const struct i3c_device_id inv_icm45600_i3c_ids[] = {
|
||||
I3C_DEVICE_EXTRA_INFO(0x0235, 0x0000, 0x0011, (void *)NULL),
|
||||
I3C_DEVICE_EXTRA_INFO(0x0235, 0x0000, 0x0084, (void *)NULL),
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i3c, inv_icm45600_i3c_ids);
|
||||
|
||||
static const struct inv_icm45600_chip_info *i3c_chip_info[] = {
|
||||
&inv_icm45605_chip_info,
|
||||
&inv_icm45606_chip_info,
|
||||
&inv_icm45608_chip_info,
|
||||
&inv_icm45634_chip_info,
|
||||
&inv_icm45686_chip_info,
|
||||
&inv_icm45687_chip_info,
|
||||
&inv_icm45688p_chip_info,
|
||||
&inv_icm45689_chip_info,
|
||||
};
|
||||
|
||||
static int inv_icm45600_i3c_probe(struct i3c_device *i3cdev)
|
||||
{
|
||||
int ret;
|
||||
unsigned int whoami;
|
||||
struct regmap *regmap;
|
||||
const int nb_chip = ARRAY_SIZE(i3c_chip_info);
|
||||
int chip;
|
||||
|
||||
regmap = devm_regmap_init_i3c(i3cdev, &inv_icm45600_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(&i3cdev->dev, PTR_ERR(regmap),
|
||||
"Failed to register i3c regmap %ld\n", PTR_ERR(regmap));
|
||||
|
||||
ret = regmap_read(regmap, INV_ICM45600_REG_WHOAMI, &whoami);
|
||||
if (ret)
|
||||
return dev_err_probe(&i3cdev->dev, ret, "Failed to read part id %d\n", whoami);
|
||||
|
||||
for (chip = 0; chip < nb_chip; chip++) {
|
||||
if (whoami == i3c_chip_info[chip]->whoami)
|
||||
break;
|
||||
}
|
||||
|
||||
if (chip == nb_chip)
|
||||
return dev_err_probe(&i3cdev->dev, -ENODEV,
|
||||
"Failed to match part id %d\n", whoami);
|
||||
|
||||
return inv_icm45600_core_probe(regmap, i3c_chip_info[chip], false, NULL);
|
||||
}
|
||||
|
||||
static struct i3c_driver inv_icm45600_driver = {
|
||||
.driver = {
|
||||
.name = "inv_icm45600_i3c",
|
||||
.pm = pm_sleep_ptr(&inv_icm45600_pm_ops),
|
||||
},
|
||||
.probe = inv_icm45600_i3c_probe,
|
||||
.id_table = inv_icm45600_i3c_ids,
|
||||
};
|
||||
module_i3c_driver(inv_icm45600_driver);
|
||||
|
||||
MODULE_AUTHOR("Remi Buisson <remi.buisson@tdk.com>");
|
||||
MODULE_DESCRIPTION("InvenSense ICM-456xx i3c driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("IIO_ICM45600");
|
||||
108
drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c
Normal file
108
drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c
Normal file
@@ -0,0 +1,108 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/* Copyright (C) 2025 InvenSense, Inc. */
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include "inv_icm45600.h"
|
||||
|
||||
static const struct regmap_config inv_icm45600_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static int inv_icm45600_spi_bus_setup(struct inv_icm45600_state *st)
|
||||
{
|
||||
/* Set slew rates for SPI. */
|
||||
return regmap_update_bits(st->map, INV_ICM45600_REG_DRIVE_CONFIG0,
|
||||
INV_ICM45600_DRIVE_CONFIG0_SPI_MASK,
|
||||
FIELD_PREP(INV_ICM45600_DRIVE_CONFIG0_SPI_MASK,
|
||||
INV_ICM45600_SPI_SLEW_RATE_5NS));
|
||||
}
|
||||
|
||||
static int inv_icm45600_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct inv_icm45600_chip_info *chip_info;
|
||||
struct regmap *regmap;
|
||||
|
||||
chip_info = spi_get_device_match_data(spi);
|
||||
if (!chip_info)
|
||||
return -ENODEV;
|
||||
|
||||
/* Use SPI specific regmap. */
|
||||
regmap = devm_regmap_init_spi(spi, &inv_icm45600_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return PTR_ERR(regmap);
|
||||
|
||||
return inv_icm45600_core_probe(regmap, chip_info, true,
|
||||
inv_icm45600_spi_bus_setup);
|
||||
}
|
||||
|
||||
/*
|
||||
* The device id table is used to identify which device is
|
||||
* supported by this driver.
|
||||
*/
|
||||
static const struct spi_device_id inv_icm45600_id[] = {
|
||||
{ "icm45605", (kernel_ulong_t)&inv_icm45605_chip_info },
|
||||
{ "icm45606", (kernel_ulong_t)&inv_icm45606_chip_info },
|
||||
{ "icm45608", (kernel_ulong_t)&inv_icm45608_chip_info },
|
||||
{ "icm45634", (kernel_ulong_t)&inv_icm45634_chip_info },
|
||||
{ "icm45686", (kernel_ulong_t)&inv_icm45686_chip_info },
|
||||
{ "icm45687", (kernel_ulong_t)&inv_icm45687_chip_info },
|
||||
{ "icm45688p", (kernel_ulong_t)&inv_icm45688p_chip_info },
|
||||
{ "icm45689", (kernel_ulong_t)&inv_icm45689_chip_info },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, inv_icm45600_id);
|
||||
|
||||
static const struct of_device_id inv_icm45600_of_matches[] = {
|
||||
{
|
||||
.compatible = "invensense,icm45605",
|
||||
.data = &inv_icm45605_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45606",
|
||||
.data = &inv_icm45606_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45608",
|
||||
.data = &inv_icm45608_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45634",
|
||||
.data = &inv_icm45634_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45686",
|
||||
.data = &inv_icm45686_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45687",
|
||||
.data = &inv_icm45687_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45688p",
|
||||
.data = &inv_icm45688p_chip_info,
|
||||
}, {
|
||||
.compatible = "invensense,icm45689",
|
||||
.data = &inv_icm45689_chip_info,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, inv_icm45600_of_matches);
|
||||
|
||||
static struct spi_driver inv_icm45600_driver = {
|
||||
.driver = {
|
||||
.name = "inv-icm45600-spi",
|
||||
.of_match_table = inv_icm45600_of_matches,
|
||||
.pm = pm_ptr(&inv_icm45600_pm_ops),
|
||||
},
|
||||
.id_table = inv_icm45600_id,
|
||||
.probe = inv_icm45600_probe,
|
||||
};
|
||||
module_spi_driver(inv_icm45600_driver);
|
||||
|
||||
MODULE_AUTHOR("InvenSense, Inc.");
|
||||
MODULE_DESCRIPTION("InvenSense ICM-456xx SPI driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_IMPORT_NS("IIO_ICM45600");
|
||||
33
drivers/iio/imu/smi330/Kconfig
Normal file
33
drivers/iio/imu/smi330/Kconfig
Normal file
@@ -0,0 +1,33 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# SMI330 IMU driver
|
||||
#
|
||||
|
||||
config SMI330
|
||||
tristate
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
|
||||
config SMI330_I2C
|
||||
tristate "Bosch SMI330 I2C driver"
|
||||
depends on I2C
|
||||
select SMI330
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Enable support for the Bosch SMI330 6-Axis IMU connected to I2C
|
||||
interface.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called smi330_i2c.
|
||||
|
||||
config SMI330_SPI
|
||||
tristate "Bosch SMI330 SPI driver"
|
||||
depends on SPI
|
||||
select SMI330
|
||||
select REGMAP_SPI
|
||||
help
|
||||
Enable support for the Bosch SMI330 6-Axis IMU connected to SPI
|
||||
interface.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called smi330_spi.
|
||||
7
drivers/iio/imu/smi330/Makefile
Normal file
7
drivers/iio/imu/smi330/Makefile
Normal file
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for Bosch SMI330 IMU
|
||||
#
|
||||
obj-$(CONFIG_SMI330) += smi330_core.o
|
||||
obj-$(CONFIG_SMI330_I2C) += smi330_i2c.o
|
||||
obj-$(CONFIG_SMI330_SPI) += smi330_spi.o
|
||||
25
drivers/iio/imu/smi330/smi330.h
Normal file
25
drivers/iio/imu/smi330/smi330.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */
|
||||
/*
|
||||
* Copyright (c) 2025 Robert Bosch GmbH.
|
||||
*/
|
||||
#ifndef _SMI330_H
|
||||
#define _SMI330_H
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
enum {
|
||||
SMI330_SCAN_ACCEL_X,
|
||||
SMI330_SCAN_ACCEL_Y,
|
||||
SMI330_SCAN_ACCEL_Z,
|
||||
SMI330_SCAN_GYRO_X,
|
||||
SMI330_SCAN_GYRO_Y,
|
||||
SMI330_SCAN_GYRO_Z,
|
||||
SMI330_SCAN_TIMESTAMP,
|
||||
SMI330_SCAN_LEN = SMI330_SCAN_TIMESTAMP,
|
||||
};
|
||||
|
||||
extern const struct regmap_config smi330_regmap_config;
|
||||
|
||||
int smi330_core_probe(struct device *dev, struct regmap *regmap);
|
||||
|
||||
#endif /* _SMI330_H */
|
||||
918
drivers/iio/imu/smi330/smi330_core.c
Normal file
918
drivers/iio/imu/smi330/smi330_core.c
Normal file
@@ -0,0 +1,918 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2025 Robert Bosch GmbH.
|
||||
*/
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
#include "smi330.h"
|
||||
|
||||
/* Register map */
|
||||
#define SMI330_CHIP_ID_REG 0x00
|
||||
#define SMI330_ERR_REG 0x01
|
||||
#define SMI330_STATUS_REG 0x02
|
||||
#define SMI330_ACCEL_X_REG 0x03
|
||||
#define SMI330_GYRO_X_REG 0x06
|
||||
#define SMI330_TEMP_REG 0x09
|
||||
#define SMI330_INT1_STATUS_REG 0x0D
|
||||
#define SMI330_ACCEL_CFG_REG 0x20
|
||||
#define SMI330_GYRO_CFG_REG 0x21
|
||||
#define SMI330_IO_INT_CTRL_REG 0x38
|
||||
#define SMI330_INT_CONF_REG 0x39
|
||||
#define SMI330_INT_MAP1_REG 0x3A
|
||||
#define SMI330_INT_MAP2_REG 0x3B
|
||||
#define SMI330_CMD_REG 0x7E
|
||||
|
||||
/* Register mask */
|
||||
#define SMI330_CHIP_ID_MASK GENMASK(7, 0)
|
||||
#define SMI330_ERR_FATAL_MASK BIT(0)
|
||||
#define SMI330_ERR_ACC_CONF_MASK BIT(5)
|
||||
#define SMI330_ERR_GYR_CONF_MASK BIT(6)
|
||||
#define SMI330_STATUS_POR_MASK BIT(0)
|
||||
#define SMI330_INT_STATUS_ACC_GYR_DRDY_MASK GENMASK(13, 12)
|
||||
#define SMI330_CFG_ODR_MASK GENMASK(3, 0)
|
||||
#define SMI330_CFG_RANGE_MASK GENMASK(6, 4)
|
||||
#define SMI330_CFG_BW_MASK BIT(7)
|
||||
#define SMI330_CFG_AVG_NUM_MASK GENMASK(10, 8)
|
||||
#define SMI330_CFG_MODE_MASK GENMASK(14, 12)
|
||||
#define SMI330_IO_INT_CTRL_INT1_MASK GENMASK(2, 0)
|
||||
#define SMI330_IO_INT_CTRL_INT2_MASK GENMASK(10, 8)
|
||||
#define SMI330_INT_CONF_LATCH_MASK BIT(0)
|
||||
#define SMI330_INT_MAP2_ACC_DRDY_MASK GENMASK(11, 10)
|
||||
#define SMI330_INT_MAP2_GYR_DRDY_MASK GENMASK(9, 8)
|
||||
|
||||
/* Register values */
|
||||
#define SMI330_IO_INT_CTRL_LVL BIT(0)
|
||||
#define SMI330_IO_INT_CTRL_OD BIT(1)
|
||||
#define SMI330_IO_INT_CTRL_EN BIT(2)
|
||||
#define SMI330_CMD_SOFT_RESET 0xDEAF
|
||||
|
||||
/* T°C = (temp / 512) + 23 */
|
||||
#define SMI330_TEMP_OFFSET 11776 /* 23 * 512 */
|
||||
#define SMI330_TEMP_SCALE 1953125 /* (1 / 512) * 1e9 */
|
||||
|
||||
#define SMI330_CHIP_ID 0x42
|
||||
#define SMI330_SOFT_RESET_DELAY 2000
|
||||
|
||||
/* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */
|
||||
#define smi330_field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1))
|
||||
#define smi330_field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
|
||||
|
||||
#define SMI330_ACCEL_CHANNEL(_axis) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##_axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \
|
||||
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
|
||||
.info_mask_shared_by_type_available = \
|
||||
BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \
|
||||
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
|
||||
.info_mask_shared_by_dir_available = \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.scan_index = SMI330_SCAN_ACCEL_##_axis, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define SMI330_GYRO_CHANNEL(_axis) { \
|
||||
.type = IIO_ANGL_VEL, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##_axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \
|
||||
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
|
||||
.info_mask_shared_by_type_available = \
|
||||
BIT(IIO_CHAN_INFO_SCALE) | \
|
||||
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \
|
||||
BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \
|
||||
.info_mask_shared_by_dir_available = \
|
||||
BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.scan_index = SMI330_SCAN_GYRO_##_axis, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define SMI330_TEMP_CHANNEL(_index) { \
|
||||
.type = IIO_TEMP, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_OFFSET) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.scan_index = _index, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
}
|
||||
|
||||
enum smi330_accel_range {
|
||||
SMI330_ACCEL_RANGE_2G = 0x00,
|
||||
SMI330_ACCEL_RANGE_4G = 0x01,
|
||||
SMI330_ACCEL_RANGE_8G = 0x02,
|
||||
SMI330_ACCEL_RANGE_16G = 0x03
|
||||
};
|
||||
|
||||
enum smi330_gyro_range {
|
||||
SMI330_GYRO_RANGE_125 = 0x0,
|
||||
SMI330_GYRO_RANGE_250 = 0x01,
|
||||
SMI330_GYRO_RANGE_500 = 0x02
|
||||
};
|
||||
|
||||
enum smi330_odr {
|
||||
SMI330_ODR_12_5_HZ = 0x05,
|
||||
SMI330_ODR_25_HZ = 0x06,
|
||||
SMI330_ODR_50_HZ = 0x07,
|
||||
SMI330_ODR_100_HZ = 0x08,
|
||||
SMI330_ODR_200_HZ = 0x09,
|
||||
SMI330_ODR_400_HZ = 0x0A,
|
||||
SMI330_ODR_800_HZ = 0x0B,
|
||||
SMI330_ODR_1600_HZ = 0x0C,
|
||||
SMI330_ODR_3200_HZ = 0x0D,
|
||||
SMI330_ODR_6400_HZ = 0x0E
|
||||
};
|
||||
|
||||
enum smi330_avg_num {
|
||||
SMI330_AVG_NUM_1 = 0x00,
|
||||
SMI330_AVG_NUM_2 = 0x01,
|
||||
SMI330_AVG_NUM_4 = 0x02,
|
||||
SMI330_AVG_NUM_8 = 0x03,
|
||||
SMI330_AVG_NUM_16 = 0x04,
|
||||
SMI330_AVG_NUM_32 = 0x05,
|
||||
SMI330_AVG_NUM_64 = 0x06
|
||||
};
|
||||
|
||||
enum smi330_mode {
|
||||
SMI330_MODE_SUSPEND = 0x00,
|
||||
SMI330_MODE_GYRO_DRIVE = 0x01,
|
||||
SMI330_MODE_LOW_POWER = 0x03,
|
||||
SMI330_MODE_NORMAL = 0x04,
|
||||
SMI330_MODE_HIGH_PERF = 0x07
|
||||
};
|
||||
|
||||
enum smi330_bw {
|
||||
SMI330_BW_2 = 0x00, /* ODR/2 */
|
||||
SMI330_BW_4 = 0x01 /* ODR/4 */
|
||||
};
|
||||
|
||||
enum smi330_operation_mode {
|
||||
SMI330_POLLING,
|
||||
SMI330_DATA_READY,
|
||||
};
|
||||
|
||||
enum smi330_sensor {
|
||||
SMI330_ACCEL,
|
||||
SMI330_GYRO,
|
||||
};
|
||||
|
||||
enum smi330_sensor_conf_select {
|
||||
SMI330_ODR,
|
||||
SMI330_RANGE,
|
||||
SMI330_BW,
|
||||
SMI330_AVG_NUM,
|
||||
};
|
||||
|
||||
enum smi330_int_out {
|
||||
SMI330_INT_DISABLED,
|
||||
SMI330_INT_1,
|
||||
SMI330_INT_2,
|
||||
};
|
||||
|
||||
struct smi330_attributes {
|
||||
int *reg_vals;
|
||||
int *vals;
|
||||
int len;
|
||||
int type;
|
||||
int mask;
|
||||
};
|
||||
|
||||
struct smi330_cfg {
|
||||
enum smi330_operation_mode op_mode;
|
||||
enum smi330_int_out data_irq;
|
||||
};
|
||||
|
||||
struct smi330_data {
|
||||
struct regmap *regmap;
|
||||
struct smi330_cfg cfg;
|
||||
struct iio_trigger *trig;
|
||||
IIO_DECLARE_BUFFER_WITH_TS(__le16, buf, SMI330_SCAN_LEN);
|
||||
};
|
||||
|
||||
const struct regmap_config smi330_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 16,
|
||||
.val_format_endian = REGMAP_ENDIAN_LITTLE,
|
||||
};
|
||||
EXPORT_SYMBOL_NS_GPL(smi330_regmap_config, "IIO_SMI330");
|
||||
|
||||
static const struct iio_chan_spec smi330_channels[] = {
|
||||
SMI330_ACCEL_CHANNEL(X),
|
||||
SMI330_ACCEL_CHANNEL(Y),
|
||||
SMI330_ACCEL_CHANNEL(Z),
|
||||
SMI330_GYRO_CHANNEL(X),
|
||||
SMI330_GYRO_CHANNEL(Y),
|
||||
SMI330_GYRO_CHANNEL(Z),
|
||||
SMI330_TEMP_CHANNEL(-1), /* No buffer support */
|
||||
IIO_CHAN_SOFT_TIMESTAMP(SMI330_SCAN_TIMESTAMP),
|
||||
};
|
||||
|
||||
static const unsigned long smi330_avail_scan_masks[] = {
|
||||
(BIT(SMI330_SCAN_ACCEL_X) | BIT(SMI330_SCAN_ACCEL_Y) |
|
||||
BIT(SMI330_SCAN_ACCEL_Z) | BIT(SMI330_SCAN_GYRO_X) |
|
||||
BIT(SMI330_SCAN_GYRO_Y) | BIT(SMI330_SCAN_GYRO_Z)),
|
||||
0
|
||||
};
|
||||
|
||||
static const struct smi330_attributes smi330_accel_scale_attr = {
|
||||
.reg_vals = (int[]){ SMI330_ACCEL_RANGE_2G, SMI330_ACCEL_RANGE_4G,
|
||||
SMI330_ACCEL_RANGE_8G, SMI330_ACCEL_RANGE_16G },
|
||||
.vals = (int[]){ 0, 61035, 0, 122070, 0, 244140, 0, 488281 },
|
||||
.len = 8,
|
||||
.type = IIO_VAL_INT_PLUS_NANO,
|
||||
.mask = SMI330_CFG_RANGE_MASK
|
||||
};
|
||||
|
||||
static const struct smi330_attributes smi330_gyro_scale_attr = {
|
||||
.reg_vals = (int[]){ SMI330_GYRO_RANGE_125, SMI330_GYRO_RANGE_250,
|
||||
SMI330_GYRO_RANGE_500 },
|
||||
.vals = (int[]){ 0, 3814697, 0, 7629395, 0, 15258789 },
|
||||
.len = 6,
|
||||
.type = IIO_VAL_INT_PLUS_NANO,
|
||||
.mask = SMI330_CFG_RANGE_MASK
|
||||
};
|
||||
|
||||
static const struct smi330_attributes smi330_average_attr = {
|
||||
.reg_vals = (int[]){ SMI330_AVG_NUM_1, SMI330_AVG_NUM_2,
|
||||
SMI330_AVG_NUM_4, SMI330_AVG_NUM_8,
|
||||
SMI330_AVG_NUM_16, SMI330_AVG_NUM_32,
|
||||
SMI330_AVG_NUM_64 },
|
||||
.vals = (int[]){ 1, 2, 4, 8, 16, 32, 64 },
|
||||
.len = 7,
|
||||
.type = IIO_VAL_INT,
|
||||
.mask = SMI330_CFG_AVG_NUM_MASK
|
||||
};
|
||||
|
||||
static const struct smi330_attributes smi330_bandwidth_attr = {
|
||||
.reg_vals = (int[]){ SMI330_BW_2, SMI330_BW_4 },
|
||||
.vals = (int[]){ 2, 4 },
|
||||
.len = 2,
|
||||
.type = IIO_VAL_INT,
|
||||
.mask = SMI330_CFG_BW_MASK
|
||||
};
|
||||
|
||||
static const struct smi330_attributes smi330_odr_attr = {
|
||||
.reg_vals = (int[]){ SMI330_ODR_12_5_HZ, SMI330_ODR_25_HZ,
|
||||
SMI330_ODR_50_HZ, SMI330_ODR_100_HZ,
|
||||
SMI330_ODR_200_HZ, SMI330_ODR_400_HZ,
|
||||
SMI330_ODR_800_HZ, SMI330_ODR_1600_HZ,
|
||||
SMI330_ODR_3200_HZ, SMI330_ODR_6400_HZ },
|
||||
.vals = (int[]){ 12, 25, 50, 100, 200, 400, 800, 1600, 3200, 6400 },
|
||||
.len = 10,
|
||||
.type = IIO_VAL_INT,
|
||||
.mask = SMI330_CFG_ODR_MASK
|
||||
};
|
||||
|
||||
static int smi330_get_attributes(enum smi330_sensor_conf_select config,
|
||||
enum smi330_sensor sensor,
|
||||
const struct smi330_attributes **attr)
|
||||
{
|
||||
switch (config) {
|
||||
case SMI330_ODR:
|
||||
*attr = &smi330_odr_attr;
|
||||
return 0;
|
||||
case SMI330_RANGE:
|
||||
if (sensor == SMI330_ACCEL)
|
||||
*attr = &smi330_accel_scale_attr;
|
||||
else
|
||||
*attr = &smi330_gyro_scale_attr;
|
||||
return 0;
|
||||
case SMI330_BW:
|
||||
*attr = &smi330_bandwidth_attr;
|
||||
return 0;
|
||||
case SMI330_AVG_NUM:
|
||||
*attr = &smi330_average_attr;
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int smi330_get_config_reg(enum smi330_sensor sensor, int *reg)
|
||||
{
|
||||
switch (sensor) {
|
||||
case SMI330_ACCEL:
|
||||
*reg = SMI330_ACCEL_CFG_REG;
|
||||
return 0;
|
||||
case SMI330_GYRO:
|
||||
*reg = SMI330_GYRO_CFG_REG;
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int smi330_get_sensor_config(struct smi330_data *data,
|
||||
enum smi330_sensor sensor,
|
||||
enum smi330_sensor_conf_select config,
|
||||
int *value)
|
||||
|
||||
{
|
||||
int ret, reg, reg_val, i;
|
||||
const struct smi330_attributes *attr;
|
||||
|
||||
ret = smi330_get_config_reg(sensor, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(data->regmap, reg, ®_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = smi330_get_attributes(config, sensor, &attr);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg_val = smi330_field_get(attr->mask, reg_val);
|
||||
|
||||
if (attr->type == IIO_VAL_INT) {
|
||||
for (i = 0; i < attr->len; i++) {
|
||||
if (attr->reg_vals[i] == reg_val) {
|
||||
*value = attr->vals[i];
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < attr->len / 2; i++) {
|
||||
if (attr->reg_vals[i] == reg_val) {
|
||||
*value = attr->vals[2 * i + 1];
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int smi330_set_sensor_config(struct smi330_data *data,
|
||||
enum smi330_sensor sensor,
|
||||
enum smi330_sensor_conf_select config,
|
||||
int value)
|
||||
{
|
||||
int ret, i, reg, reg_val, error;
|
||||
const struct smi330_attributes *attr;
|
||||
|
||||
ret = smi330_get_attributes(config, sensor, &attr);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < attr->len; i++) {
|
||||
if (attr->vals[i] == value) {
|
||||
if (attr->type == IIO_VAL_INT)
|
||||
reg_val = attr->reg_vals[i];
|
||||
else
|
||||
reg_val = attr->reg_vals[i / 2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == attr->len)
|
||||
return -EINVAL;
|
||||
|
||||
ret = smi330_get_config_reg(sensor, ®);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
reg_val = smi330_field_prep(attr->mask, reg_val);
|
||||
ret = regmap_update_bits(data->regmap, reg, attr->mask, reg_val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(data->regmap, SMI330_ERR_REG, &error);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (FIELD_GET(SMI330_ERR_ACC_CONF_MASK, error) ||
|
||||
FIELD_GET(SMI330_ERR_GYR_CONF_MASK, error))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smi330_get_data(struct smi330_data *data, int chan_type, int axis,
|
||||
int *val)
|
||||
{
|
||||
u8 reg;
|
||||
int ret, sample;
|
||||
|
||||
switch (chan_type) {
|
||||
case IIO_ACCEL:
|
||||
reg = SMI330_ACCEL_X_REG + (axis - IIO_MOD_X);
|
||||
break;
|
||||
case IIO_ANGL_VEL:
|
||||
reg = SMI330_GYRO_X_REG + (axis - IIO_MOD_X);
|
||||
break;
|
||||
case IIO_TEMP:
|
||||
reg = SMI330_TEMP_REG;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = regmap_read(data->regmap, reg, &sample);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = sign_extend32(sample, 15);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smi330_read_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_SCALE:
|
||||
if (chan->type == IIO_ACCEL) {
|
||||
*vals = smi330_accel_scale_attr.vals;
|
||||
*length = smi330_accel_scale_attr.len;
|
||||
*type = smi330_accel_scale_attr.type;
|
||||
} else {
|
||||
*vals = smi330_gyro_scale_attr.vals;
|
||||
*length = smi330_gyro_scale_attr.len;
|
||||
*type = smi330_gyro_scale_attr.type;
|
||||
}
|
||||
return IIO_AVAIL_LIST;
|
||||
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
||||
*vals = smi330_average_attr.vals;
|
||||
*length = smi330_average_attr.len;
|
||||
*type = smi330_average_attr.type;
|
||||
*type = IIO_VAL_INT;
|
||||
return IIO_AVAIL_LIST;
|
||||
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
|
||||
*vals = smi330_bandwidth_attr.vals;
|
||||
*length = smi330_bandwidth_attr.len;
|
||||
*type = smi330_bandwidth_attr.type;
|
||||
return IIO_AVAIL_LIST;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
*vals = smi330_odr_attr.vals;
|
||||
*length = smi330_odr_attr.len;
|
||||
*type = smi330_odr_attr.type;
|
||||
return IIO_AVAIL_LIST;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int smi330_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val,
|
||||
int *val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
struct smi330_data *data = iio_priv(indio_dev);
|
||||
enum smi330_sensor sensor;
|
||||
|
||||
/* valid for all channel types */
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (!iio_device_claim_direct(indio_dev))
|
||||
return -EBUSY;
|
||||
ret = smi330_get_data(data, chan->type, chan->channel2, val);
|
||||
iio_device_release_direct(indio_dev);
|
||||
return ret ? ret : IIO_VAL_INT;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_ACCEL:
|
||||
sensor = SMI330_ACCEL;
|
||||
break;
|
||||
case IIO_ANGL_VEL:
|
||||
sensor = SMI330_GYRO;
|
||||
break;
|
||||
case IIO_TEMP:
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = SMI330_TEMP_SCALE / GIGA;
|
||||
*val2 = SMI330_TEMP_SCALE % GIGA;
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
case IIO_CHAN_INFO_OFFSET:
|
||||
*val = SMI330_TEMP_OFFSET;
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* valid for acc and gyro channels */
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
||||
ret = smi330_get_sensor_config(data, sensor, SMI330_AVG_NUM,
|
||||
val);
|
||||
return ret ? ret : IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
|
||||
ret = smi330_get_sensor_config(data, sensor, SMI330_BW, val);
|
||||
return ret ? ret : IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret = smi330_get_sensor_config(data, sensor, SMI330_ODR, val);
|
||||
return ret ? ret : IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
ret = smi330_get_sensor_config(data, sensor, SMI330_RANGE,
|
||||
val2);
|
||||
return ret ? ret : IIO_VAL_INT_PLUS_NANO;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int smi330_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int val, int val2,
|
||||
long mask)
|
||||
{
|
||||
struct smi330_data *data = iio_priv(indio_dev);
|
||||
enum smi330_sensor sensor;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_ACCEL:
|
||||
sensor = SMI330_ACCEL;
|
||||
break;
|
||||
case IIO_ANGL_VEL:
|
||||
sensor = SMI330_GYRO;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
return smi330_set_sensor_config(data, sensor, SMI330_RANGE,
|
||||
val2);
|
||||
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
|
||||
return smi330_set_sensor_config(data, sensor, SMI330_AVG_NUM,
|
||||
val);
|
||||
case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY:
|
||||
return smi330_set_sensor_config(data, sensor, SMI330_BW, val);
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return smi330_set_sensor_config(data, sensor, SMI330_ODR, val);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int smi330_write_raw_get_fmt(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, long info)
|
||||
{
|
||||
switch (info) {
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
return IIO_VAL_INT_PLUS_NANO;
|
||||
default:
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
}
|
||||
|
||||
static int smi330_soft_reset(struct smi330_data *data)
|
||||
{
|
||||
int ret, dummy_byte;
|
||||
|
||||
ret = regmap_write(data->regmap, SMI330_CMD_REG, SMI330_CMD_SOFT_RESET);
|
||||
if (ret)
|
||||
return ret;
|
||||
fsleep(SMI330_SOFT_RESET_DELAY);
|
||||
|
||||
/* Performing a dummy read after a soft-reset */
|
||||
regmap_read(data->regmap, SMI330_CHIP_ID_REG, &dummy_byte);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t smi330_trigger_handler(int irq, void *p)
|
||||
{
|
||||
int ret;
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct smi330_data *data = iio_priv(indio_dev);
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, SMI330_ACCEL_X_REG, data->buf,
|
||||
SMI330_SCAN_LEN);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, data->buf, pf->timestamp);
|
||||
|
||||
out:
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t smi330_irq_thread_handler(int irq, void *indio_dev_)
|
||||
{
|
||||
int ret, int_stat;
|
||||
s16 int_status[2] = { 0 };
|
||||
struct iio_dev *indio_dev = indio_dev_;
|
||||
struct smi330_data *data = iio_priv(indio_dev);
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, SMI330_INT1_STATUS_REG, int_status, 2);
|
||||
if (ret)
|
||||
return IRQ_NONE;
|
||||
|
||||
int_stat = int_status[0] | int_status[1];
|
||||
|
||||
if (FIELD_GET(SMI330_INT_STATUS_ACC_GYR_DRDY_MASK, int_stat)) {
|
||||
indio_dev->pollfunc->timestamp = iio_get_time_ns(indio_dev);
|
||||
iio_trigger_poll_nested(data->trig);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int smi330_set_int_pin_config(struct smi330_data *data,
|
||||
enum smi330_int_out irq_num,
|
||||
bool active_high, bool open_drain,
|
||||
bool latch)
|
||||
{
|
||||
int ret, val;
|
||||
|
||||
val = active_high ? SMI330_IO_INT_CTRL_LVL : 0;
|
||||
val |= open_drain ? SMI330_IO_INT_CTRL_OD : 0;
|
||||
val |= SMI330_IO_INT_CTRL_EN;
|
||||
|
||||
switch (irq_num) {
|
||||
case SMI330_INT_1:
|
||||
val = FIELD_PREP(SMI330_IO_INT_CTRL_INT1_MASK, val);
|
||||
ret = regmap_update_bits(data->regmap, SMI330_IO_INT_CTRL_REG,
|
||||
SMI330_IO_INT_CTRL_INT1_MASK, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
case SMI330_INT_2:
|
||||
val = FIELD_PREP(SMI330_IO_INT_CTRL_INT2_MASK, val);
|
||||
ret = regmap_update_bits(data->regmap, SMI330_IO_INT_CTRL_REG,
|
||||
SMI330_IO_INT_CTRL_INT2_MASK, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return regmap_update_bits(data->regmap, SMI330_INT_CONF_REG,
|
||||
SMI330_INT_CONF_LATCH_MASK,
|
||||
FIELD_PREP(SMI330_INT_CONF_LATCH_MASK,
|
||||
latch));
|
||||
}
|
||||
|
||||
static int smi330_setup_irq(struct device *dev, struct iio_dev *indio_dev,
|
||||
int irq, enum smi330_int_out irq_num)
|
||||
{
|
||||
int ret, irq_type;
|
||||
bool open_drain, active_high, latch;
|
||||
struct smi330_data *data = iio_priv(indio_dev);
|
||||
struct irq_data *desc;
|
||||
|
||||
desc = irq_get_irq_data(irq);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
irq_type = irqd_get_trigger_type(desc);
|
||||
switch (irq_type) {
|
||||
case IRQF_TRIGGER_RISING:
|
||||
latch = false;
|
||||
active_high = true;
|
||||
break;
|
||||
case IRQF_TRIGGER_HIGH:
|
||||
latch = true;
|
||||
active_high = true;
|
||||
break;
|
||||
case IRQF_TRIGGER_FALLING:
|
||||
latch = false;
|
||||
active_high = false;
|
||||
break;
|
||||
case IRQF_TRIGGER_LOW:
|
||||
latch = true;
|
||||
active_high = false;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
open_drain = device_property_read_bool(dev, "drive-open-drain");
|
||||
|
||||
ret = smi330_set_int_pin_config(data, irq_num, active_high, open_drain,
|
||||
latch);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_request_threaded_irq(dev, irq, NULL,
|
||||
smi330_irq_thread_handler,
|
||||
irq_type | IRQF_ONESHOT,
|
||||
indio_dev->name, indio_dev);
|
||||
}
|
||||
|
||||
static int smi330_register_irq(struct device *dev, struct iio_dev *indio_dev)
|
||||
{
|
||||
int ret, irq;
|
||||
struct smi330_data *data = iio_priv(indio_dev);
|
||||
struct fwnode_handle *fwnode;
|
||||
|
||||
fwnode = dev_fwnode(dev);
|
||||
if (!fwnode)
|
||||
return -ENODEV;
|
||||
|
||||
data->cfg.data_irq = SMI330_INT_DISABLED;
|
||||
|
||||
irq = fwnode_irq_get_byname(fwnode, "INT1");
|
||||
if (irq > 0) {
|
||||
ret = smi330_setup_irq(dev, indio_dev, irq, SMI330_INT_1);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->cfg.data_irq = SMI330_INT_1;
|
||||
} else {
|
||||
irq = fwnode_irq_get_byname(fwnode, "INT2");
|
||||
if (irq > 0) {
|
||||
ret = smi330_setup_irq(dev, indio_dev, irq,
|
||||
SMI330_INT_2);
|
||||
if (ret)
|
||||
return ret;
|
||||
data->cfg.data_irq = SMI330_INT_2;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smi330_set_drdy_trigger_state(struct iio_trigger *trig, bool enable)
|
||||
{
|
||||
int val;
|
||||
struct smi330_data *data = iio_trigger_get_drvdata(trig);
|
||||
|
||||
if (enable)
|
||||
data->cfg.op_mode = SMI330_DATA_READY;
|
||||
else
|
||||
data->cfg.op_mode = SMI330_POLLING;
|
||||
|
||||
val = FIELD_PREP(SMI330_INT_MAP2_ACC_DRDY_MASK,
|
||||
enable ? data->cfg.data_irq : 0);
|
||||
val |= FIELD_PREP(SMI330_INT_MAP2_GYR_DRDY_MASK,
|
||||
enable ? data->cfg.data_irq : 0);
|
||||
return regmap_update_bits(data->regmap, SMI330_INT_MAP2_REG,
|
||||
SMI330_INT_MAP2_ACC_DRDY_MASK |
|
||||
SMI330_INT_MAP2_GYR_DRDY_MASK,
|
||||
val);
|
||||
}
|
||||
|
||||
static const struct iio_trigger_ops smi330_trigger_ops = {
|
||||
.set_trigger_state = &smi330_set_drdy_trigger_state,
|
||||
};
|
||||
|
||||
static struct iio_info smi330_info = {
|
||||
.read_avail = smi330_read_avail,
|
||||
.read_raw = smi330_read_raw,
|
||||
.write_raw = smi330_write_raw,
|
||||
.write_raw_get_fmt = smi330_write_raw_get_fmt,
|
||||
};
|
||||
|
||||
static int smi330_dev_init(struct smi330_data *data)
|
||||
{
|
||||
int ret, chip_id, val, mode;
|
||||
struct device *dev = regmap_get_device(data->regmap);
|
||||
|
||||
ret = regmap_read(data->regmap, SMI330_CHIP_ID_REG, &chip_id);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
chip_id = FIELD_GET(SMI330_CHIP_ID_MASK, chip_id);
|
||||
if (chip_id != SMI330_CHIP_ID)
|
||||
dev_info(dev, "Unknown chip id: 0x%04x\n", chip_id);
|
||||
|
||||
ret = regmap_read(data->regmap, SMI330_ERR_REG, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (FIELD_GET(SMI330_ERR_FATAL_MASK, val))
|
||||
return -ENODEV;
|
||||
|
||||
ret = regmap_read(data->regmap, SMI330_STATUS_REG, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (FIELD_GET(SMI330_STATUS_POR_MASK, val) == 0)
|
||||
return -ENODEV;
|
||||
|
||||
mode = FIELD_PREP(SMI330_CFG_MODE_MASK, SMI330_MODE_NORMAL);
|
||||
|
||||
ret = regmap_update_bits(data->regmap, SMI330_ACCEL_CFG_REG,
|
||||
SMI330_CFG_MODE_MASK, mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return regmap_update_bits(data->regmap, SMI330_GYRO_CFG_REG,
|
||||
SMI330_CFG_MODE_MASK, mode);
|
||||
}
|
||||
|
||||
int smi330_core_probe(struct device *dev, struct regmap *regmap)
|
||||
{
|
||||
int ret;
|
||||
struct iio_dev *indio_dev;
|
||||
struct smi330_data *data;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->regmap = regmap;
|
||||
|
||||
ret = smi330_soft_reset(data);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Soft reset failed\n");
|
||||
|
||||
indio_dev->channels = smi330_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(smi330_channels);
|
||||
indio_dev->available_scan_masks = smi330_avail_scan_masks;
|
||||
indio_dev->name = "smi330";
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->info = &smi330_info;
|
||||
|
||||
data->cfg.op_mode = SMI330_POLLING;
|
||||
|
||||
ret = smi330_dev_init(data);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Init failed\n");
|
||||
|
||||
ret = smi330_register_irq(dev, indio_dev);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Register IRQ failed\n");
|
||||
|
||||
if (data->cfg.data_irq != SMI330_INT_DISABLED) {
|
||||
data->trig = devm_iio_trigger_alloc(dev, "%s-drdy-trigger",
|
||||
indio_dev->name);
|
||||
if (!data->trig)
|
||||
return -ENOMEM;
|
||||
|
||||
data->trig->ops = &smi330_trigger_ops;
|
||||
iio_trigger_set_drvdata(data->trig, data);
|
||||
|
||||
ret = devm_iio_trigger_register(dev, data->trig);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"IIO register trigger failed\n");
|
||||
|
||||
/* Set default operation mode to data ready. */
|
||||
indio_dev->trig = iio_trigger_get(data->trig);
|
||||
}
|
||||
|
||||
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
|
||||
iio_pollfunc_store_time,
|
||||
smi330_trigger_handler, NULL);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "IIO buffer setup failed\n");
|
||||
|
||||
ret = devm_iio_device_register(dev, indio_dev);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Register IIO device failed\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(smi330_core_probe, "IIO_SMI330");
|
||||
|
||||
MODULE_AUTHOR("Stefan Gutmann <stefan.gutmann@de.bosch.com>");
|
||||
MODULE_AUTHOR("Roman Huber <roman.huber@de.bosch.com>");
|
||||
MODULE_AUTHOR("Filip Andrei <Andrei.Filip@ro.bosch.com>");
|
||||
MODULE_AUTHOR("Drimbarean Avram Andrei <Avram-Andrei.Drimbarean@ro.bosch.com>");
|
||||
MODULE_DESCRIPTION("Bosch SMI330 IMU driver");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
133
drivers/iio/imu/smi330/smi330_i2c.c
Normal file
133
drivers/iio/imu/smi330/smi330_i2c.c
Normal file
@@ -0,0 +1,133 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2025 Robert Bosch GmbH.
|
||||
*/
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "smi330.h"
|
||||
|
||||
#define SMI330_NUM_DUMMY_BYTES 2
|
||||
#define SMI330_I2C_MAX_RX_BUFFER_SIZE \
|
||||
(SMI330_NUM_DUMMY_BYTES + SMI330_SCAN_LEN * sizeof(s16))
|
||||
|
||||
struct smi330_i2c_priv {
|
||||
struct i2c_client *i2c;
|
||||
u8 rx_buffer[SMI330_I2C_MAX_RX_BUFFER_SIZE];
|
||||
};
|
||||
|
||||
static int smi330_regmap_i2c_read(void *context, const void *reg_buf,
|
||||
size_t reg_size, void *val_buf,
|
||||
size_t val_size)
|
||||
{
|
||||
struct smi330_i2c_priv *priv = context;
|
||||
int ret;
|
||||
|
||||
if (SMI330_NUM_DUMMY_BYTES + val_size > SMI330_I2C_MAX_RX_BUFFER_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* SMI330 I2C read frame:
|
||||
* <Slave address[6:0], RnW> <x, Register address[6:0]>
|
||||
* <Slave address[6:0], RnW> <Dummy[7:0]> <Dummy[7:0]> <Data_0[7:0]> <Data_1[15:8]>...
|
||||
* <Data_N[7:0]> <Data_N[15:8]>
|
||||
* Remark: Slave address is not considered part of the frame in the following definitions
|
||||
*/
|
||||
struct i2c_msg msgs[] = {
|
||||
{
|
||||
.addr = priv->i2c->addr,
|
||||
.flags = priv->i2c->flags,
|
||||
.len = reg_size,
|
||||
.buf = (u8 *)reg_buf,
|
||||
},
|
||||
{
|
||||
.addr = priv->i2c->addr,
|
||||
.flags = priv->i2c->flags | I2C_M_RD,
|
||||
.len = SMI330_NUM_DUMMY_BYTES + val_size,
|
||||
.buf = priv->rx_buffer,
|
||||
},
|
||||
};
|
||||
|
||||
ret = i2c_transfer(priv->i2c->adapter, msgs, ARRAY_SIZE(msgs));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
memcpy(val_buf, priv->rx_buffer + SMI330_NUM_DUMMY_BYTES, val_size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smi330_regmap_i2c_write(void *context, const void *data,
|
||||
size_t count)
|
||||
{
|
||||
struct smi330_i2c_priv *priv = context;
|
||||
u8 reg;
|
||||
|
||||
/*
|
||||
* SMI330 I2C write frame:
|
||||
* <Slave address[6:0], RnW> <x, Register address[6:0]> <Data_0[7:0]> <Data_1[15:8]>...
|
||||
* <Data_N[7:0]> <Data_N[15:8]>
|
||||
* Remark: Slave address is not considered part of the frame in the following definitions
|
||||
*/
|
||||
reg = *(u8 *)data;
|
||||
return i2c_smbus_write_i2c_block_data(priv->i2c, reg,
|
||||
count - sizeof(u8),
|
||||
data + sizeof(u8));
|
||||
}
|
||||
|
||||
static const struct regmap_bus smi330_regmap_bus = {
|
||||
.read = smi330_regmap_i2c_read,
|
||||
.write = smi330_regmap_i2c_write,
|
||||
};
|
||||
|
||||
static int smi330_i2c_probe(struct i2c_client *i2c)
|
||||
{
|
||||
struct device *dev = &i2c->dev;
|
||||
struct smi330_i2c_priv *priv;
|
||||
struct regmap *regmap;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->i2c = i2c;
|
||||
regmap = devm_regmap_init(dev, &smi330_regmap_bus, priv,
|
||||
&smi330_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(regmap),
|
||||
"Failed to initialize I2C Regmap\n");
|
||||
|
||||
return smi330_core_probe(dev, regmap);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id smi330_i2c_device_id[] = {
|
||||
{ .name = "smi330" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, smi330_i2c_device_id);
|
||||
|
||||
static const struct of_device_id smi330_of_match[] = {
|
||||
{ .compatible = "bosch,smi330" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, smi330_of_match);
|
||||
|
||||
static struct i2c_driver smi330_i2c_driver = {
|
||||
.probe = smi330_i2c_probe,
|
||||
.id_table = smi330_i2c_device_id,
|
||||
.driver = {
|
||||
.of_match_table = smi330_of_match,
|
||||
.name = "smi330_i2c",
|
||||
},
|
||||
};
|
||||
module_i2c_driver(smi330_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Stefan Gutmann <stefan.gutmann@de.bosch.com>");
|
||||
MODULE_AUTHOR("Roman Huber <roman.huber@de.bosch.com>");
|
||||
MODULE_AUTHOR("Filip Andrei <Andrei.Filip@ro.bosch.com>");
|
||||
MODULE_AUTHOR("Drimbarean Avram Andrei <Avram-Andrei.Drimbarean@ro.bosch.com>");
|
||||
MODULE_DESCRIPTION("Bosch SMI330 I2C driver");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_IMPORT_NS("IIO_SMI330");
|
||||
85
drivers/iio/imu/smi330/smi330_spi.c
Normal file
85
drivers/iio/imu/smi330/smi330_spi.c
Normal file
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2025 Robert Bosch GmbH.
|
||||
*/
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include "smi330.h"
|
||||
|
||||
static int smi330_regmap_spi_read(void *context, const void *reg_buf,
|
||||
size_t reg_size, void *val_buf,
|
||||
size_t val_size)
|
||||
{
|
||||
struct spi_device *spi = context;
|
||||
|
||||
/* Insert pad byte for reading */
|
||||
u8 reg[] = { *(u8 *)reg_buf, 0 };
|
||||
|
||||
if (reg_size + 1 != ARRAY_SIZE(reg)) {
|
||||
dev_err(&spi->dev, "Invalid register size %zu\n", reg_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return spi_write_then_read(spi, reg, ARRAY_SIZE(reg), val_buf,
|
||||
val_size);
|
||||
}
|
||||
|
||||
static int smi330_regmap_spi_write(void *context, const void *data,
|
||||
size_t count)
|
||||
{
|
||||
struct spi_device *spi = context;
|
||||
|
||||
return spi_write(spi, data, count);
|
||||
}
|
||||
|
||||
static const struct regmap_bus smi330_regmap_bus = {
|
||||
.read = smi330_regmap_spi_read,
|
||||
.write = smi330_regmap_spi_write,
|
||||
.read_flag_mask = 0x80,
|
||||
};
|
||||
|
||||
static int smi330_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = devm_regmap_init(&spi->dev, &smi330_regmap_bus, &spi->dev,
|
||||
&smi330_regmap_config);
|
||||
if (IS_ERR(regmap))
|
||||
return dev_err_probe(&spi->dev, PTR_ERR(regmap),
|
||||
"Failed to initialize SPI Regmap\n");
|
||||
|
||||
return smi330_core_probe(&spi->dev, regmap);
|
||||
}
|
||||
|
||||
static const struct spi_device_id smi330_spi_device_id[] = {
|
||||
{ .name = "smi330" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, smi330_spi_device_id);
|
||||
|
||||
static const struct of_device_id smi330_of_match[] = {
|
||||
{ .compatible = "bosch,smi330" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, smi330_of_match);
|
||||
|
||||
static struct spi_driver smi330_spi_driver = {
|
||||
.probe = smi330_spi_probe,
|
||||
.id_table = smi330_spi_device_id,
|
||||
.driver = {
|
||||
.of_match_table = smi330_of_match,
|
||||
.name = "smi330_spi",
|
||||
},
|
||||
};
|
||||
module_spi_driver(smi330_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Stefan Gutmann <stefan.gutmann@de.bosch.com>");
|
||||
MODULE_AUTHOR("Roman Huber <roman.huber@de.bosch.com>");
|
||||
MODULE_AUTHOR("Filip Andrei <Andrei.Filip@ro.bosch.com>");
|
||||
MODULE_AUTHOR("Drimbarean Avram Andrei <Avram-Andrei.Drimbarean@ro.bosch.com>");
|
||||
MODULE_DESCRIPTION("Bosch SMI330 SPI driver");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_IMPORT_NS("IIO_SMI330");
|
||||
@@ -365,7 +365,8 @@ enum st_lsm6dsx_fifo_mode {
|
||||
* @id: Sensor identifier.
|
||||
* @hw: Pointer to instance of struct st_lsm6dsx_hw.
|
||||
* @gain: Configured sensor sensitivity.
|
||||
* @odr: Output data rate of the sensor [Hz].
|
||||
* @odr: Output data rate of the sensor [mHz].
|
||||
* hwfifo_odr_mHz: Batch data rate for hardware FIFO [mHz]
|
||||
* @samples_to_discard: Number of samples to discard for filters settling time.
|
||||
* @watermark: Sensor watermark level.
|
||||
* @decimator: Sensor decimation factor.
|
||||
@@ -380,6 +381,7 @@ struct st_lsm6dsx_sensor {
|
||||
|
||||
u32 gain;
|
||||
u32 odr;
|
||||
u32 hwfifo_odr_mHz;
|
||||
|
||||
u16 samples_to_discard;
|
||||
u16 watermark;
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
@@ -105,7 +106,7 @@ static int
|
||||
st_lsm6dsx_get_decimator_val(struct st_lsm6dsx_sensor *sensor, u32 max_odr)
|
||||
{
|
||||
const int max_size = ARRAY_SIZE(st_lsm6dsx_decimator_table);
|
||||
u32 decimator = max_odr / sensor->odr;
|
||||
u32 decimator = max_odr / sensor->hwfifo_odr_mHz;
|
||||
int i;
|
||||
|
||||
if (decimator > 1)
|
||||
@@ -136,14 +137,14 @@ static void st_lsm6dsx_get_max_min_odr(struct st_lsm6dsx_hw *hw,
|
||||
if (!(hw->enable_mask & BIT(sensor->id)))
|
||||
continue;
|
||||
|
||||
*max_odr = max_t(u32, *max_odr, sensor->odr);
|
||||
*min_odr = min_t(u32, *min_odr, sensor->odr);
|
||||
*max_odr = max(*max_odr, sensor->hwfifo_odr_mHz);
|
||||
*min_odr = min(*min_odr, sensor->hwfifo_odr_mHz);
|
||||
}
|
||||
}
|
||||
|
||||
static u8 st_lsm6dsx_get_sip(struct st_lsm6dsx_sensor *sensor, u32 min_odr)
|
||||
{
|
||||
u8 sip = sensor->odr / min_odr;
|
||||
u8 sip = sensor->hwfifo_odr_mHz / min_odr;
|
||||
|
||||
return sip > 1 ? round_down(sip, 2) : sip;
|
||||
}
|
||||
@@ -231,7 +232,7 @@ static int st_lsm6dsx_set_fifo_odr(struct st_lsm6dsx_sensor *sensor,
|
||||
if (enable) {
|
||||
int err;
|
||||
|
||||
err = st_lsm6dsx_check_odr(sensor, sensor->odr,
|
||||
err = st_lsm6dsx_check_odr(sensor, sensor->hwfifo_odr_mHz,
|
||||
&data);
|
||||
if (err < 0)
|
||||
return err;
|
||||
@@ -713,7 +714,7 @@ st_lsm6dsx_update_samples_to_discard(struct st_lsm6dsx_sensor *sensor)
|
||||
|
||||
data = &hw->settings->samples_to_discard[sensor->id];
|
||||
for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++) {
|
||||
if (data->val[i].milli_hz == sensor->odr) {
|
||||
if (data->val[i].milli_hz == sensor->hwfifo_odr_mHz) {
|
||||
sensor->samples_to_discard = data->val[i].samples;
|
||||
return;
|
||||
}
|
||||
@@ -799,6 +800,59 @@ static const struct iio_buffer_setup_ops st_lsm6dsx_buffer_ops = {
|
||||
.postdisable = st_lsm6dsx_buffer_postdisable,
|
||||
};
|
||||
|
||||
static ssize_t st_lsm6dsx_hwfifo_odr_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct st_lsm6dsx_sensor *sensor = iio_priv(dev_to_iio_dev(dev));
|
||||
|
||||
return sysfs_emit(buf, "%d.%03d\n", sensor->hwfifo_odr_mHz / 1000,
|
||||
sensor->hwfifo_odr_mHz % 1000);
|
||||
}
|
||||
|
||||
static ssize_t st_lsm6dsx_hwfifo_odr_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct iio_dev *iio_dev = dev_to_iio_dev(dev);
|
||||
struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
|
||||
int integer, milli;
|
||||
int ret;
|
||||
u32 hwfifo_odr;
|
||||
u8 data;
|
||||
|
||||
if (!iio_device_claim_direct(iio_dev))
|
||||
return -EBUSY;
|
||||
|
||||
ret = iio_str_to_fixpoint(buf, 100, &integer, &milli);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
hwfifo_odr = integer * 1000 + milli;
|
||||
ret = st_lsm6dsx_check_odr(sensor, hwfifo_odr, &data);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
hwfifo_odr = ret;
|
||||
|
||||
/* the batch data rate must not exceed the sensor output data rate */
|
||||
if (hwfifo_odr <= sensor->odr)
|
||||
sensor->hwfifo_odr_mHz = hwfifo_odr;
|
||||
else
|
||||
ret = -EINVAL;
|
||||
|
||||
out:
|
||||
iio_device_release_direct(iio_dev);
|
||||
|
||||
return ret < 0 ? ret : len;
|
||||
}
|
||||
|
||||
static IIO_DEV_ATTR_SAMP_FREQ(0664, st_lsm6dsx_hwfifo_odr_show, st_lsm6dsx_hwfifo_odr_store);
|
||||
|
||||
static const struct iio_dev_attr *st_lsm6dsx_buffer_attrs[] = {
|
||||
&iio_dev_attr_sampling_frequency,
|
||||
NULL
|
||||
};
|
||||
|
||||
int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw)
|
||||
{
|
||||
int i, ret;
|
||||
@@ -807,8 +861,9 @@ int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw)
|
||||
if (!hw->iio_devs[i])
|
||||
continue;
|
||||
|
||||
ret = devm_iio_kfifo_buffer_setup(hw->dev, hw->iio_devs[i],
|
||||
&st_lsm6dsx_buffer_ops);
|
||||
ret = devm_iio_kfifo_buffer_setup_ext(hw->dev, hw->iio_devs[i],
|
||||
&st_lsm6dsx_buffer_ops,
|
||||
st_lsm6dsx_buffer_attrs);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1847,10 +1847,12 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev,
|
||||
|
||||
val = val * 1000 + val2 / 1000;
|
||||
val = st_lsm6dsx_check_odr(sensor, val, &data);
|
||||
if (val < 0)
|
||||
if (val < 0) {
|
||||
err = val;
|
||||
else
|
||||
} else {
|
||||
sensor->odr = val;
|
||||
sensor->hwfifo_odr_mHz = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -2384,6 +2386,7 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw,
|
||||
sensor->id = id;
|
||||
sensor->hw = hw;
|
||||
sensor->odr = hw->settings->odr_table[id].odr_avl[0].milli_hz;
|
||||
sensor->hwfifo_odr_mHz = sensor->odr;
|
||||
sensor->gain = hw->settings->fs_table[id].fs_avl[0].gain;
|
||||
sensor->watermark = 1;
|
||||
|
||||
|
||||
@@ -640,6 +640,7 @@ __st_lsm6dsx_shub_write_raw(struct iio_dev *iio_dev,
|
||||
|
||||
sensor->ext_info.slv_odr = val;
|
||||
sensor->odr = odr;
|
||||
sensor->hwfifo_odr_mHz = odr;
|
||||
return 0;
|
||||
}
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
@@ -746,6 +747,7 @@ st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw,
|
||||
sensor->id = id;
|
||||
sensor->hw = hw;
|
||||
sensor->odr = hw->settings->odr_table[ref_id].odr_avl[0].milli_hz;
|
||||
sensor->hwfifo_odr_mHz = sensor->odr;
|
||||
sensor->ext_info.slv_odr = info->odr_table.odr_avl[0].milli_hz;
|
||||
sensor->gain = info->fs_table.fs_avl[0].gain;
|
||||
sensor->ext_info.settings = info;
|
||||
|
||||
@@ -702,7 +702,7 @@ EXPORT_SYMBOL_NS_GPL(iio_backend_interface_type_get, "IIO_BACKEND");
|
||||
* interface/data bus. Hence, the backend device needs to be aware of it so
|
||||
* data can be correctly transferred.
|
||||
*
|
||||
* Return:
|
||||
* RETURNS:
|
||||
* 0 on success, negative error number on failure.
|
||||
*/
|
||||
int iio_backend_data_size_set(struct iio_backend *back, unsigned int size)
|
||||
@@ -717,9 +717,10 @@ EXPORT_SYMBOL_NS_GPL(iio_backend_data_size_set, "IIO_BACKEND");
|
||||
/**
|
||||
* iio_backend_oversampling_ratio_set - set the oversampling ratio
|
||||
* @back: Backend device
|
||||
* @chan: Channel number
|
||||
* @ratio: The oversampling ratio - value 1 corresponds to no oversampling.
|
||||
*
|
||||
* Return:
|
||||
* RETURNS:
|
||||
* 0 on success, negative error number on failure.
|
||||
*/
|
||||
int iio_backend_oversampling_ratio_set(struct iio_backend *back,
|
||||
@@ -1064,6 +1065,9 @@ EXPORT_SYMBOL_NS_GPL(__devm_iio_backend_get_from_fwnode_lookup, "IIO_BACKEND");
|
||||
/**
|
||||
* iio_backend_get_priv - Get driver private data
|
||||
* @back: Backend device
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to the driver private data associated with the backend.
|
||||
*/
|
||||
void *iio_backend_get_priv(const struct iio_backend *back)
|
||||
{
|
||||
|
||||
@@ -1563,9 +1563,7 @@ static void iio_buffer_dmabuf_release(struct kref *ref)
|
||||
struct iio_buffer *buffer = priv->buffer;
|
||||
struct dma_buf *dmabuf = attach->dmabuf;
|
||||
|
||||
dma_resv_lock(dmabuf->resv, NULL);
|
||||
dma_buf_unmap_attachment(attach, priv->sgt, priv->dir);
|
||||
dma_resv_unlock(dmabuf->resv);
|
||||
dma_buf_unmap_attachment_unlocked(attach, priv->sgt, priv->dir);
|
||||
|
||||
buffer->access->detach_dmabuf(buffer, priv->block);
|
||||
|
||||
@@ -2372,6 +2370,9 @@ static int iio_push_to_buffer(struct iio_buffer *buffer, const void *data)
|
||||
* iio_push_to_buffers() - push to a registered buffer.
|
||||
* @indio_dev: iio_dev structure for device.
|
||||
* @data: Full scan.
|
||||
*
|
||||
* Context: Any context.
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
int iio_push_to_buffers(struct iio_dev *indio_dev, const void *data)
|
||||
{
|
||||
@@ -2401,6 +2402,9 @@ EXPORT_SYMBOL_GPL(iio_push_to_buffers);
|
||||
* not require space for the timestamp, or 8 byte alignment of data.
|
||||
* It does however require an allocation on first call and additional
|
||||
* copies on all calls, so should be avoided if possible.
|
||||
*
|
||||
* Context: May sleep.
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
int iio_push_to_buffers_with_ts_unaligned(struct iio_dev *indio_dev,
|
||||
const void *data,
|
||||
@@ -2409,6 +2413,8 @@ int iio_push_to_buffers_with_ts_unaligned(struct iio_dev *indio_dev,
|
||||
{
|
||||
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
|
||||
|
||||
might_sleep();
|
||||
|
||||
/*
|
||||
* Conservative estimate - we can always safely copy the minimum
|
||||
* of either the data provided or the length of the destination buffer.
|
||||
|
||||
@@ -1654,6 +1654,9 @@ static void iio_dev_release(struct device *device)
|
||||
|
||||
iio_device_detach_buffers(indio_dev);
|
||||
|
||||
mutex_destroy(&iio_dev_opaque->info_exist_lock);
|
||||
mutex_destroy(&iio_dev_opaque->mlock);
|
||||
|
||||
lockdep_unregister_key(&iio_dev_opaque->mlock_key);
|
||||
|
||||
ida_free(&iio_ida, iio_dev_opaque->id);
|
||||
@@ -1694,12 +1697,6 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv)
|
||||
ACCESS_PRIVATE(indio_dev, priv) = (char *)iio_dev_opaque +
|
||||
ALIGN(sizeof(*iio_dev_opaque), IIO_DMA_MINALIGN);
|
||||
|
||||
indio_dev->dev.parent = parent;
|
||||
indio_dev->dev.type = &iio_device_type;
|
||||
indio_dev->dev.bus = &iio_bus_type;
|
||||
device_initialize(&indio_dev->dev);
|
||||
mutex_init(&iio_dev_opaque->mlock);
|
||||
mutex_init(&iio_dev_opaque->info_exist_lock);
|
||||
INIT_LIST_HEAD(&iio_dev_opaque->channel_attr_list);
|
||||
|
||||
iio_dev_opaque->id = ida_alloc(&iio_ida, GFP_KERNEL);
|
||||
@@ -1720,7 +1717,14 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv)
|
||||
INIT_LIST_HEAD(&iio_dev_opaque->ioctl_handlers);
|
||||
|
||||
lockdep_register_key(&iio_dev_opaque->mlock_key);
|
||||
lockdep_set_class(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key);
|
||||
|
||||
mutex_init_with_key(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key);
|
||||
mutex_init(&iio_dev_opaque->info_exist_lock);
|
||||
|
||||
indio_dev->dev.parent = parent;
|
||||
indio_dev->dev.type = &iio_device_type;
|
||||
indio_dev->dev.bus = &iio_bus_type;
|
||||
device_initialize(&indio_dev->dev);
|
||||
|
||||
return indio_dev;
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ static const struct regmap_config apds9306_regmap = {
|
||||
.volatile_table = &apds9306_volatile_table,
|
||||
.precious_table = &apds9306_precious_table,
|
||||
.max_register = APDS9306_ALS_THRES_VAR_REG,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.cache_type = REGCACHE_MAPLE,
|
||||
};
|
||||
|
||||
static const struct reg_field apds9306_rf_sw_reset =
|
||||
|
||||
@@ -234,7 +234,7 @@ static const struct regmap_config apds9960_regmap_config = {
|
||||
.reg_defaults = apds9960_reg_defaults,
|
||||
.num_reg_defaults = ARRAY_SIZE(apds9960_reg_defaults),
|
||||
.max_register = APDS9960_REG_GFIFO_DIR(RIGHT),
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.cache_type = REGCACHE_MAPLE,
|
||||
};
|
||||
|
||||
static const struct iio_event_spec apds9960_pxs_event_spec[] = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user