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:
Greg Kroah-Hartman
2025-11-21 15:25:20 +01:00
116 changed files with 10734 additions and 1503 deletions

View File

@@ -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]"

View File

@@ -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";
};
};

View File

@@ -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

View File

@@ -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>;
};
};
...

View File

@@ -26,6 +26,11 @@ properties:
compatible:
enum:
- adi,ad4080
- adi,ad4081
- adi,ad4083
- adi,ad4084
- adi,ad4086
- adi,ad4087
reg:
maxItems: 1

View 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>;
};
};
...

View File

@@ -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.

View File

@@ -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

View File

@@ -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>;
};
};

View 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>;
};
...

View File

@@ -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:

View 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>;
};
};
...

View File

@@ -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:

View 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";
};
};

View File

@@ -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";
};
};

View File

@@ -86,7 +86,6 @@ unevaluatedProperties: false
required:
- compatible
- reg
- interrupts
examples:
- |

View File

@@ -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>;
};
};

View File

@@ -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";
};
};

View File

@@ -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>;
};
};

View File

@@ -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)

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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");

View File

@@ -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;

View File

@@ -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 },
{ }

View File

@@ -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 },
{ }

View 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

View 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, &reg);
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");

View 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");

View File

@@ -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");

View File

@@ -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;

View File

@@ -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, &reg_val);
ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG, &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,
&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,
&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,
&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;

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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
View 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");

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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 = {

View File

@@ -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;
}

View File

@@ -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));
}

View File

@@ -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);
}

View File

@@ -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 = {

View File

@@ -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
View 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
View 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, &reg, 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");

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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"

View File

@@ -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

View 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");

View 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");

View File

@@ -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
View 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

View File

@@ -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, &ltc2688_regmap_bus, st,
&ltc2688_regmap_config);

View File

@@ -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;

View File

@@ -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"

View File

@@ -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/

View File

@@ -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, &regval);
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, &regval);
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, &regval);
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, &regval);
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, &regval);
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, &regval);
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;

View File

@@ -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);
}

View 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.

View 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

View 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

View 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, &timestamp, &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;
}

View 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, &timestamp, &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);
}

View 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

View 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");

View 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, &timestamp, &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;
}

View 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");

View 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");

View 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");

View 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.

View 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

View 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 */

View 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, &reg);
if (ret)
return ret;
ret = regmap_read(data->regmap, reg, &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, &reg);
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");

View 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");

View 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");

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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 =

View File

@@ -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