diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2025-10-04 16:26:32 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2025-10-04 16:26:32 -0700 |
commit | 6093a688a07da07808f0122f9aa2a3eed250d853 (patch) | |
tree | 83b189258a392eb2212a8a5a01ebc64fe1985e60 | |
parent | 59697e061f6aec86d5738cd4752e16520f1d60dc (diff) | |
parent | 22d693e45d4a4513bd99489a4e50b81cc0175b21 (diff) |
Merge tag 'char-misc-6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull Char/Misc/IIO/Binder updates from Greg KH:
"Here is the big set of char/misc/iio and other driver subsystem
changes for 6.18-rc1.
Loads of different stuff in here, it was a busy development cycle in
lots of different subsystems, with over 27k new lines added to the
tree.
Included in here are:
- IIO updates including new drivers, reworking of existing apis, and
other goodness in the sensor subsystems
- MEI driver updates and additions
- NVMEM driver updates
- slimbus removal for an unused driver and some other minor updates
- coresight driver updates and additions
- MHI driver updates
- comedi driver updates and fixes
- extcon driver updates
- interconnect driver additions
- eeprom driver updates and fixes
- minor UIO driver updates
- tiny W1 driver updates
But the majority of new code is in the rust bindings and additions,
which includes:
- misc driver rust binding updates for read/write support, we can now
write "normal" misc drivers in rust fully, and the sample driver
shows how this can be done.
- Initial framework for USB driver rust bindings, which are disabled
for now in the build, due to limited support, but coming in through
this tree due to dependencies on other rust binding changes that
were in here. I'll be enabling these back on in the build in the
usb.git tree after -rc1 is out so that developers can continue to
work on these in linux-next over the next development cycle.
- Android Binder driver implemented in Rust.
This is the big one, and was driving a huge majority of the rust
binding work over the past years. Right now there are two binder
drivers in the kernel, selected only at build time as to which one
to use as binder wants to be included in the system at boot time.
The binder C maintainers all agreed on this, as eventually, they
want the C code to be removed from the tree, but it will take a few
releases to get there while both are maintained to ensure that the
rust implementation is fully stable and compliant with the existing
userspace apis.
All of these have been in linux-next for a while"
* tag 'char-misc-6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (320 commits)
rust: usb: keep usb::Device private for now
rust: usb: don't retain device context for the interface parent
USB: disable rust bindings from the build for now
samples: rust: add a USB driver sample
rust: usb: add basic USB abstractions
coresight: Add label sysfs node support
dt-bindings: arm: Add label in the coresight components
coresight: tnoc: add new AMBA ID to support Trace Noc V2
coresight: Fix incorrect handling for return value of devm_kzalloc
coresight: tpda: fix the logic to setup the element size
coresight: trbe: Return NULL pointer for allocation failures
coresight: Refactor runtime PM
coresight: Make clock sequence consistent
coresight: Refactor driver data allocation
coresight: Consolidate clock enabling
coresight: Avoid enable programming clock duplicately
coresight: Appropriately disable trace bus clocks
coresight: Appropriately disable programming clocks
coresight: etm4x: Support atclk
coresight: catu: Support atclk
...
467 files changed, 27357 insertions, 3819 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-cti b/Documentation/ABI/testing/sysfs-bus-coresight-devices-cti index a97b70f588da..a2aef7f5a6d7 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-cti +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-cti @@ -239,3 +239,9 @@ Date: March 2020 KernelVersion: 5.7 Contact: Mike Leach or Mathieu Poirier Description: (Write) Clear all channel / trigger programming. + +What: /sys/bus/coresight/devices/<cti-name>/label +Date: Aug 2025 +KernelVersion 6.18 +Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Description: (Read) Show hardware context information of device. diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source b/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source index 0830661ef656..321e3ee1fc9d 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source @@ -13,3 +13,9 @@ KernelVersion: 6.14 Contact: Mao Jinlong <quic_jinlmao@quicinc.com> Description: (R) Show the trace ID that will appear in the trace stream coming from this trace entity. + +What: /sys/bus/coresight/devices/dummy_source<N>/label +Date: Aug 2025 +KernelVersion 6.18 +Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Description: (Read) Show hardware context information of device. diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-etb10 b/Documentation/ABI/testing/sysfs-bus-coresight-devices-etb10 index 9a383f6a74eb..f30526949687 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-etb10 +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-etb10 @@ -19,6 +19,12 @@ Description: (RW) Disables write access to the Trace RAM by stopping the into the Trace RAM following the trigger event is equal to the value stored in this register+1 (from ARM ETB-TRM). +What: /sys/bus/coresight/devices/<memory_map>.etb/label +Date: Aug 2025 +KernelVersion 6.18 +Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Description: (Read) Show hardware context information of device. + What: /sys/bus/coresight/devices/<memory_map>.etb/mgmt/rdp Date: March 2016 KernelVersion: 4.7 diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm3x b/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm3x index 271b57c571aa..245c322c91f1 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm3x +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm3x @@ -251,6 +251,12 @@ KernelVersion: 4.4 Contact: Mathieu Poirier <mathieu.poirier@linaro.org> Description: (RO) Holds the cpu number this tracer is affined to. +What: /sys/bus/coresight/devices/<memory_map>.[etm|ptm]/label +Date: Aug 2025 +KernelVersion 6.18 +Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Description: (Read) Show hardware context information of device. + What: /sys/bus/coresight/devices/<memory_map>.[etm|ptm]/mgmt/etmccr Date: September 2015 KernelVersion: 4.4 diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm4x b/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm4x index a0425d70d009..6f19a6a5f2e1 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm4x +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-etm4x @@ -329,6 +329,12 @@ Contact: Mathieu Poirier <mathieu.poirier@linaro.org> Description: (RW) Access the selected single show PE comparator control register. +What: /sys/bus/coresight/devices/etm<N>/label +Date: Aug 2025 +KernelVersion 6.18 +Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Description: (Read) Show hardware context information of device. + What: /sys/bus/coresight/devices/etm<N>/mgmt/trcoslsr Date: April 2015 KernelVersion: 4.01 diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-funnel b/Documentation/ABI/testing/sysfs-bus-coresight-devices-funnel index d75acda5e1b3..86938e9bbcde 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-funnel +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-funnel @@ -10,3 +10,9 @@ Date: November 2014 KernelVersion: 3.19 Contact: Mathieu Poirier <mathieu.poirier@linaro.org> Description: (RW) Defines input port priority order. + +What: /sys/bus/coresight/devices/<memory_map>.funnel/label +Date: Aug 2025 +KernelVersion 6.18 +Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Description: (Read) Show hardware context information of device. diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-stm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-stm index 53e1f4815d64..848e2ffc1480 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-stm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-stm @@ -51,3 +51,9 @@ KernelVersion: 4.7 Contact: Mathieu Poirier <mathieu.poirier@linaro.org> Description: (RW) Holds the trace ID that will appear in the trace stream coming from this trace entity. + +What: /sys/bus/coresight/devices/<memory_map>.stm/label +Date: Aug 2025 +KernelVersion 6.18 +Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Description: (Read) Show hardware context information of device. diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc index 339cec3b2f1a..55e298b9c4a4 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc @@ -107,3 +107,9 @@ Contact: Anshuman Khandual <anshuman.khandual@arm.com> Description: (RW) Current Coresight TMC-ETR buffer mode selected. But user could only provide a mode which is supported for a given ETR device. This file is available only for TMC ETR devices. + +What: /sys/bus/coresight/devices/<memory_map>.tmc/label +Date: Aug 2025 +KernelVersion 6.18 +Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Description: (Read) Show hardware context information of device. diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index a341b08ae70b..98f1c6545027 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -272,3 +272,9 @@ KernelVersion 6.15 Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> Description: (RW) Set/Get the enablement of the individual lane. + +What: /sys/bus/coresight/devices/<tpdm-name>/label +Date: Aug 2025 +KernelVersion 6.18 +Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Description: (Read) Show hardware context information of device. diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-trbe b/Documentation/ABI/testing/sysfs-bus-coresight-devices-trbe index ad3bbc6fa751..8a4b749ed26e 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-trbe +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-trbe @@ -12,3 +12,9 @@ Contact: Anshuman Khandual <anshuman.khandual@arm.com> Description: (Read) Shows if TRBE updates in the memory are with access and dirty flag updates as well. This value is fetched from the TRBIDR register. + +What: /sys/bus/coresight/devices/trbe<cpu>/label +Date: Aug 2025 +KernelVersion 6.18 +Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Description: (Read) Show hardware context information of device. diff --git a/Documentation/ABI/testing/sysfs-bus-counter b/Documentation/ABI/testing/sysfs-bus-counter index 3e8259e56d38..3e7eddd8aff3 100644 --- a/Documentation/ABI/testing/sysfs-bus-counter +++ b/Documentation/ABI/testing/sysfs-bus-counter @@ -309,26 +309,26 @@ Description: What: /sys/bus/counter/devices/counterX/cascade_counts_enable_component_id What: /sys/bus/counter/devices/counterX/external_input_phase_clock_select_component_id -What: /sys/bus/counter/devices/counterX/countY/compare_component_id What: /sys/bus/counter/devices/counterX/countY/capture_component_id What: /sys/bus/counter/devices/counterX/countY/ceiling_component_id -What: /sys/bus/counter/devices/counterX/countY/floor_component_id +What: /sys/bus/counter/devices/counterX/countY/compare_component_id What: /sys/bus/counter/devices/counterX/countY/count_mode_component_id What: /sys/bus/counter/devices/counterX/countY/direction_component_id What: /sys/bus/counter/devices/counterX/countY/enable_component_id What: /sys/bus/counter/devices/counterX/countY/error_noise_component_id +What: /sys/bus/counter/devices/counterX/countY/floor_component_id +What: /sys/bus/counter/devices/counterX/countY/num_overflows_component_id What: /sys/bus/counter/devices/counterX/countY/prescaler_component_id What: /sys/bus/counter/devices/counterX/countY/preset_component_id What: /sys/bus/counter/devices/counterX/countY/preset_enable_component_id What: /sys/bus/counter/devices/counterX/countY/signalZ_action_component_id -What: /sys/bus/counter/devices/counterX/countY/num_overflows_component_id What: /sys/bus/counter/devices/counterX/signalY/cable_fault_component_id What: /sys/bus/counter/devices/counterX/signalY/cable_fault_enable_component_id What: /sys/bus/counter/devices/counterX/signalY/filter_clock_prescaler_component_id +What: /sys/bus/counter/devices/counterX/signalY/frequency_component_id What: /sys/bus/counter/devices/counterX/signalY/index_polarity_component_id What: /sys/bus/counter/devices/counterX/signalY/polarity_component_id What: /sys/bus/counter/devices/counterX/signalY/synchronous_mode_component_id -What: /sys/bus/counter/devices/counterX/signalY/frequency_component_id KernelVersion: 5.16 Contact: linux-iio@vger.kernel.org Description: diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-m24lr b/Documentation/ABI/testing/sysfs-bus-i2c-devices-m24lr new file mode 100644 index 000000000000..7c51ce8d38ba --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-m24lr @@ -0,0 +1,100 @@ +What: /sys/bus/i2c/devices/<busnum>-<primary-addr>/unlock +Date: 2025-07-04 +KernelVersion: 6.17 +Contact: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> +Description: + Write-only attribute used to present a password and unlock + access to protected areas of the M24LR chip, including + configuration registers such as the Sector Security Status + (SSS) bytes. A valid password must be written to enable write + access to these regions via the I2C interface. + + Format: + - Hexadecimal string representing a 32-bit (4-byte) password + - Accepts 1 to 8 hex digits (e.g., "c", "1F", "a1b2c3d4") + - No "0x" prefix, whitespace, or trailing newline + - Case-insensitive + + Behavior: + - If the password matches the internal stored value, + access to protected memory/configuration is granted + - If the password does not match the internally stored value, + it will fail silently + +What: /sys/bus/i2c/devices/<busnum>-<primary-addr>/new_pass +Date: 2025-07-04 +KernelVersion: 6.17 +Contact: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> +Description: + Write-only attribute used to update the password required to + unlock the M24LR chip. + + Format: + - Hexadecimal string representing a new 32-bit password + - Accepts 1 to 8 hex digits (e.g., "1A", "ffff", "c0ffee00") + - No "0x" prefix, whitespace, or trailing newline + - Case-insensitive + + Behavior: + - Overwrites the current password stored in the I2C password + register + - Requires the device to be unlocked before changing the + password + - If the device is locked, the write silently fails + +What: /sys/bus/i2c/devices/<busnum>-<primary-addr>/uid +Date: 2025-07-04 +KernelVersion: 6.17 +Contact: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> +Description: + Read-only attribute that exposes the 8-byte unique identifier + programmed into the M24LR chip at the factory. + + Format: + - Lowercase hexadecimal string representing a 64-bit value + - 1 to 16 hex digits (e.g., "e00204f12345678") + - No "0x" prefix + - Includes a trailing newline + +What: /sys/bus/i2c/devices/<busnum>-<primary-addr>/total_sectors +Date: 2025-07-04 +KernelVersion: 6.17 +Contact: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> +Description: + Read-only attribute that exposes the total number of EEPROM + sectors available in the M24LR chip. + + Format: + - 1 to 2 hex digits (e.g. "F") + - No "0x" prefix + - Includes a trailing newline + + Notes: + - Value is encoded by the chip and corresponds to the EEPROM + size (e.g., 3 = 4 kbit for M24LR04E-R) + +What: /sys/bus/i2c/devices/<busnum>-<primary-addr>/sss +Date: 2025-07-04 +KernelVersion: 6.17 +Contact: Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> +Description: + Read/write binary attribute representing the Sector Security + Status (SSS) bytes for all EEPROM sectors in STMicroelectronics + M24LR chips. + + Each EEPROM sector has one SSS byte, which controls I2C and + RF access through protection bits and optional password + authentication. + + Format: + - The file contains one byte per EEPROM sector + - Byte at offset N corresponds to sector N + - Binary access only; use tools like dd, Python, or C that + support byte-level I/O and offset control. + + Notes: + - The number of valid bytes in this file is equal to the + value exposed by 'total_sectors' file + - Write access requires prior password authentication in + I2C mode + - Refer to the M24LR datasheet for full SSS bit layout diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 7e31b8cd49b3..89b4740dcfa1 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -167,7 +167,18 @@ Description: is required is a consistent labeling. Units after application of scale and offset are millivolts. +What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_rms_raw +KernelVersion: 6.18 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled) Root Mean Square (RMS) voltage measurement from + channel Y. Units after application of scale and offset are + millivolts. + What: /sys/bus/iio/devices/iio:deviceX/in_powerY_raw +What: /sys/bus/iio/devices/iio:deviceX/in_powerY_active_raw +What: /sys/bus/iio/devices/iio:deviceX/in_powerY_reactive_raw +What: /sys/bus/iio/devices/iio:deviceX/in_powerY_apparent_raw KernelVersion: 4.5 Contact: linux-iio@vger.kernel.org Description: @@ -176,6 +187,13 @@ Description: unique to allow association with event codes. Units after application of scale and offset are milliwatts. +What: /sys/bus/iio/devices/iio:deviceX/in_powerY_powerfactor +KernelVersion: 6.18 +Contact: linux-iio@vger.kernel.org +Description: + Power factor measurement from channel Y. Power factor is the + ratio of active power to apparent power. The value is unitless. + What: /sys/bus/iio/devices/iio:deviceX/in_capacitanceY_raw KernelVersion: 3.2 Contact: linux-iio@vger.kernel.org @@ -1569,6 +1587,9 @@ Description: What: /sys/.../iio:deviceX/in_energy_input What: /sys/.../iio:deviceX/in_energy_raw +What: /sys/.../iio:deviceX/in_energyY_active_raw +What: /sys/.../iio:deviceX/in_energyY_reactive_raw +What: /sys/.../iio:deviceX/in_energyY_apparent_raw KernelVersion: 4.0 Contact: linux-iio@vger.kernel.org Description: @@ -1707,6 +1728,14 @@ Description: component of the signal while the 'q' channel contains the quadrature component. +What: /sys/bus/iio/devices/iio:deviceX/in_altcurrentY_rms_raw +KernelVersion: 6.18 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled no bias removal etc.) Root Mean Square (RMS) current + measurement from channel Y. Units after application of scale and + offset are milliamps. + What: /sys/.../iio:deviceX/in_energy_en What: /sys/.../iio:deviceX/in_distance_en What: /sys/.../iio:deviceX/in_velocity_sqrt(x^2+y^2+z^2)_en @@ -2281,21 +2310,28 @@ Description: conversion time. Poor noise performance. * "sinc3" - The digital sinc3 filter. Moderate 1st conversion time. Good noise performance. - * "sinc4" - Sinc 4. Excellent noise performance. Long - 1st conversion time. - * "sinc5" - The digital sinc5 filter. Excellent noise - performance - * "sinc4+sinc1" - Sinc4 + averaging by 8. Low 1st conversion - time. - * "sinc3+rej60" - Sinc3 + 60Hz rejection. - * "sinc3+sinc1" - Sinc3 + averaging by 8. Low 1st conversion - time. * "sinc3+pf1" - Sinc3 + device specific Post Filter 1. * "sinc3+pf2" - Sinc3 + device specific Post Filter 2. * "sinc3+pf3" - Sinc3 + device specific Post Filter 3. * "sinc3+pf4" - Sinc3 + device specific Post Filter 4. - * "sinc5+pf1" - Sinc5 + device specific Post Filter 1. + * "sinc3+rej60" - Sinc3 + 60Hz rejection. + * "sinc3+sinc1" - Sinc3 + averaging by 8. Low 1st conversion + time. + * "sinc4" - Sinc 4. Excellent noise performance. Long + 1st conversion time. + * "sinc4+lp" - Sinc4 + Low Pass Filter. + * "sinc4+sinc1" - Sinc4 + averaging by 8. Low 1st conversion + time. + * "sinc4+rej60" - Sinc4 + 60Hz rejection. + * "sinc5" - The digital sinc5 filter. Excellent noise + performance * "sinc5+avg" - Sinc5 + averaging by 4. + * "sinc5+pf1" - Sinc5 + device specific Post Filter 1. + * "sinc5+sinc1" - Sinc5 + Sinc1. + * "sinc5+sinc1+pf1" - Sinc5 + Sinc1 + device specific Post Filter 1. + * "sinc5+sinc1+pf2" - Sinc5 + Sinc1 + device specific Post Filter 2. + * "sinc5+sinc1+pf3" - Sinc5 + Sinc1 + device specific Post Filter 3. + * "sinc5+sinc1+pf4" - Sinc5 + Sinc1 + device specific Post Filter 4. * "wideband" - filter with wideband low ripple passband and sharp transition band. diff --git a/Documentation/ABI/testing/sysfs-bus-iio-cros-ec b/Documentation/ABI/testing/sysfs-bus-iio-cros-ec index adf24c40126f..9e3926243797 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio-cros-ec +++ b/Documentation/ABI/testing/sysfs-bus-iio-cros-ec @@ -7,16 +7,6 @@ Description: corresponding calibration offsets can be read from `*_calibbias` entries. -What: /sys/bus/iio/devices/iio:deviceX/location -Date: July 2015 -KernelVersion: 4.7 -Contact: linux-iio@vger.kernel.org -Description: - This attribute returns a string with the physical location where - the motion sensor is placed. For example, in a laptop a motion - sensor can be located on the base or on the lid. Current valid - values are 'base' and 'lid'. - What: /sys/bus/iio/devices/iio:deviceX/id Date: September 2017 KernelVersion: 4.14 diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-cti.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-cti.yaml index 2d5545a2b49c..2a91670ccb8c 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-cti.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-cti.yaml @@ -98,6 +98,10 @@ properties: power-domains: maxItems: 1 + label: + description: + Description of a coresight device. + arm,cti-ctm-id: $ref: /schemas/types.yaml#/definitions/uint32 description: diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml index 08b89b62c505..ed091dc0c10a 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml @@ -39,6 +39,10 @@ properties: enum: - arm,coresight-dummy-sink + label: + description: + Description of a coresight device. + in-ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml index 742dc4e25d3b..78337be42b55 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml @@ -38,6 +38,10 @@ properties: enum: - arm,coresight-dummy-source + label: + description: + Description of a coresight device. + arm,static-trace-id: description: If dummy source needs static id support, use this to set trace id. $ref: /schemas/types.yaml#/definitions/uint32 diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-dynamic-funnel.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-dynamic-funnel.yaml index 44a1041cb0fc..b74db15e5f8a 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-dynamic-funnel.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-dynamic-funnel.yaml @@ -57,6 +57,10 @@ properties: power-domains: maxItems: 1 + label: + description: + Description of a coresight device. + in-ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-dynamic-replicator.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-dynamic-replicator.yaml index 03792e9bd97a..17ea936b796f 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-dynamic-replicator.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-dynamic-replicator.yaml @@ -54,6 +54,10 @@ properties: - const: apb_pclk - const: atclk + label: + description: + Description of a coresight device. + power-domains: maxItems: 1 diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-etb10.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-etb10.yaml index 90679788e0bf..892df7aca1ac 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-etb10.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-etb10.yaml @@ -54,6 +54,10 @@ properties: - const: apb_pclk - const: atclk + label: + description: + Description of a coresight device. + power-domains: maxItems: 1 diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-etm.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-etm.yaml index 01200f67504a..71f2e1ed27e5 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-etm.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-etm.yaml @@ -85,6 +85,10 @@ properties: CPU powers down the coresight component also powers down and loses its context. + label: + description: + Description of a coresight device. + arm,cp14: type: boolean description: diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-static-funnel.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-static-funnel.yaml index cc8c3baa79b4..9598a3d0a95b 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-static-funnel.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-static-funnel.yaml @@ -30,6 +30,10 @@ properties: power-domains: maxItems: 1 + label: + description: + Description of a coresight device. + in-ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-static-replicator.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-static-replicator.yaml index 0c1017affbad..b81851b26c74 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-static-replicator.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-static-replicator.yaml @@ -43,6 +43,10 @@ properties: - const: dbg_trc - const: dbg_apb + label: + description: + Description of a coresight device. + in-ports: $ref: /schemas/graph.yaml#/properties/ports additionalProperties: false diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-tmc.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-tmc.yaml index 4787d7c6bac2..96dd5b5f771a 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-tmc.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-tmc.yaml @@ -55,6 +55,10 @@ properties: - const: apb_pclk - const: atclk + label: + description: + Description of a coresight device. + iommus: maxItems: 1 diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-tpiu.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-tpiu.yaml index 61a0cdc27745..a207f6899e67 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-tpiu.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-tpiu.yaml @@ -54,6 +54,10 @@ properties: - const: apb_pclk - const: atclk + label: + description: + Description of a coresight device. + power-domains: maxItems: 1 diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml index 843b52eaf872..c969c16c21ef 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml @@ -39,6 +39,10 @@ properties: items: - const: apb + label: + description: + Description of a coresight device. + in-ports: $ref: /schemas/graph.yaml#/properties/ports diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml index 4fd5752978cd..ffe613efeabe 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml @@ -20,6 +20,10 @@ properties: compatible: const: qcom,coresight-remote-etm + label: + description: + Description of a coresight device. + out-ports: $ref: /schemas/graph.yaml#/properties/ports additionalProperties: false diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml new file mode 100644 index 000000000000..9d1c93a9ade3 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/arm/qcom,coresight-tnoc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Trace Network On Chip - TNOC + +maintainers: + - Yuanfang Zhang <quic_yuanfang@quicinc.com> + +description: > + The Trace Network On Chip (TNOC) is an integration hierarchy hardware + component that integrates the functionalities of TPDA and funnels. + + It sits in the different subsystem of SOC and aggregates the trace and + transports it to Aggregation TNOC or to coresight trace sink eventually. + TNOC embeds bridges for all the interfaces APB, ATB, TPDA and NTS (Narrow + Time Stamp). + + TNOC can take inputs from different trace sources i.e. ATB, TPDM. + + Note this binding is specifically intended for Aggregator TNOC instances. + +# Need a custom select here or 'arm,primecell' will match on lots of nodes +select: + properties: + compatible: + contains: + enum: + - qcom,coresight-tnoc + required: + - compatible + +properties: + $nodename: + pattern: "^tn(@[0-9a-f]+)$" + + compatible: + items: + - const: qcom,coresight-tnoc + - const: arm,primecell + + reg: + maxItems: 1 + + clock-names: + items: + - const: apb_pclk + + clocks: + items: + - description: APB register access clock + + in-ports: + $ref: /schemas/graph.yaml#/properties/ports + + patternProperties: + '^port(@[0-9a-f]{1,2})?$': + description: Input connections from CoreSight Trace Bus + $ref: /schemas/graph.yaml#/properties/port + + out-ports: + $ref: /schemas/graph.yaml#/properties/ports + additionalProperties: false + + properties: + port: + description: + Output connection to CoreSight Trace Bus + $ref: /schemas/graph.yaml#/properties/port + +required: + - compatible + - reg + - clocks + - clock-names + - in-ports + - out-ports + +additionalProperties: false + +examples: + - | + tn@109ab000 { + compatible = "qcom,coresight-tnoc", "arm,primecell"; + reg = <0x109ab000 0x4200>; + + clocks = <&aoss_qmp>; + clock-names = "apb_pclk"; + + in-ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + tn_ag_in_tpdm_gcc: endpoint { + remote-endpoint = <&tpdm_gcc_out_tn_ag>; + }; + }; + }; + + out-ports { + port { + tn_ag_out_funnel_in1: endpoint { + remote-endpoint = <&funnel_in1_in_tn_ag>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml index 5ed40f21b8eb..a48c9ac3eaa9 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml @@ -64,6 +64,10 @@ properties: items: - const: apb_pclk + label: + description: + Description of a coresight device. + in-ports: description: | Input connections from TPDM to TPDA diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml index 07d21a3617f5..4edc47483851 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml @@ -76,6 +76,10 @@ properties: minimum: 0 maximum: 32 + label: + description: + Description of a coresight device. + clocks: maxItems: 1 diff --git a/Documentation/devicetree/bindings/eeprom/at25.yaml b/Documentation/devicetree/bindings/eeprom/at25.yaml index c31e5e719525..00e0f07b44f8 100644 --- a/Documentation/devicetree/bindings/eeprom/at25.yaml +++ b/Documentation/devicetree/bindings/eeprom/at25.yaml @@ -56,6 +56,7 @@ properties: $ref: /schemas/types.yaml#/definitions/uint32 description: Total eeprom size in bytes. + Also used for FRAMs without device ID where the size cannot be detected. address-width: $ref: /schemas/types.yaml#/definitions/uint32 @@ -146,4 +147,11 @@ examples: reg = <1>; spi-max-frequency = <40000000>; }; + + fram@2 { + compatible = "cypress,fm25", "atmel,at25"; + reg = <2>; + spi-max-frequency = <20000000>; + size = <2048>; + }; }; diff --git a/Documentation/devicetree/bindings/eeprom/st,m24lr.yaml b/Documentation/devicetree/bindings/eeprom/st,m24lr.yaml new file mode 100644 index 000000000000..0a0820e9d11f --- /dev/null +++ b/Documentation/devicetree/bindings/eeprom/st,m24lr.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/eeprom/st,m24lr.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: STMicroelectronics M24LR NFC/RFID EEPROM + +maintainers: + - Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> + +description: + STMicroelectronics M24LR series are dual-interface (RF + I2C) + EEPROM chips. These devices support I2C-based access to both + memory and a system area that controls authentication and configuration. + They expose two I2C addresses, one for the system parameter sector and + one for the EEPROM. + +allOf: + - $ref: /schemas/nvmem/nvmem.yaml# + +properties: + compatible: + enum: + - st,m24lr04e-r + - st,m24lr16e-r + - st,m24lr64e-r + + reg: + items: + - description: I2C address used for control/system registers + - description: I2C address used for EEPROM memory access + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + eeprom@57 { + compatible = "st,m24lr04e-r"; + reg = <0x57>, /* primary-device */ + <0x53>; /* secondary-device */ + }; + }; +... diff --git a/Documentation/devicetree/bindings/extcon/extcon-rt8973a.txt b/Documentation/devicetree/bindings/extcon/extcon-rt8973a.txt deleted file mode 100644 index cfcf455ad4de..000000000000 --- a/Documentation/devicetree/bindings/extcon/extcon-rt8973a.txt +++ /dev/null @@ -1,23 +0,0 @@ - -* Richtek RT8973A - Micro USB Switch device - -The Richtek RT8973A is Micro USB Switch with OVP and I2C interface. The RT8973A -is a USB port accessory detector and switch that is optimized to protect low -voltage system from abnormal high input voltage (up to 28V) and supports high -speed USB operation. Also, RT8973A support 'auto-configuration' mode. -If auto-configuration mode is enabled, RT8973A would control internal h/w patch -for USB D-/D+ switching. - -Required properties: -- compatible: Should be "richtek,rt8973a-muic" -- reg: Specifies the I2C slave address of the MUIC block. It should be 0x14 -- interrupts: Interrupt specifiers for detection interrupt sources. - -Example: - - rt8973a@14 { - compatible = "richtek,rt8973a-muic"; - interrupt-parent = <&gpx1>; - interrupts = <5 0>; - reg = <0x14>; - }; diff --git a/Documentation/devicetree/bindings/extcon/linux,extcon-usb-gpio.yaml b/Documentation/devicetree/bindings/extcon/linux,extcon-usb-gpio.yaml index 8856107bdd33..8f29d333602b 100644 --- a/Documentation/devicetree/bindings/extcon/linux,extcon-usb-gpio.yaml +++ b/Documentation/devicetree/bindings/extcon/linux,extcon-usb-gpio.yaml @@ -25,6 +25,12 @@ properties: required: - compatible +anyOf: + - required: + - id-gpios + - required: + - vbus-gpios + additionalProperties: false examples: diff --git a/Documentation/devicetree/bindings/extcon/maxim,max14526.yaml b/Documentation/devicetree/bindings/extcon/maxim,max14526.yaml new file mode 100644 index 000000000000..7eb5918df1c2 --- /dev/null +++ b/Documentation/devicetree/bindings/extcon/maxim,max14526.yaml @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/extcon/maxim,max14526.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim MAX14526 MicroUSB Integrated Circuit (MUIC) + +maintainers: + - Svyatoslav Ryhel <clamor95@gmail.com> + +properties: + compatible: + const: maxim,max14526 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + connector: + $ref: /schemas/connector/usb-connector.yaml# + + port: + $ref: /schemas/graph.yaml#/properties/port + +required: + - compatible + - reg + - interrupts + - connector + - port + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + muic@44 { + compatible = "maxim,max14526"; + reg = <0x44>; + + interrupt-parent = <&gpio>; + interrupts = <72 IRQ_TYPE_EDGE_FALLING>; + + connector { + compatible = "usb-b-connector"; + label = "micro-USB"; + type = "micro"; + }; + + port { + #address-cells = <1>; + #size-cells = <0>; + + muic_to_charger: endpoint@0 { + reg = <0>; + remote-endpoint = <&charger_input>; + }; + + muic_to_usb: endpoint@1 { + reg = <1>; + remote-endpoint = <&usb_input>; + }; + + muic_to_mhl: endpoint@2 { + reg = <2>; + remote-endpoint = <&mhl_input>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/extcon/richtek,rt8973a-muic.yaml b/Documentation/devicetree/bindings/extcon/richtek,rt8973a-muic.yaml new file mode 100644 index 000000000000..f9e0d816c025 --- /dev/null +++ b/Documentation/devicetree/bindings/extcon/richtek,rt8973a-muic.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/extcon/richtek,rt8973a-muic.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Richtek RT8973A MUIC + +maintainers: + - Chanwoo Choi <cw00.choi@samsung.com> + +description: + The Richtek RT8973A is Micro USB Switch with OVP and I2C interface. The RT8973A + is a USB port accessory detector and switch that is optimized to protect low + voltage system from abnormal high input voltage (up to 28V) and supports high + speed USB operation. Also, RT8973A support 'auto-configuration' mode. + If auto-configuration mode is enabled, RT8973A would control internal h/w patch + for USB D-/D+ switching. + +properties: + compatible: + const: richtek,rt8973a-muic + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + i2c { + #address-cells = <1>; + #size-cells = <0>; + usb-switch@14 { + compatible = "richtek,rt8973a-muic"; + reg = <0x14>; + interrupt-parent = <&gpio>; + interrupts = <1 IRQ_TYPE_EDGE_FALLING>; + }; + }; diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adis16240.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adis16240.yaml index 5887021cc90f..a92e153705f3 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adis16240.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adis16240.yaml @@ -7,7 +7,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: ADIS16240 Programmable Impact Sensor and Recorder driver maintainers: - - Alexandru Tachici <alexandru.tachici@analog.com> + - Marcelo Schmitt <marcelo.schmitt@analog.com> + - Nuno Sá <nuno.sa@analog.com> description: | ADIS16240 Programmable Impact Sensor and Recorder driver that supports @@ -37,7 +38,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl313.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl313.yaml index 0c5b64cae965..3a8c69eecfde 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adxl313.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl313.yaml @@ -57,7 +57,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> i2c { #address-cells = <1>; @@ -73,7 +72,6 @@ examples: }; }; - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml index 84d949392012..a23a626bfab6 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml @@ -56,7 +56,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> i2c { #address-cells = <1>; @@ -72,7 +71,6 @@ examples: }; }; - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl355.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl355.yaml index c07261c71013..f39e2912731f 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adxl355.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl355.yaml @@ -58,7 +58,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> i2c { #address-cells = <1>; @@ -74,7 +73,6 @@ examples: }; }; - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl372.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl372.yaml index 62465e36a590..0ba0df46c3a9 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adxl372.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl372.yaml @@ -7,7 +7,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Analog Devices ADXL372 3-Axis, +/-(200g) Digital Accelerometer maintainers: - - Stefan Popa <stefan.popa@analog.com> + - Marcelo Schmitt <marcelo.schmitt@analog.com> + - Nuno Sá <nuno.sa@analog.com> description: | Analog Devices ADXL372 3-Axis, +/-(200g) Digital Accelerometer that supports @@ -37,7 +38,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> i2c { #address-cells = <1>; @@ -52,7 +52,6 @@ examples: }; }; - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/accel/bosch,bma255.yaml b/Documentation/devicetree/bindings/iio/accel/bosch,bma255.yaml index 457a709b583c..85c9537f1f02 100644 --- a/Documentation/devicetree/bindings/iio/accel/bosch,bma255.yaml +++ b/Documentation/devicetree/bindings/iio/accel/bosch,bma255.yaml @@ -107,7 +107,6 @@ examples: }; }; - | - # include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; #size-cells = <0>; diff --git a/Documentation/devicetree/bindings/iio/accel/bosch,bma400.yaml b/Documentation/devicetree/bindings/iio/accel/bosch,bma400.yaml index 8723a336229e..c5fedcf998f2 100644 --- a/Documentation/devicetree/bindings/iio/accel/bosch,bma400.yaml +++ b/Documentation/devicetree/bindings/iio/accel/bosch,bma400.yaml @@ -40,7 +40,6 @@ additionalProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> i2c { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/accel/kionix,kxsd9.yaml b/Documentation/devicetree/bindings/iio/accel/kionix,kxsd9.yaml index f64d99b35492..53de921768ac 100644 --- a/Documentation/devicetree/bindings/iio/accel/kionix,kxsd9.yaml +++ b/Documentation/devicetree/bindings/iio/accel/kionix,kxsd9.yaml @@ -57,7 +57,6 @@ examples: }; }; - | - # include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; #size-cells = <0>; diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7091r5.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7091r5.yaml index ddec9747436c..705adbe88def 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7091r5.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7091r5.yaml @@ -93,7 +93,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> i2c { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7124.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7124.yaml index 7146a654ae38..2e3f84db6193 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7124.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7124.yaml @@ -8,7 +8,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Analog Devices AD7124 ADC device driver maintainers: - - Stefan Popa <stefan.popa@analog.com> + - Marcelo Schmitt <marcelo.schmitt@analog.com> + - Nuno Sá <nuno.sa@analog.com> description: | Bindings for the Analog Devices AD7124 ADC device. Datasheet can be @@ -27,12 +28,21 @@ properties: clocks: maxItems: 1 - description: phandle to the master clock (mclk) + description: Optional external clock connected to the CLK pin. clock-names: + deprecated: true + description: + MCLK is an internal counter in the ADC. Do not use this property. items: - const: mclk + '#clock-cells': + description: + The CLK pin can be used as an output. When that is the case, include + this property. + const: 0 + interrupts: description: IRQ line for the ADC maxItems: 1 @@ -66,10 +76,14 @@ properties: required: - compatible - reg - - clocks - - clock-names - interrupts +# Can't have both clock input and output at the same time. +not: + required: + - '#clock-cells' + - clocks + patternProperties: "^channel@([0-9]|1[0-5])$": $ref: adc.yaml @@ -135,8 +149,6 @@ examples: interrupt-parent = <&gpio>; rdy-gpios = <&gpio 25 GPIO_ACTIVE_LOW>; refin1-supply = <&adc_vref>; - clocks = <&ad7124_mclk>; - clock-names = "mclk"; #address-cells = <1>; #size-cells = <0>; diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml index 21ee319d4675..62d906e24997 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7173.yaml @@ -379,7 +379,6 @@ unevaluatedProperties: false examples: # Example AD7173-8 with external reference connected to REF+/REF-: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> spi { diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7476.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7476.yaml index d0cb32f136e5..55880191c511 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7476.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7476.yaml @@ -41,6 +41,7 @@ properties: - adi,ad7910 - adi,ad7920 - adi,ad7940 + - rohm,bd79105 - ti,adc081s - ti,adc101s - ti,adc121s @@ -55,6 +56,11 @@ properties: reg: maxItems: 1 + interrupts: + description: + The data-ready interrupt. Provided via DOUT pin. + maxItems: 1 + vcc-supply: description: Main powersupply voltage for the chips, sometimes referred to as VDD on @@ -75,6 +81,10 @@ properties: description: A GPIO used to trigger the start of a conversion maxItems: 1 + rdy-gpios: + description: A GPIO for detecting the data-ready. + maxItems: 1 + required: - compatible - reg @@ -82,6 +92,20 @@ required: allOf: - $ref: /schemas/spi/spi-peripheral-props.yaml# +# Devices with an IRQ + - if: + properties: + compatible: + contains: + enum: + - rohm,bd79105 + then: + properties: + interrupts: true + else: + properties: + interrupts: false + # Devices where reference is vcc - if: properties: @@ -106,20 +130,19 @@ allOf: - vcc-supply # Devices with a vref - if: - properties: - compatible: - contains: - enum: - - adi,ad7091r - - adi,ad7273 - - adi,ad7274 - - adi,ad7475 - - lltc,ltc2314-14 + not: + properties: + compatible: + contains: + enum: + - adi,ad7091r + - adi,ad7273 + - adi,ad7274 + - adi,ad7475 + - lltc,ltc2314-14 + - rohm,bd79105 then: properties: - vref-supply: true - else: - properties: vref-supply: false # Devices with a vref where it is not optional - if: @@ -131,35 +154,58 @@ allOf: - adi,ad7274 - adi,ad7475 - lltc,ltc2314-14 + - rohm,bd79105 then: required: - vref-supply - if: + not: + properties: + compatible: + contains: + enum: + - adi,ad7475 + - adi,ad7495 + - rohm,bd79105 + then: properties: - compatible: - contains: - enum: - - adi,ad7475 - - adi,ad7495 + vdrive-supply: false + + # Devices which support polling the data-ready via GPIO + - if: + not: + properties: + compatible: + contains: + enum: + - rohm,bd79105 then: properties: - vdrive-supply: true - else: + rdy-gpios: false + + - if: + not: + properties: + compatible: + contains: + enum: + - adi,ad7091 + - adi,ad7091r + - rohm,bd79105 + then: properties: - vdrive-supply: false + adi,conversion-start-gpios: false + + # Devices with a convstart GPIO where it is not optional - if: properties: compatible: contains: enum: - - adi,ad7091 - - adi,ad7091r + - rohm,bd79105 then: - properties: - adi,conversion-start-gpios: true - else: - properties: - adi,conversion-start-gpios: false + required: + - adi,conversion-start-gpios unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7779.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7779.yaml index 044f92f39cfa..ba3f7b2bd6cf 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7779.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7779.yaml @@ -80,11 +80,36 @@ properties: reset-gpios: maxItems: 1 + io-backends: + maxItems: 1 + + adi,num-lanes: + description: + Number of lanes on which the data is sent on the output when the data + output interface is used. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 4] + default: 4 + required: - compatible - reg - clocks - - interrupts + +allOf: + - if: + not: + required: + - io-backends + then: + properties: + adi,num-lanes: false + +oneOf: + - required: + - interrupts + - required: + - io-backends unevaluatedProperties: false @@ -107,4 +132,21 @@ examples: clocks = <&adc_clk>; }; }; + + - | + #include <dt-bindings/gpio/gpio.h> + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,ad7779"; + reg = <0>; + start-gpios = <&gpio0 87 GPIO_ACTIVE_LOW>; + reset-gpios = <&gpio0 93 GPIO_ACTIVE_LOW>; + clocks = <&adc_clk>; + io-backends = <&iio_backend>; + adi,num-lanes = <4>; + }; + }; ... diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml new file mode 100644 index 000000000000..bd429552d568 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml @@ -0,0 +1,95 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright 2025 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ade9000.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices ADE9000 High Performance, Polyphase Energy Metering + +maintainers: + - Antoniu Miclaus <antoniu.miclaus@analog.com> + +description: | + The ADE9000 is a highly accurate, fully integrated, multiphase energy and power + quality monitoring device. Superior analog performance and a digital signal + processing (DSP) core enable accurate energy monitoring over a wide dynamic + range. An integrated high end reference ensures low drift over temperature + with a combined drift of less than ±25 ppm/°C maximum for the entire channel + including a programmable gain amplifier (PGA) and an analog-to-digital + converter (ADC). + + https://www.analog.com/media/en/technical-documentation/data-sheets/ADE9000.pdf + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - adi,ade9000 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 20000000 + + interrupts: + maxItems: 3 + + interrupt-names: + items: + enum: [irq0, irq1, dready] + minItems: 1 + maxItems: 3 + + reset-gpios: + description: + Must be the device tree identifier of the RESET pin. As the line is + active low, it should be marked GPIO_ACTIVE_LOW. + maxItems: 1 + + vdd-supply: true + + vref-supply: true + + clocks: + description: External clock source when not using crystal + maxItems: 1 + + + "#clock-cells": + description: + ADE9000 can provide clock output via CLKOUT pin with external buffer. + const: 0 + +required: + - compatible + - reg + - vdd-supply + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,ade9000"; + reg = <0>; + spi-max-frequency = <7000000>; + + #clock-cells = <0>; + reset-gpios = <&gpio 4 GPIO_ACTIVE_LOW>; + interrupts = <2 IRQ_TYPE_EDGE_FALLING>, <3 IRQ_TYPE_EDGE_FALLING>, <4 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "irq0", "irq1", "dready"; + interrupt-parent = <&gpio>; + clocks = <&ext_clock_24576khz>; + vdd-supply = <&vdd_reg>; + }; + }; diff --git a/Documentation/devicetree/bindings/iio/adc/lltc,ltc2496.yaml b/Documentation/devicetree/bindings/iio/adc/lltc,ltc2496.yaml index 5207c919abe0..eac48166fe72 100644 --- a/Documentation/devicetree/bindings/iio/adc/lltc,ltc2496.yaml +++ b/Documentation/devicetree/bindings/iio/adc/lltc,ltc2496.yaml @@ -9,7 +9,6 @@ title: Linear Technology / Analog Devices LTC2496 ADC maintainers: - Lars-Peter Clausen <lars@metafoo.de> - Michael Hennerich <Michael.Hennerich@analog.com> - - Stefan Popa <stefan.popa@analog.com> properties: compatible: diff --git a/Documentation/devicetree/bindings/iio/adc/maxim,max1238.yaml b/Documentation/devicetree/bindings/iio/adc/maxim,max1238.yaml index 60d7b34e3286..ae3c89393f1a 100644 --- a/Documentation/devicetree/bindings/iio/adc/maxim,max1238.yaml +++ b/Documentation/devicetree/bindings/iio/adc/maxim,max1238.yaml @@ -53,6 +53,9 @@ properties: reg: maxItems: 1 + "#io-channel-cells": + const: 1 + vcc-supply: true vref-supply: description: Optional external reference. If not supplied, internal diff --git a/Documentation/devicetree/bindings/iio/adc/maxim,max1241.yaml b/Documentation/devicetree/bindings/iio/adc/maxim,max1241.yaml index ef8d51e74c08..592854766583 100644 --- a/Documentation/devicetree/bindings/iio/adc/maxim,max1241.yaml +++ b/Documentation/devicetree/bindings/iio/adc/maxim,max1241.yaml @@ -63,6 +63,6 @@ examples: vdd-supply = <&adc_vdd>; vref-supply = <&adc_vref>; spi-max-frequency = <1000000>; - shutdown-gpios = <&gpio 26 1>; + shutdown-gpios = <&gpio 26 GPIO_ACTIVE_LOW>; }; }; diff --git a/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml b/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml index c28db0d635a0..b9dc04b0d307 100644 --- a/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/qcom,spmi-vadc.yaml @@ -278,7 +278,6 @@ examples: - | #include <dt-bindings/iio/qcom,spmi-adc7-pmk8350.h> #include <dt-bindings/iio/qcom,spmi-adc7-pm8350.h> - #include <dt-bindings/interrupt-controller/irq.h> pmic { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml b/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml index 41e0c56ef8e3..f776041fd08f 100644 --- a/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml @@ -47,6 +47,9 @@ properties: - const: saradc - const: apb_pclk + power-domains: + maxItems: 1 + resets: maxItems: 1 diff --git a/Documentation/devicetree/bindings/iio/adc/rohm,bd79104.yaml b/Documentation/devicetree/bindings/iio/adc/rohm,bd79104.yaml index 2a8ad4fdfc6b..d5192ec58f59 100644 --- a/Documentation/devicetree/bindings/iio/adc/rohm,bd79104.yaml +++ b/Documentation/devicetree/bindings/iio/adc/rohm,bd79104.yaml @@ -14,7 +14,15 @@ description: | properties: compatible: - const: rohm,bd79104 + oneOf: + - enum: + - rohm,bd79100 + - rohm,bd79101 + - rohm,bd79102 + - rohm,bd79104 + - items: + - const: rohm,bd79103 + - const: rohm,bd79104 reg: maxItems: 1 @@ -50,7 +58,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; #size-cells = <0>; diff --git a/Documentation/devicetree/bindings/iio/adc/rohm,bd79112.yaml b/Documentation/devicetree/bindings/iio/adc/rohm,bd79112.yaml new file mode 100644 index 000000000000..aa8b07c3fac1 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/rohm,bd79112.yaml @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/rohm,bd79112.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ROHM BD79112 ADC/GPO + +maintainers: + - Matti Vaittinen <mazziesaccount@gmail.com> + +description: | + The ROHM BD79112 is a 12-bit, 32-channel, SAR ADC. ADC input pins can be + also configured as general purpose inputs/outputs. SPI should use MODE 3. + +properties: + compatible: + const: rohm,bd79112 + + reg: + maxItems: 1 + + spi-cpha: true + spi-cpol: true + + gpio-controller: true + "#gpio-cells": + const: 2 + + vdd-supply: true + + iovdd-supply: true + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^channel@([0-9]|[12][0-9]|3[01])$": + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: Represents ADC channel. Omitted channels' inputs are GPIOs. + + properties: + reg: + description: AIN pin number + minimum: 0 + maximum: 31 + + required: + - reg + + additionalProperties: false + +required: + - compatible + - reg + - iovdd-supply + - vdd-supply + - spi-cpha + - spi-cpol + +additionalProperties: false + +examples: + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + adc: adc@0 { + compatible = "rohm,bd79112"; + reg = <0x0>; + + spi-cpha; + spi-cpol; + + vdd-supply = <&dummyreg>; + iovdd-supply = <&dummyreg>; + + #address-cells = <1>; + #size-cells = <0>; + + gpio-controller; + #gpio-cells = <2>; + + channel@0 { + reg = <0>; + }; + channel@1 { + reg = <1>; + }; + channel@2 { + reg = <2>; + }; + channel@16 { + reg = <16>; + }; + channel@20 { + reg = <20>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/iio/adc/rohm,bd79124.yaml b/Documentation/devicetree/bindings/iio/adc/rohm,bd79124.yaml index 503285823376..4a8f127de7e3 100644 --- a/Documentation/devicetree/bindings/iio/adc/rohm,bd79124.yaml +++ b/Documentation/devicetree/bindings/iio/adc/rohm,bd79124.yaml @@ -81,7 +81,7 @@ examples: reg = <0x10>; interrupt-parent = <&gpio1>; - interrupts = <29 8>; + interrupts = <29 IRQ_TYPE_LEVEL_LOW>; vdd-supply = <&dummyreg>; iovdd-supply = <&dummyreg>; diff --git a/Documentation/devicetree/bindings/iio/adc/samsung,exynos-adc.yaml b/Documentation/devicetree/bindings/iio/adc/samsung,exynos-adc.yaml index 4e40f6bed5db..def879f6ed20 100644 --- a/Documentation/devicetree/bindings/iio/adc/samsung,exynos-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/samsung,exynos-adc.yaml @@ -18,10 +18,6 @@ properties: - samsung,exynos3250-adc - samsung,exynos4212-adc # Exynos4212 and Exynos4412 - samsung,exynos7-adc - - samsung,s3c2410-adc - - samsung,s3c2416-adc - - samsung,s3c2440-adc - - samsung,s3c2443-adc - samsung,s3c6410-adc - samsung,s5pv210-adc - items: @@ -46,8 +42,6 @@ properties: maxItems: 2 interrupts: - description: - ADC interrupt followed by optional touchscreen interrupt. minItems: 1 maxItems: 2 @@ -62,11 +56,6 @@ properties: Phandle to the PMU system controller node (to access the ADC_PHY register on Exynos3250/4x12/5250/5420/5800). - has-touchscreen: - description: - If present, indicates that a touchscreen is connected and usable. - type: boolean - required: - compatible - reg @@ -118,20 +107,29 @@ allOf: - const: adc - if: - required: - - has-touchscreen + properties: + compatible: + contains: + const: samsung,s5pv210-adc then: properties: interrupts: - minItems: 2 - maxItems: 2 + items: + - description: main (ADC) + - description: pending (PENDN) + else: + properties: + interrupts: + maxItems: 1 examples: - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + adc: adc@12d10000 { compatible = "samsung,exynos-adc-v1"; reg = <0x12d10000 0x100>; - interrupts = <0 106 0>; + interrupts = <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>; #io-channel-cells = <1>; clocks = <&clock 303>; @@ -152,11 +150,12 @@ examples: - | #include <dt-bindings/clock/exynos3250.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> adc@126c0000 { compatible = "samsung,exynos3250-adc"; reg = <0x126c0000 0x100>; - interrupts = <0 137 0>; + interrupts = <GIC_SPI 137 IRQ_TYPE_LEVEL_HIGH>; #io-channel-cells = <1>; clocks = <&cmu CLK_TSADC>, diff --git a/Documentation/devicetree/bindings/iio/adc/ti,adc128s052.yaml b/Documentation/devicetree/bindings/iio/adc/ti,adc128s052.yaml index 775eee972b12..044b66a3b00c 100644 --- a/Documentation/devicetree/bindings/iio/adc/ti,adc128s052.yaml +++ b/Documentation/devicetree/bindings/iio/adc/ti,adc128s052.yaml @@ -44,7 +44,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; #size-cells = <0>; diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1298.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1298.yaml index bf5a43a81d59..71f9f9b745cb 100644 --- a/Documentation/devicetree/bindings/iio/adc/ti,ads1298.yaml +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1298.yaml @@ -59,7 +59,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/adc/xlnx,zynqmp-ams.yaml b/Documentation/devicetree/bindings/iio/adc/xlnx,zynqmp-ams.yaml index a403392fb263..3ae1a0bab38f 100644 --- a/Documentation/devicetree/bindings/iio/adc/xlnx,zynqmp-ams.yaml +++ b/Documentation/devicetree/bindings/iio/adc/xlnx,zynqmp-ams.yaml @@ -7,7 +7,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Xilinx Zynq Ultrascale AMS controller maintainers: - - Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com> + - Salih Erim <salih.erim@amd.com> + - Conall O'Griofa <conall.ogriofa@amd.com> description: | The AMS (Analog Monitoring System) includes an ADC as well as on-chip sensors diff --git a/Documentation/devicetree/bindings/iio/afe/current-sense-amplifier.yaml b/Documentation/devicetree/bindings/iio/afe/current-sense-amplifier.yaml index 527501c1d695..bcf4ddcfd13b 100644 --- a/Documentation/devicetree/bindings/iio/afe/current-sense-amplifier.yaml +++ b/Documentation/devicetree/bindings/iio/afe/current-sense-amplifier.yaml @@ -24,6 +24,9 @@ properties: description: | Channel node of a voltage io-channel. + "#io-channel-cells": + const: 0 + sense-resistor-micro-ohms: description: The sense resistance. @@ -46,6 +49,7 @@ examples: - | sysi { compatible = "current-sense-amplifier"; + #io-channel-cells = <0>; io-channels = <&tiadc 0>; sense-resistor-micro-ohms = <20000>; diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5770r.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5770r.yaml index 82b0eed6a7b7..091cc93f1f90 100644 --- a/Documentation/devicetree/bindings/iio/dac/adi,ad5770r.yaml +++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5770r.yaml @@ -8,7 +8,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Analog Devices AD5770R DAC device driver maintainers: - - Alexandru Tachici <alexandru.tachici@analog.com> + - Marcelo Schmitt <marcelo.schmitt@analog.com> + - Nuno Sá <nuno.sa@analog.com> description: | Bindings for the Analog Devices AD5770R current DAC device. Datasheet can be diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ltc2664.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ltc2664.yaml index 1aece3392b77..4688eccfeb89 100644 --- a/Documentation/devicetree/bindings/iio/dac/adi,ltc2664.yaml +++ b/Documentation/devicetree/bindings/iio/dac/adi,ltc2664.yaml @@ -174,7 +174,7 @@ examples: channel@1 { reg = <1>; - output-range-microvolt= <0 10000000>; + output-range-microvolt = <0 10000000>; }; }; }; diff --git a/Documentation/devicetree/bindings/iio/frequency/adf4371.yaml b/Documentation/devicetree/bindings/iio/frequency/adf4371.yaml index 53d607441612..2e1ff77fd1de 100644 --- a/Documentation/devicetree/bindings/iio/frequency/adf4371.yaml +++ b/Documentation/devicetree/bindings/iio/frequency/adf4371.yaml @@ -7,7 +7,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Analog Devices ADF4371/ADF4372 Wideband Synthesizers maintainers: - - Popa Stefan <stefan.popa@analog.com> + - Marcelo Schmitt <marcelo.schmitt@analog.com> + - Nuno Sá <nuno.sa@analog.com> description: | Analog Devices ADF4371/ADF4372 SPI Wideband Synthesizers diff --git a/Documentation/devicetree/bindings/iio/imu/adi,adis16460.yaml b/Documentation/devicetree/bindings/iio/imu/adi,adis16460.yaml index 4cacc9948726..3a725ece7ec4 100644 --- a/Documentation/devicetree/bindings/iio/imu/adi,adis16460.yaml +++ b/Documentation/devicetree/bindings/iio/imu/adi,adis16460.yaml @@ -44,7 +44,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/imu/adi,adis16480.yaml b/Documentation/devicetree/bindings/iio/imu/adi,adis16480.yaml index 7a1a74fec281..43ecf46e9c20 100644 --- a/Documentation/devicetree/bindings/iio/imu/adi,adis16480.yaml +++ b/Documentation/devicetree/bindings/iio/imu/adi,adis16480.yaml @@ -7,7 +7,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Analog Devices ADIS16480 and similar IMUs maintainers: - - Alexandru Tachici <alexandru.tachici@analog.com> + - Marcelo Schmitt <marcelo.schmitt@analog.com> + - Nuno Sá <nuno.sa@analog.com> properties: compatible: diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml index d4d4e5c3d856..119e28a833fd 100644 --- a/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml +++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml @@ -74,7 +74,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> i2c { #address-cells = <1>; @@ -91,7 +90,6 @@ examples: }; }; - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/imu/nxp,fxos8700.yaml b/Documentation/devicetree/bindings/iio/imu/nxp,fxos8700.yaml index 688100b240bc..2930b3386703 100644 --- a/Documentation/devicetree/bindings/iio/imu/nxp,fxos8700.yaml +++ b/Documentation/devicetree/bindings/iio/imu/nxp,fxos8700.yaml @@ -47,7 +47,6 @@ unevaluatedProperties: false examples: - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> i2c { #address-cells = <1>; @@ -63,7 +62,6 @@ examples: }; }; - | - #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/light/dynaimage,al3010.yaml b/Documentation/devicetree/bindings/iio/light/dynaimage,al3010.yaml index f1048c30e73e..1472c997c16f 100644 --- a/Documentation/devicetree/bindings/iio/light/dynaimage,al3010.yaml +++ b/Documentation/devicetree/bindings/iio/light/dynaimage,al3010.yaml @@ -42,6 +42,6 @@ examples: compatible = "dynaimage,al3010"; reg = <0x1c>; vdd-supply = <&vdd_reg>; - interrupts = <0 99 4>; + interrupts = <99 IRQ_TYPE_LEVEL_HIGH>; }; }; diff --git a/Documentation/devicetree/bindings/iio/light/dynaimage,al3320a.yaml b/Documentation/devicetree/bindings/iio/light/dynaimage,al3320a.yaml index 8249be99cff9..d06db737cd9e 100644 --- a/Documentation/devicetree/bindings/iio/light/dynaimage,al3320a.yaml +++ b/Documentation/devicetree/bindings/iio/light/dynaimage,al3320a.yaml @@ -40,6 +40,6 @@ examples: compatible = "dynaimage,al3320a"; reg = <0x1c>; vdd-supply = <&vdd_reg>; - interrupts = <0 99 4>; + interrupts = <99 IRQ_TYPE_LEVEL_HIGH>; }; }; diff --git a/Documentation/devicetree/bindings/iio/light/st,vl6180.yaml b/Documentation/devicetree/bindings/iio/light/st,vl6180.yaml index 27c36ab7990d..8598fb631aac 100644 --- a/Documentation/devicetree/bindings/iio/light/st,vl6180.yaml +++ b/Documentation/devicetree/bindings/iio/light/st,vl6180.yaml @@ -32,7 +32,6 @@ required: examples: - | - #include <dt-bindings/interrupt-controller/irq.h> i2c { #address-cells = <1>; #size-cells = <0>; diff --git a/Documentation/devicetree/bindings/iio/light/vishay,veml6046x00.yaml b/Documentation/devicetree/bindings/iio/light/vishay,veml6046x00.yaml new file mode 100644 index 000000000000..112d448ff0bf --- /dev/null +++ b/Documentation/devicetree/bindings/iio/light/vishay,veml6046x00.yaml @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/light/vishay,veml6046x00.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Vishay VEML6046X00 High accuracy RGBIR color sensor + +maintainers: + - Andreas Klinger <ak@it-klinger.de> + +description: + VEML6046X00 datasheet at https://www.vishay.com/docs/80173/veml6046x00.pdf + +properties: + compatible: + enum: + - vishay,veml6046x00 + + reg: + maxItems: 1 + + vdd-supply: true + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + color-sensor@29 { + compatible = "vishay,veml6046x00"; + reg = <0x29>; + vdd-supply = <&vdd_reg>; + interrupt-parent = <&gpio2>; + interrupts = <3 IRQ_TYPE_EDGE_FALLING>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/iio/magnetometer/infineon,tlv493d-a1b6.yaml b/Documentation/devicetree/bindings/iio/magnetometer/infineon,tlv493d-a1b6.yaml new file mode 100644 index 000000000000..dd23a9370a71 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/magnetometer/infineon,tlv493d-a1b6.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/magnetometer/infineon,tlv493d-a1b6.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Infineon Technologies TLV493D Low-Power 3D Magnetic Sensor + +maintainers: + - Dixit Parmar <dixitparmar19@gmail.com> + +properties: + $nodename: + pattern: '^magnetometer@[0-9a-f]+$' + + compatible: + const: infineon,tlv493d-a1b6 + + reg: + maxItems: 1 + + vdd-supply: + description: 2.8V to 3.5V VDD supply + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + magnetometer@5e { + compatible = "infineon,tlv493d-a1b6"; + reg = <0x5e>; + vdd-supply = <&hall_vcc>; + }; + }; diff --git a/Documentation/devicetree/bindings/iio/magnetometer/voltafield,af8133j.yaml b/Documentation/devicetree/bindings/iio/magnetometer/voltafield,af8133j.yaml index b6ab01a6914a..ed42dc5afb99 100644 --- a/Documentation/devicetree/bindings/iio/magnetometer/voltafield,af8133j.yaml +++ b/Documentation/devicetree/bindings/iio/magnetometer/voltafield,af8133j.yaml @@ -44,7 +44,6 @@ additionalProperties: false examples: - | - #include <dt-bindings/interrupt-controller/irq.h> #include <dt-bindings/gpio/gpio.h> i2c { #address-cells = <1>; diff --git a/Documentation/devicetree/bindings/iio/pressure/bmp085.yaml b/Documentation/devicetree/bindings/iio/pressure/bmp085.yaml index 706b7e24f182..b9ea37317b53 100644 --- a/Documentation/devicetree/bindings/iio/pressure/bmp085.yaml +++ b/Documentation/devicetree/bindings/iio/pressure/bmp085.yaml @@ -109,7 +109,6 @@ examples: }; - | # include <dt-bindings/gpio/gpio.h> - # include <dt-bindings/interrupt-controller/irq.h> spi { #address-cells = <1>; #size-cells = <0>; diff --git a/Documentation/devicetree/bindings/iio/pressure/invensense,icp10100.yaml b/Documentation/devicetree/bindings/iio/pressure/invensense,icp10100.yaml new file mode 100644 index 000000000000..5d980aa04bb3 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/pressure/invensense,icp10100.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/pressure/invensense,icp10100.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: InvenSense ICP-101xx Barometric Pressure Sensors + +maintainers: + - Jean-Baptiste Maneyrol <jean-baptiste.maneyrol@tdk.com> + +description: | + Support for ICP-101xx family: ICP-10100, ICP-10101, ICP-10110, ICP-10111. + Those devices uses a simple I2C communication bus, measuring the pressure + in a ultra-low noise at the lowest power. + Datasheet: https://product.tdk.com/system/files/dam/doc/product/sensor/pressure/capacitive-pressure/data_sheet/ds-000186-icp-101xx.pdf + +properties: + compatible: + oneOf: + - items: + - enum: + - invensense,icp10101 + - invensense,icp10110 + - invensense,icp10111 + - const: invensense,icp10100 + - const: invensense,icp10100 + + reg: + maxItems: 1 + + vdd-supply: true + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + pressure@63 { + compatible = "invensense,icp10101", "invensense,icp10100"; + reg = <0x63>; + vdd-supply = <&vdd_1v8>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/iio/temperature/microchip,mcp9600.yaml b/Documentation/devicetree/bindings/iio/temperature/microchip,mcp9600.yaml index d2cafa38a544..effe3bee495d 100644 --- a/Documentation/devicetree/bindings/iio/temperature/microchip,mcp9600.yaml +++ b/Documentation/devicetree/bindings/iio/temperature/microchip,mcp9600.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/iio/temperature/microchip,mcp9600.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Microchip MCP9600 thermocouple EMF converter +title: Microchip MCP9600 and similar thermocouple EMF converters maintainers: - Andrew Hepp <andrew.hepp@ahepp.dev> @@ -14,7 +14,11 @@ description: properties: compatible: - const: microchip,mcp9600 + oneOf: + - const: microchip,mcp9600 + - items: + - const: microchip,mcp9601 + - const: microchip,mcp9600 reg: maxItems: 1 @@ -37,13 +41,43 @@ properties: thermocouple-type: $ref: /schemas/types.yaml#/definitions/uint32 + default: 3 description: Type of thermocouple (THERMOCOUPLE_TYPE_K if omitted). Use defines in dt-bindings/iio/temperature/thermocouple.h. Supported types are B, E, J, K, N, R, S, T. + microchip,vsense: + type: boolean + description: + This flag indicates that the chip has been wired with VSENSE to + enable open and short circuit detect. + vdd-supply: true +allOf: + - if: + properties: + compatible: + not: + contains: + const: microchip,mcp9601 + then: + properties: + interrupts: + minItems: 1 + maxItems: 4 + interrupt-names: + minItems: 1 + maxItems: 4 + items: + enum: + - alert1 + - alert2 + - alert3 + - alert4 + microchip,vsense: false + required: - compatible - reg @@ -63,8 +97,24 @@ examples: reg = <0x60>; interrupt-parent = <&gpio>; interrupts = <25 IRQ_TYPE_EDGE_RISING>; - interrupt-names = "open-circuit"; + interrupt-names = "alert1"; thermocouple-type = <THERMOCOUPLE_TYPE_K>; vdd-supply = <&vdd>; }; }; + - | + #include <dt-bindings/interrupt-controller/irq.h> + i2c { + #address-cells = <1>; + #size-cells = <0>; + + temperature-sensor@62 { + compatible = "microchip,mcp9601", "microchip,mcp9600"; + reg = <0x62>; + interrupt-parent = <&gpio>; + interrupts = <22 IRQ_TYPE_EDGE_RISING>, <23 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "open-circuit", "short-circuit"; + vdd-supply = <&vdd>; + microchip,vsense; + }; + }; diff --git a/Documentation/devicetree/bindings/interconnect/qcom,glymur-rpmh.yaml b/Documentation/devicetree/bindings/interconnect/qcom,glymur-rpmh.yaml new file mode 100644 index 000000000000..d55a7bcf5591 --- /dev/null +++ b/Documentation/devicetree/bindings/interconnect/qcom,glymur-rpmh.yaml @@ -0,0 +1,172 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interconnect/qcom,glymur-rpmh.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm RPMh Network-On-Chip Interconnect on GLYMUR + +maintainers: + - Raviteja Laggyshetty <raviteja.laggyshetty@oss.qualcomm.com> + +description: | + RPMh interconnect providers support system bandwidth requirements through + RPMh hardware accelerators known as Bus Clock Manager (BCM). The provider is + able to communicate with the BCM through the Resource State Coordinator (RSC) + associated with each execution environment. Provider nodes must point to at + least one RPMh device child node pertaining to their RSC and each provider + can map to multiple RPMh resources. + + See also: include/dt-bindings/interconnect/qcom,glymur-rpmh.h + +properties: + compatible: + enum: + - qcom,glymur-aggre1-noc + - qcom,glymur-aggre2-noc + - qcom,glymur-aggre3-noc + - qcom,glymur-aggre4-noc + - qcom,glymur-clk-virt + - qcom,glymur-cnoc-cfg + - qcom,glymur-cnoc-main + - qcom,glymur-hscnoc + - qcom,glymur-lpass-ag-noc + - qcom,glymur-lpass-lpiaon-noc + - qcom,glymur-lpass-lpicx-noc + - qcom,glymur-mc-virt + - qcom,glymur-mmss-noc + - qcom,glymur-nsinoc + - qcom,glymur-nsp-noc + - qcom,glymur-oobm-ss-noc + - qcom,glymur-pcie-east-anoc + - qcom,glymur-pcie-east-slv-noc + - qcom,glymur-pcie-west-anoc + - qcom,glymur-pcie-west-slv-noc + - qcom,glymur-system-noc + + reg: + maxItems: 1 + + clocks: + minItems: 1 + maxItems: 4 + +required: + - compatible + +allOf: + - $ref: qcom,rpmh-common.yaml# + - if: + properties: + compatible: + contains: + enum: + - qcom,glymur-clk-virt + - qcom,glymur-mc-virt + then: + properties: + reg: false + else: + required: + - reg + + - if: + properties: + compatible: + contains: + enum: + - qcom,glymur-pcie-west-anoc + then: + properties: + clocks: + items: + - description: aggre PCIE_3A WEST AXI clock + - description: aggre PCIE_3B WEST AXI clock + - description: aggre PCIE_4 WEST AXI clock + - description: aggre PCIE_6 WEST AXI clock + + - if: + properties: + compatible: + contains: + enum: + - qcom,glymur-pcie-east-anoc + then: + properties: + clocks: + items: + - description: aggre PCIE_5 EAST AXI clock + + - if: + properties: + compatible: + contains: + enum: + - qcom,glymur-aggre2-noc + then: + properties: + clocks: + items: + - description: aggre USB3 TERT AXI clock + - description: aggre USB4_2 AXI clock + - description: aggre UFS PHY AXI clock + + - if: + properties: + compatible: + contains: + enum: + - qcom,glymur-aggre4-noc + then: + properties: + clocks: + items: + - description: aggre USB3 PRIM AXI clock + - description: aggre USB3 SEC AXI clock + - description: aggre USB4_0 AXI clock + - description: aggre USB4_1 AXI clock + + - if: + properties: + compatible: + contains: + enum: + - qcom,glymur-pcie-west-anoc + - qcom,glymur-pcie-east-anoc + - qcom,glymur-aggre2-noc + - qcom,glymur-aggre4-noc + then: + required: + - clocks + else: + properties: + clocks: false + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/clock/qcom,glymur-gcc.h> + clk_virt: interconnect-0 { + compatible = "qcom,glymur-clk-virt"; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + aggre1_noc: interconnect@16e0000 { + compatible = "qcom,glymur-aggre1-noc"; + reg = <0x016e0000 0x14400>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + }; + + aggre4_noc: interconnect@1740000 { + compatible = "qcom,glymur-aggre4-noc"; + reg = <0x01740000 0x14400>; + #interconnect-cells = <2>; + qcom,bcm-voters = <&apps_bcm_voter>; + clocks = <&gcc GCC_AGGRE_USB3_PRIM_AXI_CLK>, + <&gcc GCC_AGGRE_USB3_SEC_AXI_CLK>, + <&gcc GCC_AGGRE_USB4_0_AXI_CLK>, + <&gcc GCC_AGGRE_USB4_1_AXI_CLK>; + }; diff --git a/Documentation/devicetree/bindings/interconnect/qcom,osm-l3.yaml b/Documentation/devicetree/bindings/interconnect/qcom,osm-l3.yaml index ab5a921c3495..4b9b98fbe8f2 100644 --- a/Documentation/devicetree/bindings/interconnect/qcom,osm-l3.yaml +++ b/Documentation/devicetree/bindings/interconnect/qcom,osm-l3.yaml @@ -41,6 +41,11 @@ properties: - qcom,qcs8300-epss-l3 - const: qcom,sa8775p-epss-l3 - const: qcom,epss-l3 + - items: + - enum: + - qcom,qcs615-osm-l3 + - const: qcom,sm8150-osm-l3 + - const: qcom,osm-l3 reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/mfd/marvell,88pm886-a1.yaml b/Documentation/devicetree/bindings/mfd/marvell,88pm886-a1.yaml index d6a71c912b76..92a72a99fd79 100644 --- a/Documentation/devicetree/bindings/mfd/marvell,88pm886-a1.yaml +++ b/Documentation/devicetree/bindings/mfd/marvell,88pm886-a1.yaml @@ -35,6 +35,9 @@ properties: description: LDO or buck regulator. unevaluatedProperties: false + '#io-channel-cells': + const: 1 + required: - compatible - reg @@ -53,6 +56,7 @@ examples: reg = <0x30>; interrupts = <0 4 IRQ_TYPE_LEVEL_HIGH>; interrupt-parent = <&gic>; + #io-channel-cells = <1>; wakeup-source; regulators { diff --git a/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml b/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml index 0840a3d92513..3f6199fc9ae6 100644 --- a/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml +++ b/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml @@ -27,6 +27,8 @@ properties: - sdsp - cdsp - cdsp1 + - gdsp0 + - gdsp1 memory-region: maxItems: 1 diff --git a/Documentation/devicetree/bindings/nvmem/airoha,an8855-efuse.yaml b/Documentation/devicetree/bindings/nvmem/airoha,an8855-efuse.yaml new file mode 100644 index 000000000000..9802d9ea2176 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/airoha,an8855-efuse.yaml @@ -0,0 +1,123 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/nvmem/airoha,an8855-efuse.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha AN8855 Switch EFUSE + +maintainers: + - Christian Marangi <ansuelsmth@gmail.com> + +description: + Airoha AN8855 EFUSE used to calibrate internal PHYs and store additional + configuration info. + +$ref: nvmem.yaml# + +properties: + compatible: + const: airoha,an8855-efuse + + '#nvmem-cell-cells': + const: 0 + +required: + - compatible + - '#nvmem-cell-cells' + +unevaluatedProperties: false + +examples: + - | + efuse { + compatible = "airoha,an8855-efuse"; + + #nvmem-cell-cells = <0>; + + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + shift_sel_port0_tx_a: shift-sel-port0-tx-a@c { + reg = <0xc 0x4>; + }; + + shift_sel_port0_tx_b: shift-sel-port0-tx-b@10 { + reg = <0x10 0x4>; + }; + + shift_sel_port0_tx_c: shift-sel-port0-tx-c@14 { + reg = <0x14 0x4>; + }; + + shift_sel_port0_tx_d: shift-sel-port0-tx-d@18 { + reg = <0x18 0x4>; + }; + + shift_sel_port1_tx_a: shift-sel-port1-tx-a@1c { + reg = <0x1c 0x4>; + }; + + shift_sel_port1_tx_b: shift-sel-port1-tx-b@20 { + reg = <0x20 0x4>; + }; + + shift_sel_port1_tx_c: shift-sel-port1-tx-c@24 { + reg = <0x24 0x4>; + }; + + shift_sel_port1_tx_d: shift-sel-port1-tx-d@28 { + reg = <0x28 0x4>; + }; + + shift_sel_port2_tx_a: shift-sel-port2-tx-a@2c { + reg = <0x2c 0x4>; + }; + + shift_sel_port2_tx_b: shift-sel-port2-tx-b@30 { + reg = <0x30 0x4>; + }; + + shift_sel_port2_tx_c: shift-sel-port2-tx-c@34 { + reg = <0x34 0x4>; + }; + + shift_sel_port2_tx_d: shift-sel-port2-tx-d@38 { + reg = <0x38 0x4>; + }; + + shift_sel_port3_tx_a: shift-sel-port3-tx-a@4c { + reg = <0x4c 0x4>; + }; + + shift_sel_port3_tx_b: shift-sel-port3-tx-b@50 { + reg = <0x50 0x4>; + }; + + shift_sel_port3_tx_c: shift-sel-port3-tx-c@54 { + reg = <0x54 0x4>; + }; + + shift_sel_port3_tx_d: shift-sel-port3-tx-d@58 { + reg = <0x58 0x4>; + }; + + shift_sel_port4_tx_a: shift-sel-port4-tx-a@5c { + reg = <0x5c 0x4>; + }; + + shift_sel_port4_tx_b: shift-sel-port4-tx-b@60 { + reg = <0x60 0x4>; + }; + + shift_sel_port4_tx_c: shift-sel-port4-tx-c@64 { + reg = <0x64 0x4>; + }; + + shift_sel_port4_tx_d: shift-sel-port4-tx-d@68 { + reg = <0x68 0x4>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml b/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml index c713e23819f1..afd1919c6b1c 100644 --- a/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml +++ b/Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml @@ -19,7 +19,12 @@ select: false properties: compatible: - const: kontron,sl28-vpd + oneOf: + - items: + - enum: + - kontron,sa67-vpd + - const: kontron,sl28-vpd + - const: kontron,sl28-vpd serial-number: type: object diff --git a/Documentation/devicetree/bindings/nvmem/nxp,s32g-ocotp-nvmem.yaml b/Documentation/devicetree/bindings/nvmem/nxp,s32g-ocotp-nvmem.yaml new file mode 100644 index 000000000000..8d46e7d28da6 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/nxp,s32g-ocotp-nvmem.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/nvmem/nxp,s32g-ocotp-nvmem.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP S32G OCOTP NVMEM driver + +maintainers: + - Ciprian Costea <ciprianmarian.costea@nxp.com> + +description: + The drivers provides an interface to access One Time + Programmable memory pages, such as TMU fuse values. + +properties: + compatible: + oneOf: + - enum: + - nxp,s32g2-ocotp + - items: + - enum: + - nxp,s32g3-ocotp + - nxp,s32r45-ocotp + - const: nxp,s32g2-ocotp + reg: + maxItems: 1 + +required: + - compatible + - reg + +unevaluatedProperties: false + +allOf: + - $ref: nvmem.yaml# + +examples: + - | + nvmem@400a4000 { + compatible = "nxp,s32g2-ocotp"; + reg = <0x400a4000 0x400>; + #address-cells = <1>; + #size-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/slimbus/qcom,slim.yaml b/Documentation/devicetree/bindings/slimbus/qcom,slim.yaml deleted file mode 100644 index 883bda58ca97..000000000000 --- a/Documentation/devicetree/bindings/slimbus/qcom,slim.yaml +++ /dev/null @@ -1,86 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/slimbus/qcom,slim.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Qualcomm SoC SLIMbus controller - -maintainers: - - Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> - - Srinivas Kandagatla <srinivas.kandagatla@linaro.org> - -description: - SLIMbus controller used when applications processor controls SLIMbus master - component. - -allOf: - - $ref: slimbus.yaml# - -properties: - compatible: - items: - - enum: - - qcom,apq8064-slim - - const: qcom,slim - - reg: - items: - - description: Physical address of controller register blocks - - description: SLEW RATE register - - reg-names: - items: - - const: ctrl - - const: slew - - clocks: - items: - - description: Interface clock for this controller - - description: Interrupt for controller core's BAM - - clock-names: - items: - - const: iface - - const: core - - interrupts: - maxItems: 1 - -required: - - compatible - - reg - - reg-names - - clocks - - clock-names - - interrupts - -unevaluatedProperties: false - -examples: - - | - #include <dt-bindings/clock/qcom,gcc-msm8960.h> - #include <dt-bindings/clock/qcom,lcc-msm8960.h> - #include <dt-bindings/interrupt-controller/arm-gic.h> - - soc { - #address-cells = <1>; - #size-cells = <1>; - ranges; - - slim@28080000 { - compatible = "qcom,apq8064-slim", "qcom,slim"; - reg = <0x28080000 0x2000>, <0x80207c 4>; - reg-names = "ctrl", "slew"; - interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&lcc SLIMBUS_SRC>, <&lcc AUDIO_SLIMBUS_CLK>; - clock-names = "iface", "core"; - #address-cells = <2>; - #size-cells = <0>; - - audio-codec@1,0 { - compatible = "slim217,60"; - reg = <1 0>; - }; - }; - }; diff --git a/Documentation/devicetree/bindings/slimbus/slimbus.yaml b/Documentation/devicetree/bindings/slimbus/slimbus.yaml index 3b8cae9d1016..89017d9cda10 100644 --- a/Documentation/devicetree/bindings/slimbus/slimbus.yaml +++ b/Documentation/devicetree/bindings/slimbus/slimbus.yaml @@ -68,8 +68,6 @@ additionalProperties: true examples: - | - #include <dt-bindings/clock/qcom,gcc-msm8960.h> - #include <dt-bindings/clock/qcom,lcc-msm8960.h> #include <dt-bindings/interrupt-controller/arm-gic.h> soc { @@ -78,17 +76,14 @@ examples: ranges; slim@28080000 { - compatible = "qcom,apq8064-slim", "qcom,slim"; - reg = <0x28080000 0x2000>, <0x80207c 4>; - reg-names = "ctrl", "slew"; - interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&lcc SLIMBUS_SRC>, <&lcc AUDIO_SLIMBUS_CLK>; - clock-names = "iface", "core"; + compatible = "qcom,slim-ngd-v1.5.0"; + reg = <0x091c0000 0x2c000>; + interrupts = <GIC_SPI 163 IRQ_TYPE_LEVEL_HIGH>; #address-cells = <2>; #size-cells = <0>; audio-codec@1,0 { - compatible = "slim217,60"; + compatible = "slim217,1a0"; reg = <1 0>; }; }; diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 174a67707b08..58ff948d93c9 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -133,8 +133,6 @@ properties: - infineon,ir36021 # Infineon IRPS5401 Voltage Regulator (PMIC) - infineon,irps5401 - # Infineon TLV493D-A1B6 I2C 3D Magnetic Sensor - - infineon,tlv493d-a1b6 # Infineon Hot-swap controller xdp710 - infineon,xdp710 # Infineon Multi-phase Digital VR Controller xdpe11280 diff --git a/Documentation/devicetree/bindings/w1/fsl-imx-owire.yaml b/Documentation/devicetree/bindings/w1/fsl-imx-owire.yaml index 55adea827c34..2c1bbc0eb05a 100644 --- a/Documentation/devicetree/bindings/w1/fsl-imx-owire.yaml +++ b/Documentation/devicetree/bindings/w1/fsl-imx-owire.yaml @@ -24,6 +24,9 @@ properties: reg: maxItems: 1 + interrupts: + maxItems: 1 + clocks: maxItems: 1 @@ -40,5 +43,6 @@ examples: owire@63fa4000 { compatible = "fsl,imx53-owire", "fsl,imx21-owire"; reg = <0x63fa4000 0x4000>; + interrupts = <88>; clocks = <&clks IMX5_CLK_OWIRE_GATE>; }; diff --git a/Documentation/iio/ad3552r.rst b/Documentation/iio/ad3552r.rst index f5d59e4e86c7..4274e35f503d 100644 --- a/Documentation/iio/ad3552r.rst +++ b/Documentation/iio/ad3552r.rst @@ -64,7 +64,8 @@ specific debugfs path ``/sys/kernel/debug/iio/iio:deviceX``. Usage examples -------------- -. code-block:: bash +.. code-block:: bash + root:/sys/bus/iio/devices/iio:device0# cat data_source normal root:/sys/bus/iio/devices/iio:device0# echo -n ramp-16bit > data_source diff --git a/Documentation/iio/ade9000.rst b/Documentation/iio/ade9000.rst new file mode 100644 index 000000000000..43d4b8dc1cb7 --- /dev/null +++ b/Documentation/iio/ade9000.rst @@ -0,0 +1,268 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=============== +ADE9000 driver +=============== + +This driver supports Analog Device's ADE9000 energy measurement IC on SPI bus. + +1. Supported devices +==================== + +* `ADE9000 <https://www.analog.com/media/en/technical-documentation/data-sheets/ADE9000.pdf>`_ + +The ADE9000 is a highly accurate, fully integrated, multiphase energy and power +quality monitoring device. Superior analog performance and a digital signal +processing (DSP) core enable accurate energy monitoring over a wide dynamic +range. An integrated high end reference ensures low drift over temperature +with a combined drift of less than ±25 ppm/°C maximum for the entire channel +including a programmable gain amplifier (PGA) and an analog-to-digital +converter (ADC). + +2. Device attributes +==================== + +Power and energy measurements are provided for voltage, current, active power, +reactive power, apparent power, and power factor across three phases. + +Each IIO device has a device folder under ``/sys/bus/iio/devices/iio:deviceX``, +where X is the IIO index of the device. Under these folders reside a set of +device files, depending on the characteristics and features of the hardware +device in question. These files are consistently generalized and documented in +the IIO ABI documentation. + +The following tables show the ADE9000 related device files, found in the +specific device folder path ``/sys/bus/iio/devices/iio:deviceX``. + ++---------------------------------------------------+----------------------------------------------------------+ +| Current measurement related device files | Description | ++---------------------------------------------------+----------------------------------------------------------+ +| in_current[0-2]_raw | Raw current measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_current[0-2]_scale | Scale for current channels. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_current[0-2]_calibscale | Calibration gain for current channels (AIGAIN reg). | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altcurrent[0-2]_rms_raw | RMS current measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altcurrent[0-2]_rms_scale | Scale for RMS current channels. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altcurrent[0-2]_rms_calibbias | RMS offset correction for current channels (IRMSOS reg). | ++---------------------------------------------------+----------------------------------------------------------+ + ++---------------------------------------------------+----------------------------------------------------------+ +| Voltage measurement related device files | Description | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage[0-2]_raw | Raw voltage measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage[0-2]_scale | Scale for voltage channels. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage[0-2]_calibscale | Calibration gain for voltage channels (AVGAIN reg). | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage[0-2]_frequency | Measured line frequency from instantaneous voltage. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altvoltage[0-2]_rms_raw | RMS voltage measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altvoltage[0-2]_rms_scale | Scale for RMS voltage channels. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altvoltage[0-2]_rms_calibbias | RMS offset correction for voltage channels (VRMSOS reg). | ++---------------------------------------------------+----------------------------------------------------------+ + ++---------------------------------------------------+----------------------------------------------------------+ +| Power measurement related device files | Description | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_active_raw | Active power measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_active_scale | Scale for active power channels. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_active_calibbias | Calibration offset for active power (xWATTOS regs). | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_active_calibscale | Calibration gain for active power (APGAIN reg). | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_reactive_raw | Reactive power measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_reactive_scale | Scale for reactive power channels. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_reactive_calibbias | Calibration offset for reactive power (xVAROS regs). | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_apparent_raw | Apparent power measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_apparent_scale | Scale for apparent power channels. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_power[0-2]_powerfactor | Power factor for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ + ++---------------------------------------------------+----------------------------------------------------------+ +| Energy measurement related device files | Description | ++---------------------------------------------------+----------------------------------------------------------+ +| in_energy[0-2]_active_raw | Active energy measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_energy[0-2]_reactive_raw | Reactive energy measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ +| in_energy[0-2]_apparent_raw | Apparent energy measurement for phases A, B, C. | ++---------------------------------------------------+----------------------------------------------------------+ + ++------------------------------+------------------------------------------------------------------+ +| Shared device attributes | Description | ++------------------------------+------------------------------------------------------------------+ +| name | Name of the IIO device. | ++------------------------------+------------------------------------------------------------------+ +| filter_type | Waveform buffer filter type (sinc4, sinc4+lp). | ++------------------------------+------------------------------------------------------------------+ +| filter_type_available | Available filter types for waveform buffer. | ++------------------------------+------------------------------------------------------------------+ + +3. Calibration and scaling +=========================== + +The ADE9000 provides multiple levels of gain and offset correction: + +**Calibration Gain (per-channel)** + Fine-tuning calibration gains applied in the digital domain for each channel type. + Controlled via ``calibscale`` attributes (AIGAIN, AVGAIN, APGAIN registers). + +**Calibration Bias (per-channel)** + Hardware calibration offsets applied by the device internally: + + - Power measurements: Controlled via ``calibbias`` attributes for power channels (xWATTOS, xVAROS registers). + - RMS measurements: Controlled via ``calibbias`` attributes for RMS channels (IRMSOS, VRMSOS registers). + + These are internal chip calibrations, not userspace-applied offsets. + +4. Event attributes +=================== + +The ADE9000 provides various interrupts that are mapped to IIO events. +Event functionality is only available if the corresponding interrupts are +connected in the device tree. + ++---------------------------------------------------+----------------------------------------------------------+ +| IIO Event Attribute | ADE9000 Datasheet Equivalent | ++---------------------------------------------------+----------------------------------------------------------+ +| in_voltage[0-2]_thresh_either_en | Zero crossing detection interrupt (ZXVx) | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altvoltage[0-2]_rms_thresh_rising_en | RMS swell detection interrupt (SWELLx) | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altvoltage[0-2]_rms_thresh_rising_value | RMS swell threshold (SWELL_LVL register) | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altvoltage[0-2]_rms_thresh_falling_en | RMS sag/dip detection interrupt (DIPx) | ++---------------------------------------------------+----------------------------------------------------------+ +| in_altvoltage[0-2]_rms_thresh_falling_value | RMS sag/dip threshold (DIP_LVL register) | ++---------------------------------------------------+----------------------------------------------------------+ +| in_current[0-2]_thresh_either_en | Current zero crossing detection interrupt (ZXIx) | ++---------------------------------------------------+----------------------------------------------------------+ + +Event directions: + +- ``rising``: Upper threshold crossing (swell detection) +- ``falling``: Lower threshold crossing (sag/dip detection) +- ``either``: Any threshold crossing (zero crossing detection) +- ``none``: Timeout or non-directional events + +**Note**: Event attributes are only available if the corresponding interrupts +(irq0, irq1, dready) are specified in the device tree. The driver works without +interrupts but with reduced functionality. + +5. Device buffers +================= + +This driver supports IIO buffers for waveform capture. Buffer functionality +requires the dready interrupt to be connected. + +The device supports capturing voltage and current waveforms for power quality +analysis. The waveform buffer can be configured to capture data from different +channel combinations. + +Supported channel combinations for buffered capture: + +- Phase A: voltage and current (IA + VA) +- Phase B: voltage and current (IB + VB) +- Phase C: voltage and current (IC + VC) +- All phases: all voltage and current channels +- Individual channels: IA, VA, IB, VB, IC, VC + +Usage examples +-------------- + +Enable waveform capture for Phase A: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_current0_en + root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_voltage0_en + +Set buffer length and enable: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> echo 100 > buffer/length + root:/sys/bus/iio/devices/iio:device0> echo 1 > buffer/enable + +6. Clock output +=============== + +The ADE9000 can provide a clock output via the CLKOUT pin when using an external +crystal/clock source. This feature is enabled by specifying ``#clock-cells = <0>`` +in the device tree. The output clock will be registered as "clkout" and can be +referenced by other devices. + +7. Usage examples +================= + +Show device name: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat name + ade9000 + +Read voltage measurements: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat in_voltage0_raw + 12345 + root:/sys/bus/iio/devices/iio:device0> cat in_voltage0_scale + 0.000030517 + +- Phase A voltage = in_voltage0_raw * in_voltage0_scale = 0.3769 V + +Read power measurements: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat in_power0_active_raw + 5678 + root:/sys/bus/iio/devices/iio:device0> cat in_power0_scale + 0.000244140 + +- Phase A active power = in_power0_active_raw * in_power0_scale = 1.386 W + +Configure calibration gains: + +.. code-block:: bash + + # Set current channel 0 calibration gain + root:/sys/bus/iio/devices/iio:device0> echo 0x800000 > in_current0_calibscale + # Set voltage channel 0 calibration gain + root:/sys/bus/iio/devices/iio:device0> echo 0x7FFFFF > in_voltage0_calibscale + +Configure RMS voltage event thresholds (requires interrupts): + +.. code-block:: bash + + # Set RMS sag detection threshold + root:/sys/bus/iio/devices/iio:device0> echo 180000 > events/in_altvoltage0_rms_thresh_falling_value + # Enable RMS sag detection + root:/sys/bus/iio/devices/iio:device0> echo 1 > events/in_altvoltage0_rms_thresh_falling_en + + # Set RMS swell detection threshold + root:/sys/bus/iio/devices/iio:device0> echo 260000 > events/in_altvoltage0_rms_thresh_rising_value + # Enable RMS swell detection + root:/sys/bus/iio/devices/iio:device0> echo 1 > events/in_altvoltage0_rms_thresh_rising_en + +8. IIO Interfacing Tools +======================== + +See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO +interfacing tools. diff --git a/Documentation/iio/adxl345.rst b/Documentation/iio/adxl345.rst new file mode 100644 index 000000000000..afdb35f8b72e --- /dev/null +++ b/Documentation/iio/adxl345.rst @@ -0,0 +1,443 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=============== +ADXL345 driver +=============== + +This driver supports Analog Device's ADXL345/375 on SPI/I2C bus. + +1. Supported Devices +==================== + +* `ADXL345 <https://www.analog.com/ADXL345>`_ +* `ADXL375 <https://www.analog.com/ADXL375>`_ + +The ADXL345 is a generic purpose low power, 3-axis accelerometer with selectable +measurement ranges. The ADXL345 supports the ±2 g, ±4 g, ±8 g, and ±16 g ranges. + +2. Device Attributes +==================== + +Each IIO device, has a device folder under ``/sys/bus/iio/devices/iio:deviceX``, +where X is the IIO index of the device. Under these folders reside a set of +device files, depending on the characteristics and features of the hardware +device in questions. These files are consistently generalized and documented in +the IIO ABI documentation. + +The following table shows the ADXL345 related device files, found in the +specific device folder path ``/sys/bus/iio/devices/iio:deviceX``. + ++-------------------------------------------+----------------------------------------------------------+ +| 3-Axis Accelerometer related device files | Description | ++-------------------------------------------+----------------------------------------------------------+ +| in_accel_sampling_frequency | Currently selected sample rate. | ++-------------------------------------------+----------------------------------------------------------+ +| in_accel_sampling_frequency_available | Available sampling frequency configurations. | ++-------------------------------------------+----------------------------------------------------------+ +| in_accel_scale | Scale/range for the accelerometer channels. | ++-------------------------------------------+----------------------------------------------------------+ +| in_accel_scale_available | Available scale ranges for the accelerometer channel. | ++-------------------------------------------+----------------------------------------------------------+ +| in_accel_x_calibbias | Calibration offset for the X-axis accelerometer channel. | ++-------------------------------------------+----------------------------------------------------------+ +| in_accel_x_raw | Raw X-axis accelerometer channel value. | ++-------------------------------------------+----------------------------------------------------------+ +| in_accel_y_calibbias | y-axis acceleration offset correction | ++-------------------------------------------+----------------------------------------------------------+ +| in_accel_y_raw | Raw Y-axis accelerometer channel value. | ++-------------------------------------------+----------------------------------------------------------+ +| in_accel_z_calibbias | Calibration offset for the Z-axis accelerometer channel. | ++-------------------------------------------+----------------------------------------------------------+ +| in_accel_z_raw | Raw Z-axis accelerometer channel value. | ++-------------------------------------------+----------------------------------------------------------+ + +Channel Processed Values +------------------------- + +A channel value can be read from its _raw attribute. The value returned is the +raw value as reported by the devices. To get the processed value of the channel, +apply the following formula: + +.. code-block:: bash + + processed value = (_raw + _offset) * _scale + +Where _offset and _scale are device attributes. If no _offset attribute is +present, simply assume its value is 0. + ++-------------------------------------+---------------------------+ +| Channel type | Measurement unit | ++-------------------------------------+---------------------------+ +| Acceleration on X, Y, and Z axis | Meters per second squared | ++-------------------------------------+---------------------------+ + +Sensor Events +------------- + +Specific IIO events are triggered by their corresponding interrupts. The sensor +driver supports either none or a single active interrupt (INT) line, selectable +from the two available options: INT1 or INT2. The active INT line should be +specified in the device tree. If no INT line is configured, the sensor defaults +to FIFO bypass mode, where event detection is disabled and only X, Y, and Z axis +measurements are available. + +The table below lists the ADXL345-related device files located in the +device-specific path: ``/sys/bus/iio/devices/iio:deviceX/events``. +Note that activity and inactivity detection are DC-coupled by default; +therefore, only the AC-coupled activity and inactivity events are explicitly +listed. + ++---------------------------------------------+---------------------------------------------+ +| Event handle | Description | ++---------------------------------------------+---------------------------------------------+ +| in_accel_gesture_doubletap_en | Enable double tap detection on all axis | ++---------------------------------------------+---------------------------------------------+ +| in_accel_gesture_doubletap_reset_timeout | Double tap window in [us] | ++---------------------------------------------+---------------------------------------------+ +| in_accel_gesture_doubletap_tap2_min_delay | Double tap latent in [us] | ++---------------------------------------------+---------------------------------------------+ +| in_accel_gesture_singletap_timeout | Single tap duration in [us] | ++---------------------------------------------+---------------------------------------------+ +| in_accel_gesture_singletap_value | Single tap threshold value in 62.5/LSB | ++---------------------------------------------+---------------------------------------------+ +| in_accel_mag_falling_period | Inactivity time in seconds | ++---------------------------------------------+---------------------------------------------+ +| in_accel_mag_falling_value | Inactivity threshold value in 62.5/LSB | ++---------------------------------------------+---------------------------------------------+ +| in_accel_mag_adaptive_rising_en | Enable AC coupled activity on X axis | ++---------------------------------------------+---------------------------------------------+ +| in_accel_mag_adaptive_falling_period | AC coupled inactivity time in seconds | ++---------------------------------------------+---------------------------------------------+ +| in_accel_mag_adaptive_falling_value | AC coupled inactivity threshold in 62.5/LSB | ++---------------------------------------------+---------------------------------------------+ +| in_accel_mag_adaptive_rising_value | AC coupled activity threshold in 62.5/LSB | ++---------------------------------------------+---------------------------------------------+ +| in_accel_mag_rising_en | Enable activity detection on X axis | ++---------------------------------------------+---------------------------------------------+ +| in_accel_mag_rising_value | Activity threshold value in 62.5/LSB | ++---------------------------------------------+---------------------------------------------+ +| in_accel_x_gesture_singletap_en | Enable single tap detection on X axis | ++---------------------------------------------+---------------------------------------------+ +| in_accel_x&y&z_mag_falling_en | Enable inactivity detection on all axis | ++---------------------------------------------+---------------------------------------------+ +| in_accel_x&y&z_mag_adaptive_falling_en | Enable AC coupled inactivity on all axis | ++---------------------------------------------+---------------------------------------------+ +| in_accel_y_gesture_singletap_en | Enable single tap detection on Y axis | ++---------------------------------------------+---------------------------------------------+ +| in_accel_z_gesture_singletap_en | Enable single tap detection on Z axis | ++---------------------------------------------+---------------------------------------------+ + +Please refer to the sensor's datasheet for a detailed description of this +functionality. + +Manually setting the **ODR** will cause the driver to estimate default values +for inactivity detection timing, where higher ODR values correspond to longer +default wait times, and lower ODR values to shorter ones. If these defaults do +not meet your application’s needs, you can explicitly configure the inactivity +wait time. Setting this value to 0 will revert to the default behavior. + +When changing the **g range** configuration, the driver attempts to estimate +appropriate activity and inactivity thresholds by scaling the default values +based on the ratio of the previous range to the new one. The resulting threshold +will never be zero and will always fall between 1 and 255, corresponding to up +to 62.5 g/LSB as specified in the datasheet. However, you can override these +estimated thresholds by setting explicit values. + +When **activity** and **inactivity** events are enabled, the driver +automatically manages hysteresis behavior by setting the **link** and +**auto-sleep** bits. The link bit connects the activity and inactivity +functions, so that one follows the other. The auto-sleep function puts the +sensor into sleep mode when inactivity is detected, reducing power consumption +to the sub-12.5 Hz rate. + +The inactivity time is configurable between 1 and 255 seconds. In addition to +inactivity detection, the sensor also supports free-fall detection, which, from +the IIO perspective, is treated as a fall in magnitude across all axes. In +sensor terms, free-fall is defined using an inactivity period ranging from 0.000 +to 1.000 seconds. + +The driver behaves as follows: + +* If the configured inactivity period is 1 second or more, the driver uses the + sensor's inactivity register. This allows the event to be linked with + activity detection, use auto-sleep, and be either AC- or DC-coupled. + +* If the inactivity period is less than 1 second, the event is treated as plain + inactivity or free-fall detection. In this case, auto-sleep and coupling + (AC/DC) are not applied. + +* If an inactivity time of 0 seconds is configured, the driver selects a + heuristically determined default period (greater than 1 second) to optimize + power consumption. This also uses the inactivity register. + +Note: According to the datasheet, the optimal ODR for detecting activity, +or inactivity (or when operating with the free-fall register) should fall within +the range of 12.5 Hz to 400 Hz. The recommended free-fall threshold is between +300 mg and 600 mg (register values 0x05 to 0x09). + +In DC-coupled mode, the current acceleration magnitude is directly compared to +the values in the THRESH_ACT and THRESH_INACT registers to determine activity or +inactivity. In contrast, AC-coupled activity detection uses the acceleration +value at the start of detection as a reference point, and subsequent samples are +compared against this reference. While DC-coupling is the default mode-comparing +live values to fixed thresholds-AC-coupling relies on an internal filter +relative to the configured threshold. + +AC and DC coupling modes are configured separately for activity and inactivity +detection, but only one mode can be active at a time for each. For example, if +AC-coupled activity detection is enabled and then DC-coupled mode is set, only +DC-coupled activity detection will be active. In other words, only the most +recent configuration is applied. + +**Single tap** detection can be configured per the datasheet by setting the +threshold and duration parameters. When only single tap detection is enabled, +the single tap interrupt triggers as soon as the acceleration exceeds the +threshold (marking the start of the duration) and then falls below it, provided +the duration limit is not exceeded. If both single tap and double tap detections +are enabled, the single tap interrupt is triggered only after the double tap +event has been either confirmed or dismissed. + +To configure **double tap** detection, you must also set the window and latency +parameters in microseconds (µs). The latency period begins once the single tap +signal drops below the threshold and acts as a waiting time during which any +spikes are ignored for double tap detection. After the latency period ends, the +detection window starts. If the acceleration rises above the threshold and then +falls below it again within this window, a double tap event is triggered upon +the fall below the threshold. + +Double tap event detection is thoroughly explained in the datasheet. After a +single tap event is detected, a double tap event may follow, provided the signal +meets certain criteria. However, double tap detection can be invalidated for +three reasons: + +* If the **suppress bit** is set, any acceleration spike above the tap + threshold during the tap latency period immediately invalidates the double tap + detection. In other words, no spikes are allowed during latency when the + suppress bit is active. + +* The double tap event is invalid if the acceleration is above the threshold at + the start of the double tap window. + +* Double tap detection is also invalidated if the acceleration duration exceeds + the limit set by the duration register. + +For double tap detection, the same duration applies as for single tap: the +acceleration must rise above the threshold and then fall below it within the +specified duration. Note that the suppress bit is typically enabled when double +tap detection is active. + +Usage Examples +-------------- + +Show device name: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat name + adxl345 + +Show accelerometer channels value: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat in_accel_x_raw + -1 + root:/sys/bus/iio/devices/iio:device0> cat in_accel_y_raw + 2 + root:/sys/bus/iio/devices/iio:device0> cat in_accel_z_raw + -253 + +Set calibration offset for accelerometer channels: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat in_accel_x_calibbias + 0 + + root:/sys/bus/iio/devices/iio:device0> echo 50 > in_accel_x_calibbias + root:/sys/bus/iio/devices/iio:device0> cat in_accel_x_calibbias + 50 + +Given the 13-bit full resolution, the available ranges are calculated by the +following formula: + +.. code-block:: bash + + (g * 2 * 9.80665) / (2^(resolution) - 1) * 100; for g := 2|4|8|16 + +Scale range configuration: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_scale + 0.478899 + root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_scale_available + 0.478899 0.957798 1.915595 3.831190 + + root:/sys/bus/iio/devices/iio:device0> echo 1.915595 > ./in_accel_scale + root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_scale + 1.915595 + +Set output data rate (ODR): + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_sampling_frequency + 200.000000 + + root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_sampling_frequency_available + 0.097000 0.195000 0.390000 0.781000 1.562000 3.125000 6.250000 12.500000 25.000000 50.000000 100.000000 200.000000 400.000000 800.000000 1600.000000 3200.000000 + + root:/sys/bus/iio/devices/iio:device0> echo 1.562000 > ./in_accel_sampling_frequency + root:/sys/bus/iio/devices/iio:device0> cat ./in_accel_sampling_frequency + 1.562000 + +Configure one or several events: + +.. code-block:: bash + + root:> cd /sys/bus/iio/devices/iio:device0 + + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./buffer0/in_accel_x_en + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./buffer0/in_accel_y_en + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./buffer0/in_accel_z_en + + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./scan_elements/in_accel_x_en + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./scan_elements/in_accel_y_en + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./scan_elements/in_accel_z_en + + root:/sys/bus/iio/devices/iio:device0> echo 14 > ./in_accel_x_calibbias + root:/sys/bus/iio/devices/iio:device0> echo 2 > ./in_accel_y_calibbias + root:/sys/bus/iio/devices/iio:device0> echo -250 > ./in_accel_z_calibbias + + root:/sys/bus/iio/devices/iio:device0> echo 24 > ./buffer0/length + + ## AC coupled activity, threshold [62.5/LSB] + root:/sys/bus/iio/devices/iio:device0> echo 6 > ./events/in_accel_mag_adaptive_rising_value + + ## AC coupled inactivity, threshold, [62.5/LSB] + root:/sys/bus/iio/devices/iio:device0> echo 4 > ./events/in_accel_mag_adaptive_falling_value + + ## AC coupled inactivity, time [s] + root:/sys/bus/iio/devices/iio:device0> echo 3 > ./events/in_accel_mag_adaptive_falling_period + + ## singletap, threshold + root:/sys/bus/iio/devices/iio:device0> echo 35 > ./events/in_accel_gesture_singletap_value + + ## singletap, duration [us] + root:/sys/bus/iio/devices/iio:device0> echo 0.001875 > ./events/in_accel_gesture_singletap_timeout + + ## doubletap, window [us] + root:/sys/bus/iio/devices/iio:device0> echo 0.025 > ./events/in_accel_gesture_doubletap_reset_timeout + + ## doubletap, latent [us] + root:/sys/bus/iio/devices/iio:device0> echo 0.025 > ./events/in_accel_gesture_doubletap_tap2_min_delay + + ## AC coupled activity, enable + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_mag_adaptive_rising_en + + ## AC coupled inactivity, enable + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_x\&y\&z_mag_adaptive_falling_en + + ## singletap, enable + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_x_gesture_singletap_en + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_y_gesture_singletap_en + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_z_gesture_singletap_en + + ## doubletap, enable + root:/sys/bus/iio/devices/iio:device0> echo 1 > ./events/in_accel_gesture_doubletap_en + +Verify incoming events: + +.. code-block:: bash + + root:# iio_event_monitor adxl345 + Found IIO device with name adxl345 with device number 0 + Event: time: 1739063415957073383, type: accel(z), channel: 0, evtype: mag, direction: rising + Event: time: 1739063415963770218, type: accel(z), channel: 0, evtype: mag, direction: rising + Event: time: 1739063416002563061, type: accel(z), channel: 0, evtype: gesture, direction: singletap + Event: time: 1739063426271128739, type: accel(x&y&z), channel: 0, evtype: mag, direction: falling + Event: time: 1739063436539080713, type: accel(x&y&z), channel: 0, evtype: mag, direction: falling + Event: time: 1739063438357970381, type: accel(z), channel: 0, evtype: mag, direction: rising + Event: time: 1739063446726161586, type: accel(z), channel: 0, evtype: mag, direction: rising + Event: time: 1739063446727892670, type: accel(z), channel: 0, evtype: mag, direction: rising + Event: time: 1739063446743019768, type: accel(z), channel: 0, evtype: mag, direction: rising + Event: time: 1739063446744650696, type: accel(z), channel: 0, evtype: mag, direction: rising + Event: time: 1739063446763559386, type: accel(z), channel: 0, evtype: gesture, direction: singletap + Event: time: 1739063448818126480, type: accel(x&y&z), channel: 0, evtype: mag, direction: falling + ... + +Activity and inactivity belong together and indicate state changes as follows + +.. code-block:: bash + + root:# iio_event_monitor adxl345 + Found IIO device with name adxl345 with device number 0 + Event: time: 1744648001133946293, type: accel(x), channel: 0, evtype: mag, direction: rising + <after inactivity time elapsed> + Event: time: 1744648057724775499, type: accel(x&y&z), channel: 0, evtype: mag, direction: falling + ... + +3. Device Buffers +================= + +This driver supports IIO buffers. + +All devices support retrieving the raw acceleration and temperature measurements +using buffers. + +Usage examples +-------------- + +Select channels for buffer read: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_accel_x_en + root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_accel_y_en + root:/sys/bus/iio/devices/iio:device0> echo 1 > scan_elements/in_accel_z_en + +Set the number of samples to be stored in the buffer: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> echo 10 > buffer/length + +Enable buffer readings: + +.. code-block:: bash + + root:/sys/bus/iio/devices/iio:device0> echo 1 > buffer/enable + +Obtain buffered data: + +.. code-block:: bash + + root:> iio_readdev -b 16 -s 1024 adxl345 | hexdump -d + WARNING: High-speed mode not enabled + 0000000 00003 00012 00013 00005 00010 00011 00005 00011 + 0000010 00013 00004 00012 00011 00003 00012 00014 00007 + 0000020 00011 00013 00004 00013 00014 00003 00012 00013 + 0000030 00004 00012 00013 00005 00011 00011 00005 00012 + 0000040 00014 00005 00012 00014 00004 00010 00012 00004 + 0000050 00013 00011 00003 00011 00012 00005 00011 00013 + 0000060 00003 00012 00012 00003 00012 00012 00004 00012 + 0000070 00012 00003 00013 00013 00003 00013 00012 00005 + 0000080 00012 00013 00003 00011 00012 00005 00012 00013 + 0000090 00003 00013 00011 00005 00013 00014 00003 00012 + 00000a0 00012 00003 00012 00013 00004 00012 00015 00004 + 00000b0 00014 00011 00003 00014 00013 00004 00012 00011 + 00000c0 00004 00012 00013 00004 00014 00011 00004 00013 + 00000d0 00012 00002 00014 00012 00005 00012 00013 00005 + 00000e0 00013 00013 00003 00013 00013 00005 00012 00013 + 00000f0 00004 00014 00015 00005 00012 00011 00005 00012 + ... + +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 +interfacing tools. diff --git a/Documentation/iio/bno055.rst b/Documentation/iio/bno055.rst index f1111ff3fe2e..c6042586b2ae 100644 --- a/Documentation/iio/bno055.rst +++ b/Documentation/iio/bno055.rst @@ -9,11 +9,11 @@ BNO055 driver This driver supports Bosch BNO055 IMUs (on both serial and I2C busses). -Accelerometer, magnetometer and gyroscope measures are always provided. +Accelerometer, magnetometer and gyroscope measurements are always available. When "fusion_enable" sysfs attribute is set to 1, orientation (both Euler angles and quaternion), linear velocity and gravity vector are also provided, but some sensor settings (e.g. low pass filtering and range) -became locked (the IMU firmware controls them). +become locked (the IMU firmware controls them). This driver supports also IIO buffers. @@ -24,14 +24,14 @@ The IMU continuously performs an autocalibration procedure if (and only if) operating in fusion mode. The magnetometer autocalibration can however be disabled by writing 0 in the sysfs in_magn_calibration_fast_enable attribute. -The driver provides access to autocalibration flags (i.e. you can known if -the IMU has successfully autocalibrated) and to the calibration data blob. +The driver provides access to autocalibration flags (i.e. you can determine +if the IMU has successfully autocalibrated) and to the calibration data blob. The user can save this blob in a firmware file (i.e. in /lib/firmware) that the driver looks for at probe time. If found, then the IMU is initialized with this calibration data. This saves the user from performing the -calibration procedure every time (which consist of moving the IMU in -various way). +calibration procedure every time (which consists of moving the IMU in +various ways). The driver looks for calibration data file using two different names: first a file whose name is suffixed with the IMU unique ID (exposed in sysfs as diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index c106402a91f7..315ae37d6fd4 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -28,11 +28,13 @@ Industrial I/O Kernel Drivers ad7606 ad7625 ad7944 + ade9000 adis16475 adis16480 adis16550 adxl313 adxl380 + adxl345 bno055 ep93xx_adc opt4060 diff --git a/Documentation/netlink/specs/binder.yaml b/Documentation/netlink/specs/binder.yaml new file mode 100644 index 000000000000..0f0575ad1265 --- /dev/null +++ b/Documentation/netlink/specs/binder.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +# +# Copyright 2025 Google LLC +# +--- +name: binder +protocol: genetlink +uapi-header: linux/android/binder_netlink.h +doc: Binder interface over generic netlink + +attribute-sets: + - + name: report + doc: | + Attributes included within a transaction failure report. The elements + correspond directly with the specific transaction that failed, along + with the error returned to the sender e.g. BR_DEAD_REPLY. + + attributes: + - + name: error + type: u32 + doc: The enum binder_driver_return_protocol returned to the sender. + - + name: context + type: string + doc: The binder context where the transaction occurred. + - + name: from-pid + type: u32 + doc: The PID of the sender process. + - + name: from-tid + type: u32 + doc: The TID of the sender thread. + - + name: to-pid + type: u32 + doc: | + The PID of the recipient process. This attribute may not be present + if the target could not be determined. + - + name: to-tid + type: u32 + doc: | + The TID of the recipient thread. This attribute may not be present + if the target could not be determined. + - + name: is-reply + type: flag + doc: When present, indicates the failed transaction is a reply. + - + name: flags + type: u32 + doc: The bitmask of enum transaction_flags from the transaction. + - + name: code + type: u32 + doc: The application-defined code from the transaction. + - + name: data-size + type: u32 + doc: The transaction payload size in bytes. + +operations: + list: + - + name: report + doc: | + A multicast event sent to userspace subscribers to notify them about + binder transaction failures. The generated report provides the full + details of the specific transaction that failed. The intention is for + programs to monitor these events and react to the failures as needed. + + attribute-set: report + mcgrp: report + event: + attributes: + - error + - context + - from-pid + - from-tid + - to-pid + - to-tid + - is-reply + - flags + - code + - data-size + +mcast-groups: + list: + - + name: report diff --git a/MAINTAINERS b/MAINTAINERS index f922ba266c81..5a2cde903910 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -458,6 +458,11 @@ F: Documentation/devicetree/bindings/iio/adc/adi,ad7380.yaml F: Documentation/iio/ad7380.rst F: drivers/iio/adc/ad7380.c +AD7476 ADC DRIVER FOR VARIOUS SIMPLE 1-CHANNEL SPI ADCs +M: Matti Vaittinen <mazziesaccount@gmail.com> +S: Maintained +F: drivers/iio/adc/ad7476.c + AD7877 TOUCHSCREEN DRIVER M: Michael Hennerich <michael.hennerich@analog.com> S: Supported @@ -1810,6 +1815,7 @@ M: Suren Baghdasaryan <surenb@google.com> L: linux-kernel@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git +F: Documentation/netlink/specs/binder.yaml F: drivers/android/ ANDROID GOLDFISH PIC DRIVER @@ -12204,6 +12210,14 @@ S: Maintained F: Documentation/devicetree/bindings/sound/infineon,peb2466.yaml F: sound/soc/codecs/peb2466.c +INFINEON TLV493D Driver +M: Dixit Parmar <dixitparmar19@gmail.com> +L: linux-iio@vger.kernel.org +S: Maintained +W: https://www.infineon.com/part/TLV493D-A1B6 +F: Documentation/devicetree/bindings/iio/magnetometer/infineon,tlv493d-a1b6.yaml +F: drivers/iio/magnetometer/tlv493d.c + INFINIBAND SUBSYSTEM M: Jason Gunthorpe <jgg@nvidia.com> M: Leon Romanovsky <leonro@nvidia.com> @@ -14925,6 +14939,11 @@ F: drivers/regulator/88pm886-regulator.c F: drivers/rtc/rtc-88pm886.c F: include/linux/mfd/88pm886.h +MARVELL 88PM886 PMIC GPADC DRIVER +M: Duje Mihanović <duje@dujemihanovic.xyz> +S: Maintained +F: drivers/iio/adc/88pm886-gpadc.c + MARVELL ARMADA 3700 PHY DRIVERS M: Miquel Raynal <miquel.raynal@bootlin.com> S: Maintained @@ -15232,9 +15251,9 @@ F: Documentation/devicetree/bindings/regulator/maxim,max20086.yaml F: drivers/regulator/max20086-regulator.c MAXIM MAX30208 TEMPERATURE SENSOR DRIVER -M: Rajat Khandelwal <rajat.khandelwal@linux.intel.com> +M: Marcelo Schmitt <marcelo.schmitt@analog.com> L: linux-iio@vger.kernel.org -S: Maintained +S: Supported F: drivers/iio/temperature/max30208.c MAXIM MAX7360 KEYPAD LED MFD DRIVER @@ -22251,9 +22270,10 @@ S: Supported F: drivers/power/supply/bd99954-charger.c F: drivers/power/supply/bd99954-charger.h -ROHM BD79124 ADC / GPO IC +ROHM BD791xx ADC / GPO IC M: Matti Vaittinen <mazziesaccount@gmail.com> S: Supported +F: drivers/iio/adc/rohm-bd79112.c F: drivers/iio/adc/rohm-bd79124.c ROHM BH1745 COLOUR SENSOR @@ -27241,6 +27261,12 @@ S: Maintained F: Documentation/devicetree/bindings/iio/light/vishay,veml6030.yaml F: drivers/iio/light/veml6030.c +VISHAY VEML6046X00 RGBIR COLOR SENSOR DRIVER +M: Andreas Klinger <ak@it-klinger.de> +S: Maintained +F: Documentation/devicetree/bindings/iio/light/vishay,veml6046x00.yaml +F: drivers/iio/light/veml6046x00.c + VISHAY VEML6075 UVA AND UVB LIGHT SENSOR DRIVER M: Javier Carrasco <javier.carrasco.cruz@gmail.com> S: Maintained @@ -27972,7 +27998,8 @@ F: include/uapi/linux/dqblk_xfs.h F: include/uapi/linux/fsmap.h XILINX AMS DRIVER -M: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com> +M: Salih Erim <salih.erim@amd.com> +M: Conall O'Griofa <conall.ogriofa@amd.com> L: linux-iio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/iio/adc/xlnx,zynqmp-ams.yaml diff --git a/arch/sparc/kernel/apc.c b/arch/sparc/kernel/apc.c index d44725d37e30..849db20e7165 100644 --- a/arch/sparc/kernel/apc.c +++ b/arch/sparc/kernel/apc.c @@ -28,7 +28,6 @@ * #define APC_DEBUG_LED */ -#define APC_MINOR MISC_DYNAMIC_MINOR #define APC_OBPNAME "power-management" #define APC_DEVNAME "apc" @@ -138,7 +137,7 @@ static const struct file_operations apc_fops = { .llseek = noop_llseek, }; -static struct miscdevice apc_miscdev = { APC_MINOR, APC_DEVNAME, &apc_fops }; +static struct miscdevice apc_miscdev = { MISC_DYNAMIC_MINOR, APC_DEVNAME, &apc_fops }; static int apc_probe(struct platform_device *op) { diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig index 5b3b8041f827..e2e402c9d175 100644 --- a/drivers/android/Kconfig +++ b/drivers/android/Kconfig @@ -4,6 +4,7 @@ menu "Android" config ANDROID_BINDER_IPC bool "Android Binder IPC Driver" depends on MMU + depends on NET default n help Binder is used in Android for both communication between processes, @@ -13,6 +14,19 @@ config ANDROID_BINDER_IPC Android process, using Binder to identify, invoke and pass arguments between said processes. +config ANDROID_BINDER_IPC_RUST + bool "Rust version of Android Binder IPC Driver" + depends on RUST && MMU && !ANDROID_BINDER_IPC + help + This enables the Rust implementation of the Binder driver. + + Binder is used in Android for both communication between processes, + and remote method invocation. + + This means one Android process can call a method/routine in another + Android process, using Binder to identify, invoke and pass arguments + between said processes. + config ANDROID_BINDERFS bool "Android Binderfs filesystem" depends on ANDROID_BINDER_IPC @@ -27,7 +41,7 @@ config ANDROID_BINDERFS config ANDROID_BINDER_DEVICES string "Android Binder devices" - depends on ANDROID_BINDER_IPC + depends on ANDROID_BINDER_IPC || ANDROID_BINDER_IPC_RUST default "binder,hwbinder,vndbinder" help Default value for the binder.devices parameter. diff --git a/drivers/android/Makefile b/drivers/android/Makefile index c5d47be0276c..e0c650d3898e 100644 --- a/drivers/android/Makefile +++ b/drivers/android/Makefile @@ -2,5 +2,6 @@ ccflags-y += -I$(src) # needed for trace events obj-$(CONFIG_ANDROID_BINDERFS) += binderfs.o -obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o binder_alloc.o +obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o binder_alloc.o binder_netlink.o obj-$(CONFIG_ANDROID_BINDER_ALLOC_KUNIT_TEST) += tests/ +obj-$(CONFIG_ANDROID_BINDER_IPC_RUST) += binder/ diff --git a/drivers/android/binder.c b/drivers/android/binder.c index 312b462e349d..8c99ceaa303b 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -74,6 +74,7 @@ #include <linux/cacheflush.h> +#include "binder_netlink.h" #include "binder_internal.h" #include "binder_trace.h" @@ -2993,6 +2994,69 @@ static void binder_set_txn_from_error(struct binder_transaction *t, int id, binder_thread_dec_tmpref(from); } +/** + * binder_netlink_report() - report a transaction failure via netlink + * @proc: the binder proc sending the transaction + * @t: the binder transaction that failed + * @data_size: the user provided data size for the transaction + * @error: enum binder_driver_return_protocol returned to sender + */ +static void binder_netlink_report(struct binder_proc *proc, + struct binder_transaction *t, + u32 data_size, + u32 error) +{ + const char *context = proc->context->name; + struct sk_buff *skb; + void *hdr; + + if (!genl_has_listeners(&binder_nl_family, &init_net, + BINDER_NLGRP_REPORT)) + return; + + trace_binder_netlink_report(context, t, data_size, error); + + skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!skb) + return; + + hdr = genlmsg_put(skb, 0, 0, &binder_nl_family, 0, BINDER_CMD_REPORT); + if (!hdr) + goto free_skb; + + if (nla_put_u32(skb, BINDER_A_REPORT_ERROR, error) || + nla_put_string(skb, BINDER_A_REPORT_CONTEXT, context) || + nla_put_u32(skb, BINDER_A_REPORT_FROM_PID, t->from_pid) || + nla_put_u32(skb, BINDER_A_REPORT_FROM_TID, t->from_tid)) + goto cancel_skb; + + if (t->to_proc && + nla_put_u32(skb, BINDER_A_REPORT_TO_PID, t->to_proc->pid)) + goto cancel_skb; + + if (t->to_thread && + nla_put_u32(skb, BINDER_A_REPORT_TO_TID, t->to_thread->pid)) + goto cancel_skb; + + if (t->is_reply && nla_put_flag(skb, BINDER_A_REPORT_IS_REPLY)) + goto cancel_skb; + + if (nla_put_u32(skb, BINDER_A_REPORT_FLAGS, t->flags) || + nla_put_u32(skb, BINDER_A_REPORT_CODE, t->code) || + nla_put_u32(skb, BINDER_A_REPORT_DATA_SIZE, data_size)) + goto cancel_skb; + + genlmsg_end(skb, hdr); + genlmsg_multicast(&binder_nl_family, skb, 0, BINDER_NLGRP_REPORT, + GFP_KERNEL); + return; + +cancel_skb: + genlmsg_cancel(skb, hdr); +free_skb: + nlmsg_free(skb); +} + static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply, @@ -3042,6 +3106,32 @@ static void binder_transaction(struct binder_proc *proc, binder_set_extended_error(&thread->ee, t_debug_id, BR_OK, 0); binder_inner_proc_unlock(proc); + t = kzalloc(sizeof(*t), GFP_KERNEL); + if (!t) { + binder_txn_error("%d:%d cannot allocate transaction\n", + thread->pid, proc->pid); + return_error = BR_FAILED_REPLY; + return_error_param = -ENOMEM; + return_error_line = __LINE__; + goto err_alloc_t_failed; + } + INIT_LIST_HEAD(&t->fd_fixups); + binder_stats_created(BINDER_STAT_TRANSACTION); + spin_lock_init(&t->lock); + t->debug_id = t_debug_id; + t->start_time = t_start_time; + t->from_pid = proc->pid; + t->from_tid = thread->pid; + t->sender_euid = task_euid(proc->tsk); + t->code = tr->code; + t->flags = tr->flags; + t->priority = task_nice(current); + t->work.type = BINDER_WORK_TRANSACTION; + t->is_async = !reply && (tr->flags & TF_ONE_WAY); + t->is_reply = reply; + if (!reply && !(tr->flags & TF_ONE_WAY)) + t->from = thread; + if (reply) { binder_inner_proc_lock(proc); in_reply_to = thread->transaction_stack; @@ -3228,24 +3318,13 @@ static void binder_transaction(struct binder_proc *proc, } binder_inner_proc_unlock(proc); } + + t->to_proc = target_proc; + t->to_thread = target_thread; if (target_thread) e->to_thread = target_thread->pid; e->to_proc = target_proc->pid; - /* TODO: reuse incoming transaction for reply */ - t = kzalloc(sizeof(*t), GFP_KERNEL); - if (t == NULL) { - binder_txn_error("%d:%d cannot allocate transaction\n", - thread->pid, proc->pid); - return_error = BR_FAILED_REPLY; - return_error_param = -ENOMEM; - return_error_line = __LINE__; - goto err_alloc_t_failed; - } - INIT_LIST_HEAD(&t->fd_fixups); - binder_stats_created(BINDER_STAT_TRANSACTION); - spin_lock_init(&t->lock); - tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); if (tcomplete == NULL) { binder_txn_error("%d:%d cannot allocate work for transaction\n", @@ -3257,9 +3336,6 @@ static void binder_transaction(struct binder_proc *proc, } binder_stats_created(BINDER_STAT_TRANSACTION_COMPLETE); - t->debug_id = t_debug_id; - t->start_time = t_start_time; - if (reply) binder_debug(BINDER_DEBUG_TRANSACTION, "%d:%d BC_REPLY %d -> %d:%d, data size %lld-%lld-%lld\n", @@ -3275,19 +3351,6 @@ static void binder_transaction(struct binder_proc *proc, (u64)tr->data_size, (u64)tr->offsets_size, (u64)extra_buffers_size); - if (!reply && !(tr->flags & TF_ONE_WAY)) - t->from = thread; - else - t->from = NULL; - t->from_pid = proc->pid; - t->from_tid = thread->pid; - t->sender_euid = task_euid(proc->tsk); - t->to_proc = target_proc; - t->to_thread = target_thread; - t->code = tr->code; - t->flags = tr->flags; - t->priority = task_nice(current); - if (target_node && target_node->txn_security_ctx) { u32 secid; size_t added_size; @@ -3680,11 +3743,13 @@ static void binder_transaction(struct binder_proc *proc, return_error_line = __LINE__; goto err_copy_data_failed; } - if (t->buffer->oneway_spam_suspect) + if (t->buffer->oneway_spam_suspect) { tcomplete->type = BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT; - else + binder_netlink_report(proc, t, tr->data_size, + BR_ONEWAY_SPAM_SUSPECT); + } else { tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; - t->work.type = BINDER_WORK_TRANSACTION; + } if (reply) { binder_enqueue_thread_work(thread, tcomplete); @@ -3712,7 +3777,6 @@ static void binder_transaction(struct binder_proc *proc, * the target replies (or there is an error). */ binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete); - t->need_reply = 1; t->from_parent = thread->transaction_stack; thread->transaction_stack = t; binder_inner_proc_unlock(proc); @@ -3733,8 +3797,11 @@ static void binder_transaction(struct binder_proc *proc, * process and is put in a pending queue, waiting for the target * process to be unfrozen. */ - if (return_error == BR_TRANSACTION_PENDING_FROZEN) + if (return_error == BR_TRANSACTION_PENDING_FROZEN) { tcomplete->type = BINDER_WORK_TRANSACTION_PENDING; + binder_netlink_report(proc, t, tr->data_size, + return_error); + } binder_enqueue_thread_work(thread, tcomplete); if (return_error && return_error != BR_TRANSACTION_PENDING_FROZEN) @@ -3783,9 +3850,6 @@ err_get_secctx_failed: err_alloc_tcomplete_failed: if (trace_binder_txn_latency_free_enabled()) binder_txn_latency_free(t); - kfree(t); - binder_stats_deleted(BINDER_STAT_TRANSACTION); -err_alloc_t_failed: err_bad_todo_list: err_bad_call_stack: err_empty_call_stack: @@ -3796,6 +3860,11 @@ err_invalid_target_handle: binder_dec_node_tmpref(target_node); } + binder_netlink_report(proc, t, tr->data_size, return_error); + kfree(t); + binder_stats_deleted(BINDER_STAT_TRANSACTION); +err_alloc_t_failed: + binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, "%d:%d transaction %s to %d:%d failed %d/%d/%d, code %u size %lld-%lld line %d\n", proc->pid, thread->pid, reply ? "reply" : @@ -6324,13 +6393,13 @@ static void print_binder_transaction_ilocked(struct seq_file *m, spin_lock(&t->lock); to_proc = t->to_proc; seq_printf(m, - "%s %d: %pK from %d:%d to %d:%d code %x flags %x pri %ld r%d elapsed %lldms", + "%s %d: %pK from %d:%d to %d:%d code %x flags %x pri %ld a%d r%d elapsed %lldms", prefix, t->debug_id, t, t->from_pid, t->from_tid, to_proc ? to_proc->pid : 0, t->to_thread ? t->to_thread->pid : 0, - t->code, t->flags, t->priority, t->need_reply, + t->code, t->flags, t->priority, t->is_async, t->is_reply, ktime_ms_delta(current_time, t->start_time)); spin_unlock(&t->lock); @@ -7062,12 +7131,19 @@ static int __init binder_init(void) } } - ret = init_binderfs(); + ret = genl_register_family(&binder_nl_family); if (ret) goto err_init_binder_device_failed; + ret = init_binderfs(); + if (ret) + goto err_init_binderfs_failed; + return ret; +err_init_binderfs_failed: + genl_unregister_family(&binder_nl_family); + err_init_binder_device_failed: hlist_for_each_entry_safe(device, tmp, &binder_devices, hlist) { misc_deregister(&device->miscdev); @@ -7088,5 +7164,3 @@ device_initcall(binder_init); #define CREATE_TRACE_POINTS #include "binder_trace.h" - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/android/binder/Makefile b/drivers/android/binder/Makefile new file mode 100644 index 000000000000..09eabb527fa0 --- /dev/null +++ b/drivers/android/binder/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +ccflags-y += -I$(src) # needed for trace events + +obj-$(CONFIG_ANDROID_BINDER_IPC_RUST) += rust_binder.o +rust_binder-y := \ + rust_binder_main.o \ + rust_binderfs.o \ + rust_binder_events.o \ + page_range_helper.o diff --git a/drivers/android/binder/allocation.rs b/drivers/android/binder/allocation.rs new file mode 100644 index 000000000000..7f65a9c3a0e5 --- /dev/null +++ b/drivers/android/binder/allocation.rs @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use core::mem::{size_of, size_of_val, MaybeUninit}; +use core::ops::Range; + +use kernel::{ + bindings, + fs::file::{File, FileDescriptorReservation}, + prelude::*, + sync::{aref::ARef, Arc}, + transmute::{AsBytes, FromBytes}, + uaccess::UserSliceReader, + uapi, +}; + +use crate::{ + deferred_close::DeferredFdCloser, + defs::*, + node::{Node, NodeRef}, + process::Process, + DArc, +}; + +#[derive(Default)] +pub(crate) struct AllocationInfo { + /// Range within the allocation where we can find the offsets to the object descriptors. + pub(crate) offsets: Option<Range<usize>>, + /// The target node of the transaction this allocation is associated to. + /// Not set for replies. + pub(crate) target_node: Option<NodeRef>, + /// When this allocation is dropped, call `pending_oneway_finished` on the node. + /// + /// This is used to serialize oneway transaction on the same node. Binder guarantees that + /// oneway transactions to the same node are delivered sequentially in the order they are sent. + pub(crate) oneway_node: Option<DArc<Node>>, + /// Zero the data in the buffer on free. + pub(crate) clear_on_free: bool, + /// List of files embedded in this transaction. + file_list: FileList, +} + +/// Represents an allocation that the kernel is currently using. +/// +/// When allocations are idle, the range allocator holds the data related to them. +/// +/// # Invariants +/// +/// This allocation corresponds to an allocation in the range allocator, so the relevant pages are +/// marked in use in the page range. +pub(crate) struct Allocation { + pub(crate) offset: usize, + size: usize, + pub(crate) ptr: usize, + pub(crate) process: Arc<Process>, + allocation_info: Option<AllocationInfo>, + free_on_drop: bool, + pub(crate) oneway_spam_detected: bool, + #[allow(dead_code)] + pub(crate) debug_id: usize, +} + +impl Allocation { + pub(crate) fn new( + process: Arc<Process>, + debug_id: usize, + offset: usize, + size: usize, + ptr: usize, + oneway_spam_detected: bool, + ) -> Self { + Self { + process, + offset, + size, + ptr, + debug_id, + oneway_spam_detected, + allocation_info: None, + free_on_drop: true, + } + } + + fn size_check(&self, offset: usize, size: usize) -> Result { + let overflow_fail = offset.checked_add(size).is_none(); + let cmp_size_fail = offset.wrapping_add(size) > self.size; + if overflow_fail || cmp_size_fail { + return Err(EFAULT); + } + Ok(()) + } + + pub(crate) fn copy_into( + &self, + reader: &mut UserSliceReader, + offset: usize, + size: usize, + ) -> Result { + self.size_check(offset, size)?; + + // SAFETY: While this object exists, the range allocator will keep the range allocated, and + // in turn, the pages will be marked as in use. + unsafe { + self.process + .pages + .copy_from_user_slice(reader, self.offset + offset, size) + } + } + + pub(crate) fn read<T: FromBytes>(&self, offset: usize) -> Result<T> { + self.size_check(offset, size_of::<T>())?; + + // SAFETY: While this object exists, the range allocator will keep the range allocated, and + // in turn, the pages will be marked as in use. + unsafe { self.process.pages.read(self.offset + offset) } + } + + pub(crate) fn write<T: ?Sized>(&self, offset: usize, obj: &T) -> Result { + self.size_check(offset, size_of_val::<T>(obj))?; + + // SAFETY: While this object exists, the range allocator will keep the range allocated, and + // in turn, the pages will be marked as in use. + unsafe { self.process.pages.write(self.offset + offset, obj) } + } + + pub(crate) fn fill_zero(&self) -> Result { + // SAFETY: While this object exists, the range allocator will keep the range allocated, and + // in turn, the pages will be marked as in use. + unsafe { self.process.pages.fill_zero(self.offset, self.size) } + } + + pub(crate) fn keep_alive(mut self) { + self.process + .buffer_make_freeable(self.offset, self.allocation_info.take()); + self.free_on_drop = false; + } + + pub(crate) fn set_info(&mut self, info: AllocationInfo) { + self.allocation_info = Some(info); + } + + pub(crate) fn get_or_init_info(&mut self) -> &mut AllocationInfo { + self.allocation_info.get_or_insert_with(Default::default) + } + + pub(crate) fn set_info_offsets(&mut self, offsets: Range<usize>) { + self.get_or_init_info().offsets = Some(offsets); + } + + pub(crate) fn set_info_oneway_node(&mut self, oneway_node: DArc<Node>) { + self.get_or_init_info().oneway_node = Some(oneway_node); + } + + pub(crate) fn set_info_clear_on_drop(&mut self) { + self.get_or_init_info().clear_on_free = true; + } + + pub(crate) fn set_info_target_node(&mut self, target_node: NodeRef) { + self.get_or_init_info().target_node = Some(target_node); + } + + /// Reserve enough space to push at least `num_fds` fds. + pub(crate) fn info_add_fd_reserve(&mut self, num_fds: usize) -> Result { + self.get_or_init_info() + .file_list + .files_to_translate + .reserve(num_fds, GFP_KERNEL)?; + + Ok(()) + } + + pub(crate) fn info_add_fd( + &mut self, + file: ARef<File>, + buffer_offset: usize, + close_on_free: bool, + ) -> Result { + self.get_or_init_info().file_list.files_to_translate.push( + FileEntry { + file, + buffer_offset, + close_on_free, + }, + GFP_KERNEL, + )?; + + Ok(()) + } + + pub(crate) fn set_info_close_on_free(&mut self, cof: FdsCloseOnFree) { + self.get_or_init_info().file_list.close_on_free = cof.0; + } + + pub(crate) fn translate_fds(&mut self) -> Result<TranslatedFds> { + let file_list = match self.allocation_info.as_mut() { + Some(info) => &mut info.file_list, + None => return Ok(TranslatedFds::new()), + }; + + let files = core::mem::take(&mut file_list.files_to_translate); + + let num_close_on_free = files.iter().filter(|entry| entry.close_on_free).count(); + let mut close_on_free = KVec::with_capacity(num_close_on_free, GFP_KERNEL)?; + + let mut reservations = KVec::with_capacity(files.len(), GFP_KERNEL)?; + for file_info in files { + let res = FileDescriptorReservation::get_unused_fd_flags(bindings::O_CLOEXEC)?; + let fd = res.reserved_fd(); + self.write::<u32>(file_info.buffer_offset, &fd)?; + + reservations.push( + Reservation { + res, + file: file_info.file, + }, + GFP_KERNEL, + )?; + if file_info.close_on_free { + close_on_free.push(fd, GFP_KERNEL)?; + } + } + + Ok(TranslatedFds { + reservations, + close_on_free: FdsCloseOnFree(close_on_free), + }) + } + + /// Should the looper return to userspace when freeing this allocation? + pub(crate) fn looper_need_return_on_free(&self) -> bool { + // Closing fds involves pushing task_work for execution when we return to userspace. Hence, + // we should return to userspace asap if we are closing fds. + match self.allocation_info { + Some(ref info) => !info.file_list.close_on_free.is_empty(), + None => false, + } + } +} + +impl Drop for Allocation { + fn drop(&mut self) { + if !self.free_on_drop { + return; + } + + if let Some(mut info) = self.allocation_info.take() { + if let Some(oneway_node) = info.oneway_node.as_ref() { + oneway_node.pending_oneway_finished(); + } + + info.target_node = None; + + if let Some(offsets) = info.offsets.clone() { + let view = AllocationView::new(self, offsets.start); + for i in offsets.step_by(size_of::<usize>()) { + if view.cleanup_object(i).is_err() { + pr_warn!("Error cleaning up object at offset {}\n", i) + } + } + } + + for &fd in &info.file_list.close_on_free { + let closer = match DeferredFdCloser::new(GFP_KERNEL) { + Ok(closer) => closer, + Err(kernel::alloc::AllocError) => { + // Ignore allocation failures. + break; + } + }; + + // Here, we ignore errors. The operation can fail if the fd is not valid, or if the + // method is called from a kthread. However, this is always called from a syscall, + // so the latter case cannot happen, and we don't care about the first case. + let _ = closer.close_fd(fd); + } + + if info.clear_on_free { + if let Err(e) = self.fill_zero() { + pr_warn!("Failed to clear data on free: {:?}", e); + } + } + } + + self.process.buffer_raw_free(self.ptr); + } +} + +/// A wrapper around `Allocation` that is being created. +/// +/// If the allocation is destroyed while wrapped in this wrapper, then the allocation will be +/// considered to be part of a failed transaction. Successful transactions avoid that by calling +/// `success`, which skips the destructor. +#[repr(transparent)] +pub(crate) struct NewAllocation(pub(crate) Allocation); + +impl NewAllocation { + pub(crate) fn success(self) -> Allocation { + // This skips the destructor. + // + // SAFETY: This type is `#[repr(transparent)]`, so the layout matches. + unsafe { core::mem::transmute(self) } + } +} + +impl core::ops::Deref for NewAllocation { + type Target = Allocation; + fn deref(&self) -> &Allocation { + &self.0 + } +} + +impl core::ops::DerefMut for NewAllocation { + fn deref_mut(&mut self) -> &mut Allocation { + &mut self.0 + } +} + +/// A view into the beginning of an allocation. +/// +/// All attempts to read or write outside of the view will fail. To intentionally access outside of +/// this view, use the `alloc` field of this struct directly. +pub(crate) struct AllocationView<'a> { + pub(crate) alloc: &'a mut Allocation, + limit: usize, +} + +impl<'a> AllocationView<'a> { + pub(crate) fn new(alloc: &'a mut Allocation, limit: usize) -> Self { + AllocationView { alloc, limit } + } + + pub(crate) fn read<T: FromBytes>(&self, offset: usize) -> Result<T> { + if offset.checked_add(size_of::<T>()).ok_or(EINVAL)? > self.limit { + return Err(EINVAL); + } + self.alloc.read(offset) + } + + pub(crate) fn write<T: AsBytes>(&self, offset: usize, obj: &T) -> Result { + if offset.checked_add(size_of::<T>()).ok_or(EINVAL)? > self.limit { + return Err(EINVAL); + } + self.alloc.write(offset, obj) + } + + pub(crate) fn copy_into( + &self, + reader: &mut UserSliceReader, + offset: usize, + size: usize, + ) -> Result { + if offset.checked_add(size).ok_or(EINVAL)? > self.limit { + return Err(EINVAL); + } + self.alloc.copy_into(reader, offset, size) + } + + pub(crate) fn transfer_binder_object( + &self, + offset: usize, + obj: &uapi::flat_binder_object, + strong: bool, + node_ref: NodeRef, + ) -> Result { + let mut newobj = FlatBinderObject::default(); + let node = node_ref.node.clone(); + if Arc::ptr_eq(&node_ref.node.owner, &self.alloc.process) { + // The receiving process is the owner of the node, so send it a binder object (instead + // of a handle). + let (ptr, cookie) = node.get_id(); + newobj.hdr.type_ = if strong { + BINDER_TYPE_BINDER + } else { + BINDER_TYPE_WEAK_BINDER + }; + newobj.flags = obj.flags; + newobj.__bindgen_anon_1.binder = ptr as _; + newobj.cookie = cookie as _; + self.write(offset, &newobj)?; + // Increment the user ref count on the node. It will be decremented as part of the + // destruction of the buffer, when we see a binder or weak-binder object. + node.update_refcount(true, 1, strong); + } else { + // The receiving process is different from the owner, so we need to insert a handle to + // the binder object. + let handle = self + .alloc + .process + .as_arc_borrow() + .insert_or_update_handle(node_ref, false)?; + newobj.hdr.type_ = if strong { + BINDER_TYPE_HANDLE + } else { + BINDER_TYPE_WEAK_HANDLE + }; + newobj.flags = obj.flags; + newobj.__bindgen_anon_1.handle = handle; + if self.write(offset, &newobj).is_err() { + // Decrement ref count on the handle we just created. + let _ = self + .alloc + .process + .as_arc_borrow() + .update_ref(handle, false, strong); + return Err(EINVAL); + } + } + + Ok(()) + } + + fn cleanup_object(&self, index_offset: usize) -> Result { + let offset = self.alloc.read(index_offset)?; + let header = self.read::<BinderObjectHeader>(offset)?; + match header.type_ { + BINDER_TYPE_WEAK_BINDER | BINDER_TYPE_BINDER => { + let obj = self.read::<FlatBinderObject>(offset)?; + let strong = header.type_ == BINDER_TYPE_BINDER; + // SAFETY: The type is `BINDER_TYPE_{WEAK_}BINDER`, so the `binder` field is + // populated. + let ptr = unsafe { obj.__bindgen_anon_1.binder }; + let cookie = obj.cookie; + self.alloc.process.update_node(ptr, cookie, strong); + Ok(()) + } + BINDER_TYPE_WEAK_HANDLE | BINDER_TYPE_HANDLE => { + let obj = self.read::<FlatBinderObject>(offset)?; + let strong = header.type_ == BINDER_TYPE_HANDLE; + // SAFETY: The type is `BINDER_TYPE_{WEAK_}HANDLE`, so the `handle` field is + // populated. + let handle = unsafe { obj.__bindgen_anon_1.handle }; + self.alloc + .process + .as_arc_borrow() + .update_ref(handle, false, strong) + } + _ => Ok(()), + } + } +} + +/// A binder object as it is serialized. +/// +/// # Invariants +/// +/// All bytes must be initialized, and the value of `self.hdr.type_` must be one of the allowed +/// types. +#[repr(C)] +pub(crate) union BinderObject { + hdr: uapi::binder_object_header, + fbo: uapi::flat_binder_object, + fdo: uapi::binder_fd_object, + bbo: uapi::binder_buffer_object, + fdao: uapi::binder_fd_array_object, +} + +/// A view into a `BinderObject` that can be used in a match statement. +pub(crate) enum BinderObjectRef<'a> { + Binder(&'a mut uapi::flat_binder_object), + Handle(&'a mut uapi::flat_binder_object), + Fd(&'a mut uapi::binder_fd_object), + Ptr(&'a mut uapi::binder_buffer_object), + Fda(&'a mut uapi::binder_fd_array_object), +} + +impl BinderObject { + pub(crate) fn read_from(reader: &mut UserSliceReader) -> Result<BinderObject> { + let object = Self::read_from_inner(|slice| { + let read_len = usize::min(slice.len(), reader.len()); + reader.clone_reader().read_slice(&mut slice[..read_len])?; + Ok(()) + })?; + + // If we used a object type smaller than the largest object size, then we've read more + // bytes than we needed to. However, we used `.clone_reader()` to avoid advancing the + // original reader. Now, we call `skip` so that the caller's reader is advanced by the + // right amount. + // + // The `skip` call fails if the reader doesn't have `size` bytes available. This could + // happen if the type header corresponds to an object type that is larger than the rest of + // the reader. + // + // Any extra bytes beyond the size of the object are inaccessible after this call, so + // reading them again from the `reader` later does not result in TOCTOU bugs. + reader.skip(object.size())?; + + Ok(object) + } + + /// Use the provided reader closure to construct a `BinderObject`. + /// + /// The closure should write the bytes for the object into the provided slice. + pub(crate) fn read_from_inner<R>(reader: R) -> Result<BinderObject> + where + R: FnOnce(&mut [u8; size_of::<BinderObject>()]) -> Result<()>, + { + let mut obj = MaybeUninit::<BinderObject>::zeroed(); + + // SAFETY: The lengths of `BinderObject` and `[u8; size_of::<BinderObject>()]` are equal, + // and the byte array has an alignment requirement of one, so the pointer cast is okay. + // Additionally, `obj` was initialized to zeros, so the byte array will not be + // uninitialized. + (reader)(unsafe { &mut *obj.as_mut_ptr().cast() })?; + + // SAFETY: The entire object is initialized, so accessing this field is safe. + let type_ = unsafe { obj.assume_init_ref().hdr.type_ }; + if Self::type_to_size(type_).is_none() { + // The value of `obj.hdr_type_` was invalid. + return Err(EINVAL); + } + + // SAFETY: All bytes are initialized (since we zeroed them at the start) and we checked + // that `self.hdr.type_` is one of the allowed types, so the type invariants are satisfied. + unsafe { Ok(obj.assume_init()) } + } + + pub(crate) fn as_ref(&mut self) -> BinderObjectRef<'_> { + use BinderObjectRef::*; + // SAFETY: The constructor ensures that all bytes of `self` are initialized, and all + // variants of this union accept all initialized bit patterns. + unsafe { + match self.hdr.type_ { + BINDER_TYPE_WEAK_BINDER | BINDER_TYPE_BINDER => Binder(&mut self.fbo), + BINDER_TYPE_WEAK_HANDLE | BINDER_TYPE_HANDLE => Handle(&mut self.fbo), + BINDER_TYPE_FD => Fd(&mut self.fdo), + BINDER_TYPE_PTR => Ptr(&mut self.bbo), + BINDER_TYPE_FDA => Fda(&mut self.fdao), + // SAFETY: By the type invariant, the value of `self.hdr.type_` cannot have any + // other value than the ones checked above. + _ => core::hint::unreachable_unchecked(), + } + } + } + + pub(crate) fn size(&self) -> usize { + // SAFETY: The entire object is initialized, so accessing this field is safe. + let type_ = unsafe { self.hdr.type_ }; + + // SAFETY: The type invariants guarantee that the type field is correct. + unsafe { Self::type_to_size(type_).unwrap_unchecked() } + } + + fn type_to_size(type_: u32) -> Option<usize> { + match type_ { + BINDER_TYPE_WEAK_BINDER => Some(size_of::<uapi::flat_binder_object>()), + BINDER_TYPE_BINDER => Some(size_of::<uapi::flat_binder_object>()), + BINDER_TYPE_WEAK_HANDLE => Some(size_of::<uapi::flat_binder_object>()), + BINDER_TYPE_HANDLE => Some(size_of::<uapi::flat_binder_object>()), + BINDER_TYPE_FD => Some(size_of::<uapi::binder_fd_object>()), + BINDER_TYPE_PTR => Some(size_of::<uapi::binder_buffer_object>()), + BINDER_TYPE_FDA => Some(size_of::<uapi::binder_fd_array_object>()), + _ => None, + } + } +} + +#[derive(Default)] +struct FileList { + files_to_translate: KVec<FileEntry>, + close_on_free: KVec<u32>, +} + +struct FileEntry { + /// The file for which a descriptor will be created in the recipient process. + file: ARef<File>, + /// The offset in the buffer where the file descriptor is stored. + buffer_offset: usize, + /// Whether this fd should be closed when the allocation is freed. + close_on_free: bool, +} + +pub(crate) struct TranslatedFds { + reservations: KVec<Reservation>, + /// If commit is called, then these fds should be closed. (If commit is not called, then they + /// shouldn't be closed.) + close_on_free: FdsCloseOnFree, +} + +struct Reservation { + res: FileDescriptorReservation, + file: ARef<File>, +} + +impl TranslatedFds { + pub(crate) fn new() -> Self { + Self { + reservations: KVec::new(), + close_on_free: FdsCloseOnFree(KVec::new()), + } + } + + pub(crate) fn commit(self) -> FdsCloseOnFree { + for entry in self.reservations { + entry.res.fd_install(entry.file); + } + + self.close_on_free + } +} + +pub(crate) struct FdsCloseOnFree(KVec<u32>); diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs new file mode 100644 index 000000000000..3d135ec03ca7 --- /dev/null +++ b/drivers/android/binder/context.rs @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{ + error::Error, + list::{List, ListArc, ListLinks}, + prelude::*, + security, + str::{CStr, CString}, + sync::{Arc, Mutex}, + task::Kuid, +}; + +use crate::{error::BinderError, node::NodeRef, process::Process}; + +kernel::sync::global_lock! { + // SAFETY: We call `init` in the module initializer, so it's initialized before first use. + pub(crate) unsafe(uninit) static CONTEXTS: Mutex<ContextList> = ContextList { + list: List::new(), + }; +} + +pub(crate) struct ContextList { + list: List<Context>, +} + +pub(crate) fn get_all_contexts() -> Result<KVec<Arc<Context>>> { + let lock = CONTEXTS.lock(); + + let count = lock.list.iter().count(); + + let mut ctxs = KVec::with_capacity(count, GFP_KERNEL)?; + for ctx in &lock.list { + ctxs.push(Arc::from(ctx), GFP_KERNEL)?; + } + Ok(ctxs) +} + +/// This struct keeps track of the processes using this context, and which process is the context +/// manager. +struct Manager { + node: Option<NodeRef>, + uid: Option<Kuid>, + all_procs: List<Process>, +} + +/// There is one context per binder file (/dev/binder, /dev/hwbinder, etc) +#[pin_data] +pub(crate) struct Context { + #[pin] + manager: Mutex<Manager>, + pub(crate) name: CString, + #[pin] + links: ListLinks, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for Context { untracked; } +} +kernel::list::impl_list_item! { + impl ListItem<0> for Context { + using ListLinks { self.links }; + } +} + +impl Context { + pub(crate) fn new(name: &CStr) -> Result<Arc<Self>> { + let name = CString::try_from(name)?; + let list_ctx = ListArc::pin_init::<Error>( + try_pin_init!(Context { + name, + links <- ListLinks::new(), + manager <- kernel::new_mutex!(Manager { + all_procs: List::new(), + node: None, + uid: None, + }, "Context::manager"), + }), + GFP_KERNEL, + )?; + + let ctx = list_ctx.clone_arc(); + CONTEXTS.lock().list.push_back(list_ctx); + + Ok(ctx) + } + + /// Called when the file for this context is unlinked. + /// + /// No-op if called twice. + pub(crate) fn deregister(&self) { + // SAFETY: We never add the context to any other linked list than this one, so it is either + // in this list, or not in any list. + unsafe { CONTEXTS.lock().list.remove(self) }; + } + + pub(crate) fn register_process(self: &Arc<Self>, proc: ListArc<Process>) { + if !Arc::ptr_eq(self, &proc.ctx) { + pr_err!("Context::register_process called on the wrong context."); + return; + } + self.manager.lock().all_procs.push_back(proc); + } + + pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Process) { + if !Arc::ptr_eq(self, &proc.ctx) { + pr_err!("Context::deregister_process called on the wrong context."); + return; + } + // SAFETY: We just checked that this is the right list. + unsafe { self.manager.lock().all_procs.remove(proc) }; + } + + pub(crate) fn set_manager_node(&self, node_ref: NodeRef) -> Result { + let mut manager = self.manager.lock(); + if manager.node.is_some() { + pr_warn!("BINDER_SET_CONTEXT_MGR already set"); + return Err(EBUSY); + } + security::binder_set_context_mgr(&node_ref.node.owner.cred)?; + + // If the context manager has been set before, ensure that we use the same euid. + let caller_uid = Kuid::current_euid(); + if let Some(ref uid) = manager.uid { + if *uid != caller_uid { + return Err(EPERM); + } + } + + manager.node = Some(node_ref); + manager.uid = Some(caller_uid); + Ok(()) + } + + pub(crate) fn unset_manager_node(&self) { + let node_ref = self.manager.lock().node.take(); + drop(node_ref); + } + + pub(crate) fn get_manager_node(&self, strong: bool) -> Result<NodeRef, BinderError> { + self.manager + .lock() + .node + .as_ref() + .ok_or_else(BinderError::new_dead)? + .clone(strong) + .map_err(BinderError::from) + } + + pub(crate) fn for_each_proc<F>(&self, mut func: F) + where + F: FnMut(&Process), + { + let lock = self.manager.lock(); + for proc in &lock.all_procs { + func(&proc); + } + } + + pub(crate) fn get_all_procs(&self) -> Result<KVec<Arc<Process>>> { + let lock = self.manager.lock(); + let count = lock.all_procs.iter().count(); + + let mut procs = KVec::with_capacity(count, GFP_KERNEL)?; + for proc in &lock.all_procs { + procs.push(Arc::from(proc), GFP_KERNEL)?; + } + Ok(procs) + } + + pub(crate) fn get_procs_with_pid(&self, pid: i32) -> Result<KVec<Arc<Process>>> { + let orig = self.get_all_procs()?; + let mut backing = KVec::with_capacity(orig.len(), GFP_KERNEL)?; + for proc in orig.into_iter().filter(|proc| proc.task.pid() == pid) { + backing.push(proc, GFP_KERNEL)?; + } + Ok(backing) + } +} diff --git a/drivers/android/binder/deferred_close.rs b/drivers/android/binder/deferred_close.rs new file mode 100644 index 000000000000..ac895c04d0cb --- /dev/null +++ b/drivers/android/binder/deferred_close.rs @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! Logic for closing files in a deferred manner. +//! +//! This file could make sense to have in `kernel::fs`, but it was rejected for being too +//! Binder-specific. + +use core::mem::MaybeUninit; +use kernel::{ + alloc::{AllocError, Flags}, + bindings, + prelude::*, +}; + +/// Helper used for closing file descriptors in a way that is safe even if the file is currently +/// held using `fdget`. +/// +/// Additional motivation can be found in commit 80cd795630d6 ("binder: fix use-after-free due to +/// ksys_close() during fdget()") and in the comments on `binder_do_fd_close`. +pub(crate) struct DeferredFdCloser { + inner: KBox<DeferredFdCloserInner>, +} + +/// SAFETY: This just holds an allocation with no real content, so there's no safety issue with +/// moving it across threads. +unsafe impl Send for DeferredFdCloser {} +/// SAFETY: This just holds an allocation with no real content, so there's no safety issue with +/// moving it across threads. +unsafe impl Sync for DeferredFdCloser {} + +/// # Invariants +/// +/// If the `file` pointer is non-null, then it points at a `struct file` and owns a refcount to +/// that file. +#[repr(C)] +struct DeferredFdCloserInner { + twork: MaybeUninit<bindings::callback_head>, + file: *mut bindings::file, +} + +impl DeferredFdCloser { + /// Create a new [`DeferredFdCloser`]. + pub(crate) fn new(flags: Flags) -> Result<Self, AllocError> { + Ok(Self { + // INVARIANT: The `file` pointer is null, so the type invariant does not apply. + inner: KBox::new( + DeferredFdCloserInner { + twork: MaybeUninit::uninit(), + file: core::ptr::null_mut(), + }, + flags, + )?, + }) + } + + /// Schedule a task work that closes the file descriptor when this task returns to userspace. + /// + /// Fails if this is called from a context where we cannot run work when returning to + /// userspace. (E.g., from a kthread.) + pub(crate) fn close_fd(self, fd: u32) -> Result<(), DeferredFdCloseError> { + use bindings::task_work_notify_mode_TWA_RESUME as TWA_RESUME; + + // In this method, we schedule the task work before closing the file. This is because + // scheduling a task work is fallible, and we need to know whether it will fail before we + // attempt to close the file. + + // Task works are not available on kthreads. + let current = kernel::current!(); + + // Check if this is a kthread. + // SAFETY: Reading `flags` from a task is always okay. + if unsafe { ((*current.as_ptr()).flags & bindings::PF_KTHREAD) != 0 } { + return Err(DeferredFdCloseError::TaskWorkUnavailable); + } + + // Transfer ownership of the box's allocation to a raw pointer. This disables the + // destructor, so we must manually convert it back to a KBox to drop it. + // + // Until we convert it back to a `KBox`, there are no aliasing requirements on this + // pointer. + let inner = KBox::into_raw(self.inner); + + // The `callback_head` field is first in the struct, so this cast correctly gives us a + // pointer to the field. + let callback_head = inner.cast::<bindings::callback_head>(); + // SAFETY: This pointer offset operation does not go out-of-bounds. + let file_field = unsafe { core::ptr::addr_of_mut!((*inner).file) }; + + let current = current.as_ptr(); + + // SAFETY: This function currently has exclusive access to the `DeferredFdCloserInner`, so + // it is okay for us to perform unsynchronized writes to its `callback_head` field. + unsafe { bindings::init_task_work(callback_head, Some(Self::do_close_fd)) }; + + // SAFETY: This inserts the `DeferredFdCloserInner` into the task workqueue for the current + // task. If this operation is successful, then this transfers exclusive ownership of the + // `callback_head` field to the C side until it calls `do_close_fd`, and we don't touch or + // invalidate the field during that time. + // + // When the C side calls `do_close_fd`, the safety requirements of that method are + // satisfied because when a task work is executed, the callback is given ownership of the + // pointer. + // + // The file pointer is currently null. If it is changed to be non-null before `do_close_fd` + // is called, then that change happens due to the write at the end of this function, and + // that write has a safety comment that explains why the refcount can be dropped when + // `do_close_fd` runs. + let res = unsafe { bindings::task_work_add(current, callback_head, TWA_RESUME) }; + + if res != 0 { + // SAFETY: Scheduling the task work failed, so we still have ownership of the box, so + // we may destroy it. + unsafe { drop(KBox::from_raw(inner)) }; + + return Err(DeferredFdCloseError::TaskWorkUnavailable); + } + + // This removes the fd from the fd table in `current`. The file is not fully closed until + // `filp_close` is called. We are given ownership of one refcount to the file. + // + // SAFETY: This is safe no matter what `fd` is. If the `fd` is valid (that is, if the + // pointer is non-null), then we call `filp_close` on the returned pointer as required by + // `file_close_fd`. + let file = unsafe { bindings::file_close_fd(fd) }; + if file.is_null() { + // We don't clean up the task work since that might be expensive if the task work queue + // is long. Just let it execute and let it clean up for itself. + return Err(DeferredFdCloseError::BadFd); + } + + // Acquire a second refcount to the file. + // + // SAFETY: The `file` pointer points at a file with a non-zero refcount. + unsafe { bindings::get_file(file) }; + + // This method closes the fd, consuming one of our two refcounts. There could be active + // light refcounts created from that fd, so we must ensure that the file has a positive + // refcount for the duration of those active light refcounts. We do that by holding on to + // the second refcount until the current task returns to userspace. + // + // SAFETY: The `file` pointer is valid. Passing `current->files` as the file table to close + // it in is correct, since we just got the `fd` from `file_close_fd` which also uses + // `current->files`. + // + // Note: fl_owner_t is currently a void pointer. + unsafe { bindings::filp_close(file, (*current).files as bindings::fl_owner_t) }; + + // We update the file pointer that the task work is supposed to fput. This transfers + // ownership of our last refcount. + // + // INVARIANT: This changes the `file` field of a `DeferredFdCloserInner` from null to + // non-null. This doesn't break the type invariant for `DeferredFdCloserInner` because we + // still own a refcount to the file, so we can pass ownership of that refcount to the + // `DeferredFdCloserInner`. + // + // When `do_close_fd` runs, it must be safe for it to `fput` the refcount. However, this is + // the case because all light refcounts that are associated with the fd we closed + // previously must be dropped when `do_close_fd`, since light refcounts must be dropped + // before returning to userspace. + // + // SAFETY: Task works are executed on the current thread right before we return to + // userspace, so this write is guaranteed to happen before `do_close_fd` is called, which + // means that a race is not possible here. + unsafe { *file_field = file }; + + Ok(()) + } + + /// # Safety + /// + /// The provided pointer must point at the `twork` field of a `DeferredFdCloserInner` stored in + /// a `KBox`, and the caller must pass exclusive ownership of that `KBox`. Furthermore, if the + /// file pointer is non-null, then it must be okay to release the refcount by calling `fput`. + unsafe extern "C" fn do_close_fd(inner: *mut bindings::callback_head) { + // SAFETY: The caller just passed us ownership of this box. + let inner = unsafe { KBox::from_raw(inner.cast::<DeferredFdCloserInner>()) }; + if !inner.file.is_null() { + // SAFETY: By the type invariants, we own a refcount to this file, and the caller + // guarantees that dropping the refcount now is okay. + unsafe { bindings::fput(inner.file) }; + } + // The allocation is freed when `inner` goes out of scope. + } +} + +/// Represents a failure to close an fd in a deferred manner. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum DeferredFdCloseError { + /// Closing the fd failed because we were unable to schedule a task work. + TaskWorkUnavailable, + /// Closing the fd failed because the fd does not exist. + BadFd, +} + +impl From<DeferredFdCloseError> for Error { + fn from(err: DeferredFdCloseError) -> Error { + match err { + DeferredFdCloseError::TaskWorkUnavailable => ESRCH, + DeferredFdCloseError::BadFd => EBADF, + } + } +} diff --git a/drivers/android/binder/defs.rs b/drivers/android/binder/defs.rs new file mode 100644 index 000000000000..33f51b4139c7 --- /dev/null +++ b/drivers/android/binder/defs.rs @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use kernel::{ + transmute::{AsBytes, FromBytes}, + uapi::{self, *}, +}; + +macro_rules! pub_no_prefix { + ($prefix:ident, $($newname:ident),+ $(,)?) => { + $(pub(crate) const $newname: u32 = kernel::macros::concat_idents!($prefix, $newname);)+ + }; +} + +pub_no_prefix!( + binder_driver_return_protocol_, + BR_TRANSACTION, + BR_TRANSACTION_SEC_CTX, + BR_REPLY, + BR_DEAD_REPLY, + BR_FAILED_REPLY, + BR_FROZEN_REPLY, + BR_NOOP, + BR_SPAWN_LOOPER, + BR_TRANSACTION_COMPLETE, + BR_TRANSACTION_PENDING_FROZEN, + BR_ONEWAY_SPAM_SUSPECT, + BR_OK, + BR_ERROR, + BR_INCREFS, + BR_ACQUIRE, + BR_RELEASE, + BR_DECREFS, + BR_DEAD_BINDER, + BR_CLEAR_DEATH_NOTIFICATION_DONE, + BR_FROZEN_BINDER, + BR_CLEAR_FREEZE_NOTIFICATION_DONE, +); + +pub_no_prefix!( + binder_driver_command_protocol_, + BC_TRANSACTION, + BC_TRANSACTION_SG, + BC_REPLY, + BC_REPLY_SG, + BC_FREE_BUFFER, + BC_ENTER_LOOPER, + BC_EXIT_LOOPER, + BC_REGISTER_LOOPER, + BC_INCREFS, + BC_ACQUIRE, + BC_RELEASE, + BC_DECREFS, + BC_INCREFS_DONE, + BC_ACQUIRE_DONE, + BC_REQUEST_DEATH_NOTIFICATION, + BC_CLEAR_DEATH_NOTIFICATION, + BC_DEAD_BINDER_DONE, + BC_REQUEST_FREEZE_NOTIFICATION, + BC_CLEAR_FREEZE_NOTIFICATION, + BC_FREEZE_NOTIFICATION_DONE, +); + +pub_no_prefix!( + flat_binder_object_flags_, + FLAT_BINDER_FLAG_ACCEPTS_FDS, + FLAT_BINDER_FLAG_TXN_SECURITY_CTX +); + +pub_no_prefix!( + transaction_flags_, + TF_ONE_WAY, + TF_ACCEPT_FDS, + TF_CLEAR_BUF, + TF_UPDATE_TXN +); + +pub(crate) use uapi::{ + BINDER_TYPE_BINDER, BINDER_TYPE_FD, BINDER_TYPE_FDA, BINDER_TYPE_HANDLE, BINDER_TYPE_PTR, + BINDER_TYPE_WEAK_BINDER, BINDER_TYPE_WEAK_HANDLE, +}; + +macro_rules! decl_wrapper { + ($newname:ident, $wrapped:ty) => { + // Define a wrapper around the C type. Use `MaybeUninit` to enforce that the value of + // padding bytes must be preserved. + #[derive(Copy, Clone)] + #[repr(transparent)] + pub(crate) struct $newname(MaybeUninit<$wrapped>); + + // SAFETY: This macro is only used with types where this is ok. + unsafe impl FromBytes for $newname {} + // SAFETY: This macro is only used with types where this is ok. + unsafe impl AsBytes for $newname {} + + impl Deref for $newname { + type Target = $wrapped; + fn deref(&self) -> &Self::Target { + // SAFETY: We use `MaybeUninit` only to preserve padding. The value must still + // always be valid. + unsafe { self.0.assume_init_ref() } + } + } + + impl DerefMut for $newname { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: We use `MaybeUninit` only to preserve padding. The value must still + // always be valid. + unsafe { self.0.assume_init_mut() } + } + } + + impl Default for $newname { + fn default() -> Self { + // Create a new value of this type where all bytes (including padding) are zeroed. + Self(MaybeUninit::zeroed()) + } + } + }; +} + +decl_wrapper!(BinderNodeDebugInfo, uapi::binder_node_debug_info); +decl_wrapper!(BinderNodeInfoForRef, uapi::binder_node_info_for_ref); +decl_wrapper!(FlatBinderObject, uapi::flat_binder_object); +decl_wrapper!(BinderFdObject, uapi::binder_fd_object); +decl_wrapper!(BinderFdArrayObject, uapi::binder_fd_array_object); +decl_wrapper!(BinderObjectHeader, uapi::binder_object_header); +decl_wrapper!(BinderBufferObject, uapi::binder_buffer_object); +decl_wrapper!(BinderTransactionData, uapi::binder_transaction_data); +decl_wrapper!( + BinderTransactionDataSecctx, + uapi::binder_transaction_data_secctx +); +decl_wrapper!(BinderTransactionDataSg, uapi::binder_transaction_data_sg); +decl_wrapper!(BinderWriteRead, uapi::binder_write_read); +decl_wrapper!(BinderVersion, uapi::binder_version); +decl_wrapper!(BinderFrozenStatusInfo, uapi::binder_frozen_status_info); +decl_wrapper!(BinderFreezeInfo, uapi::binder_freeze_info); +decl_wrapper!(BinderFrozenStateInfo, uapi::binder_frozen_state_info); +decl_wrapper!(BinderHandleCookie, uapi::binder_handle_cookie); +decl_wrapper!(ExtendedError, uapi::binder_extended_error); + +impl BinderVersion { + pub(crate) fn current() -> Self { + Self(MaybeUninit::new(uapi::binder_version { + protocol_version: BINDER_CURRENT_PROTOCOL_VERSION as _, + })) + } +} + +impl BinderTransactionData { + pub(crate) fn with_buffers_size(self, buffers_size: u64) -> BinderTransactionDataSg { + BinderTransactionDataSg(MaybeUninit::new(uapi::binder_transaction_data_sg { + transaction_data: *self, + buffers_size, + })) + } +} + +impl BinderTransactionDataSecctx { + /// View the inner data as wrapped in `BinderTransactionData`. + pub(crate) fn tr_data(&mut self) -> &mut BinderTransactionData { + // SAFETY: Transparent wrapper is safe to transmute. + unsafe { + &mut *(&mut self.transaction_data as *mut uapi::binder_transaction_data + as *mut BinderTransactionData) + } + } +} + +impl ExtendedError { + pub(crate) fn new(id: u32, command: u32, param: i32) -> Self { + Self(MaybeUninit::new(uapi::binder_extended_error { + id, + command, + param, + })) + } +} diff --git a/drivers/android/binder/error.rs b/drivers/android/binder/error.rs new file mode 100644 index 000000000000..9921827267d0 --- /dev/null +++ b/drivers/android/binder/error.rs @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::prelude::*; + +use crate::defs::*; + +pub(crate) type BinderResult<T = ()> = core::result::Result<T, BinderError>; + +/// An error that will be returned to userspace via the `BINDER_WRITE_READ` ioctl rather than via +/// errno. +pub(crate) struct BinderError { + pub(crate) reply: u32, + source: Option<Error>, +} + +impl BinderError { + pub(crate) fn new_dead() -> Self { + Self { + reply: BR_DEAD_REPLY, + source: None, + } + } + + pub(crate) fn new_frozen() -> Self { + Self { + reply: BR_FROZEN_REPLY, + source: None, + } + } + + pub(crate) fn new_frozen_oneway() -> Self { + Self { + reply: BR_TRANSACTION_PENDING_FROZEN, + source: None, + } + } + + pub(crate) fn is_dead(&self) -> bool { + self.reply == BR_DEAD_REPLY + } + + pub(crate) fn as_errno(&self) -> kernel::ffi::c_int { + self.source.unwrap_or(EINVAL).to_errno() + } + + pub(crate) fn should_pr_warn(&self) -> bool { + self.source.is_some() + } +} + +/// Convert an errno into a `BinderError` and store the errno used to construct it. The errno +/// should be stored as the thread's extended error when given to userspace. +impl From<Error> for BinderError { + fn from(source: Error) -> Self { + Self { + reply: BR_FAILED_REPLY, + source: Some(source), + } + } +} + +impl From<kernel::fs::file::BadFdError> for BinderError { + fn from(source: kernel::fs::file::BadFdError) -> Self { + BinderError::from(Error::from(source)) + } +} + +impl From<kernel::alloc::AllocError> for BinderError { + fn from(_: kernel::alloc::AllocError) -> Self { + Self { + reply: BR_FAILED_REPLY, + source: Some(ENOMEM), + } + } +} + +impl core::fmt::Debug for BinderError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.reply { + BR_FAILED_REPLY => match self.source.as_ref() { + Some(source) => f + .debug_struct("BR_FAILED_REPLY") + .field("source", source) + .finish(), + None => f.pad("BR_FAILED_REPLY"), + }, + BR_DEAD_REPLY => f.pad("BR_DEAD_REPLY"), + BR_FROZEN_REPLY => f.pad("BR_FROZEN_REPLY"), + BR_TRANSACTION_PENDING_FROZEN => f.pad("BR_TRANSACTION_PENDING_FROZEN"), + BR_TRANSACTION_COMPLETE => f.pad("BR_TRANSACTION_COMPLETE"), + _ => f + .debug_struct("BinderError") + .field("reply", &self.reply) + .finish(), + } + } +} diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs new file mode 100644 index 000000000000..e68c3c8bc55a --- /dev/null +++ b/drivers/android/binder/freeze.rs @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{ + alloc::AllocError, + list::ListArc, + prelude::*, + rbtree::{self, RBTreeNodeReservation}, + seq_file::SeqFile, + seq_print, + sync::{Arc, UniqueArc}, + uaccess::UserSliceReader, +}; + +use crate::{ + defs::*, node::Node, process::Process, thread::Thread, BinderReturnWriter, DArc, DLArc, + DTRWrap, DeliverToRead, +}; + +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] +pub(crate) struct FreezeCookie(u64); + +/// Represents a listener for changes to the frozen state of a process. +pub(crate) struct FreezeListener { + /// The node we are listening for. + pub(crate) node: DArc<Node>, + /// The cookie of this freeze listener. + cookie: FreezeCookie, + /// What value of `is_frozen` did we most recently tell userspace about? + last_is_frozen: Option<bool>, + /// We sent a `BR_FROZEN_BINDER` and we are waiting for `BC_FREEZE_NOTIFICATION_DONE` before + /// sending any other commands. + is_pending: bool, + /// Userspace sent `BC_CLEAR_FREEZE_NOTIFICATION` and we need to reply with + /// `BR_CLEAR_FREEZE_NOTIFICATION_DONE` as soon as possible. If `is_pending` is set, then we + /// must wait for it to be unset before we can reply. + is_clearing: bool, + /// Number of cleared duplicates that can't be deleted until userspace sends + /// `BC_FREEZE_NOTIFICATION_DONE`. + num_pending_duplicates: u64, + /// Number of cleared duplicates that can be deleted. + num_cleared_duplicates: u64, +} + +impl FreezeListener { + /// Is it okay to create a new listener with the same cookie as this one for the provided node? + /// + /// Under some scenarios, userspace may delete a freeze listener and immediately recreate it + /// with the same cookie. This results in duplicate listeners. To avoid issues with ambiguity, + /// we allow this only if the new listener is for the same node, and we also require that the + /// old listener has already been cleared. + fn allow_duplicate(&self, node: &DArc<Node>) -> bool { + Arc::ptr_eq(&self.node, node) && self.is_clearing + } +} + +type UninitFM = UniqueArc<core::mem::MaybeUninit<DTRWrap<FreezeMessage>>>; + +/// Represents a notification that the freeze state has changed. +pub(crate) struct FreezeMessage { + cookie: FreezeCookie, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for FreezeMessage { + untracked; + } +} + +impl FreezeMessage { + fn new(flags: kernel::alloc::Flags) -> Result<UninitFM, AllocError> { + UniqueArc::new_uninit(flags) + } + + fn init(ua: UninitFM, cookie: FreezeCookie) -> DLArc<FreezeMessage> { + match ua.pin_init_with(DTRWrap::new(FreezeMessage { cookie })) { + Ok(msg) => ListArc::from(msg), + Err(err) => match err {}, + } + } +} + +impl DeliverToRead for FreezeMessage { + fn do_work( + self: DArc<Self>, + thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let _removed_listener; + let mut node_refs = thread.process.node_refs.lock(); + let Some(mut freeze_entry) = node_refs.freeze_listeners.find_mut(&self.cookie) else { + return Ok(true); + }; + let freeze = freeze_entry.get_mut(); + + if freeze.num_cleared_duplicates > 0 { + freeze.num_cleared_duplicates -= 1; + drop(node_refs); + writer.write_code(BR_CLEAR_FREEZE_NOTIFICATION_DONE)?; + writer.write_payload(&self.cookie.0)?; + return Ok(true); + } + + if freeze.is_pending { + return Ok(true); + } + if freeze.is_clearing { + _removed_listener = freeze_entry.remove_node(); + drop(node_refs); + writer.write_code(BR_CLEAR_FREEZE_NOTIFICATION_DONE)?; + writer.write_payload(&self.cookie.0)?; + Ok(true) + } else { + let is_frozen = freeze.node.owner.inner.lock().is_frozen; + if freeze.last_is_frozen == Some(is_frozen) { + return Ok(true); + } + + let mut state_info = BinderFrozenStateInfo::default(); + state_info.is_frozen = is_frozen as u32; + state_info.cookie = freeze.cookie.0; + freeze.is_pending = true; + freeze.last_is_frozen = Some(is_frozen); + drop(node_refs); + + writer.write_code(BR_FROZEN_BINDER)?; + writer.write_payload(&state_info)?; + // BR_FROZEN_BINDER notifications can cause transactions + Ok(false) + } + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + #[inline(never)] + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + seq_print!(m, "{}has frozen binder\n", prefix); + Ok(()) + } +} + +impl FreezeListener { + pub(crate) fn on_process_exit(&self, proc: &Arc<Process>) { + if !self.is_clearing { + self.node.remove_freeze_listener(proc); + } + } +} + +impl Process { + pub(crate) fn request_freeze_notif( + self: &Arc<Self>, + reader: &mut UserSliceReader, + ) -> Result<()> { + let hc = reader.read::<BinderHandleCookie>()?; + let handle = hc.handle; + let cookie = FreezeCookie(hc.cookie); + + let msg = FreezeMessage::new(GFP_KERNEL)?; + let alloc = RBTreeNodeReservation::new(GFP_KERNEL)?; + + let mut node_refs_guard = self.node_refs.lock(); + let node_refs = &mut *node_refs_guard; + let Some(info) = node_refs.by_handle.get_mut(&handle) else { + pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION invalid ref {}\n", handle); + return Err(EINVAL); + }; + if info.freeze().is_some() { + pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION already set\n"); + return Err(EINVAL); + } + let node_ref = info.node_ref(); + let freeze_entry = node_refs.freeze_listeners.entry(cookie); + + if let rbtree::Entry::Occupied(ref dupe) = freeze_entry { + if !dupe.get().allow_duplicate(&node_ref.node) { + pr_warn!("BC_REQUEST_FREEZE_NOTIFICATION duplicate cookie\n"); + return Err(EINVAL); + } + } + + // All failure paths must come before this call, and all modifications must come after this + // call. + node_ref.node.add_freeze_listener(self, GFP_KERNEL)?; + + match freeze_entry { + rbtree::Entry::Vacant(entry) => { + entry.insert( + FreezeListener { + cookie, + node: node_ref.node.clone(), + last_is_frozen: None, + is_pending: false, + is_clearing: false, + num_pending_duplicates: 0, + num_cleared_duplicates: 0, + }, + alloc, + ); + } + rbtree::Entry::Occupied(mut dupe) => { + let dupe = dupe.get_mut(); + if dupe.is_pending { + dupe.num_pending_duplicates += 1; + } else { + dupe.num_cleared_duplicates += 1; + } + dupe.last_is_frozen = None; + dupe.is_pending = false; + dupe.is_clearing = false; + } + } + + *info.freeze() = Some(cookie); + let msg = FreezeMessage::init(msg, cookie); + drop(node_refs_guard); + let _ = self.push_work(msg); + Ok(()) + } + + pub(crate) fn freeze_notif_done(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result<()> { + let cookie = FreezeCookie(reader.read()?); + let alloc = FreezeMessage::new(GFP_KERNEL)?; + let mut node_refs_guard = self.node_refs.lock(); + let node_refs = &mut *node_refs_guard; + let Some(freeze) = node_refs.freeze_listeners.get_mut(&cookie) else { + pr_warn!("BC_FREEZE_NOTIFICATION_DONE {:016x} not found\n", cookie.0); + return Err(EINVAL); + }; + let mut clear_msg = None; + if freeze.num_pending_duplicates > 0 { + clear_msg = Some(FreezeMessage::init(alloc, cookie)); + freeze.num_pending_duplicates -= 1; + freeze.num_cleared_duplicates += 1; + } else { + if !freeze.is_pending { + pr_warn!( + "BC_FREEZE_NOTIFICATION_DONE {:016x} not pending\n", + cookie.0 + ); + return Err(EINVAL); + } + if freeze.is_clearing { + // Immediately send another FreezeMessage for BR_CLEAR_FREEZE_NOTIFICATION_DONE. + clear_msg = Some(FreezeMessage::init(alloc, cookie)); + } + freeze.is_pending = false; + } + drop(node_refs_guard); + if let Some(clear_msg) = clear_msg { + let _ = self.push_work(clear_msg); + } + Ok(()) + } + + pub(crate) fn clear_freeze_notif(self: &Arc<Self>, reader: &mut UserSliceReader) -> Result<()> { + let hc = reader.read::<BinderHandleCookie>()?; + let handle = hc.handle; + let cookie = FreezeCookie(hc.cookie); + + let alloc = FreezeMessage::new(GFP_KERNEL)?; + let mut node_refs_guard = self.node_refs.lock(); + let node_refs = &mut *node_refs_guard; + let Some(info) = node_refs.by_handle.get_mut(&handle) else { + pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION invalid ref {}\n", handle); + return Err(EINVAL); + }; + let Some(info_cookie) = info.freeze() else { + pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active\n"); + return Err(EINVAL); + }; + if *info_cookie != cookie { + pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch\n"); + return Err(EINVAL); + } + let Some(listener) = node_refs.freeze_listeners.get_mut(&cookie) else { + pr_warn!("BC_CLEAR_FREEZE_NOTIFICATION invalid cookie {}\n", handle); + return Err(EINVAL); + }; + listener.is_clearing = true; + listener.node.remove_freeze_listener(self); + *info.freeze() = None; + let mut msg = None; + if !listener.is_pending { + msg = Some(FreezeMessage::init(alloc, cookie)); + } + drop(node_refs_guard); + + if let Some(msg) = msg { + let _ = self.push_work(msg); + } + Ok(()) + } + + fn get_freeze_cookie(&self, node: &DArc<Node>) -> Option<FreezeCookie> { + let node_refs = &mut *self.node_refs.lock(); + let handle = node_refs.by_node.get(&node.global_id())?; + let node_ref = node_refs.by_handle.get_mut(handle)?; + *node_ref.freeze() + } + + /// Creates a vector of every freeze listener on this process. + /// + /// Returns pairs of the remote process listening for notifications and the local node it is + /// listening on. + #[expect(clippy::type_complexity)] + fn find_freeze_recipients(&self) -> Result<KVVec<(DArc<Node>, Arc<Process>)>, AllocError> { + // Defined before `inner` to drop after releasing spinlock if `push_within_capacity` fails. + let mut node_proc_pair; + + // We pre-allocate space for up to 8 recipients before we take the spinlock. However, if + // the allocation fails, use a vector with a capacity of zero instead of failing. After + // all, there might not be any freeze listeners, in which case this operation could still + // succeed. + let mut recipients = + KVVec::with_capacity(8, GFP_KERNEL).unwrap_or_else(|_err| KVVec::new()); + + let mut inner = self.lock_with_nodes(); + let mut curr = inner.nodes.cursor_front(); + while let Some(cursor) = curr { + let (key, node) = cursor.current(); + let key = *key; + let list = node.freeze_list(&inner.inner); + let len = list.len(); + + if recipients.spare_capacity_mut().len() < len { + drop(inner); + recipients.reserve(len, GFP_KERNEL)?; + inner = self.lock_with_nodes(); + // Find the node we were looking at and try again. If the set of nodes was changed, + // then just proceed to the next node. This is ok because we don't guarantee the + // inclusion of nodes that are added or removed in parallel with this operation. + curr = inner.nodes.cursor_lower_bound(&key); + continue; + } + + for proc in list { + node_proc_pair = (node.clone(), proc.clone()); + recipients + .push_within_capacity(node_proc_pair) + .map_err(|_| { + pr_err!( + "push_within_capacity failed even though we checked the capacity\n" + ); + AllocError + })?; + } + + curr = cursor.move_next(); + } + Ok(recipients) + } + + /// Prepare allocations for sending freeze messages. + pub(crate) fn prepare_freeze_messages(&self) -> Result<FreezeMessages, AllocError> { + let recipients = self.find_freeze_recipients()?; + let mut batch = KVVec::with_capacity(recipients.len(), GFP_KERNEL)?; + for (node, proc) in recipients { + let Some(cookie) = proc.get_freeze_cookie(&node) else { + // If the freeze listener was removed in the meantime, just discard the + // notification. + continue; + }; + let msg_alloc = FreezeMessage::new(GFP_KERNEL)?; + let msg = FreezeMessage::init(msg_alloc, cookie); + batch.push((proc, msg), GFP_KERNEL)?; + } + + Ok(FreezeMessages { batch }) + } +} + +pub(crate) struct FreezeMessages { + batch: KVVec<(Arc<Process>, DLArc<FreezeMessage>)>, +} + +impl FreezeMessages { + pub(crate) fn send_messages(self) { + for (proc, msg) in self.batch { + let _ = proc.push_work(msg); + } + } +} diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs new file mode 100644 index 000000000000..ade895ef791e --- /dev/null +++ b/drivers/android/binder/node.rs @@ -0,0 +1,1131 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{ + list::{AtomicTracker, List, ListArc, ListLinks, TryNewListArc}, + prelude::*, + seq_file::SeqFile, + seq_print, + sync::lock::{spinlock::SpinLockBackend, Guard}, + sync::{Arc, LockedBy, SpinLock}, +}; + +use crate::{ + defs::*, + error::BinderError, + process::{NodeRefInfo, Process, ProcessInner}, + thread::Thread, + transaction::Transaction, + BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead, +}; + +use core::mem; + +mod wrapper; +pub(crate) use self::wrapper::CritIncrWrapper; + +#[derive(Debug)] +pub(crate) struct CouldNotDeliverCriticalIncrement; + +/// Keeps track of how this node is scheduled. +/// +/// There are two ways to schedule a node to a work list. Just schedule the node itself, or +/// allocate a wrapper that references the node and schedule the wrapper. These wrappers exists to +/// make it possible to "move" a node from one list to another - when `do_work` is called directly +/// on the `Node`, then it's a no-op if there's also a pending wrapper. +/// +/// Wrappers are generally only needed for zero-to-one refcount increments, and there are two cases +/// of this: weak increments and strong increments. We call such increments "critical" because it +/// is critical that they are delivered to the thread doing the increment. Some examples: +/// +/// * One thread makes a zero-to-one strong increment, and another thread makes a zero-to-one weak +/// increment. Delivering the node to the thread doing the weak increment is wrong, since the +/// thread doing the strong increment may have ended a long time ago when the command is actually +/// processed by userspace. +/// +/// * We have a weak reference and are about to drop it on one thread. But then another thread does +/// a zero-to-one strong increment. If the strong increment gets sent to the thread that was +/// about to drop the weak reference, then the strong increment could be processed after the +/// other thread has already exited, which would be too late. +/// +/// Note that trying to create a `ListArc` to the node can succeed even if `has_normal_push` is +/// set. This is because another thread might just have popped the node from a todo list, but not +/// yet called `do_work`. However, if `has_normal_push` is false, then creating a `ListArc` should +/// always succeed. +/// +/// Like the other fields in `NodeInner`, the delivery state is protected by the process lock. +struct DeliveryState { + /// Is the `Node` currently scheduled? + has_pushed_node: bool, + + /// Is a wrapper currently scheduled? + /// + /// The wrapper is used only for strong zero2one increments. + has_pushed_wrapper: bool, + + /// Is the currently scheduled `Node` scheduled due to a weak zero2one increment? + /// + /// Weak zero2one operations are always scheduled using the `Node`. + has_weak_zero2one: bool, + + /// Is the currently scheduled wrapper/`Node` scheduled due to a strong zero2one increment? + /// + /// If `has_pushed_wrapper` is set, then the strong zero2one increment was scheduled using the + /// wrapper. Otherwise, `has_pushed_node` must be set and it was scheduled using the `Node`. + has_strong_zero2one: bool, +} + +impl DeliveryState { + fn should_normal_push(&self) -> bool { + !self.has_pushed_node && !self.has_pushed_wrapper + } + + fn did_normal_push(&mut self) { + assert!(self.should_normal_push()); + self.has_pushed_node = true; + } + + fn should_push_weak_zero2one(&self) -> bool { + !self.has_weak_zero2one && !self.has_strong_zero2one + } + + fn can_push_weak_zero2one_normally(&self) -> bool { + !self.has_pushed_node + } + + fn did_push_weak_zero2one(&mut self) { + assert!(self.should_push_weak_zero2one()); + assert!(self.can_push_weak_zero2one_normally()); + self.has_pushed_node = true; + self.has_weak_zero2one = true; + } + + fn should_push_strong_zero2one(&self) -> bool { + !self.has_strong_zero2one + } + + fn can_push_strong_zero2one_normally(&self) -> bool { + !self.has_pushed_node + } + + fn did_push_strong_zero2one(&mut self) { + assert!(self.should_push_strong_zero2one()); + assert!(self.can_push_strong_zero2one_normally()); + self.has_pushed_node = true; + self.has_strong_zero2one = true; + } + + fn did_push_strong_zero2one_wrapper(&mut self) { + assert!(self.should_push_strong_zero2one()); + assert!(!self.can_push_strong_zero2one_normally()); + self.has_pushed_wrapper = true; + self.has_strong_zero2one = true; + } +} + +struct CountState { + /// The reference count. + count: usize, + /// Whether the process that owns this node thinks that we hold a refcount on it. (Note that + /// even if count is greater than one, we only increment it once in the owning process.) + has_count: bool, +} + +impl CountState { + fn new() -> Self { + Self { + count: 0, + has_count: false, + } + } +} + +struct NodeInner { + /// Strong refcounts held on this node by `NodeRef` objects. + strong: CountState, + /// Weak refcounts held on this node by `NodeRef` objects. + weak: CountState, + delivery_state: DeliveryState, + /// The binder driver guarantees that oneway transactions sent to the same node are serialized, + /// that is, userspace will not be given the next one until it has finished processing the + /// previous oneway transaction. This is done to avoid the case where two oneway transactions + /// arrive in opposite order from the order in which they were sent. (E.g., they could be + /// delivered to two different threads, which could appear as-if they were sent in opposite + /// order.) + /// + /// To fix that, we store pending oneway transactions in a separate list in the node, and don't + /// deliver the next oneway transaction until userspace signals that it has finished processing + /// the previous oneway transaction by calling the `BC_FREE_BUFFER` ioctl. + oneway_todo: List<DTRWrap<Transaction>>, + /// Keeps track of whether this node has a pending oneway transaction. + /// + /// When this is true, incoming oneway transactions are stored in `oneway_todo`, instead of + /// being delivered directly to the process. + has_oneway_transaction: bool, + /// List of processes to deliver a notification to when this node is destroyed (usually due to + /// the process dying). + death_list: List<DTRWrap<NodeDeath>, 1>, + /// List of processes to deliver freeze notifications to. + freeze_list: KVVec<Arc<Process>>, + /// The number of active BR_INCREFS or BR_ACQUIRE operations. (should be maximum two) + /// + /// If this is non-zero, then we postpone any BR_RELEASE or BR_DECREFS notifications until the + /// active operations have ended. This avoids the situation an increment and decrement get + /// reordered from userspace's perspective. + active_inc_refs: u8, + /// List of `NodeRefInfo` objects that reference this node. + refs: List<NodeRefInfo, { NodeRefInfo::LIST_NODE }>, +} + +#[pin_data] +pub(crate) struct Node { + pub(crate) debug_id: usize, + ptr: u64, + pub(crate) cookie: u64, + pub(crate) flags: u32, + pub(crate) owner: Arc<Process>, + inner: LockedBy<NodeInner, ProcessInner>, + #[pin] + links_track: AtomicTracker, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for Node { + tracked_by links_track: AtomicTracker; + } +} + +// Make `oneway_todo` work. +kernel::list::impl_list_item! { + impl ListItem<0> for DTRWrap<Transaction> { + using ListLinks { self.links.inner }; + } +} + +impl Node { + pub(crate) fn new( + ptr: u64, + cookie: u64, + flags: u32, + owner: Arc<Process>, + ) -> impl PinInit<Self> { + pin_init!(Self { + inner: LockedBy::new( + &owner.inner, + NodeInner { + strong: CountState::new(), + weak: CountState::new(), + delivery_state: DeliveryState { + has_pushed_node: false, + has_pushed_wrapper: false, + has_weak_zero2one: false, + has_strong_zero2one: false, + }, + death_list: List::new(), + oneway_todo: List::new(), + freeze_list: KVVec::new(), + has_oneway_transaction: false, + active_inc_refs: 0, + refs: List::new(), + }, + ), + debug_id: super::next_debug_id(), + ptr, + cookie, + flags, + owner, + links_track <- AtomicTracker::new(), + }) + } + + pub(crate) fn has_oneway_transaction(&self, owner_inner: &mut ProcessInner) -> bool { + let inner = self.inner.access_mut(owner_inner); + inner.has_oneway_transaction + } + + #[inline(never)] + pub(crate) fn full_debug_print( + &self, + m: &SeqFile, + owner_inner: &mut ProcessInner, + ) -> Result<()> { + let inner = self.inner.access_mut(owner_inner); + seq_print!( + m, + " node {}: u{:016x} c{:016x} hs {} hw {} cs {} cw {}", + self.debug_id, + self.ptr, + self.cookie, + inner.strong.has_count, + inner.weak.has_count, + inner.strong.count, + inner.weak.count, + ); + if !inner.refs.is_empty() { + seq_print!(m, " proc"); + for node_ref in &inner.refs { + seq_print!(m, " {}", node_ref.process.task.pid()); + } + } + seq_print!(m, "\n"); + for t in &inner.oneway_todo { + t.debug_print_inner(m, " pending async transaction "); + } + Ok(()) + } + + /// Insert the `NodeRef` into this `refs` list. + /// + /// # Safety + /// + /// It must be the case that `info.node_ref.node` is this node. + pub(crate) unsafe fn insert_node_info( + &self, + info: ListArc<NodeRefInfo, { NodeRefInfo::LIST_NODE }>, + ) { + self.inner + .access_mut(&mut self.owner.inner.lock()) + .refs + .push_front(info); + } + + /// Insert the `NodeRef` into this `refs` list. + /// + /// # Safety + /// + /// It must be the case that `info.node_ref.node` is this node. + pub(crate) unsafe fn remove_node_info( + &self, + info: &NodeRefInfo, + ) -> Option<ListArc<NodeRefInfo, { NodeRefInfo::LIST_NODE }>> { + // SAFETY: We always insert `NodeRefInfo` objects into the `refs` list of the node that it + // references in `info.node_ref.node`. That is this node, so `info` cannot possibly be in + // the `refs` list of another node. + unsafe { + self.inner + .access_mut(&mut self.owner.inner.lock()) + .refs + .remove(info) + } + } + + /// An id that is unique across all binder nodes on the system. Used as the key in the + /// `by_node` map. + pub(crate) fn global_id(&self) -> usize { + self as *const Node as usize + } + + pub(crate) fn get_id(&self) -> (u64, u64) { + (self.ptr, self.cookie) + } + + pub(crate) fn add_death( + &self, + death: ListArc<DTRWrap<NodeDeath>, 1>, + guard: &mut Guard<'_, ProcessInner, SpinLockBackend>, + ) { + self.inner.access_mut(guard).death_list.push_back(death); + } + + pub(crate) fn inc_ref_done_locked( + self: &DArc<Node>, + _strong: bool, + owner_inner: &mut ProcessInner, + ) -> Option<DLArc<Node>> { + let inner = self.inner.access_mut(owner_inner); + if inner.active_inc_refs == 0 { + pr_err!("inc_ref_done called when no active inc_refs"); + return None; + } + + inner.active_inc_refs -= 1; + if inner.active_inc_refs == 0 { + // Having active inc_refs can inhibit dropping of ref-counts. Calculate whether we + // would send a refcount decrement, and if so, tell the caller to schedule us. + let strong = inner.strong.count > 0; + let has_strong = inner.strong.has_count; + let weak = strong || inner.weak.count > 0; + let has_weak = inner.weak.has_count; + + let should_drop_weak = !weak && has_weak; + let should_drop_strong = !strong && has_strong; + + // If we want to drop the ref-count again, tell the caller to schedule a work node for + // that. + let need_push = should_drop_weak || should_drop_strong; + + if need_push && inner.delivery_state.should_normal_push() { + let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap(); + inner.delivery_state.did_normal_push(); + Some(list_arc) + } else { + None + } + } else { + None + } + } + + pub(crate) fn update_refcount_locked( + self: &DArc<Node>, + inc: bool, + strong: bool, + count: usize, + owner_inner: &mut ProcessInner, + ) -> Option<DLArc<Node>> { + let is_dead = owner_inner.is_dead; + let inner = self.inner.access_mut(owner_inner); + + // Get a reference to the state we'll update. + let state = if strong { + &mut inner.strong + } else { + &mut inner.weak + }; + + // Update the count and determine whether we need to push work. + let need_push = if inc { + state.count += count; + // TODO: This method shouldn't be used for zero-to-one increments. + !is_dead && !state.has_count + } else { + if state.count < count { + pr_err!("Failure: refcount underflow!"); + return None; + } + state.count -= count; + !is_dead && state.count == 0 && state.has_count + }; + + if need_push && inner.delivery_state.should_normal_push() { + let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap(); + inner.delivery_state.did_normal_push(); + Some(list_arc) + } else { + None + } + } + + pub(crate) fn incr_refcount_allow_zero2one( + self: &DArc<Self>, + strong: bool, + owner_inner: &mut ProcessInner, + ) -> Result<Option<DLArc<Node>>, CouldNotDeliverCriticalIncrement> { + let is_dead = owner_inner.is_dead; + let inner = self.inner.access_mut(owner_inner); + + // Get a reference to the state we'll update. + let state = if strong { + &mut inner.strong + } else { + &mut inner.weak + }; + + // Update the count and determine whether we need to push work. + state.count += 1; + if is_dead || state.has_count { + return Ok(None); + } + + // Userspace needs to be notified of this. + if !strong && inner.delivery_state.should_push_weak_zero2one() { + assert!(inner.delivery_state.can_push_weak_zero2one_normally()); + let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap(); + inner.delivery_state.did_push_weak_zero2one(); + Ok(Some(list_arc)) + } else if strong && inner.delivery_state.should_push_strong_zero2one() { + if inner.delivery_state.can_push_strong_zero2one_normally() { + let list_arc = ListArc::try_from_arc(self.clone()).ok().unwrap(); + inner.delivery_state.did_push_strong_zero2one(); + Ok(Some(list_arc)) + } else { + state.count -= 1; + Err(CouldNotDeliverCriticalIncrement) + } + } else { + // Work is already pushed, and we don't need to push again. + Ok(None) + } + } + + pub(crate) fn incr_refcount_allow_zero2one_with_wrapper( + self: &DArc<Self>, + strong: bool, + wrapper: CritIncrWrapper, + owner_inner: &mut ProcessInner, + ) -> Option<DLArc<dyn DeliverToRead>> { + match self.incr_refcount_allow_zero2one(strong, owner_inner) { + Ok(Some(node)) => Some(node as _), + Ok(None) => None, + Err(CouldNotDeliverCriticalIncrement) => { + assert!(strong); + let inner = self.inner.access_mut(owner_inner); + inner.strong.count += 1; + inner.delivery_state.did_push_strong_zero2one_wrapper(); + Some(wrapper.init(self.clone())) + } + } + } + + pub(crate) fn update_refcount(self: &DArc<Self>, inc: bool, count: usize, strong: bool) { + self.owner + .inner + .lock() + .update_node_refcount(self, inc, strong, count, None); + } + + pub(crate) fn populate_counts( + &self, + out: &mut BinderNodeInfoForRef, + guard: &Guard<'_, ProcessInner, SpinLockBackend>, + ) { + let inner = self.inner.access(guard); + out.strong_count = inner.strong.count as _; + out.weak_count = inner.weak.count as _; + } + + pub(crate) fn populate_debug_info( + &self, + out: &mut BinderNodeDebugInfo, + guard: &Guard<'_, ProcessInner, SpinLockBackend>, + ) { + out.ptr = self.ptr as _; + out.cookie = self.cookie as _; + let inner = self.inner.access(guard); + if inner.strong.has_count { + out.has_strong_ref = 1; + } + if inner.weak.has_count { + out.has_weak_ref = 1; + } + } + + pub(crate) fn force_has_count(&self, guard: &mut Guard<'_, ProcessInner, SpinLockBackend>) { + let inner = self.inner.access_mut(guard); + inner.strong.has_count = true; + inner.weak.has_count = true; + } + + fn write(&self, writer: &mut BinderReturnWriter<'_>, code: u32) -> Result { + writer.write_code(code)?; + writer.write_payload(&self.ptr)?; + writer.write_payload(&self.cookie)?; + Ok(()) + } + + pub(crate) fn submit_oneway( + &self, + transaction: DLArc<Transaction>, + guard: &mut Guard<'_, ProcessInner, SpinLockBackend>, + ) -> Result<(), (BinderError, DLArc<dyn DeliverToRead>)> { + if guard.is_dead { + return Err((BinderError::new_dead(), transaction)); + } + + let inner = self.inner.access_mut(guard); + if inner.has_oneway_transaction { + inner.oneway_todo.push_back(transaction); + } else { + inner.has_oneway_transaction = true; + guard.push_work(transaction)?; + } + Ok(()) + } + + pub(crate) fn release(&self) { + let mut guard = self.owner.inner.lock(); + while let Some(work) = self.inner.access_mut(&mut guard).oneway_todo.pop_front() { + drop(guard); + work.into_arc().cancel(); + guard = self.owner.inner.lock(); + } + + let death_list = core::mem::take(&mut self.inner.access_mut(&mut guard).death_list); + drop(guard); + for death in death_list { + death.into_arc().set_dead(); + } + } + + pub(crate) fn pending_oneway_finished(&self) { + let mut guard = self.owner.inner.lock(); + if guard.is_dead { + // Cleanup will happen in `Process::deferred_release`. + return; + } + + let inner = self.inner.access_mut(&mut guard); + + let transaction = inner.oneway_todo.pop_front(); + inner.has_oneway_transaction = transaction.is_some(); + if let Some(transaction) = transaction { + match guard.push_work(transaction) { + Ok(()) => {} + Err((_err, work)) => { + // Process is dead. + // This shouldn't happen due to the `is_dead` check, but if it does, just drop + // the transaction and return. + drop(guard); + drop(work); + } + } + } + } + + /// Finds an outdated transaction that the given transaction can replace. + /// + /// If one is found, it is removed from the list and returned. + pub(crate) fn take_outdated_transaction( + &self, + new: &Transaction, + guard: &mut Guard<'_, ProcessInner, SpinLockBackend>, + ) -> Option<DLArc<Transaction>> { + let inner = self.inner.access_mut(guard); + let mut cursor = inner.oneway_todo.cursor_front(); + while let Some(next) = cursor.peek_next() { + if new.can_replace(&next) { + return Some(next.remove()); + } + cursor.move_next(); + } + None + } + + /// This is split into a separate function since it's called by both `Node::do_work` and + /// `NodeWrapper::do_work`. + fn do_work_locked( + &self, + writer: &mut BinderReturnWriter<'_>, + mut guard: Guard<'_, ProcessInner, SpinLockBackend>, + ) -> Result<bool> { + let inner = self.inner.access_mut(&mut guard); + let strong = inner.strong.count > 0; + let has_strong = inner.strong.has_count; + let weak = strong || inner.weak.count > 0; + let has_weak = inner.weak.has_count; + + if weak && !has_weak { + inner.weak.has_count = true; + inner.active_inc_refs += 1; + } + + if strong && !has_strong { + inner.strong.has_count = true; + inner.active_inc_refs += 1; + } + + let no_active_inc_refs = inner.active_inc_refs == 0; + let should_drop_weak = no_active_inc_refs && (!weak && has_weak); + let should_drop_strong = no_active_inc_refs && (!strong && has_strong); + if should_drop_weak { + inner.weak.has_count = false; + } + if should_drop_strong { + inner.strong.has_count = false; + } + if no_active_inc_refs && !weak { + // Remove the node if there are no references to it. + guard.remove_node(self.ptr); + } + drop(guard); + + if weak && !has_weak { + self.write(writer, BR_INCREFS)?; + } + if strong && !has_strong { + self.write(writer, BR_ACQUIRE)?; + } + if should_drop_strong { + self.write(writer, BR_RELEASE)?; + } + if should_drop_weak { + self.write(writer, BR_DECREFS)?; + } + + Ok(true) + } + + pub(crate) fn add_freeze_listener( + &self, + process: &Arc<Process>, + flags: kernel::alloc::Flags, + ) -> Result { + let mut vec_alloc = KVVec::<Arc<Process>>::new(); + loop { + let mut guard = self.owner.inner.lock(); + // Do not check for `guard.dead`. The `dead` flag that matters here is the owner of the + // listener, no the target. + let inner = self.inner.access_mut(&mut guard); + let len = inner.freeze_list.len(); + if len >= inner.freeze_list.capacity() { + if len >= vec_alloc.capacity() { + drop(guard); + vec_alloc = KVVec::with_capacity((1 + len).next_power_of_two(), flags)?; + continue; + } + mem::swap(&mut inner.freeze_list, &mut vec_alloc); + for elem in vec_alloc.drain_all() { + inner.freeze_list.push_within_capacity(elem)?; + } + } + inner.freeze_list.push_within_capacity(process.clone())?; + return Ok(()); + } + } + + pub(crate) fn remove_freeze_listener(&self, p: &Arc<Process>) { + let _unused_capacity; + let mut guard = self.owner.inner.lock(); + let inner = self.inner.access_mut(&mut guard); + let len = inner.freeze_list.len(); + inner.freeze_list.retain(|proc| !Arc::ptr_eq(proc, p)); + if len == inner.freeze_list.len() { + pr_warn!( + "Could not remove freeze listener for {}\n", + p.pid_in_current_ns() + ); + } + if inner.freeze_list.is_empty() { + _unused_capacity = mem::replace(&mut inner.freeze_list, KVVec::new()); + } + } + + pub(crate) fn freeze_list<'a>(&'a self, guard: &'a ProcessInner) -> &'a [Arc<Process>] { + &self.inner.access(guard).freeze_list + } +} + +impl DeliverToRead for Node { + fn do_work( + self: DArc<Self>, + _thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let mut owner_inner = self.owner.inner.lock(); + let inner = self.inner.access_mut(&mut owner_inner); + + assert!(inner.delivery_state.has_pushed_node); + if inner.delivery_state.has_pushed_wrapper { + // If the wrapper is scheduled, then we are either a normal push or weak zero2one + // increment, and the wrapper is a strong zero2one increment, so the wrapper always + // takes precedence over us. + assert!(inner.delivery_state.has_strong_zero2one); + inner.delivery_state.has_pushed_node = false; + inner.delivery_state.has_weak_zero2one = false; + return Ok(true); + } + + inner.delivery_state.has_pushed_node = false; + inner.delivery_state.has_weak_zero2one = false; + inner.delivery_state.has_strong_zero2one = false; + + self.do_work_locked(writer, owner_inner) + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + #[inline(never)] + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + seq_print!( + m, + "{}node work {}: u{:016x} c{:016x}\n", + prefix, + self.debug_id, + self.ptr, + self.cookie, + ); + Ok(()) + } +} + +/// Represents something that holds one or more ref-counts to a `Node`. +/// +/// Whenever process A holds a refcount to a node owned by a different process B, then process A +/// will store a `NodeRef` that refers to the `Node` in process B. When process A releases the +/// refcount, we destroy the NodeRef, which decrements the ref-count in process A. +/// +/// This type is also used for some other cases. For example, a transaction allocation holds a +/// refcount on the target node, and this is implemented by storing a `NodeRef` in the allocation +/// so that the destructor of the allocation will drop a refcount of the `Node`. +pub(crate) struct NodeRef { + pub(crate) node: DArc<Node>, + /// How many times does this NodeRef hold a refcount on the Node? + strong_node_count: usize, + weak_node_count: usize, + /// How many times does userspace hold a refcount on this NodeRef? + strong_count: usize, + weak_count: usize, +} + +impl NodeRef { + pub(crate) fn new(node: DArc<Node>, strong_count: usize, weak_count: usize) -> Self { + Self { + node, + strong_node_count: strong_count, + weak_node_count: weak_count, + strong_count, + weak_count, + } + } + + pub(crate) fn absorb(&mut self, mut other: Self) { + assert!( + Arc::ptr_eq(&self.node, &other.node), + "absorb called with differing nodes" + ); + self.strong_node_count += other.strong_node_count; + self.weak_node_count += other.weak_node_count; + self.strong_count += other.strong_count; + self.weak_count += other.weak_count; + other.strong_count = 0; + other.weak_count = 0; + other.strong_node_count = 0; + other.weak_node_count = 0; + + if self.strong_node_count >= 2 || self.weak_node_count >= 2 { + let mut guard = self.node.owner.inner.lock(); + let inner = self.node.inner.access_mut(&mut guard); + + if self.strong_node_count >= 2 { + inner.strong.count -= self.strong_node_count - 1; + self.strong_node_count = 1; + assert_ne!(inner.strong.count, 0); + } + if self.weak_node_count >= 2 { + inner.weak.count -= self.weak_node_count - 1; + self.weak_node_count = 1; + assert_ne!(inner.weak.count, 0); + } + } + } + + pub(crate) fn get_count(&self) -> (usize, usize) { + (self.strong_count, self.weak_count) + } + + pub(crate) fn clone(&self, strong: bool) -> Result<NodeRef> { + if strong && self.strong_count == 0 { + return Err(EINVAL); + } + Ok(self + .node + .owner + .inner + .lock() + .new_node_ref(self.node.clone(), strong, None)) + } + + /// Updates (increments or decrements) the number of references held against the node. If the + /// count being updated transitions from 0 to 1 or from 1 to 0, the node is notified by having + /// its `update_refcount` function called. + /// + /// Returns whether `self` should be removed (when both counts are zero). + pub(crate) fn update(&mut self, inc: bool, strong: bool) -> bool { + if strong && self.strong_count == 0 { + return false; + } + let (count, node_count, other_count) = if strong { + ( + &mut self.strong_count, + &mut self.strong_node_count, + self.weak_count, + ) + } else { + ( + &mut self.weak_count, + &mut self.weak_node_count, + self.strong_count, + ) + }; + if inc { + if *count == 0 { + *node_count = 1; + self.node.update_refcount(true, 1, strong); + } + *count += 1; + } else { + if *count == 0 { + pr_warn!( + "pid {} performed invalid decrement on ref\n", + kernel::current!().pid() + ); + return false; + } + *count -= 1; + if *count == 0 { + self.node.update_refcount(false, *node_count, strong); + *node_count = 0; + return other_count == 0; + } + } + false + } +} + +impl Drop for NodeRef { + // This destructor is called conditionally from `Allocation::drop`. That branch is often + // mispredicted. Inlining this method call reduces the cost of those branch mispredictions. + #[inline(always)] + fn drop(&mut self) { + if self.strong_node_count > 0 { + self.node + .update_refcount(false, self.strong_node_count, true); + } + if self.weak_node_count > 0 { + self.node + .update_refcount(false, self.weak_node_count, false); + } + } +} + +struct NodeDeathInner { + dead: bool, + cleared: bool, + notification_done: bool, + /// Indicates whether the normal flow was interrupted by removing the handle. In this case, we + /// need behave as if the death notification didn't exist (i.e., we don't deliver anything to + /// the user. + aborted: bool, +} + +/// Used to deliver notifications when a process dies. +/// +/// A process can request to be notified when a process dies using `BC_REQUEST_DEATH_NOTIFICATION`. +/// This will make the driver send a `BR_DEAD_BINDER` to userspace when the process dies (or +/// immediately if it is already dead). Userspace is supposed to respond with `BC_DEAD_BINDER_DONE` +/// once it has processed the notification. +/// +/// Userspace can unregister from death notifications using the `BC_CLEAR_DEATH_NOTIFICATION` +/// command. In this case, the kernel will respond with `BR_CLEAR_DEATH_NOTIFICATION_DONE` once the +/// notification has been removed. Note that if the remote process dies before the kernel has +/// responded with `BR_CLEAR_DEATH_NOTIFICATION_DONE`, then the kernel will still send a +/// `BR_DEAD_BINDER`, which userspace must be able to process. In this case, the kernel will wait +/// for the `BC_DEAD_BINDER_DONE` command before it sends `BR_CLEAR_DEATH_NOTIFICATION_DONE`. +/// +/// Note that even if the kernel sends a `BR_DEAD_BINDER`, this does not remove the death +/// notification. Userspace must still remove it manually using `BC_CLEAR_DEATH_NOTIFICATION`. +/// +/// If a process uses `BC_RELEASE` to destroy its last refcount on a node that has an active death +/// registration, then the death registration is immediately deleted (we implement this using the +/// `aborted` field). However, userspace is not supposed to delete a `NodeRef` without first +/// deregistering death notifications, so this codepath is not executed under normal circumstances. +#[pin_data] +pub(crate) struct NodeDeath { + node: DArc<Node>, + process: Arc<Process>, + pub(crate) cookie: u64, + #[pin] + links_track: AtomicTracker<0>, + /// Used by the owner `Node` to store a list of registered death notifications. + /// + /// # Invariants + /// + /// Only ever used with the `death_list` list of `self.node`. + #[pin] + death_links: ListLinks<1>, + /// Used by the process to keep track of the death notifications for which we have sent a + /// `BR_DEAD_BINDER` but not yet received a `BC_DEAD_BINDER_DONE`. + /// + /// # Invariants + /// + /// Only ever used with the `delivered_deaths` list of `self.process`. + #[pin] + delivered_links: ListLinks<2>, + #[pin] + delivered_links_track: AtomicTracker<2>, + #[pin] + inner: SpinLock<NodeDeathInner>, +} + +impl NodeDeath { + /// Constructs a new node death notification object. + pub(crate) fn new( + node: DArc<Node>, + process: Arc<Process>, + cookie: u64, + ) -> impl PinInit<DTRWrap<Self>> { + DTRWrap::new(pin_init!( + Self { + node, + process, + cookie, + links_track <- AtomicTracker::new(), + death_links <- ListLinks::new(), + delivered_links <- ListLinks::new(), + delivered_links_track <- AtomicTracker::new(), + inner <- kernel::new_spinlock!(NodeDeathInner { + dead: false, + cleared: false, + notification_done: false, + aborted: false, + }, "NodeDeath::inner"), + } + )) + } + + /// Sets the cleared flag to `true`. + /// + /// It removes `self` from the node's death notification list if needed. + /// + /// Returns whether it needs to be queued. + pub(crate) fn set_cleared(self: &DArc<Self>, abort: bool) -> bool { + let (needs_removal, needs_queueing) = { + // Update state and determine if we need to queue a work item. We only need to do it + // when the node is not dead or if the user already completed the death notification. + let mut inner = self.inner.lock(); + if abort { + inner.aborted = true; + } + if inner.cleared { + // Already cleared. + return false; + } + inner.cleared = true; + (!inner.dead, !inner.dead || inner.notification_done) + }; + + // Remove death notification from node. + if needs_removal { + let mut owner_inner = self.node.owner.inner.lock(); + let node_inner = self.node.inner.access_mut(&mut owner_inner); + // SAFETY: A `NodeDeath` is never inserted into the death list of any node other than + // its owner, so it is either in this death list or in no death list. + unsafe { node_inner.death_list.remove(self) }; + } + needs_queueing + } + + /// Sets the 'notification done' flag to `true`. + pub(crate) fn set_notification_done(self: DArc<Self>, thread: &Thread) { + let needs_queueing = { + let mut inner = self.inner.lock(); + inner.notification_done = true; + inner.cleared + }; + if needs_queueing { + if let Some(death) = ListArc::try_from_arc_or_drop(self) { + let _ = thread.push_work_if_looper(death); + } + } + } + + /// Sets the 'dead' flag to `true` and queues work item if needed. + pub(crate) fn set_dead(self: DArc<Self>) { + let needs_queueing = { + let mut inner = self.inner.lock(); + if inner.cleared { + false + } else { + inner.dead = true; + true + } + }; + if needs_queueing { + // Push the death notification to the target process. There is nothing else to do if + // it's already dead. + if let Some(death) = ListArc::try_from_arc_or_drop(self) { + let process = death.process.clone(); + let _ = process.push_work(death); + } + } + } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for NodeDeath { + tracked_by links_track: AtomicTracker; + } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<1> for DTRWrap<NodeDeath> { untracked; } +} +kernel::list::impl_list_item! { + impl ListItem<1> for DTRWrap<NodeDeath> { + using ListLinks { self.wrapped.death_links }; + } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<2> for DTRWrap<NodeDeath> { + tracked_by wrapped: NodeDeath; + } +} +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<2> for NodeDeath { + tracked_by delivered_links_track: AtomicTracker<2>; + } +} +kernel::list::impl_list_item! { + impl ListItem<2> for DTRWrap<NodeDeath> { + using ListLinks { self.wrapped.delivered_links }; + } +} + +impl DeliverToRead for NodeDeath { + fn do_work( + self: DArc<Self>, + _thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let done = { + let inner = self.inner.lock(); + if inner.aborted { + return Ok(true); + } + inner.cleared && (!inner.dead || inner.notification_done) + }; + + let cookie = self.cookie; + let cmd = if done { + BR_CLEAR_DEATH_NOTIFICATION_DONE + } else { + let process = self.process.clone(); + let mut process_inner = process.inner.lock(); + let inner = self.inner.lock(); + if inner.aborted { + return Ok(true); + } + // We're still holding the inner lock, so it cannot be aborted while we insert it into + // the delivered list. + process_inner.death_delivered(self.clone()); + BR_DEAD_BINDER + }; + + writer.write_code(cmd)?; + writer.write_payload(&cookie)?; + // DEAD_BINDER notifications can cause transactions, so stop processing work items when we + // get to a death notification. + Ok(cmd != BR_DEAD_BINDER) + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + #[inline(never)] + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + let inner = self.inner.lock(); + + let dead_binder = inner.dead && !inner.notification_done; + + if dead_binder { + if inner.cleared { + seq_print!(m, "{}has cleared dead binder\n", prefix); + } else { + seq_print!(m, "{}has dead binder\n", prefix); + } + } else { + seq_print!(m, "{}has cleared death notification\n", prefix); + } + + Ok(()) + } +} diff --git a/drivers/android/binder/node/wrapper.rs b/drivers/android/binder/node/wrapper.rs new file mode 100644 index 000000000000..43294c050502 --- /dev/null +++ b/drivers/android/binder/node/wrapper.rs @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{list::ListArc, prelude::*, seq_file::SeqFile, seq_print, sync::UniqueArc}; + +use crate::{node::Node, thread::Thread, BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead}; + +use core::mem::MaybeUninit; + +pub(crate) struct CritIncrWrapper { + inner: UniqueArc<MaybeUninit<DTRWrap<NodeWrapper>>>, +} + +impl CritIncrWrapper { + pub(crate) fn new() -> Result<Self> { + Ok(CritIncrWrapper { + inner: UniqueArc::new_uninit(GFP_KERNEL)?, + }) + } + + pub(super) fn init(self, node: DArc<Node>) -> DLArc<dyn DeliverToRead> { + match self.inner.pin_init_with(DTRWrap::new(NodeWrapper { node })) { + Ok(initialized) => ListArc::from(initialized) as _, + Err(err) => match err {}, + } + } +} + +struct NodeWrapper { + node: DArc<Node>, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for NodeWrapper { + untracked; + } +} + +impl DeliverToRead for NodeWrapper { + fn do_work( + self: DArc<Self>, + _thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let node = &self.node; + let mut owner_inner = node.owner.inner.lock(); + let inner = node.inner.access_mut(&mut owner_inner); + + let ds = &mut inner.delivery_state; + + assert!(ds.has_pushed_wrapper); + assert!(ds.has_strong_zero2one); + ds.has_pushed_wrapper = false; + ds.has_strong_zero2one = false; + + node.do_work_locked(writer, owner_inner) + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + #[inline(never)] + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + seq_print!( + m, + "{}node work {}: u{:016x} c{:016x}\n", + prefix, + self.node.debug_id, + self.node.ptr, + self.node.cookie, + ); + Ok(()) + } +} diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs new file mode 100644 index 000000000000..9379038f61f5 --- /dev/null +++ b/drivers/android/binder/page_range.rs @@ -0,0 +1,734 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! This module has utilities for managing a page range where unused pages may be reclaimed by a +//! vma shrinker. + +// To avoid deadlocks, locks are taken in the order: +// +// 1. mmap lock +// 2. spinlock +// 3. lru spinlock +// +// The shrinker will use trylock methods because it locks them in a different order. + +use core::{ + marker::PhantomPinned, + mem::{size_of, size_of_val, MaybeUninit}, + ptr, +}; + +use kernel::{ + bindings, + error::Result, + ffi::{c_ulong, c_void}, + mm::{virt, Mm, MmWithUser}, + new_mutex, new_spinlock, + page::{Page, PAGE_SHIFT, PAGE_SIZE}, + prelude::*, + str::CStr, + sync::{aref::ARef, Mutex, SpinLock}, + task::Pid, + transmute::FromBytes, + types::Opaque, + uaccess::UserSliceReader, +}; + +/// Represents a shrinker that can be registered with the kernel. +/// +/// Each shrinker can be used by many `ShrinkablePageRange` objects. +#[repr(C)] +pub(crate) struct Shrinker { + inner: Opaque<*mut bindings::shrinker>, + list_lru: Opaque<bindings::list_lru>, +} + +// SAFETY: The shrinker and list_lru are thread safe. +unsafe impl Send for Shrinker {} +// SAFETY: The shrinker and list_lru are thread safe. +unsafe impl Sync for Shrinker {} + +impl Shrinker { + /// Create a new shrinker. + /// + /// # Safety + /// + /// Before using this shrinker with a `ShrinkablePageRange`, the `register` method must have + /// been called exactly once, and it must not have returned an error. + pub(crate) const unsafe fn new() -> Self { + Self { + inner: Opaque::uninit(), + list_lru: Opaque::uninit(), + } + } + + /// Register this shrinker with the kernel. + pub(crate) fn register(&'static self, name: &CStr) -> Result<()> { + // SAFETY: These fields are not yet used, so it's okay to zero them. + unsafe { + self.inner.get().write(ptr::null_mut()); + self.list_lru.get().write_bytes(0, 1); + } + + // SAFETY: The field is not yet used, so we can initialize it. + let ret = unsafe { bindings::__list_lru_init(self.list_lru.get(), false, ptr::null_mut()) }; + if ret != 0 { + return Err(Error::from_errno(ret)); + } + + // SAFETY: The `name` points at a valid c string. + let shrinker = unsafe { bindings::shrinker_alloc(0, name.as_char_ptr()) }; + if shrinker.is_null() { + // SAFETY: We initialized it, so its okay to destroy it. + unsafe { bindings::list_lru_destroy(self.list_lru.get()) }; + return Err(Error::from_errno(ret)); + } + + // SAFETY: We're about to register the shrinker, and these are the fields we need to + // initialize. (All other fields are already zeroed.) + unsafe { + (&raw mut (*shrinker).count_objects).write(Some(rust_shrink_count)); + (&raw mut (*shrinker).scan_objects).write(Some(rust_shrink_scan)); + (&raw mut (*shrinker).private_data).write(self.list_lru.get().cast()); + } + + // SAFETY: The new shrinker has been fully initialized, so we can register it. + unsafe { bindings::shrinker_register(shrinker) }; + + // SAFETY: This initializes the pointer to the shrinker so that we can use it. + unsafe { self.inner.get().write(shrinker) }; + + Ok(()) + } +} + +/// A container that manages a page range in a vma. +/// +/// The pages can be thought of as an array of booleans of whether the pages are usable. The +/// methods `use_range` and `stop_using_range` set all booleans in a range to true or false +/// respectively. Initially, no pages are allocated. When a page is not used, it is not freed +/// immediately. Instead, it is made available to the memory shrinker to free it if the device is +/// under memory pressure. +/// +/// It's okay for `use_range` and `stop_using_range` to race with each other, although there's no +/// way to know whether an index ends up with true or false if a call to `use_range` races with +/// another call to `stop_using_range` on a given index. +/// +/// It's also okay for the two methods to race with themselves, e.g. if two threads call +/// `use_range` on the same index, then that's fine and neither call will return until the page is +/// allocated and mapped. +/// +/// The methods that read or write to a range require that the page is marked as in use. So it is +/// _not_ okay to call `stop_using_range` on a page that is in use by the methods that read or +/// write to the page. +#[pin_data(PinnedDrop)] +pub(crate) struct ShrinkablePageRange { + /// Shrinker object registered with the kernel. + shrinker: &'static Shrinker, + /// Pid using this page range. Only used as debugging information. + pid: Pid, + /// The mm for the relevant process. + mm: ARef<Mm>, + /// Used to synchronize calls to `vm_insert_page` and `zap_page_range_single`. + #[pin] + mm_lock: Mutex<()>, + /// Spinlock protecting changes to pages. + #[pin] + lock: SpinLock<Inner>, + + /// Must not move, since page info has pointers back. + #[pin] + _pin: PhantomPinned, +} + +struct Inner { + /// Array of pages. + /// + /// Since this is also accessed by the shrinker, we can't use a `Box`, which asserts exclusive + /// ownership. To deal with that, we manage it using raw pointers. + pages: *mut PageInfo, + /// Length of the `pages` array. + size: usize, + /// The address of the vma to insert the pages into. + vma_addr: usize, +} + +// SAFETY: proper locking is in place for `Inner` +unsafe impl Send for Inner {} + +type StableMmGuard = + kernel::sync::lock::Guard<'static, (), kernel::sync::lock::mutex::MutexBackend>; + +/// An array element that describes the current state of a page. +/// +/// There are three states: +/// +/// * Free. The page is None. The `lru` element is not queued. +/// * Available. The page is Some. The `lru` element is queued to the shrinker's lru. +/// * Used. The page is Some. The `lru` element is not queued. +/// +/// When an element is available, the shrinker is able to free the page. +#[repr(C)] +struct PageInfo { + lru: bindings::list_head, + page: Option<Page>, + range: *const ShrinkablePageRange, +} + +impl PageInfo { + /// # Safety + /// + /// The caller ensures that writing to `me.page` is ok, and that the page is not currently set. + unsafe fn set_page(me: *mut PageInfo, page: Page) { + // SAFETY: This pointer offset is in bounds. + let ptr = unsafe { &raw mut (*me).page }; + + // SAFETY: The pointer is valid for writing, so also valid for reading. + if unsafe { (*ptr).is_some() } { + pr_err!("set_page called when there is already a page"); + // SAFETY: We will initialize the page again below. + unsafe { ptr::drop_in_place(ptr) }; + } + + // SAFETY: The pointer is valid for writing. + unsafe { ptr::write(ptr, Some(page)) }; + } + + /// # Safety + /// + /// The caller ensures that reading from `me.page` is ok for the duration of 'a. + unsafe fn get_page<'a>(me: *const PageInfo) -> Option<&'a Page> { + // SAFETY: This pointer offset is in bounds. + let ptr = unsafe { &raw const (*me).page }; + + // SAFETY: The pointer is valid for reading. + unsafe { (*ptr).as_ref() } + } + + /// # Safety + /// + /// The caller ensures that writing to `me.page` is ok for the duration of 'a. + unsafe fn take_page(me: *mut PageInfo) -> Option<Page> { + // SAFETY: This pointer offset is in bounds. + let ptr = unsafe { &raw mut (*me).page }; + + // SAFETY: The pointer is valid for reading. + unsafe { (*ptr).take() } + } + + /// Add this page to the lru list, if not already in the list. + /// + /// # Safety + /// + /// The pointer must be valid, and it must be the right shrinker and nid. + unsafe fn list_lru_add(me: *mut PageInfo, nid: i32, shrinker: &'static Shrinker) { + // SAFETY: This pointer offset is in bounds. + let lru_ptr = unsafe { &raw mut (*me).lru }; + // SAFETY: The lru pointer is valid, and we're not using it with any other lru list. + unsafe { bindings::list_lru_add(shrinker.list_lru.get(), lru_ptr, nid, ptr::null_mut()) }; + } + + /// Remove this page from the lru list, if it is in the list. + /// + /// # Safety + /// + /// The pointer must be valid, and it must be the right shrinker and nid. + unsafe fn list_lru_del(me: *mut PageInfo, nid: i32, shrinker: &'static Shrinker) { + // SAFETY: This pointer offset is in bounds. + let lru_ptr = unsafe { &raw mut (*me).lru }; + // SAFETY: The lru pointer is valid, and we're not using it with any other lru list. + unsafe { bindings::list_lru_del(shrinker.list_lru.get(), lru_ptr, nid, ptr::null_mut()) }; + } +} + +impl ShrinkablePageRange { + /// Create a new `ShrinkablePageRange` using the given shrinker. + pub(crate) fn new(shrinker: &'static Shrinker) -> impl PinInit<Self, Error> { + try_pin_init!(Self { + shrinker, + pid: kernel::current!().pid(), + mm: ARef::from(&**kernel::current!().mm().ok_or(ESRCH)?), + mm_lock <- new_mutex!((), "ShrinkablePageRange::mm"), + lock <- new_spinlock!(Inner { + pages: ptr::null_mut(), + size: 0, + vma_addr: 0, + }, "ShrinkablePageRange"), + _pin: PhantomPinned, + }) + } + + pub(crate) fn stable_trylock_mm(&self) -> Option<StableMmGuard> { + // SAFETY: This extends the duration of the reference. Since this call happens before + // `mm_lock` is taken in the destructor of `ShrinkablePageRange`, the destructor will block + // until the returned guard is dropped. This ensures that the guard is valid until dropped. + let mm_lock = unsafe { &*ptr::from_ref(&self.mm_lock) }; + + mm_lock.try_lock() + } + + /// Register a vma with this page range. Returns the size of the region. + pub(crate) fn register_with_vma(&self, vma: &virt::VmaNew) -> Result<usize> { + let num_bytes = usize::min(vma.end() - vma.start(), bindings::SZ_4M as usize); + let num_pages = num_bytes >> PAGE_SHIFT; + + if !ptr::eq::<Mm>(&*self.mm, &**vma.mm()) { + pr_debug!("Failed to register with vma: invalid vma->vm_mm"); + return Err(EINVAL); + } + if num_pages == 0 { + pr_debug!("Failed to register with vma: size zero"); + return Err(EINVAL); + } + + let mut pages = KVVec::<PageInfo>::with_capacity(num_pages, GFP_KERNEL)?; + + // SAFETY: This just initializes the pages array. + unsafe { + let self_ptr = self as *const ShrinkablePageRange; + for i in 0..num_pages { + let info = pages.as_mut_ptr().add(i); + (&raw mut (*info).range).write(self_ptr); + (&raw mut (*info).page).write(None); + let lru = &raw mut (*info).lru; + (&raw mut (*lru).next).write(lru); + (&raw mut (*lru).prev).write(lru); + } + } + + let mut inner = self.lock.lock(); + if inner.size > 0 { + pr_debug!("Failed to register with vma: already registered"); + drop(inner); + return Err(EBUSY); + } + + inner.pages = pages.into_raw_parts().0; + inner.size = num_pages; + inner.vma_addr = vma.start(); + + Ok(num_pages) + } + + /// Make sure that the given pages are allocated and mapped. + /// + /// Must not be called from an atomic context. + pub(crate) fn use_range(&self, start: usize, end: usize) -> Result<()> { + if start >= end { + return Ok(()); + } + let mut inner = self.lock.lock(); + assert!(end <= inner.size); + + for i in start..end { + // SAFETY: This pointer offset is in bounds. + let page_info = unsafe { inner.pages.add(i) }; + + // SAFETY: The pointer is valid, and we hold the lock so reading from the page is okay. + if let Some(page) = unsafe { PageInfo::get_page(page_info) } { + // Since we're going to use the page, we should remove it from the lru list so that + // the shrinker will not free it. + // + // SAFETY: The pointer is valid, and this is the right shrinker. + // + // The shrinker can't free the page between the check and this call to + // `list_lru_del` because we hold the lock. + unsafe { PageInfo::list_lru_del(page_info, page.nid(), self.shrinker) }; + } else { + // We have to allocate a new page. Use the slow path. + drop(inner); + // SAFETY: `i < end <= inner.size` so `i` is in bounds. + match unsafe { self.use_page_slow(i) } { + Ok(()) => {} + Err(err) => { + pr_warn!("Error in use_page_slow: {:?}", err); + return Err(err); + } + } + inner = self.lock.lock(); + } + } + Ok(()) + } + + /// Mark the given page as in use, slow path. + /// + /// Must not be called from an atomic context. + /// + /// # Safety + /// + /// Assumes that `i` is in bounds. + #[cold] + unsafe fn use_page_slow(&self, i: usize) -> Result<()> { + let new_page = Page::alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO)?; + + let mm_mutex = self.mm_lock.lock(); + let inner = self.lock.lock(); + + // SAFETY: This pointer offset is in bounds. + let page_info = unsafe { inner.pages.add(i) }; + + // SAFETY: The pointer is valid, and we hold the lock so reading from the page is okay. + if let Some(page) = unsafe { PageInfo::get_page(page_info) } { + // The page was already there, or someone else added the page while we didn't hold the + // spinlock. + // + // SAFETY: The pointer is valid, and this is the right shrinker. + // + // The shrinker can't free the page between the check and this call to + // `list_lru_del` because we hold the lock. + unsafe { PageInfo::list_lru_del(page_info, page.nid(), self.shrinker) }; + return Ok(()); + } + + let vma_addr = inner.vma_addr; + // Release the spinlock while we insert the page into the vma. + drop(inner); + + // No overflow since we stay in bounds of the vma. + let user_page_addr = vma_addr + (i << PAGE_SHIFT); + + // We use `mmput_async` when dropping the `mm` because `use_page_slow` is usually used from + // a remote process. If the call to `mmput` races with the process shutting down, then the + // caller of `use_page_slow` becomes responsible for cleaning up the `mm`, which doesn't + // happen until it returns to userspace. However, the caller might instead go to sleep and + // wait for the owner of the `mm` to wake it up, which doesn't happen because it's in the + // middle of a shutdown process that won't complete until the `mm` is dropped. This can + // amount to a deadlock. + // + // Using `mmput_async` avoids this, because then the `mm` cleanup is instead queued to a + // workqueue. + MmWithUser::into_mmput_async(self.mm.mmget_not_zero().ok_or(ESRCH)?) + .mmap_read_lock() + .vma_lookup(vma_addr) + .ok_or(ESRCH)? + .as_mixedmap_vma() + .ok_or(ESRCH)? + .vm_insert_page(user_page_addr, &new_page) + .inspect_err(|err| { + pr_warn!( + "Failed to vm_insert_page({}): vma_addr:{} i:{} err:{:?}", + user_page_addr, + vma_addr, + i, + err + ) + })?; + + let inner = self.lock.lock(); + + // SAFETY: The `page_info` pointer is valid and currently does not have a page. The page + // can be written to since we hold the lock. + // + // We released and reacquired the spinlock since we checked that the page is null, but we + // always hold the mm_lock mutex when setting the page to a non-null value, so it's not + // possible for someone else to have changed it since our check. + unsafe { PageInfo::set_page(page_info, new_page) }; + + drop(inner); + drop(mm_mutex); + + Ok(()) + } + + /// If the given page is in use, then mark it as available so that the shrinker can free it. + /// + /// May be called from an atomic context. + pub(crate) fn stop_using_range(&self, start: usize, end: usize) { + if start >= end { + return; + } + let inner = self.lock.lock(); + assert!(end <= inner.size); + + for i in (start..end).rev() { + // SAFETY: The pointer is in bounds. + let page_info = unsafe { inner.pages.add(i) }; + + // SAFETY: Okay for reading since we have the lock. + if let Some(page) = unsafe { PageInfo::get_page(page_info) } { + // SAFETY: The pointer is valid, and it's the right shrinker. + unsafe { PageInfo::list_lru_add(page_info, page.nid(), self.shrinker) }; + } + } + } + + /// Helper for reading or writing to a range of bytes that may overlap with several pages. + /// + /// # Safety + /// + /// All pages touched by this operation must be in use for the duration of this call. + unsafe fn iterate<T>(&self, mut offset: usize, mut size: usize, mut cb: T) -> Result + where + T: FnMut(&Page, usize, usize) -> Result, + { + if size == 0 { + return Ok(()); + } + + let (pages, num_pages) = { + let inner = self.lock.lock(); + (inner.pages, inner.size) + }; + let num_bytes = num_pages << PAGE_SHIFT; + + // Check that the request is within the buffer. + if offset.checked_add(size).ok_or(EFAULT)? > num_bytes { + return Err(EFAULT); + } + + let mut page_index = offset >> PAGE_SHIFT; + offset &= PAGE_SIZE - 1; + while size > 0 { + let available = usize::min(size, PAGE_SIZE - offset); + // SAFETY: The pointer is in bounds. + let page_info = unsafe { pages.add(page_index) }; + // SAFETY: The caller guarantees that this page is in the "in use" state for the + // duration of this call to `iterate`, so nobody will change the page. + let page = unsafe { PageInfo::get_page(page_info) }; + if page.is_none() { + pr_warn!("Page is null!"); + } + let page = page.ok_or(EFAULT)?; + cb(page, offset, available)?; + size -= available; + page_index += 1; + offset = 0; + } + Ok(()) + } + + /// Copy from userspace into this page range. + /// + /// # Safety + /// + /// All pages touched by this operation must be in use for the duration of this call. + pub(crate) unsafe fn copy_from_user_slice( + &self, + reader: &mut UserSliceReader, + offset: usize, + size: usize, + ) -> Result { + // SAFETY: `self.iterate` has the same safety requirements as `copy_from_user_slice`. + unsafe { + self.iterate(offset, size, |page, offset, to_copy| { + page.copy_from_user_slice_raw(reader, offset, to_copy) + }) + } + } + + /// Copy from this page range into kernel space. + /// + /// # Safety + /// + /// All pages touched by this operation must be in use for the duration of this call. + pub(crate) unsafe fn read<T: FromBytes>(&self, offset: usize) -> Result<T> { + let mut out = MaybeUninit::<T>::uninit(); + let mut out_offset = 0; + // SAFETY: `self.iterate` has the same safety requirements as `read`. + unsafe { + self.iterate(offset, size_of::<T>(), |page, offset, to_copy| { + // SAFETY: The sum of `offset` and `to_copy` is bounded by the size of T. + let obj_ptr = (out.as_mut_ptr() as *mut u8).add(out_offset); + // SAFETY: The pointer points is in-bounds of the `out` variable, so it is valid. + page.read_raw(obj_ptr, offset, to_copy)?; + out_offset += to_copy; + Ok(()) + })?; + } + // SAFETY: We just initialised the data. + Ok(unsafe { out.assume_init() }) + } + + /// Copy from kernel space into this page range. + /// + /// # Safety + /// + /// All pages touched by this operation must be in use for the duration of this call. + pub(crate) unsafe fn write<T: ?Sized>(&self, offset: usize, obj: &T) -> Result { + let mut obj_offset = 0; + // SAFETY: `self.iterate` has the same safety requirements as `write`. + unsafe { + self.iterate(offset, size_of_val(obj), |page, offset, to_copy| { + // SAFETY: The sum of `offset` and `to_copy` is bounded by the size of T. + let obj_ptr = (obj as *const T as *const u8).add(obj_offset); + // SAFETY: We have a reference to the object, so the pointer is valid. + page.write_raw(obj_ptr, offset, to_copy)?; + obj_offset += to_copy; + Ok(()) + }) + } + } + + /// Write zeroes to the given range. + /// + /// # Safety + /// + /// All pages touched by this operation must be in use for the duration of this call. + pub(crate) unsafe fn fill_zero(&self, offset: usize, size: usize) -> Result { + // SAFETY: `self.iterate` has the same safety requirements as `copy_into`. + unsafe { + self.iterate(offset, size, |page, offset, len| { + page.fill_zero_raw(offset, len) + }) + } + } +} + +#[pinned_drop] +impl PinnedDrop for ShrinkablePageRange { + fn drop(self: Pin<&mut Self>) { + let (pages, size) = { + let lock = self.lock.lock(); + (lock.pages, lock.size) + }; + + if size == 0 { + return; + } + + // Note: This call is also necessary for the safety of `stable_trylock_mm`. + let mm_lock = self.mm_lock.lock(); + + // This is the destructor, so unlike the other methods, we only need to worry about races + // with the shrinker here. Since we hold the `mm_lock`, we also can't race with the + // shrinker, and after this loop, the shrinker will not access any of our pages since we + // removed them from the lru list. + for i in 0..size { + // SAFETY: Loop is in-bounds of the size. + let p_ptr = unsafe { pages.add(i) }; + // SAFETY: No other readers, so we can read. + if let Some(p) = unsafe { PageInfo::get_page(p_ptr) } { + // SAFETY: The pointer is valid and it's the right shrinker. + unsafe { PageInfo::list_lru_del(p_ptr, p.nid(), self.shrinker) }; + } + } + + drop(mm_lock); + + // SAFETY: `pages` was allocated as an `KVVec<PageInfo>` with capacity `size`. Furthermore, + // all `size` elements are initialized. Also, the array is no longer shared with the + // shrinker due to the above loop. + drop(unsafe { KVVec::from_raw_parts(pages, size, size) }); + } +} + +/// # Safety +/// Called by the shrinker. +#[no_mangle] +unsafe extern "C" fn rust_shrink_count( + shrink: *mut bindings::shrinker, + _sc: *mut bindings::shrink_control, +) -> c_ulong { + // SAFETY: We can access our own private data. + let list_lru = unsafe { (*shrink).private_data.cast::<bindings::list_lru>() }; + // SAFETY: Accessing the lru list is okay. Just an FFI call. + unsafe { bindings::list_lru_count(list_lru) } +} + +/// # Safety +/// Called by the shrinker. +#[no_mangle] +unsafe extern "C" fn rust_shrink_scan( + shrink: *mut bindings::shrinker, + sc: *mut bindings::shrink_control, +) -> c_ulong { + // SAFETY: We can access our own private data. + let list_lru = unsafe { (*shrink).private_data.cast::<bindings::list_lru>() }; + // SAFETY: Caller guarantees that it is safe to read this field. + let nr_to_scan = unsafe { (*sc).nr_to_scan }; + // SAFETY: Accessing the lru list is okay. Just an FFI call. + unsafe { + bindings::list_lru_walk( + list_lru, + Some(bindings::rust_shrink_free_page_wrap), + ptr::null_mut(), + nr_to_scan, + ) + } +} + +const LRU_SKIP: bindings::lru_status = bindings::lru_status_LRU_SKIP; +const LRU_REMOVED_ENTRY: bindings::lru_status = bindings::lru_status_LRU_REMOVED_RETRY; + +/// # Safety +/// Called by the shrinker. +#[no_mangle] +unsafe extern "C" fn rust_shrink_free_page( + item: *mut bindings::list_head, + lru: *mut bindings::list_lru_one, + _cb_arg: *mut c_void, +) -> bindings::lru_status { + // Fields that should survive after unlocking the lru lock. + let page; + let page_index; + let mm; + let mmap_read; + let mm_mutex; + let vma_addr; + + { + // CAST: The `list_head` field is first in `PageInfo`. + let info = item as *mut PageInfo; + // SAFETY: The `range` field of `PageInfo` is immutable. + let range = unsafe { &*((*info).range) }; + + mm = match range.mm.mmget_not_zero() { + Some(mm) => MmWithUser::into_mmput_async(mm), + None => return LRU_SKIP, + }; + + mm_mutex = match range.stable_trylock_mm() { + Some(guard) => guard, + None => return LRU_SKIP, + }; + + mmap_read = match mm.mmap_read_trylock() { + Some(guard) => guard, + None => return LRU_SKIP, + }; + + // We can't lock it normally here, since we hold the lru lock. + let inner = match range.lock.try_lock() { + Some(inner) => inner, + None => return LRU_SKIP, + }; + + // SAFETY: The item is in this lru list, so it's okay to remove it. + unsafe { bindings::list_lru_isolate(lru, item) }; + + // SAFETY: Both pointers are in bounds of the same allocation. + page_index = unsafe { info.offset_from(inner.pages) } as usize; + + // SAFETY: We hold the spinlock, so we can take the page. + // + // This sets the page pointer to zero before we unmap it from the vma. However, we call + // `zap_page_range` before we release the mmap lock, so `use_page_slow` will not be able to + // insert a new page until after our call to `zap_page_range`. + page = unsafe { PageInfo::take_page(info) }; + vma_addr = inner.vma_addr; + + // From this point on, we don't access this PageInfo or ShrinkablePageRange again, because + // they can be freed at any point after we unlock `lru_lock`. This is with the exception of + // `mm_mutex` which is kept alive by holding the lock. + } + + // SAFETY: The lru lock is locked when this method is called. + unsafe { bindings::spin_unlock(&raw mut (*lru).lock) }; + + if let Some(vma) = mmap_read.vma_lookup(vma_addr) { + let user_page_addr = vma_addr + (page_index << PAGE_SHIFT); + vma.zap_page_range_single(user_page_addr, PAGE_SIZE); + } + + drop(mmap_read); + drop(mm_mutex); + drop(mm); + drop(page); + + // SAFETY: We just unlocked the lru lock, but it should be locked when we return. + unsafe { bindings::spin_lock(&raw mut (*lru).lock) }; + + LRU_REMOVED_ENTRY +} diff --git a/drivers/android/binder/page_range_helper.c b/drivers/android/binder/page_range_helper.c new file mode 100644 index 000000000000..496887723ee0 --- /dev/null +++ b/drivers/android/binder/page_range_helper.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* C helper for page_range.rs to work around a CFI violation. + * + * Bindgen currently pretends that `enum lru_status` is the same as an integer. + * This assumption is fine ABI-wise, but once you add CFI to the mix, it + * triggers a CFI violation because `enum lru_status` gets a different CFI tag. + * + * This file contains a workaround until bindgen can be fixed. + * + * Copyright (C) 2025 Google LLC. + */ +#include "page_range_helper.h" + +unsigned int rust_shrink_free_page(struct list_head *item, + struct list_lru_one *list, + void *cb_arg); + +enum lru_status +rust_shrink_free_page_wrap(struct list_head *item, struct list_lru_one *list, + void *cb_arg) +{ + return rust_shrink_free_page(item, list, cb_arg); +} diff --git a/drivers/android/binder/page_range_helper.h b/drivers/android/binder/page_range_helper.h new file mode 100644 index 000000000000..18dd2dd117b2 --- /dev/null +++ b/drivers/android/binder/page_range_helper.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Google, Inc. + */ + +#ifndef _LINUX_PAGE_RANGE_HELPER_H +#define _LINUX_PAGE_RANGE_HELPER_H + +#include <linux/list_lru.h> + +enum lru_status +rust_shrink_free_page_wrap(struct list_head *item, struct list_lru_one *list, + void *cb_arg); + +#endif /* _LINUX_PAGE_RANGE_HELPER_H */ diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs new file mode 100644 index 000000000000..f13a747e784c --- /dev/null +++ b/drivers/android/binder/process.rs @@ -0,0 +1,1696 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! This module defines the `Process` type, which represents a process using a particular binder +//! context. +//! +//! The `Process` object keeps track of all of the resources that this process owns in the binder +//! context. +//! +//! There is one `Process` object for each binder fd that a process has opened, so processes using +//! several binder contexts have several `Process` objects. This ensures that the contexts are +//! fully separated. + +use core::mem::take; + +use kernel::{ + bindings, + cred::Credential, + error::Error, + fs::file::{self, File}, + list::{List, ListArc, ListArcField, ListLinks}, + mm, + prelude::*, + rbtree::{self, RBTree, RBTreeNode, RBTreeNodeReservation}, + seq_file::SeqFile, + seq_print, + sync::poll::PollTable, + sync::{ + lock::{spinlock::SpinLockBackend, Guard}, + Arc, ArcBorrow, CondVar, CondVarTimeoutResult, Mutex, SpinLock, UniqueArc, + }, + task::Task, + types::ARef, + uaccess::{UserSlice, UserSliceReader}, + uapi, + workqueue::{self, Work}, +}; + +use crate::{ + allocation::{Allocation, AllocationInfo, NewAllocation}, + context::Context, + defs::*, + error::{BinderError, BinderResult}, + node::{CouldNotDeliverCriticalIncrement, CritIncrWrapper, Node, NodeDeath, NodeRef}, + page_range::ShrinkablePageRange, + range_alloc::{RangeAllocator, ReserveNew, ReserveNewArgs}, + stats::BinderStats, + thread::{PushWorkRes, Thread}, + BinderfsProcFile, DArc, DLArc, DTRWrap, DeliverToRead, +}; + +#[path = "freeze.rs"] +mod freeze; +use self::freeze::{FreezeCookie, FreezeListener}; + +struct Mapping { + address: usize, + alloc: RangeAllocator<AllocationInfo>, +} + +impl Mapping { + fn new(address: usize, size: usize) -> Self { + Self { + address, + alloc: RangeAllocator::new(size), + } + } +} + +// bitflags for defer_work. +const PROC_DEFER_FLUSH: u8 = 1; +const PROC_DEFER_RELEASE: u8 = 2; + +/// The fields of `Process` protected by the spinlock. +pub(crate) struct ProcessInner { + is_manager: bool, + pub(crate) is_dead: bool, + threads: RBTree<i32, Arc<Thread>>, + /// INVARIANT: Threads pushed to this list must be owned by this process. + ready_threads: List<Thread>, + nodes: RBTree<u64, DArc<Node>>, + mapping: Option<Mapping>, + work: List<DTRWrap<dyn DeliverToRead>>, + delivered_deaths: List<DTRWrap<NodeDeath>, 2>, + + /// The number of requested threads that haven't registered yet. + requested_thread_count: u32, + /// The maximum number of threads used by the process thread pool. + max_threads: u32, + /// The number of threads the started and registered with the thread pool. + started_thread_count: u32, + + /// Bitmap of deferred work to do. + defer_work: u8, + + /// Number of transactions to be transmitted before processes in freeze_wait + /// are woken up. + outstanding_txns: u32, + /// Process is frozen and unable to service binder transactions. + pub(crate) is_frozen: bool, + /// Process received sync transactions since last frozen. + pub(crate) sync_recv: bool, + /// Process received async transactions since last frozen. + pub(crate) async_recv: bool, + pub(crate) binderfs_file: Option<BinderfsProcFile>, + /// Check for oneway spam + oneway_spam_detection_enabled: bool, +} + +impl ProcessInner { + fn new() -> Self { + Self { + is_manager: false, + is_dead: false, + threads: RBTree::new(), + ready_threads: List::new(), + mapping: None, + nodes: RBTree::new(), + work: List::new(), + delivered_deaths: List::new(), + requested_thread_count: 0, + max_threads: 0, + started_thread_count: 0, + defer_work: 0, + outstanding_txns: 0, + is_frozen: false, + sync_recv: false, + async_recv: false, + binderfs_file: None, + oneway_spam_detection_enabled: false, + } + } + + /// Schedule the work item for execution on this process. + /// + /// If any threads are ready for work, then the work item is given directly to that thread and + /// it is woken up. Otherwise, it is pushed to the process work list. + /// + /// This call can fail only if the process is dead. In this case, the work item is returned to + /// the caller so that the caller can drop it after releasing the inner process lock. This is + /// necessary since the destructor of `Transaction` will take locks that can't necessarily be + /// taken while holding the inner process lock. + pub(crate) fn push_work( + &mut self, + work: DLArc<dyn DeliverToRead>, + ) -> Result<(), (BinderError, DLArc<dyn DeliverToRead>)> { + // Try to find a ready thread to which to push the work. + if let Some(thread) = self.ready_threads.pop_front() { + // Push to thread while holding state lock. This prevents the thread from giving up + // (for example, because of a signal) when we're about to deliver work. + match thread.push_work(work) { + PushWorkRes::Ok => Ok(()), + PushWorkRes::FailedDead(work) => Err((BinderError::new_dead(), work)), + } + } else if self.is_dead { + Err((BinderError::new_dead(), work)) + } else { + let sync = work.should_sync_wakeup(); + + // Didn't find a thread waiting for proc work; this can happen + // in two scenarios: + // 1. All threads are busy handling transactions + // In that case, one of those threads should call back into + // the kernel driver soon and pick up this work. + // 2. Threads are using the (e)poll interface, in which case + // they may be blocked on the waitqueue without having been + // added to waiting_threads. For this case, we just iterate + // over all threads not handling transaction work, and + // wake them all up. We wake all because we don't know whether + // a thread that called into (e)poll is handling non-binder + // work currently. + self.work.push_back(work); + + // Wake up polling threads, if any. + for thread in self.threads.values() { + thread.notify_if_poll_ready(sync); + } + + Ok(()) + } + } + + pub(crate) fn remove_node(&mut self, ptr: u64) { + self.nodes.remove(&ptr); + } + + /// Updates the reference count on the given node. + pub(crate) fn update_node_refcount( + &mut self, + node: &DArc<Node>, + inc: bool, + strong: bool, + count: usize, + othread: Option<&Thread>, + ) { + let push = node.update_refcount_locked(inc, strong, count, self); + + // If we decided that we need to push work, push either to the process or to a thread if + // one is specified. + if let Some(node) = push { + if let Some(thread) = othread { + thread.push_work_deferred(node); + } else { + let _ = self.push_work(node); + // Nothing to do: `push_work` may fail if the process is dead, but that's ok as in + // that case, it doesn't care about the notification. + } + } + } + + pub(crate) fn new_node_ref( + &mut self, + node: DArc<Node>, + strong: bool, + thread: Option<&Thread>, + ) -> NodeRef { + self.update_node_refcount(&node, true, strong, 1, thread); + let strong_count = if strong { 1 } else { 0 }; + NodeRef::new(node, strong_count, 1 - strong_count) + } + + pub(crate) fn new_node_ref_with_thread( + &mut self, + node: DArc<Node>, + strong: bool, + thread: &Thread, + wrapper: Option<CritIncrWrapper>, + ) -> Result<NodeRef, CouldNotDeliverCriticalIncrement> { + let push = match wrapper { + None => node + .incr_refcount_allow_zero2one(strong, self)? + .map(|node| node as _), + Some(wrapper) => node.incr_refcount_allow_zero2one_with_wrapper(strong, wrapper, self), + }; + if let Some(node) = push { + thread.push_work_deferred(node); + } + let strong_count = if strong { 1 } else { 0 }; + Ok(NodeRef::new(node, strong_count, 1 - strong_count)) + } + + /// Returns an existing node with the given pointer and cookie, if one exists. + /// + /// Returns an error if a node with the given pointer but a different cookie exists. + fn get_existing_node(&self, ptr: u64, cookie: u64) -> Result<Option<DArc<Node>>> { + match self.nodes.get(&ptr) { + None => Ok(None), + Some(node) => { + let (_, node_cookie) = node.get_id(); + if node_cookie == cookie { + Ok(Some(node.clone())) + } else { + Err(EINVAL) + } + } + } + } + + fn register_thread(&mut self) -> bool { + if self.requested_thread_count == 0 { + return false; + } + + self.requested_thread_count -= 1; + self.started_thread_count += 1; + true + } + + /// Finds a delivered death notification with the given cookie, removes it from the thread's + /// delivered list, and returns it. + fn pull_delivered_death(&mut self, cookie: u64) -> Option<DArc<NodeDeath>> { + let mut cursor = self.delivered_deaths.cursor_front(); + while let Some(next) = cursor.peek_next() { + if next.cookie == cookie { + return Some(next.remove().into_arc()); + } + cursor.move_next(); + } + None + } + + pub(crate) fn death_delivered(&mut self, death: DArc<NodeDeath>) { + if let Some(death) = ListArc::try_from_arc_or_drop(death) { + self.delivered_deaths.push_back(death); + } else { + pr_warn!("Notification added to `delivered_deaths` twice."); + } + } + + pub(crate) fn add_outstanding_txn(&mut self) { + self.outstanding_txns += 1; + } + + fn txns_pending_locked(&self) -> bool { + if self.outstanding_txns > 0 { + return true; + } + for thread in self.threads.values() { + if thread.has_current_transaction() { + return true; + } + } + false + } +} + +/// Used to keep track of a node that this process has a handle to. +#[pin_data] +pub(crate) struct NodeRefInfo { + debug_id: usize, + /// The refcount that this process owns to the node. + node_ref: ListArcField<NodeRef, { Self::LIST_PROC }>, + death: ListArcField<Option<DArc<NodeDeath>>, { Self::LIST_PROC }>, + /// Cookie of the active freeze listener for this node. + freeze: ListArcField<Option<FreezeCookie>, { Self::LIST_PROC }>, + /// Used to store this `NodeRefInfo` in the node's `refs` list. + #[pin] + links: ListLinks<{ Self::LIST_NODE }>, + /// The handle for this `NodeRefInfo`. + handle: u32, + /// The process that has a handle to the node. + pub(crate) process: Arc<Process>, +} + +impl NodeRefInfo { + /// The id used for the `Node::refs` list. + pub(crate) const LIST_NODE: u64 = 0x2da16350fb724a10; + /// The id used for the `ListArc` in `ProcessNodeRefs`. + const LIST_PROC: u64 = 0xd703a5263dcc8650; + + fn new(node_ref: NodeRef, handle: u32, process: Arc<Process>) -> impl PinInit<Self> { + pin_init!(Self { + debug_id: super::next_debug_id(), + node_ref: ListArcField::new(node_ref), + death: ListArcField::new(None), + freeze: ListArcField::new(None), + links <- ListLinks::new(), + handle, + process, + }) + } + + kernel::list::define_list_arc_field_getter! { + pub(crate) fn death(&mut self<{Self::LIST_PROC}>) -> &mut Option<DArc<NodeDeath>> { death } + pub(crate) fn freeze(&mut self<{Self::LIST_PROC}>) -> &mut Option<FreezeCookie> { freeze } + pub(crate) fn node_ref(&mut self<{Self::LIST_PROC}>) -> &mut NodeRef { node_ref } + pub(crate) fn node_ref2(&self<{Self::LIST_PROC}>) -> &NodeRef { node_ref } + } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<{Self::LIST_NODE}> for NodeRefInfo { untracked; } + impl ListArcSafe<{Self::LIST_PROC}> for NodeRefInfo { untracked; } +} +kernel::list::impl_list_item! { + impl ListItem<{Self::LIST_NODE}> for NodeRefInfo { + using ListLinks { self.links }; + } +} + +/// Keeps track of references this process has to nodes owned by other processes. +/// +/// TODO: Currently, the rbtree requires two allocations per node reference, and two tree +/// traversals to look up a node by `Node::global_id`. Once the rbtree is more powerful, these +/// extra costs should be eliminated. +struct ProcessNodeRefs { + /// Used to look up nodes using the 32-bit id that this process knows it by. + by_handle: RBTree<u32, ListArc<NodeRefInfo, { NodeRefInfo::LIST_PROC }>>, + /// Used to look up nodes without knowing their local 32-bit id. The usize is the address of + /// the underlying `Node` struct as returned by `Node::global_id`. + by_node: RBTree<usize, u32>, + /// Used to look up a `FreezeListener` by cookie. + /// + /// There might be multiple freeze listeners for the same node, but at most one of them is + /// active. + freeze_listeners: RBTree<FreezeCookie, FreezeListener>, +} + +impl ProcessNodeRefs { + fn new() -> Self { + Self { + by_handle: RBTree::new(), + by_node: RBTree::new(), + freeze_listeners: RBTree::new(), + } + } +} + +/// A process using binder. +/// +/// Strictly speaking, there can be multiple of these per process. There is one for each binder fd +/// that a process has opened, so processes using several binder contexts have several `Process` +/// objects. This ensures that the contexts are fully separated. +#[pin_data] +pub(crate) struct Process { + pub(crate) ctx: Arc<Context>, + + // The task leader (process). + pub(crate) task: ARef<Task>, + + // Credential associated with file when `Process` is created. + pub(crate) cred: ARef<Credential>, + + #[pin] + pub(crate) inner: SpinLock<ProcessInner>, + + #[pin] + pub(crate) pages: ShrinkablePageRange, + + // Waitqueue of processes waiting for all outstanding transactions to be + // processed. + #[pin] + freeze_wait: CondVar, + + // Node references are in a different lock to avoid recursive acquisition when + // incrementing/decrementing a node in another process. + #[pin] + node_refs: Mutex<ProcessNodeRefs>, + + // Work node for deferred work item. + #[pin] + defer_work: Work<Process>, + + // Links for process list in Context. + #[pin] + links: ListLinks, + + pub(crate) stats: BinderStats, +} + +kernel::impl_has_work! { + impl HasWork<Process> for Process { self.defer_work } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for Process { untracked; } +} +kernel::list::impl_list_item! { + impl ListItem<0> for Process { + using ListLinks { self.links }; + } +} + +impl workqueue::WorkItem for Process { + type Pointer = Arc<Process>; + + fn run(me: Arc<Self>) { + let defer; + { + let mut inner = me.inner.lock(); + defer = inner.defer_work; + inner.defer_work = 0; + } + + if defer & PROC_DEFER_FLUSH != 0 { + me.deferred_flush(); + } + if defer & PROC_DEFER_RELEASE != 0 { + me.deferred_release(); + } + } +} + +impl Process { + fn new(ctx: Arc<Context>, cred: ARef<Credential>) -> Result<Arc<Self>> { + let current = kernel::current!(); + let list_process = ListArc::pin_init::<Error>( + try_pin_init!(Process { + ctx, + cred, + inner <- kernel::new_spinlock!(ProcessInner::new(), "Process::inner"), + pages <- ShrinkablePageRange::new(&super::BINDER_SHRINKER), + node_refs <- kernel::new_mutex!(ProcessNodeRefs::new(), "Process::node_refs"), + freeze_wait <- kernel::new_condvar!("Process::freeze_wait"), + task: current.group_leader().into(), + defer_work <- kernel::new_work!("Process::defer_work"), + links <- ListLinks::new(), + stats: BinderStats::new(), + }), + GFP_KERNEL, + )?; + + let process = list_process.clone_arc(); + process.ctx.register_process(list_process); + + Ok(process) + } + + pub(crate) fn pid_in_current_ns(&self) -> kernel::task::Pid { + self.task.tgid_nr_ns(None) + } + + #[inline(never)] + pub(crate) fn debug_print_stats(&self, m: &SeqFile, ctx: &Context) -> Result<()> { + seq_print!(m, "proc {}\n", self.pid_in_current_ns()); + seq_print!(m, "context {}\n", &*ctx.name); + + let inner = self.inner.lock(); + seq_print!(m, " threads: {}\n", inner.threads.iter().count()); + seq_print!( + m, + " requested threads: {}+{}/{}\n", + inner.requested_thread_count, + inner.started_thread_count, + inner.max_threads, + ); + if let Some(mapping) = &inner.mapping { + seq_print!( + m, + " free oneway space: {}\n", + mapping.alloc.free_oneway_space() + ); + seq_print!(m, " buffers: {}\n", mapping.alloc.count_buffers()); + } + seq_print!( + m, + " outstanding transactions: {}\n", + inner.outstanding_txns + ); + seq_print!(m, " nodes: {}\n", inner.nodes.iter().count()); + drop(inner); + + { + let mut refs = self.node_refs.lock(); + let (mut count, mut weak, mut strong) = (0, 0, 0); + for r in refs.by_handle.values_mut() { + let node_ref = r.node_ref(); + let (nstrong, nweak) = node_ref.get_count(); + count += 1; + weak += nweak; + strong += nstrong; + } + seq_print!(m, " refs: {count} s {strong} w {weak}\n"); + } + + self.stats.debug_print(" ", m); + + Ok(()) + } + + #[inline(never)] + pub(crate) fn debug_print(&self, m: &SeqFile, ctx: &Context, print_all: bool) -> Result<()> { + seq_print!(m, "proc {}\n", self.pid_in_current_ns()); + seq_print!(m, "context {}\n", &*ctx.name); + + let mut all_threads = KVec::new(); + let mut all_nodes = KVec::new(); + loop { + let inner = self.inner.lock(); + let num_threads = inner.threads.iter().count(); + let num_nodes = inner.nodes.iter().count(); + + if all_threads.capacity() < num_threads || all_nodes.capacity() < num_nodes { + drop(inner); + all_threads.reserve(num_threads, GFP_KERNEL)?; + all_nodes.reserve(num_nodes, GFP_KERNEL)?; + continue; + } + + for thread in inner.threads.values() { + assert!(all_threads.len() < all_threads.capacity()); + let _ = all_threads.push(thread.clone(), GFP_ATOMIC); + } + + for node in inner.nodes.values() { + assert!(all_nodes.len() < all_nodes.capacity()); + let _ = all_nodes.push(node.clone(), GFP_ATOMIC); + } + + break; + } + + for thread in all_threads { + thread.debug_print(m, print_all)?; + } + + let mut inner = self.inner.lock(); + for node in all_nodes { + if print_all || node.has_oneway_transaction(&mut inner) { + node.full_debug_print(m, &mut inner)?; + } + } + drop(inner); + + if print_all { + let mut refs = self.node_refs.lock(); + for r in refs.by_handle.values_mut() { + let node_ref = r.node_ref(); + let dead = node_ref.node.owner.inner.lock().is_dead; + let (strong, weak) = node_ref.get_count(); + let debug_id = node_ref.node.debug_id; + + seq_print!( + m, + " ref {}: desc {} {}node {debug_id} s {strong} w {weak}", + r.debug_id, + r.handle, + if dead { "dead " } else { "" }, + ); + } + } + + let inner = self.inner.lock(); + for work in &inner.work { + work.debug_print(m, " ", " pending transaction ")?; + } + for _death in &inner.delivered_deaths { + seq_print!(m, " has delivered dead binder\n"); + } + if let Some(mapping) = &inner.mapping { + mapping.alloc.debug_print(m)?; + } + drop(inner); + + Ok(()) + } + + /// Attempts to fetch a work item from the process queue. + pub(crate) fn get_work(&self) -> Option<DLArc<dyn DeliverToRead>> { + self.inner.lock().work.pop_front() + } + + /// Attempts to fetch a work item from the process queue. If none is available, it registers the + /// given thread as ready to receive work directly. + /// + /// This must only be called when the thread is not participating in a transaction chain; when + /// it is, work will always be delivered directly to the thread (and not through the process + /// queue). + pub(crate) fn get_work_or_register<'a>( + &'a self, + thread: &'a Arc<Thread>, + ) -> GetWorkOrRegister<'a> { + let mut inner = self.inner.lock(); + // Try to get work from the process queue. + if let Some(work) = inner.work.pop_front() { + return GetWorkOrRegister::Work(work); + } + + // Register the thread as ready. + GetWorkOrRegister::Register(Registration::new(thread, &mut inner)) + } + + fn get_current_thread(self: ArcBorrow<'_, Self>) -> Result<Arc<Thread>> { + let id = { + let current = kernel::current!(); + if !core::ptr::eq(current.group_leader(), &*self.task) { + pr_err!("get_current_thread was called from the wrong process."); + return Err(EINVAL); + } + current.pid() + }; + + { + let inner = self.inner.lock(); + if let Some(thread) = inner.threads.get(&id) { + return Ok(thread.clone()); + } + } + + // Allocate a new `Thread` without holding any locks. + let reservation = RBTreeNodeReservation::new(GFP_KERNEL)?; + let ta: Arc<Thread> = Thread::new(id, self.into())?; + + let mut inner = self.inner.lock(); + match inner.threads.entry(id) { + rbtree::Entry::Vacant(entry) => { + entry.insert(ta.clone(), reservation); + Ok(ta) + } + rbtree::Entry::Occupied(_entry) => { + pr_err!("Cannot create two threads with the same id."); + Err(EINVAL) + } + } + } + + pub(crate) fn push_work(&self, work: DLArc<dyn DeliverToRead>) -> BinderResult { + // If push_work fails, drop the work item outside the lock. + let res = self.inner.lock().push_work(work); + match res { + Ok(()) => Ok(()), + Err((err, work)) => { + drop(work); + Err(err) + } + } + } + + fn set_as_manager( + self: ArcBorrow<'_, Self>, + info: Option<FlatBinderObject>, + thread: &Thread, + ) -> Result { + let (ptr, cookie, flags) = if let Some(obj) = info { + ( + // SAFETY: The object type for this ioctl is implicitly `BINDER_TYPE_BINDER`, so it + // is safe to access the `binder` field. + unsafe { obj.__bindgen_anon_1.binder }, + obj.cookie, + obj.flags, + ) + } else { + (0, 0, 0) + }; + let node_ref = self.get_node(ptr, cookie, flags as _, true, thread)?; + let node = node_ref.node.clone(); + self.ctx.set_manager_node(node_ref)?; + self.inner.lock().is_manager = true; + + // Force the state of the node to prevent the delivery of acquire/increfs. + let mut owner_inner = node.owner.inner.lock(); + node.force_has_count(&mut owner_inner); + Ok(()) + } + + fn get_node_inner( + self: ArcBorrow<'_, Self>, + ptr: u64, + cookie: u64, + flags: u32, + strong: bool, + thread: &Thread, + wrapper: Option<CritIncrWrapper>, + ) -> Result<Result<NodeRef, CouldNotDeliverCriticalIncrement>> { + // Try to find an existing node. + { + let mut inner = self.inner.lock(); + if let Some(node) = inner.get_existing_node(ptr, cookie)? { + return Ok(inner.new_node_ref_with_thread(node, strong, thread, wrapper)); + } + } + + // Allocate the node before reacquiring the lock. + let node = DTRWrap::arc_pin_init(Node::new(ptr, cookie, flags, self.into()))?.into_arc(); + let rbnode = RBTreeNode::new(ptr, node.clone(), GFP_KERNEL)?; + let mut inner = self.inner.lock(); + if let Some(node) = inner.get_existing_node(ptr, cookie)? { + return Ok(inner.new_node_ref_with_thread(node, strong, thread, wrapper)); + } + + inner.nodes.insert(rbnode); + // This can only fail if someone has already pushed the node to a list, but we just created + // it and still hold the lock, so it can't fail right now. + let node_ref = inner + .new_node_ref_with_thread(node, strong, thread, wrapper) + .unwrap(); + + Ok(Ok(node_ref)) + } + + pub(crate) fn get_node( + self: ArcBorrow<'_, Self>, + ptr: u64, + cookie: u64, + flags: u32, + strong: bool, + thread: &Thread, + ) -> Result<NodeRef> { + let mut wrapper = None; + for _ in 0..2 { + match self.get_node_inner(ptr, cookie, flags, strong, thread, wrapper) { + Err(err) => return Err(err), + Ok(Ok(node_ref)) => return Ok(node_ref), + Ok(Err(CouldNotDeliverCriticalIncrement)) => { + wrapper = Some(CritIncrWrapper::new()?); + } + } + } + // We only get a `CouldNotDeliverCriticalIncrement` error if `wrapper` is `None`, so the + // loop should run at most twice. + unreachable!() + } + + pub(crate) fn insert_or_update_handle( + self: ArcBorrow<'_, Process>, + node_ref: NodeRef, + is_mananger: bool, + ) -> Result<u32> { + { + let mut refs = self.node_refs.lock(); + + // Do a lookup before inserting. + if let Some(handle_ref) = refs.by_node.get(&node_ref.node.global_id()) { + let handle = *handle_ref; + let info = refs.by_handle.get_mut(&handle).unwrap(); + info.node_ref().absorb(node_ref); + return Ok(handle); + } + } + + // Reserve memory for tree nodes. + let reserve1 = RBTreeNodeReservation::new(GFP_KERNEL)?; + let reserve2 = RBTreeNodeReservation::new(GFP_KERNEL)?; + let info = UniqueArc::new_uninit(GFP_KERNEL)?; + + let mut refs = self.node_refs.lock(); + + // Do a lookup again as node may have been inserted before the lock was reacquired. + if let Some(handle_ref) = refs.by_node.get(&node_ref.node.global_id()) { + let handle = *handle_ref; + let info = refs.by_handle.get_mut(&handle).unwrap(); + info.node_ref().absorb(node_ref); + return Ok(handle); + } + + // Find id. + let mut target: u32 = if is_mananger { 0 } else { 1 }; + for handle in refs.by_handle.keys() { + if *handle > target { + break; + } + if *handle == target { + target = target.checked_add(1).ok_or(ENOMEM)?; + } + } + + let gid = node_ref.node.global_id(); + let (info_proc, info_node) = { + let info_init = NodeRefInfo::new(node_ref, target, self.into()); + match info.pin_init_with(info_init) { + Ok(info) => ListArc::pair_from_pin_unique(info), + // error is infallible + Err(err) => match err {}, + } + }; + + // Ensure the process is still alive while we insert a new reference. + // + // This releases the lock before inserting the nodes, but since `is_dead` is set as the + // first thing in `deferred_release`, process cleanup will not miss the items inserted into + // `refs` below. + if self.inner.lock().is_dead { + return Err(ESRCH); + } + + // SAFETY: `info_proc` and `info_node` reference the same node, so we are inserting + // `info_node` into the right node's `refs` list. + unsafe { info_proc.node_ref2().node.insert_node_info(info_node) }; + + refs.by_node.insert(reserve1.into_node(gid, target)); + refs.by_handle.insert(reserve2.into_node(target, info_proc)); + Ok(target) + } + + pub(crate) fn get_transaction_node(&self, handle: u32) -> BinderResult<NodeRef> { + // When handle is zero, try to get the context manager. + if handle == 0 { + Ok(self.ctx.get_manager_node(true)?) + } else { + Ok(self.get_node_from_handle(handle, true)?) + } + } + + pub(crate) fn get_node_from_handle(&self, handle: u32, strong: bool) -> Result<NodeRef> { + self.node_refs + .lock() + .by_handle + .get_mut(&handle) + .ok_or(ENOENT)? + .node_ref() + .clone(strong) + } + + pub(crate) fn remove_from_delivered_deaths(&self, death: &DArc<NodeDeath>) { + let mut inner = self.inner.lock(); + // SAFETY: By the invariant on the `delivered_links` field, this is the right linked list. + let removed = unsafe { inner.delivered_deaths.remove(death) }; + drop(inner); + drop(removed); + } + + pub(crate) fn update_ref( + self: ArcBorrow<'_, Process>, + handle: u32, + inc: bool, + strong: bool, + ) -> Result { + if inc && handle == 0 { + if let Ok(node_ref) = self.ctx.get_manager_node(strong) { + if core::ptr::eq(&*self, &*node_ref.node.owner) { + return Err(EINVAL); + } + let _ = self.insert_or_update_handle(node_ref, true); + return Ok(()); + } + } + + // To preserve original binder behaviour, we only fail requests where the manager tries to + // increment references on itself. + let mut refs = self.node_refs.lock(); + if let Some(info) = refs.by_handle.get_mut(&handle) { + if info.node_ref().update(inc, strong) { + // Clean up death if there is one attached to this node reference. + if let Some(death) = info.death().take() { + death.set_cleared(true); + self.remove_from_delivered_deaths(&death); + } + + // Remove reference from process tables, and from the node's `refs` list. + + // SAFETY: We are removing the `NodeRefInfo` from the right node. + unsafe { info.node_ref2().node.remove_node_info(info) }; + + let id = info.node_ref().node.global_id(); + refs.by_handle.remove(&handle); + refs.by_node.remove(&id); + } + } else { + // All refs are cleared in process exit, so this warning is expected in that case. + if !self.inner.lock().is_dead { + pr_warn!("{}: no such ref {handle}\n", self.pid_in_current_ns()); + } + } + Ok(()) + } + + /// Decrements the refcount of the given node, if one exists. + pub(crate) fn update_node(&self, ptr: u64, cookie: u64, strong: bool) { + let mut inner = self.inner.lock(); + if let Ok(Some(node)) = inner.get_existing_node(ptr, cookie) { + inner.update_node_refcount(&node, false, strong, 1, None); + } + } + + pub(crate) fn inc_ref_done(&self, reader: &mut UserSliceReader, strong: bool) -> Result { + let ptr = reader.read::<u64>()?; + let cookie = reader.read::<u64>()?; + let mut inner = self.inner.lock(); + if let Ok(Some(node)) = inner.get_existing_node(ptr, cookie) { + if let Some(node) = node.inc_ref_done_locked(strong, &mut inner) { + // This only fails if the process is dead. + let _ = inner.push_work(node); + } + } + Ok(()) + } + + pub(crate) fn buffer_alloc( + self: &Arc<Self>, + debug_id: usize, + size: usize, + is_oneway: bool, + from_pid: i32, + ) -> BinderResult<NewAllocation> { + use kernel::page::PAGE_SIZE; + + let mut reserve_new_args = ReserveNewArgs { + debug_id, + size, + is_oneway, + pid: from_pid, + ..ReserveNewArgs::default() + }; + + let (new_alloc, addr) = loop { + let mut inner = self.inner.lock(); + let mapping = inner.mapping.as_mut().ok_or_else(BinderError::new_dead)?; + let alloc_request = match mapping.alloc.reserve_new(reserve_new_args)? { + ReserveNew::Success(new_alloc) => break (new_alloc, mapping.address), + ReserveNew::NeedAlloc(request) => request, + }; + drop(inner); + // We need to allocate memory and then call `reserve_new` again. + reserve_new_args = alloc_request.make_alloc()?; + }; + + let res = Allocation::new( + self.clone(), + debug_id, + new_alloc.offset, + size, + addr + new_alloc.offset, + new_alloc.oneway_spam_detected, + ); + + // This allocation will be marked as in use until the `Allocation` is used to free it. + // + // This method can't be called while holding a lock, so we release the lock first. It's + // okay for several threads to use the method on the same index at the same time. In that + // case, one of the calls will allocate the given page (if missing), and the other call + // will wait for the other call to finish allocating the page. + // + // We will not call `stop_using_range` in parallel with this on the same page, because the + // allocation can only be removed via the destructor of the `Allocation` object that we + // currently own. + match self.pages.use_range( + new_alloc.offset / PAGE_SIZE, + (new_alloc.offset + size).div_ceil(PAGE_SIZE), + ) { + Ok(()) => {} + Err(err) => { + pr_warn!("use_range failure {:?}", err); + return Err(err.into()); + } + } + + Ok(NewAllocation(res)) + } + + pub(crate) fn buffer_get(self: &Arc<Self>, ptr: usize) -> Option<Allocation> { + let mut inner = self.inner.lock(); + let mapping = inner.mapping.as_mut()?; + let offset = ptr.checked_sub(mapping.address)?; + let (size, debug_id, odata) = mapping.alloc.reserve_existing(offset).ok()?; + let mut alloc = Allocation::new(self.clone(), debug_id, offset, size, ptr, false); + if let Some(data) = odata { + alloc.set_info(data); + } + Some(alloc) + } + + pub(crate) fn buffer_raw_free(&self, ptr: usize) { + let mut inner = self.inner.lock(); + if let Some(ref mut mapping) = &mut inner.mapping { + let offset = match ptr.checked_sub(mapping.address) { + Some(offset) => offset, + None => return, + }; + + let freed_range = match mapping.alloc.reservation_abort(offset) { + Ok(freed_range) => freed_range, + Err(_) => { + pr_warn!( + "Pointer {:x} failed to free, base = {:x}\n", + ptr, + mapping.address + ); + return; + } + }; + + // No more allocations in this range. Mark them as not in use. + // + // Must be done before we release the lock so that `use_range` is not used on these + // indices until `stop_using_range` returns. + self.pages + .stop_using_range(freed_range.start_page_idx, freed_range.end_page_idx); + } + } + + pub(crate) fn buffer_make_freeable(&self, offset: usize, mut data: Option<AllocationInfo>) { + let mut inner = self.inner.lock(); + if let Some(ref mut mapping) = &mut inner.mapping { + if mapping.alloc.reservation_commit(offset, &mut data).is_err() { + pr_warn!("Offset {} failed to be marked freeable\n", offset); + } + } + } + + fn create_mapping(&self, vma: &mm::virt::VmaNew) -> Result { + use kernel::page::PAGE_SIZE; + let size = usize::min(vma.end() - vma.start(), bindings::SZ_4M as usize); + let mapping = Mapping::new(vma.start(), size); + let page_count = self.pages.register_with_vma(vma)?; + if page_count * PAGE_SIZE != size { + return Err(EINVAL); + } + + // Save range allocator for later. + self.inner.lock().mapping = Some(mapping); + + Ok(()) + } + + fn version(&self, data: UserSlice) -> Result { + data.writer().write(&BinderVersion::current()) + } + + pub(crate) fn register_thread(&self) -> bool { + self.inner.lock().register_thread() + } + + fn remove_thread(&self, thread: Arc<Thread>) { + self.inner.lock().threads.remove(&thread.id); + thread.release(); + } + + fn set_max_threads(&self, max: u32) { + self.inner.lock().max_threads = max; + } + + fn set_oneway_spam_detection_enabled(&self, enabled: u32) { + self.inner.lock().oneway_spam_detection_enabled = enabled != 0; + } + + pub(crate) fn is_oneway_spam_detection_enabled(&self) -> bool { + self.inner.lock().oneway_spam_detection_enabled + } + + fn get_node_debug_info(&self, data: UserSlice) -> Result { + let (mut reader, mut writer) = data.reader_writer(); + + // Read the starting point. + let ptr = reader.read::<BinderNodeDebugInfo>()?.ptr; + let mut out = BinderNodeDebugInfo::default(); + + { + let inner = self.inner.lock(); + for (node_ptr, node) in &inner.nodes { + if *node_ptr > ptr { + node.populate_debug_info(&mut out, &inner); + break; + } + } + } + + writer.write(&out) + } + + fn get_node_info_from_ref(&self, data: UserSlice) -> Result { + let (mut reader, mut writer) = data.reader_writer(); + let mut out = reader.read::<BinderNodeInfoForRef>()?; + + if out.strong_count != 0 + || out.weak_count != 0 + || out.reserved1 != 0 + || out.reserved2 != 0 + || out.reserved3 != 0 + { + return Err(EINVAL); + } + + // Only the context manager is allowed to use this ioctl. + if !self.inner.lock().is_manager { + return Err(EPERM); + } + + { + let mut node_refs = self.node_refs.lock(); + let node_info = node_refs.by_handle.get_mut(&out.handle).ok_or(ENOENT)?; + let node_ref = node_info.node_ref(); + let owner_inner = node_ref.node.owner.inner.lock(); + node_ref.node.populate_counts(&mut out, &owner_inner); + } + + // Write the result back. + writer.write(&out) + } + + pub(crate) fn needs_thread(&self) -> bool { + let mut inner = self.inner.lock(); + let ret = inner.requested_thread_count == 0 + && inner.ready_threads.is_empty() + && inner.started_thread_count < inner.max_threads; + if ret { + inner.requested_thread_count += 1 + } + ret + } + + pub(crate) fn request_death( + self: &Arc<Self>, + reader: &mut UserSliceReader, + thread: &Thread, + ) -> Result { + let handle: u32 = reader.read()?; + let cookie: u64 = reader.read()?; + + // Queue BR_ERROR if we can't allocate memory for the death notification. + let death = UniqueArc::new_uninit(GFP_KERNEL).inspect_err(|_| { + thread.push_return_work(BR_ERROR); + })?; + let mut refs = self.node_refs.lock(); + let Some(info) = refs.by_handle.get_mut(&handle) else { + pr_warn!("BC_REQUEST_DEATH_NOTIFICATION invalid ref {handle}\n"); + return Ok(()); + }; + + // Nothing to do if there is already a death notification request for this handle. + if info.death().is_some() { + pr_warn!("BC_REQUEST_DEATH_NOTIFICATION death notification already set\n"); + return Ok(()); + } + + let death = { + let death_init = NodeDeath::new(info.node_ref().node.clone(), self.clone(), cookie); + match death.pin_init_with(death_init) { + Ok(death) => death, + // error is infallible + Err(err) => match err {}, + } + }; + + // Register the death notification. + { + let owner = info.node_ref2().node.owner.clone(); + let mut owner_inner = owner.inner.lock(); + if owner_inner.is_dead { + let death = Arc::from(death); + *info.death() = Some(death.clone()); + drop(owner_inner); + death.set_dead(); + } else { + let death = ListArc::from(death); + *info.death() = Some(death.clone_arc()); + info.node_ref().node.add_death(death, &mut owner_inner); + } + } + Ok(()) + } + + pub(crate) fn clear_death(&self, reader: &mut UserSliceReader, thread: &Thread) -> Result { + let handle: u32 = reader.read()?; + let cookie: u64 = reader.read()?; + + let mut refs = self.node_refs.lock(); + let Some(info) = refs.by_handle.get_mut(&handle) else { + pr_warn!("BC_CLEAR_DEATH_NOTIFICATION invalid ref {handle}\n"); + return Ok(()); + }; + + let Some(death) = info.death().take() else { + pr_warn!("BC_CLEAR_DEATH_NOTIFICATION death notification not active\n"); + return Ok(()); + }; + if death.cookie != cookie { + *info.death() = Some(death); + pr_warn!("BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch\n"); + return Ok(()); + } + + // Update state and determine if we need to queue a work item. We only need to do it when + // the node is not dead or if the user already completed the death notification. + if death.set_cleared(false) { + if let Some(death) = ListArc::try_from_arc_or_drop(death) { + let _ = thread.push_work_if_looper(death); + } + } + + Ok(()) + } + + pub(crate) fn dead_binder_done(&self, cookie: u64, thread: &Thread) { + if let Some(death) = self.inner.lock().pull_delivered_death(cookie) { + death.set_notification_done(thread); + } + } + + /// Locks the spinlock and move the `nodes` rbtree out. + /// + /// This allows you to iterate through `nodes` while also allowing you to give other parts of + /// the codebase exclusive access to `ProcessInner`. + pub(crate) fn lock_with_nodes(&self) -> WithNodes<'_> { + let mut inner = self.inner.lock(); + WithNodes { + nodes: take(&mut inner.nodes), + inner, + } + } + + fn deferred_flush(&self) { + let inner = self.inner.lock(); + for thread in inner.threads.values() { + thread.exit_looper(); + } + } + + fn deferred_release(self: Arc<Self>) { + let is_manager = { + let mut inner = self.inner.lock(); + inner.is_dead = true; + inner.is_frozen = false; + inner.sync_recv = false; + inner.async_recv = false; + inner.is_manager + }; + + if is_manager { + self.ctx.unset_manager_node(); + } + + self.ctx.deregister_process(&self); + + let binderfs_file = self.inner.lock().binderfs_file.take(); + drop(binderfs_file); + + // Release threads. + let threads = { + let mut inner = self.inner.lock(); + let threads = take(&mut inner.threads); + let ready = take(&mut inner.ready_threads); + drop(inner); + drop(ready); + + for thread in threads.values() { + thread.release(); + } + threads + }; + + // Release nodes. + { + while let Some(node) = { + let mut lock = self.inner.lock(); + lock.nodes.cursor_front().map(|c| c.remove_current().1) + } { + node.to_key_value().1.release(); + } + } + + // Clean up death listeners and remove nodes from external node info lists. + for info in self.node_refs.lock().by_handle.values_mut() { + // SAFETY: We are removing the `NodeRefInfo` from the right node. + unsafe { info.node_ref2().node.remove_node_info(info) }; + + // Remove all death notifications from the nodes (that belong to a different process). + let death = if let Some(existing) = info.death().take() { + existing + } else { + continue; + }; + death.set_cleared(false); + } + + // Clean up freeze listeners. + let freeze_listeners = take(&mut self.node_refs.lock().freeze_listeners); + for listener in freeze_listeners.values() { + listener.on_process_exit(&self); + } + drop(freeze_listeners); + + // Release refs on foreign nodes. + { + let mut refs = self.node_refs.lock(); + let by_handle = take(&mut refs.by_handle); + let by_node = take(&mut refs.by_node); + drop(refs); + drop(by_node); + drop(by_handle); + } + + // Cancel all pending work items. + while let Some(work) = self.get_work() { + work.into_arc().cancel(); + } + + let delivered_deaths = take(&mut self.inner.lock().delivered_deaths); + drop(delivered_deaths); + + // Free any resources kept alive by allocated buffers. + let omapping = self.inner.lock().mapping.take(); + if let Some(mut mapping) = omapping { + let address = mapping.address; + mapping + .alloc + .take_for_each(|offset, size, debug_id, odata| { + let ptr = offset + address; + pr_warn!( + "{}: removing orphan mapping {offset}:{size}\n", + self.pid_in_current_ns() + ); + let mut alloc = + Allocation::new(self.clone(), debug_id, offset, size, ptr, false); + if let Some(data) = odata { + alloc.set_info(data); + } + drop(alloc) + }); + } + + // calls to synchronize_rcu() in thread drop will happen here + drop(threads); + } + + pub(crate) fn drop_outstanding_txn(&self) { + let wake = { + let mut inner = self.inner.lock(); + if inner.outstanding_txns == 0 { + pr_err!("outstanding_txns underflow"); + return; + } + inner.outstanding_txns -= 1; + inner.is_frozen && inner.outstanding_txns == 0 + }; + + if wake { + self.freeze_wait.notify_all(); + } + } + + pub(crate) fn ioctl_freeze(&self, info: &BinderFreezeInfo) -> Result { + if info.enable == 0 { + let msgs = self.prepare_freeze_messages()?; + let mut inner = self.inner.lock(); + inner.sync_recv = false; + inner.async_recv = false; + inner.is_frozen = false; + drop(inner); + msgs.send_messages(); + return Ok(()); + } + + let mut inner = self.inner.lock(); + inner.sync_recv = false; + inner.async_recv = false; + inner.is_frozen = true; + + if info.timeout_ms > 0 { + let mut jiffies = kernel::time::msecs_to_jiffies(info.timeout_ms); + while jiffies > 0 { + if inner.outstanding_txns == 0 { + break; + } + + match self + .freeze_wait + .wait_interruptible_timeout(&mut inner, jiffies) + { + CondVarTimeoutResult::Signal { .. } => { + inner.is_frozen = false; + return Err(ERESTARTSYS); + } + CondVarTimeoutResult::Woken { jiffies: remaining } => { + jiffies = remaining; + } + CondVarTimeoutResult::Timeout => { + jiffies = 0; + } + } + } + } + + if inner.txns_pending_locked() { + inner.is_frozen = false; + Err(EAGAIN) + } else { + drop(inner); + match self.prepare_freeze_messages() { + Ok(batch) => { + batch.send_messages(); + Ok(()) + } + Err(kernel::alloc::AllocError) => { + self.inner.lock().is_frozen = false; + Err(ENOMEM) + } + } + } + } +} + +fn get_frozen_status(data: UserSlice) -> Result { + let (mut reader, mut writer) = data.reader_writer(); + + let mut info = reader.read::<BinderFrozenStatusInfo>()?; + info.sync_recv = 0; + info.async_recv = 0; + let mut found = false; + + for ctx in crate::context::get_all_contexts()? { + ctx.for_each_proc(|proc| { + if proc.task.pid() == info.pid as _ { + found = true; + let inner = proc.inner.lock(); + let txns_pending = inner.txns_pending_locked(); + info.async_recv |= inner.async_recv as u32; + info.sync_recv |= inner.sync_recv as u32; + info.sync_recv |= (txns_pending as u32) << 1; + } + }); + } + + if found { + writer.write(&info)?; + Ok(()) + } else { + Err(EINVAL) + } +} + +fn ioctl_freeze(reader: &mut UserSliceReader) -> Result { + let info = reader.read::<BinderFreezeInfo>()?; + + // Very unlikely for there to be more than 3, since a process normally uses at most binder and + // hwbinder. + let mut procs = KVec::with_capacity(3, GFP_KERNEL)?; + + let ctxs = crate::context::get_all_contexts()?; + for ctx in ctxs { + for proc in ctx.get_procs_with_pid(info.pid as i32)? { + procs.push(proc, GFP_KERNEL)?; + } + } + + for proc in procs { + proc.ioctl_freeze(&info)?; + } + Ok(()) +} + +/// The ioctl handler. +impl Process { + /// Ioctls that are write-only from the perspective of userspace. + /// + /// The kernel will only read from the pointer that userspace provided to us. + fn ioctl_write_only( + this: ArcBorrow<'_, Process>, + _file: &File, + cmd: u32, + reader: &mut UserSliceReader, + ) -> Result { + let thread = this.get_current_thread()?; + match cmd { + uapi::BINDER_SET_MAX_THREADS => this.set_max_threads(reader.read()?), + uapi::BINDER_THREAD_EXIT => this.remove_thread(thread), + uapi::BINDER_SET_CONTEXT_MGR => this.set_as_manager(None, &thread)?, + uapi::BINDER_SET_CONTEXT_MGR_EXT => { + this.set_as_manager(Some(reader.read()?), &thread)? + } + uapi::BINDER_ENABLE_ONEWAY_SPAM_DETECTION => { + this.set_oneway_spam_detection_enabled(reader.read()?) + } + uapi::BINDER_FREEZE => ioctl_freeze(reader)?, + _ => return Err(EINVAL), + } + Ok(()) + } + + /// Ioctls that are read/write from the perspective of userspace. + /// + /// The kernel will both read from and write to the pointer that userspace provided to us. + fn ioctl_write_read( + this: ArcBorrow<'_, Process>, + file: &File, + cmd: u32, + data: UserSlice, + ) -> Result { + let thread = this.get_current_thread()?; + let blocking = (file.flags() & file::flags::O_NONBLOCK) == 0; + match cmd { + uapi::BINDER_WRITE_READ => thread.write_read(data, blocking)?, + uapi::BINDER_GET_NODE_DEBUG_INFO => this.get_node_debug_info(data)?, + uapi::BINDER_GET_NODE_INFO_FOR_REF => this.get_node_info_from_ref(data)?, + uapi::BINDER_VERSION => this.version(data)?, + uapi::BINDER_GET_FROZEN_INFO => get_frozen_status(data)?, + uapi::BINDER_GET_EXTENDED_ERROR => thread.get_extended_error(data)?, + _ => return Err(EINVAL), + } + Ok(()) + } +} + +/// The file operations supported by `Process`. +impl Process { + pub(crate) fn open(ctx: ArcBorrow<'_, Context>, file: &File) -> Result<Arc<Process>> { + Self::new(ctx.into(), ARef::from(file.cred())) + } + + pub(crate) fn release(this: Arc<Process>, _file: &File) { + let binderfs_file; + let should_schedule; + { + let mut inner = this.inner.lock(); + should_schedule = inner.defer_work == 0; + inner.defer_work |= PROC_DEFER_RELEASE; + binderfs_file = inner.binderfs_file.take(); + } + + if should_schedule { + // Ignore failures to schedule to the workqueue. Those just mean that we're already + // scheduled for execution. + let _ = workqueue::system().enqueue(this); + } + + drop(binderfs_file); + } + + pub(crate) fn flush(this: ArcBorrow<'_, Process>) -> Result { + let should_schedule; + { + let mut inner = this.inner.lock(); + should_schedule = inner.defer_work == 0; + inner.defer_work |= PROC_DEFER_FLUSH; + } + + if should_schedule { + // Ignore failures to schedule to the workqueue. Those just mean that we're already + // scheduled for execution. + let _ = workqueue::system().enqueue(Arc::from(this)); + } + Ok(()) + } + + pub(crate) fn ioctl(this: ArcBorrow<'_, Process>, file: &File, cmd: u32, arg: usize) -> Result { + use kernel::ioctl::{_IOC_DIR, _IOC_SIZE}; + use kernel::uapi::{_IOC_READ, _IOC_WRITE}; + + crate::trace::trace_ioctl(cmd, arg); + + let user_slice = UserSlice::new(UserPtr::from_addr(arg), _IOC_SIZE(cmd)); + + const _IOC_READ_WRITE: u32 = _IOC_READ | _IOC_WRITE; + + match _IOC_DIR(cmd) { + _IOC_WRITE => Self::ioctl_write_only(this, file, cmd, &mut user_slice.reader()), + _IOC_READ_WRITE => Self::ioctl_write_read(this, file, cmd, user_slice), + _ => Err(EINVAL), + } + } + + pub(crate) fn compat_ioctl( + this: ArcBorrow<'_, Process>, + file: &File, + cmd: u32, + arg: usize, + ) -> Result { + Self::ioctl(this, file, cmd, arg) + } + + pub(crate) fn mmap( + this: ArcBorrow<'_, Process>, + _file: &File, + vma: &mm::virt::VmaNew, + ) -> Result { + // We don't allow mmap to be used in a different process. + if !core::ptr::eq(kernel::current!().group_leader(), &*this.task) { + return Err(EINVAL); + } + if vma.start() == 0 { + return Err(EINVAL); + } + + vma.try_clear_maywrite().map_err(|_| EPERM)?; + vma.set_dontcopy(); + vma.set_mixedmap(); + + // TODO: Set ops. We need to learn when the user unmaps so that we can stop using it. + this.create_mapping(vma) + } + + pub(crate) fn poll( + this: ArcBorrow<'_, Process>, + file: &File, + table: PollTable<'_>, + ) -> Result<u32> { + let thread = this.get_current_thread()?; + let (from_proc, mut mask) = thread.poll(file, table); + if mask == 0 && from_proc && !this.inner.lock().work.is_empty() { + mask |= bindings::POLLIN; + } + Ok(mask) + } +} + +/// Represents that a thread has registered with the `ready_threads` list of its process. +/// +/// The destructor of this type will unregister the thread from the list of ready threads. +pub(crate) struct Registration<'a> { + thread: &'a Arc<Thread>, +} + +impl<'a> Registration<'a> { + fn new(thread: &'a Arc<Thread>, guard: &mut Guard<'_, ProcessInner, SpinLockBackend>) -> Self { + assert!(core::ptr::eq(&thread.process.inner, guard.lock_ref())); + // INVARIANT: We are pushing this thread to the right `ready_threads` list. + if let Ok(list_arc) = ListArc::try_from_arc(thread.clone()) { + guard.ready_threads.push_front(list_arc); + } else { + // It is an error to hit this branch, and it should not be reachable. We try to do + // something reasonable when the failure path happens. Most likely, the thread in + // question will sleep forever. + pr_err!("Same thread registered with `ready_threads` twice."); + } + Self { thread } + } +} + +impl Drop for Registration<'_> { + fn drop(&mut self) { + let mut inner = self.thread.process.inner.lock(); + // SAFETY: The thread has the invariant that we never push it to any other linked list than + // the `ready_threads` list of its parent process. Therefore, the thread is either in that + // list, or in no list. + unsafe { inner.ready_threads.remove(self.thread) }; + } +} + +pub(crate) struct WithNodes<'a> { + pub(crate) inner: Guard<'a, ProcessInner, SpinLockBackend>, + pub(crate) nodes: RBTree<u64, DArc<Node>>, +} + +impl Drop for WithNodes<'_> { + fn drop(&mut self) { + core::mem::swap(&mut self.nodes, &mut self.inner.nodes); + if self.nodes.iter().next().is_some() { + pr_err!("nodes array was modified while using lock_with_nodes\n"); + } + } +} + +pub(crate) enum GetWorkOrRegister<'a> { + Work(DLArc<dyn DeliverToRead>), + Register(Registration<'a>), +} diff --git a/drivers/android/binder/range_alloc/array.rs b/drivers/android/binder/range_alloc/array.rs new file mode 100644 index 000000000000..07e1dec2ce63 --- /dev/null +++ b/drivers/android/binder/range_alloc/array.rs @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{ + page::{PAGE_MASK, PAGE_SIZE}, + prelude::*, + seq_file::SeqFile, + seq_print, + task::Pid, +}; + +use crate::range_alloc::{DescriptorState, FreedRange, Range}; + +/// Keeps track of allocations in a process' mmap. +/// +/// Each process has an mmap where the data for incoming transactions will be placed. This struct +/// keeps track of allocations made in the mmap. For each allocation, we store a descriptor that +/// has metadata related to the allocation. We also keep track of available free space. +pub(super) struct ArrayRangeAllocator<T> { + /// This stores all ranges that are allocated. Unlike the tree based allocator, we do *not* + /// store the free ranges. + /// + /// Sorted by offset. + pub(super) ranges: KVec<Range<T>>, + size: usize, + free_oneway_space: usize, +} + +struct FindEmptyRes { + /// Which index in `ranges` should we insert the new range at? + /// + /// Inserting the new range at this index keeps `ranges` sorted. + insert_at_idx: usize, + /// Which offset should we insert the new range at? + insert_at_offset: usize, +} + +impl<T> ArrayRangeAllocator<T> { + pub(crate) fn new(size: usize, alloc: EmptyArrayAlloc<T>) -> Self { + Self { + ranges: alloc.ranges, + size, + free_oneway_space: size / 2, + } + } + + pub(crate) fn free_oneway_space(&self) -> usize { + self.free_oneway_space + } + + pub(crate) fn count_buffers(&self) -> usize { + self.ranges.len() + } + + pub(crate) fn total_size(&self) -> usize { + self.size + } + + pub(crate) fn is_full(&self) -> bool { + self.ranges.len() == self.ranges.capacity() + } + + pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> { + for range in &self.ranges { + seq_print!( + m, + " buffer {}: {} size {} pid {} oneway {}", + 0, + range.offset, + range.size, + range.state.pid(), + range.state.is_oneway(), + ); + if let DescriptorState::Reserved(_) = range.state { + seq_print!(m, " reserved\n"); + } else { + seq_print!(m, " allocated\n"); + } + } + Ok(()) + } + + /// Find somewhere to put a new range. + /// + /// Unlike the tree implementation, we do not bother to find the smallest gap. The idea is that + /// fragmentation isn't a big issue when we don't have many ranges. + /// + /// Returns the index that the new range should have in `self.ranges` after insertion. + fn find_empty_range(&self, size: usize) -> Option<FindEmptyRes> { + let after_last_range = self.ranges.last().map(Range::endpoint).unwrap_or(0); + + if size <= self.total_size() - after_last_range { + // We can put the range at the end, so just do that. + Some(FindEmptyRes { + insert_at_idx: self.ranges.len(), + insert_at_offset: after_last_range, + }) + } else { + let mut end_of_prev = 0; + for (i, range) in self.ranges.iter().enumerate() { + // Does it fit before the i'th range? + if size <= range.offset - end_of_prev { + return Some(FindEmptyRes { + insert_at_idx: i, + insert_at_offset: end_of_prev, + }); + } + end_of_prev = range.endpoint(); + } + None + } + } + + pub(crate) fn reserve_new( + &mut self, + debug_id: usize, + size: usize, + is_oneway: bool, + pid: Pid, + ) -> Result<usize> { + // Compute new value of free_oneway_space, which is set only on success. + let new_oneway_space = if is_oneway { + match self.free_oneway_space.checked_sub(size) { + Some(new_oneway_space) => new_oneway_space, + None => return Err(ENOSPC), + } + } else { + self.free_oneway_space + }; + + let FindEmptyRes { + insert_at_idx, + insert_at_offset, + } = self.find_empty_range(size).ok_or(ENOSPC)?; + self.free_oneway_space = new_oneway_space; + + let new_range = Range { + offset: insert_at_offset, + size, + state: DescriptorState::new(is_oneway, debug_id, pid), + }; + // Insert the value at the given index to keep the array sorted. + self.ranges + .insert_within_capacity(insert_at_idx, new_range) + .ok() + .unwrap(); + + Ok(insert_at_offset) + } + + pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> { + // This could use a binary search, but linear scans are usually faster for small arrays. + let i = self + .ranges + .iter() + .position(|range| range.offset == offset) + .ok_or(EINVAL)?; + let range = &self.ranges[i]; + + if let DescriptorState::Allocated(_) = range.state { + return Err(EPERM); + } + + let size = range.size; + let offset = range.offset; + + if range.state.is_oneway() { + self.free_oneway_space += size; + } + + // This computes the range of pages that are no longer used by *any* allocated range. The + // caller will mark them as unused, which means that they can be freed if the system comes + // under memory pressure. + let mut freed_range = FreedRange::interior_pages(offset, size); + #[expect(clippy::collapsible_if)] // reads better like this + if offset % PAGE_SIZE != 0 { + if i == 0 || self.ranges[i - 1].endpoint() <= (offset & PAGE_MASK) { + freed_range.start_page_idx -= 1; + } + } + if range.endpoint() % PAGE_SIZE != 0 { + let page_after = (range.endpoint() & PAGE_MASK) + PAGE_SIZE; + if i + 1 == self.ranges.len() || page_after <= self.ranges[i + 1].offset { + freed_range.end_page_idx += 1; + } + } + + self.ranges.remove(i)?; + Ok(freed_range) + } + + pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result { + // This could use a binary search, but linear scans are usually faster for small arrays. + let range = self + .ranges + .iter_mut() + .find(|range| range.offset == offset) + .ok_or(ENOENT)?; + + let DescriptorState::Reserved(reservation) = &range.state else { + return Err(ENOENT); + }; + + range.state = DescriptorState::Allocated(reservation.clone().allocate(data.take())); + Ok(()) + } + + pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> { + // This could use a binary search, but linear scans are usually faster for small arrays. + let range = self + .ranges + .iter_mut() + .find(|range| range.offset == offset) + .ok_or(ENOENT)?; + + let DescriptorState::Allocated(allocation) = &mut range.state else { + return Err(ENOENT); + }; + + let data = allocation.take(); + let debug_id = allocation.reservation.debug_id; + range.state = DescriptorState::Reserved(allocation.reservation.clone()); + Ok((range.size, debug_id, data)) + } + + pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) { + for range in self.ranges.iter_mut() { + if let DescriptorState::Allocated(allocation) = &mut range.state { + callback( + range.offset, + range.size, + allocation.reservation.debug_id, + allocation.data.take(), + ); + } + } + } +} + +pub(crate) struct EmptyArrayAlloc<T> { + ranges: KVec<Range<T>>, +} + +impl<T> EmptyArrayAlloc<T> { + pub(crate) fn try_new(capacity: usize) -> Result<Self> { + Ok(Self { + ranges: KVec::with_capacity(capacity, GFP_KERNEL)?, + }) + } +} diff --git a/drivers/android/binder/range_alloc/mod.rs b/drivers/android/binder/range_alloc/mod.rs new file mode 100644 index 000000000000..2301e2bc1a1f --- /dev/null +++ b/drivers/android/binder/range_alloc/mod.rs @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{page::PAGE_SIZE, prelude::*, seq_file::SeqFile, task::Pid}; + +mod tree; +use self::tree::{FromArrayAllocs, ReserveNewTreeAlloc, TreeRangeAllocator}; + +mod array; +use self::array::{ArrayRangeAllocator, EmptyArrayAlloc}; + +enum DescriptorState<T> { + Reserved(Reservation), + Allocated(Allocation<T>), +} + +impl<T> DescriptorState<T> { + fn new(is_oneway: bool, debug_id: usize, pid: Pid) -> Self { + DescriptorState::Reserved(Reservation { + debug_id, + is_oneway, + pid, + }) + } + + fn pid(&self) -> Pid { + match self { + DescriptorState::Reserved(inner) => inner.pid, + DescriptorState::Allocated(inner) => inner.reservation.pid, + } + } + + fn is_oneway(&self) -> bool { + match self { + DescriptorState::Reserved(inner) => inner.is_oneway, + DescriptorState::Allocated(inner) => inner.reservation.is_oneway, + } + } +} + +#[derive(Clone)] +struct Reservation { + debug_id: usize, + is_oneway: bool, + pid: Pid, +} + +impl Reservation { + fn allocate<T>(self, data: Option<T>) -> Allocation<T> { + Allocation { + data, + reservation: self, + } + } +} + +struct Allocation<T> { + reservation: Reservation, + data: Option<T>, +} + +impl<T> Allocation<T> { + fn deallocate(self) -> (Reservation, Option<T>) { + (self.reservation, self.data) + } + + fn debug_id(&self) -> usize { + self.reservation.debug_id + } + + fn take(&mut self) -> Option<T> { + self.data.take() + } +} + +/// The array implementation must switch to the tree if it wants to go beyond this number of +/// ranges. +const TREE_THRESHOLD: usize = 8; + +/// Represents a range of pages that have just become completely free. +#[derive(Copy, Clone)] +pub(crate) struct FreedRange { + pub(crate) start_page_idx: usize, + pub(crate) end_page_idx: usize, +} + +impl FreedRange { + fn interior_pages(offset: usize, size: usize) -> FreedRange { + FreedRange { + // Divide round up + start_page_idx: offset.div_ceil(PAGE_SIZE), + // Divide round down + end_page_idx: (offset + size) / PAGE_SIZE, + } + } +} + +struct Range<T> { + offset: usize, + size: usize, + state: DescriptorState<T>, +} + +impl<T> Range<T> { + fn endpoint(&self) -> usize { + self.offset + self.size + } +} + +pub(crate) struct RangeAllocator<T> { + inner: Impl<T>, +} + +enum Impl<T> { + Empty(usize), + Array(ArrayRangeAllocator<T>), + Tree(TreeRangeAllocator<T>), +} + +impl<T> RangeAllocator<T> { + pub(crate) fn new(size: usize) -> Self { + Self { + inner: Impl::Empty(size), + } + } + + pub(crate) fn free_oneway_space(&self) -> usize { + match &self.inner { + Impl::Empty(size) => size / 2, + Impl::Array(array) => array.free_oneway_space(), + Impl::Tree(tree) => tree.free_oneway_space(), + } + } + + pub(crate) fn count_buffers(&self) -> usize { + match &self.inner { + Impl::Empty(_size) => 0, + Impl::Array(array) => array.count_buffers(), + Impl::Tree(tree) => tree.count_buffers(), + } + } + + pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> { + match &self.inner { + Impl::Empty(_size) => Ok(()), + Impl::Array(array) => array.debug_print(m), + Impl::Tree(tree) => tree.debug_print(m), + } + } + + /// Try to reserve a new buffer, using the provided allocation if necessary. + pub(crate) fn reserve_new(&mut self, mut args: ReserveNewArgs<T>) -> Result<ReserveNew<T>> { + match &mut self.inner { + Impl::Empty(size) => { + let empty_array = match args.empty_array_alloc.take() { + Some(empty_array) => ArrayRangeAllocator::new(*size, empty_array), + None => { + return Ok(ReserveNew::NeedAlloc(ReserveNewNeedAlloc { + args, + need_empty_array_alloc: true, + need_new_tree_alloc: false, + need_tree_alloc: false, + })) + } + }; + + self.inner = Impl::Array(empty_array); + self.reserve_new(args) + } + Impl::Array(array) if array.is_full() => { + let allocs = match args.new_tree_alloc { + Some(ref mut allocs) => allocs, + None => { + return Ok(ReserveNew::NeedAlloc(ReserveNewNeedAlloc { + args, + need_empty_array_alloc: false, + need_new_tree_alloc: true, + need_tree_alloc: true, + })) + } + }; + + let new_tree = + TreeRangeAllocator::from_array(array.total_size(), &mut array.ranges, allocs); + + self.inner = Impl::Tree(new_tree); + self.reserve_new(args) + } + Impl::Array(array) => { + let offset = + array.reserve_new(args.debug_id, args.size, args.is_oneway, args.pid)?; + Ok(ReserveNew::Success(ReserveNewSuccess { + offset, + oneway_spam_detected: false, + _empty_array_alloc: args.empty_array_alloc, + _new_tree_alloc: args.new_tree_alloc, + _tree_alloc: args.tree_alloc, + })) + } + Impl::Tree(tree) => { + let alloc = match args.tree_alloc { + Some(alloc) => alloc, + None => { + return Ok(ReserveNew::NeedAlloc(ReserveNewNeedAlloc { + args, + need_empty_array_alloc: false, + need_new_tree_alloc: false, + need_tree_alloc: true, + })); + } + }; + let (offset, oneway_spam_detected) = + tree.reserve_new(args.debug_id, args.size, args.is_oneway, args.pid, alloc)?; + Ok(ReserveNew::Success(ReserveNewSuccess { + offset, + oneway_spam_detected, + _empty_array_alloc: args.empty_array_alloc, + _new_tree_alloc: args.new_tree_alloc, + _tree_alloc: None, + })) + } + } + } + + /// Deletes the allocations at `offset`. + pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> { + match &mut self.inner { + Impl::Empty(_size) => Err(EINVAL), + Impl::Array(array) => array.reservation_abort(offset), + Impl::Tree(tree) => { + let freed_range = tree.reservation_abort(offset)?; + if tree.is_empty() { + self.inner = Impl::Empty(tree.total_size()); + } + Ok(freed_range) + } + } + } + + /// Called when an allocation is no longer in use by the kernel. + /// + /// The value in `data` will be stored, if any. A mutable reference is used to avoid dropping + /// the `T` when an error is returned. + pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result { + match &mut self.inner { + Impl::Empty(_size) => Err(EINVAL), + Impl::Array(array) => array.reservation_commit(offset, data), + Impl::Tree(tree) => tree.reservation_commit(offset, data), + } + } + + /// Called when the kernel starts using an allocation. + /// + /// Returns the size of the existing entry and the data associated with it. + pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> { + match &mut self.inner { + Impl::Empty(_size) => Err(EINVAL), + Impl::Array(array) => array.reserve_existing(offset), + Impl::Tree(tree) => tree.reserve_existing(offset), + } + } + + /// Call the provided callback at every allocated region. + /// + /// This destroys the range allocator. Used only during shutdown. + pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) { + match &mut self.inner { + Impl::Empty(_size) => {} + Impl::Array(array) => array.take_for_each(callback), + Impl::Tree(tree) => tree.take_for_each(callback), + } + } +} + +/// The arguments for `reserve_new`. +#[derive(Default)] +pub(crate) struct ReserveNewArgs<T> { + pub(crate) size: usize, + pub(crate) is_oneway: bool, + pub(crate) debug_id: usize, + pub(crate) pid: Pid, + pub(crate) empty_array_alloc: Option<EmptyArrayAlloc<T>>, + pub(crate) new_tree_alloc: Option<FromArrayAllocs<T>>, + pub(crate) tree_alloc: Option<ReserveNewTreeAlloc<T>>, +} + +/// The return type of `ReserveNew`. +pub(crate) enum ReserveNew<T> { + Success(ReserveNewSuccess<T>), + NeedAlloc(ReserveNewNeedAlloc<T>), +} + +/// Returned by `reserve_new` when the reservation was successul. +pub(crate) struct ReserveNewSuccess<T> { + pub(crate) offset: usize, + pub(crate) oneway_spam_detected: bool, + + // If the user supplied an allocation that we did not end up using, then we return it here. + // The caller will kfree it outside of the lock. + _empty_array_alloc: Option<EmptyArrayAlloc<T>>, + _new_tree_alloc: Option<FromArrayAllocs<T>>, + _tree_alloc: Option<ReserveNewTreeAlloc<T>>, +} + +/// Returned by `reserve_new` to request the caller to make an allocation before calling the method +/// again. +pub(crate) struct ReserveNewNeedAlloc<T> { + args: ReserveNewArgs<T>, + need_empty_array_alloc: bool, + need_new_tree_alloc: bool, + need_tree_alloc: bool, +} + +impl<T> ReserveNewNeedAlloc<T> { + /// Make the necessary allocations for another call to `reserve_new`. + pub(crate) fn make_alloc(mut self) -> Result<ReserveNewArgs<T>> { + if self.need_empty_array_alloc && self.args.empty_array_alloc.is_none() { + self.args.empty_array_alloc = Some(EmptyArrayAlloc::try_new(TREE_THRESHOLD)?); + } + if self.need_new_tree_alloc && self.args.new_tree_alloc.is_none() { + self.args.new_tree_alloc = Some(FromArrayAllocs::try_new(TREE_THRESHOLD)?); + } + if self.need_tree_alloc && self.args.tree_alloc.is_none() { + self.args.tree_alloc = Some(ReserveNewTreeAlloc::try_new()?); + } + Ok(self.args) + } +} diff --git a/drivers/android/binder/range_alloc/tree.rs b/drivers/android/binder/range_alloc/tree.rs new file mode 100644 index 000000000000..7b1a248fcb02 --- /dev/null +++ b/drivers/android/binder/range_alloc/tree.rs @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::{ + page::PAGE_SIZE, + prelude::*, + rbtree::{RBTree, RBTreeNode, RBTreeNodeReservation}, + seq_file::SeqFile, + seq_print, + task::Pid, +}; + +use crate::range_alloc::{DescriptorState, FreedRange, Range}; + +/// Keeps track of allocations in a process' mmap. +/// +/// Each process has an mmap where the data for incoming transactions will be placed. This struct +/// keeps track of allocations made in the mmap. For each allocation, we store a descriptor that +/// has metadata related to the allocation. We also keep track of available free space. +pub(super) struct TreeRangeAllocator<T> { + /// This collection contains descriptors for *both* ranges containing an allocation, *and* free + /// ranges between allocations. The free ranges get merged, so there are never two free ranges + /// next to each other. + tree: RBTree<usize, Descriptor<T>>, + /// Contains an entry for every free range in `self.tree`. This tree sorts the ranges by size, + /// letting us look up the smallest range whose size is at least some lower bound. + free_tree: RBTree<FreeKey, ()>, + size: usize, + free_oneway_space: usize, +} + +impl<T> TreeRangeAllocator<T> { + pub(crate) fn from_array( + size: usize, + ranges: &mut KVec<Range<T>>, + alloc: &mut FromArrayAllocs<T>, + ) -> Self { + let mut tree = TreeRangeAllocator { + tree: RBTree::new(), + free_tree: RBTree::new(), + size, + free_oneway_space: size / 2, + }; + + let mut free_offset = 0; + for range in ranges.drain_all() { + let free_size = range.offset - free_offset; + if free_size > 0 { + let free_node = alloc.free_tree.pop().unwrap(); + tree.free_tree + .insert(free_node.into_node((free_size, free_offset), ())); + let tree_node = alloc.tree.pop().unwrap(); + tree.tree.insert( + tree_node.into_node(free_offset, Descriptor::new(free_offset, free_size)), + ); + } + free_offset = range.endpoint(); + + if range.state.is_oneway() { + tree.free_oneway_space = tree.free_oneway_space.saturating_sub(range.size); + } + + let free_res = alloc.free_tree.pop().unwrap(); + let tree_node = alloc.tree.pop().unwrap(); + let mut desc = Descriptor::new(range.offset, range.size); + desc.state = Some((range.state, free_res)); + tree.tree.insert(tree_node.into_node(range.offset, desc)); + } + + // After the last range, we may need a free range. + if free_offset < size { + let free_size = size - free_offset; + let free_node = alloc.free_tree.pop().unwrap(); + tree.free_tree + .insert(free_node.into_node((free_size, free_offset), ())); + let tree_node = alloc.tree.pop().unwrap(); + tree.tree + .insert(tree_node.into_node(free_offset, Descriptor::new(free_offset, free_size))); + } + + tree + } + + pub(crate) fn is_empty(&self) -> bool { + let mut tree_iter = self.tree.values(); + // There's always at least one range, because index zero is either the start of a free or + // allocated range. + let first_value = tree_iter.next().unwrap(); + if tree_iter.next().is_some() { + // There are never two free ranges next to each other, so if there is more than one + // descriptor, then at least one of them must hold an allocated range. + return false; + } + // There is only one descriptor. Return true if it is for a free range. + first_value.state.is_none() + } + + pub(crate) fn total_size(&self) -> usize { + self.size + } + + pub(crate) fn free_oneway_space(&self) -> usize { + self.free_oneway_space + } + + pub(crate) fn count_buffers(&self) -> usize { + self.tree + .values() + .filter(|desc| desc.state.is_some()) + .count() + } + + pub(crate) fn debug_print(&self, m: &SeqFile) -> Result<()> { + for desc in self.tree.values() { + let state = match &desc.state { + Some(state) => &state.0, + None => continue, + }; + seq_print!( + m, + " buffer: {} size {} pid {}", + desc.offset, + desc.size, + state.pid(), + ); + if state.is_oneway() { + seq_print!(m, " oneway"); + } + match state { + DescriptorState::Reserved(_res) => { + seq_print!(m, " reserved\n"); + } + DescriptorState::Allocated(_alloc) => { + seq_print!(m, " allocated\n"); + } + } + } + Ok(()) + } + + fn find_best_match(&mut self, size: usize) -> Option<&mut Descriptor<T>> { + let free_cursor = self.free_tree.cursor_lower_bound(&(size, 0))?; + let ((_, offset), ()) = free_cursor.current(); + self.tree.get_mut(offset) + } + + /// Try to reserve a new buffer, using the provided allocation if necessary. + pub(crate) fn reserve_new( + &mut self, + debug_id: usize, + size: usize, + is_oneway: bool, + pid: Pid, + alloc: ReserveNewTreeAlloc<T>, + ) -> Result<(usize, bool)> { + // Compute new value of free_oneway_space, which is set only on success. + let new_oneway_space = if is_oneway { + match self.free_oneway_space.checked_sub(size) { + Some(new_oneway_space) => new_oneway_space, + None => return Err(ENOSPC), + } + } else { + self.free_oneway_space + }; + + // Start detecting spammers once we have less than 20% + // of async space left (which is less than 10% of total + // buffer size). + // + // (This will short-circut, so `low_oneway_space` is + // only called when necessary.) + let oneway_spam_detected = + is_oneway && new_oneway_space < self.size / 10 && self.low_oneway_space(pid); + + let (found_size, found_off, tree_node, free_tree_node) = match self.find_best_match(size) { + None => { + pr_warn!("ENOSPC from range_alloc.reserve_new - size: {}", size); + return Err(ENOSPC); + } + Some(desc) => { + let found_size = desc.size; + let found_offset = desc.offset; + + // In case we need to break up the descriptor + let new_desc = Descriptor::new(found_offset + size, found_size - size); + let (tree_node, free_tree_node, desc_node_res) = alloc.initialize(new_desc); + + desc.state = Some(( + DescriptorState::new(is_oneway, debug_id, pid), + desc_node_res, + )); + desc.size = size; + + (found_size, found_offset, tree_node, free_tree_node) + } + }; + self.free_oneway_space = new_oneway_space; + self.free_tree.remove(&(found_size, found_off)); + + if found_size != size { + self.tree.insert(tree_node); + self.free_tree.insert(free_tree_node); + } + + Ok((found_off, oneway_spam_detected)) + } + + pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result<FreedRange> { + let mut cursor = self.tree.cursor_lower_bound(&offset).ok_or_else(|| { + pr_warn!( + "EINVAL from range_alloc.reservation_abort - offset: {}", + offset + ); + EINVAL + })?; + + let (_, desc) = cursor.current_mut(); + + if desc.offset != offset { + pr_warn!( + "EINVAL from range_alloc.reservation_abort - offset: {}", + offset + ); + return Err(EINVAL); + } + + let (reservation, free_node_res) = desc.try_change_state(|state| match state { + Some((DescriptorState::Reserved(reservation), free_node_res)) => { + (None, Ok((reservation, free_node_res))) + } + None => { + pr_warn!( + "EINVAL from range_alloc.reservation_abort - offset: {}", + offset + ); + (None, Err(EINVAL)) + } + allocated => { + pr_warn!( + "EPERM from range_alloc.reservation_abort - offset: {}", + offset + ); + (allocated, Err(EPERM)) + } + })?; + + let mut size = desc.size; + let mut offset = desc.offset; + let free_oneway_space_add = if reservation.is_oneway { size } else { 0 }; + + self.free_oneway_space += free_oneway_space_add; + + let mut freed_range = FreedRange::interior_pages(offset, size); + // Compute how large the next free region needs to be to include one more page in + // the newly freed range. + let add_next_page_needed = match (offset + size) % PAGE_SIZE { + 0 => usize::MAX, + unalign => PAGE_SIZE - unalign, + }; + // Compute how large the previous free region needs to be to include one more page + // in the newly freed range. + let add_prev_page_needed = match offset % PAGE_SIZE { + 0 => usize::MAX, + unalign => unalign, + }; + + // Merge next into current if next is free + let remove_next = match cursor.peek_next() { + Some((_, next)) if next.state.is_none() => { + if next.size >= add_next_page_needed { + freed_range.end_page_idx += 1; + } + self.free_tree.remove(&(next.size, next.offset)); + size += next.size; + true + } + _ => false, + }; + + if remove_next { + let (_, desc) = cursor.current_mut(); + desc.size = size; + cursor.remove_next(); + } + + // Merge current into prev if prev is free + match cursor.peek_prev_mut() { + Some((_, prev)) if prev.state.is_none() => { + if prev.size >= add_prev_page_needed { + freed_range.start_page_idx -= 1; + } + // merge previous with current, remove current + self.free_tree.remove(&(prev.size, prev.offset)); + offset = prev.offset; + size += prev.size; + prev.size = size; + cursor.remove_current(); + } + _ => {} + }; + + self.free_tree + .insert(free_node_res.into_node((size, offset), ())); + + Ok(freed_range) + } + + pub(crate) fn reservation_commit(&mut self, offset: usize, data: &mut Option<T>) -> Result { + let desc = self.tree.get_mut(&offset).ok_or(ENOENT)?; + + desc.try_change_state(|state| match state { + Some((DescriptorState::Reserved(reservation), free_node_res)) => ( + Some(( + DescriptorState::Allocated(reservation.allocate(data.take())), + free_node_res, + )), + Ok(()), + ), + other => (other, Err(ENOENT)), + }) + } + + /// Takes an entry at the given offset from [`DescriptorState::Allocated`] to + /// [`DescriptorState::Reserved`]. + /// + /// Returns the size of the existing entry and the data associated with it. + pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option<T>)> { + let desc = self.tree.get_mut(&offset).ok_or_else(|| { + pr_warn!( + "ENOENT from range_alloc.reserve_existing - offset: {}", + offset + ); + ENOENT + })?; + + let (debug_id, data) = desc.try_change_state(|state| match state { + Some((DescriptorState::Allocated(allocation), free_node_res)) => { + let (reservation, data) = allocation.deallocate(); + let debug_id = reservation.debug_id; + ( + Some((DescriptorState::Reserved(reservation), free_node_res)), + Ok((debug_id, data)), + ) + } + other => { + pr_warn!( + "ENOENT from range_alloc.reserve_existing - offset: {}", + offset + ); + (other, Err(ENOENT)) + } + })?; + + Ok((desc.size, debug_id, data)) + } + + /// Call the provided callback at every allocated region. + /// + /// This destroys the range allocator. Used only during shutdown. + pub(crate) fn take_for_each<F: Fn(usize, usize, usize, Option<T>)>(&mut self, callback: F) { + for (_, desc) in self.tree.iter_mut() { + if let Some((DescriptorState::Allocated(allocation), _)) = &mut desc.state { + callback( + desc.offset, + desc.size, + allocation.debug_id(), + allocation.take(), + ); + } + } + } + + /// Find the amount and size of buffers allocated by the current caller. + /// + /// The idea is that once we cross the threshold, whoever is responsible + /// for the low async space is likely to try to send another async transaction, + /// and at some point we'll catch them in the act. This is more efficient + /// than keeping a map per pid. + fn low_oneway_space(&self, calling_pid: Pid) -> bool { + let mut total_alloc_size = 0; + let mut num_buffers = 0; + for (_, desc) in self.tree.iter() { + if let Some((state, _)) = &desc.state { + if state.is_oneway() && state.pid() == calling_pid { + total_alloc_size += desc.size; + num_buffers += 1; + } + } + } + + // Warn if this pid has more than 50 transactions, or more than 50% of + // async space (which is 25% of total buffer size). Oneway spam is only + // detected when the threshold is exceeded. + num_buffers > 50 || total_alloc_size > self.size / 4 + } +} + +type TreeDescriptorState<T> = (DescriptorState<T>, FreeNodeRes); +struct Descriptor<T> { + size: usize, + offset: usize, + state: Option<TreeDescriptorState<T>>, +} + +impl<T> Descriptor<T> { + fn new(offset: usize, size: usize) -> Self { + Self { + size, + offset, + state: None, + } + } + + fn try_change_state<F, Data>(&mut self, f: F) -> Result<Data> + where + F: FnOnce(Option<TreeDescriptorState<T>>) -> (Option<TreeDescriptorState<T>>, Result<Data>), + { + let (new_state, result) = f(self.state.take()); + self.state = new_state; + result + } +} + +// (Descriptor.size, Descriptor.offset) +type FreeKey = (usize, usize); +type FreeNodeRes = RBTreeNodeReservation<FreeKey, ()>; + +/// An allocation for use by `reserve_new`. +pub(crate) struct ReserveNewTreeAlloc<T> { + tree_node_res: RBTreeNodeReservation<usize, Descriptor<T>>, + free_tree_node_res: FreeNodeRes, + desc_node_res: FreeNodeRes, +} + +impl<T> ReserveNewTreeAlloc<T> { + pub(crate) fn try_new() -> Result<Self> { + let tree_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?; + let free_tree_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?; + let desc_node_res = RBTreeNodeReservation::new(GFP_KERNEL)?; + Ok(Self { + tree_node_res, + free_tree_node_res, + desc_node_res, + }) + } + + fn initialize( + self, + desc: Descriptor<T>, + ) -> ( + RBTreeNode<usize, Descriptor<T>>, + RBTreeNode<FreeKey, ()>, + FreeNodeRes, + ) { + let size = desc.size; + let offset = desc.offset; + ( + self.tree_node_res.into_node(offset, desc), + self.free_tree_node_res.into_node((size, offset), ()), + self.desc_node_res, + ) + } +} + +/// An allocation for creating a tree from an `ArrayRangeAllocator`. +pub(crate) struct FromArrayAllocs<T> { + tree: KVec<RBTreeNodeReservation<usize, Descriptor<T>>>, + free_tree: KVec<RBTreeNodeReservation<FreeKey, ()>>, +} + +impl<T> FromArrayAllocs<T> { + pub(crate) fn try_new(len: usize) -> Result<Self> { + let num_descriptors = 2 * len + 1; + + let mut tree = KVec::with_capacity(num_descriptors, GFP_KERNEL)?; + for _ in 0..num_descriptors { + tree.push(RBTreeNodeReservation::new(GFP_KERNEL)?, GFP_KERNEL)?; + } + + let mut free_tree = KVec::with_capacity(num_descriptors, GFP_KERNEL)?; + for _ in 0..num_descriptors { + free_tree.push(RBTreeNodeReservation::new(GFP_KERNEL)?, GFP_KERNEL)?; + } + + Ok(Self { tree, free_tree }) + } +} diff --git a/drivers/android/binder/rust_binder.h b/drivers/android/binder/rust_binder.h new file mode 100644 index 000000000000..31806890ed1a --- /dev/null +++ b/drivers/android/binder/rust_binder.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2025 Google, Inc. + */ + +#ifndef _LINUX_RUST_BINDER_H +#define _LINUX_RUST_BINDER_H + +#include <uapi/linux/android/binder.h> +#include <uapi/linux/android/binderfs.h> + +/* + * These symbols are exposed by `rust_binderfs.c` and exist here so that Rust + * Binder can call them. + */ +int init_rust_binderfs(void); + +struct dentry; +struct inode; +struct dentry *rust_binderfs_create_proc_file(struct inode *nodp, int pid); +void rust_binderfs_remove_file(struct dentry *dentry); + +#endif diff --git a/drivers/android/binder/rust_binder_events.c b/drivers/android/binder/rust_binder_events.c new file mode 100644 index 000000000000..488b1470060c --- /dev/null +++ b/drivers/android/binder/rust_binder_events.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* rust_binder_events.c + * + * Rust Binder tracepoints. + * + * Copyright 2025 Google LLC + */ + +#include "rust_binder.h" + +const char * const binder_command_strings[] = { + "BC_TRANSACTION", + "BC_REPLY", + "BC_ACQUIRE_RESULT", + "BC_FREE_BUFFER", + "BC_INCREFS", + "BC_ACQUIRE", + "BC_RELEASE", + "BC_DECREFS", + "BC_INCREFS_DONE", + "BC_ACQUIRE_DONE", + "BC_ATTEMPT_ACQUIRE", + "BC_REGISTER_LOOPER", + "BC_ENTER_LOOPER", + "BC_EXIT_LOOPER", + "BC_REQUEST_DEATH_NOTIFICATION", + "BC_CLEAR_DEATH_NOTIFICATION", + "BC_DEAD_BINDER_DONE", + "BC_TRANSACTION_SG", + "BC_REPLY_SG", +}; + +const char * const binder_return_strings[] = { + "BR_ERROR", + "BR_OK", + "BR_TRANSACTION", + "BR_REPLY", + "BR_ACQUIRE_RESULT", + "BR_DEAD_REPLY", + "BR_TRANSACTION_COMPLETE", + "BR_INCREFS", + "BR_ACQUIRE", + "BR_RELEASE", + "BR_DECREFS", + "BR_ATTEMPT_ACQUIRE", + "BR_NOOP", + "BR_SPAWN_LOOPER", + "BR_FINISHED", + "BR_DEAD_BINDER", + "BR_CLEAR_DEATH_NOTIFICATION_DONE", + "BR_FAILED_REPLY", + "BR_FROZEN_REPLY", + "BR_ONEWAY_SPAM_SUSPECT", + "BR_TRANSACTION_PENDING_FROZEN" +}; + +#define CREATE_TRACE_POINTS +#define CREATE_RUST_TRACE_POINTS +#include "rust_binder_events.h" diff --git a/drivers/android/binder/rust_binder_events.h b/drivers/android/binder/rust_binder_events.h new file mode 100644 index 000000000000..2f3efbf9dba6 --- /dev/null +++ b/drivers/android/binder/rust_binder_events.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Google, Inc. + */ + +#undef TRACE_SYSTEM +#undef TRACE_INCLUDE_FILE +#undef TRACE_INCLUDE_PATH +#define TRACE_SYSTEM rust_binder +#define TRACE_INCLUDE_FILE rust_binder_events +#define TRACE_INCLUDE_PATH ../drivers/android/binder + +#if !defined(_RUST_BINDER_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _RUST_BINDER_TRACE_H + +#include <linux/tracepoint.h> + +TRACE_EVENT(rust_binder_ioctl, + TP_PROTO(unsigned int cmd, unsigned long arg), + TP_ARGS(cmd, arg), + + TP_STRUCT__entry( + __field(unsigned int, cmd) + __field(unsigned long, arg) + ), + TP_fast_assign( + __entry->cmd = cmd; + __entry->arg = arg; + ), + TP_printk("cmd=0x%x arg=0x%lx", __entry->cmd, __entry->arg) +); + +#endif /* _RUST_BINDER_TRACE_H */ + +/* This part must be outside protection */ +#include <trace/define_trace.h> diff --git a/drivers/android/binder/rust_binder_internal.h b/drivers/android/binder/rust_binder_internal.h new file mode 100644 index 000000000000..78288fe7964d --- /dev/null +++ b/drivers/android/binder/rust_binder_internal.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* rust_binder_internal.h + * + * This file contains internal data structures used by Rust Binder. Mostly, + * these are type definitions used only by binderfs or things that Rust Binder + * define and export to binderfs. + * + * It does not include things exported by binderfs to Rust Binder since this + * file is not included as input to bindgen. + * + * Copyright (C) 2025 Google LLC. + */ + +#ifndef _LINUX_RUST_BINDER_INTERNAL_H +#define _LINUX_RUST_BINDER_INTERNAL_H + +#define RUST_BINDERFS_SUPER_MAGIC 0x6c6f6f71 + +#include <linux/seq_file.h> +#include <uapi/linux/android/binder.h> +#include <uapi/linux/android/binderfs.h> + +/* + * The internal data types in the Rust Binder driver are opaque to C, so we use + * void pointer typedefs for these types. + */ +typedef void *rust_binder_context; + +/** + * struct binder_device - information about a binder device node + * @minor: the minor number used by this device + * @ctx: the Rust Context used by this device, or null for binder-control + * + * This is used as the private data for files directly in binderfs, but not + * files in the binder_logs subdirectory. This struct owns a refcount on `ctx` + * and the entry for `minor` in `binderfs_minors`. For binder-control `ctx` is + * null. + */ +struct binder_device { + int minor; + rust_binder_context ctx; +}; + +int rust_binder_stats_show(struct seq_file *m, void *unused); +int rust_binder_state_show(struct seq_file *m, void *unused); +int rust_binder_transactions_show(struct seq_file *m, void *unused); +int rust_binder_proc_show(struct seq_file *m, void *pid); + +extern const struct file_operations rust_binder_fops; +rust_binder_context rust_binder_new_context(char *name); +void rust_binder_remove_context(rust_binder_context device); + +/** + * binderfs_mount_opts - mount options for binderfs + * @max: maximum number of allocatable binderfs binder devices + * @stats_mode: enable binder stats in binderfs. + */ +struct binderfs_mount_opts { + int max; + int stats_mode; +}; + +/** + * binderfs_info - information about a binderfs mount + * @ipc_ns: The ipc namespace the binderfs mount belongs to. + * @control_dentry: This records the dentry of this binderfs mount + * binder-control device. + * @root_uid: uid that needs to be used when a new binder device is + * created. + * @root_gid: gid that needs to be used when a new binder device is + * created. + * @mount_opts: The mount options in use. + * @device_count: The current number of allocated binder devices. + * @proc_log_dir: Pointer to the directory dentry containing process-specific + * logs. + */ +struct binderfs_info { + struct ipc_namespace *ipc_ns; + struct dentry *control_dentry; + kuid_t root_uid; + kgid_t root_gid; + struct binderfs_mount_opts mount_opts; + int device_count; + struct dentry *proc_log_dir; +}; + +#endif /* _LINUX_RUST_BINDER_INTERNAL_H */ diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs new file mode 100644 index 000000000000..6773b7c273ec --- /dev/null +++ b/drivers/android/binder/rust_binder_main.rs @@ -0,0 +1,627 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! Binder -- the Android IPC mechanism. +#![recursion_limit = "256"] +#![allow( + clippy::as_underscore, + clippy::ref_as_ptr, + clippy::ptr_as_ptr, + clippy::cast_lossless +)] + +use kernel::{ + bindings::{self, seq_file}, + fs::File, + list::{ListArc, ListArcSafe, ListLinksSelfPtr, TryNewListArc}, + prelude::*, + seq_file::SeqFile, + seq_print, + sync::poll::PollTable, + sync::Arc, + task::Pid, + transmute::AsBytes, + types::ForeignOwnable, + uaccess::UserSliceWriter, +}; + +use crate::{context::Context, page_range::Shrinker, process::Process, thread::Thread}; + +use core::{ + ptr::NonNull, + sync::atomic::{AtomicBool, AtomicUsize, Ordering}, +}; + +mod allocation; +mod context; +mod deferred_close; +mod defs; +mod error; +mod node; +mod page_range; +mod process; +mod range_alloc; +mod stats; +mod thread; +mod trace; +mod transaction; + +#[allow(warnings)] // generated bindgen code +mod binderfs { + use kernel::bindings::{dentry, inode}; + + extern "C" { + pub fn init_rust_binderfs() -> kernel::ffi::c_int; + } + extern "C" { + pub fn rust_binderfs_create_proc_file( + nodp: *mut inode, + pid: kernel::ffi::c_int, + ) -> *mut dentry; + } + extern "C" { + pub fn rust_binderfs_remove_file(dentry: *mut dentry); + } + pub type rust_binder_context = *mut kernel::ffi::c_void; + #[repr(C)] + #[derive(Copy, Clone)] + pub struct binder_device { + pub minor: kernel::ffi::c_int, + pub ctx: rust_binder_context, + } + impl Default for binder_device { + fn default() -> Self { + let mut s = ::core::mem::MaybeUninit::<Self>::uninit(); + unsafe { + ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } + } +} + +module! { + type: BinderModule, + name: "rust_binder", + authors: ["Wedson Almeida Filho", "Alice Ryhl"], + description: "Android Binder", + license: "GPL", +} + +fn next_debug_id() -> usize { + static NEXT_DEBUG_ID: AtomicUsize = AtomicUsize::new(0); + + NEXT_DEBUG_ID.fetch_add(1, Ordering::Relaxed) +} + +/// Provides a single place to write Binder return values via the +/// supplied `UserSliceWriter`. +pub(crate) struct BinderReturnWriter<'a> { + writer: UserSliceWriter, + thread: &'a Thread, +} + +impl<'a> BinderReturnWriter<'a> { + fn new(writer: UserSliceWriter, thread: &'a Thread) -> Self { + BinderReturnWriter { writer, thread } + } + + /// Write a return code back to user space. + /// Should be a `BR_` constant from [`defs`] e.g. [`defs::BR_TRANSACTION_COMPLETE`]. + fn write_code(&mut self, code: u32) -> Result { + stats::GLOBAL_STATS.inc_br(code); + self.thread.process.stats.inc_br(code); + self.writer.write(&code) + } + + /// Write something *other than* a return code to user space. + fn write_payload<T: AsBytes>(&mut self, payload: &T) -> Result { + self.writer.write(payload) + } + + fn len(&self) -> usize { + self.writer.len() + } +} + +/// Specifies how a type should be delivered to the read part of a BINDER_WRITE_READ ioctl. +/// +/// When a value is pushed to the todo list for a process or thread, it is stored as a trait object +/// with the type `Arc<dyn DeliverToRead>`. Trait objects are a Rust feature that lets you +/// implement dynamic dispatch over many different types. This lets us store many different types +/// in the todo list. +trait DeliverToRead: ListArcSafe + Send + Sync { + /// Performs work. Returns true if remaining work items in the queue should be processed + /// immediately, or false if it should return to caller before processing additional work + /// items. + fn do_work( + self: DArc<Self>, + thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool>; + + /// Cancels the given work item. This is called instead of [`DeliverToRead::do_work`] when work + /// won't be delivered. + fn cancel(self: DArc<Self>); + + /// Should we use `wake_up_interruptible_sync` or `wake_up_interruptible` when scheduling this + /// work item? + /// + /// Generally only set to true for non-oneway transactions. + fn should_sync_wakeup(&self) -> bool; + + fn debug_print(&self, m: &SeqFile, prefix: &str, transaction_prefix: &str) -> Result<()>; +} + +// Wrapper around a `DeliverToRead` with linked list links. +#[pin_data] +struct DTRWrap<T: ?Sized> { + #[pin] + links: ListLinksSelfPtr<DTRWrap<dyn DeliverToRead>>, + #[pin] + wrapped: T, +} +kernel::list::impl_list_arc_safe! { + impl{T: ListArcSafe + ?Sized} ListArcSafe<0> for DTRWrap<T> { + tracked_by wrapped: T; + } +} +kernel::list::impl_list_item! { + impl ListItem<0> for DTRWrap<dyn DeliverToRead> { + using ListLinksSelfPtr { self.links }; + } +} + +impl<T: ?Sized> core::ops::Deref for DTRWrap<T> { + type Target = T; + fn deref(&self) -> &T { + &self.wrapped + } +} + +type DArc<T> = kernel::sync::Arc<DTRWrap<T>>; +type DLArc<T> = kernel::list::ListArc<DTRWrap<T>>; + +impl<T: ListArcSafe> DTRWrap<T> { + fn new(val: impl PinInit<T>) -> impl PinInit<Self> { + pin_init!(Self { + links <- ListLinksSelfPtr::new(), + wrapped <- val, + }) + } + + fn arc_try_new(val: T) -> Result<DLArc<T>, kernel::alloc::AllocError> { + ListArc::pin_init( + try_pin_init!(Self { + links <- ListLinksSelfPtr::new(), + wrapped: val, + }), + GFP_KERNEL, + ) + .map_err(|_| kernel::alloc::AllocError) + } + + fn arc_pin_init(init: impl PinInit<T>) -> Result<DLArc<T>, kernel::error::Error> { + ListArc::pin_init( + try_pin_init!(Self { + links <- ListLinksSelfPtr::new(), + wrapped <- init, + }), + GFP_KERNEL, + ) + } +} + +struct DeliverCode { + code: u32, + skip: AtomicBool, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for DeliverCode { untracked; } +} + +impl DeliverCode { + fn new(code: u32) -> Self { + Self { + code, + skip: AtomicBool::new(false), + } + } + + /// Disable this DeliverCode and make it do nothing. + /// + /// This is used instead of removing it from the work list, since `LinkedList::remove` is + /// unsafe, whereas this method is not. + fn skip(&self) { + self.skip.store(true, Ordering::Relaxed); + } +} + +impl DeliverToRead for DeliverCode { + fn do_work( + self: DArc<Self>, + _thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + if !self.skip.load(Ordering::Relaxed) { + writer.write_code(self.code)?; + } + Ok(true) + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + seq_print!(m, "{}", prefix); + if self.skip.load(Ordering::Relaxed) { + seq_print!(m, "(skipped) "); + } + if self.code == defs::BR_TRANSACTION_COMPLETE { + seq_print!(m, "transaction complete\n"); + } else { + seq_print!(m, "transaction error: {}\n", self.code); + } + Ok(()) + } +} + +fn ptr_align(value: usize) -> Option<usize> { + let size = core::mem::size_of::<usize>() - 1; + Some(value.checked_add(size)? & !size) +} + +// SAFETY: We call register in `init`. +static BINDER_SHRINKER: Shrinker = unsafe { Shrinker::new() }; + +struct BinderModule {} + +impl kernel::Module for BinderModule { + fn init(_module: &'static kernel::ThisModule) -> Result<Self> { + // SAFETY: The module initializer never runs twice, so we only call this once. + unsafe { crate::context::CONTEXTS.init() }; + + pr_warn!("Loaded Rust Binder."); + + BINDER_SHRINKER.register(kernel::c_str!("android-binder"))?; + + // SAFETY: The module is being loaded, so we can initialize binderfs. + unsafe { kernel::error::to_result(binderfs::init_rust_binderfs())? }; + + Ok(Self {}) + } +} + +/// Makes the inner type Sync. +#[repr(transparent)] +pub struct AssertSync<T>(T); +// SAFETY: Used only to insert `file_operations` into a global, which is safe. +unsafe impl<T> Sync for AssertSync<T> {} + +/// File operations that rust_binderfs.c can use. +#[no_mangle] +#[used] +pub static rust_binder_fops: AssertSync<kernel::bindings::file_operations> = { + // SAFETY: All zeroes is safe for the `file_operations` type. + let zeroed_ops = unsafe { core::mem::MaybeUninit::zeroed().assume_init() }; + + let ops = kernel::bindings::file_operations { + owner: THIS_MODULE.as_ptr(), + poll: Some(rust_binder_poll), + unlocked_ioctl: Some(rust_binder_unlocked_ioctl), + compat_ioctl: Some(rust_binder_compat_ioctl), + mmap: Some(rust_binder_mmap), + open: Some(rust_binder_open), + release: Some(rust_binder_release), + flush: Some(rust_binder_flush), + ..zeroed_ops + }; + AssertSync(ops) +}; + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_new_context( + name: *const kernel::ffi::c_char, +) -> *mut kernel::ffi::c_void { + // SAFETY: The caller will always provide a valid c string here. + let name = unsafe { kernel::str::CStr::from_char_ptr(name) }; + match Context::new(name) { + Ok(ctx) => Arc::into_foreign(ctx), + Err(_err) => core::ptr::null_mut(), + } +} + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_remove_context(device: *mut kernel::ffi::c_void) { + if !device.is_null() { + // SAFETY: The caller ensures that the `device` pointer came from a previous call to + // `rust_binder_new_device`. + let ctx = unsafe { Arc::<Context>::from_foreign(device) }; + ctx.deregister(); + drop(ctx); + } +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_open( + inode: *mut bindings::inode, + file_ptr: *mut bindings::file, +) -> kernel::ffi::c_int { + // SAFETY: The `rust_binderfs.c` file ensures that `i_private` is set to a + // `struct binder_device`. + let device = unsafe { (*inode).i_private } as *const binderfs::binder_device; + + assert!(!device.is_null()); + + // SAFETY: The `rust_binderfs.c` file ensures that `device->ctx` holds a binder context when + // using the rust binder fops. + let ctx = unsafe { Arc::<Context>::borrow((*device).ctx) }; + + // SAFETY: The caller provides a valid file pointer to a new `struct file`. + let file = unsafe { File::from_raw_file(file_ptr) }; + let process = match Process::open(ctx, file) { + Ok(process) => process, + Err(err) => return err.to_errno(), + }; + + // SAFETY: This is an `inode` for a newly created binder file. + match unsafe { BinderfsProcFile::new(inode, process.task.pid()) } { + Ok(Some(file)) => process.inner.lock().binderfs_file = Some(file), + Ok(None) => { /* pid already exists */ } + Err(err) => return err.to_errno(), + } + + // SAFETY: This file is associated with Rust binder, so we own the `private_data` field. + unsafe { (*file_ptr).private_data = process.into_foreign() }; + 0 +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_release( + _inode: *mut bindings::inode, + file: *mut bindings::file, +) -> kernel::ffi::c_int { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let process = unsafe { Arc::<Process>::from_foreign((*file).private_data) }; + // SAFETY: The caller ensures that the file is valid. + let file = unsafe { File::from_raw_file(file) }; + Process::release(process, file); + 0 +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_compat_ioctl( + file: *mut bindings::file, + cmd: kernel::ffi::c_uint, + arg: kernel::ffi::c_ulong, +) -> kernel::ffi::c_long { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let f = unsafe { Arc::<Process>::borrow((*file).private_data) }; + // SAFETY: The caller ensures that the file is valid. + match Process::compat_ioctl(f, unsafe { File::from_raw_file(file) }, cmd as _, arg as _) { + Ok(()) => 0, + Err(err) => err.to_errno() as isize, + } +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_unlocked_ioctl( + file: *mut bindings::file, + cmd: kernel::ffi::c_uint, + arg: kernel::ffi::c_ulong, +) -> kernel::ffi::c_long { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let f = unsafe { Arc::<Process>::borrow((*file).private_data) }; + // SAFETY: The caller ensures that the file is valid. + match Process::ioctl(f, unsafe { File::from_raw_file(file) }, cmd as _, arg as _) { + Ok(()) => 0, + Err(err) => err.to_errno() as isize, + } +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_mmap( + file: *mut bindings::file, + vma: *mut bindings::vm_area_struct, +) -> kernel::ffi::c_int { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let f = unsafe { Arc::<Process>::borrow((*file).private_data) }; + // SAFETY: The caller ensures that the vma is valid. + let area = unsafe { kernel::mm::virt::VmaNew::from_raw(vma) }; + // SAFETY: The caller ensures that the file is valid. + match Process::mmap(f, unsafe { File::from_raw_file(file) }, area) { + Ok(()) => 0, + Err(err) => err.to_errno(), + } +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_poll( + file: *mut bindings::file, + wait: *mut bindings::poll_table_struct, +) -> bindings::__poll_t { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let f = unsafe { Arc::<Process>::borrow((*file).private_data) }; + // SAFETY: The caller ensures that the file is valid. + let fileref = unsafe { File::from_raw_file(file) }; + // SAFETY: The caller ensures that the `PollTable` is valid. + match Process::poll(f, fileref, unsafe { PollTable::from_raw(wait) }) { + Ok(v) => v, + Err(_) => bindings::POLLERR, + } +} + +/// # Safety +/// Only called by binderfs. +unsafe extern "C" fn rust_binder_flush( + file: *mut bindings::file, + _id: bindings::fl_owner_t, +) -> kernel::ffi::c_int { + // SAFETY: We previously set `private_data` in `rust_binder_open`. + let f = unsafe { Arc::<Process>::borrow((*file).private_data) }; + match Process::flush(f) { + Ok(()) => 0, + Err(err) => err.to_errno(), + } +} + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_stats_show( + ptr: *mut seq_file, + _: *mut kernel::ffi::c_void, +) -> kernel::ffi::c_int { + // SAFETY: The caller ensures that the pointer is valid and exclusive for the duration in which + // this method is called. + let m = unsafe { SeqFile::from_raw(ptr) }; + if let Err(err) = rust_binder_stats_show_impl(m) { + seq_print!(m, "failed to generate state: {:?}\n", err); + } + 0 +} + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_state_show( + ptr: *mut seq_file, + _: *mut kernel::ffi::c_void, +) -> kernel::ffi::c_int { + // SAFETY: The caller ensures that the pointer is valid and exclusive for the duration in which + // this method is called. + let m = unsafe { SeqFile::from_raw(ptr) }; + if let Err(err) = rust_binder_state_show_impl(m) { + seq_print!(m, "failed to generate state: {:?}\n", err); + } + 0 +} + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_proc_show( + ptr: *mut seq_file, + _: *mut kernel::ffi::c_void, +) -> kernel::ffi::c_int { + // SAFETY: Accessing the private field of `seq_file` is okay. + let pid = (unsafe { (*ptr).private }) as usize as Pid; + // SAFETY: The caller ensures that the pointer is valid and exclusive for the duration in which + // this method is called. + let m = unsafe { SeqFile::from_raw(ptr) }; + if let Err(err) = rust_binder_proc_show_impl(m, pid) { + seq_print!(m, "failed to generate state: {:?}\n", err); + } + 0 +} + +/// # Safety +/// Only called by binderfs. +#[no_mangle] +unsafe extern "C" fn rust_binder_transactions_show( + ptr: *mut seq_file, + _: *mut kernel::ffi::c_void, +) -> kernel::ffi::c_int { + // SAFETY: The caller ensures that the pointer is valid and exclusive for the duration in which + // this method is called. + let m = unsafe { SeqFile::from_raw(ptr) }; + if let Err(err) = rust_binder_transactions_show_impl(m) { + seq_print!(m, "failed to generate state: {:?}\n", err); + } + 0 +} + +fn rust_binder_transactions_show_impl(m: &SeqFile) -> Result<()> { + seq_print!(m, "binder transactions:\n"); + let contexts = context::get_all_contexts()?; + for ctx in contexts { + let procs = ctx.get_all_procs()?; + for proc in procs { + proc.debug_print(m, &ctx, false)?; + seq_print!(m, "\n"); + } + } + Ok(()) +} + +fn rust_binder_stats_show_impl(m: &SeqFile) -> Result<()> { + seq_print!(m, "binder stats:\n"); + stats::GLOBAL_STATS.debug_print("", m); + let contexts = context::get_all_contexts()?; + for ctx in contexts { + let procs = ctx.get_all_procs()?; + for proc in procs { + proc.debug_print_stats(m, &ctx)?; + seq_print!(m, "\n"); + } + } + Ok(()) +} + +fn rust_binder_state_show_impl(m: &SeqFile) -> Result<()> { + seq_print!(m, "binder state:\n"); + let contexts = context::get_all_contexts()?; + for ctx in contexts { + let procs = ctx.get_all_procs()?; + for proc in procs { + proc.debug_print(m, &ctx, true)?; + seq_print!(m, "\n"); + } + } + Ok(()) +} + +fn rust_binder_proc_show_impl(m: &SeqFile, pid: Pid) -> Result<()> { + seq_print!(m, "binder proc state:\n"); + let contexts = context::get_all_contexts()?; + for ctx in contexts { + let procs = ctx.get_procs_with_pid(pid)?; + for proc in procs { + proc.debug_print(m, &ctx, true)?; + seq_print!(m, "\n"); + } + } + Ok(()) +} + +struct BinderfsProcFile(NonNull<bindings::dentry>); + +// SAFETY: Safe to drop any thread. +unsafe impl Send for BinderfsProcFile {} + +impl BinderfsProcFile { + /// # Safety + /// + /// Takes an inode from a newly created binder file. + unsafe fn new(nodp: *mut bindings::inode, pid: i32) -> Result<Option<Self>> { + // SAFETY: The caller passes an `inode` for a newly created binder file. + let dentry = unsafe { binderfs::rust_binderfs_create_proc_file(nodp, pid) }; + match kernel::error::from_err_ptr(dentry) { + Ok(dentry) => Ok(NonNull::new(dentry).map(Self)), + Err(err) if err == EEXIST => Ok(None), + Err(err) => Err(err), + } + } +} + +impl Drop for BinderfsProcFile { + fn drop(&mut self) { + // SAFETY: This is a dentry from `rust_binderfs_remove_file` that has not been deleted yet. + unsafe { binderfs::rust_binderfs_remove_file(self.0.as_ptr()) }; + } +} diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c new file mode 100644 index 000000000000..6b497146b698 --- /dev/null +++ b/drivers/android/binder/rust_binderfs.c @@ -0,0 +1,850 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/compiler_types.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/fsnotify.h> +#include <linux/gfp.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/ipc_namespace.h> +#include <linux/kdev_t.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/namei.h> +#include <linux/magic.h> +#include <linux/major.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/mount.h> +#include <linux/fs_parser.h> +#include <linux/sched.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/spinlock_types.h> +#include <linux/stddef.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <linux/user_namespace.h> +#include <linux/xarray.h> +#include <uapi/asm-generic/errno-base.h> +#include <uapi/linux/android/binder.h> +#include <uapi/linux/android/binderfs.h> + +#include "rust_binder.h" +#include "rust_binder_internal.h" + +#define FIRST_INODE 1 +#define SECOND_INODE 2 +#define INODE_OFFSET 3 +#define BINDERFS_MAX_MINOR (1U << MINORBITS) +/* Ensure that the initial ipc namespace always has devices available. */ +#define BINDERFS_MAX_MINOR_CAPPED (BINDERFS_MAX_MINOR - 4) + +DEFINE_SHOW_ATTRIBUTE(rust_binder_stats); +DEFINE_SHOW_ATTRIBUTE(rust_binder_state); +DEFINE_SHOW_ATTRIBUTE(rust_binder_transactions); +DEFINE_SHOW_ATTRIBUTE(rust_binder_proc); + +char *rust_binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES; +module_param_named(rust_devices, rust_binder_devices_param, charp, 0444); + +static dev_t binderfs_dev; +static DEFINE_MUTEX(binderfs_minors_mutex); +static DEFINE_IDA(binderfs_minors); + +enum binderfs_param { + Opt_max, + Opt_stats_mode, +}; + +enum binderfs_stats_mode { + binderfs_stats_mode_unset, + binderfs_stats_mode_global, +}; + +struct binder_features { + bool oneway_spam_detection; + bool extended_error; + bool freeze_notification; +}; + +static const struct constant_table binderfs_param_stats[] = { + { "global", binderfs_stats_mode_global }, + {} +}; + +static const struct fs_parameter_spec binderfs_fs_parameters[] = { + fsparam_u32("max", Opt_max), + fsparam_enum("stats", Opt_stats_mode, binderfs_param_stats), + {} +}; + +static struct binder_features binder_features = { + .oneway_spam_detection = true, + .extended_error = true, + .freeze_notification = true, +}; + +static inline struct binderfs_info *BINDERFS_SB(const struct super_block *sb) +{ + return sb->s_fs_info; +} + +/** + * binderfs_binder_device_create - allocate inode from super block of a + * binderfs mount + * @ref_inode: inode from wich the super block will be taken + * @userp: buffer to copy information about new device for userspace to + * @req: struct binderfs_device as copied from userspace + * + * This function allocates a new binder_device and reserves a new minor + * number for it. + * Minor numbers are limited and tracked globally in binderfs_minors. The + * function will stash a struct binder_device for the specific binder + * device in i_private of the inode. + * It will go on to allocate a new inode from the super block of the + * filesystem mount, stash a struct binder_device in its i_private field + * and attach a dentry to that inode. + * + * Return: 0 on success, negative errno on failure + */ +static int binderfs_binder_device_create(struct inode *ref_inode, + struct binderfs_device __user *userp, + struct binderfs_device *req) +{ + int minor, ret; + struct dentry *dentry, *root; + struct binder_device *device = NULL; + rust_binder_context ctx = NULL; + struct inode *inode = NULL; + struct super_block *sb = ref_inode->i_sb; + struct binderfs_info *info = sb->s_fs_info; +#if defined(CONFIG_IPC_NS) + bool use_reserve = (info->ipc_ns == &init_ipc_ns); +#else + bool use_reserve = true; +#endif + + /* Reserve new minor number for the new device. */ + mutex_lock(&binderfs_minors_mutex); + if (++info->device_count <= info->mount_opts.max) + minor = ida_alloc_max(&binderfs_minors, + use_reserve ? BINDERFS_MAX_MINOR : + BINDERFS_MAX_MINOR_CAPPED, + GFP_KERNEL); + else + minor = -ENOSPC; + if (minor < 0) { + --info->device_count; + mutex_unlock(&binderfs_minors_mutex); + return minor; + } + mutex_unlock(&binderfs_minors_mutex); + + ret = -ENOMEM; + device = kzalloc(sizeof(*device), GFP_KERNEL); + if (!device) + goto err; + + req->name[BINDERFS_MAX_NAME] = '\0'; /* NUL-terminate */ + + ctx = rust_binder_new_context(req->name); + if (!ctx) + goto err; + + inode = new_inode(sb); + if (!inode) + goto err; + + inode->i_ino = minor + INODE_OFFSET; + simple_inode_init_ts(inode); + init_special_inode(inode, S_IFCHR | 0600, + MKDEV(MAJOR(binderfs_dev), minor)); + inode->i_fop = &rust_binder_fops; + inode->i_uid = info->root_uid; + inode->i_gid = info->root_gid; + + req->major = MAJOR(binderfs_dev); + req->minor = minor; + device->ctx = ctx; + device->minor = minor; + + if (userp && copy_to_user(userp, req, sizeof(*req))) { + ret = -EFAULT; + goto err; + } + + root = sb->s_root; + inode_lock(d_inode(root)); + + /* look it up */ + dentry = lookup_noperm(&QSTR(req->name), root); + if (IS_ERR(dentry)) { + inode_unlock(d_inode(root)); + ret = PTR_ERR(dentry); + goto err; + } + + if (d_really_is_positive(dentry)) { + /* already exists */ + dput(dentry); + inode_unlock(d_inode(root)); + ret = -EEXIST; + goto err; + } + + inode->i_private = device; + d_instantiate(dentry, inode); + fsnotify_create(root->d_inode, dentry); + inode_unlock(d_inode(root)); + + return 0; + +err: + kfree(device); + rust_binder_remove_context(ctx); + mutex_lock(&binderfs_minors_mutex); + --info->device_count; + ida_free(&binderfs_minors, minor); + mutex_unlock(&binderfs_minors_mutex); + iput(inode); + + return ret; +} + +/** + * binder_ctl_ioctl - handle binder device node allocation requests + * + * The request handler for the binder-control device. All requests operate on + * the binderfs mount the binder-control device resides in: + * - BINDER_CTL_ADD + * Allocate a new binder device. + * + * Return: %0 on success, negative errno on failure. + */ +static long binder_ctl_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -EINVAL; + struct inode *inode = file_inode(file); + struct binderfs_device __user *device = (struct binderfs_device __user *)arg; + struct binderfs_device device_req; + + switch (cmd) { + case BINDER_CTL_ADD: + ret = copy_from_user(&device_req, device, sizeof(device_req)); + if (ret) { + ret = -EFAULT; + break; + } + + ret = binderfs_binder_device_create(inode, device, &device_req); + break; + default: + break; + } + + return ret; +} + +static void binderfs_evict_inode(struct inode *inode) +{ + struct binder_device *device = inode->i_private; + struct binderfs_info *info = BINDERFS_SB(inode->i_sb); + + clear_inode(inode); + + if (!S_ISCHR(inode->i_mode) || !device) + return; + + mutex_lock(&binderfs_minors_mutex); + --info->device_count; + ida_free(&binderfs_minors, device->minor); + mutex_unlock(&binderfs_minors_mutex); + + /* ctx is null for binder-control, but this function ignores null pointers */ + rust_binder_remove_context(device->ctx); + + kfree(device); +} + +static int binderfs_fs_context_parse_param(struct fs_context *fc, + struct fs_parameter *param) +{ + int opt; + struct binderfs_mount_opts *ctx = fc->fs_private; + struct fs_parse_result result; + + opt = fs_parse(fc, binderfs_fs_parameters, param, &result); + if (opt < 0) + return opt; + + switch (opt) { + case Opt_max: + if (result.uint_32 > BINDERFS_MAX_MINOR) + return invalfc(fc, "Bad value for '%s'", param->key); + + ctx->max = result.uint_32; + break; + case Opt_stats_mode: + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + ctx->stats_mode = result.uint_32; + break; + default: + return invalfc(fc, "Unsupported parameter '%s'", param->key); + } + + return 0; +} + +static int binderfs_fs_context_reconfigure(struct fs_context *fc) +{ + struct binderfs_mount_opts *ctx = fc->fs_private; + struct binderfs_info *info = BINDERFS_SB(fc->root->d_sb); + + if (info->mount_opts.stats_mode != ctx->stats_mode) + return invalfc(fc, "Binderfs stats mode cannot be changed during a remount"); + + info->mount_opts.stats_mode = ctx->stats_mode; + info->mount_opts.max = ctx->max; + return 0; +} + +static int binderfs_show_options(struct seq_file *seq, struct dentry *root) +{ + struct binderfs_info *info = BINDERFS_SB(root->d_sb); + + if (info->mount_opts.max <= BINDERFS_MAX_MINOR) + seq_printf(seq, ",max=%d", info->mount_opts.max); + + switch (info->mount_opts.stats_mode) { + case binderfs_stats_mode_unset: + break; + case binderfs_stats_mode_global: + seq_puts(seq, ",stats=global"); + break; + } + + return 0; +} + +static const struct super_operations binderfs_super_ops = { + .evict_inode = binderfs_evict_inode, + .show_options = binderfs_show_options, + .statfs = simple_statfs, +}; + +static inline bool is_binderfs_control_device(const struct dentry *dentry) +{ + struct binderfs_info *info = dentry->d_sb->s_fs_info; + + return info->control_dentry == dentry; +} + +static int binderfs_rename(struct mnt_idmap *idmap, + struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) +{ + if (is_binderfs_control_device(old_dentry) || + is_binderfs_control_device(new_dentry)) + return -EPERM; + + return simple_rename(idmap, old_dir, old_dentry, new_dir, + new_dentry, flags); +} + +static int binderfs_unlink(struct inode *dir, struct dentry *dentry) +{ + if (is_binderfs_control_device(dentry)) + return -EPERM; + + return simple_unlink(dir, dentry); +} + +static const struct file_operations binder_ctl_fops = { + .owner = THIS_MODULE, + .open = nonseekable_open, + .unlocked_ioctl = binder_ctl_ioctl, + .compat_ioctl = binder_ctl_ioctl, + .llseek = noop_llseek, +}; + +/** + * binderfs_binder_ctl_create - create a new binder-control device + * @sb: super block of the binderfs mount + * + * This function creates a new binder-control device node in the binderfs mount + * referred to by @sb. + * + * Return: 0 on success, negative errno on failure + */ +static int binderfs_binder_ctl_create(struct super_block *sb) +{ + int minor, ret; + struct dentry *dentry; + struct binder_device *device; + struct inode *inode = NULL; + struct dentry *root = sb->s_root; + struct binderfs_info *info = sb->s_fs_info; +#if defined(CONFIG_IPC_NS) + bool use_reserve = (info->ipc_ns == &init_ipc_ns); +#else + bool use_reserve = true; +#endif + + device = kzalloc(sizeof(*device), GFP_KERNEL); + if (!device) + return -ENOMEM; + + /* If we have already created a binder-control node, return. */ + if (info->control_dentry) { + ret = 0; + goto out; + } + + ret = -ENOMEM; + inode = new_inode(sb); + if (!inode) + goto out; + + /* Reserve a new minor number for the new device. */ + mutex_lock(&binderfs_minors_mutex); + minor = ida_alloc_max(&binderfs_minors, + use_reserve ? BINDERFS_MAX_MINOR : + BINDERFS_MAX_MINOR_CAPPED, + GFP_KERNEL); + mutex_unlock(&binderfs_minors_mutex); + if (minor < 0) { + ret = minor; + goto out; + } + + inode->i_ino = SECOND_INODE; + simple_inode_init_ts(inode); + init_special_inode(inode, S_IFCHR | 0600, + MKDEV(MAJOR(binderfs_dev), minor)); + inode->i_fop = &binder_ctl_fops; + inode->i_uid = info->root_uid; + inode->i_gid = info->root_gid; + + device->minor = minor; + device->ctx = NULL; + + dentry = d_alloc_name(root, "binder-control"); + if (!dentry) + goto out; + + inode->i_private = device; + info->control_dentry = dentry; + d_add(dentry, inode); + + return 0; + +out: + kfree(device); + iput(inode); + + return ret; +} + +static const struct inode_operations binderfs_dir_inode_operations = { + .lookup = simple_lookup, + .rename = binderfs_rename, + .unlink = binderfs_unlink, +}; + +static struct inode *binderfs_make_inode(struct super_block *sb, int mode) +{ + struct inode *ret; + + ret = new_inode(sb); + if (ret) { + ret->i_ino = iunique(sb, BINDERFS_MAX_MINOR + INODE_OFFSET); + ret->i_mode = mode; + simple_inode_init_ts(ret); + } + return ret; +} + +static struct dentry *binderfs_create_dentry(struct dentry *parent, + const char *name) +{ + struct dentry *dentry; + + dentry = lookup_noperm(&QSTR(name), parent); + if (IS_ERR(dentry)) + return dentry; + + /* Return error if the file/dir already exists. */ + if (d_really_is_positive(dentry)) { + dput(dentry); + return ERR_PTR(-EEXIST); + } + + return dentry; +} + +void rust_binderfs_remove_file(struct dentry *dentry) +{ + struct inode *parent_inode; + + parent_inode = d_inode(dentry->d_parent); + inode_lock(parent_inode); + if (simple_positive(dentry)) { + dget(dentry); + simple_unlink(parent_inode, dentry); + d_delete(dentry); + dput(dentry); + } + inode_unlock(parent_inode); +} + +static struct dentry *rust_binderfs_create_file(struct dentry *parent, const char *name, + const struct file_operations *fops, + void *data) +{ + struct dentry *dentry; + struct inode *new_inode, *parent_inode; + struct super_block *sb; + + parent_inode = d_inode(parent); + inode_lock(parent_inode); + + dentry = binderfs_create_dentry(parent, name); + if (IS_ERR(dentry)) + goto out; + + sb = parent_inode->i_sb; + new_inode = binderfs_make_inode(sb, S_IFREG | 0444); + if (!new_inode) { + dput(dentry); + dentry = ERR_PTR(-ENOMEM); + goto out; + } + + new_inode->i_fop = fops; + new_inode->i_private = data; + d_instantiate(dentry, new_inode); + fsnotify_create(parent_inode, dentry); + +out: + inode_unlock(parent_inode); + return dentry; +} + +struct dentry *rust_binderfs_create_proc_file(struct inode *nodp, int pid) +{ + struct binderfs_info *info = nodp->i_sb->s_fs_info; + struct dentry *dir = info->proc_log_dir; + char strbuf[20 + 1]; + void *data = (void *)(unsigned long) pid; + + if (!dir) + return NULL; + + snprintf(strbuf, sizeof(strbuf), "%u", pid); + return rust_binderfs_create_file(dir, strbuf, &rust_binder_proc_fops, data); +} + +static struct dentry *binderfs_create_dir(struct dentry *parent, + const char *name) +{ + struct dentry *dentry; + struct inode *new_inode, *parent_inode; + struct super_block *sb; + + parent_inode = d_inode(parent); + inode_lock(parent_inode); + + dentry = binderfs_create_dentry(parent, name); + if (IS_ERR(dentry)) + goto out; + + sb = parent_inode->i_sb; + new_inode = binderfs_make_inode(sb, S_IFDIR | 0755); + if (!new_inode) { + dput(dentry); + dentry = ERR_PTR(-ENOMEM); + goto out; + } + + new_inode->i_fop = &simple_dir_operations; + new_inode->i_op = &simple_dir_inode_operations; + + set_nlink(new_inode, 2); + d_instantiate(dentry, new_inode); + inc_nlink(parent_inode); + fsnotify_mkdir(parent_inode, dentry); + +out: + inode_unlock(parent_inode); + return dentry; +} + +static int binder_features_show(struct seq_file *m, void *unused) +{ + bool *feature = m->private; + + seq_printf(m, "%d\n", *feature); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(binder_features); + +static int init_binder_features(struct super_block *sb) +{ + struct dentry *dentry, *dir; + + dir = binderfs_create_dir(sb->s_root, "features"); + if (IS_ERR(dir)) + return PTR_ERR(dir); + + dentry = rust_binderfs_create_file(dir, "oneway_spam_detection", + &binder_features_fops, + &binder_features.oneway_spam_detection); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + dentry = rust_binderfs_create_file(dir, "extended_error", + &binder_features_fops, + &binder_features.extended_error); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + dentry = rust_binderfs_create_file(dir, "freeze_notification", + &binder_features_fops, + &binder_features.freeze_notification); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + return 0; +} + +static int init_binder_logs(struct super_block *sb) +{ + struct dentry *binder_logs_root_dir, *dentry, *proc_log_dir; + struct binderfs_info *info; + int ret = 0; + + binder_logs_root_dir = binderfs_create_dir(sb->s_root, + "binder_logs"); + if (IS_ERR(binder_logs_root_dir)) { + ret = PTR_ERR(binder_logs_root_dir); + goto out; + } + + dentry = rust_binderfs_create_file(binder_logs_root_dir, "stats", + &rust_binder_stats_fops, NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + dentry = rust_binderfs_create_file(binder_logs_root_dir, "state", + &rust_binder_state_fops, NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + dentry = rust_binderfs_create_file(binder_logs_root_dir, "transactions", + &rust_binder_transactions_fops, NULL); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto out; + } + + proc_log_dir = binderfs_create_dir(binder_logs_root_dir, "proc"); + if (IS_ERR(proc_log_dir)) { + ret = PTR_ERR(proc_log_dir); + goto out; + } + info = sb->s_fs_info; + info->proc_log_dir = proc_log_dir; + +out: + return ret; +} + +static int binderfs_fill_super(struct super_block *sb, struct fs_context *fc) +{ + int ret; + struct binderfs_info *info; + struct binderfs_mount_opts *ctx = fc->fs_private; + struct inode *inode = NULL; + struct binderfs_device device_info = {}; + const char *name; + size_t len; + + sb->s_blocksize = PAGE_SIZE; + sb->s_blocksize_bits = PAGE_SHIFT; + + /* + * The binderfs filesystem can be mounted by userns root in a + * non-initial userns. By default such mounts have the SB_I_NODEV flag + * set in s_iflags to prevent security issues where userns root can + * just create random device nodes via mknod() since it owns the + * filesystem mount. But binderfs does not allow to create any files + * including devices nodes. The only way to create binder devices nodes + * is through the binder-control device which userns root is explicitly + * allowed to do. So removing the SB_I_NODEV flag from s_iflags is both + * necessary and safe. + */ + sb->s_iflags &= ~SB_I_NODEV; + sb->s_iflags |= SB_I_NOEXEC; + sb->s_magic = RUST_BINDERFS_SUPER_MAGIC; + sb->s_op = &binderfs_super_ops; + sb->s_time_gran = 1; + + sb->s_fs_info = kzalloc(sizeof(struct binderfs_info), GFP_KERNEL); + if (!sb->s_fs_info) + return -ENOMEM; + info = sb->s_fs_info; + + info->ipc_ns = get_ipc_ns(current->nsproxy->ipc_ns); + + info->root_gid = make_kgid(sb->s_user_ns, 0); + if (!gid_valid(info->root_gid)) + info->root_gid = GLOBAL_ROOT_GID; + info->root_uid = make_kuid(sb->s_user_ns, 0); + if (!uid_valid(info->root_uid)) + info->root_uid = GLOBAL_ROOT_UID; + info->mount_opts.max = ctx->max; + info->mount_opts.stats_mode = ctx->stats_mode; + + inode = new_inode(sb); + if (!inode) + return -ENOMEM; + + inode->i_ino = FIRST_INODE; + inode->i_fop = &simple_dir_operations; + inode->i_mode = S_IFDIR | 0755; + simple_inode_init_ts(inode); + inode->i_op = &binderfs_dir_inode_operations; + set_nlink(inode, 2); + + sb->s_root = d_make_root(inode); + if (!sb->s_root) + return -ENOMEM; + + ret = binderfs_binder_ctl_create(sb); + if (ret) + return ret; + + name = rust_binder_devices_param; + for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) { + strscpy(device_info.name, name, len + 1); + ret = binderfs_binder_device_create(inode, NULL, &device_info); + if (ret) + return ret; + name += len; + if (*name == ',') + name++; + } + + ret = init_binder_features(sb); + if (ret) + return ret; + + if (info->mount_opts.stats_mode == binderfs_stats_mode_global) + return init_binder_logs(sb); + + return 0; +} + +static int binderfs_fs_context_get_tree(struct fs_context *fc) +{ + return get_tree_nodev(fc, binderfs_fill_super); +} + +static void binderfs_fs_context_free(struct fs_context *fc) +{ + struct binderfs_mount_opts *ctx = fc->fs_private; + + kfree(ctx); +} + +static const struct fs_context_operations binderfs_fs_context_ops = { + .free = binderfs_fs_context_free, + .get_tree = binderfs_fs_context_get_tree, + .parse_param = binderfs_fs_context_parse_param, + .reconfigure = binderfs_fs_context_reconfigure, +}; + +static int binderfs_init_fs_context(struct fs_context *fc) +{ + struct binderfs_mount_opts *ctx; + + ctx = kzalloc(sizeof(struct binderfs_mount_opts), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->max = BINDERFS_MAX_MINOR; + ctx->stats_mode = binderfs_stats_mode_unset; + + fc->fs_private = ctx; + fc->ops = &binderfs_fs_context_ops; + + return 0; +} + +static void binderfs_kill_super(struct super_block *sb) +{ + struct binderfs_info *info = sb->s_fs_info; + + /* + * During inode eviction struct binderfs_info is needed. + * So first wipe the super_block then free struct binderfs_info. + */ + kill_litter_super(sb); + + if (info && info->ipc_ns) + put_ipc_ns(info->ipc_ns); + + kfree(info); +} + +static struct file_system_type binder_fs_type = { + .name = "binder", + .init_fs_context = binderfs_init_fs_context, + .parameters = binderfs_fs_parameters, + .kill_sb = binderfs_kill_super, + .fs_flags = FS_USERNS_MOUNT, +}; + +int init_rust_binderfs(void) +{ + int ret; + const char *name; + size_t len; + + /* Verify that the default binderfs device names are valid. */ + name = rust_binder_devices_param; + for (len = strcspn(name, ","); len > 0; len = strcspn(name, ",")) { + if (len > BINDERFS_MAX_NAME) + return -E2BIG; + name += len; + if (*name == ',') + name++; + } + + /* Allocate new major number for binderfs. */ + ret = alloc_chrdev_region(&binderfs_dev, 0, BINDERFS_MAX_MINOR, + "rust_binder"); + if (ret) + return ret; + + ret = register_filesystem(&binder_fs_type); + if (ret) { + unregister_chrdev_region(binderfs_dev, BINDERFS_MAX_MINOR); + return ret; + } + + return ret; +} diff --git a/drivers/android/binder/stats.rs b/drivers/android/binder/stats.rs new file mode 100644 index 000000000000..a83ec111d2cb --- /dev/null +++ b/drivers/android/binder/stats.rs @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! Keep track of statistics for binder_logs. + +use crate::defs::*; +use core::sync::atomic::{AtomicU32, Ordering::Relaxed}; +use kernel::{ioctl::_IOC_NR, seq_file::SeqFile, seq_print}; + +const BC_COUNT: usize = _IOC_NR(BC_REPLY_SG) as usize + 1; +const BR_COUNT: usize = _IOC_NR(BR_TRANSACTION_PENDING_FROZEN) as usize + 1; + +pub(crate) static GLOBAL_STATS: BinderStats = BinderStats::new(); + +pub(crate) struct BinderStats { + bc: [AtomicU32; BC_COUNT], + br: [AtomicU32; BR_COUNT], +} + +impl BinderStats { + pub(crate) const fn new() -> Self { + #[expect(clippy::declare_interior_mutable_const)] + const ZERO: AtomicU32 = AtomicU32::new(0); + + Self { + bc: [ZERO; BC_COUNT], + br: [ZERO; BR_COUNT], + } + } + + pub(crate) fn inc_bc(&self, bc: u32) { + let idx = _IOC_NR(bc) as usize; + if let Some(bc_ref) = self.bc.get(idx) { + bc_ref.fetch_add(1, Relaxed); + } + } + + pub(crate) fn inc_br(&self, br: u32) { + let idx = _IOC_NR(br) as usize; + if let Some(br_ref) = self.br.get(idx) { + br_ref.fetch_add(1, Relaxed); + } + } + + pub(crate) fn debug_print(&self, prefix: &str, m: &SeqFile) { + for (i, cnt) in self.bc.iter().enumerate() { + let cnt = cnt.load(Relaxed); + if cnt > 0 { + seq_print!(m, "{}{}: {}\n", prefix, command_string(i), cnt); + } + } + for (i, cnt) in self.br.iter().enumerate() { + let cnt = cnt.load(Relaxed); + if cnt > 0 { + seq_print!(m, "{}{}: {}\n", prefix, return_string(i), cnt); + } + } + } +} + +mod strings { + use core::str::from_utf8_unchecked; + use kernel::str::CStr; + + extern "C" { + static binder_command_strings: [*const u8; super::BC_COUNT]; + static binder_return_strings: [*const u8; super::BR_COUNT]; + } + + pub(super) fn command_string(i: usize) -> &'static str { + // SAFETY: Accessing `binder_command_strings` is always safe. + let c_str_ptr = unsafe { binder_command_strings[i] }; + // SAFETY: The `binder_command_strings` array only contains nul-terminated strings. + let bytes = unsafe { CStr::from_char_ptr(c_str_ptr) }.as_bytes(); + // SAFETY: The `binder_command_strings` array only contains strings with ascii-chars. + unsafe { from_utf8_unchecked(bytes) } + } + + pub(super) fn return_string(i: usize) -> &'static str { + // SAFETY: Accessing `binder_return_strings` is always safe. + let c_str_ptr = unsafe { binder_return_strings[i] }; + // SAFETY: The `binder_command_strings` array only contains nul-terminated strings. + let bytes = unsafe { CStr::from_char_ptr(c_str_ptr) }.as_bytes(); + // SAFETY: The `binder_command_strings` array only contains strings with ascii-chars. + unsafe { from_utf8_unchecked(bytes) } + } +} +use strings::{command_string, return_string}; diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs new file mode 100644 index 000000000000..7e34ccd394f8 --- /dev/null +++ b/drivers/android/binder/thread.rs @@ -0,0 +1,1596 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! This module defines the `Thread` type, which represents a userspace thread that is using +//! binder. +//! +//! The `Process` object stores all of the threads in an rb tree. + +use kernel::{ + bindings, + fs::{File, LocalFile}, + list::{AtomicTracker, List, ListArc, ListLinks, TryNewListArc}, + prelude::*, + security, + seq_file::SeqFile, + seq_print, + sync::poll::{PollCondVar, PollTable}, + sync::{Arc, SpinLock}, + task::Task, + types::ARef, + uaccess::UserSlice, + uapi, +}; + +use crate::{ + allocation::{Allocation, AllocationView, BinderObject, BinderObjectRef, NewAllocation}, + defs::*, + error::BinderResult, + process::{GetWorkOrRegister, Process}, + ptr_align, + stats::GLOBAL_STATS, + transaction::Transaction, + BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverCode, DeliverToRead, +}; + +use core::{ + mem::size_of, + sync::atomic::{AtomicU32, Ordering}, +}; + +/// Stores the layout of the scatter-gather entries. This is used during the `translate_objects` +/// call and is discarded when it returns. +struct ScatterGatherState { + /// A struct that tracks the amount of unused buffer space. + unused_buffer_space: UnusedBufferSpace, + /// Scatter-gather entries to copy. + sg_entries: KVec<ScatterGatherEntry>, + /// Indexes into `sg_entries` corresponding to the last binder_buffer_object that + /// was processed and all of its ancestors. The array is in sorted order. + ancestors: KVec<usize>, +} + +/// This entry specifies an additional buffer that should be copied using the scatter-gather +/// mechanism. +struct ScatterGatherEntry { + /// The index in the offset array of the BINDER_TYPE_PTR that this entry originates from. + obj_index: usize, + /// Offset in target buffer. + offset: usize, + /// User address in source buffer. + sender_uaddr: usize, + /// Number of bytes to copy. + length: usize, + /// The minimum offset of the next fixup in this buffer. + fixup_min_offset: usize, + /// The offsets within this buffer that contain pointers which should be translated. + pointer_fixups: KVec<PointerFixupEntry>, +} + +/// This entry specifies that a fixup should happen at `target_offset` of the +/// buffer. If `skip` is nonzero, then the fixup is a `binder_fd_array_object` +/// and is applied later. Otherwise if `skip` is zero, then the size of the +/// fixup is `sizeof::<u64>()` and `pointer_value` is written to the buffer. +struct PointerFixupEntry { + /// The number of bytes to skip, or zero for a `binder_buffer_object` fixup. + skip: usize, + /// The translated pointer to write when `skip` is zero. + pointer_value: u64, + /// The offset at which the value should be written. The offset is relative + /// to the original buffer. + target_offset: usize, +} + +/// Return type of `apply_and_validate_fixup_in_parent`. +struct ParentFixupInfo { + /// The index of the parent buffer in `sg_entries`. + parent_sg_index: usize, + /// The number of ancestors of the buffer. + /// + /// The buffer is considered an ancestor of itself, so this is always at + /// least one. + num_ancestors: usize, + /// New value of `fixup_min_offset` if this fixup is applied. + new_min_offset: usize, + /// The offset of the fixup in the target buffer. + target_offset: usize, +} + +impl ScatterGatherState { + /// Called when a `binder_buffer_object` or `binder_fd_array_object` tries + /// to access a region in its parent buffer. These accesses have various + /// restrictions, which this method verifies. + /// + /// The `parent_offset` and `length` arguments describe the offset and + /// length of the access in the parent buffer. + /// + /// # Detailed restrictions + /// + /// Obviously the fixup must be in-bounds for the parent buffer. + /// + /// For safety reasons, we only allow fixups inside a buffer to happen + /// at increasing offsets; additionally, we only allow fixup on the last + /// buffer object that was verified, or one of its parents. + /// + /// Example of what is allowed: + /// + /// A + /// B (parent = A, offset = 0) + /// C (parent = A, offset = 16) + /// D (parent = C, offset = 0) + /// E (parent = A, offset = 32) // min_offset is 16 (C.parent_offset) + /// + /// Examples of what is not allowed: + /// + /// Decreasing offsets within the same parent: + /// A + /// C (parent = A, offset = 16) + /// B (parent = A, offset = 0) // decreasing offset within A + /// + /// Arcerring to a parent that wasn't the last object or any of its parents: + /// A + /// B (parent = A, offset = 0) + /// C (parent = A, offset = 0) + /// C (parent = A, offset = 16) + /// D (parent = B, offset = 0) // B is not A or any of A's parents + fn validate_parent_fixup( + &self, + parent: usize, + parent_offset: usize, + length: usize, + ) -> Result<ParentFixupInfo> { + // Using `position` would also be correct, but `rposition` avoids + // quadratic running times. + let ancestors_i = self + .ancestors + .iter() + .copied() + .rposition(|sg_idx| self.sg_entries[sg_idx].obj_index == parent) + .ok_or(EINVAL)?; + let sg_idx = self.ancestors[ancestors_i]; + let sg_entry = match self.sg_entries.get(sg_idx) { + Some(sg_entry) => sg_entry, + None => { + pr_err!( + "self.ancestors[{}] is {}, but self.sg_entries.len() is {}", + ancestors_i, + sg_idx, + self.sg_entries.len() + ); + return Err(EINVAL); + } + }; + if sg_entry.fixup_min_offset > parent_offset { + pr_warn!( + "validate_parent_fixup: fixup_min_offset={}, parent_offset={}", + sg_entry.fixup_min_offset, + parent_offset + ); + return Err(EINVAL); + } + let new_min_offset = parent_offset.checked_add(length).ok_or(EINVAL)?; + if new_min_offset > sg_entry.length { + pr_warn!( + "validate_parent_fixup: new_min_offset={}, sg_entry.length={}", + new_min_offset, + sg_entry.length + ); + return Err(EINVAL); + } + let target_offset = sg_entry.offset.checked_add(parent_offset).ok_or(EINVAL)?; + // The `ancestors_i + 1` operation can't overflow since the output of the addition is at + // most `self.ancestors.len()`, which also fits in a usize. + Ok(ParentFixupInfo { + parent_sg_index: sg_idx, + num_ancestors: ancestors_i + 1, + new_min_offset, + target_offset, + }) + } +} + +/// Keeps track of how much unused buffer space is left. The initial amount is the number of bytes +/// requested by the user using the `buffers_size` field of `binder_transaction_data_sg`. Each time +/// we translate an object of type `BINDER_TYPE_PTR`, some of the unused buffer space is consumed. +struct UnusedBufferSpace { + /// The start of the remaining space. + offset: usize, + /// The end of the remaining space. + limit: usize, +} +impl UnusedBufferSpace { + /// Claim the next `size` bytes from the unused buffer space. The offset for the claimed chunk + /// into the buffer is returned. + fn claim_next(&mut self, size: usize) -> Result<usize> { + // We require every chunk to be aligned. + let size = ptr_align(size).ok_or(EINVAL)?; + let new_offset = self.offset.checked_add(size).ok_or(EINVAL)?; + + if new_offset <= self.limit { + let offset = self.offset; + self.offset = new_offset; + Ok(offset) + } else { + Err(EINVAL) + } + } +} + +pub(crate) enum PushWorkRes { + Ok, + FailedDead(DLArc<dyn DeliverToRead>), +} + +impl PushWorkRes { + fn is_ok(&self) -> bool { + match self { + PushWorkRes::Ok => true, + PushWorkRes::FailedDead(_) => false, + } + } +} + +/// The fields of `Thread` protected by the spinlock. +struct InnerThread { + /// Determines the looper state of the thread. It is a bit-wise combination of the constants + /// prefixed with `LOOPER_`. + looper_flags: u32, + + /// Determines whether the looper should return. + looper_need_return: bool, + + /// Determines if thread is dead. + is_dead: bool, + + /// Work item used to deliver error codes to the thread that started a transaction. Stored here + /// so that it can be reused. + reply_work: DArc<ThreadError>, + + /// Work item used to deliver error codes to the current thread. Stored here so that it can be + /// reused. + return_work: DArc<ThreadError>, + + /// Determines whether the work list below should be processed. When set to false, `work_list` + /// is treated as if it were empty. + process_work_list: bool, + /// List of work items to deliver to userspace. + work_list: List<DTRWrap<dyn DeliverToRead>>, + current_transaction: Option<DArc<Transaction>>, + + /// Extended error information for this thread. + extended_error: ExtendedError, +} + +const LOOPER_REGISTERED: u32 = 0x01; +const LOOPER_ENTERED: u32 = 0x02; +const LOOPER_EXITED: u32 = 0x04; +const LOOPER_INVALID: u32 = 0x08; +const LOOPER_WAITING: u32 = 0x10; +const LOOPER_WAITING_PROC: u32 = 0x20; +const LOOPER_POLL: u32 = 0x40; + +impl InnerThread { + fn new() -> Result<Self> { + fn next_err_id() -> u32 { + static EE_ID: AtomicU32 = AtomicU32::new(0); + EE_ID.fetch_add(1, Ordering::Relaxed) + } + + Ok(Self { + looper_flags: 0, + looper_need_return: false, + is_dead: false, + process_work_list: false, + reply_work: ThreadError::try_new()?, + return_work: ThreadError::try_new()?, + work_list: List::new(), + current_transaction: None, + extended_error: ExtendedError::new(next_err_id(), BR_OK, 0), + }) + } + + fn pop_work(&mut self) -> Option<DLArc<dyn DeliverToRead>> { + if !self.process_work_list { + return None; + } + + let ret = self.work_list.pop_front(); + self.process_work_list = !self.work_list.is_empty(); + ret + } + + fn push_work(&mut self, work: DLArc<dyn DeliverToRead>) -> PushWorkRes { + if self.is_dead { + PushWorkRes::FailedDead(work) + } else { + self.work_list.push_back(work); + self.process_work_list = true; + PushWorkRes::Ok + } + } + + fn push_reply_work(&mut self, code: u32) { + if let Ok(work) = ListArc::try_from_arc(self.reply_work.clone()) { + work.set_error_code(code); + self.push_work(work); + } else { + pr_warn!("Thread reply work is already in use."); + } + } + + fn push_return_work(&mut self, reply: u32) { + if let Ok(work) = ListArc::try_from_arc(self.return_work.clone()) { + work.set_error_code(reply); + self.push_work(work); + } else { + pr_warn!("Thread return work is already in use."); + } + } + + /// Used to push work items that do not need to be processed immediately and can wait until the + /// thread gets another work item. + fn push_work_deferred(&mut self, work: DLArc<dyn DeliverToRead>) { + self.work_list.push_back(work); + } + + /// Fetches the transaction this thread can reply to. If the thread has a pending transaction + /// (that it could respond to) but it has also issued a transaction, it must first wait for the + /// previously-issued transaction to complete. + /// + /// The `thread` parameter should be the thread containing this `ThreadInner`. + fn pop_transaction_to_reply(&mut self, thread: &Thread) -> Result<DArc<Transaction>> { + let transaction = self.current_transaction.take().ok_or(EINVAL)?; + if core::ptr::eq(thread, transaction.from.as_ref()) { + self.current_transaction = Some(transaction); + return Err(EINVAL); + } + // Find a new current transaction for this thread. + self.current_transaction = transaction.find_from(thread).cloned(); + Ok(transaction) + } + + fn pop_transaction_replied(&mut self, transaction: &DArc<Transaction>) -> bool { + match self.current_transaction.take() { + None => false, + Some(old) => { + if !Arc::ptr_eq(transaction, &old) { + self.current_transaction = Some(old); + return false; + } + self.current_transaction = old.clone_next(); + true + } + } + } + + fn looper_enter(&mut self) { + self.looper_flags |= LOOPER_ENTERED; + if self.looper_flags & LOOPER_REGISTERED != 0 { + self.looper_flags |= LOOPER_INVALID; + } + } + + fn looper_register(&mut self, valid: bool) { + self.looper_flags |= LOOPER_REGISTERED; + if !valid || self.looper_flags & LOOPER_ENTERED != 0 { + self.looper_flags |= LOOPER_INVALID; + } + } + + fn looper_exit(&mut self) { + self.looper_flags |= LOOPER_EXITED; + } + + /// Determines whether the thread is part of a pool, i.e., if it is a looper. + fn is_looper(&self) -> bool { + self.looper_flags & (LOOPER_ENTERED | LOOPER_REGISTERED) != 0 + } + + /// Determines whether the thread should attempt to fetch work items from the process queue. + /// This is generally case when the thread is registered as a looper and not part of a + /// transaction stack. But if there is local work, we want to return to userspace before we + /// deliver any remote work. + fn should_use_process_work_queue(&self) -> bool { + self.current_transaction.is_none() && !self.process_work_list && self.is_looper() + } + + fn poll(&mut self) -> u32 { + self.looper_flags |= LOOPER_POLL; + if self.process_work_list || self.looper_need_return { + bindings::POLLIN + } else { + 0 + } + } +} + +/// This represents a thread that's used with binder. +#[pin_data] +pub(crate) struct Thread { + pub(crate) id: i32, + pub(crate) process: Arc<Process>, + pub(crate) task: ARef<Task>, + #[pin] + inner: SpinLock<InnerThread>, + #[pin] + work_condvar: PollCondVar, + /// Used to insert this thread into the process' `ready_threads` list. + /// + /// INVARIANT: May never be used for any other list than the `self.process.ready_threads`. + #[pin] + links: ListLinks, + #[pin] + links_track: AtomicTracker, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for Thread { + tracked_by links_track: AtomicTracker; + } +} +kernel::list::impl_list_item! { + impl ListItem<0> for Thread { + using ListLinks { self.links }; + } +} + +impl Thread { + pub(crate) fn new(id: i32, process: Arc<Process>) -> Result<Arc<Self>> { + let inner = InnerThread::new()?; + + Arc::pin_init( + try_pin_init!(Thread { + id, + process, + task: ARef::from(&**kernel::current!()), + inner <- kernel::new_spinlock!(inner, "Thread::inner"), + work_condvar <- kernel::new_poll_condvar!("Thread::work_condvar"), + links <- ListLinks::new(), + links_track <- AtomicTracker::new(), + }), + GFP_KERNEL, + ) + } + + #[inline(never)] + pub(crate) fn debug_print(self: &Arc<Self>, m: &SeqFile, print_all: bool) -> Result<()> { + let inner = self.inner.lock(); + + if print_all || inner.current_transaction.is_some() || !inner.work_list.is_empty() { + seq_print!( + m, + " thread {}: l {:02x} need_return {}\n", + self.id, + inner.looper_flags, + inner.looper_need_return, + ); + } + + let mut t_opt = inner.current_transaction.as_ref(); + while let Some(t) = t_opt { + if Arc::ptr_eq(&t.from, self) { + t.debug_print_inner(m, " outgoing transaction "); + t_opt = t.from_parent.as_ref(); + } else if Arc::ptr_eq(&t.to, &self.process) { + t.debug_print_inner(m, " incoming transaction "); + t_opt = t.find_from(self); + } else { + t.debug_print_inner(m, " bad transaction "); + t_opt = None; + } + } + + for work in &inner.work_list { + work.debug_print(m, " ", " pending transaction ")?; + } + Ok(()) + } + + pub(crate) fn get_extended_error(&self, data: UserSlice) -> Result { + let mut writer = data.writer(); + let ee = self.inner.lock().extended_error; + writer.write(&ee)?; + Ok(()) + } + + pub(crate) fn set_current_transaction(&self, transaction: DArc<Transaction>) { + self.inner.lock().current_transaction = Some(transaction); + } + + pub(crate) fn has_current_transaction(&self) -> bool { + self.inner.lock().current_transaction.is_some() + } + + /// Attempts to fetch a work item from the thread-local queue. The behaviour if the queue is + /// empty depends on `wait`: if it is true, the function waits for some work to be queued (or a + /// signal); otherwise it returns indicating that none is available. + fn get_work_local(self: &Arc<Self>, wait: bool) -> Result<Option<DLArc<dyn DeliverToRead>>> { + { + let mut inner = self.inner.lock(); + if inner.looper_need_return { + return Ok(inner.pop_work()); + } + } + + // Try once if the caller does not want to wait. + if !wait { + return self.inner.lock().pop_work().ok_or(EAGAIN).map(Some); + } + + // Loop waiting only on the local queue (i.e., not registering with the process queue). + let mut inner = self.inner.lock(); + loop { + if let Some(work) = inner.pop_work() { + return Ok(Some(work)); + } + + inner.looper_flags |= LOOPER_WAITING; + let signal_pending = self.work_condvar.wait_interruptible_freezable(&mut inner); + inner.looper_flags &= !LOOPER_WAITING; + + if signal_pending { + return Err(EINTR); + } + if inner.looper_need_return { + return Ok(None); + } + } + } + + /// Attempts to fetch a work item from the thread-local queue, falling back to the process-wide + /// queue if none is available locally. + /// + /// This must only be called when the thread is not participating in a transaction chain. If it + /// is, the local version (`get_work_local`) should be used instead. + fn get_work(self: &Arc<Self>, wait: bool) -> Result<Option<DLArc<dyn DeliverToRead>>> { + // Try to get work from the thread's work queue, using only a local lock. + { + let mut inner = self.inner.lock(); + if let Some(work) = inner.pop_work() { + return Ok(Some(work)); + } + if inner.looper_need_return { + drop(inner); + return Ok(self.process.get_work()); + } + } + + // If the caller doesn't want to wait, try to grab work from the process queue. + // + // We know nothing will have been queued directly to the thread queue because it is not in + // a transaction and it is not in the process' ready list. + if !wait { + return self.process.get_work().ok_or(EAGAIN).map(Some); + } + + // Get work from the process queue. If none is available, atomically register as ready. + let reg = match self.process.get_work_or_register(self) { + GetWorkOrRegister::Work(work) => return Ok(Some(work)), + GetWorkOrRegister::Register(reg) => reg, + }; + + let mut inner = self.inner.lock(); + loop { + if let Some(work) = inner.pop_work() { + return Ok(Some(work)); + } + + inner.looper_flags |= LOOPER_WAITING | LOOPER_WAITING_PROC; + let signal_pending = self.work_condvar.wait_interruptible_freezable(&mut inner); + inner.looper_flags &= !(LOOPER_WAITING | LOOPER_WAITING_PROC); + + if signal_pending || inner.looper_need_return { + // We need to return now. We need to pull the thread off the list of ready threads + // (by dropping `reg`), then check the state again after it's off the list to + // ensure that something was not queued in the meantime. If something has been + // queued, we just return it (instead of the error). + drop(inner); + drop(reg); + + let res = match self.inner.lock().pop_work() { + Some(work) => Ok(Some(work)), + None if signal_pending => Err(EINTR), + None => Ok(None), + }; + return res; + } + } + } + + /// Push the provided work item to be delivered to user space via this thread. + /// + /// Returns whether the item was successfully pushed. This can only fail if the thread is dead. + pub(crate) fn push_work(&self, work: DLArc<dyn DeliverToRead>) -> PushWorkRes { + let sync = work.should_sync_wakeup(); + + let res = self.inner.lock().push_work(work); + + if res.is_ok() { + if sync { + self.work_condvar.notify_sync(); + } else { + self.work_condvar.notify_one(); + } + } + + res + } + + /// Attempts to push to given work item to the thread if it's a looper thread (i.e., if it's + /// part of a thread pool) and is alive. Otherwise, push the work item to the process instead. + pub(crate) fn push_work_if_looper(&self, work: DLArc<dyn DeliverToRead>) -> BinderResult { + let mut inner = self.inner.lock(); + if inner.is_looper() && !inner.is_dead { + inner.push_work(work); + Ok(()) + } else { + drop(inner); + self.process.push_work(work) + } + } + + pub(crate) fn push_work_deferred(&self, work: DLArc<dyn DeliverToRead>) { + self.inner.lock().push_work_deferred(work); + } + + pub(crate) fn push_return_work(&self, reply: u32) { + self.inner.lock().push_return_work(reply); + } + + fn translate_object( + &self, + obj_index: usize, + offset: usize, + object: BinderObjectRef<'_>, + view: &mut AllocationView<'_>, + allow_fds: bool, + sg_state: &mut ScatterGatherState, + ) -> BinderResult { + match object { + BinderObjectRef::Binder(obj) => { + let strong = obj.hdr.type_ == BINDER_TYPE_BINDER; + // SAFETY: `binder` is a `binder_uintptr_t`; any bit pattern is a valid + // representation. + let ptr = unsafe { obj.__bindgen_anon_1.binder } as _; + let cookie = obj.cookie as _; + let flags = obj.flags as _; + let node = self + .process + .as_arc_borrow() + .get_node(ptr, cookie, flags, strong, self)?; + security::binder_transfer_binder(&self.process.cred, &view.alloc.process.cred)?; + view.transfer_binder_object(offset, obj, strong, node)?; + } + BinderObjectRef::Handle(obj) => { + let strong = obj.hdr.type_ == BINDER_TYPE_HANDLE; + // SAFETY: `handle` is a `u32`; any bit pattern is a valid representation. + let handle = unsafe { obj.__bindgen_anon_1.handle } as _; + let node = self.process.get_node_from_handle(handle, strong)?; + security::binder_transfer_binder(&self.process.cred, &view.alloc.process.cred)?; + view.transfer_binder_object(offset, obj, strong, node)?; + } + BinderObjectRef::Fd(obj) => { + if !allow_fds { + return Err(EPERM.into()); + } + + // SAFETY: `fd` is a `u32`; any bit pattern is a valid representation. + let fd = unsafe { obj.__bindgen_anon_1.fd }; + let file = LocalFile::fget(fd)?; + // SAFETY: The binder driver never calls `fdget_pos` and this code runs from an + // ioctl, so there are no active calls to `fdget_pos` on this thread. + let file = unsafe { LocalFile::assume_no_fdget_pos(file) }; + security::binder_transfer_file( + &self.process.cred, + &view.alloc.process.cred, + &file, + )?; + + let mut obj_write = BinderFdObject::default(); + obj_write.hdr.type_ = BINDER_TYPE_FD; + // This will be overwritten with the actual fd when the transaction is received. + obj_write.__bindgen_anon_1.fd = u32::MAX; + obj_write.cookie = obj.cookie; + view.write::<BinderFdObject>(offset, &obj_write)?; + + const FD_FIELD_OFFSET: usize = + core::mem::offset_of!(uapi::binder_fd_object, __bindgen_anon_1.fd); + + let field_offset = offset + FD_FIELD_OFFSET; + + view.alloc.info_add_fd(file, field_offset, false)?; + } + BinderObjectRef::Ptr(obj) => { + let obj_length = obj.length.try_into().map_err(|_| EINVAL)?; + let alloc_offset = match sg_state.unused_buffer_space.claim_next(obj_length) { + Ok(alloc_offset) => alloc_offset, + Err(err) => { + pr_warn!( + "Failed to claim space for a BINDER_TYPE_PTR. (offset: {}, limit: {}, size: {})", + sg_state.unused_buffer_space.offset, + sg_state.unused_buffer_space.limit, + obj_length, + ); + return Err(err.into()); + } + }; + + let sg_state_idx = sg_state.sg_entries.len(); + sg_state.sg_entries.push( + ScatterGatherEntry { + obj_index, + offset: alloc_offset, + sender_uaddr: obj.buffer as _, + length: obj_length, + pointer_fixups: KVec::new(), + fixup_min_offset: 0, + }, + GFP_KERNEL, + )?; + + let buffer_ptr_in_user_space = (view.alloc.ptr + alloc_offset) as u64; + + if obj.flags & uapi::BINDER_BUFFER_FLAG_HAS_PARENT == 0 { + sg_state.ancestors.clear(); + sg_state.ancestors.push(sg_state_idx, GFP_KERNEL)?; + } else { + // Another buffer also has a pointer to this buffer, and we need to fixup that + // pointer too. + + let parent_index = usize::try_from(obj.parent).map_err(|_| EINVAL)?; + let parent_offset = usize::try_from(obj.parent_offset).map_err(|_| EINVAL)?; + + let info = sg_state.validate_parent_fixup( + parent_index, + parent_offset, + size_of::<u64>(), + )?; + + sg_state.ancestors.truncate(info.num_ancestors); + sg_state.ancestors.push(sg_state_idx, GFP_KERNEL)?; + + let parent_entry = match sg_state.sg_entries.get_mut(info.parent_sg_index) { + Some(parent_entry) => parent_entry, + None => { + pr_err!( + "validate_parent_fixup returned index out of bounds for sg.entries" + ); + return Err(EINVAL.into()); + } + }; + + parent_entry.fixup_min_offset = info.new_min_offset; + parent_entry.pointer_fixups.push( + PointerFixupEntry { + skip: 0, + pointer_value: buffer_ptr_in_user_space, + target_offset: info.target_offset, + }, + GFP_KERNEL, + )?; + } + + let mut obj_write = BinderBufferObject::default(); + obj_write.hdr.type_ = BINDER_TYPE_PTR; + obj_write.flags = obj.flags; + obj_write.buffer = buffer_ptr_in_user_space; + obj_write.length = obj.length; + obj_write.parent = obj.parent; + obj_write.parent_offset = obj.parent_offset; + view.write::<BinderBufferObject>(offset, &obj_write)?; + } + BinderObjectRef::Fda(obj) => { + if !allow_fds { + return Err(EPERM.into()); + } + let parent_index = usize::try_from(obj.parent).map_err(|_| EINVAL)?; + let parent_offset = usize::try_from(obj.parent_offset).map_err(|_| EINVAL)?; + let num_fds = usize::try_from(obj.num_fds).map_err(|_| EINVAL)?; + let fds_len = num_fds.checked_mul(size_of::<u32>()).ok_or(EINVAL)?; + + let info = sg_state.validate_parent_fixup(parent_index, parent_offset, fds_len)?; + view.alloc.info_add_fd_reserve(num_fds)?; + + sg_state.ancestors.truncate(info.num_ancestors); + let parent_entry = match sg_state.sg_entries.get_mut(info.parent_sg_index) { + Some(parent_entry) => parent_entry, + None => { + pr_err!( + "validate_parent_fixup returned index out of bounds for sg.entries" + ); + return Err(EINVAL.into()); + } + }; + + parent_entry.fixup_min_offset = info.new_min_offset; + parent_entry + .pointer_fixups + .push( + PointerFixupEntry { + skip: fds_len, + pointer_value: 0, + target_offset: info.target_offset, + }, + GFP_KERNEL, + ) + .map_err(|_| ENOMEM)?; + + let fda_uaddr = parent_entry + .sender_uaddr + .checked_add(parent_offset) + .ok_or(EINVAL)?; + let mut fda_bytes = KVec::new(); + UserSlice::new(UserPtr::from_addr(fda_uaddr as _), fds_len) + .read_all(&mut fda_bytes, GFP_KERNEL)?; + + if fds_len != fda_bytes.len() { + pr_err!("UserSlice::read_all returned wrong length in BINDER_TYPE_FDA"); + return Err(EINVAL.into()); + } + + for i in (0..fds_len).step_by(size_of::<u32>()) { + let fd = { + let mut fd_bytes = [0u8; size_of::<u32>()]; + fd_bytes.copy_from_slice(&fda_bytes[i..i + size_of::<u32>()]); + u32::from_ne_bytes(fd_bytes) + }; + + let file = LocalFile::fget(fd)?; + // SAFETY: The binder driver never calls `fdget_pos` and this code runs from an + // ioctl, so there are no active calls to `fdget_pos` on this thread. + let file = unsafe { LocalFile::assume_no_fdget_pos(file) }; + security::binder_transfer_file( + &self.process.cred, + &view.alloc.process.cred, + &file, + )?; + + // The `validate_parent_fixup` call ensuers that this addition will not + // overflow. + view.alloc.info_add_fd(file, info.target_offset + i, true)?; + } + drop(fda_bytes); + + let mut obj_write = BinderFdArrayObject::default(); + obj_write.hdr.type_ = BINDER_TYPE_FDA; + obj_write.num_fds = obj.num_fds; + obj_write.parent = obj.parent; + obj_write.parent_offset = obj.parent_offset; + view.write::<BinderFdArrayObject>(offset, &obj_write)?; + } + } + Ok(()) + } + + fn apply_sg(&self, alloc: &mut Allocation, sg_state: &mut ScatterGatherState) -> BinderResult { + for sg_entry in &mut sg_state.sg_entries { + let mut end_of_previous_fixup = sg_entry.offset; + let offset_end = sg_entry.offset.checked_add(sg_entry.length).ok_or(EINVAL)?; + + let mut reader = + UserSlice::new(UserPtr::from_addr(sg_entry.sender_uaddr), sg_entry.length).reader(); + for fixup in &mut sg_entry.pointer_fixups { + let fixup_len = if fixup.skip == 0 { + size_of::<u64>() + } else { + fixup.skip + }; + + let target_offset_end = fixup.target_offset.checked_add(fixup_len).ok_or(EINVAL)?; + if fixup.target_offset < end_of_previous_fixup || offset_end < target_offset_end { + pr_warn!( + "Fixups oob {} {} {} {}", + fixup.target_offset, + end_of_previous_fixup, + offset_end, + target_offset_end + ); + return Err(EINVAL.into()); + } + + let copy_off = end_of_previous_fixup; + let copy_len = fixup.target_offset - end_of_previous_fixup; + if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) { + pr_warn!("Failed copying into alloc: {:?}", err); + return Err(err.into()); + } + if fixup.skip == 0 { + let res = alloc.write::<u64>(fixup.target_offset, &fixup.pointer_value); + if let Err(err) = res { + pr_warn!("Failed copying ptr into alloc: {:?}", err); + return Err(err.into()); + } + } + if let Err(err) = reader.skip(fixup_len) { + pr_warn!("Failed skipping {} from reader: {:?}", fixup_len, err); + return Err(err.into()); + } + end_of_previous_fixup = target_offset_end; + } + let copy_off = end_of_previous_fixup; + let copy_len = offset_end - end_of_previous_fixup; + if let Err(err) = alloc.copy_into(&mut reader, copy_off, copy_len) { + pr_warn!("Failed copying remainder into alloc: {:?}", err); + return Err(err.into()); + } + } + Ok(()) + } + + /// This method copies the payload of a transaction into the target process. + /// + /// The resulting payload will have several different components, which will be stored next to + /// each other in the allocation. Furthermore, various objects can be embedded in the payload, + /// and those objects have to be translated so that they make sense to the target transaction. + pub(crate) fn copy_transaction_data( + &self, + to_process: Arc<Process>, + tr: &BinderTransactionDataSg, + debug_id: usize, + allow_fds: bool, + txn_security_ctx_offset: Option<&mut usize>, + ) -> BinderResult<NewAllocation> { + let trd = &tr.transaction_data; + let is_oneway = trd.flags & TF_ONE_WAY != 0; + let mut secctx = if let Some(offset) = txn_security_ctx_offset { + let secid = self.process.cred.get_secid(); + let ctx = match security::SecurityCtx::from_secid(secid) { + Ok(ctx) => ctx, + Err(err) => { + pr_warn!("Failed to get security ctx for id {}: {:?}", secid, err); + return Err(err.into()); + } + }; + Some((offset, ctx)) + } else { + None + }; + + let data_size = trd.data_size.try_into().map_err(|_| EINVAL)?; + let aligned_data_size = ptr_align(data_size).ok_or(EINVAL)?; + let offsets_size = trd.offsets_size.try_into().map_err(|_| EINVAL)?; + let aligned_offsets_size = ptr_align(offsets_size).ok_or(EINVAL)?; + let buffers_size = tr.buffers_size.try_into().map_err(|_| EINVAL)?; + let aligned_buffers_size = ptr_align(buffers_size).ok_or(EINVAL)?; + let aligned_secctx_size = match secctx.as_ref() { + Some((_offset, ctx)) => ptr_align(ctx.len()).ok_or(EINVAL)?, + None => 0, + }; + + // This guarantees that at least `sizeof(usize)` bytes will be allocated. + let len = usize::max( + aligned_data_size + .checked_add(aligned_offsets_size) + .and_then(|sum| sum.checked_add(aligned_buffers_size)) + .and_then(|sum| sum.checked_add(aligned_secctx_size)) + .ok_or(ENOMEM)?, + size_of::<usize>(), + ); + let secctx_off = aligned_data_size + aligned_offsets_size + aligned_buffers_size; + let mut alloc = + match to_process.buffer_alloc(debug_id, len, is_oneway, self.process.task.pid()) { + Ok(alloc) => alloc, + Err(err) => { + pr_warn!( + "Failed to allocate buffer. len:{}, is_oneway:{}", + len, + is_oneway + ); + return Err(err); + } + }; + + // SAFETY: This accesses a union field, but it's okay because the field's type is valid for + // all bit-patterns. + let trd_data_ptr = unsafe { &trd.data.ptr }; + let mut buffer_reader = + UserSlice::new(UserPtr::from_addr(trd_data_ptr.buffer as _), data_size).reader(); + let mut end_of_previous_object = 0; + let mut sg_state = None; + + // Copy offsets if there are any. + if offsets_size > 0 { + { + let mut reader = + UserSlice::new(UserPtr::from_addr(trd_data_ptr.offsets as _), offsets_size) + .reader(); + alloc.copy_into(&mut reader, aligned_data_size, offsets_size)?; + } + + let offsets_start = aligned_data_size; + let offsets_end = aligned_data_size + aligned_offsets_size; + + // This state is used for BINDER_TYPE_PTR objects. + let sg_state = sg_state.insert(ScatterGatherState { + unused_buffer_space: UnusedBufferSpace { + offset: offsets_end, + limit: len, + }, + sg_entries: KVec::new(), + ancestors: KVec::new(), + }); + + // Traverse the objects specified. + let mut view = AllocationView::new(&mut alloc, data_size); + for (index, index_offset) in (offsets_start..offsets_end) + .step_by(size_of::<usize>()) + .enumerate() + { + let offset = view.alloc.read(index_offset)?; + + if offset < end_of_previous_object { + pr_warn!("Got transaction with invalid offset."); + return Err(EINVAL.into()); + } + + // Copy data between two objects. + if end_of_previous_object < offset { + view.copy_into( + &mut buffer_reader, + end_of_previous_object, + offset - end_of_previous_object, + )?; + } + + let mut object = BinderObject::read_from(&mut buffer_reader)?; + + match self.translate_object( + index, + offset, + object.as_ref(), + &mut view, + allow_fds, + sg_state, + ) { + Ok(()) => end_of_previous_object = offset + object.size(), + Err(err) => { + pr_warn!("Error while translating object."); + return Err(err); + } + } + + // Update the indexes containing objects to clean up. + let offset_after_object = index_offset + size_of::<usize>(); + view.alloc + .set_info_offsets(offsets_start..offset_after_object); + } + } + + // Copy remaining raw data. + alloc.copy_into( + &mut buffer_reader, + end_of_previous_object, + data_size - end_of_previous_object, + )?; + + if let Some(sg_state) = sg_state.as_mut() { + if let Err(err) = self.apply_sg(&mut alloc, sg_state) { + pr_warn!("Failure in apply_sg: {:?}", err); + return Err(err); + } + } + + if let Some((off_out, secctx)) = secctx.as_mut() { + if let Err(err) = alloc.write(secctx_off, secctx.as_bytes()) { + pr_warn!("Failed to write security context: {:?}", err); + return Err(err.into()); + } + **off_out = secctx_off; + } + Ok(alloc) + } + + fn unwind_transaction_stack(self: &Arc<Self>) { + let mut thread = self.clone(); + while let Ok(transaction) = { + let mut inner = thread.inner.lock(); + inner.pop_transaction_to_reply(thread.as_ref()) + } { + let reply = Err(BR_DEAD_REPLY); + if !transaction.from.deliver_single_reply(reply, &transaction) { + break; + } + + thread = transaction.from.clone(); + } + } + + pub(crate) fn deliver_reply( + &self, + reply: Result<DLArc<Transaction>, u32>, + transaction: &DArc<Transaction>, + ) { + if self.deliver_single_reply(reply, transaction) { + transaction.from.unwind_transaction_stack(); + } + } + + /// Delivers a reply to the thread that started a transaction. The reply can either be a + /// reply-transaction or an error code to be delivered instead. + /// + /// Returns whether the thread is dead. If it is, the caller is expected to unwind the + /// transaction stack by completing transactions for threads that are dead. + fn deliver_single_reply( + &self, + reply: Result<DLArc<Transaction>, u32>, + transaction: &DArc<Transaction>, + ) -> bool { + if let Ok(transaction) = &reply { + transaction.set_outstanding(&mut self.process.inner.lock()); + } + + { + let mut inner = self.inner.lock(); + if !inner.pop_transaction_replied(transaction) { + return false; + } + + if inner.is_dead { + return true; + } + + match reply { + Ok(work) => { + inner.push_work(work); + } + Err(code) => inner.push_reply_work(code), + } + } + + // Notify the thread now that we've released the inner lock. + self.work_condvar.notify_sync(); + false + } + + /// Determines if the given transaction is the current transaction for this thread. + fn is_current_transaction(&self, transaction: &DArc<Transaction>) -> bool { + let inner = self.inner.lock(); + match &inner.current_transaction { + None => false, + Some(current) => Arc::ptr_eq(current, transaction), + } + } + + /// Determines the current top of the transaction stack. It fails if the top is in another + /// thread (i.e., this thread belongs to a stack but it has called another thread). The top is + /// [`None`] if the thread is not currently participating in a transaction stack. + fn top_of_transaction_stack(&self) -> Result<Option<DArc<Transaction>>> { + let inner = self.inner.lock(); + if let Some(cur) = &inner.current_transaction { + if core::ptr::eq(self, cur.from.as_ref()) { + pr_warn!("got new transaction with bad transaction stack"); + return Err(EINVAL); + } + Ok(Some(cur.clone())) + } else { + Ok(None) + } + } + + fn transaction<T>(self: &Arc<Self>, tr: &BinderTransactionDataSg, inner: T) + where + T: FnOnce(&Arc<Self>, &BinderTransactionDataSg) -> BinderResult, + { + if let Err(err) = inner(self, tr) { + if err.should_pr_warn() { + let mut ee = self.inner.lock().extended_error; + ee.command = err.reply; + ee.param = err.as_errno(); + pr_warn!( + "Transaction failed: {:?} my_pid:{}", + err, + self.process.pid_in_current_ns() + ); + } + + self.push_return_work(err.reply); + } + } + + fn transaction_inner(self: &Arc<Self>, tr: &BinderTransactionDataSg) -> BinderResult { + // SAFETY: Handle's type has no invalid bit patterns. + let handle = unsafe { tr.transaction_data.target.handle }; + let node_ref = self.process.get_transaction_node(handle)?; + security::binder_transaction(&self.process.cred, &node_ref.node.owner.cred)?; + // TODO: We need to ensure that there isn't a pending transaction in the work queue. How + // could this happen? + let top = self.top_of_transaction_stack()?; + let list_completion = DTRWrap::arc_try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?; + let completion = list_completion.clone_arc(); + let transaction = Transaction::new(node_ref, top, self, tr)?; + + // Check that the transaction stack hasn't changed while the lock was released, then update + // it with the new transaction. + { + let mut inner = self.inner.lock(); + if !transaction.is_stacked_on(&inner.current_transaction) { + pr_warn!("Transaction stack changed during transaction!"); + return Err(EINVAL.into()); + } + inner.current_transaction = Some(transaction.clone_arc()); + // We push the completion as a deferred work so that we wait for the reply before + // returning to userland. + inner.push_work_deferred(list_completion); + } + + if let Err(e) = transaction.submit() { + completion.skip(); + // Define `transaction` first to drop it after `inner`. + let transaction; + let mut inner = self.inner.lock(); + transaction = inner.current_transaction.take().unwrap(); + inner.current_transaction = transaction.clone_next(); + Err(e) + } else { + Ok(()) + } + } + + fn reply_inner(self: &Arc<Self>, tr: &BinderTransactionDataSg) -> BinderResult { + let orig = self.inner.lock().pop_transaction_to_reply(self)?; + if !orig.from.is_current_transaction(&orig) { + return Err(EINVAL.into()); + } + + // We need to complete the transaction even if we cannot complete building the reply. + let out = (|| -> BinderResult<_> { + let completion = DTRWrap::arc_try_new(DeliverCode::new(BR_TRANSACTION_COMPLETE))?; + let process = orig.from.process.clone(); + let allow_fds = orig.flags & TF_ACCEPT_FDS != 0; + let reply = Transaction::new_reply(self, process, tr, allow_fds)?; + self.inner.lock().push_work(completion); + orig.from.deliver_reply(Ok(reply), &orig); + Ok(()) + })() + .map_err(|mut err| { + // At this point we only return `BR_TRANSACTION_COMPLETE` to the caller, and we must let + // the sender know that the transaction has completed (with an error in this case). + pr_warn!( + "Failure {:?} during reply - delivering BR_FAILED_REPLY to sender.", + err + ); + let reply = Err(BR_FAILED_REPLY); + orig.from.deliver_reply(reply, &orig); + err.reply = BR_TRANSACTION_COMPLETE; + err + }); + + out + } + + fn oneway_transaction_inner(self: &Arc<Self>, tr: &BinderTransactionDataSg) -> BinderResult { + // SAFETY: The `handle` field is valid for all possible byte values, so reading from the + // union is okay. + let handle = unsafe { tr.transaction_data.target.handle }; + let node_ref = self.process.get_transaction_node(handle)?; + security::binder_transaction(&self.process.cred, &node_ref.node.owner.cred)?; + let transaction = Transaction::new(node_ref, None, self, tr)?; + let code = if self.process.is_oneway_spam_detection_enabled() + && transaction.oneway_spam_detected + { + BR_ONEWAY_SPAM_SUSPECT + } else { + BR_TRANSACTION_COMPLETE + }; + let list_completion = DTRWrap::arc_try_new(DeliverCode::new(code))?; + let completion = list_completion.clone_arc(); + self.inner.lock().push_work(list_completion); + match transaction.submit() { + Ok(()) => Ok(()), + Err(err) => { + completion.skip(); + Err(err) + } + } + } + + fn write(self: &Arc<Self>, req: &mut BinderWriteRead) -> Result { + let write_start = req.write_buffer.wrapping_add(req.write_consumed); + let write_len = req.write_size.saturating_sub(req.write_consumed); + let mut reader = + UserSlice::new(UserPtr::from_addr(write_start as _), write_len as _).reader(); + + while reader.len() >= size_of::<u32>() && self.inner.lock().return_work.is_unused() { + let before = reader.len(); + let cmd = reader.read::<u32>()?; + GLOBAL_STATS.inc_bc(cmd); + self.process.stats.inc_bc(cmd); + match cmd { + BC_TRANSACTION => { + let tr = reader.read::<BinderTransactionData>()?.with_buffers_size(0); + if tr.transaction_data.flags & TF_ONE_WAY != 0 { + self.transaction(&tr, Self::oneway_transaction_inner); + } else { + self.transaction(&tr, Self::transaction_inner); + } + } + BC_TRANSACTION_SG => { + let tr = reader.read::<BinderTransactionDataSg>()?; + if tr.transaction_data.flags & TF_ONE_WAY != 0 { + self.transaction(&tr, Self::oneway_transaction_inner); + } else { + self.transaction(&tr, Self::transaction_inner); + } + } + BC_REPLY => { + let tr = reader.read::<BinderTransactionData>()?.with_buffers_size(0); + self.transaction(&tr, Self::reply_inner) + } + BC_REPLY_SG => { + let tr = reader.read::<BinderTransactionDataSg>()?; + self.transaction(&tr, Self::reply_inner) + } + BC_FREE_BUFFER => { + let buffer = self.process.buffer_get(reader.read()?); + if let Some(buffer) = &buffer { + if buffer.looper_need_return_on_free() { + self.inner.lock().looper_need_return = true; + } + } + drop(buffer); + } + BC_INCREFS => { + self.process + .as_arc_borrow() + .update_ref(reader.read()?, true, false)? + } + BC_ACQUIRE => { + self.process + .as_arc_borrow() + .update_ref(reader.read()?, true, true)? + } + BC_RELEASE => { + self.process + .as_arc_borrow() + .update_ref(reader.read()?, false, true)? + } + BC_DECREFS => { + self.process + .as_arc_borrow() + .update_ref(reader.read()?, false, false)? + } + BC_INCREFS_DONE => self.process.inc_ref_done(&mut reader, false)?, + BC_ACQUIRE_DONE => self.process.inc_ref_done(&mut reader, true)?, + BC_REQUEST_DEATH_NOTIFICATION => self.process.request_death(&mut reader, self)?, + BC_CLEAR_DEATH_NOTIFICATION => self.process.clear_death(&mut reader, self)?, + BC_DEAD_BINDER_DONE => self.process.dead_binder_done(reader.read()?, self), + BC_REGISTER_LOOPER => { + let valid = self.process.register_thread(); + self.inner.lock().looper_register(valid); + } + BC_ENTER_LOOPER => self.inner.lock().looper_enter(), + BC_EXIT_LOOPER => self.inner.lock().looper_exit(), + BC_REQUEST_FREEZE_NOTIFICATION => self.process.request_freeze_notif(&mut reader)?, + BC_CLEAR_FREEZE_NOTIFICATION => self.process.clear_freeze_notif(&mut reader)?, + BC_FREEZE_NOTIFICATION_DONE => self.process.freeze_notif_done(&mut reader)?, + + // Fail if given an unknown error code. + // BC_ATTEMPT_ACQUIRE and BC_ACQUIRE_RESULT are no longer supported. + _ => return Err(EINVAL), + } + // Update the number of write bytes consumed. + req.write_consumed += (before - reader.len()) as u64; + } + + Ok(()) + } + + fn read(self: &Arc<Self>, req: &mut BinderWriteRead, wait: bool) -> Result { + let read_start = req.read_buffer.wrapping_add(req.read_consumed); + let read_len = req.read_size.saturating_sub(req.read_consumed); + let mut writer = BinderReturnWriter::new( + UserSlice::new(UserPtr::from_addr(read_start as _), read_len as _).writer(), + self, + ); + let (in_pool, use_proc_queue) = { + let inner = self.inner.lock(); + (inner.is_looper(), inner.should_use_process_work_queue()) + }; + + let getter = if use_proc_queue { + Self::get_work + } else { + Self::get_work_local + }; + + // Reserve some room at the beginning of the read buffer so that we can send a + // BR_SPAWN_LOOPER if we need to. + let mut has_noop_placeholder = false; + if req.read_consumed == 0 { + if let Err(err) = writer.write_code(BR_NOOP) { + pr_warn!("Failure when writing BR_NOOP at beginning of buffer."); + return Err(err); + } + has_noop_placeholder = true; + } + + // Loop doing work while there is room in the buffer. + let initial_len = writer.len(); + while writer.len() >= size_of::<uapi::binder_transaction_data_secctx>() + 4 { + match getter(self, wait && initial_len == writer.len()) { + Ok(Some(work)) => match work.into_arc().do_work(self, &mut writer) { + Ok(true) => {} + Ok(false) => break, + Err(err) => { + return Err(err); + } + }, + Ok(None) => { + break; + } + Err(err) => { + // Propagate the error if we haven't written anything else. + if err != EINTR && err != EAGAIN { + pr_warn!("Failure in work getter: {:?}", err); + } + if initial_len == writer.len() { + return Err(err); + } else { + break; + } + } + } + } + + req.read_consumed += read_len - writer.len() as u64; + + // Write BR_SPAWN_LOOPER if the process needs more threads for its pool. + if has_noop_placeholder && in_pool && self.process.needs_thread() { + let mut writer = + UserSlice::new(UserPtr::from_addr(req.read_buffer as _), req.read_size as _) + .writer(); + writer.write(&BR_SPAWN_LOOPER)?; + } + Ok(()) + } + + pub(crate) fn write_read(self: &Arc<Self>, data: UserSlice, wait: bool) -> Result { + let (mut reader, mut writer) = data.reader_writer(); + let mut req = reader.read::<BinderWriteRead>()?; + + // Go through the write buffer. + let mut ret = Ok(()); + if req.write_size > 0 { + ret = self.write(&mut req); + if let Err(err) = ret { + pr_warn!( + "Write failure {:?} in pid:{}", + err, + self.process.pid_in_current_ns() + ); + req.read_consumed = 0; + writer.write(&req)?; + self.inner.lock().looper_need_return = false; + return ret; + } + } + + // Go through the work queue. + if req.read_size > 0 { + ret = self.read(&mut req, wait); + if ret.is_err() && ret != Err(EINTR) { + pr_warn!( + "Read failure {:?} in pid:{}", + ret, + self.process.pid_in_current_ns() + ); + } + } + + // Write the request back so that the consumed fields are visible to the caller. + writer.write(&req)?; + + self.inner.lock().looper_need_return = false; + + ret + } + + pub(crate) fn poll(&self, file: &File, table: PollTable<'_>) -> (bool, u32) { + table.register_wait(file, &self.work_condvar); + let mut inner = self.inner.lock(); + (inner.should_use_process_work_queue(), inner.poll()) + } + + /// Make the call to `get_work` or `get_work_local` return immediately, if any. + pub(crate) fn exit_looper(&self) { + let mut inner = self.inner.lock(); + let should_notify = inner.looper_flags & LOOPER_WAITING != 0; + if should_notify { + inner.looper_need_return = true; + } + drop(inner); + + if should_notify { + self.work_condvar.notify_one(); + } + } + + pub(crate) fn notify_if_poll_ready(&self, sync: bool) { + // Determine if we need to notify. This requires the lock. + let inner = self.inner.lock(); + let notify = inner.looper_flags & LOOPER_POLL != 0 && inner.should_use_process_work_queue(); + drop(inner); + + // Now that the lock is no longer held, notify the waiters if we have to. + if notify { + if sync { + self.work_condvar.notify_sync(); + } else { + self.work_condvar.notify_one(); + } + } + } + + pub(crate) fn release(self: &Arc<Self>) { + self.inner.lock().is_dead = true; + + //self.work_condvar.clear(); + self.unwind_transaction_stack(); + + // Cancel all pending work items. + while let Ok(Some(work)) = self.get_work_local(false) { + work.into_arc().cancel(); + } + } +} + +#[pin_data] +struct ThreadError { + error_code: AtomicU32, + #[pin] + links_track: AtomicTracker, +} + +impl ThreadError { + fn try_new() -> Result<DArc<Self>> { + DTRWrap::arc_pin_init(pin_init!(Self { + error_code: AtomicU32::new(BR_OK), + links_track <- AtomicTracker::new(), + })) + .map(ListArc::into_arc) + } + + fn set_error_code(&self, code: u32) { + self.error_code.store(code, Ordering::Relaxed); + } + + fn is_unused(&self) -> bool { + self.error_code.load(Ordering::Relaxed) == BR_OK + } +} + +impl DeliverToRead for ThreadError { + fn do_work( + self: DArc<Self>, + _thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let code = self.error_code.load(Ordering::Relaxed); + self.error_code.store(BR_OK, Ordering::Relaxed); + writer.write_code(code)?; + Ok(true) + } + + fn cancel(self: DArc<Self>) {} + + fn should_sync_wakeup(&self) -> bool { + false + } + + fn debug_print(&self, m: &SeqFile, prefix: &str, _tprefix: &str) -> Result<()> { + seq_print!( + m, + "{}transaction error: {}\n", + prefix, + self.error_code.load(Ordering::Relaxed) + ); + Ok(()) + } +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for ThreadError { + tracked_by links_track: AtomicTracker; + } +} diff --git a/drivers/android/binder/trace.rs b/drivers/android/binder/trace.rs new file mode 100644 index 000000000000..af0e4392805e --- /dev/null +++ b/drivers/android/binder/trace.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use kernel::ffi::{c_uint, c_ulong}; +use kernel::tracepoint::declare_trace; + +declare_trace! { + unsafe fn rust_binder_ioctl(cmd: c_uint, arg: c_ulong); +} + +#[inline] +pub(crate) fn trace_ioctl(cmd: u32, arg: usize) { + // SAFETY: Always safe to call. + unsafe { rust_binder_ioctl(cmd, arg as c_ulong) } +} diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs new file mode 100644 index 000000000000..02512175d622 --- /dev/null +++ b/drivers/android/binder/transaction.rs @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +use core::sync::atomic::{AtomicBool, Ordering}; +use kernel::{ + prelude::*, + seq_file::SeqFile, + seq_print, + sync::{Arc, SpinLock}, + task::Kuid, + time::{Instant, Monotonic}, + types::ScopeGuard, +}; + +use crate::{ + allocation::{Allocation, TranslatedFds}, + defs::*, + error::{BinderError, BinderResult}, + node::{Node, NodeRef}, + process::{Process, ProcessInner}, + ptr_align, + thread::{PushWorkRes, Thread}, + BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead, +}; + +#[pin_data(PinnedDrop)] +pub(crate) struct Transaction { + pub(crate) debug_id: usize, + target_node: Option<DArc<Node>>, + pub(crate) from_parent: Option<DArc<Transaction>>, + pub(crate) from: Arc<Thread>, + pub(crate) to: Arc<Process>, + #[pin] + allocation: SpinLock<Option<Allocation>>, + is_outstanding: AtomicBool, + code: u32, + pub(crate) flags: u32, + data_size: usize, + offsets_size: usize, + data_address: usize, + sender_euid: Kuid, + txn_security_ctx_off: Option<usize>, + pub(crate) oneway_spam_detected: bool, + start_time: Instant<Monotonic>, +} + +kernel::list::impl_list_arc_safe! { + impl ListArcSafe<0> for Transaction { untracked; } +} + +impl Transaction { + pub(crate) fn new( + node_ref: NodeRef, + from_parent: Option<DArc<Transaction>>, + from: &Arc<Thread>, + tr: &BinderTransactionDataSg, + ) -> BinderResult<DLArc<Self>> { + let debug_id = super::next_debug_id(); + let trd = &tr.transaction_data; + let allow_fds = node_ref.node.flags & FLAT_BINDER_FLAG_ACCEPTS_FDS != 0; + let txn_security_ctx = node_ref.node.flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX != 0; + let mut txn_security_ctx_off = if txn_security_ctx { Some(0) } else { None }; + let to = node_ref.node.owner.clone(); + let mut alloc = match from.copy_transaction_data( + to.clone(), + tr, + debug_id, + allow_fds, + txn_security_ctx_off.as_mut(), + ) { + Ok(alloc) => alloc, + Err(err) => { + if !err.is_dead() { + pr_warn!("Failure in copy_transaction_data: {:?}", err); + } + return Err(err); + } + }; + let oneway_spam_detected = alloc.oneway_spam_detected; + if trd.flags & TF_ONE_WAY != 0 { + if from_parent.is_some() { + pr_warn!("Oneway transaction should not be in a transaction stack."); + return Err(EINVAL.into()); + } + alloc.set_info_oneway_node(node_ref.node.clone()); + } + if trd.flags & TF_CLEAR_BUF != 0 { + alloc.set_info_clear_on_drop(); + } + let target_node = node_ref.node.clone(); + alloc.set_info_target_node(node_ref); + let data_address = alloc.ptr; + + Ok(DTRWrap::arc_pin_init(pin_init!(Transaction { + debug_id, + target_node: Some(target_node), + from_parent, + sender_euid: from.process.task.euid(), + from: from.clone(), + to, + code: trd.code, + flags: trd.flags, + data_size: trd.data_size as _, + offsets_size: trd.offsets_size as _, + data_address, + allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"), + is_outstanding: AtomicBool::new(false), + txn_security_ctx_off, + oneway_spam_detected, + start_time: Instant::now(), + }))?) + } + + pub(crate) fn new_reply( + from: &Arc<Thread>, + to: Arc<Process>, + tr: &BinderTransactionDataSg, + allow_fds: bool, + ) -> BinderResult<DLArc<Self>> { + let debug_id = super::next_debug_id(); + let trd = &tr.transaction_data; + let mut alloc = match from.copy_transaction_data(to.clone(), tr, debug_id, allow_fds, None) + { + Ok(alloc) => alloc, + Err(err) => { + pr_warn!("Failure in copy_transaction_data: {:?}", err); + return Err(err); + } + }; + let oneway_spam_detected = alloc.oneway_spam_detected; + if trd.flags & TF_CLEAR_BUF != 0 { + alloc.set_info_clear_on_drop(); + } + Ok(DTRWrap::arc_pin_init(pin_init!(Transaction { + debug_id, + target_node: None, + from_parent: None, + sender_euid: from.process.task.euid(), + from: from.clone(), + to, + code: trd.code, + flags: trd.flags, + data_size: trd.data_size as _, + offsets_size: trd.offsets_size as _, + data_address: alloc.ptr, + allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"), + is_outstanding: AtomicBool::new(false), + txn_security_ctx_off: None, + oneway_spam_detected, + start_time: Instant::now(), + }))?) + } + + #[inline(never)] + pub(crate) fn debug_print_inner(&self, m: &SeqFile, prefix: &str) { + seq_print!( + m, + "{}{}: from {}:{} to {} code {:x} flags {:x} elapsed {}ms", + prefix, + self.debug_id, + self.from.process.task.pid(), + self.from.id, + self.to.task.pid(), + self.code, + self.flags, + self.start_time.elapsed().as_millis(), + ); + if let Some(target_node) = &self.target_node { + seq_print!(m, " node {}", target_node.debug_id); + } + seq_print!(m, " size {}:{}\n", self.data_size, self.offsets_size); + } + + /// Determines if the transaction is stacked on top of the given transaction. + pub(crate) fn is_stacked_on(&self, onext: &Option<DArc<Self>>) -> bool { + match (&self.from_parent, onext) { + (None, None) => true, + (Some(from_parent), Some(next)) => Arc::ptr_eq(from_parent, next), + _ => false, + } + } + + /// Returns a pointer to the next transaction on the transaction stack, if there is one. + pub(crate) fn clone_next(&self) -> Option<DArc<Self>> { + Some(self.from_parent.as_ref()?.clone()) + } + + /// Searches in the transaction stack for a thread that belongs to the target process. This is + /// useful when finding a target for a new transaction: if the node belongs to a process that + /// is already part of the transaction stack, we reuse the thread. + fn find_target_thread(&self) -> Option<Arc<Thread>> { + let mut it = &self.from_parent; + while let Some(transaction) = it { + if Arc::ptr_eq(&transaction.from.process, &self.to) { + return Some(transaction.from.clone()); + } + it = &transaction.from_parent; + } + None + } + + /// Searches in the transaction stack for a transaction originating at the given thread. + pub(crate) fn find_from(&self, thread: &Thread) -> Option<&DArc<Transaction>> { + let mut it = &self.from_parent; + while let Some(transaction) = it { + if core::ptr::eq(thread, transaction.from.as_ref()) { + return Some(transaction); + } + + it = &transaction.from_parent; + } + None + } + + pub(crate) fn set_outstanding(&self, to_process: &mut ProcessInner) { + // No race because this method is only called once. + if !self.is_outstanding.load(Ordering::Relaxed) { + self.is_outstanding.store(true, Ordering::Relaxed); + to_process.add_outstanding_txn(); + } + } + + /// Decrement `outstanding_txns` in `to` if it hasn't already been decremented. + fn drop_outstanding_txn(&self) { + // No race because this is called at most twice, and one of the calls are in the + // destructor, which is guaranteed to not race with any other operations on the + // transaction. It also cannot race with `set_outstanding`, since submission happens + // before delivery. + if self.is_outstanding.load(Ordering::Relaxed) { + self.is_outstanding.store(false, Ordering::Relaxed); + self.to.drop_outstanding_txn(); + } + } + + /// Submits the transaction to a work queue. Uses a thread if there is one in the transaction + /// stack, otherwise uses the destination process. + /// + /// Not used for replies. + pub(crate) fn submit(self: DLArc<Self>) -> BinderResult { + // Defined before `process_inner` so that the destructor runs after releasing the lock. + let mut _t_outdated; + + let oneway = self.flags & TF_ONE_WAY != 0; + let process = self.to.clone(); + let mut process_inner = process.inner.lock(); + + self.set_outstanding(&mut process_inner); + + if oneway { + if let Some(target_node) = self.target_node.clone() { + if process_inner.is_frozen { + process_inner.async_recv = true; + if self.flags & TF_UPDATE_TXN != 0 { + if let Some(t_outdated) = + target_node.take_outdated_transaction(&self, &mut process_inner) + { + // Save the transaction to be dropped after locks are released. + _t_outdated = t_outdated; + } + } + } + match target_node.submit_oneway(self, &mut process_inner) { + Ok(()) => {} + Err((err, work)) => { + drop(process_inner); + // Drop work after releasing process lock. + drop(work); + return Err(err); + } + } + + if process_inner.is_frozen { + return Err(BinderError::new_frozen_oneway()); + } else { + return Ok(()); + } + } else { + pr_err!("Failed to submit oneway transaction to node."); + } + } + + if process_inner.is_frozen { + process_inner.sync_recv = true; + return Err(BinderError::new_frozen()); + } + + let res = if let Some(thread) = self.find_target_thread() { + match thread.push_work(self) { + PushWorkRes::Ok => Ok(()), + PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)), + } + } else { + process_inner.push_work(self) + }; + drop(process_inner); + + match res { + Ok(()) => Ok(()), + Err((err, work)) => { + // Drop work after releasing process lock. + drop(work); + Err(err) + } + } + } + + /// Check whether one oneway transaction can supersede another. + pub(crate) fn can_replace(&self, old: &Transaction) -> bool { + if self.from.process.task.pid() != old.from.process.task.pid() { + return false; + } + + if self.flags & old.flags & (TF_ONE_WAY | TF_UPDATE_TXN) != (TF_ONE_WAY | TF_UPDATE_TXN) { + return false; + } + + let target_node_match = match (self.target_node.as_ref(), old.target_node.as_ref()) { + (None, None) => true, + (Some(tn1), Some(tn2)) => Arc::ptr_eq(tn1, tn2), + _ => false, + }; + + self.code == old.code && self.flags == old.flags && target_node_match + } + + fn prepare_file_list(&self) -> Result<TranslatedFds> { + let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?; + + match alloc.translate_fds() { + Ok(translated) => { + *self.allocation.lock() = Some(alloc); + Ok(translated) + } + Err(err) => { + // Free the allocation eagerly. + drop(alloc); + Err(err) + } + } + } +} + +impl DeliverToRead for Transaction { + fn do_work( + self: DArc<Self>, + thread: &Thread, + writer: &mut BinderReturnWriter<'_>, + ) -> Result<bool> { + let send_failed_reply = ScopeGuard::new(|| { + if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { + let reply = Err(BR_FAILED_REPLY); + self.from.deliver_reply(reply, &self); + } + self.drop_outstanding_txn(); + }); + + let files = if let Ok(list) = self.prepare_file_list() { + list + } else { + // On failure to process the list, we send a reply back to the sender and ignore the + // transaction on the recipient. + return Ok(true); + }; + + let mut tr_sec = BinderTransactionDataSecctx::default(); + let tr = tr_sec.tr_data(); + if let Some(target_node) = &self.target_node { + let (ptr, cookie) = target_node.get_id(); + tr.target.ptr = ptr as _; + tr.cookie = cookie as _; + }; + tr.code = self.code; + tr.flags = self.flags; + tr.data_size = self.data_size as _; + tr.data.ptr.buffer = self.data_address as _; + tr.offsets_size = self.offsets_size as _; + if tr.offsets_size > 0 { + tr.data.ptr.offsets = (self.data_address + ptr_align(self.data_size).unwrap()) as _; + } + tr.sender_euid = self.sender_euid.into_uid_in_current_ns(); + tr.sender_pid = 0; + if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { + // Not a reply and not one-way. + tr.sender_pid = self.from.process.pid_in_current_ns(); + } + let code = if self.target_node.is_none() { + BR_REPLY + } else if self.txn_security_ctx_off.is_some() { + BR_TRANSACTION_SEC_CTX + } else { + BR_TRANSACTION + }; + + // Write the transaction code and data to the user buffer. + writer.write_code(code)?; + if let Some(off) = self.txn_security_ctx_off { + tr_sec.secctx = (self.data_address + off) as u64; + writer.write_payload(&tr_sec)?; + } else { + writer.write_payload(&*tr)?; + } + + let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?; + + // Dismiss the completion of transaction with a failure. No failure paths are allowed from + // here on out. + send_failed_reply.dismiss(); + + // Commit files, and set FDs in FDA to be closed on buffer free. + let close_on_free = files.commit(); + alloc.set_info_close_on_free(close_on_free); + + // It is now the user's responsibility to clear the allocation. + alloc.keep_alive(); + + self.drop_outstanding_txn(); + + // When this is not a reply and not a oneway transaction, update `current_transaction`. If + // it's a reply, `current_transaction` has already been updated appropriately. + if self.target_node.is_some() && tr_sec.transaction_data.flags & TF_ONE_WAY == 0 { + thread.set_current_transaction(self); + } + + Ok(false) + } + + fn cancel(self: DArc<Self>) { + let allocation = self.allocation.lock().take(); + drop(allocation); + + // If this is not a reply or oneway transaction, then send a dead reply. + if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { + let reply = Err(BR_DEAD_REPLY); + self.from.deliver_reply(reply, &self); + } + + self.drop_outstanding_txn(); + } + + fn should_sync_wakeup(&self) -> bool { + self.flags & TF_ONE_WAY == 0 + } + + fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()> { + self.debug_print_inner(m, tprefix); + Ok(()) + } +} + +#[pinned_drop] +impl PinnedDrop for Transaction { + fn drop(self: Pin<&mut Self>) { + self.drop_outstanding_txn(); + } +} diff --git a/drivers/android/binder_internal.h b/drivers/android/binder_internal.h index 8b08976146ba..342574bfd28a 100644 --- a/drivers/android/binder_internal.h +++ b/drivers/android/binder_internal.h @@ -537,8 +537,8 @@ struct binder_transaction { struct binder_proc *to_proc; struct binder_thread *to_thread; struct binder_transaction *to_parent; - unsigned need_reply:1; - /* unsigned is_dead:1; */ /* not used at the moment */ + unsigned is_async:1; + unsigned is_reply:1; struct binder_buffer *buffer; unsigned int code; diff --git a/drivers/android/binder_netlink.c b/drivers/android/binder_netlink.c new file mode 100644 index 000000000000..d05397a50ca6 --- /dev/null +++ b/drivers/android/binder_netlink.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/binder.yaml */ +/* YNL-GEN kernel source */ + +#include <net/netlink.h> +#include <net/genetlink.h> + +#include "binder_netlink.h" + +#include <uapi/linux/android/binder_netlink.h> + +/* Ops table for binder */ +static const struct genl_split_ops binder_nl_ops[] = { +}; + +static const struct genl_multicast_group binder_nl_mcgrps[] = { + [BINDER_NLGRP_REPORT] = { "report", }, +}; + +struct genl_family binder_nl_family __ro_after_init = { + .name = BINDER_FAMILY_NAME, + .version = BINDER_FAMILY_VERSION, + .netnsok = true, + .parallel_ops = true, + .module = THIS_MODULE, + .split_ops = binder_nl_ops, + .n_split_ops = ARRAY_SIZE(binder_nl_ops), + .mcgrps = binder_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(binder_nl_mcgrps), +}; diff --git a/drivers/android/binder_netlink.h b/drivers/android/binder_netlink.h new file mode 100644 index 000000000000..882c7a6b537e --- /dev/null +++ b/drivers/android/binder_netlink.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/binder.yaml */ +/* YNL-GEN kernel header */ + +#ifndef _LINUX_BINDER_GEN_H +#define _LINUX_BINDER_GEN_H + +#include <net/netlink.h> +#include <net/genetlink.h> + +#include <uapi/linux/android/binder_netlink.h> + +enum { + BINDER_NLGRP_REPORT, +}; + +extern struct genl_family binder_nl_family; + +#endif /* _LINUX_BINDER_GEN_H */ diff --git a/drivers/android/binder_trace.h b/drivers/android/binder_trace.h index 97a78e5623db..fa5eb61cf580 100644 --- a/drivers/android/binder_trace.h +++ b/drivers/android/binder_trace.h @@ -402,6 +402,43 @@ TRACE_EVENT(binder_return, "unknown") ); +TRACE_EVENT(binder_netlink_report, + TP_PROTO(const char *context, + struct binder_transaction *t, + u32 data_size, + u32 error), + TP_ARGS(context, t, data_size, error), + TP_STRUCT__entry( + __field(const char *, context) + __field(u32, error) + __field(int, from_pid) + __field(int, from_tid) + __field(int, to_pid) + __field(int, to_tid) + __field(bool, is_reply) + __field(unsigned int, flags) + __field(unsigned int, code) + __field(size_t, data_size) + ), + TP_fast_assign( + __entry->context = context; + __entry->error = error; + __entry->from_pid = t->from_pid; + __entry->from_tid = t->from_tid; + __entry->to_pid = t->to_proc ? t->to_proc->pid : 0; + __entry->to_tid = t->to_thread ? t->to_thread->pid : 0; + __entry->is_reply = t->is_reply; + __entry->flags = t->flags; + __entry->code = t->code; + __entry->data_size = data_size; + ), + TP_printk("from %d:%d to %d:%d context=%s error=%d is_reply=%d flags=0x%x code=0x%x size=%zu", + __entry->from_pid, __entry->from_tid, + __entry->to_pid, __entry->to_tid, + __entry->context, __entry->error, __entry->is_reply, + __entry->flags, __entry->code, __entry->data_size) +); + #endif /* _BINDER_TRACE_H */ #undef TRACE_INCLUDE_PATH diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c index 0d9d95a7fb60..be8e64eb39ec 100644 --- a/drivers/android/binderfs.c +++ b/drivers/android/binderfs.c @@ -59,6 +59,7 @@ struct binder_features { bool oneway_spam_detection; bool extended_error; bool freeze_notification; + bool transaction_report; }; static const struct constant_table binderfs_param_stats[] = { @@ -76,6 +77,7 @@ static struct binder_features binder_features = { .oneway_spam_detection = true, .extended_error = true, .freeze_notification = true, + .transaction_report = true, }; static inline struct binderfs_info *BINDERFS_SB(const struct super_block *sb) @@ -601,6 +603,12 @@ static int init_binder_features(struct super_block *sb) if (IS_ERR(dentry)) return PTR_ERR(dentry); + dentry = binderfs_create_file(dir, "transaction_report", + &binder_features_fops, + &binder_features.transaction_report); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + return 0; } diff --git a/drivers/android/dbitmap.h b/drivers/android/dbitmap.h index 956f1bd087d1..c7299ce8b374 100644 --- a/drivers/android/dbitmap.h +++ b/drivers/android/dbitmap.h @@ -37,6 +37,7 @@ static inline void dbitmap_free(struct dbitmap *dmap) { dmap->nbits = 0; kfree(dmap->map); + dmap->map = NULL; } /* Returns the nbits that a dbitmap can shrink to, 0 if not possible. */ diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c index b3eafcf2a2c5..cdea24e92919 100644 --- a/drivers/bus/mhi/ep/main.c +++ b/drivers/bus/mhi/ep/main.c @@ -403,17 +403,13 @@ static int mhi_ep_read_channel(struct mhi_ep_cntrl *mhi_cntrl, { struct mhi_ep_chan *mhi_chan = &mhi_cntrl->mhi_chan[ring->ch_id]; struct device *dev = &mhi_cntrl->mhi_dev->dev; - size_t tr_len, read_offset, write_offset; + size_t tr_len, read_offset; struct mhi_ep_buf_info buf_info = {}; u32 len = MHI_EP_DEFAULT_MTU; struct mhi_ring_element *el; - bool tr_done = false; void *buf_addr; - u32 buf_left; int ret; - buf_left = len; - do { /* Don't process the transfer ring if the channel is not in RUNNING state */ if (mhi_chan->state != MHI_CH_STATE_RUNNING) { @@ -426,24 +422,23 @@ static int mhi_ep_read_channel(struct mhi_ep_cntrl *mhi_cntrl, /* Check if there is data pending to be read from previous read operation */ if (mhi_chan->tre_bytes_left) { dev_dbg(dev, "TRE bytes remaining: %u\n", mhi_chan->tre_bytes_left); - tr_len = min(buf_left, mhi_chan->tre_bytes_left); + tr_len = min(len, mhi_chan->tre_bytes_left); } else { mhi_chan->tre_loc = MHI_TRE_DATA_GET_PTR(el); mhi_chan->tre_size = MHI_TRE_DATA_GET_LEN(el); mhi_chan->tre_bytes_left = mhi_chan->tre_size; - tr_len = min(buf_left, mhi_chan->tre_size); + tr_len = min(len, mhi_chan->tre_size); } read_offset = mhi_chan->tre_size - mhi_chan->tre_bytes_left; - write_offset = len - buf_left; buf_addr = kmem_cache_zalloc(mhi_cntrl->tre_buf_cache, GFP_KERNEL); if (!buf_addr) return -ENOMEM; buf_info.host_addr = mhi_chan->tre_loc + read_offset; - buf_info.dev_addr = buf_addr + write_offset; + buf_info.dev_addr = buf_addr; buf_info.size = tr_len; buf_info.cb = mhi_ep_read_completion; buf_info.cb_buf = buf_addr; @@ -459,16 +454,12 @@ static int mhi_ep_read_channel(struct mhi_ep_cntrl *mhi_cntrl, goto err_free_buf_addr; } - buf_left -= tr_len; mhi_chan->tre_bytes_left -= tr_len; - if (!mhi_chan->tre_bytes_left) { - if (MHI_TRE_DATA_GET_IEOT(el)) - tr_done = true; - + if (!mhi_chan->tre_bytes_left) mhi_chan->rd_offset = (mhi_chan->rd_offset + 1) % ring->ring_size; - } - } while (buf_left && !tr_done); + /* Read until the some buffer is left or the ring becomes not empty */ + } while (!mhi_ep_queue_is_empty(mhi_chan->mhi_dev, DMA_TO_DEVICE)); return 0; @@ -502,15 +493,11 @@ static int mhi_ep_process_ch_ring(struct mhi_ep_ring *ring) mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); } else { /* UL channel */ - do { - ret = mhi_ep_read_channel(mhi_cntrl, ring); - if (ret < 0) { - dev_err(&mhi_chan->mhi_dev->dev, "Failed to read channel\n"); - return ret; - } - - /* Read until the ring becomes empty */ - } while (!mhi_ep_queue_is_empty(mhi_chan->mhi_dev, DMA_TO_DEVICE)); + ret = mhi_ep_read_channel(mhi_cntrl, ring); + if (ret < 0) { + dev_err(&mhi_chan->mhi_dev->dev, "Failed to read channel\n"); + return ret; + } } return 0; diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c index 7f72aab38ce9..099be8dd1900 100644 --- a/drivers/bus/mhi/host/init.c +++ b/drivers/bus/mhi/host/init.c @@ -194,7 +194,6 @@ static void mhi_deinit_free_irq(struct mhi_controller *mhi_cntrl) static int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl) { struct mhi_event *mhi_event = mhi_cntrl->mhi_event; - struct device *dev = &mhi_cntrl->mhi_dev->dev; unsigned long irq_flags = IRQF_SHARED | IRQF_NO_SUSPEND; int i, ret; @@ -221,7 +220,7 @@ static int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl) continue; if (mhi_event->irq >= mhi_cntrl->nr_irqs) { - dev_err(dev, "irq %d not available for event ring\n", + dev_err(mhi_cntrl->cntrl_dev, "irq %d not available for event ring\n", mhi_event->irq); ret = -EINVAL; goto error_request; @@ -232,7 +231,7 @@ static int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl) irq_flags, "mhi", mhi_event); if (ret) { - dev_err(dev, "Error requesting irq:%d for ev:%d\n", + dev_err(mhi_cntrl->cntrl_dev, "Error requesting irq:%d for ev:%d\n", mhi_cntrl->irq[mhi_event->irq], i); goto error_request; } diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h index 034be33565b7..7937bb1f742c 100644 --- a/drivers/bus/mhi/host/internal.h +++ b/drivers/bus/mhi/host/internal.h @@ -170,6 +170,8 @@ enum mhi_pm_state { MHI_PM_IN_ERROR_STATE(pm_state)) #define MHI_PM_IN_SUSPEND_STATE(pm_state) (pm_state & \ (MHI_PM_M3_ENTER | MHI_PM_M3)) +#define MHI_PM_FATAL_ERROR(pm_state) ((pm_state == MHI_PM_FW_DL_ERR) || \ + (pm_state >= MHI_PM_SYS_ERR_FAIL)) #define NR_OF_CMD_RINGS 1 #define CMD_EL_PER_RING 128 @@ -403,6 +405,7 @@ int mhi_process_data_event_ring(struct mhi_controller *mhi_cntrl, struct mhi_event *mhi_event, u32 event_quota); int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl, struct mhi_event *mhi_event, u32 event_quota); +void mhi_uevent_notify(struct mhi_controller *mhi_cntrl, enum mhi_ee_type ee); /* ISR handlers */ irqreturn_t mhi_irq_handler(int irq_number, void *dev); diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c index 52bef663e182..861551274319 100644 --- a/drivers/bus/mhi/host/main.c +++ b/drivers/bus/mhi/host/main.c @@ -512,6 +512,7 @@ irqreturn_t mhi_intvec_threaded_handler(int irq_number, void *priv) if (mhi_cntrl->rddm_image && mhi_is_active(mhi_cntrl)) { mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_RDDM); mhi_cntrl->ee = ee; + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); wake_up_all(&mhi_cntrl->state_event); } break; diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c index 4edb5bb476ba..b188bbf7de04 100644 --- a/drivers/bus/mhi/host/pci_generic.c +++ b/drivers/bus/mhi/host/pci_generic.c @@ -34,28 +34,34 @@ /** * struct mhi_pci_dev_info - MHI PCI device specific information * @config: MHI controller configuration + * @vf_config: MHI controller configuration for Virtual function (optional) * @name: name of the PCI module * @fw: firmware path (if any) * @edl: emergency download mode firmware path (if any) * @edl_trigger: capable of triggering EDL mode in the device (if supported) * @bar_num: PCI base address register to use for MHI MMIO register space * @dma_data_width: DMA transfer word size (32 or 64 bits) + * @vf_dma_data_width: DMA transfer word size for VF's (optional) * @mru_default: default MRU size for MBIM network packets * @sideband_wake: Devices using dedicated sideband GPIO for wakeup instead * of inband wake support (such as sdx24) * @no_m3: M3 not supported + * @reset_on_remove: Set true for devices that require SoC during driver removal */ struct mhi_pci_dev_info { const struct mhi_controller_config *config; + const struct mhi_controller_config *vf_config; const char *name; const char *fw; const char *edl; bool edl_trigger; unsigned int bar_num; unsigned int dma_data_width; + unsigned int vf_dma_data_width; unsigned int mru_default; bool sideband_wake; bool no_m3; + bool reset_on_remove; }; #define MHI_CHANNEL_CONFIG_UL(ch_num, ch_name, el_count, ev_ring) \ @@ -296,8 +302,10 @@ static const struct mhi_pci_dev_info mhi_qcom_qdu100_info = { .config = &mhi_qcom_qdu100_config, .bar_num = MHI_PCI_DEFAULT_BAR_NUM, .dma_data_width = 32, + .vf_dma_data_width = 40, .sideband_wake = false, .no_m3 = true, + .reset_on_remove = true, }; static const struct mhi_channel_config mhi_qcom_sa8775p_channels[] = { @@ -917,20 +925,8 @@ static const struct pci_device_id mhi_pci_id_table[] = { /* Telit FE990A */ { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, 0x1c5d, 0x2015), .driver_data = (kernel_ulong_t) &mhi_telit_fe990a_info }, - /* Foxconn T99W696.01, Lenovo Generic SKU */ - { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe142), - .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info }, - /* Foxconn T99W696.02, Lenovo X1 Carbon SKU */ - { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe143), - .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info }, - /* Foxconn T99W696.03, Lenovo X1 2in1 SKU */ - { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe144), - .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info }, - /* Foxconn T99W696.04, Lenovo PRC SKU */ - { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe145), - .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info }, - /* Foxconn T99W696.00, Foxconn SKU */ - { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, 0xe146), + /* Foxconn T99W696, all variants */ + { PCI_DEVICE_SUB(PCI_VENDOR_ID_QCOM, 0x0308, PCI_VENDOR_ID_FOXCONN, PCI_ANY_ID), .driver_data = (kernel_ulong_t) &mhi_foxconn_t99w696_info }, { PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x0308), .driver_data = (kernel_ulong_t) &mhi_qcom_sdx65_info }, @@ -1037,6 +1033,7 @@ struct mhi_pci_device { struct work_struct recovery_work; struct timer_list health_check_timer; unsigned long status; + bool reset_on_remove; }; static int mhi_pci_read_reg(struct mhi_controller *mhi_cntrl, @@ -1092,7 +1089,7 @@ static bool mhi_pci_is_alive(struct mhi_controller *mhi_cntrl) struct pci_dev *pdev = to_pci_dev(mhi_cntrl->cntrl_dev); u16 vendor = 0; - if (pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor)) + if (pci_read_config_word(pci_physfn(pdev), PCI_VENDOR_ID, &vendor)) return false; if (vendor == (u16) ~0 || vendor == 0) @@ -1203,7 +1200,9 @@ static void mhi_pci_recovery_work(struct work_struct *work) dev_warn(&pdev->dev, "device recovery started\n"); - timer_delete(&mhi_pdev->health_check_timer); + if (pdev->is_physfn) + timer_delete(&mhi_pdev->health_check_timer); + pm_runtime_forbid(&pdev->dev); /* Clean up MHI state */ @@ -1230,7 +1229,10 @@ static void mhi_pci_recovery_work(struct work_struct *work) dev_dbg(&pdev->dev, "Recovery completed\n"); set_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status); - mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); + + if (pdev->is_physfn) + mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); + return; err_unprepare: @@ -1301,6 +1303,7 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) const struct mhi_controller_config *mhi_cntrl_config; struct mhi_pci_device *mhi_pdev; struct mhi_controller *mhi_cntrl; + unsigned int dma_data_width; int err; dev_info(&pdev->dev, "MHI PCI device found: %s\n", info->name); @@ -1311,14 +1314,24 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) return -ENOMEM; INIT_WORK(&mhi_pdev->recovery_work, mhi_pci_recovery_work); - timer_setup(&mhi_pdev->health_check_timer, health_check, 0); - mhi_cntrl_config = info->config; + if (pdev->is_virtfn && info->vf_config) + mhi_cntrl_config = info->vf_config; + else + mhi_cntrl_config = info->config; + + /* Initialize health check monitor only for Physical functions */ + if (pdev->is_physfn) + timer_setup(&mhi_pdev->health_check_timer, health_check, 0); + mhi_cntrl = &mhi_pdev->mhi_cntrl; + dma_data_width = (pdev->is_virtfn && info->vf_dma_data_width) ? + info->vf_dma_data_width : info->dma_data_width; + mhi_cntrl->cntrl_dev = &pdev->dev; mhi_cntrl->iova_start = 0; - mhi_cntrl->iova_stop = (dma_addr_t)DMA_BIT_MASK(info->dma_data_width); + mhi_cntrl->iova_stop = (dma_addr_t)DMA_BIT_MASK(dma_data_width); mhi_cntrl->fw_image = info->fw; mhi_cntrl->edl_image = info->edl; @@ -1330,6 +1343,9 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) mhi_cntrl->mru = info->mru_default; mhi_cntrl->name = info->name; + if (pdev->is_physfn) + mhi_pdev->reset_on_remove = info->reset_on_remove; + if (info->edl_trigger) mhi_cntrl->edl_trigger = mhi_pci_generic_edl_trigger; @@ -1339,7 +1355,7 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) mhi_cntrl->wake_toggle = mhi_pci_wake_toggle_nop; } - err = mhi_pci_claim(mhi_cntrl, info->bar_num, DMA_BIT_MASK(info->dma_data_width)); + err = mhi_pci_claim(mhi_cntrl, info->bar_num, DMA_BIT_MASK(dma_data_width)); if (err) return err; @@ -1376,7 +1392,8 @@ static int mhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) set_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status); /* start health check */ - mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); + if (pdev->is_physfn) + mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); /* Allow runtime suspend only if both PME from D3Hot and M3 are supported */ if (pci_pme_capable(pdev, PCI_D3hot) && !(info->no_m3)) { @@ -1401,7 +1418,10 @@ static void mhi_pci_remove(struct pci_dev *pdev) struct mhi_pci_device *mhi_pdev = pci_get_drvdata(pdev); struct mhi_controller *mhi_cntrl = &mhi_pdev->mhi_cntrl; - timer_delete_sync(&mhi_pdev->health_check_timer); + pci_disable_sriov(pdev); + + if (pdev->is_physfn) + timer_delete_sync(&mhi_pdev->health_check_timer); cancel_work_sync(&mhi_pdev->recovery_work); if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) { @@ -1413,6 +1433,9 @@ static void mhi_pci_remove(struct pci_dev *pdev) if (pci_pme_capable(pdev, PCI_D3hot)) pm_runtime_get_noresume(&pdev->dev); + if (mhi_pdev->reset_on_remove) + mhi_soc_reset(mhi_cntrl); + mhi_unregister_controller(mhi_cntrl); } @@ -1429,7 +1452,8 @@ static void mhi_pci_reset_prepare(struct pci_dev *pdev) dev_info(&pdev->dev, "reset\n"); - timer_delete(&mhi_pdev->health_check_timer); + if (pdev->is_physfn) + timer_delete(&mhi_pdev->health_check_timer); /* Clean up MHI state */ if (test_and_clear_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status)) { @@ -1474,7 +1498,8 @@ static void mhi_pci_reset_done(struct pci_dev *pdev) } set_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status); - mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); + if (pdev->is_physfn) + mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); } static pci_ers_result_t mhi_pci_error_detected(struct pci_dev *pdev, @@ -1539,7 +1564,9 @@ static int __maybe_unused mhi_pci_runtime_suspend(struct device *dev) if (test_and_set_bit(MHI_PCI_DEV_SUSPENDED, &mhi_pdev->status)) return 0; - timer_delete(&mhi_pdev->health_check_timer); + if (pdev->is_physfn) + timer_delete(&mhi_pdev->health_check_timer); + cancel_work_sync(&mhi_pdev->recovery_work); if (!test_bit(MHI_PCI_DEV_STARTED, &mhi_pdev->status) || @@ -1590,7 +1617,8 @@ static int __maybe_unused mhi_pci_runtime_resume(struct device *dev) } /* Resume health check */ - mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); + if (pdev->is_physfn) + mod_timer(&mhi_pdev->health_check_timer, jiffies + HEALTH_CHECK_PERIOD); /* It can be a remote wakeup (no mhi runtime_get), update access time */ pm_runtime_mark_last_busy(dev); @@ -1676,7 +1704,8 @@ static struct pci_driver mhi_pci_driver = { .remove = mhi_pci_remove, .shutdown = mhi_pci_shutdown, .err_handler = &mhi_pci_err_handler, - .driver.pm = &mhi_pci_pm_ops + .driver.pm = &mhi_pci_pm_ops, + .sriov_configure = pci_sriov_configure_simple, }; module_pci_driver(mhi_pci_driver); diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c index 33d92bf2fc3e..b4ef115189b5 100644 --- a/drivers/bus/mhi/host/pm.c +++ b/drivers/bus/mhi/host/pm.c @@ -418,6 +418,7 @@ static int mhi_pm_mission_mode_transition(struct mhi_controller *mhi_cntrl) device_for_each_child(&mhi_cntrl->mhi_dev->dev, ¤t_ee, mhi_destroy_device); mhi_cntrl->status_cb(mhi_cntrl, MHI_CB_EE_MISSION_MODE); + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); /* Force MHI to be in M0 state before continuing */ ret = __mhi_device_get_sync(mhi_cntrl); @@ -631,6 +632,8 @@ static void mhi_pm_sys_error_transition(struct mhi_controller *mhi_cntrl) /* Wake up threads waiting for state transition */ wake_up_all(&mhi_cntrl->state_event); + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); + if (MHI_REG_ACCESS_VALID(prev_state)) { /* * If the device is in PBL or SBL, it will only respond to @@ -829,6 +832,8 @@ void mhi_pm_st_worker(struct work_struct *work) mhi_create_devices(mhi_cntrl); if (mhi_cntrl->fbc_download) mhi_download_amss_image(mhi_cntrl); + + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); break; case DEV_ST_TRANSITION_MISSION_MODE: mhi_pm_mission_mode_transition(mhi_cntrl); @@ -838,6 +843,7 @@ void mhi_pm_st_worker(struct work_struct *work) mhi_cntrl->ee = MHI_EE_FP; write_unlock_irq(&mhi_cntrl->pm_lock); mhi_create_devices(mhi_cntrl); + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); break; case DEV_ST_TRANSITION_READY: mhi_ready_state_transition(mhi_cntrl); @@ -1240,6 +1246,8 @@ static void __mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful, write_unlock_irq(&mhi_cntrl->pm_lock); mutex_unlock(&mhi_cntrl->pm_mutex); + mhi_uevent_notify(mhi_cntrl, mhi_cntrl->ee); + if (destroy_device) mhi_queue_state_transition(mhi_cntrl, DEV_ST_TRANSITION_DISABLE_DESTROY_DEVICE); @@ -1279,7 +1287,7 @@ int mhi_sync_power_up(struct mhi_controller *mhi_cntrl) mhi_cntrl->ready_timeout_ms : mhi_cntrl->timeout_ms; wait_event_timeout(mhi_cntrl->state_event, MHI_IN_MISSION_MODE(mhi_cntrl->ee) || - MHI_PM_IN_ERROR_STATE(mhi_cntrl->pm_state), + MHI_PM_FATAL_ERROR(mhi_cntrl->pm_state), msecs_to_jiffies(timeout_ms)); ret = (MHI_IN_MISSION_MODE(mhi_cntrl->ee)) ? 0 : -ETIMEDOUT; @@ -1338,3 +1346,22 @@ void mhi_device_put(struct mhi_device *mhi_dev) read_unlock_bh(&mhi_cntrl->pm_lock); } EXPORT_SYMBOL_GPL(mhi_device_put); + +void mhi_uevent_notify(struct mhi_controller *mhi_cntrl, enum mhi_ee_type ee) +{ + struct device *dev = &mhi_cntrl->mhi_dev->dev; + char *buf[2]; + int ret; + + buf[0] = kasprintf(GFP_KERNEL, "EXEC_ENV=%s", TO_MHI_EXEC_STR(ee)); + buf[1] = NULL; + + if (!buf[0]) + return; + + ret = kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, buf); + if (ret) + dev_err(dev, "Failed to send %s uevent\n", TO_MHI_EXEC_STR(ee)); + + kfree(buf[0]); +} diff --git a/drivers/cdx/cdx_msi.c b/drivers/cdx/cdx_msi.c index 3388a5d1462c..91b95422b263 100644 --- a/drivers/cdx/cdx_msi.c +++ b/drivers/cdx/cdx_msi.c @@ -174,6 +174,7 @@ struct irq_domain *cdx_msi_domain_init(struct device *dev) } parent = irq_find_matching_fwnode(of_fwnode_handle(parent_node), DOMAIN_BUS_NEXUS); + of_node_put(parent_node); if (!parent || !msi_get_domain_info(parent)) { dev_err(dev, "unable to locate ITS domain\n"); return NULL; diff --git a/drivers/char/Makefile b/drivers/char/Makefile index e9b360cdc99a..1291369b9126 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -6,6 +6,7 @@ obj-y += mem.o random.o obj-$(CONFIG_TTY_PRINTK) += ttyprintk.o obj-y += misc.o +obj-$(CONFIG_TEST_MISC_MINOR) += misc_minor_kunit.o obj-$(CONFIG_ATARI_DSP56K) += dsp56k.o obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o obj-$(CONFIG_UV_MMTIMER) += uv_mmtimer.o diff --git a/drivers/char/adi.c b/drivers/char/adi.c index f9bec10a6064..4312b0cc391c 100644 --- a/drivers/char/adi.c +++ b/drivers/char/adi.c @@ -131,7 +131,7 @@ static ssize_t adi_write(struct file *file, const char __user *buf, ssize_t ret; int i; - if (count <= 0) + if (count == 0) return -EINVAL; ver_buf_sz = min_t(size_t, count, MAX_BUF_SZ); diff --git a/drivers/char/hpet.c b/drivers/char/hpet.c index 0713ea2b2a51..4f5ccd3a1f56 100644 --- a/drivers/char/hpet.c +++ b/drivers/char/hpet.c @@ -867,7 +867,7 @@ int hpet_alloc(struct hpet_data *hdp) printk(KERN_INFO "hpet%u: at MMIO 0x%lx, IRQ%s", hpetp->hp_which, hdp->hd_phys_address, - hpetp->hp_ntimer > 1 ? "s" : ""); + str_plural(hpetp->hp_ntimer)); for (i = 0; i < hpetp->hp_ntimer; i++) printk(KERN_CONT "%s %u", i > 0 ? "," : "", hdp->hd_irq[i]); printk(KERN_CONT "\n"); diff --git a/drivers/char/misc.c b/drivers/char/misc.c index 558302a64dd9..726516fb0a3b 100644 --- a/drivers/char/misc.c +++ b/drivers/char/misc.c @@ -132,7 +132,8 @@ static int misc_open(struct inode *inode, struct file *file) break; } - if (!new_fops) { + /* Only request module for fixed minor code */ + if (!new_fops && minor < MISC_DYNAMIC_MINOR) { mutex_unlock(&misc_mtx); request_module("char-major-%d-%d", MISC_MAJOR, minor); mutex_lock(&misc_mtx); @@ -144,10 +145,11 @@ static int misc_open(struct inode *inode, struct file *file) new_fops = fops_get(iter->fops); break; } - if (!new_fops) - goto fail; } + if (!new_fops) + goto fail; + /* * Place the miscdevice in the file's * private_data so it can be used by the @@ -210,6 +212,12 @@ int misc_register(struct miscdevice *misc) int err = 0; bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR); + if (misc->minor > MISC_DYNAMIC_MINOR) { + pr_err("Invalid fixed minor %d for miscdevice '%s'\n", + misc->minor, misc->name); + return -EINVAL; + } + INIT_LIST_HEAD(&misc->list); mutex_lock(&misc_mtx); @@ -275,13 +283,12 @@ EXPORT_SYMBOL(misc_register); void misc_deregister(struct miscdevice *misc) { - if (WARN_ON(list_empty(&misc->list))) - return; - mutex_lock(&misc_mtx); - list_del(&misc->list); + list_del_init(&misc->list); device_destroy(&misc_class, MKDEV(MISC_MAJOR, misc->minor)); misc_minor_free(misc->minor); + if (misc->minor > MISC_DYNAMIC_MINOR) + misc->minor = MISC_DYNAMIC_MINOR; mutex_unlock(&misc_mtx); } EXPORT_SYMBOL(misc_deregister); diff --git a/drivers/misc/misc_minor_kunit.c b/drivers/char/misc_minor_kunit.c index 30eceac5f1b6..6fc8b05169c5 100644 --- a/drivers/misc/misc_minor_kunit.c +++ b/drivers/char/misc_minor_kunit.c @@ -7,12 +7,6 @@ #include <linux/file.h> #include <linux/init_syscalls.h> -/* dynamic minor (2) */ -static struct miscdevice dev_dynamic_minor = { - .minor = 2, - .name = "dev_dynamic_minor", -}; - /* static minor (LCD_MINOR) */ static struct miscdevice dev_static_minor = { .minor = LCD_MINOR, @@ -25,16 +19,6 @@ static struct miscdevice dev_misc_dynamic_minor = { .name = "dev_misc_dynamic_minor", }; -static void kunit_dynamic_minor(struct kunit *test) -{ - int ret; - - ret = misc_register(&dev_dynamic_minor); - KUNIT_EXPECT_EQ(test, 0, ret); - KUNIT_EXPECT_EQ(test, 2, dev_dynamic_minor.minor); - misc_deregister(&dev_dynamic_minor); -} - static void kunit_static_minor(struct kunit *test) { int ret; @@ -157,13 +141,7 @@ static bool is_valid_dynamic_minor(int minor) { if (minor < 0) return false; - if (minor == MISC_DYNAMIC_MINOR) - return false; - if (minor >= 0 && minor <= 15) - return false; - if (minor >= 128 && minor < MISC_DYNAMIC_MINOR) - return false; - return true; + return minor > MISC_DYNAMIC_MINOR; } static int miscdev_test_open(struct inode *inode, struct file *file) @@ -557,7 +535,7 @@ static void __init miscdev_test_conflict(struct kunit *test) */ miscstat.minor = miscdyn.minor; ret = misc_register(&miscstat); - KUNIT_EXPECT_EQ(test, ret, -EBUSY); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); if (ret == 0) misc_deregister(&miscstat); @@ -590,8 +568,9 @@ static void __init miscdev_test_conflict_reverse(struct kunit *test) misc_deregister(&miscdyn); ret = misc_register(&miscstat); - KUNIT_EXPECT_EQ(test, ret, 0); - KUNIT_EXPECT_EQ(test, miscstat.minor, miscdyn.minor); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + if (ret == 0) + misc_deregister(&miscstat); /* * Try to register a dynamic minor after registering a static minor @@ -601,25 +580,81 @@ static void __init miscdev_test_conflict_reverse(struct kunit *test) miscdyn.minor = MISC_DYNAMIC_MINOR; ret = misc_register(&miscdyn); KUNIT_EXPECT_EQ(test, ret, 0); - KUNIT_EXPECT_NE(test, miscdyn.minor, miscstat.minor); + KUNIT_EXPECT_EQ(test, miscdyn.minor, miscstat.minor); KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn.minor)); if (ret == 0) misc_deregister(&miscdyn); +} - miscdev_test_can_open(test, &miscstat); +/* Take minor(> MISC_DYNAMIC_MINOR) as invalid when register miscdevice */ +static void miscdev_test_invalid_input(struct kunit *test) +{ + struct miscdevice misc_test = { + .minor = MISC_DYNAMIC_MINOR + 1, + .name = "misc_test", + .fops = &miscdev_test_fops, + }; + int ret; - misc_deregister(&miscstat); + ret = misc_register(&misc_test); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + if (ret == 0) + misc_deregister(&misc_test); +} + +/* + * Verify if @miscdyn_a can still be registered successfully without + * reinitialization even if its minor ever owned was requested by + * another miscdevice such as @miscdyn_b. + */ +static void miscdev_test_dynamic_reentry(struct kunit *test) +{ + struct miscdevice miscdyn_a = { + .name = "miscdyn_a", + .minor = MISC_DYNAMIC_MINOR, + .fops = &miscdev_test_fops, + }; + struct miscdevice miscdyn_b = { + .name = "miscdyn_b", + .minor = MISC_DYNAMIC_MINOR, + .fops = &miscdev_test_fops, + }; + int ret, minor_a; + + ret = misc_register(&miscdyn_a); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn_a.minor)); + minor_a = miscdyn_a.minor; + if (ret != 0) + return; + misc_deregister(&miscdyn_a); + + ret = misc_register(&miscdyn_b); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, miscdyn_b.minor, minor_a); + if (ret != 0) + return; + + ret = misc_register(&miscdyn_a); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, is_valid_dynamic_minor(miscdyn_a.minor)); + KUNIT_EXPECT_NE(test, miscdyn_a.minor, miscdyn_b.minor); + if (ret == 0) + misc_deregister(&miscdyn_a); + + misc_deregister(&miscdyn_b); } static struct kunit_case test_cases[] = { - KUNIT_CASE(kunit_dynamic_minor), KUNIT_CASE(kunit_static_minor), KUNIT_CASE(kunit_misc_dynamic_minor), + KUNIT_CASE(miscdev_test_invalid_input), KUNIT_CASE_PARAM(miscdev_test_twice, miscdev_gen_params), KUNIT_CASE_PARAM(miscdev_test_duplicate_minor, miscdev_gen_params), KUNIT_CASE(miscdev_test_duplicate_name), KUNIT_CASE(miscdev_test_duplicate_name_leak), KUNIT_CASE_PARAM(miscdev_test_duplicate_error, miscdev_gen_params), + KUNIT_CASE(miscdev_test_dynamic_reentry), {} }; diff --git a/drivers/comedi/Kconfig b/drivers/comedi/Kconfig index 93c68a40a17b..6dcc2567de6d 100644 --- a/drivers/comedi/Kconfig +++ b/drivers/comedi/Kconfig @@ -705,6 +705,15 @@ config COMEDI_ADL_PCI6208 To compile this driver as a module, choose M here: the module will be called adl_pci6208. +config COMEDI_ADL_PCI7250 + tristate "ADLink PCI-7250 support" + help + Enable support for ADLink PCI-7250/LPCI-7250/LPCIe-7250 relay output + and isolated digital input boards. + + To compile this driver as a module, choose M here: the module will be + called adl_pci7250. + config COMEDI_ADL_PCI7X3X tristate "ADLink PCI-723X/743X isolated digital i/o board support" depends on HAS_IOPORT diff --git a/drivers/comedi/drivers/Makefile b/drivers/comedi/drivers/Makefile index b24ac00cab73..7b99a431330d 100644 --- a/drivers/comedi/drivers/Makefile +++ b/drivers/comedi/drivers/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_COMEDI_ADDI_APCI_3120) += addi_apci_3120.o obj-$(CONFIG_COMEDI_ADDI_APCI_3501) += addi_apci_3501.o obj-$(CONFIG_COMEDI_ADDI_APCI_3XXX) += addi_apci_3xxx.o obj-$(CONFIG_COMEDI_ADL_PCI6208) += adl_pci6208.o +obj-$(CONFIG_COMEDI_ADL_PCI7250) += adl_pci7250.o obj-$(CONFIG_COMEDI_ADL_PCI7X3X) += adl_pci7x3x.o obj-$(CONFIG_COMEDI_ADL_PCI8164) += adl_pci8164.o obj-$(CONFIG_COMEDI_ADL_PCI9111) += adl_pci9111.o diff --git a/drivers/comedi/drivers/adl_pci7250.c b/drivers/comedi/drivers/adl_pci7250.c new file mode 100644 index 000000000000..78c85a402435 --- /dev/null +++ b/drivers/comedi/drivers/adl_pci7250.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * adl_pci7250.c + * + * Comedi driver for ADLink PCI-7250 series cards. + * + * Copyright (C) 2015, 2025 Ian Abbott <abbotti@mev.co.uk> + */ + +/* + * Driver: adl_pci7250 + * Description: Driver for the ADLINK PCI-7250 relay output & digital input card + * Devices: [ADLINK] PCI-7250 (adl_pci7250) LPCI-7250 LPCIe-7250 + * Author: Ian Abbott <abbotti@mev.co.uk> + * Status: works + * Updated: Mon, 02 Jun 2025 13:54:11 +0100 + * + * The driver assumes that 3 PCI-7251 modules are fitted to the PCI-7250, + * giving 32 channels of relay outputs and 32 channels of isolated digital + * inputs. That is also the case for the LPCI-7250 and older LPCIe-7250 + * cards although they do not physically support the PCI-7251 modules. + * Newer LPCIe-7250 cards have a different PCI subsystem device ID, so + * set the number of channels to 8 for these cards. + * + * Not fitting the PCI-7251 modules shouldn't do any harm, but the extra + * inputs and relay outputs won't work! + * + * Configuration Options: not applicable, uses PCI auto config + */ + +#include <linux/module.h> +#include <linux/comedi/comedi_pci.h> + +static unsigned char adl_pci7250_read8(struct comedi_device *dev, + unsigned int offset) +{ +#ifdef CONFIG_HAS_IOPORT + if (!dev->mmio) + return inb(dev->iobase + offset); +#endif + return readb(dev->mmio + offset); +} + +static void adl_pci7250_write8(struct comedi_device *dev, unsigned int offset, + unsigned char val) +{ +#ifdef CONFIG_HAS_IOPORT + if (!dev->mmio) { + outb(val, dev->iobase + offset); + return; + } +#endif + writeb(val, dev->mmio + offset); +} + +static int adl_pci7250_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int mask = comedi_dio_update_state(s, data); + + if (mask) { + unsigned int state = s->state; + unsigned int i; + + for (i = 0; i * 8 < s->n_chan; i++) { + if ((mask & 0xffu) != 0) { + /* write relay data to even offset registers */ + adl_pci7250_write8(dev, i * 2, state & 0xffu); + } + state >>= 8; + mask >>= 8; + } + } + + data[1] = s->state; + + return 2; +} + +static int adl_pci7250_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int value = 0; + unsigned int i; + + for (i = 0; i * 8 < s->n_chan; i++) { + /* read DI value from odd offset registers */ + value |= (unsigned int)adl_pci7250_read8(dev, i * 2 + 1) << + (i * 8); + } + + data[1] = value; + + return 2; +} + +static int pci7250_auto_attach(struct comedi_device *dev, + unsigned long context_unused) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + struct comedi_subdevice *s; + unsigned int max_chans; + unsigned int i; + int ret; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + if (pci_resource_len(pcidev, 2) < 8) + return -ENXIO; + + /* + * Newer LPCIe-7250 boards use MMIO. Older LPCIe-7250, LPCI-7250, and + * PCI-7250 boards use Port I/O. + */ + if (pci_resource_flags(pcidev, 2) & IORESOURCE_MEM) { + dev->mmio = pci_ioremap_bar(pcidev, 2); + if (!dev->mmio) + return -ENOMEM; + } else if (IS_ENABLED(CONFIG_HAS_IOPORT)) { + dev->iobase = pci_resource_start(pcidev, 2); + } else { + dev_err(dev->class_dev, + "error! need I/O port support\n"); + return -ENXIO; + } + + if (pcidev->subsystem_device == 0x7000) { + /* + * This is a newer LPCIe-7250 variant and cannot possibly + * have PCI-7251 modules fitted, so limit the number of + * channels to 8. + */ + max_chans = 8; + } else { + /* + * It is unknown whether the board is a PCI-7250, an LPCI-7250, + * or an older LPCIe-7250 variant, so treat it as a PCI-7250 + * and assume it can have PCI-7251 modules fitted to increase + * the number of channels to a maximum of 32. + */ + max_chans = 32; + } + + ret = comedi_alloc_subdevices(dev, 2); + if (ret) + return ret; + + /* Relay digital output. */ + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = max_chans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = adl_pci7250_do_insn_bits; + /* Read initial state of relays from the even offset registers. */ + s->state = 0; + for (i = 0; i * 8 < max_chans; i++) { + s->state |= (unsigned int)adl_pci7250_read8(dev, i * 2) << + (i * 8); + } + + /* Isolated digital input. */ + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = max_chans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = adl_pci7250_di_insn_bits; + + return 0; +} + +static struct comedi_driver adl_pci7250_driver = { + .driver_name = "adl_pci7250", + .module = THIS_MODULE, + .auto_attach = pci7250_auto_attach, + .detach = comedi_pci_detach, +}; + +static int adl_pci7250_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &adl_pci7250_driver, + id->driver_data); +} + +static const struct pci_device_id adl_pci7250_pci_table[] = { +#ifdef CONFIG_HAS_IOPORT + { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, + 0x9999, 0x7250) }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADLINK, 0x7250, + 0x9999, 0x7250) }, + { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADLINK, 0x7250, + PCI_VENDOR_ID_ADLINK, 0x7250) }, +#endif + { PCI_DEVICE_SUB(PCI_VENDOR_ID_ADLINK, 0x7250, + PCI_VENDOR_ID_ADLINK, 0x7000) }, /* newer LPCIe-7250 */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, adl_pci7250_pci_table); + +static struct pci_driver adl_pci7250_pci_driver = { + .name = "adl_pci7250", + .id_table = adl_pci7250_pci_table, + .probe = adl_pci7250_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(adl_pci7250_driver, adl_pci7250_pci_driver); + +MODULE_AUTHOR("Comedi https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for ADLink PCI-7250 series boards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/counter/ti-ecap-capture.c b/drivers/counter/ti-ecap-capture.c index 3faaf7f60539..3586a7ab9887 100644 --- a/drivers/counter/ti-ecap-capture.c +++ b/drivers/counter/ti-ecap-capture.c @@ -465,11 +465,6 @@ static irqreturn_t ecap_cnt_isr(int irq, void *dev_id) return IRQ_HANDLED; } -static void ecap_cnt_pm_disable(void *dev) -{ - pm_runtime_disable(dev); -} - static int ecap_cnt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -523,12 +518,9 @@ static int ecap_cnt_probe(struct platform_device *pdev) platform_set_drvdata(pdev, counter_dev); - pm_runtime_enable(dev); - - /* Register a cleanup callback to care for disabling PM */ - ret = devm_add_action_or_reset(dev, ecap_cnt_pm_disable, dev); + ret = devm_pm_runtime_enable(dev); if (ret) - return dev_err_probe(dev, ret, "failed to add pm disable action\n"); + return ret; ret = devm_counter_add(dev, counter_dev); if (ret) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index a6f6d467aacf..aec46bf03302 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -134,6 +134,19 @@ config EXTCON_MAX8997 Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory detector and switch. +config EXTCON_MAX14526 + tristate "Maxim MAX14526 EXTCON Support" + depends on I2C + select IRQ_DOMAIN + select REGMAP_I2C + help + If you say yes here you get support for the Maxim MAX14526 + MUIC device. The MAX14526 MUIC is a USB port accessory + detector and switch. The MAX14526 is designed to simplify + interface requirements on portable devices by multiplexing + common inputs (USB, UART, Microphone, Stereo Audio and + Composite Video) on a single micro/mini USB connector. + config EXTCON_PALMAS tristate "Palmas USB EXTCON support" depends on MFD_PALMAS diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 0d6d23faf748..6482f2bfd661 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o obj-$(CONFIG_EXTCON_MAX77843) += extcon-max77843.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o +obj-$(CONFIG_EXTCON_MAX14526) += extcon-max14526.o obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o obj-$(CONFIG_EXTCON_PTN5150) += extcon-ptn5150.o obj-$(CONFIG_EXTCON_QCOM_SPMI_MISC) += extcon-qcom-spmi-misc.o diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c index 46c40d85c2ac..7e3c9f38297b 100644 --- a/drivers/extcon/extcon-adc-jack.c +++ b/drivers/extcon/extcon-adc-jack.c @@ -164,6 +164,8 @@ static void adc_jack_remove(struct platform_device *pdev) { struct adc_jack_data *data = platform_get_drvdata(pdev); + if (data->wakeup_source) + device_init_wakeup(&pdev->dev, false); free_irq(data->irq, data); cancel_work_sync(&data->handler.work); } diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c index d3bcbe839c09..19856dddade6 100644 --- a/drivers/extcon/extcon-axp288.c +++ b/drivers/extcon/extcon-axp288.c @@ -470,7 +470,7 @@ static int axp288_extcon_probe(struct platform_device *pdev) if (ret < 0) return ret; - device_init_wakeup(dev, true); + devm_device_init_wakeup(dev); platform_set_drvdata(pdev, info); return 0; diff --git a/drivers/extcon/extcon-fsa9480.c b/drivers/extcon/extcon-fsa9480.c index b11b43171063..a031eb0914a0 100644 --- a/drivers/extcon/extcon-fsa9480.c +++ b/drivers/extcon/extcon-fsa9480.c @@ -317,7 +317,7 @@ static int fsa9480_probe(struct i2c_client *client) return ret; } - device_init_wakeup(info->dev, true); + devm_device_init_wakeup(info->dev); fsa9480_detect_dev(info); return 0; diff --git a/drivers/extcon/extcon-max14526.c b/drivers/extcon/extcon-max14526.c new file mode 100644 index 000000000000..3750a5c20612 --- /dev/null +++ b/drivers/extcon/extcon-max14526.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <linux/device.h> +#include <linux/devm-helpers.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/extcon-provider.h> +#include <linux/i2c.h> +#include <linux/mod_devicetable.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/regmap.h> + +/* I2C addresses of MUIC internal registers */ +#define MAX14526_DEVICE_ID 0x00 +#define MAX14526_ID 0x02 + +/* CONTROL_1 register masks */ +#define MAX14526_CONTROL_1 0x01 +#define ID_2P2 BIT(6) +#define ID_620 BIT(5) +#define ID_200 BIT(4) +#define VLDO BIT(3) +#define SEMREN BIT(2) +#define ADC_EN BIT(1) +#define CP_EN BIT(0) + +/* CONTROL_2 register masks */ +#define MAX14526_CONTROL_2 0x02 +#define INTPOL BIT(7) +#define INT_EN BIT(6) +#define MIC_LP BIT(5) +#define CP_AUD BIT(4) +#define CHG_TYPE BIT(1) +#define USB_DET_DIS BIT(0) + +/* SW_CONTROL register masks */ +#define MAX14526_SW_CONTROL 0x03 +#define SW_DATA 0x00 +#define SW_UART 0x01 +#define SW_AUDIO 0x02 +#define SW_OPEN 0x07 + +/* INT_STATUS register masks */ +#define MAX14526_INT_STAT 0x04 +#define CHGDET BIT(7) +#define MR_COMP BIT(6) +#define SENDEND BIT(5) +#define V_VBUS BIT(4) + +/* STATUS register masks */ +#define MAX14526_STATUS 0x05 +#define CPORT BIT(7) +#define CHPORT BIT(6) +#define C1COMP BIT(0) + +enum max14526_idno_resistance { + MAX14526_GND, + MAX14526_24KOHM, + MAX14526_56KOHM, + MAX14526_100KOHM, + MAX14526_130KOHM, + MAX14526_180KOHM, + MAX14526_240KOHM, + MAX14526_330KOHM, + MAX14526_430KOHM, + MAX14526_620KOHM, + MAX14526_910KOHM, + MAX14526_OPEN +}; + +enum max14526_field_idx { + VENDOR_ID, CHIP_REV, /* DEVID */ + DM, DP, /* SW_CONTROL */ + MAX14526_N_REGMAP_FIELDS +}; + +static const struct reg_field max14526_reg_field[MAX14526_N_REGMAP_FIELDS] = { + [VENDOR_ID] = REG_FIELD(MAX14526_DEVICE_ID, 4, 7), + [CHIP_REV] = REG_FIELD(MAX14526_DEVICE_ID, 0, 3), + [DM] = REG_FIELD(MAX14526_SW_CONTROL, 0, 2), + [DP] = REG_FIELD(MAX14526_SW_CONTROL, 3, 5), +}; + +struct max14526_data { + struct i2c_client *client; + struct extcon_dev *edev; + + struct regmap *regmap; + struct regmap_field *rfield[MAX14526_N_REGMAP_FIELDS]; + + int last_state; + int cable; +}; + +enum max14526_muic_modes { + MAX14526_OTG = MAX14526_GND, /* no power */ + MAX14526_MHL = MAX14526_56KOHM, /* no power */ + MAX14526_OTG_Y = MAX14526_GND | V_VBUS, + MAX14526_MHL_CHG = MAX14526_GND | V_VBUS | CHGDET, + MAX14526_NONE = MAX14526_OPEN, + MAX14526_USB = MAX14526_OPEN | V_VBUS, + MAX14526_CHG = MAX14526_OPEN | V_VBUS | CHGDET, +}; + +static const unsigned int max14526_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_CHG_USB_FAST, + EXTCON_DISP_MHL, + EXTCON_NONE, +}; + +static int max14526_ap_usb_mode(struct max14526_data *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + /* Enable USB Path */ + ret = regmap_field_write(priv->rfield[DM], SW_DATA); + if (ret) + return ret; + + ret = regmap_field_write(priv->rfield[DP], SW_DATA); + if (ret) + return ret; + + /* Enable 200K, Charger Pump and ADC */ + ret = regmap_write(priv->regmap, MAX14526_CONTROL_1, + ID_200 | ADC_EN | CP_EN); + if (ret) + return ret; + + dev_dbg(dev, "AP USB mode set\n"); + + return 0; +} + +static irqreturn_t max14526_interrupt(int irq, void *dev_id) +{ + struct max14526_data *priv = dev_id; + struct device *dev = &priv->client->dev; + int state, ret; + + /* + * Upon an MUIC IRQ (MUIC_INT_N falls), wait at least 70ms + * before reading INT_STAT and STATUS. After the reads, + * MUIC_INT_N returns to high (but the INT_STAT and STATUS + * contents will be held). + */ + msleep(100); + + ret = regmap_read(priv->regmap, MAX14526_INT_STAT, &state); + if (ret) + dev_err(dev, "failed to read MUIC state %d\n", ret); + + if (state == priv->last_state) + return IRQ_HANDLED; + + /* Detach previous device */ + extcon_set_state_sync(priv->edev, priv->cable, false); + + switch (state) { + case MAX14526_USB: + priv->cable = EXTCON_USB; + break; + + case MAX14526_CHG: + priv->cable = EXTCON_CHG_USB_FAST; + break; + + case MAX14526_OTG: + case MAX14526_OTG_Y: + priv->cable = EXTCON_USB_HOST; + break; + + case MAX14526_MHL: + case MAX14526_MHL_CHG: + priv->cable = EXTCON_DISP_MHL; + break; + + case MAX14526_NONE: + default: + priv->cable = EXTCON_NONE; + break; + } + + extcon_set_state_sync(priv->edev, priv->cable, true); + + priv->last_state = state; + + return IRQ_HANDLED; +} + +static const struct regmap_config max14526_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX14526_STATUS, +}; + +static int max14526_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max14526_data *priv; + int ret, dev_id, rev, i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + i2c_set_clientdata(client, priv); + + priv->regmap = devm_regmap_init_i2c(client, &max14526_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n"); + + for (i = 0; i < MAX14526_N_REGMAP_FIELDS; i++) { + priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap, + max14526_reg_field[i]); + if (IS_ERR(priv->rfield[i])) + return dev_err_probe(dev, PTR_ERR(priv->rfield[i]), + "cannot allocate regmap field\n"); + } + + /* Detect if MUIC version is supported */ + ret = regmap_field_read(priv->rfield[VENDOR_ID], &dev_id); + if (ret) + return dev_err_probe(dev, ret, "failed to read MUIC ID\n"); + + regmap_field_read(priv->rfield[CHIP_REV], &rev); + + if (dev_id == MAX14526_ID) + dev_info(dev, "detected MAX14526 MUIC with id 0x%x, rev 0x%x\n", dev_id, rev); + else + dev_err_probe(dev, -EINVAL, "MUIC vendor id 0x%X is not recognized\n", dev_id); + + priv->edev = devm_extcon_dev_allocate(dev, max14526_extcon_cable); + if (IS_ERR(priv->edev)) + return dev_err_probe(dev, (IS_ERR(priv->edev)), + "failed to allocate extcon device\n"); + + ret = devm_extcon_dev_register(dev, priv->edev); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to register extcon device\n"); + + ret = max14526_ap_usb_mode(priv); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set AP USB mode\n"); + + regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, INT_EN, INT_EN); + regmap_write_bits(priv->regmap, MAX14526_CONTROL_2, USB_DET_DIS, (u32)~USB_DET_DIS); + + ret = devm_request_threaded_irq(dev, client->irq, NULL, &max14526_interrupt, + IRQF_ONESHOT | IRQF_SHARED, client->name, priv); + if (ret) + return dev_err_probe(dev, ret, "failed to register IRQ\n"); + + irq_wake_thread(client->irq, priv); + + return 0; +} + +static int max14526_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max14526_data *priv = i2c_get_clientdata(client); + + irq_wake_thread(client->irq, priv); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(max14526_pm_ops, NULL, max14526_resume); + +static const struct of_device_id max14526_match[] = { + { .compatible = "maxim,max14526" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, max14526_match); + +static const struct i2c_device_id max14526_id[] = { + { "max14526" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max14526_id); + +static struct i2c_driver max14526_driver = { + .driver = { + .name = "max14526", + .of_match_table = max14526_match, + .pm = &max14526_pm_ops, + }, + .probe = max14526_probe, + .id_table = max14526_id, +}; +module_i2c_driver(max14526_driver); + +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); +MODULE_DESCRIPTION("MAX14526 extcon driver to support MUIC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/extcon/extcon-qcom-spmi-misc.c b/drivers/extcon/extcon-qcom-spmi-misc.c index 53de581a393a..afaba5685c3d 100644 --- a/drivers/extcon/extcon-qcom-spmi-misc.c +++ b/drivers/extcon/extcon-qcom-spmi-misc.c @@ -155,7 +155,7 @@ static int qcom_usb_extcon_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, info); - device_init_wakeup(dev, 1); + devm_device_init_wakeup(dev); /* Perform initial detection */ qcom_usb_extcon_detect_cable(&info->wq_detcable.work); diff --git a/drivers/greybus/svc.c b/drivers/greybus/svc.c index 4256467fcd35..35ea7147dca6 100644 --- a/drivers/greybus/svc.c +++ b/drivers/greybus/svc.c @@ -10,6 +10,7 @@ #include <linux/kstrtox.h> #include <linux/workqueue.h> #include <linux/greybus.h> +#include <linux/string_choices.h> #define SVC_INTF_EJECT_TIMEOUT 9000 #define SVC_INTF_ACTIVATE_TIMEOUT 6000 @@ -73,7 +74,7 @@ static ssize_t watchdog_show(struct device *dev, struct device_attribute *attr, struct gb_svc *svc = to_gb_svc(dev); return sprintf(buf, "%s\n", - gb_svc_watchdog_enabled(svc) ? "enabled" : "disabled"); + str_enabled_disabled(gb_svc_watchdog_enabled(svc))); } static ssize_t watchdog_store(struct device *dev, diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index f064e3d172b3..6a4239ebb582 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -268,4 +268,16 @@ config CORESIGHT_KUNIT_TESTS Enable Coresight unit tests. Only useful for development and not intended for production. +config CORESIGHT_TNOC + tristate "Coresight Trace Network On Chip driver" + help + This driver provides support for Trace Network On Chip (TNOC) component. + TNOC is an interconnect used to collect traces from various subsystems + and transport to a coresight trace sink. It sits in the different + tiles of SOC and aggregates the trace local to the tile and transports + it another tile or to coresight trace sink eventually. + + To compile this driver as a module, choose M here: the module will be + called coresight-tnoc. + endif diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 4e7cc3c5bf99..ab16d06783a5 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_CORESIGHT_SINK_TPIU) += coresight-tpiu.o obj-$(CONFIG_CORESIGHT_SINK_ETBV10) += coresight-etb10.o obj-$(CONFIG_CORESIGHT_LINKS_AND_SINKS) += coresight-funnel.o \ coresight-replicator.o +obj-$(CONFIG_CORESIGHT_TNOC) += coresight-tnoc.o obj-$(CONFIG_CORESIGHT_SOURCE_ETM3X) += coresight-etm3x.o coresight-etm3x-y := coresight-etm3x-core.o coresight-etm-cp14.o \ coresight-etm3x-sysfs.o diff --git a/drivers/hwtracing/coresight/coresight-catu.c b/drivers/hwtracing/coresight/coresight-catu.c index 5058432233da..a3ccb7034ae1 100644 --- a/drivers/hwtracing/coresight/coresight-catu.c +++ b/drivers/hwtracing/coresight/coresight-catu.c @@ -515,11 +515,21 @@ static int __catu_probe(struct device *dev, struct resource *res) { int ret = 0; u32 dma_mask; - struct catu_drvdata *drvdata = dev_get_drvdata(dev); + struct catu_drvdata *drvdata; struct coresight_desc catu_desc; struct coresight_platform_data *pdata = NULL; void __iomem *base; + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + dev_set_drvdata(dev, drvdata); + + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; + catu_desc.name = coresight_alloc_device_name(&catu_devs, dev); if (!catu_desc.name) return -ENOMEM; @@ -576,14 +586,8 @@ out: static int catu_probe(struct amba_device *adev, const struct amba_id *id) { - struct catu_drvdata *drvdata; int ret; - drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - amba_set_drvdata(adev, drvdata); ret = __catu_probe(&adev->dev, &adev->res); if (!ret) pm_runtime_put(&adev->dev); @@ -623,29 +627,16 @@ static struct amba_driver catu_driver = { static int catu_platform_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - struct catu_drvdata *drvdata; int ret = 0; - drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; - pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); - dev_set_drvdata(&pdev->dev, drvdata); ret = __catu_probe(&pdev->dev, res); pm_runtime_put(&pdev->dev); - if (ret) { + if (ret) pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); - } return ret; } @@ -659,8 +650,6 @@ static void catu_platform_remove(struct platform_device *pdev) __catu_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_PM @@ -668,18 +657,26 @@ static int catu_runtime_suspend(struct device *dev) { struct catu_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); + return 0; } static int catu_runtime_resume(struct device *dev) { struct catu_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; + + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif diff --git a/drivers/hwtracing/coresight/coresight-catu.h b/drivers/hwtracing/coresight/coresight-catu.h index 755776cd19c5..6e6b7aac206d 100644 --- a/drivers/hwtracing/coresight/coresight-catu.h +++ b/drivers/hwtracing/coresight/coresight-catu.h @@ -62,6 +62,7 @@ struct catu_drvdata { struct clk *pclk; + struct clk *atclk; void __iomem *base; struct coresight_device *csdev; int irq; diff --git a/drivers/hwtracing/coresight/coresight-core.c b/drivers/hwtracing/coresight/coresight-core.c index fa758cc21827..3267192f0c1c 100644 --- a/drivers/hwtracing/coresight/coresight-core.c +++ b/drivers/hwtracing/coresight/coresight-core.c @@ -3,6 +3,8 @@ * Copyright (c) 2012, The Linux Foundation. All rights reserved. */ +#include <linux/acpi.h> +#include <linux/bitfield.h> #include <linux/build_bug.h> #include <linux/kernel.h> #include <linux/init.h> @@ -1374,8 +1376,9 @@ struct coresight_device *coresight_register(struct coresight_desc *desc) goto out_unlock; } - if (csdev->type == CORESIGHT_DEV_TYPE_SINK || - csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) { + if ((csdev->type == CORESIGHT_DEV_TYPE_SINK || + csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) && + sink_ops(csdev)->alloc_buffer) { ret = etm_perf_add_symlink_sink(csdev); if (ret) { @@ -1698,6 +1701,53 @@ int coresight_etm_get_trace_id(struct coresight_device *csdev, enum cs_mode mode } EXPORT_SYMBOL_GPL(coresight_etm_get_trace_id); +/* + * Attempt to find and enable programming clock (pclk) and trace clock (atclk) + * for the given device. + * + * For ACPI devices, clocks are controlled by firmware, so bail out early in + * this case. Also, skip enabling pclk if the clock is managed by the AMBA + * bus driver instead. + * + * atclk is an optional clock, it will be only enabled when it is existed. + * Otherwise, a NULL pointer will be returned to caller. + * + * Returns: '0' on Success; Error code otherwise. + */ +int coresight_get_enable_clocks(struct device *dev, struct clk **pclk, + struct clk **atclk) +{ + WARN_ON(!pclk); + + if (has_acpi_companion(dev)) + return 0; + + if (!dev_is_amba(dev)) { + /* + * "apb_pclk" is the default clock name for an Arm Primecell + * peripheral, while "apb" is used only by the CTCU driver. + * + * For easier maintenance, CoreSight drivers should use + * "apb_pclk" as the programming clock name. + */ + *pclk = devm_clk_get_optional_enabled(dev, "apb_pclk"); + if (!*pclk) + *pclk = devm_clk_get_optional_enabled(dev, "apb"); + if (IS_ERR(*pclk)) + return PTR_ERR(*pclk); + } + + /* Initialization of atclk is skipped if it is a NULL pointer. */ + if (atclk) { + *atclk = devm_clk_get_optional_enabled(dev, "atclk"); + if (IS_ERR(*atclk)) + return PTR_ERR(*atclk); + } + + return 0; +} +EXPORT_SYMBOL_GPL(coresight_get_enable_clocks); + MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Pratik Patel <pratikp@codeaurora.org>"); MODULE_AUTHOR("Mathieu Poirier <mathieu.poirier@linaro.org>"); diff --git a/drivers/hwtracing/coresight/coresight-cpu-debug.c b/drivers/hwtracing/coresight/coresight-cpu-debug.c index a871d997330b..5f21366406aa 100644 --- a/drivers/hwtracing/coresight/coresight-cpu-debug.c +++ b/drivers/hwtracing/coresight/coresight-cpu-debug.c @@ -562,10 +562,20 @@ static void debug_func_exit(void) static int __debug_probe(struct device *dev, struct resource *res) { - struct debug_drvdata *drvdata = dev_get_drvdata(dev); + struct debug_drvdata *drvdata; void __iomem *base; int ret; + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + dev_set_drvdata(dev, drvdata); + + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, NULL); + if (ret) + return ret; + drvdata->cpu = coresight_get_cpu(dev); if (drvdata->cpu < 0) return drvdata->cpu; @@ -625,13 +635,6 @@ err: static int debug_probe(struct amba_device *adev, const struct amba_id *id) { - struct debug_drvdata *drvdata; - - drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - amba_set_drvdata(adev, drvdata); return __debug_probe(&adev->dev, &adev->res); } @@ -690,18 +693,8 @@ static struct amba_driver debug_driver = { static int debug_platform_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - struct debug_drvdata *drvdata; int ret = 0; - drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; - - dev_set_drvdata(&pdev->dev, drvdata); pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); @@ -710,8 +703,6 @@ static int debug_platform_probe(struct platform_device *pdev) if (ret) { pm_runtime_put_noidle(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } return ret; } @@ -725,8 +716,6 @@ static void debug_platform_remove(struct platform_device *pdev) __debug_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_ACPI @@ -742,8 +731,8 @@ static int debug_runtime_suspend(struct device *dev) { struct debug_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); + clk_disable_unprepare(drvdata->pclk); + return 0; } @@ -751,9 +740,7 @@ static int debug_runtime_resume(struct device *dev) { struct debug_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + return clk_prepare_enable(drvdata->pclk); } #endif diff --git a/drivers/hwtracing/coresight/coresight-ctcu-core.c b/drivers/hwtracing/coresight/coresight-ctcu-core.c index c6bafc96db96..c586495e9a08 100644 --- a/drivers/hwtracing/coresight/coresight-ctcu-core.c +++ b/drivers/hwtracing/coresight/coresight-ctcu-core.c @@ -188,7 +188,7 @@ static int ctcu_probe(struct platform_device *pdev) const struct ctcu_config *cfgs; struct ctcu_drvdata *drvdata; void __iomem *base; - int i; + int i, ret; desc.name = coresight_alloc_device_name(&ctcu_devs, dev); if (!desc.name) @@ -207,9 +207,9 @@ static int ctcu_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); - drvdata->apb_clk = coresight_get_enable_apb_pclk(dev); - if (IS_ERR(drvdata->apb_clk)) - return -ENODEV; + ret = coresight_get_enable_clocks(dev, &drvdata->apb_clk, NULL); + if (ret) + return ret; cfgs = of_device_get_match_data(dev); if (cfgs) { @@ -233,12 +233,8 @@ static int ctcu_probe(struct platform_device *pdev) desc.access = CSDEV_ACCESS_IOMEM(base); drvdata->csdev = coresight_register(&desc); - if (IS_ERR(drvdata->csdev)) { - if (!IS_ERR_OR_NULL(drvdata->apb_clk)) - clk_put(drvdata->apb_clk); - + if (IS_ERR(drvdata->csdev)) return PTR_ERR(drvdata->csdev); - } return 0; } @@ -275,8 +271,6 @@ static void ctcu_platform_remove(struct platform_device *pdev) ctcu_remove(pdev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->apb_clk)) - clk_put(drvdata->apb_clk); } #ifdef CONFIG_PM @@ -284,8 +278,7 @@ static int ctcu_runtime_suspend(struct device *dev) { struct ctcu_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->apb_clk)) - clk_disable_unprepare(drvdata->apb_clk); + clk_disable_unprepare(drvdata->apb_clk); return 0; } @@ -294,10 +287,7 @@ static int ctcu_runtime_resume(struct device *dev) { struct ctcu_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->apb_clk)) - clk_prepare_enable(drvdata->apb_clk); - - return 0; + return clk_prepare_enable(drvdata->apb_clk); } #endif diff --git a/drivers/hwtracing/coresight/coresight-etb10.c b/drivers/hwtracing/coresight/coresight-etb10.c index d5efb085b30d..35db1b6093d1 100644 --- a/drivers/hwtracing/coresight/coresight-etb10.c +++ b/drivers/hwtracing/coresight/coresight-etb10.c @@ -730,12 +730,10 @@ static int etb_probe(struct amba_device *adev, const struct amba_id *id) if (!drvdata) return -ENOMEM; - drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } + drvdata->atclk = devm_clk_get_optional_enabled(dev, "atclk"); + if (IS_ERR(drvdata->atclk)) + return PTR_ERR(drvdata->atclk); + dev_set_drvdata(dev, drvdata); /* validity for the resource is already checked by the AMBA core */ @@ -811,8 +809,7 @@ static int etb_runtime_suspend(struct device *dev) { struct etb_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->atclk); return 0; } @@ -821,10 +818,7 @@ static int etb_runtime_resume(struct device *dev) { struct etb_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); - - return 0; + return clk_prepare_enable(drvdata->atclk); } #endif diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index f1551c08ecb2..f677c08233ba 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -851,7 +851,7 @@ static ssize_t etm_perf_sink_name_show(struct device *dev, struct dev_ext_attribute *ea; ea = container_of(dattr, struct dev_ext_attribute, attr); - return scnprintf(buf, PAGE_SIZE, "0x%lx\n", (unsigned long)(ea->var)); + return scnprintf(buf, PAGE_SIZE, "0x%px\n", ea->var); } static struct dev_ext_attribute * @@ -943,7 +943,7 @@ static ssize_t etm_perf_cscfg_event_show(struct device *dev, struct dev_ext_attribute *ea; ea = container_of(dattr, struct dev_ext_attribute, attr); - return scnprintf(buf, PAGE_SIZE, "configid=0x%lx\n", (unsigned long)(ea->var)); + return scnprintf(buf, PAGE_SIZE, "configid=0x%px\n", ea->var); } int etm_perf_add_symlink_cscfg(struct device *dev, struct cscfg_config_desc *config_desc) diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c index 1c6204e14422..45630a1cd32f 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c @@ -832,12 +832,9 @@ static int etm_probe(struct amba_device *adev, const struct amba_id *id) spin_lock_init(&drvdata->spinlock); - drvdata->atclk = devm_clk_get(&adev->dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } + drvdata->atclk = devm_clk_get_optional_enabled(dev, "atclk"); + if (IS_ERR(drvdata->atclk)) + return PTR_ERR(drvdata->atclk); drvdata->cpu = coresight_get_cpu(dev); if (drvdata->cpu < 0) @@ -928,8 +925,7 @@ static int etm_runtime_suspend(struct device *dev) { struct etm_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->atclk); return 0; } @@ -938,10 +934,7 @@ static int etm_runtime_resume(struct device *dev) { struct etm_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); - - return 0; + return clk_prepare_enable(drvdata->atclk); } #endif diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 42e5d37403ad..020f070bf17d 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -4,6 +4,7 @@ */ #include <linux/acpi.h> +#include <linux/bitfield.h> #include <linux/bitops.h> #include <linux/kernel.h> #include <linux/kvm_host.h> @@ -528,7 +529,8 @@ static int etm4_enable_hw(struct etmv4_drvdata *drvdata) etm4x_relaxed_write32(csa, config->seq_rst, TRCSEQRSTEVR); etm4x_relaxed_write32(csa, config->seq_state, TRCSEQSTR); } - etm4x_relaxed_write32(csa, config->ext_inp, TRCEXTINSELR); + if (drvdata->numextinsel) + etm4x_relaxed_write32(csa, config->ext_inp, TRCEXTINSELR); for (i = 0; i < drvdata->nr_cntr; i++) { etm4x_relaxed_write32(csa, config->cntrldvr[i], TRCCNTRLDVRn(i)); etm4x_relaxed_write32(csa, config->cntr_ctrl[i], TRCCNTCTLRn(i)); @@ -1423,6 +1425,7 @@ static void etm4_init_arch_data(void *info) etmidr5 = etm4x_relaxed_read32(csa, TRCIDR5); /* NUMEXTIN, bits[8:0] number of external inputs implemented */ drvdata->nr_ext_inp = FIELD_GET(TRCIDR5_NUMEXTIN_MASK, etmidr5); + drvdata->numextinsel = FIELD_GET(TRCIDR5_NUMEXTINSEL_MASK, etmidr5); /* TRACEIDSIZE, bits[21:16] indicates the trace ID width */ drvdata->trcid_size = FIELD_GET(TRCIDR5_TRACEIDSIZE_MASK, etmidr5); /* ATBTRIG, bit[22] implementation can support ATB triggers? */ @@ -1852,7 +1855,9 @@ static int __etm4_cpu_save(struct etmv4_drvdata *drvdata) state->trcseqrstevr = etm4x_read32(csa, TRCSEQRSTEVR); state->trcseqstr = etm4x_read32(csa, TRCSEQSTR); } - state->trcextinselr = etm4x_read32(csa, TRCEXTINSELR); + + if (drvdata->numextinsel) + state->trcextinselr = etm4x_read32(csa, TRCEXTINSELR); for (i = 0; i < drvdata->nr_cntr; i++) { state->trccntrldvr[i] = etm4x_read32(csa, TRCCNTRLDVRn(i)); @@ -1984,7 +1989,8 @@ static void __etm4_cpu_restore(struct etmv4_drvdata *drvdata) etm4x_relaxed_write32(csa, state->trcseqrstevr, TRCSEQRSTEVR); etm4x_relaxed_write32(csa, state->trcseqstr, TRCSEQSTR); } - etm4x_relaxed_write32(csa, state->trcextinselr, TRCEXTINSELR); + if (drvdata->numextinsel) + etm4x_relaxed_write32(csa, state->trcextinselr, TRCEXTINSELR); for (i = 0; i < drvdata->nr_cntr; i++) { etm4x_relaxed_write32(csa, state->trccntrldvr[i], TRCCNTRLDVRn(i)); @@ -2211,10 +2217,15 @@ static int etm4_probe(struct device *dev) struct csdev_access access = { 0 }; struct etm4_init_arg init_arg = { 0 }; struct etm4_init_arg *delayed; + int ret; if (WARN_ON(!drvdata)) return -ENOMEM; + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; + if (pm_save_enable == PARAM_PM_SAVE_FIRMWARE) pm_save_enable = coresight_loses_context_with_cpu(dev) ? PARAM_PM_SAVE_SELF_HOSTED : PARAM_PM_SAVE_NEVER; @@ -2297,16 +2308,10 @@ static int etm4_probe_platform_dev(struct platform_device *pdev) if (!drvdata) return -ENOMEM; - drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; - if (res) { drvdata->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(drvdata->base)) { - clk_put(drvdata->pclk); + if (IS_ERR(drvdata->base)) return PTR_ERR(drvdata->base); - } } dev_set_drvdata(&pdev->dev, drvdata); @@ -2413,9 +2418,6 @@ static void etm4_remove_platform_dev(struct platform_device *pdev) if (drvdata) etm4_remove_dev(drvdata); pm_runtime_disable(&pdev->dev); - - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } static const struct amba_id etm4_ids[] = { @@ -2463,8 +2465,8 @@ static int etm4_runtime_suspend(struct device *dev) { struct etmv4_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata->pclk && !IS_ERR(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); return 0; } @@ -2472,11 +2474,17 @@ static int etm4_runtime_suspend(struct device *dev) static int etm4_runtime_resume(struct device *dev) { struct etmv4_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata->pclk && !IS_ERR(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; - return 0; + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif diff --git a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c index ab251865b893..e9eeea6240d5 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-sysfs.c @@ -4,6 +4,7 @@ * Author: Mathieu Poirier <mathieu.poirier@linaro.org> */ +#include <linux/bitfield.h> #include <linux/coresight.h> #include <linux/pid_namespace.h> #include <linux/pm_runtime.h> diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index ac649515054d..13ec9ecef46f 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -162,6 +162,7 @@ #define TRCIDR4_NUMVMIDC_MASK GENMASK(31, 28) #define TRCIDR5_NUMEXTIN_MASK GENMASK(8, 0) +#define TRCIDR5_NUMEXTINSEL_MASK GENMASK(11, 9) #define TRCIDR5_TRACEIDSIZE_MASK GENMASK(21, 16) #define TRCIDR5_ATBTRIG BIT(22) #define TRCIDR5_LPOVERRIDE BIT(23) @@ -919,7 +920,8 @@ struct etmv4_save_state { /** * struct etm4_drvdata - specifics associated to an ETM component - * @pclk APB clock if present, otherwise NULL + * @pclk: APB clock if present, otherwise NULL + * @atclk: Optional clock for the core parts of the ETMv4. * @base: Memory mapped base address for this component. * @csdev: Component vitals needed by the framework. * @spinlock: Only one at a time pls. @@ -988,6 +990,7 @@ struct etmv4_save_state { */ struct etmv4_drvdata { struct clk *pclk; + struct clk *atclk; void __iomem *base; struct coresight_device *csdev; raw_spinlock_t spinlock; @@ -999,6 +1002,7 @@ struct etmv4_drvdata { u8 nr_cntr; u8 nr_ext_inp; u8 numcidc; + u8 numextinsel; u8 numvmidc; u8 nrseqstate; u8 nr_event; diff --git a/drivers/hwtracing/coresight/coresight-funnel.c b/drivers/hwtracing/coresight/coresight-funnel.c index b1922dbe9292..3b248e54471a 100644 --- a/drivers/hwtracing/coresight/coresight-funnel.c +++ b/drivers/hwtracing/coresight/coresight-funnel.c @@ -213,11 +213,11 @@ ATTRIBUTE_GROUPS(coresight_funnel); static int funnel_probe(struct device *dev, struct resource *res) { - int ret; void __iomem *base; struct coresight_platform_data *pdata = NULL; struct funnel_drvdata *drvdata; struct coresight_desc desc = { 0 }; + int ret; if (is_of_node(dev_fwnode(dev)) && of_device_is_compatible(dev->of_node, "arm,coresight-funnel")) @@ -231,16 +231,9 @@ static int funnel_probe(struct device *dev, struct resource *res) if (!drvdata) return -ENOMEM; - drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } - - drvdata->pclk = coresight_get_enable_apb_pclk(dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; /* * Map the device base for dynamic-funnel, which has been @@ -248,10 +241,8 @@ static int funnel_probe(struct device *dev, struct resource *res) */ if (res) { base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) { - ret = PTR_ERR(base); - goto out_disable_clk; - } + if (IS_ERR(base)) + return PTR_ERR(base); drvdata->base = base; desc.groups = coresight_funnel_groups; desc.access = CSDEV_ACCESS_IOMEM(base); @@ -261,10 +252,9 @@ static int funnel_probe(struct device *dev, struct resource *res) dev_set_drvdata(dev, drvdata); pdata = coresight_get_platform_data(dev); - if (IS_ERR(pdata)) { - ret = PTR_ERR(pdata); - goto out_disable_clk; - } + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + dev->platform_data = pdata; raw_spin_lock_init(&drvdata->spinlock); @@ -274,19 +264,10 @@ static int funnel_probe(struct device *dev, struct resource *res) desc.pdata = pdata; desc.dev = dev; drvdata->csdev = coresight_register(&desc); - if (IS_ERR(drvdata->csdev)) { - ret = PTR_ERR(drvdata->csdev); - goto out_disable_clk; - } + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); - ret = 0; - -out_disable_clk: - if (ret && !IS_ERR_OR_NULL(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); - if (ret && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); - return ret; + return 0; } static int funnel_remove(struct device *dev) @@ -303,11 +284,8 @@ static int funnel_runtime_suspend(struct device *dev) { struct funnel_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); - - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); return 0; } @@ -315,13 +293,17 @@ static int funnel_runtime_suspend(struct device *dev) static int funnel_runtime_resume(struct device *dev) { struct funnel_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif @@ -355,8 +337,6 @@ static void funnel_platform_remove(struct platform_device *pdev) funnel_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } static const struct of_device_id funnel_match[] = { diff --git a/drivers/hwtracing/coresight/coresight-replicator.c b/drivers/hwtracing/coresight/coresight-replicator.c index 06efd2b01a0f..e6472658235d 100644 --- a/drivers/hwtracing/coresight/coresight-replicator.c +++ b/drivers/hwtracing/coresight/coresight-replicator.c @@ -219,11 +219,11 @@ static const struct attribute_group *replicator_groups[] = { static int replicator_probe(struct device *dev, struct resource *res) { - int ret = 0; struct coresight_platform_data *pdata = NULL; struct replicator_drvdata *drvdata; struct coresight_desc desc = { 0 }; void __iomem *base; + int ret; if (is_of_node(dev_fwnode(dev)) && of_device_is_compatible(dev->of_node, "arm,coresight-replicator")) @@ -238,16 +238,9 @@ static int replicator_probe(struct device *dev, struct resource *res) if (!drvdata) return -ENOMEM; - drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } - - drvdata->pclk = coresight_get_enable_apb_pclk(dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; /* * Map the device base for dynamic-replicator, which has been @@ -255,10 +248,8 @@ static int replicator_probe(struct device *dev, struct resource *res) */ if (res) { base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) { - ret = PTR_ERR(base); - goto out_disable_clk; - } + if (IS_ERR(base)) + return PTR_ERR(base); drvdata->base = base; desc.groups = replicator_groups; desc.access = CSDEV_ACCESS_IOMEM(base); @@ -272,10 +263,8 @@ static int replicator_probe(struct device *dev, struct resource *res) dev_set_drvdata(dev, drvdata); pdata = coresight_get_platform_data(dev); - if (IS_ERR(pdata)) { - ret = PTR_ERR(pdata); - goto out_disable_clk; - } + if (IS_ERR(pdata)) + return PTR_ERR(pdata); dev->platform_data = pdata; raw_spin_lock_init(&drvdata->spinlock); @@ -286,19 +275,11 @@ static int replicator_probe(struct device *dev, struct resource *res) desc.dev = dev; drvdata->csdev = coresight_register(&desc); - if (IS_ERR(drvdata->csdev)) { - ret = PTR_ERR(drvdata->csdev); - goto out_disable_clk; - } + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); replicator_reset(drvdata); - -out_disable_clk: - if (ret && !IS_ERR_OR_NULL(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); - if (ret && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); - return ret; + return 0; } static int replicator_remove(struct device *dev) @@ -335,8 +316,6 @@ static void replicator_platform_remove(struct platform_device *pdev) replicator_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_PM @@ -344,24 +323,26 @@ static int replicator_runtime_suspend(struct device *dev) { struct replicator_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); return 0; } static int replicator_runtime_resume(struct device *dev) { struct replicator_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif diff --git a/drivers/hwtracing/coresight/coresight-stm.c b/drivers/hwtracing/coresight/coresight-stm.c index e45c6c7204b4..e68529bf89c9 100644 --- a/drivers/hwtracing/coresight/coresight-stm.c +++ b/drivers/hwtracing/coresight/coresight-stm.c @@ -342,7 +342,7 @@ static int stm_generic_link(struct stm_data *stm_data, { struct stm_drvdata *drvdata = container_of(stm_data, struct stm_drvdata, stm); - if (!drvdata || !drvdata->csdev) + if (!drvdata->csdev) return -EINVAL; return coresight_enable_sysfs(drvdata->csdev); @@ -353,7 +353,7 @@ static void stm_generic_unlink(struct stm_data *stm_data, { struct stm_drvdata *drvdata = container_of(stm_data, struct stm_drvdata, stm); - if (!drvdata || !drvdata->csdev) + if (!drvdata->csdev) return; coresight_disable_sysfs(drvdata->csdev); @@ -384,7 +384,7 @@ static long stm_generic_set_options(struct stm_data *stm_data, { struct stm_drvdata *drvdata = container_of(stm_data, struct stm_drvdata, stm); - if (!(drvdata && coresight_get_mode(drvdata->csdev))) + if (!coresight_get_mode(drvdata->csdev)) return -EINVAL; if (channel >= drvdata->numsp) @@ -419,7 +419,7 @@ static ssize_t notrace stm_generic_packet(struct stm_data *stm_data, struct stm_drvdata, stm); unsigned int stm_flags; - if (!(drvdata && coresight_get_mode(drvdata->csdev))) + if (!coresight_get_mode(drvdata->csdev)) return -EACCES; if (channel >= drvdata->numsp) @@ -842,16 +842,10 @@ static int __stm_probe(struct device *dev, struct resource *res) if (!drvdata) return -ENOMEM; - drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; - drvdata->pclk = coresight_get_enable_apb_pclk(dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; dev_set_drvdata(dev, drvdata); base = devm_ioremap_resource(dev, res); @@ -963,24 +957,26 @@ static int stm_runtime_suspend(struct device *dev) { struct stm_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); return 0; } static int stm_runtime_resume(struct device *dev) { struct stm_drvdata *drvdata = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + return ret; } #endif @@ -1033,8 +1029,6 @@ static void stm_platform_remove(struct platform_device *pdev) __stm_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_ACPI diff --git a/drivers/hwtracing/coresight/coresight-syscfg.c b/drivers/hwtracing/coresight/coresight-syscfg.c index 83dad24e0116..6836b05986e8 100644 --- a/drivers/hwtracing/coresight/coresight-syscfg.c +++ b/drivers/hwtracing/coresight/coresight-syscfg.c @@ -395,7 +395,7 @@ static void cscfg_remove_owned_csdev_configs(struct coresight_device *csdev, voi if (list_empty(&csdev->config_csdev_list)) return; - guard(raw_spinlock_irqsave)(&csdev->cscfg_csdev_lock); + guard(raw_spinlock_irqsave)(&csdev->cscfg_csdev_lock); list_for_each_entry_safe(config_csdev, tmp, &csdev->config_csdev_list, node) { if (config_csdev->config_desc->load_owner == load_owner) diff --git a/drivers/hwtracing/coresight/coresight-sysfs.c b/drivers/hwtracing/coresight/coresight-sysfs.c index feadaf065b53..5e52324aa9ac 100644 --- a/drivers/hwtracing/coresight/coresight-sysfs.c +++ b/drivers/hwtracing/coresight/coresight-sysfs.c @@ -7,6 +7,7 @@ #include <linux/device.h> #include <linux/idr.h> #include <linux/kernel.h> +#include <linux/property.h> #include "coresight-priv.h" #include "coresight-trace-id.h" @@ -371,17 +372,81 @@ static ssize_t enable_source_store(struct device *dev, } static DEVICE_ATTR_RW(enable_source); +static ssize_t label_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + const char *str; + int ret; + + ret = fwnode_property_read_string(dev_fwnode(dev), "label", &str); + if (ret == 0) + return sysfs_emit(buf, "%s\n", str); + else + return ret; +} +static DEVICE_ATTR_RO(label); + +static umode_t label_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + + if (attr == &dev_attr_label.attr) { + if (fwnode_property_present(dev_fwnode(dev), "label")) + return attr->mode; + else + return 0; + } + + return attr->mode; +} + static struct attribute *coresight_sink_attrs[] = { &dev_attr_enable_sink.attr, + &dev_attr_label.attr, NULL, }; -ATTRIBUTE_GROUPS(coresight_sink); + +static struct attribute_group coresight_sink_group = { + .attrs = coresight_sink_attrs, + .is_visible = label_is_visible, +}; +__ATTRIBUTE_GROUPS(coresight_sink); static struct attribute *coresight_source_attrs[] = { &dev_attr_enable_source.attr, + &dev_attr_label.attr, NULL, }; -ATTRIBUTE_GROUPS(coresight_source); + +static struct attribute_group coresight_source_group = { + .attrs = coresight_source_attrs, + .is_visible = label_is_visible, +}; +__ATTRIBUTE_GROUPS(coresight_source); + +static struct attribute *coresight_link_attrs[] = { + &dev_attr_label.attr, + NULL, +}; + +static struct attribute_group coresight_link_group = { + .attrs = coresight_link_attrs, + .is_visible = label_is_visible, +}; +__ATTRIBUTE_GROUPS(coresight_link); + +static struct attribute *coresight_helper_attrs[] = { + &dev_attr_label.attr, + NULL, +}; + +static struct attribute_group coresight_helper_group = { + .attrs = coresight_helper_attrs, + .is_visible = label_is_visible, +}; +__ATTRIBUTE_GROUPS(coresight_helper); const struct device_type coresight_dev_type[] = { [CORESIGHT_DEV_TYPE_SINK] = { @@ -390,6 +455,7 @@ const struct device_type coresight_dev_type[] = { }, [CORESIGHT_DEV_TYPE_LINK] = { .name = "link", + .groups = coresight_link_groups, }, [CORESIGHT_DEV_TYPE_LINKSINK] = { .name = "linksink", @@ -401,6 +467,7 @@ const struct device_type coresight_dev_type[] = { }, [CORESIGHT_DEV_TYPE_HELPER] = { .name = "helper", + .groups = coresight_helper_groups, } }; /* Ensure the enum matches the names and groups */ diff --git a/drivers/hwtracing/coresight/coresight-tmc-core.c b/drivers/hwtracing/coresight/coresight-tmc-core.c index 88afb16bb6be..36599c431be6 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-core.c +++ b/drivers/hwtracing/coresight/coresight-tmc-core.c @@ -24,6 +24,7 @@ #include <linux/pm_runtime.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_reserved_mem.h> #include <linux/coresight.h> #include <linux/amba/bus.h> #include <linux/platform_device.h> @@ -634,25 +635,14 @@ static int of_tmc_get_reserved_resource_by_name(struct device *dev, const char *name, struct resource *res) { - int index, rc = -ENODEV; - struct device_node *node; + int rc = -ENODEV; - if (!is_of_node(dev->fwnode)) - return -ENODEV; - - index = of_property_match_string(dev->of_node, "memory-region-names", - name); - if (index < 0) - return rc; - - node = of_parse_phandle(dev->of_node, "memory-region", index); - if (!node) + rc = of_reserved_mem_region_to_resource_byname(dev->of_node, name, res); + if (rc < 0) return rc; - if (!of_address_to_resource(node, 0, res) && - res->start != 0 && resource_size(res) != 0) - rc = 0; - of_node_put(node); + if (res->start == 0 || resource_size(res) == 0) + rc = -ENODEV; return rc; } @@ -785,10 +775,20 @@ static int __tmc_probe(struct device *dev, struct resource *res) u32 devid; void __iomem *base; struct coresight_platform_data *pdata = NULL; - struct tmc_drvdata *drvdata = dev_get_drvdata(dev); + struct tmc_drvdata *drvdata; struct coresight_desc desc = { 0 }; struct coresight_dev_list *dev_list = NULL; + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + dev_set_drvdata(dev, drvdata); + + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; + ret = -ENOMEM; /* Validity for the resource is already checked by the AMBA core */ @@ -894,14 +894,8 @@ out: static int tmc_probe(struct amba_device *adev, const struct amba_id *id) { - struct tmc_drvdata *drvdata; int ret; - drvdata = devm_kzalloc(&adev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - amba_set_drvdata(adev, drvdata); ret = __tmc_probe(&adev->dev, &adev->res); if (!ret) pm_runtime_put(&adev->dev); @@ -978,18 +972,8 @@ static struct amba_driver tmc_driver = { static int tmc_platform_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - struct tmc_drvdata *drvdata; int ret = 0; - drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - - drvdata->pclk = coresight_get_enable_apb_pclk(&pdev->dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; - - dev_set_drvdata(&pdev->dev, drvdata); pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); @@ -1011,8 +995,6 @@ static void tmc_platform_remove(struct platform_device *pdev) __tmc_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_PM @@ -1020,18 +1002,26 @@ static int tmc_runtime_suspend(struct device *dev) { struct tmc_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); + return 0; } static int tmc_runtime_resume(struct device *dev) { struct tmc_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; + + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index 6541a27a018e..cbb4ba439158 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -210,6 +210,7 @@ struct tmc_resrv_buf { /** * struct tmc_drvdata - specifics associated to an TMC component + * @atclk: optional clock for the core parts of the TMC. * @pclk: APB clock if present, otherwise NULL * @base: memory mapped base address for this component. * @csdev: component vitals needed by the framework. @@ -244,6 +245,7 @@ struct tmc_resrv_buf { * Used by ETR/ETF. */ struct tmc_drvdata { + struct clk *atclk; struct clk *pclk; void __iomem *base; struct coresight_device *csdev; diff --git a/drivers/hwtracing/coresight/coresight-tnoc.c b/drivers/hwtracing/coresight/coresight-tnoc.c new file mode 100644 index 000000000000..ff9a0a9cfe96 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-tnoc.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + + #include <linux/amba/bus.h> + #include <linux/coresight.h> + #include <linux/device.h> + #include <linux/io.h> + #include <linux/kernel.h> + #include <linux/module.h> + #include <linux/of.h> + #include <linux/platform_device.h> + +#include "coresight-priv.h" +#include "coresight-trace-id.h" + +#define TRACE_NOC_CTRL 0x008 +#define TRACE_NOC_XLD 0x010 +#define TRACE_NOC_FREQVAL 0x018 +#define TRACE_NOC_SYNCR 0x020 + +/* Enable generation of output ATB traffic.*/ +#define TRACE_NOC_CTRL_PORTEN BIT(0) +/* Sets the type of issued ATB FLAG packets.*/ +#define TRACE_NOC_CTRL_FLAGTYPE BIT(7) +/* Sets the type of issued ATB FREQ packet*/ +#define TRACE_NOC_CTRL_FREQTYPE BIT(8) + +#define TRACE_NOC_SYNC_INTERVAL 0xFFFF + +/* + * struct trace_noc_drvdata - specifics associated to a trace noc component + * @base: memory mapped base address for this component. + * @dev: device node for trace_noc_drvdata. + * @csdev: component vitals needed by the framework. + * @spinlock: serialize enable/disable operation. + * @atid: id for the trace packet. + */ +struct trace_noc_drvdata { + void __iomem *base; + struct device *dev; + struct coresight_device *csdev; + spinlock_t spinlock; + u32 atid; +}; + +DEFINE_CORESIGHT_DEVLIST(trace_noc_devs, "traceNoc"); + +static void trace_noc_enable_hw(struct trace_noc_drvdata *drvdata) +{ + u32 val; + + /* Set ATID */ + writel_relaxed(drvdata->atid, drvdata->base + TRACE_NOC_XLD); + + /* Set the data word count between 'SYNC' packets */ + writel_relaxed(TRACE_NOC_SYNC_INTERVAL, drvdata->base + TRACE_NOC_SYNCR); + + /* Set the Control register: + * - Set the FLAG packets to 'FLAG' packets + * - Set the FREQ packets to 'FREQ_TS' packets + * - Enable generation of output ATB traffic + */ + + val = readl_relaxed(drvdata->base + TRACE_NOC_CTRL); + + val &= ~TRACE_NOC_CTRL_FLAGTYPE; + val |= TRACE_NOC_CTRL_FREQTYPE; + val |= TRACE_NOC_CTRL_PORTEN; + + writel(val, drvdata->base + TRACE_NOC_CTRL); +} + +static int trace_noc_enable(struct coresight_device *csdev, struct coresight_connection *inport, + struct coresight_connection *outport) +{ + struct trace_noc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + scoped_guard(spinlock, &drvdata->spinlock) { + if (csdev->refcnt == 0) + trace_noc_enable_hw(drvdata); + + csdev->refcnt++; + } + + dev_dbg(drvdata->dev, "Trace NOC is enabled\n"); + return 0; +} + +static void trace_noc_disable(struct coresight_device *csdev, struct coresight_connection *inport, + struct coresight_connection *outport) +{ + struct trace_noc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + scoped_guard(spinlock, &drvdata->spinlock) { + if (--csdev->refcnt == 0) + writel(0x0, drvdata->base + TRACE_NOC_CTRL); + } + dev_dbg(drvdata->dev, "Trace NOC is disabled\n"); +} + +static int trace_noc_id(struct coresight_device *csdev, __maybe_unused enum cs_mode mode, + __maybe_unused struct coresight_device *sink) +{ + struct trace_noc_drvdata *drvdata; + + drvdata = dev_get_drvdata(csdev->dev.parent); + + return drvdata->atid; +} + +static const struct coresight_ops_link trace_noc_link_ops = { + .enable = trace_noc_enable, + .disable = trace_noc_disable, +}; + +static const struct coresight_ops trace_noc_cs_ops = { + .trace_id = trace_noc_id, + .link_ops = &trace_noc_link_ops, +}; + +static int trace_noc_init_default_data(struct trace_noc_drvdata *drvdata) +{ + int atid; + + atid = coresight_trace_id_get_system_id(); + if (atid < 0) + return atid; + + drvdata->atid = atid; + + return 0; +} + +static ssize_t traceid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long val; + struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev->parent); + + val = drvdata->atid; + return sprintf(buf, "%#lx\n", val); +} +static DEVICE_ATTR_RO(traceid); + +static struct attribute *coresight_tnoc_attrs[] = { + &dev_attr_traceid.attr, + NULL, +}; + +static const struct attribute_group coresight_tnoc_group = { + .attrs = coresight_tnoc_attrs, +}; + +static const struct attribute_group *coresight_tnoc_groups[] = { + &coresight_tnoc_group, + NULL, +}; + +static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id) +{ + struct device *dev = &adev->dev; + struct coresight_platform_data *pdata; + struct trace_noc_drvdata *drvdata; + struct coresight_desc desc = { 0 }; + int ret; + + desc.name = coresight_alloc_device_name(&trace_noc_devs, dev); + if (!desc.name) + return -ENOMEM; + + pdata = coresight_get_platform_data(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + adev->dev.platform_data = pdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->dev = &adev->dev; + dev_set_drvdata(dev, drvdata); + + drvdata->base = devm_ioremap_resource(dev, &adev->res); + if (IS_ERR(drvdata->base)) + return PTR_ERR(drvdata->base); + + spin_lock_init(&drvdata->spinlock); + + ret = trace_noc_init_default_data(drvdata); + if (ret) + return ret; + + desc.ops = &trace_noc_cs_ops; + desc.type = CORESIGHT_DEV_TYPE_LINK; + desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG; + desc.pdata = adev->dev.platform_data; + desc.dev = &adev->dev; + desc.access = CSDEV_ACCESS_IOMEM(drvdata->base); + desc.groups = coresight_tnoc_groups; + drvdata->csdev = coresight_register(&desc); + if (IS_ERR(drvdata->csdev)) { + coresight_trace_id_put_system_id(drvdata->atid); + return PTR_ERR(drvdata->csdev); + } + pm_runtime_put(&adev->dev); + + return 0; +} + +static void trace_noc_remove(struct amba_device *adev) +{ + struct trace_noc_drvdata *drvdata = dev_get_drvdata(&adev->dev); + + coresight_unregister(drvdata->csdev); + coresight_trace_id_put_system_id(drvdata->atid); +} + +static struct amba_id trace_noc_ids[] = { + { + .id = 0x000f0c00, + .mask = 0x00ffff00, + }, + { + .id = 0x001f0c00, + .mask = 0x00ffff00, + }, + {}, +}; +MODULE_DEVICE_TABLE(amba, trace_noc_ids); + +static struct amba_driver trace_noc_driver = { + .drv = { + .name = "coresight-trace-noc", + .suppress_bind_attrs = true, + }, + .probe = trace_noc_probe, + .remove = trace_noc_remove, + .id_table = trace_noc_ids, +}; + +module_amba_driver(trace_noc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Trace NOC driver"); diff --git a/drivers/hwtracing/coresight/coresight-tpda.c b/drivers/hwtracing/coresight/coresight-tpda.c index 0633f04beb24..333b3cb23685 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.c +++ b/drivers/hwtracing/coresight/coresight-tpda.c @@ -71,6 +71,8 @@ static int tpdm_read_element_size(struct tpda_drvdata *drvdata, if (tpdm_data->dsb) { rc = fwnode_property_read_u32(dev_fwnode(csdev->dev.parent), "qcom,dsb-element-bits", &drvdata->dsb_esize); + if (rc) + goto out; } if (tpdm_data->cmb) { @@ -78,6 +80,7 @@ static int tpdm_read_element_size(struct tpda_drvdata *drvdata, "qcom,cmb-element-bits", &drvdata->cmb_esize); } +out: if (rc) dev_warn_once(&csdev->dev, "Failed to read TPDM Element size: %d\n", rc); diff --git a/drivers/hwtracing/coresight/coresight-tpiu.c b/drivers/hwtracing/coresight/coresight-tpiu.c index 3e0159288428..9463afdbda8a 100644 --- a/drivers/hwtracing/coresight/coresight-tpiu.c +++ b/drivers/hwtracing/coresight/coresight-tpiu.c @@ -128,11 +128,11 @@ static const struct coresight_ops tpiu_cs_ops = { static int __tpiu_probe(struct device *dev, struct resource *res) { - int ret; void __iomem *base; struct coresight_platform_data *pdata = NULL; struct tpiu_drvdata *drvdata; struct coresight_desc desc = { 0 }; + int ret; desc.name = coresight_alloc_device_name(&tpiu_devs, dev); if (!desc.name) @@ -144,16 +144,10 @@ static int __tpiu_probe(struct device *dev, struct resource *res) spin_lock_init(&drvdata->spinlock); - drvdata->atclk = devm_clk_get(dev, "atclk"); /* optional */ - if (!IS_ERR(drvdata->atclk)) { - ret = clk_prepare_enable(drvdata->atclk); - if (ret) - return ret; - } + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, &drvdata->atclk); + if (ret) + return ret; - drvdata->pclk = coresight_get_enable_apb_pclk(dev); - if (IS_ERR(drvdata->pclk)) - return -ENODEV; dev_set_drvdata(dev, drvdata); /* Validity for the resource is already checked by the AMBA core */ @@ -212,24 +206,26 @@ static int tpiu_runtime_suspend(struct device *dev) { struct tpiu_drvdata *drvdata = dev_get_drvdata(dev); - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->atclk); + clk_disable_unprepare(drvdata->pclk); - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_disable_unprepare(drvdata->pclk); return 0; } static int tpiu_runtime_resume(struct device *dev) { struct tpiu_drvdata *drvdata = dev_get_drvdata(dev); + int ret; - if (drvdata && !IS_ERR(drvdata->atclk)) - clk_prepare_enable(drvdata->atclk); + ret = clk_prepare_enable(drvdata->pclk); + if (ret) + return ret; - if (drvdata && !IS_ERR_OR_NULL(drvdata->pclk)) - clk_prepare_enable(drvdata->pclk); - return 0; + ret = clk_prepare_enable(drvdata->atclk); + if (ret) + clk_disable_unprepare(drvdata->pclk); + + return ret; } #endif @@ -293,8 +289,6 @@ static void tpiu_platform_remove(struct platform_device *pdev) __tpiu_remove(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!IS_ERR_OR_NULL(drvdata->pclk)) - clk_put(drvdata->pclk); } #ifdef CONFIG_ACPI diff --git a/drivers/hwtracing/coresight/coresight-trbe.c b/drivers/hwtracing/coresight/coresight-trbe.c index 8f426f94e32a..43643d2c5bdd 100644 --- a/drivers/hwtracing/coresight/coresight-trbe.c +++ b/drivers/hwtracing/coresight/coresight-trbe.c @@ -258,6 +258,7 @@ static void trbe_drain_and_disable_local(struct trbe_cpudata *cpudata) static void trbe_reset_local(struct trbe_cpudata *cpudata) { write_sysreg_s(0, SYS_TRBLIMITR_EL1); + isb(); trbe_drain_buffer(); write_sysreg_s(0, SYS_TRBPTR_EL1); write_sysreg_s(0, SYS_TRBBASER_EL1); @@ -748,12 +749,12 @@ static void *arm_trbe_alloc_buffer(struct coresight_device *csdev, buf = kzalloc_node(sizeof(*buf), GFP_KERNEL, trbe_alloc_node(event)); if (!buf) - return ERR_PTR(-ENOMEM); + return NULL; pglist = kcalloc(nr_pages, sizeof(*pglist), GFP_KERNEL); if (!pglist) { kfree(buf); - return ERR_PTR(-ENOMEM); + return NULL; } for (i = 0; i < nr_pages; i++) @@ -763,7 +764,7 @@ static void *arm_trbe_alloc_buffer(struct coresight_device *csdev, if (!buf->trbe_base) { kfree(pglist); kfree(buf); - return ERR_PTR(-ENOMEM); + return NULL; } buf->trbe_limit = buf->trbe_base + nr_pages * PAGE_SIZE; buf->trbe_write = buf->trbe_base; @@ -1280,7 +1281,7 @@ static void arm_trbe_register_coresight_cpu(struct trbe_drvdata *drvdata, int cp * into the device for that purpose. */ desc.pdata = devm_kzalloc(dev, sizeof(*desc.pdata), GFP_KERNEL); - if (IS_ERR(desc.pdata)) + if (!desc.pdata) goto cpu_clear; desc.type = CORESIGHT_DEV_TYPE_SINK; diff --git a/drivers/hwtracing/coresight/ultrasoc-smb.h b/drivers/hwtracing/coresight/ultrasoc-smb.h index c4c111275627..323f0ccb6878 100644 --- a/drivers/hwtracing/coresight/ultrasoc-smb.h +++ b/drivers/hwtracing/coresight/ultrasoc-smb.h @@ -7,6 +7,7 @@ #ifndef _ULTRASOC_SMB_H #define _ULTRASOC_SMB_H +#include <linux/bitfield.h> #include <linux/miscdevice.h> #include <linux/spinlock.h> diff --git a/drivers/iio/accel/adxl345_core.c b/drivers/iio/accel/adxl345_core.c index b7dfd0007aa0..78e3f799ecc1 100644 --- a/drivers/iio/accel/adxl345_core.c +++ b/drivers/iio/accel/adxl345_core.c @@ -36,10 +36,29 @@ #define ADXL345_REG_TAP_AXIS_MSK GENMASK(2, 0) #define ADXL345_REG_TAP_SUPPRESS_MSK BIT(3) #define ADXL345_REG_TAP_SUPPRESS BIT(3) +#define ADXL345_POWER_CTL_INACT_MSK (ADXL345_POWER_CTL_AUTO_SLEEP | ADXL345_POWER_CTL_LINK) #define ADXL345_TAP_Z_EN BIT(0) #define ADXL345_TAP_Y_EN BIT(1) #define ADXL345_TAP_X_EN BIT(2) +#define ADXL345_REG_TAP_SUPPRESS BIT(3) + +#define ADXL345_INACT_Z_EN BIT(0) +#define ADXL345_INACT_Y_EN BIT(1) +#define ADXL345_INACT_X_EN BIT(2) +#define ADXL345_REG_INACT_ACDC BIT(3) +#define ADXL345_ACT_INACT_NO_AXIS_EN 0x00 +#define ADXL345_INACT_XYZ_EN (ADXL345_INACT_Z_EN | ADXL345_INACT_Y_EN | ADXL345_INACT_X_EN) + +#define ADXL345_ACT_Z_EN BIT(4) +#define ADXL345_ACT_Y_EN BIT(5) +#define ADXL345_ACT_X_EN BIT(6) +#define ADXL345_REG_ACT_ACDC BIT(7) +#define ADXL345_ACT_XYZ_EN (ADXL345_ACT_Z_EN | ADXL345_ACT_Y_EN | ADXL345_ACT_X_EN) + +#define ADXL345_COUPLING_DC 0 +#define ADXL345_COUPLING_AC 1 +#define ADXL345_REG_NO_ACDC 0x00 /* single/double tap */ enum adxl345_tap_type { @@ -64,6 +83,39 @@ static const unsigned int adxl345_tap_time_reg[] = { [ADXL345_TAP_TIME_DUR] = ADXL345_REG_DUR, }; +/* activity/inactivity */ +enum adxl345_activity_type { + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + ADXL345_INACTIVITY_FF, +}; + +static const unsigned int adxl345_act_int_reg[] = { + [ADXL345_ACTIVITY] = ADXL345_INT_ACTIVITY, + [ADXL345_INACTIVITY] = ADXL345_INT_INACTIVITY, + [ADXL345_ACTIVITY_AC] = ADXL345_INT_ACTIVITY, + [ADXL345_INACTIVITY_AC] = ADXL345_INT_INACTIVITY, + [ADXL345_INACTIVITY_FF] = ADXL345_INT_FREE_FALL, +}; + +static const unsigned int adxl345_act_thresh_reg[] = { + [ADXL345_ACTIVITY] = ADXL345_REG_THRESH_ACT, + [ADXL345_INACTIVITY] = ADXL345_REG_THRESH_INACT, + [ADXL345_ACTIVITY_AC] = ADXL345_REG_THRESH_ACT, + [ADXL345_INACTIVITY_AC] = ADXL345_REG_THRESH_INACT, + [ADXL345_INACTIVITY_FF] = ADXL345_REG_THRESH_FF, +}; + +static const unsigned int adxl345_act_acdc_msk[] = { + [ADXL345_ACTIVITY] = ADXL345_REG_ACT_ACDC, + [ADXL345_INACTIVITY] = ADXL345_REG_INACT_ACDC, + [ADXL345_ACTIVITY_AC] = ADXL345_REG_ACT_ACDC, + [ADXL345_INACTIVITY_AC] = ADXL345_REG_INACT_ACDC, + [ADXL345_INACTIVITY_FF] = ADXL345_REG_NO_ACDC, +}; + enum adxl345_odr { ADXL345_ODR_0P10HZ = 0, ADXL345_ODR_0P20HZ, @@ -129,6 +181,14 @@ static const int adxl345_fullres_range_tbl[][2] = { [ADXL345_16G_RANGE] = { 0, 38312 }, }; +/* scaling */ +static const int adxl345_range_factor_tbl[] = { + [ADXL345_2G_RANGE] = 1, + [ADXL345_4G_RANGE] = 2, + [ADXL345_8G_RANGE] = 4, + [ADXL345_16G_RANGE] = 8, +}; + struct adxl345_state { const struct adxl345_chip_info *info; struct regmap *regmap; @@ -136,6 +196,9 @@ struct adxl345_state { u8 watermark; u8 fifo_mode; + u8 inact_threshold; + u32 inact_time_ms; + u32 tap_duration_us; u32 tap_latent_us; u32 tap_window_us; @@ -145,6 +208,22 @@ struct adxl345_state { static const struct iio_event_spec adxl345_events[] = { { + /* activity */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, + { + /* activity, ac bit set */ + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = + BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, + { /* single tap */ .type = IIO_EV_TYPE_GESTURE, .dir = IIO_EV_DIR_SINGLETAP, @@ -188,10 +267,39 @@ enum adxl345_chans { chan_x, chan_y, chan_z, }; +static const struct iio_event_spec adxl345_fake_chan_events[] = { + { + /* inactivity */ + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD), + }, + { + /* inactivity, AC bit set */ + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_FALLING, + .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_chan_spec adxl345_channels[] = { ADXL345_CHANNEL(0, chan_x, X), ADXL345_CHANNEL(1, chan_y, Y), ADXL345_CHANNEL(2, chan_z, Z), + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X_AND_Y_AND_Z, + .scan_index = -1, /* Fake channel */ + .event_spec = adxl345_fake_chan_events, + .num_event_specs = ARRAY_SIZE(adxl345_fake_chan_events), + }, }; static const unsigned long adxl345_scan_masks[] = { @@ -237,6 +345,394 @@ static int adxl345_set_measure_en(struct adxl345_state *st, bool en) ADXL345_POWER_CTL_MEASURE, en); } +/* activity / inactivity */ + +static int adxl345_set_inact_threshold(struct adxl345_state *st, + unsigned int threshold) +{ + int ret; + + st->inact_threshold = min(U8_MAX, threshold); + + ret = regmap_write(st->regmap, + adxl345_act_thresh_reg[ADXL345_INACTIVITY], + st->inact_threshold); + if (ret) + return ret; + + return regmap_write(st->regmap, + adxl345_act_thresh_reg[ADXL345_INACTIVITY_FF], + st->inact_threshold); +} + +static int adxl345_set_default_time(struct adxl345_state *st) +{ + int max_boundary = U8_MAX; + int min_boundary = 10; + enum adxl345_odr odr; + unsigned int regval; + unsigned int val; + int ret; + + /* Generated inactivity time based on ODR */ + ret = regmap_read(st->regmap, ADXL345_REG_BW_RATE, ®val); + if (ret) + return ret; + + odr = FIELD_GET(ADXL345_BW_RATE_MSK, regval); + val = clamp(max_boundary - adxl345_odr_tbl[odr][0], + min_boundary, max_boundary); + st->inact_time_ms = MILLI * val; + + /* Inactivity time in s */ + return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val); +} + +static int adxl345_set_inactivity_time(struct adxl345_state *st, u32 val_int) +{ + st->inact_time_ms = MILLI * val_int; + + return regmap_write(st->regmap, ADXL345_REG_TIME_INACT, val_int); +} + +static int adxl345_set_freefall_time(struct adxl345_state *st, u32 val_fract) +{ + /* + * Datasheet max. value is 255 * 5000 us = 1.275000 seconds. + * + * Recommended values between 100ms and 350ms (0x14 to 0x46) + */ + st->inact_time_ms = DIV_ROUND_UP(val_fract, MILLI); + + return regmap_write(st->regmap, ADXL345_REG_TIME_FF, + DIV_ROUND_CLOSEST(val_fract, 5)); +} + +/** + * adxl345_set_inact_time - Configure inactivity time explicitly or by ODR. + * @st: The sensor state instance. + * @val_int: The inactivity time, integer part. + * @val_fract: The inactivity time, fractional part when val_int is 0. + * + * Inactivity time can be configured between 1 and 255 seconds. If a user sets + * val_s to 0, a default inactivity time is calculated automatically (since 0 is + * also invalid and undefined by the sensor). + * + * In such cases, power consumption should be considered: the inactivity period + * should be shorter at higher sampling frequencies and longer at lower ones. + * Specifically, for frequencies above 255 Hz, the default is set to 10 seconds; + * for frequencies below 10 Hz, it defaults to 255 seconds. + * + * The calculation method subtracts the integer part of the configured sample + * frequency from 255 to estimate the inactivity time in seconds. Sub-Hertz + * values are ignored in this approximation. Since the recommended output data + * rates (ODRs) for features like activity/inactivity detection, sleep modes, + * and free fall range between 12.5 Hz and 400 Hz, frequencies outside this + * range will either use the defined boundary defaults or require explicit + * configuration via val_s. + * + * Return: 0 or error value. + */ +static int adxl345_set_inact_time(struct adxl345_state *st, u32 val_int, + u32 val_fract) +{ + if (val_int > 0) { + /* Time >= 1s, inactivity */ + return adxl345_set_inactivity_time(st, val_int); + } else if (val_int == 0) { + if (val_fract > 0) { + /* Time < 1s, free-fall */ + return adxl345_set_freefall_time(st, val_fract); + } else if (val_fract == 0) { + /* Time == 0.0s */ + return adxl345_set_default_time(st); + } + } + + /* Do not support negative or wrong input. */ + return -EINVAL; +} + +/** + * adxl345_is_act_inact_ac() - Verify if AC or DC coupling is currently enabled. + * + * @st: The device data. + * @type: The activity or inactivity type. + * + * Given a type of activity / inactivity combined with either AC coupling set or + * default to DC, this function verifies if the combination is currently + * configured, hence enabled or not. + * + * Return: true if configured coupling matches the provided type, else a negative + * error value. + */ +static int adxl345_is_act_inact_ac(struct adxl345_state *st, + enum adxl345_activity_type type) +{ + unsigned int regval; + bool coupling; + int ret; + + if (type == ADXL345_INACTIVITY_FF) + return true; + + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val); + if (ret) + return ret; + + coupling = adxl345_act_acdc_msk[type] & regval; + + switch (type) { + case ADXL345_ACTIVITY: + case ADXL345_INACTIVITY: + return coupling == ADXL345_COUPLING_DC; + case ADXL345_ACTIVITY_AC: + case ADXL345_INACTIVITY_AC: + return coupling == ADXL345_COUPLING_AC; + default: + return -EINVAL; + } +} + +/** + * adxl345_set_act_inact_ac() - Configure AC coupling or DC coupling. + * + * @st: The device data. + * @type: Provide a type of activity or inactivity. + * @cmd_en: enable or disable AC coupling. + * + * Enables AC coupling or DC coupling depending on the provided type argument. + * Note: Activity and inactivity can be either AC coupled or DC coupled not + * both at the same time. + * + * Return: 0 if successful, else error value. + */ +static int adxl345_set_act_inact_ac(struct adxl345_state *st, + enum adxl345_activity_type type, + bool cmd_en) +{ + unsigned int act_inact_ac; + + if (type == ADXL345_ACTIVITY_AC || type == ADXL345_INACTIVITY_AC) + act_inact_ac = ADXL345_COUPLING_AC && cmd_en; + else + act_inact_ac = ADXL345_COUPLING_DC && cmd_en; + + /* + * A setting of false selects dc-coupled operation, and a setting of + * true enables ac-coupled operation. In dc-coupled operation, the + * current acceleration magnitude is compared directly with + * ADXL345_REG_THRESH_ACT and ADXL345_REG_THRESH_INACT to determine + * whether activity or inactivity is detected. + * + * In ac-coupled operation for activity detection, the acceleration + * value at the start of activity detection is taken as a reference + * value. New samples of acceleration are then compared to this + * reference value, and if the magnitude of the difference exceeds the + * ADXL345_REG_THRESH_ACT value, the device triggers an activity + * interrupt. + * + * Similarly, in ac-coupled operation for inactivity detection, a + * reference value is used for comparison and is updated whenever the + * device exceeds the inactivity threshold. After the reference value + * is selected, the device compares the magnitude of the difference + * between the reference value and the current acceleration with + * ADXL345_REG_THRESH_INACT. If the difference is less than the value in + * ADXL345_REG_THRESH_INACT for the time in ADXL345_REG_TIME_INACT, the + * device is considered inactive and the inactivity interrupt is + * triggered. [quoted from p. 24, ADXL345 datasheet Rev. G] + * + * In a conclusion, the first acceleration snapshot sample which hit the + * threshold in a particular direction is always taken as acceleration + * reference value to that direction. Since for the hardware activity + * and inactivity depend on the x/y/z axis, so do ac and dc coupling. + * Note, this sw driver always enables or disables all three x/y/z axis + * for detection via act_axis_ctrl and inact_axis_ctrl, respectively. + * Where in dc-coupling samples are compared against the thresholds, in + * ac-coupling measurement difference to the first acceleration + * reference value are compared against the threshold. So, ac-coupling + * allows for a bit more dynamic compensation depending on the initial + * sample. + */ + return regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL, + adxl345_act_acdc_msk[type], act_inact_ac); +} + +static int adxl345_is_act_inact_en(struct adxl345_state *st, + enum adxl345_activity_type type) +{ + unsigned int axis_ctrl; + unsigned int regval; + bool int_en, en; + int ret; + + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, &axis_ctrl); + if (ret) + return ret; + + /* Check if axis for activity are enabled */ + switch (type) { + case ADXL345_ACTIVITY: + case ADXL345_ACTIVITY_AC: + en = FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl); + if (!en) + return false; + break; + case ADXL345_INACTIVITY: + case ADXL345_INACTIVITY_AC: + en = FIELD_GET(ADXL345_INACT_XYZ_EN, axis_ctrl); + if (!en) + return false; + break; + case ADXL345_INACTIVITY_FF: + en = true; + break; + default: + return -EINVAL; + } + + /* Check if specific interrupt is enabled */ + ret = regmap_read(st->regmap, ADXL345_REG_INT_ENABLE, ®val); + if (ret) + return ret; + + int_en = adxl345_act_int_reg[type] & regval; + if (!int_en) + return false; + + /* Check if configured coupling matches provided type */ + return adxl345_is_act_inact_ac(st, type); +} + +static int adxl345_set_act_inact_linkbit(struct adxl345_state *st, + enum adxl345_activity_type type, + bool en) +{ + int act_ac_en, inact_ac_en; + int act_en, inact_en; + + act_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY); + if (act_en < 0) + return act_en; + + act_ac_en = adxl345_is_act_inact_en(st, ADXL345_ACTIVITY_AC); + if (act_ac_en < 0) + return act_ac_en; + + if (type == ADXL345_INACTIVITY_FF) { + inact_en = false; + } else { + inact_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY); + if (inact_en < 0) + return inact_en; + + inact_ac_en = adxl345_is_act_inact_en(st, ADXL345_INACTIVITY_AC); + if (inact_ac_en < 0) + return inact_ac_en; + + inact_en = inact_en || inact_ac_en; + } + + act_en = act_en || act_ac_en; + + return regmap_assign_bits(st->regmap, ADXL345_REG_POWER_CTL, + ADXL345_POWER_CTL_INACT_MSK, + en && act_en && inact_en); +} + +static int adxl345_set_act_inact_en(struct adxl345_state *st, + enum adxl345_activity_type type, + bool cmd_en) +{ + unsigned int axis_ctrl; + unsigned int threshold; + unsigned int period; + int ret; + + if (cmd_en) { + /* When turning on, check if threshold is valid */ + if (type == ADXL345_ACTIVITY || type == ADXL345_ACTIVITY_AC) { + ret = regmap_read(st->regmap, + adxl345_act_thresh_reg[type], + &threshold); + if (ret) + return ret; + } else { + threshold = st->inact_threshold; + } + + if (!threshold) /* Just ignore the command if threshold is 0 */ + return 0; + + /* When turning on inactivity, check if inact time is valid */ + if (type == ADXL345_INACTIVITY || type == ADXL345_INACTIVITY_AC) { + ret = regmap_read(st->regmap, + ADXL345_REG_TIME_INACT, + &period); + if (ret) + return ret; + + if (!period) + return 0; + } + } else { + /* + * When turning off an activity, ensure that the correct + * coupling event is specified. This step helps prevent misuse - + * for example, if an AC-coupled activity is active and the + * current call attempts to turn off a DC-coupled activity, this + * inconsistency should be detected here. + */ + if (adxl345_is_act_inact_ac(st, type) <= 0) + return 0; + } + + /* Start modifying configuration registers */ + ret = adxl345_set_measure_en(st, false); + if (ret) + return ret; + + /* Enable axis according to the command */ + switch (type) { + case ADXL345_ACTIVITY: + case ADXL345_ACTIVITY_AC: + axis_ctrl = ADXL345_ACT_XYZ_EN; + break; + case ADXL345_INACTIVITY: + case ADXL345_INACTIVITY_AC: + axis_ctrl = ADXL345_INACT_XYZ_EN; + break; + case ADXL345_INACTIVITY_FF: + axis_ctrl = ADXL345_ACT_INACT_NO_AXIS_EN; + break; + default: + return -EINVAL; + } + + ret = regmap_assign_bits(st->regmap, ADXL345_REG_ACT_INACT_CTRL, + axis_ctrl, cmd_en); + if (ret) + return ret; + + /* Update AC/DC-coupling according to the command */ + ret = adxl345_set_act_inact_ac(st, type, cmd_en); + if (ret) + return ret; + + /* Enable the interrupt line, according to the command */ + ret = regmap_assign_bits(st->regmap, ADXL345_REG_INT_ENABLE, + adxl345_act_int_reg[type], cmd_en); + if (ret) + return ret; + + /* Set link-bit and auto-sleep only when ACT and INACT are enabled */ + ret = adxl345_set_act_inact_linkbit(st, type, cmd_en); + if (ret) + return ret; + + return adxl345_set_measure_en(st, true); +} + /* tap */ static int _adxl345_set_tap_int(struct adxl345_state *st, @@ -368,9 +864,8 @@ static int adxl345_set_doubletap_en(struct adxl345_state *st, bool en) * Generally suppress detection of spikes during the latency period as * double taps here, this is fully optional for double tap detection */ - ret = regmap_update_bits(st->regmap, ADXL345_REG_TAP_AXIS, - ADXL345_REG_TAP_SUPPRESS_MSK, - en ? ADXL345_REG_TAP_SUPPRESS : 0x00); + ret = regmap_assign_bits(st->regmap, ADXL345_REG_TAP_AXIS, + ADXL345_REG_TAP_SUPPRESS, en); if (ret) return ret; @@ -466,9 +961,16 @@ static int adxl345_find_odr(struct adxl345_state *st, int val, static int adxl345_set_odr(struct adxl345_state *st, enum adxl345_odr odr) { - return regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE, + int ret; + + ret = regmap_update_bits(st->regmap, ADXL345_REG_BW_RATE, ADXL345_BW_RATE_MSK, FIELD_PREP(ADXL345_BW_RATE_MSK, odr)); + if (ret) + return ret; + + /* update inactivity time by ODR */ + return adxl345_set_inact_time(st, 0, 0); } static int adxl345_find_range(struct adxl345_state *st, int val, int val2, @@ -489,9 +991,43 @@ static int adxl345_find_range(struct adxl345_state *st, int val, int val2, static int adxl345_set_range(struct adxl345_state *st, enum adxl345_range range) { - return regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT, + unsigned int act_threshold, inact_threshold; + unsigned int range_old; + unsigned int regval; + int ret; + + ret = regmap_read(st->regmap, ADXL345_REG_DATA_FORMAT, ®val); + if (ret) + return ret; + range_old = FIELD_GET(ADXL345_DATA_FORMAT_RANGE, regval); + + ret = regmap_read(st->regmap, + adxl345_act_thresh_reg[ADXL345_ACTIVITY], + &act_threshold); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, ADXL345_REG_DATA_FORMAT, ADXL345_DATA_FORMAT_RANGE, FIELD_PREP(ADXL345_DATA_FORMAT_RANGE, range)); + if (ret) + return ret; + + act_threshold = act_threshold * adxl345_range_factor_tbl[range_old] + / adxl345_range_factor_tbl[range]; + act_threshold = min(U8_MAX, max(1, act_threshold)); + + inact_threshold = st->inact_threshold; + inact_threshold = inact_threshold * adxl345_range_factor_tbl[range_old] + / adxl345_range_factor_tbl[range]; + inact_threshold = min(U8_MAX, max(1, inact_threshold)); + + ret = regmap_write(st->regmap, adxl345_act_thresh_reg[ADXL345_ACTIVITY], + act_threshold); + if (ret) + return ret; + + return adxl345_set_inact_threshold(st, inact_threshold); } static int adxl345_read_avail(struct iio_dev *indio_dev, @@ -624,6 +1160,37 @@ static int adxl345_write_raw(struct iio_dev *indio_dev, return adxl345_set_measure_en(st, true); } +static int adxl345_read_mag_config(struct adxl345_state *st, + enum iio_event_direction dir, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return !!adxl345_is_act_inact_en(st, type_act); + case IIO_EV_DIR_FALLING: + return !!adxl345_is_act_inact_en(st, type_inact); + default: + return -EINVAL; + } +} + +static int adxl345_write_mag_config(struct adxl345_state *st, + enum iio_event_direction dir, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact, + bool state) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return adxl345_set_act_inact_en(st, type_act, state); + case IIO_EV_DIR_FALLING: + return adxl345_set_act_inact_en(st, type_inact, state); + default: + return -EINVAL; + } +} + static int adxl345_read_event_config(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, @@ -634,6 +1201,14 @@ static int adxl345_read_event_config(struct iio_dev *indio_dev, int ret; switch (type) { + case IIO_EV_TYPE_MAG: + return adxl345_read_mag_config(st, dir, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl345_read_mag_config(st, dir, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC); case IIO_EV_TYPE_GESTURE: switch (dir) { case IIO_EV_DIR_SINGLETAP: @@ -665,6 +1240,16 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev, struct adxl345_state *st = iio_priv(indio_dev); switch (type) { + case IIO_EV_TYPE_MAG: + return adxl345_write_mag_config(st, dir, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + state); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl345_write_mag_config(st, dir, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + state); case IIO_EV_TYPE_GESTURE: switch (dir) { case IIO_EV_DIR_SINGLETAP: @@ -679,6 +1264,72 @@ static int adxl345_write_event_config(struct iio_dev *indio_dev, } } +static int adxl345_read_mag_value(struct adxl345_state *st, + enum iio_event_direction dir, + enum iio_event_info info, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact, + int *val, int *val2) +{ + unsigned int threshold; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_RISING: + ret = regmap_read(st->regmap, + adxl345_act_thresh_reg[type_act], + &threshold); + if (ret) + return ret; + *val = 62500 * threshold; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + case IIO_EV_DIR_FALLING: + *val = 62500 * st->inact_threshold; + *val2 = MICRO; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + *val = st->inact_time_ms; + *val2 = MILLI; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +static int adxl345_write_mag_value(struct adxl345_state *st, + enum iio_event_direction dir, + enum iio_event_info info, + enum adxl345_activity_type type_act, + enum adxl345_activity_type type_inact, + int val, int val2) +{ + switch (info) { + case IIO_EV_INFO_VALUE: + /* Scaling factor 62.5mg/LSB, i.e. ~16g corresponds to 0xff */ + val = DIV_ROUND_CLOSEST(val * MICRO + val2, 62500); + switch (dir) { + case IIO_EV_DIR_RISING: + return regmap_write(st->regmap, + adxl345_act_thresh_reg[type_act], + val); + case IIO_EV_DIR_FALLING: + return adxl345_set_inact_threshold(st, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_PERIOD: + return adxl345_set_inact_time(st, val, val2); + default: + return -EINVAL; + } +} + static int adxl345_read_event_value(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, @@ -691,6 +1342,16 @@ static int adxl345_read_event_value(struct iio_dev *indio_dev, int ret; switch (type) { + case IIO_EV_TYPE_MAG: + return adxl345_read_mag_value(st, dir, info, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + val, val2); + case IIO_EV_TYPE_MAG_ADAPTIVE: + return adxl345_read_mag_value(st, dir, info, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + val, val2); case IIO_EV_TYPE_GESTURE: switch (info) { case IIO_EV_INFO_VALUE: @@ -741,6 +1402,22 @@ static int adxl345_write_event_value(struct iio_dev *indio_dev, return ret; switch (type) { + case IIO_EV_TYPE_MAG: + ret = adxl345_write_mag_value(st, dir, info, + ADXL345_ACTIVITY, + ADXL345_INACTIVITY, + val, val2); + if (ret) + return ret; + break; + case IIO_EV_TYPE_MAG_ADAPTIVE: + ret = adxl345_write_mag_value(st, dir, info, + ADXL345_ACTIVITY_AC, + ADXL345_INACTIVITY_AC, + val, val2); + if (ret) + return ret; + break; case IIO_EV_TYPE_GESTURE: switch (info) { case IIO_EV_INFO_VALUE: @@ -980,10 +1657,12 @@ static int adxl345_fifo_push(struct iio_dev *indio_dev, } static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat, + enum iio_modifier act_dir, enum iio_modifier tap_dir) { s64 ts = iio_get_time_ns(indio_dev); struct adxl345_state *st = iio_priv(indio_dev); + unsigned int regval; int samples; int ret = -ENOENT; @@ -1007,6 +1686,68 @@ static int adxl345_push_event(struct iio_dev *indio_dev, int int_stat, return ret; } + if (FIELD_GET(ADXL345_INT_ACTIVITY, int_stat)) { + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val); + if (ret) + return ret; + + if (FIELD_GET(ADXL345_REG_ACT_ACDC, regval)) { + /* AC coupled */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_RISING), + ts); + + } else { + /* DC coupled, relying on THRESH */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, act_dir, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + ts); + } + if (ret) + return ret; + } + + if (FIELD_GET(ADXL345_INT_INACTIVITY, int_stat)) { + ret = regmap_read(st->regmap, ADXL345_REG_ACT_INACT_CTRL, ®val); + if (ret) + return ret; + + if (FIELD_GET(ADXL345_REG_INACT_ACDC, regval)) { + /* AC coupled */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_FALLING), + ts); + } else { + /* DC coupled, relying on THRESH */ + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + ts); + } + if (ret) + return ret; + } + + if (FIELD_GET(ADXL345_INT_FREE_FALL, int_stat)) { + ret = iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + ts); + if (ret) + return ret; + } + if (FIELD_GET(ADXL345_INT_WATERMARK, int_stat)) { samples = adxl345_get_samples(st); if (samples < 0) @@ -1034,6 +1775,7 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p) struct adxl345_state *st = iio_priv(indio_dev); unsigned int regval; enum iio_modifier tap_dir = IIO_NO_MOD; + enum iio_modifier act_dir = IIO_NO_MOD; u32 axis_ctrl; int int_stat; int ret; @@ -1042,7 +1784,8 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p) if (ret) return IRQ_NONE; - if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl)) { + if (FIELD_GET(ADXL345_REG_TAP_AXIS_MSK, axis_ctrl) || + FIELD_GET(ADXL345_ACT_XYZ_EN, axis_ctrl)) { ret = regmap_read(st->regmap, ADXL345_REG_ACT_TAP_STATUS, ®val); if (ret) return IRQ_NONE; @@ -1053,12 +1796,19 @@ static irqreturn_t adxl345_irq_handler(int irq, void *p) tap_dir = IIO_MOD_Y; else if (FIELD_GET(ADXL345_TAP_X_EN, regval)) tap_dir = IIO_MOD_X; + + if (FIELD_GET(ADXL345_ACT_Z_EN, regval)) + act_dir = IIO_MOD_Z; + else if (FIELD_GET(ADXL345_ACT_Y_EN, regval)) + act_dir = IIO_MOD_Y; + else if (FIELD_GET(ADXL345_ACT_X_EN, regval)) + act_dir = IIO_MOD_X; } if (regmap_read(st->regmap, ADXL345_REG_INT_SOURCE, &int_stat)) return IRQ_NONE; - if (adxl345_push_event(indio_dev, int_stat, tap_dir)) + if (adxl345_push_event(indio_dev, int_stat, act_dir, tap_dir)) goto err; if (FIELD_GET(ADXL345_INT_OVERRUN, int_stat)) @@ -1226,6 +1976,24 @@ int adxl345_core_probe(struct device *dev, struct regmap *regmap, if (ret) return ret; + /* + * Initialized with sensible default values to streamline + * sensor operation. These defaults are partly derived from + * the previous input driver for the ADXL345 and partly + * based on the recommendations provided in the datasheet. + */ + ret = regmap_write(st->regmap, ADXL345_REG_ACT_INACT_CTRL, 0); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_ACT, 6); + if (ret) + return ret; + + ret = adxl345_set_inact_threshold(st, 4); + if (ret) + return ret; + ret = regmap_write(st->regmap, ADXL345_REG_THRESH_TAP, tap_threshold); if (ret) return ret; diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c index 4fccbcb76e04..8925f5279e62 100644 --- a/drivers/iio/accel/bma180.c +++ b/drivers/iio/accel/bma180.c @@ -139,11 +139,6 @@ struct bma180_data { int scale; int bw; bool pmode; - /* Ensure timestamp is naturally aligned */ - struct { - s16 chan[4]; - aligned_s64 timestamp; - } scan; }; enum bma180_chan { @@ -870,6 +865,10 @@ static irqreturn_t bma180_trigger_handler(int irq, void *p) struct bma180_data *data = iio_priv(indio_dev); s64 time_ns = iio_get_time_ns(indio_dev); int bit, ret, i = 0; + struct { + s16 chan[4]; + aligned_s64 timestamp; + } scan = { }; mutex_lock(&data->mutex); @@ -879,12 +878,12 @@ static irqreturn_t bma180_trigger_handler(int irq, void *p) mutex_unlock(&data->mutex); goto err; } - data->scan.chan[i++] = ret; + scan.chan[i++] = ret; } mutex_unlock(&data->mutex); - iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), time_ns); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); err: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index 38f7498431ee..01592eebf05b 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -255,10 +255,8 @@ static int bma220_probe(struct spi_device *spi) struct bma220_data *data; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&spi->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->spi_device = spi; diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c index be5fbb0c5d29..3c5d1560b163 100644 --- a/drivers/iio/accel/bmc150-accel-core.c +++ b/drivers/iio/accel/bmc150-accel-core.c @@ -332,13 +332,10 @@ static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on) struct device *dev = regmap_get_device(data->regmap); int ret; - if (on) { + if (on) ret = pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); + else ret = pm_runtime_put_autosuspend(dev); - } - if (ret < 0) { dev_err(dev, "Failed: %s for %d\n", __func__, on); diff --git a/drivers/iio/accel/bmi088-accel-core.c b/drivers/iio/accel/bmi088-accel-core.c index dea126f993c1..c7da90af0d2d 100644 --- a/drivers/iio/accel/bmi088-accel-core.c +++ b/drivers/iio/accel/bmi088-accel-core.c @@ -375,7 +375,6 @@ static int bmi088_accel_read_raw(struct iio_dev *indio_dev, return -EINVAL; out_read_raw_pm_put: - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -419,7 +418,6 @@ static int bmi088_accel_write_raw(struct iio_dev *indio_dev, return ret; ret = bmi088_accel_set_scale(data, val, val2); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; case IIO_CHAN_INFO_SAMP_FREQ: @@ -428,7 +426,6 @@ static int bmi088_accel_write_raw(struct iio_dev *indio_dev, return ret; ret = bmi088_accel_set_sample_freq(data, val); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; default: diff --git a/drivers/iio/accel/dmard06.c b/drivers/iio/accel/dmard06.c index fb14894c66f9..33f225d73e7b 100644 --- a/drivers/iio/accel/dmard06.c +++ b/drivers/iio/accel/dmard06.c @@ -137,10 +137,8 @@ static int dmard06_probe(struct i2c_client *client) } indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dmard06)); - if (!indio_dev) { - dev_err(&client->dev, "Failed to allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } dmard06 = iio_priv(indio_dev); dmard06->client = client; diff --git a/drivers/iio/accel/dmard09.c b/drivers/iio/accel/dmard09.c index 4ec70ca6910d..d9290e3b9c46 100644 --- a/drivers/iio/accel/dmard09.c +++ b/drivers/iio/accel/dmard09.c @@ -95,10 +95,8 @@ static int dmard09_probe(struct i2c_client *client) struct dmard09_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/accel/dmard10.c b/drivers/iio/accel/dmard10.c index 71cd1928baa6..575e8510e1bd 100644 --- a/drivers/iio/accel/dmard10.c +++ b/drivers/iio/accel/dmard10.c @@ -191,10 +191,8 @@ static int dmard10_probe(struct i2c_client *client) return (ret < 0) ? ret : -ENODEV; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/accel/fxls8962af-core.c b/drivers/iio/accel/fxls8962af-core.c index b10a30960e1e..8763e91c63d2 100644 --- a/drivers/iio/accel/fxls8962af-core.c +++ b/drivers/iio/accel/fxls8962af-core.c @@ -222,7 +222,6 @@ static int fxls8962af_power_off(struct fxls8962af_data *data) struct device *dev = regmap_get_device(data->regmap); int ret; - pm_runtime_mark_last_busy(dev); ret = pm_runtime_put_autosuspend(dev); if (ret) dev_err(dev, "failed to power off\n"); diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c index 6aefe8221296..2823ddde4bf2 100644 --- a/drivers/iio/accel/kxcjk-1013.c +++ b/drivers/iio/accel/kxcjk-1013.c @@ -636,10 +636,8 @@ static int kxcjk1013_set_power_state(struct kxcjk1013_data *data, bool on) if (on) ret = pm_runtime_resume_and_get(&data->client->dev); - else { - pm_runtime_mark_last_busy(&data->client->dev); + else ret = pm_runtime_put_autosuspend(&data->client->dev); - } if (ret < 0) { dev_err(&data->client->dev, "Failed: %s for %d\n", __func__, on); diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c index cfc31265cdd0..4717d80fc24a 100644 --- a/drivers/iio/accel/kxsd9.c +++ b/drivers/iio/accel/kxsd9.c @@ -151,7 +151,6 @@ static int kxsd9_write_raw(struct iio_dev *indio_dev, ret = kxsd9_write_scale(indio_dev, val2); } - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return ret; @@ -199,7 +198,6 @@ static int kxsd9_read_raw(struct iio_dev *indio_dev, } error_ret: - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return ret; @@ -250,7 +248,6 @@ static int kxsd9_buffer_postdisable(struct iio_dev *indio_dev) { struct kxsd9_state *st = iio_priv(indio_dev); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return 0; diff --git a/drivers/iio/accel/mc3230.c b/drivers/iio/accel/mc3230.c index e2853090fa6e..3e494f9ddc56 100644 --- a/drivers/iio/accel/mc3230.c +++ b/drivers/iio/accel/mc3230.c @@ -169,10 +169,8 @@ static int mc3230_probe(struct i2c_client *client) } indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->chip_info = chip_info; diff --git a/drivers/iio/accel/mma7660.c b/drivers/iio/accel/mma7660.c index d0a16f227903..be3213600cf4 100644 --- a/drivers/iio/accel/mma7660.c +++ b/drivers/iio/accel/mma7660.c @@ -192,10 +192,8 @@ static int mma7660_probe(struct i2c_client *client) struct mma7660_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index aba444a980d9..15172ba2972c 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -224,13 +224,10 @@ static int mma8452_set_runtime_pm_state(struct i2c_client *client, bool on) #ifdef CONFIG_PM int ret; - if (on) { + if (on) ret = pm_runtime_resume_and_get(&client->dev); - } else { - pm_runtime_mark_last_busy(&client->dev); + else ret = pm_runtime_put_autosuspend(&client->dev); - } - if (ret < 0) { dev_err(&client->dev, "failed to change power state to %d\n", on); diff --git a/drivers/iio/accel/mma9551_core.c b/drivers/iio/accel/mma9551_core.c index 3e7d9b79ed0e..2ccb1fb19b96 100644 --- a/drivers/iio/accel/mma9551_core.c +++ b/drivers/iio/accel/mma9551_core.c @@ -671,11 +671,8 @@ int mma9551_set_power_state(struct i2c_client *client, bool on) if (on) ret = pm_runtime_resume_and_get(&client->dev); - else { - pm_runtime_mark_last_busy(&client->dev); + else ret = pm_runtime_put_autosuspend(&client->dev); - } - if (ret < 0) { dev_err(&client->dev, "failed to change power state to %d\n", on); diff --git a/drivers/iio/accel/msa311.c b/drivers/iio/accel/msa311.c index 3e10225410e8..5eace0de3750 100644 --- a/drivers/iio/accel/msa311.c +++ b/drivers/iio/accel/msa311.c @@ -607,7 +607,6 @@ static int msa311_read_raw_data(struct iio_dev *indio_dev, err = msa311_get_axis(msa311, chan, &axis); mutex_unlock(&msa311->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); iio_device_release_direct(indio_dev); @@ -741,7 +740,6 @@ static int msa311_write_scale(struct iio_dev *indio_dev, int val, int val2) break; } - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); if (err) @@ -781,7 +779,6 @@ static int msa311_write_samp_freq(struct iio_dev *indio_dev, int val, int val2) break; } - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); iio_device_release_direct(indio_dev); @@ -832,7 +829,6 @@ static int msa311_debugfs_reg_access(struct iio_dev *indio_dev, mutex_unlock(&msa311->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); if (err) @@ -855,7 +851,6 @@ static int msa311_buffer_postdisable(struct iio_dev *indio_dev) struct msa311_priv *msa311 = iio_priv(indio_dev); struct device *dev = msa311->dev; - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; @@ -990,7 +985,7 @@ static int msa311_check_partid(struct msa311_priv *msa311) msa311->chip_name = devm_kasprintf(dev, GFP_KERNEL, "msa311-%02x", partid); if (!msa311->chip_name) - return dev_err_probe(dev, -ENOMEM, "can't alloc chip name\n"); + return -ENOMEM; return 0; } @@ -1069,8 +1064,7 @@ static int msa311_setup_interrupts(struct msa311_priv *msa311) trig = devm_iio_trigger_alloc(dev, "%s-new-data", msa311->chip_name); if (!trig) - return dev_err_probe(dev, -ENOMEM, - "can't allocate newdata trigger\n"); + return -ENOMEM; msa311->new_data_trig = trig; msa311->new_data_trig->ops = &msa311_new_data_trig_ops; @@ -1153,8 +1147,7 @@ static int msa311_probe(struct i2c_client *i2c) indio_dev = devm_iio_device_alloc(dev, sizeof(*msa311)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "IIO device allocation failed\n"); + return -ENOMEM; msa311 = iio_priv(indio_dev); msa311->dev = dev; @@ -1195,7 +1188,7 @@ static int msa311_probe(struct i2c_client *i2c) */ err = devm_add_action_or_reset(dev, msa311_powerdown, msa311); if (err) - return dev_err_probe(dev, err, "can't add powerdown action\n"); + return err; err = pm_runtime_set_active(dev); if (err) @@ -1231,7 +1224,6 @@ static int msa311_probe(struct i2c_client *i2c) if (err) return err; - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); err = devm_iio_device_register(dev, indio_dev); diff --git a/drivers/iio/accel/stk8312.c b/drivers/iio/accel/stk8312.c index 89569ce221d7..f31c6ab3392d 100644 --- a/drivers/iio/accel/stk8312.c +++ b/drivers/iio/accel/stk8312.c @@ -504,10 +504,8 @@ static int stk8312_probe(struct i2c_client *client) struct stk8312_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/accel/stk8ba50.c b/drivers/iio/accel/stk8ba50.c index c1d7e7dcb09b..384f1fbcbcb3 100644 --- a/drivers/iio/accel/stk8ba50.c +++ b/drivers/iio/accel/stk8ba50.c @@ -385,10 +385,8 @@ static int stk8ba50_probe(struct i2c_client *client) struct stk8ba50_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/adc/88pm886-gpadc.c b/drivers/iio/adc/88pm886-gpadc.c new file mode 100644 index 000000000000..cffe35136685 --- /dev/null +++ b/drivers/iio/adc/88pm886-gpadc.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2025, Duje Mihanović <duje@dujemihanovic.xyz> + */ + +#include <linux/bits.h> +#include <linux/bug.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/math.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <asm/byteorder.h> + +#include <linux/iio/iio.h> +#include <linux/iio/types.h> + +#include <linux/mfd/88pm886.h> + +struct pm886_gpadc { + struct regmap *map; +}; + +enum pm886_gpadc_channel { + VSC_CHAN, + VCHG_PWR_CHAN, + VCF_OUT_CHAN, + VBAT_CHAN, + VBAT_SLP_CHAN, + VBUS_CHAN, + + GPADC0_CHAN, + GPADC1_CHAN, + GPADC2_CHAN, + GPADC3_CHAN, + + GND_DET1_CHAN, + GND_DET2_CHAN, + MIC_DET_CHAN, + + TINT_CHAN, +}; + +static const int pm886_gpadc_regs[] = { + [VSC_CHAN] = PM886_REG_GPADC_VSC, + [VCHG_PWR_CHAN] = PM886_REG_GPADC_VCHG_PWR, + [VCF_OUT_CHAN] = PM886_REG_GPADC_VCF_OUT, + [VBAT_CHAN] = PM886_REG_GPADC_VBAT, + [VBAT_SLP_CHAN] = PM886_REG_GPADC_VBAT_SLP, + [VBUS_CHAN] = PM886_REG_GPADC_VBUS, + + [GPADC0_CHAN] = PM886_REG_GPADC_GPADC0, + [GPADC1_CHAN] = PM886_REG_GPADC_GPADC1, + [GPADC2_CHAN] = PM886_REG_GPADC_GPADC2, + [GPADC3_CHAN] = PM886_REG_GPADC_GPADC3, + + [GND_DET1_CHAN] = PM886_REG_GPADC_GND_DET1, + [GND_DET2_CHAN] = PM886_REG_GPADC_GND_DET2, + [MIC_DET_CHAN] = PM886_REG_GPADC_MIC_DET, + + [TINT_CHAN] = PM886_REG_GPADC_TINT, +}; + +#define ADC_CHANNEL_VOLTAGE(index, lsb, name) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .address = lsb, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = name, \ +} + +#define ADC_CHANNEL_RESISTANCE(index, lsb, name) \ +{ \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .channel = index, \ + .address = lsb, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .datasheet_name = name, \ +} + +#define ADC_CHANNEL_TEMPERATURE(index, lsb, name) \ +{ \ + .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = index, \ + .address = lsb, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .datasheet_name = name, \ +} + +static const struct iio_chan_spec pm886_gpadc_channels[] = { + ADC_CHANNEL_VOLTAGE(VSC_CHAN, 1367, "vsc"), + ADC_CHANNEL_VOLTAGE(VCHG_PWR_CHAN, 1709, "vchg_pwr"), + ADC_CHANNEL_VOLTAGE(VCF_OUT_CHAN, 1367, "vcf_out"), + ADC_CHANNEL_VOLTAGE(VBAT_CHAN, 1367, "vbat"), + ADC_CHANNEL_VOLTAGE(VBAT_SLP_CHAN, 1367, "vbat_slp"), + ADC_CHANNEL_VOLTAGE(VBUS_CHAN, 1709, "vbus"), + + ADC_CHANNEL_RESISTANCE(GPADC0_CHAN, 342, "gpadc0"), + ADC_CHANNEL_RESISTANCE(GPADC1_CHAN, 342, "gpadc1"), + ADC_CHANNEL_RESISTANCE(GPADC2_CHAN, 342, "gpadc2"), + ADC_CHANNEL_RESISTANCE(GPADC3_CHAN, 342, "gpadc3"), + + ADC_CHANNEL_VOLTAGE(GND_DET1_CHAN, 342, "gnddet1"), + ADC_CHANNEL_VOLTAGE(GND_DET2_CHAN, 342, "gnddet2"), + ADC_CHANNEL_VOLTAGE(MIC_DET_CHAN, 1367, "mic_det"), + + ADC_CHANNEL_TEMPERATURE(TINT_CHAN, 104, "tint"), +}; + +static const struct regmap_config pm886_gpadc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = PM886_GPADC_MAX_REGISTER, +}; + +static int gpadc_get_raw(struct iio_dev *iio, enum pm886_gpadc_channel chan) +{ + struct pm886_gpadc *gpadc = iio_priv(iio); + __be16 buf; + int ret; + + ret = regmap_bulk_read(gpadc->map, pm886_gpadc_regs[chan], &buf, sizeof(buf)); + if (ret) + return ret; + + return be16_to_cpu(buf) >> 4; +} + +static int +gpadc_set_bias(struct pm886_gpadc *gpadc, enum pm886_gpadc_channel chan, bool on) +{ + unsigned int gpadc_num = chan - GPADC0_CHAN; + unsigned int bits = BIT(gpadc_num + 4) | BIT(gpadc_num); + + return regmap_assign_bits(gpadc->map, PM886_REG_GPADC_CONFIG(0x14), bits, on); +} + +static int +gpadc_find_bias_current(struct iio_dev *iio, struct iio_chan_spec const *chan, + unsigned int *raw_uV, unsigned int *raw_uA) +{ + struct pm886_gpadc *gpadc = iio_priv(iio); + unsigned int gpadc_num = chan->channel - GPADC0_CHAN; + unsigned int reg = PM886_REG_GPADC_CONFIG(0xb + gpadc_num); + unsigned long lsb = chan->address; + int ret; + + for (unsigned int i = 0; i < PM886_GPADC_BIAS_LEVELS; i++) { + ret = regmap_update_bits(gpadc->map, reg, GENMASK(3, 0), i); + if (ret) + return ret; + + /* Wait for the new bias level to apply. */ + fsleep(5 * USEC_PER_MSEC); + + *raw_uA = PM886_GPADC_INDEX_TO_BIAS_uA(i); + *raw_uV = gpadc_get_raw(iio, chan->channel) * lsb; + + /* + * Vendor kernel errors out above 1.25 V, but testing shows + * that the resistance of the battery detection channel (GPADC2 + * on coreprimevelte) reaches about 1.4 MΩ when the battery is + * removed, which can't be measured with such a low upper + * limit. Therefore, to be able to detect the battery without + * ugly externs as used in the vendor fuel gauge driver, + * increase this limit a bit. + */ + if (WARN_ON(*raw_uV > 1500 * (MICRO / MILLI))) + return -EIO; + + /* + * Vendor kernel errors out under 300 mV, but for the same + * reason as above (except the channel hovers around 3.5 kΩ + * with battery present) reduce this limit. + */ + if (*raw_uV < 200 * (MICRO / MILLI)) { + dev_dbg(&iio->dev, "bad bias for chan %d: %d uA @ %d uV\n", + chan->channel, *raw_uA, *raw_uV); + continue; + } + + dev_dbg(&iio->dev, "good bias for chan %d: %d uA @ %d uV\n", + chan->channel, *raw_uA, *raw_uV); + return 0; + } + + dev_err(&iio->dev, "failed to find good bias for chan %d\n", chan->channel); + return -EINVAL; +} + +static int +gpadc_get_resistance_ohm(struct iio_dev *iio, struct iio_chan_spec const *chan) +{ + struct pm886_gpadc *gpadc = iio_priv(iio); + unsigned int raw_uV, raw_uA; + int ret; + + ret = gpadc_set_bias(gpadc, chan->channel, true); + if (ret) + goto out; + + ret = gpadc_find_bias_current(iio, chan, &raw_uV, &raw_uA); + if (ret) + goto out; + + ret = DIV_ROUND_CLOSEST(raw_uV, raw_uA); +out: + gpadc_set_bias(gpadc, chan->channel, false); + return ret; +} + +static int +__pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + unsigned long lsb = chan->address; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = gpadc_get_raw(iio, chan->channel); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = lsb; + + if (chan->type == IIO_VOLTAGE) { + *val2 = MILLI; + return IIO_VAL_FRACTIONAL; + } else { + return IIO_VAL_INT; + } + case IIO_CHAN_INFO_OFFSET: + /* Raw value is 104 millikelvin/LSB, convert it to 104 millicelsius/LSB */ + *val = ABSOLUTE_ZERO_MILLICELSIUS; + *val2 = lsb; + return IIO_VAL_FRACTIONAL; + case IIO_CHAN_INFO_PROCESSED: + *val = gpadc_get_resistance_ohm(iio, chan); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int pm886_gpadc_read_raw(struct iio_dev *iio, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct device *dev = iio->dev.parent; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = __pm886_gpadc_read_raw(iio, chan, val, val2, mask); + + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int pm886_gpadc_hw_enable(struct regmap *map) +{ + const u8 config[] = { + PM886_GPADC_CONFIG1_EN_ALL, + PM886_GPADC_CONFIG2_EN_ALL, + PM886_GPADC_GND_DET2_EN, + }; + int ret; + + /* Enable the ADC block. */ + ret = regmap_set_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0)); + if (ret) + return ret; + + /* Enable all channels. */ + return regmap_bulk_write(map, PM886_REG_GPADC_CONFIG(0x1), config, ARRAY_SIZE(config)); +} + +static int pm886_gpadc_hw_disable(struct regmap *map) +{ + return regmap_clear_bits(map, PM886_REG_GPADC_CONFIG(0x6), BIT(0)); +} + +static const struct iio_info pm886_gpadc_iio_info = { + .read_raw = pm886_gpadc_read_raw, +}; + +static int pm886_gpadc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pm886_chip *chip = dev_get_drvdata(dev->parent); + struct i2c_client *client = chip->client; + struct pm886_gpadc *gpadc; + struct i2c_client *page; + struct iio_dev *iio; + int ret; + + iio = devm_iio_device_alloc(dev, sizeof(*gpadc)); + if (!iio) + return -ENOMEM; + + gpadc = iio_priv(iio); + dev_set_drvdata(dev, iio); + + page = devm_i2c_new_dummy_device(dev, client->adapter, + client->addr + PM886_PAGE_OFFSET_GPADC); + if (IS_ERR(page)) + return dev_err_probe(dev, PTR_ERR(page), "Failed to initialize GPADC page\n"); + + gpadc->map = devm_regmap_init_i2c(page, &pm886_gpadc_regmap_config); + if (IS_ERR(gpadc->map)) + return dev_err_probe(dev, PTR_ERR(gpadc->map), + "Failed to initialize GPADC regmap\n"); + + iio->name = "88pm886-gpadc"; + iio->modes = INDIO_DIRECT_MODE; + iio->info = &pm886_gpadc_iio_info; + iio->channels = pm886_gpadc_channels; + iio->num_channels = ARRAY_SIZE(pm886_gpadc_channels); + device_set_node(&iio->dev, dev_fwnode(dev->parent)); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + pm_runtime_set_autosuspend_delay(dev, 50); + pm_runtime_use_autosuspend(dev); + ret = devm_iio_device_register(dev, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to register ADC\n"); + + return 0; +} + +static int pm886_gpadc_runtime_resume(struct device *dev) +{ + struct iio_dev *iio = dev_get_drvdata(dev); + struct pm886_gpadc *gpadc = iio_priv(iio); + + return pm886_gpadc_hw_enable(gpadc->map); +} + +static int pm886_gpadc_runtime_suspend(struct device *dev) +{ + struct iio_dev *iio = dev_get_drvdata(dev); + struct pm886_gpadc *gpadc = iio_priv(iio); + + return pm886_gpadc_hw_disable(gpadc->map); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(pm886_gpadc_pm_ops, + pm886_gpadc_runtime_suspend, + pm886_gpadc_runtime_resume, NULL); + +static const struct platform_device_id pm886_gpadc_id[] = { + { "88pm886-gpadc" }, + { } +}; +MODULE_DEVICE_TABLE(platform, pm886_gpadc_id); + +static struct platform_driver pm886_gpadc_driver = { + .driver = { + .name = "88pm886-gpadc", + .pm = pm_ptr(&pm886_gpadc_pm_ops), + }, + .probe = pm886_gpadc_probe, + .id_table = pm886_gpadc_id, +}; +module_platform_driver(pm886_gpadc_driver); + +MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>"); +MODULE_DESCRIPTION("Marvell 88PM886 GPADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 24f2572c487e..58a14e6833f6 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -9,6 +9,19 @@ menu "Analog to digital converters" config IIO_ADC_HELPER tristate +config 88PM886_GPADC + tristate "Marvell 88PM886 GPADC driver" + depends on MFD_88PM886_PMIC + default MFD_88PM886_PMIC + help + Say Y here to enable support for the GPADC (General Purpose ADC) + found on the Marvell 88PM886 PMIC. The GPADC measures various + internal voltages and temperatures, including (but not limited to) + system, battery and USB Vbus. + + To compile this driver as a module, choose M here: the module will be + called 88pm886-gpadc. + config AB8500_GPADC bool "ST-Ericsson AB8500 GPADC driver" depends on AB8500_CORE && REGULATOR_AB8500 @@ -389,6 +402,7 @@ config AD7779 depends on SPI select CRC8 select IIO_BUFFER + select IIO_BACKEND help Say yes here to build support for Analog Devices AD777X family (AD7770, AD7771, AD7779) analog to digital converter (ADC). @@ -507,6 +521,25 @@ config AD9467 To compile this driver as a module, choose M here: the module will be called ad9467. +config ADE9000 + tristate "Analog Devices ADE9000 Multiphase Energy, and Power Quality Monitoring IC Driver" + depends on SPI + select REGMAP_SPI + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for the Analog Devices ADE9000, + a highly accurate, multiphase energy and power quality monitoring + integrated circuit. + + The device features high-precision analog-to-digital converters + and digital signal processing to compute RMS values, power factor, + frequency, and harmonic analysis. It supports SPI communication + and provides buffered data output through the IIO framework. + + To compile this driver as a module, choose M here: the module will + be called ade9000. + config ADI_AXI_ADC tristate "Analog Devices Generic AXI ADC IP core driver" depends on MICROBLAZE || NIOS2 || ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || COMPILE_TEST @@ -766,6 +799,17 @@ config INGENIC_ADC This driver can also be built as a module. If so, the module will be called ingenic_adc. +config INTEL_DC_TI_ADC + tristate "Intel Bay Trail / Cherry Trail Dollar Cove TI ADC driver" + depends on INTEL_SOC_PMIC_CHTDC_TI + help + Say yes here to have support for the Dollar Cove TI PMIC ADC device. + Depending on platform configuration, this general purpose ADC can be + used for sensors such as battery voltage and thermal resistors. + + To compile this driver as a module, choose M here: the module will be + called intel_dc_ti_adc. + config INTEL_MRFLD_ADC tristate "Intel Merrifield Basin Cove ADC driver" depends on INTEL_SOC_PMIC_MRFLD @@ -1298,6 +1342,16 @@ config RN5T618_ADC This driver can also be built as a module. If so, the module will be called rn5t618-adc. +config ROHM_BD79112 + tristate "Rohm BD79112 ADC driver" + depends on SPI && GPIOLIB + select REGMAP_SPI + select IIO_ADC_HELPER + help + Say yes here to build support for the ROHM BD79112 ADC. The + ROHM BD79112 is a 12-bit, 32-channel, SAR ADC. Analog inputs + can also be used for GPIO. + config ROHM_BD79124 tristate "Rohm BD79124 ADC driver" depends on I2C && GPIOLIB diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 1c6ca5fd4b6d..d008f78dc010 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_IIO_ADC_HELPER) += industrialio-adc.o # When adding new entries keep the list in alphabetical order +obj-$(CONFIG_88PM886_GPADC) += 88pm886-gpadc.o obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o obj-$(CONFIG_AD4000) += ad4000.o @@ -46,6 +47,7 @@ obj-$(CONFIG_AD7944) += ad7944.o obj-$(CONFIG_AD7949) += ad7949.o obj-$(CONFIG_AD799X) += ad799x.o obj-$(CONFIG_AD9467) += ad9467.o +obj-$(CONFIG_ADE9000) += ade9000.o obj-$(CONFIG_ADI_AXI_ADC) += adi-axi-adc.o obj-$(CONFIG_ASPEED_ADC) += aspeed_adc.o obj-$(CONFIG_AT91_ADC) += at91_adc.o @@ -70,6 +72,7 @@ obj-$(CONFIG_IMX8QXP_ADC) += imx8qxp-adc.o obj-$(CONFIG_IMX93_ADC) += imx93_adc.o obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o obj-$(CONFIG_INGENIC_ADC) += ingenic-adc.o +obj-$(CONFIG_INTEL_DC_TI_ADC) += intel_dc_ti_adc.o obj-$(CONFIG_INTEL_MRFLD_ADC) += intel_mrfld_adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o @@ -116,6 +119,7 @@ obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o obj-$(CONFIG_RCAR_GYRO_ADC) += rcar-gyroadc.o obj-$(CONFIG_RICHTEK_RTQ6056) += rtq6056.o obj-$(CONFIG_RN5T618_ADC) += rn5t618-adc.o +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 diff --git a/drivers/iio/adc/ab8500-gpadc.c b/drivers/iio/adc/ab8500-gpadc.c index f3b057f92310..8eaa1dd6a89b 100644 --- a/drivers/iio/adc/ab8500-gpadc.c +++ b/drivers/iio/adc/ab8500-gpadc.c @@ -607,7 +607,6 @@ static int ab8500_gpadc_read(struct ab8500_gpadc *gpadc, } /* This eventually drops the regulator */ - pm_runtime_mark_last_busy(gpadc->dev); pm_runtime_put_autosuspend(gpadc->dev); return (high_data << 8) | low_data; diff --git a/drivers/iio/adc/ad4130.c b/drivers/iio/adc/ad4130.c index dcdb5778f7d6..5567ae5dee88 100644 --- a/drivers/iio/adc/ad4130.c +++ b/drivers/iio/adc/ad4130.c @@ -2035,8 +2035,7 @@ static int ad4130_probe(struct spi_device *spi) ret = devm_add_action_or_reset(dev, ad4130_disable_regulators, st); if (ret) - return dev_err_probe(dev, ret, - "Failed to add regulators disable action\n"); + return ret; ret = ad4130_soft_reset(st); if (ret) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index 4d8c6bafd1c3..910b40393f77 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -3,21 +3,27 @@ * AD7124 SPI ADC driver * * Copyright 2018 Analog Devices Inc. + * Copyright 2025 BayLibre, SAS */ #include <linux/bitfield.h> #include <linux/bitops.h> +#include <linux/cleanup.h> #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/err.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/kfifo.h> +#include <linux/minmax.h> #include <linux/module.h> #include <linux/mod_devicetable.h> #include <linux/property.h> #include <linux/regulator/consumer.h> #include <linux/spi/spi.h> +#include <linux/sprintf.h> +#include <linux/units.h> #include <linux/iio/iio.h> #include <linux/iio/adc/ad_sigma_delta.h> @@ -44,6 +50,11 @@ #define AD7124_STATUS_POR_FLAG BIT(4) /* AD7124_ADC_CONTROL */ +#define AD7124_ADC_CONTROL_CLK_SEL GENMASK(1, 0) +#define AD7124_ADC_CONTROL_CLK_SEL_INT 0 +#define AD7124_ADC_CONTROL_CLK_SEL_INT_OUT 1 +#define AD7124_ADC_CONTROL_CLK_SEL_EXT 2 +#define AD7124_ADC_CONTROL_CLK_SEL_EXT_DIV4 3 #define AD7124_ADC_CONTROL_MODE GENMASK(5, 2) #define AD7124_ADC_CONTROL_MODE_CONTINUOUS 0 #define AD7124_ADC_CONTROL_MODE_SINGLE 1 @@ -84,14 +95,26 @@ #define AD7124_CONFIG_PGA GENMASK(2, 0) /* AD7124_FILTER_X */ -#define AD7124_FILTER_FS GENMASK(10, 0) #define AD7124_FILTER_FILTER GENMASK(23, 21) #define AD7124_FILTER_FILTER_SINC4 0 #define AD7124_FILTER_FILTER_SINC3 2 +#define AD7124_FILTER_FILTER_SINC4_SINC1 4 +#define AD7124_FILTER_FILTER_SINC3_SINC1 5 +#define AD7124_FILTER_FILTER_SINC3_PF 7 +#define AD7124_FILTER_REJ60 BIT(20) +#define AD7124_FILTER_POST_FILTER GENMASK(19, 17) +#define AD7124_FILTER_POST_FILTER_47dB 2 +#define AD7124_FILTER_POST_FILTER_62dB 3 +#define AD7124_FILTER_POST_FILTER_86dB 5 +#define AD7124_FILTER_POST_FILTER_92dB 6 +#define AD7124_FILTER_SINGLE_CYCLE BIT(16) +#define AD7124_FILTER_FS GENMASK(10, 0) #define AD7124_MAX_CONFIGS 8 #define AD7124_MAX_CHANNELS 16 +#define AD7124_INT_CLK_HZ 614400 + /* AD7124 input sources */ enum ad7124_ref_sel { @@ -120,9 +143,9 @@ static const unsigned int ad7124_reg_size[] = { }; static const int ad7124_master_clk_freq_hz[3] = { - [AD7124_LOW_POWER] = 76800, - [AD7124_MID_POWER] = 153600, - [AD7124_FULL_POWER] = 614400, + [AD7124_LOW_POWER] = AD7124_INT_CLK_HZ / 8, + [AD7124_MID_POWER] = AD7124_INT_CLK_HZ / 4, + [AD7124_FULL_POWER] = AD7124_INT_CLK_HZ, }; static const char * const ad7124_ref_names[] = { @@ -138,9 +161,24 @@ struct ad7124_chip_info { unsigned int num_inputs; }; +enum ad7124_filter_type { + AD7124_FILTER_TYPE_SINC3, + AD7124_FILTER_TYPE_SINC3_PF1, + AD7124_FILTER_TYPE_SINC3_PF2, + AD7124_FILTER_TYPE_SINC3_PF3, + AD7124_FILTER_TYPE_SINC3_PF4, + AD7124_FILTER_TYPE_SINC3_REJ60, + AD7124_FILTER_TYPE_SINC3_SINC1, + AD7124_FILTER_TYPE_SINC4, + AD7124_FILTER_TYPE_SINC4_REJ60, + AD7124_FILTER_TYPE_SINC4_SINC1, +}; + 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 @@ -153,9 +191,8 @@ struct ad7124_channel_config { bool buf_negative; unsigned int vref_mv; unsigned int pga_bits; - unsigned int odr; unsigned int odr_sel_bits; - unsigned int filter_type; + enum ad7124_filter_type filter_type; unsigned int calibration_offset; unsigned int calibration_gain; ); @@ -174,7 +211,7 @@ struct ad7124_state { struct ad_sigma_delta sd; struct ad7124_channel *channels; struct regulator *vref[4]; - struct clk *mclk; + u32 clk_hz; unsigned int adc_control; unsigned int num_channels; struct mutex cfgs_lock; /* lock for configs access */ @@ -250,44 +287,117 @@ static int ad7124_set_mode(struct ad_sigma_delta *sd, return ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, st->adc_control); } -static void ad7124_set_channel_odr(struct ad7124_state *st, unsigned int channel, unsigned int odr) +static u32 ad7124_get_fclk_hz(struct ad7124_state *st) +{ + enum ad7124_power_mode power_mode; + u32 fclk_hz; + + power_mode = FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE, st->adc_control); + fclk_hz = st->clk_hz; + + switch (power_mode) { + case AD7124_LOW_POWER: + fclk_hz /= 8; + break; + case AD7124_MID_POWER: + fclk_hz /= 4; + break; + default: + break; + } + + return fclk_hz; +} + +static u32 ad7124_get_fs_factor(struct ad7124_state *st, unsigned int channel) { - unsigned int fclk, odr_sel_bits; + enum ad7124_power_mode power_mode = + FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE, st->adc_control); + u32 avg = power_mode == AD7124_LOW_POWER ? 8 : 16; - fclk = clk_get_rate(st->mclk); /* - * FS[10:0] = fCLK / (fADC x 32) where: + * These are the "zero-latency" factors from the data sheet. For the + * sinc1 filters, these aren't documented, but derived by taking the + * single-channel formula from the sinc1 section of the data sheet and + * multiplying that by the sinc3/4 factor from the corresponding zero- + * latency sections. + */ + switch (st->channels[channel].cfg.filter_type) { + case AD7124_FILTER_TYPE_SINC4: + case AD7124_FILTER_TYPE_SINC4_REJ60: + return 4 * 32; + case AD7124_FILTER_TYPE_SINC4_SINC1: + return 4 * avg * 32; + case AD7124_FILTER_TYPE_SINC3_SINC1: + return 3 * avg * 32; + default: + return 3 * 32; + } +} + +static u32 ad7124_get_fadc_divisor(struct ad7124_state *st, unsigned int channel) +{ + u32 factor = ad7124_get_fs_factor(st, channel); + + /* + * The output data rate (f_ADC) is f_CLK / divisor. We are returning + * the divisor. + */ + return st->channels[channel].cfg.odr_sel_bits * factor; +} + +static void ad7124_set_channel_odr(struct ad7124_state *st, unsigned int channel) +{ + struct ad7124_channel_config *cfg = &st->channels[channel].cfg; + unsigned int fclk, factor, divisor, odr_sel_bits; + + fclk = ad7124_get_fclk_hz(st); + factor = ad7124_get_fs_factor(st, channel); + + /* + * FS[10:0] = fCLK / (fADC x 32 * N) where: * fADC is the output data rate * fCLK is the master clock frequency + * N is number of conversions per sample (depends on filter type) * FS[10:0] are the bits in the filter register * FS[10:0] can have a value from 1 to 2047 */ - odr_sel_bits = DIV_ROUND_CLOSEST(fclk, odr * 32); - if (odr_sel_bits < 1) - odr_sel_bits = 1; - else if (odr_sel_bits > 2047) - odr_sel_bits = 2047; + divisor = cfg->requested_odr * factor + + 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; - /* fADC = fCLK / (FS[10:0] x 32) */ - st->channels[channel].cfg.odr = DIV_ROUND_CLOSEST(fclk, odr_sel_bits * 32); st->channels[channel].cfg.odr_sel_bits = odr_sel_bits; } -static int ad7124_get_3db_filter_freq(struct ad7124_state *st, - unsigned int channel) +static int ad7124_get_3db_filter_factor(struct ad7124_state *st, + unsigned int channel) { - unsigned int fadc; + struct ad7124_channel_config *cfg = &st->channels[channel].cfg; - fadc = st->channels[channel].cfg.odr; - - switch (st->channels[channel].cfg.filter_type) { - case AD7124_FILTER_FILTER_SINC3: - return DIV_ROUND_CLOSEST(fadc * 272, 1000); - case AD7124_FILTER_FILTER_SINC4: - return DIV_ROUND_CLOSEST(fadc * 230, 1000); + /* + * 3dB point is the f_CLK rate times some factor. This functions returns + * the factor times 1000. + */ + switch (cfg->filter_type) { + case AD7124_FILTER_TYPE_SINC3: + case AD7124_FILTER_TYPE_SINC3_REJ60: + case AD7124_FILTER_TYPE_SINC3_SINC1: + return 272; + case AD7124_FILTER_TYPE_SINC4: + case AD7124_FILTER_TYPE_SINC4_REJ60: + case AD7124_FILTER_TYPE_SINC4_SINC1: + return 230; + case AD7124_FILTER_TYPE_SINC3_PF1: + return 633; + case AD7124_FILTER_TYPE_SINC3_PF2: + return 605; + case AD7124_FILTER_TYPE_SINC3_PF3: + return 669; + case AD7124_FILTER_TYPE_SINC3_PF4: + return 759; default: return -EINVAL; } @@ -311,9 +421,8 @@ static struct ad7124_channel_config *ad7124_find_similar_live_cfg(struct ad7124_ bool buf_negative; unsigned int vref_mv; unsigned int pga_bits; - unsigned int odr; unsigned int odr_sel_bits; - unsigned int filter_type; + enum ad7124_filter_type filter_type; unsigned int calibration_offset; unsigned int calibration_gain; })); @@ -328,7 +437,6 @@ static struct ad7124_channel_config *ad7124_find_similar_live_cfg(struct ad7124_ cfg->buf_negative == cfg_aux->buf_negative && cfg->vref_mv == cfg_aux->vref_mv && cfg->pga_bits == cfg_aux->pga_bits && - cfg->odr == cfg_aux->odr && cfg->odr_sel_bits == cfg_aux->odr_sel_bits && cfg->filter_type == cfg_aux->filter_type && cfg->calibration_offset == cfg_aux->calibration_offset && @@ -381,8 +489,9 @@ static int ad7124_init_config_vref(struct ad7124_state *st, struct ad7124_channe static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_config *cfg, unsigned int cfg_slot) { - unsigned int tmp; - unsigned int val; + unsigned int val, filter; + unsigned int rej60 = 0; + unsigned int post = 0; int ret; cfg->cfg_slot = cfg_slot; @@ -405,11 +514,60 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co if (ret < 0) return ret; - tmp = FIELD_PREP(AD7124_FILTER_FILTER, cfg->filter_type) | - FIELD_PREP(AD7124_FILTER_FS, cfg->odr_sel_bits); - return ad7124_spi_write_mask(st, AD7124_FILTER(cfg->cfg_slot), - AD7124_FILTER_FILTER | AD7124_FILTER_FS, - tmp, 3); + switch (cfg->filter_type) { + case AD7124_FILTER_TYPE_SINC3: + filter = AD7124_FILTER_FILTER_SINC3; + break; + case AD7124_FILTER_TYPE_SINC3_PF1: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_47dB; + break; + case AD7124_FILTER_TYPE_SINC3_PF2: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_62dB; + break; + case AD7124_FILTER_TYPE_SINC3_PF3: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_86dB; + break; + case AD7124_FILTER_TYPE_SINC3_PF4: + filter = AD7124_FILTER_FILTER_SINC3_PF; + post = AD7124_FILTER_POST_FILTER_92dB; + break; + case AD7124_FILTER_TYPE_SINC3_REJ60: + filter = AD7124_FILTER_FILTER_SINC3; + rej60 = 1; + break; + case AD7124_FILTER_TYPE_SINC3_SINC1: + filter = AD7124_FILTER_FILTER_SINC3_SINC1; + break; + case AD7124_FILTER_TYPE_SINC4: + filter = AD7124_FILTER_FILTER_SINC4; + break; + case AD7124_FILTER_TYPE_SINC4_REJ60: + filter = AD7124_FILTER_FILTER_SINC4; + rej60 = 1; + break; + case AD7124_FILTER_TYPE_SINC4_SINC1: + filter = AD7124_FILTER_FILTER_SINC4_SINC1; + break; + default: + return -EINVAL; + } + + /* + * NB: AD7124_FILTER_SINGLE_CYCLE is always set so that we get the same + * 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. + */ + return ad_sd_write_reg(&st->sd, AD7124_FILTER(cfg->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_FS, cfg->odr_sel_bits)); } static struct ad7124_channel_config *ad7124_pop_config(struct ad7124_state *st) @@ -576,6 +734,33 @@ static const struct ad_sigma_delta_info ad7124_sigma_delta_info = { .num_resetclks = 64, }; +static const int ad7124_voltage_scales[][2] = { + { 0, 1164 }, + { 0, 2328 }, + { 0, 4656 }, + { 0, 9313 }, + { 0, 18626 }, + { 0, 37252 }, + { 0, 74505 }, + { 0, 149011 }, + { 0, 298023 }, +}; + +static int ad7124_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + *vals = (const int *)ad7124_voltage_scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = ARRAY_SIZE(ad7124_voltage_scales) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + static int ad7124_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long info) @@ -644,18 +829,59 @@ static int ad7124_read_raw(struct iio_dev *indio_dev, return -EINVAL; } - case IIO_CHAN_INFO_SAMP_FREQ: - mutex_lock(&st->cfgs_lock); - *val = st->channels[chan->address].cfg.odr; - mutex_unlock(&st->cfgs_lock); + case IIO_CHAN_INFO_SAMP_FREQ: { + struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg; - return IIO_VAL_INT; - case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: - mutex_lock(&st->cfgs_lock); - *val = ad7124_get_3db_filter_freq(st, chan->scan_index); - mutex_unlock(&st->cfgs_lock); + guard(mutex)(&st->cfgs_lock); - return IIO_VAL_INT; + switch (cfg->filter_type) { + case AD7124_FILTER_TYPE_SINC3: + case AD7124_FILTER_TYPE_SINC3_REJ60: + case AD7124_FILTER_TYPE_SINC3_SINC1: + case AD7124_FILTER_TYPE_SINC4: + case AD7124_FILTER_TYPE_SINC4_REJ60: + case AD7124_FILTER_TYPE_SINC4_SINC1: + *val = ad7124_get_fclk_hz(st); + *val2 = ad7124_get_fadc_divisor(st, chan->address); + return IIO_VAL_FRACTIONAL; + /* + * Post filters force the chip to a fixed rate. These are the + * single-channel rates from the data sheet divided by 3 for + * the multi-channel case (data sheet doesn't explicitly state + * this but confirmed through testing). + */ + case AD7124_FILTER_TYPE_SINC3_PF1: + *val = 300; + *val2 = 33; + return IIO_VAL_FRACTIONAL; + case AD7124_FILTER_TYPE_SINC3_PF2: + *val = 25; + *val2 = 3; + return IIO_VAL_FRACTIONAL; + case AD7124_FILTER_TYPE_SINC3_PF3: + *val = 20; + *val2 = 3; + return IIO_VAL_FRACTIONAL; + case AD7124_FILTER_TYPE_SINC3_PF4: + *val = 50; + *val2 = 9; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + } + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: { + guard(mutex)(&st->cfgs_lock); + + ret = ad7124_get_3db_filter_factor(st, chan->address); + if (ret < 0) + return ret; + + /* 3dB point is the f_CLK rate times a fractional value */ + *val = ret * ad7124_get_fclk_hz(st); + *val2 = MILLI * ad7124_get_fadc_divisor(st, chan->address); + return IIO_VAL_FRACTIONAL; + } default: return -EINVAL; } @@ -666,25 +892,24 @@ static int ad7124_write_raw(struct iio_dev *indio_dev, int val, int val2, long info) { struct ad7124_state *st = iio_priv(indio_dev); + struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg; unsigned int res, gain, full_scale, vref; - int ret = 0; - mutex_lock(&st->cfgs_lock); + guard(mutex)(&st->cfgs_lock); switch (info) { case IIO_CHAN_INFO_SAMP_FREQ: - if (val2 != 0 || val == 0) { - ret = -EINVAL; - break; - } + if (val2 < 0 || val < 0 || (val2 == 0 && val == 0)) + return -EINVAL; - ad7124_set_channel_odr(st, chan->address, val); - break; + cfg->requested_odr = val; + cfg->requested_odr_micro = val2; + ad7124_set_channel_odr(st, chan->address); + + return 0; case IIO_CHAN_INFO_SCALE: - if (val != 0) { - ret = -EINVAL; - break; - } + if (val != 0) + return -EINVAL; if (st->channels[chan->address].cfg.bipolar) full_scale = 1 << (chan->scan_type.realbits - 1); @@ -700,13 +925,10 @@ static int ad7124_write_raw(struct iio_dev *indio_dev, st->channels[chan->address].cfg.live = false; st->channels[chan->address].cfg.pga_bits = res; - break; + return 0; default: - ret = -EINVAL; + return -EINVAL; } - - mutex_unlock(&st->cfgs_lock); - return ret; } static int ad7124_reg_access(struct iio_dev *indio_dev, @@ -730,18 +952,6 @@ static int ad7124_reg_access(struct iio_dev *indio_dev, return ret; } -static IIO_CONST_ATTR(in_voltage_scale_available, - "0.000001164 0.000002328 0.000004656 0.000009313 0.000018626 0.000037252 0.000074505 0.000149011 0.000298023"); - -static struct attribute *ad7124_attributes[] = { - &iio_const_attr_in_voltage_scale_available.dev_attr.attr, - NULL, -}; - -static const struct attribute_group ad7124_attrs_group = { - .attrs = ad7124_attributes, -}; - static int ad7124_update_scan_mode(struct iio_dev *indio_dev, const unsigned long *scan_mask) { @@ -750,7 +960,8 @@ static int ad7124_update_scan_mode(struct iio_dev *indio_dev, int ret; int i; - mutex_lock(&st->cfgs_lock); + guard(mutex)(&st->cfgs_lock); + for (i = 0; i < st->num_channels; i++) { bit_set = test_bit(i, scan_mask); if (bit_set) @@ -758,25 +969,20 @@ static int ad7124_update_scan_mode(struct iio_dev *indio_dev, else ret = ad7124_spi_write_mask(st, AD7124_CHANNEL(i), AD7124_CHANNEL_ENABLE, 0, 2); - if (ret < 0) { - mutex_unlock(&st->cfgs_lock); - + if (ret < 0) return ret; - } } - mutex_unlock(&st->cfgs_lock); - return 0; } static const struct iio_info ad7124_info = { + .read_avail = ad7124_read_avail, .read_raw = ad7124_read_raw, .write_raw = ad7124_write_raw, .debugfs_reg_access = &ad7124_reg_access, .validate_trigger = ad_sd_validate_trigger, .update_scan_mode = ad7124_update_scan_mode, - .attrs = &ad7124_attrs_group, }; /* Only called during probe, so dev_err_probe() can be used */ @@ -944,6 +1150,52 @@ static const struct iio_enum ad7124_syscalib_mode_enum = { .get = ad7124_get_syscalib_mode }; +static const char * const ad7124_filter_types[] = { + [AD7124_FILTER_TYPE_SINC3] = "sinc3", + [AD7124_FILTER_TYPE_SINC3_PF1] = "sinc3+pf1", + [AD7124_FILTER_TYPE_SINC3_PF2] = "sinc3+pf2", + [AD7124_FILTER_TYPE_SINC3_PF3] = "sinc3+pf3", + [AD7124_FILTER_TYPE_SINC3_PF4] = "sinc3+pf4", + [AD7124_FILTER_TYPE_SINC3_REJ60] = "sinc3+rej60", + [AD7124_FILTER_TYPE_SINC3_SINC1] = "sinc3+sinc1", + [AD7124_FILTER_TYPE_SINC4] = "sinc4", + [AD7124_FILTER_TYPE_SINC4_REJ60] = "sinc4+rej60", + [AD7124_FILTER_TYPE_SINC4_SINC1] = "sinc4+sinc1", +}; + +static int ad7124_set_filter_type_attr(struct iio_dev *dev, + const struct iio_chan_spec *chan, + unsigned int value) +{ + struct ad7124_state *st = iio_priv(dev); + struct ad7124_channel_config *cfg = &st->channels[chan->address].cfg; + + guard(mutex)(&st->cfgs_lock); + + cfg->live = false; + cfg->filter_type = value; + ad7124_set_channel_odr(st, chan->address); + + return 0; +} + +static int ad7124_get_filter_type_attr(struct iio_dev *dev, + const struct iio_chan_spec *chan) +{ + struct ad7124_state *st = iio_priv(dev); + + guard(mutex)(&st->cfgs_lock); + + return st->channels[chan->address].cfg.filter_type; +} + +static const struct iio_enum ad7124_filter_type_enum = { + .items = ad7124_filter_types, + .num_items = ARRAY_SIZE(ad7124_filter_types), + .set = ad7124_set_filter_type_attr, + .get = ad7124_get_filter_type_attr, +}; + static const struct iio_chan_spec_ext_info ad7124_calibsys_ext_info[] = { { .name = "sys_calibration", @@ -954,6 +1206,9 @@ static const struct iio_chan_spec_ext_info ad7124_calibsys_ext_info[] = { &ad7124_syscalib_mode_enum), IIO_ENUM_AVAILABLE("sys_calibration_mode", IIO_SHARED_BY_TYPE, &ad7124_syscalib_mode_enum), + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7124_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7124_filter_type_enum), { } }; @@ -966,6 +1221,7 @@ static const struct iio_chan_spec ad7124_channel_template = { BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SAMP_FREQ) | BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), .scan_type = { .sign = 'u', .realbits = 24, @@ -1111,24 +1367,122 @@ static int ad7124_parse_channel_config(struct iio_dev *indio_dev, static int ad7124_setup(struct ad7124_state *st) { struct device *dev = &st->sd.spi->dev; - unsigned int fclk, power_mode; + unsigned int power_mode, clk_sel; + struct clk *mclk; int i, ret; - fclk = clk_get_rate(st->mclk); - if (!fclk) - return dev_err_probe(dev, -EINVAL, "Failed to get mclk rate\n"); + /* + * Always use full power mode for max performance. If needed, the driver + * could be adapted to use a dynamic power mode based on the requested + * output data rate. + */ + power_mode = AD7124_ADC_CONTROL_POWER_MODE_FULL; + + /* + * This "mclk" business is needed for backwards compatibility with old + * devicetrees that specified a fake clock named "mclk" to select the + * power mode. + */ + mclk = devm_clk_get_optional_enabled(dev, "mclk"); + if (IS_ERR(mclk)) + return dev_err_probe(dev, PTR_ERR(mclk), "Failed to get mclk\n"); + + if (mclk) { + unsigned long mclk_hz; - /* The power mode changes the master clock frequency */ - power_mode = ad7124_find_closest_match(ad7124_master_clk_freq_hz, - ARRAY_SIZE(ad7124_master_clk_freq_hz), - fclk); - if (fclk != ad7124_master_clk_freq_hz[power_mode]) { - ret = clk_set_rate(st->mclk, fclk); + mclk_hz = clk_get_rate(mclk); + if (!mclk_hz) + return dev_err_probe(dev, -EINVAL, + "Failed to get mclk rate\n"); + + /* + * This logic is a bit backwards, which is why it is only here + * for backwards compatibility. The driver should be able to set + * the power mode as it sees fit and the f_clk/mclk rate should + * be dynamic accordingly. But here, we are selecting a fixed + * power mode based on the given "mclk" rate. + */ + power_mode = ad7124_find_closest_match(ad7124_master_clk_freq_hz, + ARRAY_SIZE(ad7124_master_clk_freq_hz), mclk_hz); + + if (mclk_hz != ad7124_master_clk_freq_hz[power_mode]) { + ret = clk_set_rate(mclk, mclk_hz); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set mclk rate\n"); + } + + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT; + st->clk_hz = AD7124_INT_CLK_HZ; + } else if (!device_property_present(dev, "clocks") && + device_property_present(dev, "#clock-cells")) { +#ifdef CONFIG_COMMON_CLK + struct clk_hw *clk_hw; + + const char *name __free(kfree) = kasprintf(GFP_KERNEL, "%pfwP-clk", + dev_fwnode(dev)); + if (!name) + return -ENOMEM; + + clk_hw = devm_clk_hw_register_fixed_rate(dev, name, NULL, 0, + AD7124_INT_CLK_HZ); + if (IS_ERR(clk_hw)) + return dev_err_probe(dev, PTR_ERR(clk_hw), + "Failed to register clock provider\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + clk_hw); if (ret) - return dev_err_probe(dev, ret, "Failed to set mclk rate\n"); + return dev_err_probe(dev, ret, + "Failed to add clock provider\n"); +#endif + + /* + * Treat the clock as always on. This way we don't have to deal + * with someone trying to enable/disable the clock while we are + * reading samples. + */ + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT_OUT; + st->clk_hz = AD7124_INT_CLK_HZ; + } else { + struct clk *clk; + + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get external clock\n"); + + if (clk) { + unsigned long clk_hz; + + clk_hz = clk_get_rate(clk); + if (!clk_hz) + return dev_err_probe(dev, -EINVAL, + "Failed to get external clock rate\n"); + + /* + * The external clock may be 4x the nominal clock rate, + * in which case the ADC needs to be configured to + * divide it by 4. Using MEGA is a bit arbitrary, but + * the expected clock rates are either 614.4 kHz or + * 2.4576 MHz, so this should work. + */ + if (clk_hz > (1 * HZ_PER_MHZ)) { + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_EXT_DIV4; + st->clk_hz = clk_hz / 4; + } else { + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_EXT; + st->clk_hz = clk_hz; + } + } else { + clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT; + st->clk_hz = AD7124_INT_CLK_HZ; + } } - /* Set the power mode */ + st->adc_control &= ~AD7124_ADC_CONTROL_CLK_SEL; + st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_CLK_SEL, clk_sel); + st->adc_control &= ~AD7124_ADC_CONTROL_POWER_MODE; st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_POWER_MODE, power_mode); @@ -1138,17 +1492,22 @@ static int ad7124_setup(struct ad7124_state *st) mutex_init(&st->cfgs_lock); INIT_KFIFO(st->live_cfgs_fifo); for (i = 0; i < st->num_channels; i++) { + struct ad7124_channel_config *cfg = &st->channels[i].cfg; - ret = ad7124_init_config_vref(st, &st->channels[i].cfg); + ret = ad7124_init_config_vref(st, cfg); if (ret < 0) return ret; + /* Default filter type on the ADC after reset. */ + cfg->filter_type = AD7124_FILTER_TYPE_SINC4; + /* * 9.38 SPS is the minimum output data rate supported * regardless of the selected power mode. Round it up to 10 and * set all channels to this default value. */ - ad7124_set_channel_odr(st, i, 10); + cfg->requested_odr = 10; + ad7124_set_channel_odr(st, i); } ad7124_disable_all(&st->sd); @@ -1300,13 +1659,9 @@ static int ad7124_probe(struct spi_device *spi) ret = devm_add_action_or_reset(&spi->dev, ad7124_reg_disable, st->vref[i]); if (ret) - return dev_err_probe(dev, ret, "Failed to register disable handler for regulator #%d\n", i); + return ret; } - st->mclk = devm_clk_get_enabled(&spi->dev, "mclk"); - if (IS_ERR(st->mclk)) - return dev_err_probe(dev, PTR_ERR(st->mclk), "Failed to get mclk\n"); - ret = ad7124_soft_reset(st); if (ret < 0) return ret; diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c index 683146e83ab2..d36612352b44 100644 --- a/drivers/iio/adc/ad7173.c +++ b/drivers/iio/adc/ad7173.c @@ -8,6 +8,7 @@ * AD7175-8/AD7176-2/AD7177-2 * * Copyright (C) 2015, 2024 Analog Devices, Inc. + * Copyright (C) 2025 BayLibre, SAS */ #include <linux/array_size.h> @@ -149,7 +150,12 @@ (pin2) < st->info->num_voltage_in && \ (pin2) >= st->info->num_voltage_in_div) -#define AD7173_FILTER_ODR0_MASK GENMASK(5, 0) +#define AD7173_FILTER_SINC3_MAP BIT(15) +#define AD7173_FILTER_SINC3_MAP_DIV GENMASK(14, 0) +#define AD7173_FILTER_ENHFILTEN BIT(11) +#define AD7173_FILTER_ENHFILT_MASK GENMASK(10, 8) +#define AD7173_FILTER_ORDER BIT(6) +#define AD7173_FILTER_ODR_MASK GENMASK(5, 0) #define AD7173_MAX_CONFIGS 8 #define AD4111_OW_DET_THRSH_MV 300 @@ -190,6 +196,15 @@ struct ad7173_device_info { u8 num_gpios; }; +enum ad7173_filter_type { + AD7173_FILTER_SINC3, + AD7173_FILTER_SINC5_SINC1, + AD7173_FILTER_SINC5_SINC1_PF1, + AD7173_FILTER_SINC5_SINC1_PF2, + AD7173_FILTER_SINC5_SINC1_PF3, + AD7173_FILTER_SINC5_SINC1_PF4, +}; + struct ad7173_channel_config { /* Openwire detection threshold */ unsigned int openwire_thrsh_raw; @@ -205,8 +220,10 @@ struct ad7173_channel_config { struct_group(config_props, bool bipolar; bool input_buf; - u8 odr; + u16 sinc3_odr_div; + u8 sinc5_odr_index; u8 ref_sel; + enum ad7173_filter_type filter_type; ); }; @@ -266,6 +283,24 @@ static const unsigned int ad7175_sinc5_data_rates[] = { 5000, /* 20 */ }; +/** + * ad7173_sinc3_odr_div_from_odr() - Convert ODR to divider value + * @odr_millihz: ODR (sampling_frequency) in milliHz + * Returns: Divider value for SINC3 filter to pass. + */ +static u16 ad7173_sinc3_odr_div_from_odr(u32 odr_millihz) +{ + /* + * Divider is f_MOD (1 MHz) / 32 / ODR. ODR freq is in milliHz, so + * we need to convert f_MOD to the same units. When SING_CYC=1 or + * multiple channels are enabled (currently always the case), there + * is an additional factor of 3. + */ + u32 div = DIV_ROUND_CLOSEST(MEGA * MILLI, odr_millihz * 32 * 3); + /* Avoid divide by 0 and limit to register field size. */ + return clamp(div, 1U, AD7173_FILTER_SINC3_MAP_DIV); +} + static unsigned int ad4111_current_channel_config[] = { /* Ain sel: pos neg */ 0x1E8, /* 15:IIN0+ 8:IIN0− */ @@ -369,7 +404,48 @@ static const struct iio_enum ad7173_syscalib_mode_enum = { .get = ad7173_get_syscalib_mode }; -static const struct iio_chan_spec_ext_info ad7173_calibsys_ext_info[] = { +static const char * const ad7173_filter_types_str[] = { + [AD7173_FILTER_SINC3] = "sinc3", + [AD7173_FILTER_SINC5_SINC1] = "sinc5+sinc1", + [AD7173_FILTER_SINC5_SINC1_PF1] = "sinc5+sinc1+pf1", + [AD7173_FILTER_SINC5_SINC1_PF2] = "sinc5+sinc1+pf2", + [AD7173_FILTER_SINC5_SINC1_PF3] = "sinc5+sinc1+pf3", + [AD7173_FILTER_SINC5_SINC1_PF4] = "sinc5+sinc1+pf4", +}; + +static int ad7173_set_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + st->channels[chan->address].cfg.filter_type = val; + st->channels[chan->address].cfg.live = false; + + iio_device_release_direct(indio_dev); + + return 0; +} + +static int ad7173_get_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad7173_state *st = iio_priv(indio_dev); + + return st->channels[chan->address].cfg.filter_type; +} + +static const struct iio_enum ad7173_filter_type_enum = { + .items = ad7173_filter_types_str, + .num_items = ARRAY_SIZE(ad7173_filter_types_str), + .set = ad7173_set_filter_type, + .get = ad7173_get_filter_type, +}; + +static const struct iio_chan_spec_ext_info ad7173_chan_spec_ext_info[] = { { .name = "sys_calibration", .write = ad7173_write_syscalib, @@ -379,6 +455,16 @@ static const struct iio_chan_spec_ext_info ad7173_calibsys_ext_info[] = { &ad7173_syscalib_mode_enum), IIO_ENUM_AVAILABLE("sys_calibration_mode", IIO_SHARED_BY_TYPE, &ad7173_syscalib_mode_enum), + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7173_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7173_filter_type_enum), + { } +}; + +static const struct iio_chan_spec_ext_info ad7173_temp_chan_spec_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ad7173_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ad7173_filter_type_enum), { } }; @@ -582,14 +668,18 @@ static bool ad7173_is_setup_equal(const struct ad7173_channel_config *cfg1, sizeof(struct { bool bipolar; bool input_buf; - u8 odr; + u16 sinc3_odr_div; + u8 sinc5_odr_index; u8 ref_sel; + enum ad7173_filter_type filter_type; })); return cfg1->bipolar == cfg2->bipolar && cfg1->input_buf == cfg2->input_buf && - cfg1->odr == cfg2->odr && - cfg1->ref_sel == cfg2->ref_sel; + cfg1->sinc3_odr_div == cfg2->sinc3_odr_div && + cfg1->sinc5_odr_index == cfg2->sinc5_odr_index && + cfg1->ref_sel == cfg2->ref_sel && + cfg1->filter_type == cfg2->filter_type; } static struct ad7173_channel_config * @@ -630,6 +720,7 @@ static int ad7173_load_config(struct ad7173_state *st, { unsigned int config; int free_cfg_slot, ret; + u8 post_filter_enable, post_filter_select; free_cfg_slot = ida_alloc_range(&st->cfg_slots_status, 0, st->info->num_configs - 1, GFP_KERNEL); @@ -649,8 +740,49 @@ static int ad7173_load_config(struct ad7173_state *st, if (ret) return ret; + /* + * When SINC3_MAP flag is enabled, the rest of the register has a + * different meaning. We are using this option to allow the most + * possible sampling frequencies with SINC3 filter. + */ + if (cfg->filter_type == AD7173_FILTER_SINC3) + return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, + FIELD_PREP(AD7173_FILTER_SINC3_MAP, 1) | + FIELD_PREP(AD7173_FILTER_SINC3_MAP_DIV, + cfg->sinc3_odr_div)); + + switch (cfg->filter_type) { + case AD7173_FILTER_SINC5_SINC1_PF1: + post_filter_enable = 1; + post_filter_select = 2; + break; + case AD7173_FILTER_SINC5_SINC1_PF2: + post_filter_enable = 1; + post_filter_select = 3; + break; + case AD7173_FILTER_SINC5_SINC1_PF3: + post_filter_enable = 1; + post_filter_select = 5; + break; + case AD7173_FILTER_SINC5_SINC1_PF4: + post_filter_enable = 1; + post_filter_select = 6; + break; + default: + post_filter_enable = 0; + post_filter_select = 0; + break; + } + return ad_sd_write_reg(&st->sd, AD7173_REG_FILTER(free_cfg_slot), 2, - AD7173_FILTER_ODR0_MASK & cfg->odr); + FIELD_PREP(AD7173_FILTER_SINC3_MAP, 0) | + FIELD_PREP(AD7173_FILTER_ENHFILT_MASK, + post_filter_enable) | + FIELD_PREP(AD7173_FILTER_ENHFILTEN, + post_filter_select) | + FIELD_PREP(AD7173_FILTER_ORDER, 0) | + FIELD_PREP(AD7173_FILTER_ODR_MASK, + cfg->sinc5_odr_index)); } static int ad7173_config_channel(struct ad7173_state *st, int addr) @@ -761,6 +893,7 @@ static const struct ad_sigma_delta_info ad7173_sigma_delta_info_4_slots = { .set_mode = ad7173_set_mode, .has_registers = true, .has_named_irqs = true, + .supports_spi_offload = true, .addr_shift = 0, .read_mask = BIT(6), .status_ch_mask = GENMASK(3, 0), @@ -777,6 +910,7 @@ static const struct ad_sigma_delta_info ad7173_sigma_delta_info_8_slots = { .set_mode = ad7173_set_mode, .has_registers = true, .has_named_irqs = true, + .supports_spi_offload = true, .addr_shift = 0, .read_mask = BIT(6), .status_ch_mask = GENMASK(3, 0), @@ -793,6 +927,7 @@ static const struct ad_sigma_delta_info ad7173_sigma_delta_info_16_slots = { .set_mode = ad7173_set_mode, .has_registers = true, .has_named_irqs = true, + .supports_spi_offload = true, .addr_shift = 0, .read_mask = BIT(6), .status_ch_mask = GENMASK(3, 0), @@ -1180,7 +1315,14 @@ static int ad7173_read_raw(struct iio_dev *indio_dev, return -EINVAL; } case IIO_CHAN_INFO_SAMP_FREQ: - reg = st->channels[chan->address].cfg.odr; + if (st->channels[chan->address].cfg.filter_type == AD7173_FILTER_SINC3) { + /* Inverse operation of ad7173_sinc3_odr_div_from_odr() */ + *val = MEGA; + *val2 = 3 * 32 * st->channels[chan->address].cfg.sinc3_odr_div; + return IIO_VAL_FRACTIONAL; + } + + reg = st->channels[chan->address].cfg.sinc5_odr_index; *val = st->info->sinc5_data_rates[reg] / MILLI; *val2 = (st->info->sinc5_data_rates[reg] % MILLI) * (MICRO / MILLI); @@ -1218,6 +1360,10 @@ static int ad7173_write_raw(struct iio_dev *indio_dev, * * This will cause the reading of CH1 to be actually done once every * 200.16ms, an effective rate of 4.99sps. + * + * Both the sinc5 and sinc3 rates are set here so that if the filter + * type is changed, the requested rate will still be set (aside from + * rounding differences). */ case IIO_CHAN_INFO_SAMP_FREQ: freq = val * MILLI + val2 / MILLI; @@ -1226,7 +1372,8 @@ static int ad7173_write_raw(struct iio_dev *indio_dev, break; cfg = &st->channels[chan->address].cfg; - cfg->odr = i; + cfg->sinc5_odr_index = i; + cfg->sinc3_odr_div = ad7173_sinc3_odr_div_from_odr(freq); cfg->live = false; break; @@ -1243,17 +1390,40 @@ static int ad7173_update_scan_mode(struct iio_dev *indio_dev, const unsigned long *scan_mask) { struct ad7173_state *st = iio_priv(indio_dev); + u16 sinc3_count = 0; + u16 sinc3_div = 0; int i, j, k, ret; for (i = 0; i < indio_dev->num_channels; i++) { - if (test_bit(i, scan_mask)) + const struct ad7173_channel_config *cfg = &st->channels[i].cfg; + + if (test_bit(i, scan_mask)) { + if (cfg->filter_type == AD7173_FILTER_SINC3) { + sinc3_count++; + + if (sinc3_div == 0) { + sinc3_div = cfg->sinc3_odr_div; + } else if (sinc3_div != cfg->sinc3_odr_div) { + dev_err(&st->sd.spi->dev, + "All enabled channels must have the same sampling_frequency for sinc3 filter_type\n"); + return -EINVAL; + } + } + ret = ad7173_set_channel(&st->sd, i); - else + } else { ret = ad_sd_write_reg(&st->sd, AD7173_REG_CH(i), 2, 0); + } if (ret < 0) return ret; } + if (sinc3_count && sinc3_count < bitmap_weight(scan_mask, indio_dev->num_channels)) { + dev_err(&st->sd.spi->dev, + "All enabled channels must have sinc3 filter_type\n"); + return -EINVAL; + } + /* * On some chips, there are more channels that setups, so if there were * more unique setups requested than the number of available slots, @@ -1396,7 +1566,7 @@ static const struct iio_chan_spec ad7173_channel_template = { .storagebits = 32, .endianness = IIO_BE, }, - .ext_info = ad7173_calibsys_ext_info, + .ext_info = ad7173_chan_spec_ext_info, }; static const struct iio_chan_spec ad7173_temp_iio_channel_template = { @@ -1412,6 +1582,7 @@ static const struct iio_chan_spec ad7173_temp_iio_channel_template = { .storagebits = 32, .endianness = IIO_BE, }, + .ext_info = ad7173_temp_chan_spec_ext_info, }; static void ad7173_disable_regulators(void *data) @@ -1652,12 +1823,21 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) chan_st_priv->cfg.bipolar = false; chan_st_priv->cfg.input_buf = st->info->has_input_buf; chan_st_priv->cfg.ref_sel = AD7173_SETUP_REF_SEL_INT_REF; - chan_st_priv->cfg.odr = st->info->odr_start_value; + chan_st_priv->cfg.sinc3_odr_div = ad7173_sinc3_odr_div_from_odr( + st->info->sinc5_data_rates[st->info->odr_start_value] + ); + chan_st_priv->cfg.sinc5_odr_index = st->info->odr_start_value; + chan_st_priv->cfg.filter_type = AD7173_FILTER_SINC5_SINC1; chan_st_priv->cfg.openwire_comp_chan = -1; st->adc_mode |= AD7173_ADC_MODE_REF_EN; if (st->info->data_reg_only_16bit) chan_arr[chan_index].scan_type = ad4113_scan_type; + if (ad_sigma_delta_has_spi_offload(&st->sd)) { + chan_arr[chan_index].scan_type.storagebits = 32; + chan_arr[chan_index].scan_type.endianness = IIO_CPU; + } + chan_index++; } @@ -1719,7 +1899,11 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) chan->scan_index = chan_index; chan->channel = ain[0]; chan_st_priv->cfg.input_buf = st->info->has_input_buf; - chan_st_priv->cfg.odr = st->info->odr_start_value; + chan_st_priv->cfg.sinc3_odr_div = ad7173_sinc3_odr_div_from_odr( + st->info->sinc5_data_rates[st->info->odr_start_value] + ); + chan_st_priv->cfg.sinc5_odr_index = st->info->odr_start_value; + chan_st_priv->cfg.filter_type = AD7173_FILTER_SINC5_SINC1; chan_st_priv->cfg.openwire_comp_chan = -1; chan_st_priv->cfg.bipolar = fwnode_property_read_bool(child, "bipolar"); @@ -1748,6 +1932,12 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev) if (st->info->data_reg_only_16bit) chan_arr[chan_index].scan_type = ad4113_scan_type; + /* Assuming SPI offload is ad411x_ad717x HDL project. */ + if (ad_sigma_delta_has_spi_offload(&st->sd)) { + chan_arr[chan_index].scan_type.storagebits = 32; + chan_arr[chan_index].scan_type.endianness = IIO_CPU; + } + chan_index++; } return 0; @@ -1780,8 +1970,7 @@ static int ad7173_fw_parse_device_config(struct iio_dev *indio_dev) ret = devm_add_action_or_reset(dev, ad7173_disable_regulators, st); if (ret) - return dev_err_probe(dev, ret, - "Failed to add regulators disable action\n"); + return ret; ret = device_property_match_property_string(dev, "clock-names", ad7173_clk_sel, diff --git a/drivers/iio/adc/ad7476.c b/drivers/iio/adc/ad7476.c index aea734aa06bd..1bec6657394c 100644 --- a/drivers/iio/adc/ad7476.c +++ b/drivers/iio/adc/ad7476.c @@ -6,6 +6,7 @@ * Copyright 2010 Analog Devices Inc. */ +#include <linux/bitops.h> #include <linux/device.h> #include <linux/kernel.h> #include <linux/slab.h> @@ -27,22 +28,24 @@ struct ad7476_state; struct ad7476_chip_info { - unsigned int int_vref_uv; + unsigned int int_vref_mv; struct iio_chan_spec channel[2]; - /* channels used when convst gpio is defined */ - struct iio_chan_spec convst_channel[2]; void (*reset)(struct ad7476_state *); + void (*conversion_pre_op)(struct ad7476_state *st); + void (*conversion_post_op)(struct ad7476_state *st); bool has_vref; bool has_vdrive; + bool convstart_required; }; struct ad7476_state { struct spi_device *spi; const struct ad7476_chip_info *chip_info; - struct regulator *ref_reg; struct gpio_desc *convst_gpio; struct spi_transfer xfer; struct spi_message msg; + struct iio_chan_spec channel[2]; + int scale_mv; /* * DMA (thus cache coherency maintenance) may require the * transfer buffers to live in their own cache lines. @@ -52,40 +55,29 @@ struct ad7476_state { unsigned char data[ALIGN(2, sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN); }; -enum ad7476_supported_device_ids { - ID_AD7091, - ID_AD7091R, - ID_AD7273, - ID_AD7274, - ID_AD7276, - ID_AD7277, - ID_AD7278, - ID_AD7466, - ID_AD7467, - ID_AD7468, - ID_AD7475, - ID_AD7495, - ID_AD7940, - ID_ADC081S, - ID_ADC101S, - ID_ADC121S, - ID_ADS7866, - ID_ADS7867, - ID_ADS7868, - ID_LTC2314_14, -}; - static void ad7091_convst(struct ad7476_state *st) { if (!st->convst_gpio) return; - gpiod_set_value(st->convst_gpio, 0); + gpiod_set_value_cansleep(st->convst_gpio, 0); udelay(1); /* CONVST pulse width: 10 ns min */ - gpiod_set_value(st->convst_gpio, 1); + gpiod_set_value_cansleep(st->convst_gpio, 1); udelay(1); /* Conversion time: 650 ns max */ } +static void bd79105_convst_disable(struct ad7476_state *st) +{ + gpiod_set_value_cansleep(st->convst_gpio, 0); +} + +static void bd79105_convst_enable(struct ad7476_state *st) +{ + gpiod_set_value_cansleep(st->convst_gpio, 1); + /* Worst case, 2790 ns required for conversion */ + ndelay(2790); +} + static irqreturn_t ad7476_trigger_handler(int irq, void *p) { struct iio_poll_func *pf = p; @@ -93,7 +85,8 @@ static irqreturn_t ad7476_trigger_handler(int irq, void *p) struct ad7476_state *st = iio_priv(indio_dev); int b_sent; - ad7091_convst(st); + if (st->chip_info->conversion_pre_op) + st->chip_info->conversion_pre_op(st); b_sent = spi_sync(st->spi, &st->msg); if (b_sent < 0) @@ -102,6 +95,8 @@ static irqreturn_t ad7476_trigger_handler(int irq, void *p) iio_push_to_buffers_with_ts(indio_dev, st->data, sizeof(st->data), iio_get_time_ns(indio_dev)); done: + if (st->chip_info->conversion_post_op) + st->chip_info->conversion_post_op(st); iio_trigger_notify_done(indio_dev->trig); return IRQ_HANDLED; @@ -117,12 +112,16 @@ static int ad7476_scan_direct(struct ad7476_state *st) { int ret; - ad7091_convst(st); + if (st->chip_info->conversion_pre_op) + st->chip_info->conversion_pre_op(st); ret = spi_sync(st->spi, &st->msg); if (ret) return ret; + if (st->chip_info->conversion_post_op) + st->chip_info->conversion_post_op(st); + return be16_to_cpup((__be16 *)st->data); } @@ -134,7 +133,6 @@ static int ad7476_read_raw(struct iio_dev *indio_dev, { int ret; struct ad7476_state *st = iio_priv(indio_dev); - int scale_uv; switch (m) { case IIO_CHAN_INFO_RAW: @@ -145,18 +143,11 @@ static int ad7476_read_raw(struct iio_dev *indio_dev, if (ret < 0) return ret; - *val = (ret >> st->chip_info->channel[0].scan_type.shift) & - GENMASK(st->chip_info->channel[0].scan_type.realbits - 1, 0); + *val = (ret >> chan->scan_type.shift) & + GENMASK(chan->scan_type.realbits - 1, 0); return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - if (st->ref_reg) { - scale_uv = regulator_get_voltage(st->ref_reg); - if (scale_uv < 0) - return scale_uv; - } else { - scale_uv = st->chip_info->int_vref_uv; - } - *val = scale_uv / 1000; + *val = st->scale_mv; *val2 = chan->scan_type.realbits; return IIO_VAL_FRACTIONAL_LOG2; } @@ -185,125 +176,147 @@ static int ad7476_read_raw(struct iio_dev *indio_dev, #define AD7940_CHAN(bits) _AD7476_CHAN((bits), 15 - (bits), \ BIT(IIO_CHAN_INFO_RAW)) #define AD7091R_CHAN(bits) _AD7476_CHAN((bits), 16 - (bits), 0) -#define AD7091R_CONVST_CHAN(bits) _AD7476_CHAN((bits), 16 - (bits), \ - BIT(IIO_CHAN_INFO_RAW)) #define ADS786X_CHAN(bits) _AD7476_CHAN((bits), 12 - (bits), \ BIT(IIO_CHAN_INFO_RAW)) -static const struct ad7476_chip_info ad7476_chip_info_tbl[] = { - [ID_AD7091] = { - .channel[0] = AD7091R_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .convst_channel[0] = AD7091R_CONVST_CHAN(12), - .convst_channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .reset = ad7091_reset, - }, - [ID_AD7091R] = { - .channel[0] = AD7091R_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .convst_channel[0] = AD7091R_CONVST_CHAN(12), - .convst_channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .int_vref_uv = 2500000, - .has_vref = true, - .reset = ad7091_reset, - }, - [ID_AD7273] = { - .channel[0] = AD7940_CHAN(10), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .has_vref = true, - }, - [ID_AD7274] = { - .channel[0] = AD7940_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .has_vref = true, - }, - [ID_AD7276] = { - .channel[0] = AD7940_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7277] = { - .channel[0] = AD7940_CHAN(10), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7278] = { - .channel[0] = AD7940_CHAN(8), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7466] = { - .channel[0] = AD7476_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7467] = { - .channel[0] = AD7476_CHAN(10), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7468] = { - .channel[0] = AD7476_CHAN(8), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_AD7475] = { - .channel[0] = AD7476_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .has_vref = true, - .has_vdrive = true, - }, - [ID_AD7495] = { - .channel[0] = AD7476_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .int_vref_uv = 2500000, - .has_vdrive = true, - }, - [ID_AD7940] = { - .channel[0] = AD7940_CHAN(14), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADC081S] = { - .channel[0] = ADC081S_CHAN(8), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADC101S] = { - .channel[0] = ADC081S_CHAN(10), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADC121S] = { - .channel[0] = ADC081S_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADS7866] = { - .channel[0] = ADS786X_CHAN(12), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADS7867] = { - .channel[0] = ADS786X_CHAN(10), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_ADS7868] = { - .channel[0] = ADS786X_CHAN(8), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - }, - [ID_LTC2314_14] = { - .channel[0] = AD7940_CHAN(14), - .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), - .has_vref = true, - }, +static const struct ad7476_chip_info ad7091_chip_info = { + .channel[0] = AD7091R_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .conversion_pre_op = ad7091_convst, + .reset = ad7091_reset, }; -static const struct iio_info ad7476_info = { - .read_raw = &ad7476_read_raw, +static const struct ad7476_chip_info ad7091r_chip_info = { + .channel[0] = AD7091R_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .conversion_pre_op = ad7091_convst, + .int_vref_mv = 2500, + .has_vref = true, + .reset = ad7091_reset, }; -static void ad7476_reg_disable(void *data) -{ - struct regulator *reg = data; +static const struct ad7476_chip_info ad7273_chip_info = { + .channel[0] = AD7940_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, +}; - regulator_disable(reg); -} +static const struct ad7476_chip_info ad7274_chip_info = { + .channel[0] = AD7940_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, +}; + +static const struct ad7476_chip_info ad7276_chip_info = { + .channel[0] = AD7940_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7277_chip_info = { + .channel[0] = AD7940_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7278_chip_info = { + .channel[0] = AD7940_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7466_chip_info = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7467_chip_info = { + .channel[0] = AD7476_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7468_chip_info = { + .channel[0] = AD7476_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ad7475_chip_info = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, + .has_vdrive = true, +}; + +static const struct ad7476_chip_info ad7495_chip_info = { + .channel[0] = AD7476_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .int_vref_mv = 2500, + .has_vdrive = true, +}; + +static const struct ad7476_chip_info ad7940_chip_info = { + .channel[0] = AD7940_CHAN(14), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info adc081s_chip_info = { + .channel[0] = ADC081S_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info adc101s_chip_info = { + .channel[0] = ADC081S_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info adc121s_chip_info = { + .channel[0] = ADC081S_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ads7866_chip_info = { + .channel[0] = ADS786X_CHAN(12), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ads7867_chip_info = { + .channel[0] = ADS786X_CHAN(10), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ads7868_chip_info = { + .channel[0] = ADS786X_CHAN(8), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), +}; + +static const struct ad7476_chip_info ltc2314_14_chip_info = { + .channel[0] = AD7940_CHAN(14), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .has_vref = true, +}; + +static const struct ad7476_chip_info bd79105_chip_info = { + .channel[0] = AD7091R_CHAN(16), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + /* + * The BD79105 starts ADC data conversion when the CONVSTART line is + * set HIGH. The CONVSTART must be kept HIGH until the data has been + * read from the ADC. + */ + .conversion_pre_op = bd79105_convst_enable, + .conversion_post_op = bd79105_convst_disable, + /* BD79105 won't do conversion without convstart */ + .convstart_required = true, + .has_vref = true, + .has_vdrive = true, +}; + +static const struct iio_info ad7476_info = { + .read_raw = &ad7476_read_raw, +}; static int ad7476_probe(struct spi_device *spi) { struct ad7476_state *st; struct iio_dev *indio_dev; - struct regulator *reg; + unsigned int i; int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); @@ -311,61 +324,37 @@ static int ad7476_probe(struct spi_device *spi) return -ENOMEM; st = iio_priv(indio_dev); - st->chip_info = - &ad7476_chip_info_tbl[spi_get_device_id(spi)->driver_data]; - reg = devm_regulator_get(&spi->dev, "vcc"); - if (IS_ERR(reg)) - return PTR_ERR(reg); + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -ENODEV; - ret = regulator_enable(reg); - if (ret) - return ret; - - ret = devm_add_action_or_reset(&spi->dev, ad7476_reg_disable, reg); - if (ret) - return ret; - - /* Either vcc or vref (below) as appropriate */ - if (!st->chip_info->int_vref_uv) - st->ref_reg = reg; + /* Use VCC for reference voltage if vref / internal vref aren't used */ + if (!st->chip_info->int_vref_mv && !st->chip_info->has_vref) { + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vcc"); + if (ret < 0) + return ret; + st->scale_mv = ret / 1000; + } else { + ret = devm_regulator_get_enable(&spi->dev, "vcc"); + if (ret < 0) + return ret; + } if (st->chip_info->has_vref) { - - /* If a device has an internal reference vref is optional */ - if (st->chip_info->int_vref_uv) { - reg = devm_regulator_get_optional(&spi->dev, "vref"); - if (IS_ERR(reg) && (PTR_ERR(reg) != -ENODEV)) - return PTR_ERR(reg); - } else { - reg = devm_regulator_get(&spi->dev, "vref"); - if (IS_ERR(reg)) - return PTR_ERR(reg); - } - - if (!IS_ERR(reg)) { - ret = regulator_enable(reg); - if (ret) - return ret; - - ret = devm_add_action_or_reset(&spi->dev, - ad7476_reg_disable, - reg); - if (ret) + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0) { + /* Vref is optional if a device has an internal reference */ + if (!st->chip_info->int_vref_mv || ret != -ENODEV) return ret; - st->ref_reg = reg; } else { - /* - * Can only get here if device supports both internal - * and external reference, but the regulator connected - * to the external reference is not connected. - * Set the reference regulator pointer to NULL to - * indicate this. - */ - st->ref_reg = NULL; + st->scale_mv = ret / 1000; } } + if (!st->scale_mv) + st->scale_mv = st->chip_info->int_vref_mv; + if (st->chip_info->has_vdrive) { ret = devm_regulator_get_enable(&spi->dev, "vdrive"); if (ret) @@ -378,20 +367,35 @@ static int ad7476_probe(struct spi_device *spi) if (IS_ERR(st->convst_gpio)) return PTR_ERR(st->convst_gpio); + if (st->chip_info->convstart_required && !st->convst_gpio) + return dev_err_probe(&spi->dev, -EINVAL, "No convstart GPIO\n"); + + /* + * This will never happen. Unless someone changes the channel specs + * in this driver. And if someone does, without changing the loop + * below, then we'd better immediately produce a big fat error, before + * the change proceeds from that developer's table. + */ + static_assert(ARRAY_SIZE(st->channel) == ARRAY_SIZE(st->chip_info->channel)); + for (i = 0; i < ARRAY_SIZE(st->channel); i++) { + st->channel[i] = st->chip_info->channel[i]; + if (st->convst_gpio) + __set_bit(IIO_CHAN_INFO_RAW, + &st->channel[i].info_mask_separate); + } + st->spi = spi; indio_dev->name = spi_get_device_id(spi)->name; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = st->chip_info->channel; - indio_dev->num_channels = 2; + indio_dev->channels = st->channel; + indio_dev->num_channels = ARRAY_SIZE(st->channel); indio_dev->info = &ad7476_info; - if (st->convst_gpio) - indio_dev->channels = st->chip_info->convst_channel; /* Setup default message */ st->xfer.rx_buf = &st->data; - st->xfer.len = st->chip_info->channel[0].scan_type.storagebits / 8; + st->xfer.len = indio_dev->channels[0].scan_type.storagebits / 8; spi_message_init(&st->msg); spi_message_add_tail(&st->xfer, &st->msg); @@ -408,41 +412,42 @@ static int ad7476_probe(struct spi_device *spi) } static const struct spi_device_id ad7476_id[] = { - { "ad7091", ID_AD7091 }, - { "ad7091r", ID_AD7091R }, - { "ad7273", ID_AD7273 }, - { "ad7274", ID_AD7274 }, - { "ad7276", ID_AD7276}, - { "ad7277", ID_AD7277 }, - { "ad7278", ID_AD7278 }, - { "ad7466", ID_AD7466 }, - { "ad7467", ID_AD7467 }, - { "ad7468", ID_AD7468 }, - { "ad7475", ID_AD7475 }, - { "ad7476", ID_AD7466 }, - { "ad7476a", ID_AD7466 }, - { "ad7477", ID_AD7467 }, - { "ad7477a", ID_AD7467 }, - { "ad7478", ID_AD7468 }, - { "ad7478a", ID_AD7468 }, - { "ad7495", ID_AD7495 }, - { "ad7910", ID_AD7467 }, - { "ad7920", ID_AD7466 }, - { "ad7940", ID_AD7940 }, - { "adc081s", ID_ADC081S }, - { "adc101s", ID_ADC101S }, - { "adc121s", ID_ADC121S }, - { "ads7866", ID_ADS7866 }, - { "ads7867", ID_ADS7867 }, - { "ads7868", ID_ADS7868 }, + { "ad7091", (kernel_ulong_t)&ad7091_chip_info }, + { "ad7091r", (kernel_ulong_t)&ad7091r_chip_info }, + { "ad7273", (kernel_ulong_t)&ad7273_chip_info }, + { "ad7274", (kernel_ulong_t)&ad7274_chip_info }, + { "ad7276", (kernel_ulong_t)&ad7276_chip_info }, + { "ad7277", (kernel_ulong_t)&ad7277_chip_info }, + { "ad7278", (kernel_ulong_t)&ad7278_chip_info }, + { "ad7466", (kernel_ulong_t)&ad7466_chip_info }, + { "ad7467", (kernel_ulong_t)&ad7467_chip_info }, + { "ad7468", (kernel_ulong_t)&ad7468_chip_info }, + { "ad7475", (kernel_ulong_t)&ad7475_chip_info }, + { "ad7476", (kernel_ulong_t)&ad7466_chip_info }, + { "ad7476a", (kernel_ulong_t)&ad7466_chip_info }, + { "ad7477", (kernel_ulong_t)&ad7467_chip_info }, + { "ad7477a", (kernel_ulong_t)&ad7467_chip_info }, + { "ad7478", (kernel_ulong_t)&ad7468_chip_info }, + { "ad7478a", (kernel_ulong_t)&ad7468_chip_info }, + { "ad7495", (kernel_ulong_t)&ad7495_chip_info }, + { "ad7910", (kernel_ulong_t)&ad7467_chip_info }, + { "ad7920", (kernel_ulong_t)&ad7466_chip_info }, + { "ad7940", (kernel_ulong_t)&ad7940_chip_info }, + { "adc081s", (kernel_ulong_t)&adc081s_chip_info }, + { "adc101s", (kernel_ulong_t)&adc101s_chip_info }, + { "adc121s", (kernel_ulong_t)&adc121s_chip_info }, + { "ads7866", (kernel_ulong_t)&ads7866_chip_info }, + { "ads7867", (kernel_ulong_t)&ads7867_chip_info }, + { "ads7868", (kernel_ulong_t)&ads7868_chip_info }, + { "bd79105", (kernel_ulong_t)&bd79105_chip_info }, /* * The ROHM BU79100G is identical to the TI's ADS7866 from the software * point of view. The binding document mandates the ADS7866 to be * marked as a fallback for the BU79100G, but we still need the SPI ID * here to make the module loading work. */ - { "bu79100g", ID_ADS7866 }, - { "ltc2314-14", ID_LTC2314_14 }, + { "bu79100g", (kernel_ulong_t)&ads7866_chip_info }, + { "ltc2314-14", (kernel_ulong_t)<c2314_14_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ad7476_id); diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c index ca8fa91796ca..872c88d0c86c 100644 --- a/drivers/iio/adc/ad7768-1.c +++ b/drivers/iio/adc/ad7768-1.c @@ -217,7 +217,7 @@ struct ad7768_state { struct spi_device *spi; struct regmap *regmap; struct regmap *regmap24; - struct regulator *vref; + int vref_uv; struct regulator_dev *vcm_rdev; unsigned int vcm_output_sel; struct clk *mclk; @@ -687,8 +687,6 @@ static int ad7768_set_freq(struct ad7768_state *st, int ret; freq = clamp(freq, 50, 1024000); - if (freq == 0) - return -EINVAL; mclk_div = DIV_ROUND_CLOSEST(st->mclk_freq, freq * st->oversampling_ratio); /* Find the closest match for the desired sampling frequency */ @@ -776,7 +774,7 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, { struct ad7768_state *st = iio_priv(indio_dev); const struct iio_scan_type *scan_type; - int scale_uv, ret, temp; + int ret, temp; scan_type = iio_get_current_scan_type(indio_dev, chan); if (IS_ERR(scan_type)) @@ -797,11 +795,7 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - scale_uv = regulator_get_voltage(st->vref); - if (scale_uv < 0) - return scale_uv; - - *val = (scale_uv * 2) / 1000; + *val = (st->vref_uv * 2) / 1000; *val2 = scan_type->realbits; return IIO_VAL_FRACTIONAL_LOG2; @@ -1134,13 +1128,6 @@ static const struct iio_trigger_ops ad7768_trigger_ops = { .validate_device = iio_trigger_validate_own_device, }; -static void ad7768_regulator_disable(void *data) -{ - struct ad7768_state *st = data; - - regulator_disable(st->vref); -} - static int ad7768_set_channel_label(struct iio_dev *indio_dev, int num_channels) { @@ -1372,19 +1359,11 @@ static int ad7768_probe(struct spi_device *spi) return dev_err_probe(&spi->dev, PTR_ERR(st->regmap24), "Failed to initialize regmap24"); - st->vref = devm_regulator_get(&spi->dev, "vref"); - if (IS_ERR(st->vref)) - return PTR_ERR(st->vref); - - ret = regulator_enable(st->vref); - if (ret) { - dev_err(&spi->dev, "Failed to enable specified vref supply\n"); - return ret; - } - - ret = devm_add_action_or_reset(&spi->dev, ad7768_regulator_disable, st); - if (ret) - return ret; + ret = devm_regulator_get_enable_read_voltage(&spi->dev, "vref"); + if (ret < 0) + return dev_err_probe(&spi->dev, ret, + "Failed to get VREF voltage\n"); + st->vref_uv = ret; st->mclk = devm_clk_get_enabled(&spi->dev, "mclk"); if (IS_ERR(st->mclk)) diff --git a/drivers/iio/adc/ad7779.c b/drivers/iio/adc/ad7779.c index 845adc510239..aac5049c9a07 100644 --- a/drivers/iio/adc/ad7779.c +++ b/drivers/iio/adc/ad7779.c @@ -25,6 +25,7 @@ #include <linux/units.h> #include <linux/iio/iio.h> +#include <linux/iio/backend.h> #include <linux/iio/buffer.h> #include <linux/iio/sysfs.h> #include <linux/iio/trigger.h> @@ -145,6 +146,7 @@ struct ad7779_state { struct completion completion; unsigned int sampling_freq; enum ad7779_filter filter_enabled; + struct iio_backend *back; /* * DMA (thus cache coherency maintenance) requires the * transfer buffers to live in their own cache lines. @@ -630,12 +632,38 @@ static int ad7779_reset(struct iio_dev *indio_dev, struct gpio_desc *reset_gpio) return ret; } +static int ad7779_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ad7779_state *st = iio_priv(indio_dev); + unsigned int c; + int ret; + + for (c = 0; c < AD7779_NUM_CHANNELS; c++) { + if (test_bit(c, scan_mask)) + ret = iio_backend_chan_enable(st->back, c); + else + ret = iio_backend_chan_disable(st->back, c); + if (ret) + return ret; + } + + return 0; +} + static const struct iio_info ad7779_info = { .read_raw = ad7779_read_raw, .write_raw = ad7779_write_raw, .debugfs_reg_access = &ad7779_reg_access, }; +static const struct iio_info ad7779_info_data = { + .read_raw = ad7779_read_raw, + .write_raw = ad7779_write_raw, + .debugfs_reg_access = &ad7779_reg_access, + .update_scan_mode = &ad7779_update_scan_mode, +}; + static const struct iio_enum ad7779_filter_enum = { .items = ad7779_filter_type, .num_items = ARRAY_SIZE(ad7779_filter_type), @@ -752,6 +780,125 @@ static int ad7779_conf(struct ad7779_state *st, struct gpio_desc *start_gpio) return 0; } +static int ad7779_set_data_lines(struct iio_dev *indio_dev, u32 num_lanes) +{ + struct ad7779_state *st = iio_priv(indio_dev); + int ret; + + if (num_lanes != 1 && num_lanes != 2 && num_lanes != 4) + return -EINVAL; + + ret = ad7779_set_sampling_frequency(st, num_lanes * AD7779_DEFAULT_SAMPLING_1LINE); + if (ret) + return ret; + + ret = iio_backend_num_lanes_set(st->back, num_lanes); + if (ret) + return ret; + + return ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT, + AD7779_DOUT_FORMAT_MSK, + FIELD_PREP(AD7779_DOUT_FORMAT_MSK, 2 - ilog2(num_lanes))); +} + +static int ad7779_setup_channels(struct iio_dev *indio_dev, const struct ad7779_state *st) +{ + struct iio_chan_spec *channels; + struct device *dev = &st->spi->dev; + + channels = devm_kmemdup_array(dev, st->chip_info->channels, + ARRAY_SIZE(ad7779_channels), + sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + for (unsigned int i = 0; i < ARRAY_SIZE(ad7779_channels); i++) + channels[i].scan_type.endianness = IIO_CPU; + + indio_dev->channels = channels; + indio_dev->num_channels = ARRAY_SIZE(ad7779_channels); + + return 0; +} + +static int ad7779_setup_without_backend(struct ad7779_state *st, struct iio_dev *indio_dev) +{ + int ret; + struct device *dev = &st->spi->dev; + + indio_dev->info = &ad7779_info; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = ARRAY_SIZE(ad7779_channels); + + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops = &ad7779_trigger_ops; + + iio_trigger_set_drvdata(st->trig, st); + + ret = devm_request_irq(dev, st->spi->irq, iio_trigger_generic_data_rdy_poll, + IRQF_ONESHOT | IRQF_NO_AUTOEN, indio_dev->name, + st->trig); + if (ret) + return dev_err_probe(dev, ret, "request IRQ %d failed\n", + st->spi->irq); + + ret = devm_iio_trigger_register(dev, st->trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trig); + + init_completion(&st->completion); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &ad7779_trigger_handler, + &ad7779_buffer_setup_ops); + if (ret) + return ret; + + return ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT, + AD7779_DCLK_CLK_DIV_MSK, + FIELD_PREP(AD7779_DCLK_CLK_DIV_MSK, 7)); +} + +static int ad7779_setup_backend(struct ad7779_state *st, struct iio_dev *indio_dev) +{ + struct device *dev = &st->spi->dev; + int ret; + u32 num_lanes; + + indio_dev->info = &ad7779_info_data; + + ret = ad7779_setup_channels(indio_dev, st); + if (ret) + return ret; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return dev_err_probe(dev, PTR_ERR(st->back), + "failed to get iio backend"); + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + num_lanes = 4; + ret = device_property_read_u32(dev, "adi,num-lanes", &num_lanes); + if (ret && ret != -EINVAL) + return ret; + + return ad7779_set_data_lines(indio_dev, num_lanes); +} + static int ad7779_probe(struct spi_device *spi) { struct iio_dev *indio_dev; @@ -760,9 +907,6 @@ static int ad7779_probe(struct spi_device *spi) struct device *dev = &spi->dev; int ret = -EINVAL; - if (!spi->irq) - return dev_err_probe(dev, ret, "DRDY irq not present\n"); - indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); if (!indio_dev) return -ENOMEM; @@ -804,45 +948,12 @@ static int ad7779_probe(struct spi_device *spi) return ret; indio_dev->name = st->chip_info->name; - indio_dev->info = &ad7779_info; indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = st->chip_info->channels; - indio_dev->num_channels = ARRAY_SIZE(ad7779_channels); - st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, - iio_device_id(indio_dev)); - if (!st->trig) - return -ENOMEM; - - st->trig->ops = &ad7779_trigger_ops; - - iio_trigger_set_drvdata(st->trig, st); - - ret = devm_request_irq(dev, spi->irq, iio_trigger_generic_data_rdy_poll, - IRQF_ONESHOT | IRQF_NO_AUTOEN, indio_dev->name, - st->trig); - if (ret) - return dev_err_probe(dev, ret, "request IRQ %d failed\n", - st->spi->irq); - - ret = devm_iio_trigger_register(dev, st->trig); - if (ret) - return ret; - - indio_dev->trig = iio_trigger_get(st->trig); - - init_completion(&st->completion); - - ret = devm_iio_triggered_buffer_setup(dev, indio_dev, - &iio_pollfunc_store_time, - &ad7779_trigger_handler, - &ad7779_buffer_setup_ops); - if (ret) - return ret; - - ret = ad7779_spi_write_mask(st, AD7779_REG_DOUT_FORMAT, - AD7779_DCLK_CLK_DIV_MSK, - FIELD_PREP(AD7779_DCLK_CLK_DIV_MSK, 7)); + if (device_property_present(dev, "io-backends")) + ret = ad7779_setup_backend(st, indio_dev); + else + ret = ad7779_setup_without_backend(st, indio_dev); if (ret) return ret; @@ -936,3 +1047,4 @@ module_spi_driver(ad7779_driver); MODULE_AUTHOR("Ramona Alexandra Nechita <ramona.nechita@analog.com>"); MODULE_DESCRIPTION("Analog Devices AD7779 ADC"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); diff --git a/drivers/iio/adc/ad7949.c b/drivers/iio/adc/ad7949.c index 202561cad401..b35d299a3977 100644 --- a/drivers/iio/adc/ad7949.c +++ b/drivers/iio/adc/ad7949.c @@ -316,10 +316,8 @@ static int ad7949_spi_probe(struct spi_device *spi) int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*ad7949_adc)); - if (!indio_dev) { - dev_err(dev, "can not allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } indio_dev->info = &ad7949_spi_info; indio_dev->name = spi_get_device_id(spi)->name; diff --git a/drivers/iio/adc/ad799x.c b/drivers/iio/adc/ad799x.c index 9c02f9199139..108bb22162ef 100644 --- a/drivers/iio/adc/ad799x.c +++ b/drivers/iio/adc/ad799x.c @@ -114,11 +114,13 @@ struct ad799x_chip_config { * @num_channels: number of channels * @noirq_config: device configuration w/o IRQ * @irq_config: device configuration w/IRQ + * @has_vref: device supports external reference voltage */ struct ad799x_chip_info { int num_channels; const struct ad799x_chip_config noirq_config; const struct ad799x_chip_config irq_config; + bool has_vref; }; struct ad799x_state { @@ -604,6 +606,7 @@ static const struct iio_event_spec ad799x_events[] = { static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { [ad7991] = { .num_channels = 5, + .has_vref = true, .noirq_config = { .channel = { AD799X_CHANNEL(0, 12), @@ -617,6 +620,7 @@ static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { }, [ad7995] = { .num_channels = 5, + .has_vref = true, .noirq_config = { .channel = { AD799X_CHANNEL(0, 10), @@ -630,6 +634,7 @@ static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { }, [ad7999] = { .num_channels = 5, + .has_vref = true, .noirq_config = { .channel = { AD799X_CHANNEL(0, 8), @@ -687,6 +692,7 @@ static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { }, [ad7994] = { .num_channels = 5, + .has_vref = true, .noirq_config = { .channel = { AD799X_CHANNEL(0, 12), @@ -809,32 +815,22 @@ static int ad799x_probe(struct i2c_client *client) return ret; /* check if an external reference is supplied */ - st->vref = devm_regulator_get_optional(&client->dev, "vref"); - - if (IS_ERR(st->vref)) { - if (PTR_ERR(st->vref) == -ENODEV) { + if (chip_info->has_vref) { + st->vref = devm_regulator_get_optional(&client->dev, "vref"); + ret = PTR_ERR_OR_ZERO(st->vref); + if (ret) { + if (ret != -ENODEV) + goto error_disable_reg; st->vref = NULL; dev_info(&client->dev, "Using VCC reference voltage\n"); - } else { - ret = PTR_ERR(st->vref); - goto error_disable_reg; } - } - if (st->vref) { - /* - * Use external reference voltage if supported by hardware. - * This is optional if voltage / regulator present, use VCC otherwise. - */ - if ((st->id == ad7991) || (st->id == ad7995) || (st->id == ad7999)) { + if (st->vref) { dev_info(&client->dev, "Using external reference voltage\n"); extra_config |= AD7991_REF_SEL; ret = regulator_enable(st->vref); if (ret) goto error_disable_reg; - } else { - st->vref = NULL; - dev_warn(&client->dev, "Supplied reference not supported\n"); } } diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c new file mode 100644 index 000000000000..94e05e11abd9 --- /dev/null +++ b/drivers/iio/adc/ade9000.c @@ -0,0 +1,1799 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * ADE9000 driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/events.h> +#include <linux/interrupt.h> +#include <linux/minmax.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/unaligned.h> + +/* Address of ADE9000 registers */ +#define ADE9000_REG_AIGAIN 0x000 +#define ADE9000_REG_AVGAIN 0x00B +#define ADE9000_REG_AIRMSOS 0x00C +#define ADE9000_REG_AVRMSOS 0x00D +#define ADE9000_REG_APGAIN 0x00E +#define ADE9000_REG_AWATTOS 0x00F +#define ADE9000_REG_AVAROS 0x010 +#define ADE9000_REG_AFVAROS 0x012 +#define ADE9000_REG_CONFIG0 0x060 +#define ADE9000_REG_DICOEFF 0x072 +#define ADE9000_REG_AI_PCF 0x20A +#define ADE9000_REG_AV_PCF 0x20B +#define ADE9000_REG_AIRMS 0x20C +#define ADE9000_REG_AVRMS 0x20D +#define ADE9000_REG_AWATT 0x210 +#define ADE9000_REG_AVAR 0x211 +#define ADE9000_REG_AVA 0x212 +#define ADE9000_REG_AFVAR 0x214 +#define ADE9000_REG_APF 0x216 +#define ADE9000_REG_BI_PCF 0x22A +#define ADE9000_REG_BV_PCF 0x22B +#define ADE9000_REG_BIRMS 0x22C +#define ADE9000_REG_BVRMS 0x22D +#define ADE9000_REG_CI_PCF 0x24A +#define ADE9000_REG_CV_PCF 0x24B +#define ADE9000_REG_CIRMS 0x24C +#define ADE9000_REG_CVRMS 0x24D +#define ADE9000_REG_AWATT_ACC 0x2E5 +#define ADE9000_REG_AWATTHR_LO 0x2E6 +#define ADE9000_REG_AVAHR_LO 0x2FA +#define ADE9000_REG_AFVARHR_LO 0x30E +#define ADE9000_REG_BWATTHR_LO 0x322 +#define ADE9000_REG_BVAHR_LO 0x336 +#define ADE9000_REG_BFVARHR_LO 0x34A +#define ADE9000_REG_CWATTHR_LO 0x35E +#define ADE9000_REG_CVAHR_LO 0x372 +#define ADE9000_REG_CFVARHR_LO 0x386 +#define ADE9000_REG_STATUS0 0x402 +#define ADE9000_REG_STATUS1 0x403 +#define ADE9000_REG_MASK0 0x405 +#define ADE9000_REG_MASK1 0x406 +#define ADE9000_REG_EVENT_MASK 0x407 +#define ADE9000_REG_VLEVEL 0x40F +#define ADE9000_REG_DIP_LVL 0x410 +#define ADE9000_REG_DIPA 0x411 +#define ADE9000_REG_DIPB 0x412 +#define ADE9000_REG_DIPC 0x413 +#define ADE9000_REG_SWELL_LVL 0x414 +#define ADE9000_REG_SWELLA 0x415 +#define ADE9000_REG_SWELLB 0x416 +#define ADE9000_REG_SWELLC 0x417 +#define ADE9000_REG_APERIOD 0x418 +#define ADE9000_REG_BPERIOD 0x419 +#define ADE9000_REG_CPERIOD 0x41A +#define ADE9000_REG_RUN 0x480 +#define ADE9000_REG_CONFIG1 0x481 +#define ADE9000_REG_ACCMODE 0x492 +#define ADE9000_REG_CONFIG3 0x493 +#define ADE9000_REG_ZXTOUT 0x498 +#define ADE9000_REG_ZX_LP_SEL 0x49A +#define ADE9000_REG_WFB_CFG 0x4A0 +#define ADE9000_REG_WFB_PG_IRQEN 0x4A1 +#define ADE9000_REG_WFB_TRG_CFG 0x4A2 +#define ADE9000_REG_WFB_TRG_STAT 0x4A3 +#define ADE9000_REG_CONFIG2 0x4AF +#define ADE9000_REG_EP_CFG 0x4B0 +#define ADE9000_REG_EGY_TIME 0x4B2 +#define ADE9000_REG_PGA_GAIN 0x4B9 +#define ADE9000_REG_VERSION 0x4FE +#define ADE9000_REG_WF_BUFF 0x800 +#define ADE9000_REG_WF_HALF_BUFF 0xC00 + +#define ADE9000_REG_ADDR_MASK GENMASK(15, 4) +#define ADE9000_REG_READ_BIT_MASK BIT(3) + +#define ADE9000_WF_CAP_EN_MASK BIT(4) +#define ADE9000_WF_CAP_SEL_MASK BIT(5) +#define ADE9000_WF_MODE_MASK GENMASK(7, 6) +#define ADE9000_WF_SRC_MASK GENMASK(9, 8) +#define ADE9000_WF_IN_EN_MASK BIT(12) + +/* External reference selection bit in CONFIG1 */ +#define ADE9000_EXT_REF_MASK BIT(15) + +/* + * Configuration registers + */ +#define ADE9000_PGA_GAIN 0x0000 + +/* Default configuration */ + +#define ADE9000_CONFIG0 0x00000000 + +/* CF3/ZX pin outputs Zero crossing, CF4 = DREADY */ +#define ADE9000_CONFIG1 0x000E + +/* Default High pass corner frequency of 1.25Hz */ +#define ADE9000_CONFIG2 0x0A00 + +/* Peak and overcurrent detection disabled */ +#define ADE9000_CONFIG3 0x0000 + +/* + * 50Hz operation, 3P4W Wye configuration, signed accumulation + * 3P4W Wye = 3-Phase 4-Wire star configuration (3 phases + neutral wire) + * Clear bit 8 i.e. ACCMODE=0x00xx for 50Hz operation + * ACCMODE=0x0x9x for 3Wire delta when phase B is used as reference + * 3Wire delta = 3-Phase 3-Wire triangle configuration (3 phases, no neutral) + */ +#define ADE9000_ACCMODE 0x0000 +#define ADE9000_ACCMODE_60HZ 0x0100 + +/*Line period and zero crossing obtained from VA */ +#define ADE9000_ZX_LP_SEL 0x0000 + +/* Interrupt mask values for initialization */ +#define ADE9000_MASK0_ALL_INT_DIS 0 +#define ADE9000_MASK1_ALL_INT_DIS 0x00000000 + +/* Events disabled */ +#define ADE9000_EVENT_DISABLE 0x00000000 + +/* + * Assuming Vnom=1/2 of full scale. + * Refer to Technical reference manual for detailed calculations. + */ +#define ADE9000_VLEVEL 0x0022EA28 + +/* Set DICOEFF= 0xFFFFE000 when integrator is enabled */ +#define ADE9000_DICOEFF 0x00000000 + +/* DSP ON */ +#define ADE9000_RUN_ON 0xFFFFFFFF + +/* + * Energy Accumulation Settings + * Enable energy accumulation, accumulate samples at 8ksps + * latch energy accumulation after EGYRDY + * If accumulation is changed to half line cycle mode, change EGY_TIME + */ +#define ADE9000_EP_CFG 0x0011 + +/* Accumulate 4000 samples */ +#define ADE9000_EGY_TIME 7999 + +/* + * Constant Definitions + * ADE9000 FDSP: 8000sps, ADE9000 FDSP: 4000sps + */ +#define ADE9000_FDSP 4000 +#define ADE9000_DEFAULT_CLK_FREQ_HZ 24576000 +#define ADE9000_WFB_CFG 0x03E9 +#define ADE9000_WFB_PAGE_SIZE 128 +#define ADE9000_WFB_NR_OF_PAGES 16 +#define ADE9000_WFB_MAX_CHANNELS 8 +#define ADE9000_WFB_BYTES_IN_SAMPLE 4 +#define ADE9000_WFB_SAMPLES_IN_PAGE \ + (ADE9000_WFB_PAGE_SIZE / ADE9000_WFB_MAX_CHANNELS) +#define ADE9000_WFB_MAX_SAMPLES_CHAN \ + (ADE9000_WFB_SAMPLES_IN_PAGE * ADE9000_WFB_NR_OF_PAGES) +#define ADE9000_WFB_FULL_BUFF_NR_SAMPLES \ + (ADE9000_WFB_PAGE_SIZE * ADE9000_WFB_NR_OF_PAGES) +#define ADE9000_WFB_FULL_BUFF_SIZE \ + (ADE9000_WFB_FULL_BUFF_NR_SAMPLES * ADE9000_WFB_BYTES_IN_SAMPLE) + +#define ADE9000_SWRST_BIT BIT(0) + +/* Status and Mask register bits*/ +#define ADE9000_ST0_WFB_TRIG_BIT BIT(16) +#define ADE9000_ST0_PAGE_FULL_BIT BIT(17) +#define ADE9000_ST0_EGYRDY BIT(0) + +#define ADE9000_ST1_ZXTOVA_BIT BIT(6) +#define ADE9000_ST1_ZXTOVB_BIT BIT(7) +#define ADE9000_ST1_ZXTOVC_BIT BIT(8) +#define ADE9000_ST1_ZXVA_BIT BIT(9) +#define ADE9000_ST1_ZXVB_BIT BIT(10) +#define ADE9000_ST1_ZXVC_BIT BIT(11) +#define ADE9000_ST1_ZXIA_BIT BIT(13) +#define ADE9000_ST1_ZXIB_BIT BIT(14) +#define ADE9000_ST1_ZXIC_BIT BIT(15) +#define ADE9000_ST1_RSTDONE_BIT BIT(16) +#define ADE9000_ST1_SEQERR_BIT BIT(18) +#define ADE9000_ST1_SWELLA_BIT BIT(20) +#define ADE9000_ST1_SWELLB_BIT BIT(21) +#define ADE9000_ST1_SWELLC_BIT BIT(22) +#define ADE9000_ST1_DIPA_BIT BIT(23) +#define ADE9000_ST1_DIPB_BIT BIT(24) +#define ADE9000_ST1_DIPC_BIT BIT(25) +#define ADE9000_ST1_ERROR0_BIT BIT(28) +#define ADE9000_ST1_ERROR1_BIT BIT(29) +#define ADE9000_ST1_ERROR2_BIT BIT(30) +#define ADE9000_ST1_ERROR3_BIT BIT(31) +#define ADE9000_ST_ERROR \ + (ADE9000_ST1_ERROR0 | ADE9000_ST1_ERROR1 | \ + ADE9000_ST1_ERROR2 | ADE9000_ST1_ERROR3) +#define ADE9000_ST1_CROSSING_FIRST 6 +#define ADE9000_ST1_CROSSING_DEPTH 25 + +#define ADE9000_WFB_TRG_DIP_BIT BIT(0) +#define ADE9000_WFB_TRG_SWELL_BIT BIT(1) +#define ADE9000_WFB_TRG_ZXIA_BIT BIT(3) +#define ADE9000_WFB_TRG_ZXIB_BIT BIT(4) +#define ADE9000_WFB_TRG_ZXIC_BIT BIT(5) +#define ADE9000_WFB_TRG_ZXVA_BIT BIT(6) +#define ADE9000_WFB_TRG_ZXVB_BIT BIT(7) +#define ADE9000_WFB_TRG_ZXVC_BIT BIT(8) + +/* Stop when waveform buffer is full */ +#define ADE9000_WFB_FULL_MODE 0x0 +/* Continuous fill—stop only on enabled trigger events */ +#define ADE9000_WFB_EN_TRIG_MODE 0x1 +/* Continuous filling—center capture around enabled trigger events */ +#define ADE9000_WFB_C_EN_TRIG_MODE 0x2 +/* Continuous fill—used as streaming mode for continuous data output */ +#define ADE9000_WFB_STREAMING_MODE 0x3 + +#define ADE9000_LAST_PAGE_BIT BIT(15) +#define ADE9000_MIDDLE_PAGE_BIT BIT(7) + +/* + * Full scale Codes referred from Datasheet. Respective digital codes are + * produced when ADC inputs are at full scale. + */ +#define ADE9000_RMS_FULL_SCALE_CODES 52866837 +#define ADE9000_WATT_FULL_SCALE_CODES 20694066 +#define ADE9000_PCF_FULL_SCALE_CODES 74770000 + +/* Phase and channel definitions */ +#define ADE9000_PHASE_A_NR 0 +#define ADE9000_PHASE_B_NR 1 +#define ADE9000_PHASE_C_NR 2 + +#define ADE9000_SCAN_POS_IA BIT(0) +#define ADE9000_SCAN_POS_VA BIT(1) +#define ADE9000_SCAN_POS_IB BIT(2) +#define ADE9000_SCAN_POS_VB BIT(3) +#define ADE9000_SCAN_POS_IC BIT(4) +#define ADE9000_SCAN_POS_VC BIT(5) + +/* Waveform buffer configuration values */ +enum ade9000_wfb_cfg { + ADE9000_WFB_CFG_ALL_CHAN = 0x0, + ADE9000_WFB_CFG_IA_VA = 0x1, + ADE9000_WFB_CFG_IB_VB = 0x2, + ADE9000_WFB_CFG_IC_VC = 0x3, + ADE9000_WFB_CFG_IA = 0x8, + ADE9000_WFB_CFG_VA = 0x9, + ADE9000_WFB_CFG_IB = 0xA, + ADE9000_WFB_CFG_VB = 0xB, + ADE9000_WFB_CFG_IC = 0xC, + ADE9000_WFB_CFG_VC = 0xD, +}; + +#define ADE9000_PHASE_B_POS_BIT BIT(5) +#define ADE9000_PHASE_C_POS_BIT BIT(6) + +#define ADE9000_MAX_PHASE_NR 3 +#define AD9000_CHANNELS_PER_PHASE 10 + +/* + * Calculate register address for multi-phase device. + * Phase A (chan 0): base address + 0x00 + * Phase B (chan 1): base address + 0x20 + * Phase C (chan 2): base address + 0x40 + */ +#define ADE9000_ADDR_ADJUST(addr, chan) \ + (((chan) == 0 ? 0 : (chan) == 1 ? 2 : 4) << 4 | (addr)) + +struct ade9000_state { + struct completion reset_completion; + struct mutex lock; /* Protects SPI transactions */ + u8 wf_src; + u32 wfb_trg; + u8 wfb_nr_activ_chan; + u32 wfb_nr_samples; + struct spi_device *spi; + struct clk *clkin; + struct spi_transfer xfer[2]; + struct spi_message spi_msg; + struct regmap *regmap; + union{ + u8 byte[ADE9000_WFB_FULL_BUFF_SIZE]; + __be32 word[ADE9000_WFB_FULL_BUFF_NR_SAMPLES]; + } rx_buff __aligned(IIO_DMA_MINALIGN); + u8 tx_buff[2] __aligned(IIO_DMA_MINALIGN); + unsigned int bulk_read_buf[2]; +}; + +struct ade9000_irq1_event { + u32 bit_mask; + enum iio_chan_type chan_type; + u32 channel; + enum iio_event_type event_type; + enum iio_event_direction event_dir; +}; + +static const struct ade9000_irq1_event ade9000_irq1_events[] = { + { ADE9000_ST1_ZXVA_BIT, IIO_VOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXIA_BIT, IIO_CURRENT, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXVB_BIT, IIO_VOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXIB_BIT, IIO_CURRENT, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXVC_BIT, IIO_VOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_ZXIC_BIT, IIO_CURRENT, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER }, + { ADE9000_ST1_SWELLA_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING }, + { ADE9000_ST1_SWELLB_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING }, + { ADE9000_ST1_SWELLC_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING }, + { ADE9000_ST1_DIPA_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_A_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING }, + { ADE9000_ST1_DIPB_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_B_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING }, + { ADE9000_ST1_DIPC_BIT, IIO_ALTVOLTAGE, ADE9000_PHASE_C_NR, IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING }, +}; + +/* Voltage events (zero crossing on instantaneous voltage) */ +static const struct iio_event_spec ade9000_voltage_events[] = { + { + /* Zero crossing detection - datasheet: ZXV interrupts */ + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +/* Current events (zero crossing on instantaneous current) */ +static const struct iio_event_spec ade9000_current_events[] = { + { + /* Zero crossing detection - datasheet: ZXI interrupts */ + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +/* RMS voltage events (swell/sag detection on RMS values) */ +static const struct iio_event_spec ade9000_rms_voltage_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, /* RMS swell detection */ + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, /* RMS sag/dip detection */ + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), + }, +}; + +static const char * const ade9000_filter_type_items[] = { + "sinc4", "sinc4+lp", +}; + +static const int ade9000_filter_type_values[] = { + 0, 2, +}; + +static int ade9000_filter_type_get(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 val; + int ret; + unsigned int i; + + ret = regmap_read(st->regmap, ADE9000_REG_WFB_CFG, &val); + if (ret) + return ret; + + val = FIELD_GET(ADE9000_WF_SRC_MASK, val); + + for (i = 0; i < ARRAY_SIZE(ade9000_filter_type_values); i++) { + if (ade9000_filter_type_values[i] == val) + return i; + } + + return -EINVAL; +} + +static int ade9000_filter_type_set(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int index) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret, val; + + if (index >= ARRAY_SIZE(ade9000_filter_type_values)) + return -EINVAL; + + val = ade9000_filter_type_values[index]; + + /* Update the WFB_CFG register with the new filter type */ + ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_SRC_MASK, + FIELD_PREP(ADE9000_WF_SRC_MASK, val)); + if (ret) + return ret; + + /* Update cached value */ + st->wf_src = val; + + return 0; +} + +static const struct iio_enum ade9000_filter_type_enum = { + .items = ade9000_filter_type_items, + .num_items = ARRAY_SIZE(ade9000_filter_type_items), + .get = ade9000_filter_type_get, + .set = ade9000_filter_type_set, +}; + +static const struct iio_chan_spec_ext_info ade9000_ext_info[] = { + IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL, &ade9000_filter_type_enum), + { } +}; + +#define ADE9000_CURRENT_CHANNEL(num) { \ + .type = IIO_CURRENT, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AI_PCF, num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .event_spec = ade9000_current_events, \ + .num_event_specs = ARRAY_SIZE(ade9000_current_events), \ + .scan_index = num, \ + .indexed = 1, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ +} + +#define ADE9000_VOLTAGE_CHANNEL(num) { \ + .type = IIO_VOLTAGE, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AV_PCF, num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_FREQUENCY), \ + .event_spec = ade9000_voltage_events, \ + .num_event_specs = ARRAY_SIZE(ade9000_voltage_events), \ + .scan_index = num + 1, /* interleave with current channels */ \ + .indexed = 1, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 32, \ + .storagebits = 32, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = ade9000_ext_info, \ +} + +#define ADE9000_ALTCURRENT_RMS_CHANNEL(num) { \ + .type = IIO_ALTCURRENT, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AIRMS, num), \ + .channel2 = IIO_MOD_RMS, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_index = -1 \ +} + +#define ADE9000_ALTVOLTAGE_RMS_CHANNEL(num) { \ + .type = IIO_ALTVOLTAGE, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMS, num), \ + .channel2 = IIO_MOD_RMS, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .event_spec = ade9000_rms_voltage_events, \ + .num_event_specs = ARRAY_SIZE(ade9000_rms_voltage_events), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_ACTIVE_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AWATT, num), \ + .channel2 = IIO_MOD_ACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_REACTIVE_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVAR, num), \ + .channel2 = IIO_MOD_REACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_APPARENT_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_AVA, num), \ + .channel2 = IIO_MOD_APPARENT, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = -1 \ +} + + #define ADE9000_ENERGY_ACTIVE_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_ACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_ENERGY_APPARENT_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_APPARENT, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_ENERGY_REACTIVE_CHANNEL(num, addr) { \ + .type = IIO_ENERGY, \ + .channel = num, \ + .address = addr, \ + .channel2 = IIO_MOD_REACTIVE, \ + .modified = 1, \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_index = -1 \ +} + +#define ADE9000_POWER_FACTOR_CHANNEL(num) { \ + .type = IIO_POWER, \ + .channel = num, \ + .address = ADE9000_ADDR_ADJUST(ADE9000_REG_APF, num), \ + .indexed = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_POWERFACTOR), \ + .scan_index = -1 \ +} + +static const struct iio_chan_spec ade9000_channels[] = { + /* Phase A channels */ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_A_NR), + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AWATTHR_LO), + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AVAHR_LO), + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_A_NR, ADE9000_REG_AFVARHR_LO), + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_A_NR), + /* Phase B channels */ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_B_NR), + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BWATTHR_LO), + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BVAHR_LO), + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_B_NR, ADE9000_REG_BFVARHR_LO), + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_B_NR), + /* Phase C channels */ + ADE9000_CURRENT_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_VOLTAGE_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_ALTCURRENT_RMS_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_ALTVOLTAGE_RMS_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_POWER_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_POWER_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_POWER_APPARENT_CHANNEL(ADE9000_PHASE_C_NR), + ADE9000_ENERGY_ACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CWATTHR_LO), + ADE9000_ENERGY_APPARENT_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CVAHR_LO), + ADE9000_ENERGY_REACTIVE_CHANNEL(ADE9000_PHASE_C_NR, ADE9000_REG_CFVARHR_LO), + ADE9000_POWER_FACTOR_CHANNEL(ADE9000_PHASE_C_NR), +}; + +static const struct reg_sequence ade9000_initialization_sequence[] = { + { ADE9000_REG_PGA_GAIN, ADE9000_PGA_GAIN }, + { ADE9000_REG_CONFIG0, ADE9000_CONFIG0 }, + { ADE9000_REG_CONFIG1, ADE9000_CONFIG1 }, + { ADE9000_REG_CONFIG2, ADE9000_CONFIG2 }, + { ADE9000_REG_CONFIG3, ADE9000_CONFIG3 }, + { ADE9000_REG_ACCMODE, ADE9000_ACCMODE }, + { ADE9000_REG_ZX_LP_SEL, ADE9000_ZX_LP_SEL }, + { ADE9000_REG_MASK0, ADE9000_MASK0_ALL_INT_DIS }, + { ADE9000_REG_MASK1, ADE9000_MASK1_ALL_INT_DIS }, + { ADE9000_REG_EVENT_MASK, ADE9000_EVENT_DISABLE }, + { ADE9000_REG_WFB_CFG, ADE9000_WFB_CFG }, + { ADE9000_REG_VLEVEL, ADE9000_VLEVEL }, + { ADE9000_REG_DICOEFF, ADE9000_DICOEFF }, + { ADE9000_REG_EGY_TIME, ADE9000_EGY_TIME }, + { ADE9000_REG_EP_CFG, ADE9000_EP_CFG }, + /* Clear all pending status bits by writing 1s */ + { ADE9000_REG_STATUS0, GENMASK(31, 0) }, + { ADE9000_REG_STATUS1, GENMASK(31, 0) }, + { ADE9000_REG_RUN, ADE9000_RUN_ON } +}; + +static int ade9000_spi_write_reg(void *context, unsigned int reg, + unsigned int val) +{ + struct ade9000_state *st = context; + u8 tx_buf[6]; + u16 addr; + int ret, len; + + guard(mutex)(&st->lock); + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg); + put_unaligned_be16(addr, tx_buf); + + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) { + put_unaligned_be16(val, &tx_buf[2]); + len = 4; + } else { + put_unaligned_be32(val, &tx_buf[2]); + len = 6; + } + + ret = spi_write_then_read(st->spi, tx_buf, len, NULL, 0); + if (ret) + dev_err(&st->spi->dev, "problem when writing register 0x%x\n", reg); + + return ret; +} + +static int ade9000_spi_read_reg(void *context, unsigned int reg, + unsigned int *val) +{ + struct ade9000_state *st = context; + u8 tx_buf[2]; + u8 rx_buf[4]; + u16 addr; + int ret, rx_len; + + guard(mutex)(&st->lock); + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, reg) | + ADE9000_REG_READ_BIT_MASK; + + put_unaligned_be16(addr, tx_buf); + + /* Skip CRC bytes - only read actual data */ + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) + rx_len = 2; + else + rx_len = 4; + + ret = spi_write_then_read(st->spi, tx_buf, 2, rx_buf, rx_len); + if (ret) { + dev_err(&st->spi->dev, "error reading register 0x%x\n", reg); + return ret; + } + + if (reg > ADE9000_REG_RUN && reg < ADE9000_REG_VERSION) + *val = get_unaligned_be16(rx_buf); + else + *val = get_unaligned_be32(rx_buf); + + return 0; +} + +static bool ade9000_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + /* Interrupt/error status registers - volatile */ + case ADE9000_REG_STATUS0: + case ADE9000_REG_STATUS1: + return true; + default: + /* All other registers are non-volatile */ + return false; + } +} + +static void ade9000_configure_scan(struct iio_dev *indio_dev, u32 wfb_addr) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u16 addr; + + addr = FIELD_PREP(ADE9000_REG_ADDR_MASK, wfb_addr) | + ADE9000_REG_READ_BIT_MASK; + + put_unaligned_be16(addr, st->tx_buff); + + st->xfer[0].tx_buf = &st->tx_buff[0]; + st->xfer[0].len = 2; + + st->xfer[1].rx_buf = st->rx_buff.byte; + + /* Always use streaming mode */ + st->xfer[1].len = (st->wfb_nr_samples / 2) * 4; + + spi_message_init_with_transfers(&st->spi_msg, st->xfer, ARRAY_SIZE(st->xfer)); +} + +static int ade9000_iio_push_streaming(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 current_page, i; + int ret; + + guard(mutex)(&st->lock); + + ret = spi_sync(st->spi, &st->spi_msg); + if (ret) { + dev_err_ratelimited(dev, "SPI fail in trigger handler\n"); + return ret; + } + + /* In streaming mode, only half the buffer is filled per interrupt */ + for (i = 0; i < st->wfb_nr_samples / 2; i += st->wfb_nr_activ_chan) + iio_push_to_buffers(indio_dev, &st->rx_buff.word[i]); + + ret = regmap_read(st->regmap, ADE9000_REG_WFB_PG_IRQEN, ¤t_page); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB read fail\n"); + return ret; + } + + if (current_page & ADE9000_MIDDLE_PAGE_BIT) { + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_LAST_PAGE_BIT); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB write fail\n"); + return ret; + } + + ade9000_configure_scan(indio_dev, + ADE9000_REG_WF_HALF_BUFF); + } else { + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_MIDDLE_PAGE_BIT); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB write fail"); + return IRQ_HANDLED; + } + + ade9000_configure_scan(indio_dev, ADE9000_REG_WF_BUFF); + } + + return 0; +} + +static int ade9000_iio_push_buffer(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret; + u32 i; + + guard(mutex)(&st->lock); + + ret = spi_sync(st->spi, &st->spi_msg); + if (ret) { + dev_err_ratelimited(&st->spi->dev, + "SPI fail in trigger handler\n"); + return ret; + } + + for (i = 0; i < st->wfb_nr_samples; i += st->wfb_nr_activ_chan) + iio_push_to_buffers(indio_dev, &st->rx_buff.word[i]); + + return 0; +} + +static irqreturn_t ade9000_irq0_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 handled_irq = 0; + u32 interrupts, status; + int ret; + + ret = regmap_read(st->regmap, ADE9000_REG_STATUS0, &status); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 read status fail\n"); + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_MASK0, &interrupts); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 read mask fail\n"); + return IRQ_HANDLED; + } + + if ((status & ADE9000_ST0_PAGE_FULL_BIT) && + (interrupts & ADE9000_ST0_PAGE_FULL_BIT)) { + /* Always use streaming mode */ + ret = ade9000_iio_push_streaming(indio_dev); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 IIO push fail\n"); + return IRQ_HANDLED; + } + + handled_irq |= ADE9000_ST0_PAGE_FULL_BIT; + } + + if ((status & ADE9000_ST0_WFB_TRIG_BIT) && + (interrupts & ADE9000_ST0_WFB_TRIG_BIT)) { + ret = regmap_update_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK, 0); + if (ret) { + dev_err_ratelimited(dev, "IRQ0 WFB fail\n"); + return IRQ_HANDLED; + } + + if (iio_buffer_enabled(indio_dev)) { + ret = ade9000_iio_push_buffer(indio_dev); + if (ret) { + dev_err_ratelimited(dev, + "IRQ0 IIO push fail @ WFB TRIG\n"); + return IRQ_HANDLED; + } + } + + handled_irq |= ADE9000_ST0_WFB_TRIG_BIT; + } + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, handled_irq); + if (ret) + dev_err_ratelimited(dev, "IRQ0 write status fail\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t ade9000_irq1_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int bit = ADE9000_ST1_CROSSING_FIRST; + s64 timestamp = iio_get_time_ns(indio_dev); + u32 handled_irq = 0; + u32 interrupts, result, status, tmp; + DECLARE_BITMAP(interrupt_bits, ADE9000_ST1_CROSSING_DEPTH); + const struct ade9000_irq1_event *event; + int ret, i; + + if (!completion_done(&st->reset_completion)) { + ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &result); + if (ret) { + dev_err_ratelimited(&st->spi->dev, "IRQ1 read status fail\n"); + return IRQ_HANDLED; + } + + if (result & ADE9000_ST1_RSTDONE_BIT) { + complete(&st->reset_completion); + /* Clear the reset done status bit */ + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, ADE9000_ST1_RSTDONE_BIT); + if (ret) + dev_err_ratelimited(&st->spi->dev, + "IRQ1 clear reset status fail\n"); + } else { + dev_err_ratelimited(&st->spi->dev, + "Error testing reset done\n"); + } + + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_STATUS1, &status); + if (ret) { + dev_err_ratelimited(&st->spi->dev, "IRQ1 read status fail\n"); + return IRQ_HANDLED; + } + + ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts); + if (ret) { + dev_err_ratelimited(&st->spi->dev, "IRQ1 read mask fail\n"); + return IRQ_HANDLED; + } + + bitmap_from_arr32(interrupt_bits, &interrupts, ADE9000_ST1_CROSSING_DEPTH); + for_each_set_bit_from(bit, interrupt_bits, + ADE9000_ST1_CROSSING_DEPTH) { + tmp = status & BIT(bit); + if (!tmp) + continue; + + event = NULL; + + /* Find corresponding event in lookup table */ + for (i = 0; i < ARRAY_SIZE(ade9000_irq1_events); i++) { + if (ade9000_irq1_events[i].bit_mask == tmp) { + event = &ade9000_irq1_events[i]; + break; + } + } + + if (event) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(event->chan_type, + event->channel, + event->event_type, + event->event_dir), + timestamp); + } + handled_irq |= tmp; + } + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, handled_irq); + if (ret) + dev_err_ratelimited(&st->spi->dev, "IRQ1 write status fail\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t ade9000_dready_thread(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + + /* Handle data ready interrupt from C4/EVENT/DREADY pin */ + if (!iio_device_claim_buffer_mode(indio_dev)) { + ade9000_iio_push_buffer(indio_dev); + iio_device_release_buffer_mode(indio_dev); + } + + return IRQ_HANDLED; +} + +static int ade9000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int measured; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_FREQUENCY: + if (chan->type == IIO_VOLTAGE) { + int period_reg; + int period; + + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + period_reg = ADE9000_REG_APERIOD; + break; + case ADE9000_PHASE_B_NR: + period_reg = ADE9000_REG_BPERIOD; + break; + case ADE9000_PHASE_C_NR: + period_reg = ADE9000_REG_CPERIOD; + break; + default: + return -EINVAL; + } + ret = regmap_read(st->regmap, period_reg, &period); + if (ret) + return ret; + /* + * Frequency = (4MHz * 65536) / (PERIOD + 1) + * 4MHz = ADC sample rate, 65536 = 2^16 period register scaling + * See ADE9000 datasheet section on period measurement + */ + *val = 4000 * 65536; + *val2 = period + 1; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_ENERGY) { + u16 lo_reg = chan->address; + + ret = regmap_bulk_read(st->regmap, lo_reg, + st->bulk_read_buf, 2); + if (ret) + return ret; + + *val = st->bulk_read_buf[0]; /* Lower 32 bits */ + *val2 = st->bulk_read_buf[1]; /* Upper 32 bits */ + return IIO_VAL_INT_64; + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, chan->address, &measured); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + *val = measured; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_POWERFACTOR: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = regmap_read(st->regmap, chan->address, &measured); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + + *val = measured; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_CURRENT: + case IIO_VOLTAGE: + case IIO_ALTVOLTAGE: + case IIO_ALTCURRENT: + switch (chan->address) { + case ADE9000_REG_AI_PCF: + case ADE9000_REG_AV_PCF: + case ADE9000_REG_BI_PCF: + case ADE9000_REG_BV_PCF: + case ADE9000_REG_CI_PCF: + case ADE9000_REG_CV_PCF: + *val = 1; + *val2 = ADE9000_PCF_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + case ADE9000_REG_AIRMS: + case ADE9000_REG_AVRMS: + case ADE9000_REG_BIRMS: + case ADE9000_REG_BVRMS: + case ADE9000_REG_CIRMS: + case ADE9000_REG_CVRMS: + *val = 1; + *val2 = ADE9000_RMS_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_POWER: + *val = 1; + *val2 = ADE9000_WATT_FULL_SCALE_CODES; + return IIO_VAL_FRACTIONAL; + default: + break; + } + + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ade9000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 tmp; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_CURRENT: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AIRMSOS, + chan->channel), val); + case IIO_VOLTAGE: + case IIO_ALTVOLTAGE: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVRMSOS, + chan->channel), val); + case IIO_POWER: + tmp = chan->address; + tmp &= ~ADE9000_PHASE_B_POS_BIT; + tmp &= ~ADE9000_PHASE_C_POS_BIT; + + switch (tmp) { + case ADE9000_REG_AWATTOS: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AWATTOS, + chan->channel), val); + case ADE9000_REG_AVAR: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVAROS, + chan->channel), val); + case ADE9000_REG_AFVAR: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AFVAROS, + chan->channel), val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: + /* + * Calibration gain registers for fine-tuning measurements. + * These are separate from PGA gain and applied in the digital domain. + */ + switch (chan->type) { + case IIO_CURRENT: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AIGAIN, + chan->channel), val); + case IIO_VOLTAGE: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_AVGAIN, + chan->channel), val); + case IIO_POWER: + return regmap_write(st->regmap, + ADE9000_ADDR_ADJUST(ADE9000_REG_APGAIN, + chan->channel), val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + /* Per-channel scales are read-only */ + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ade9000_reg_access(struct iio_dev *indio_dev, + unsigned int reg, + unsigned int tx_val, + unsigned int *rx_val) +{ + struct ade9000_state *st = iio_priv(indio_dev); + + if (rx_val) + return regmap_read(st->regmap, reg, rx_val); + + return regmap_write(st->regmap, reg, tx_val); +} + +static int ade9000_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 interrupts1; + int ret; + + /* All events use MASK1 register */ + ret = regmap_read(st->regmap, ADE9000_REG_MASK1, &interrupts1); + if (ret) + return ret; + + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXVA_BIT); + else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXIA_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) + return !!(interrupts1 & ADE9000_ST1_SWELLA_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) + return !!(interrupts1 & ADE9000_ST1_DIPA_BIT); + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase A\n", chan->type, dir); + return -EINVAL; + case ADE9000_PHASE_B_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXVB_BIT); + else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXIB_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) + return !!(interrupts1 & ADE9000_ST1_SWELLB_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) + return !!(interrupts1 & ADE9000_ST1_DIPB_BIT); + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase B\n", chan->type, dir); + return -EINVAL; + case ADE9000_PHASE_C_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXVC_BIT); + else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) + return !!(interrupts1 & ADE9000_ST1_ZXIC_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) + return !!(interrupts1 & ADE9000_ST1_SWELLC_BIT); + else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) + return !!(interrupts1 & ADE9000_ST1_DIPC_BIT); + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase C\n", chan->type, dir); + return -EINVAL; + default: + return -EINVAL; + } +} + +static int ade9000_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 bit_mask; + int ret; + + /* Clear all pending events in STATUS1 register (write 1 to clear) */ + ret = regmap_write(st->regmap, ADE9000_REG_STATUS1, GENMASK(31, 0)); + if (ret) + return ret; + + /* Determine which interrupt bit to enable/disable */ + switch (chan->channel) { + case ADE9000_PHASE_A_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXVA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXVA_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVA_BIT; + } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXIA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXIA_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIA_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) { + bit_mask = ADE9000_ST1_SWELLA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) { + bit_mask = ADE9000_ST1_DIPA_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; + } else { + dev_err_ratelimited(&indio_dev->dev, "Invalid channel type %d or direction %d for phase A\n", + chan->type, dir); + return -EINVAL; + } + break; + case ADE9000_PHASE_B_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXVB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXVB_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVB_BIT; + } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXIB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXIB_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIB_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) { + bit_mask = ADE9000_ST1_SWELLB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) { + bit_mask = ADE9000_ST1_DIPB_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; + } else { + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase B\n", + chan->type, dir); + return -EINVAL; + } + break; + case ADE9000_PHASE_C_NR: + if (chan->type == IIO_VOLTAGE && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXVC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXVC_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXVC_BIT; + } else if (chan->type == IIO_CURRENT && dir == IIO_EV_DIR_EITHER) { + bit_mask = ADE9000_ST1_ZXIC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_ZXIC_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_ZXIC_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_RISING) { + bit_mask = ADE9000_ST1_SWELLC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_SWELL_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_SWELL_BIT; + } else if (chan->type == IIO_ALTVOLTAGE && dir == IIO_EV_DIR_FALLING) { + bit_mask = ADE9000_ST1_DIPC_BIT; + if (state) + st->wfb_trg |= ADE9000_WFB_TRG_DIP_BIT; + else + st->wfb_trg &= ~ADE9000_WFB_TRG_DIP_BIT; + } else { + dev_err_ratelimited(&indio_dev->dev, + "Invalid channel type %d or direction %d for phase C\n", + chan->type, dir); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + /* Set bits if enabling event, clear bits if disabling */ + return regmap_assign_bits(st->regmap, ADE9000_REG_MASK1, bit_mask, state ? bit_mask : 0); +} + +static int ade9000_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct ade9000_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_FALLING: + return regmap_write(st->regmap, ADE9000_REG_DIP_LVL, val); + case IIO_EV_DIR_RISING: + return regmap_write(st->regmap, ADE9000_REG_SWELL_LVL, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ade9000_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct ade9000_state *st = iio_priv(indio_dev); + unsigned int data; + int ret; + + switch (info) { + case IIO_EV_INFO_VALUE: + switch (dir) { + case IIO_EV_DIR_FALLING: + ret = regmap_read(st->regmap, ADE9000_REG_DIP_LVL, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_EV_DIR_RISING: + ret = regmap_read(st->regmap, ADE9000_REG_SWELL_LVL, &data); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ade9000_waveform_buffer_config(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + u32 wfb_cfg_val; + u32 active_scans; + + bitmap_to_arr32(&active_scans, indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + + switch (active_scans) { + case ADE9000_SCAN_POS_IA | ADE9000_SCAN_POS_VA: + wfb_cfg_val = ADE9000_WFB_CFG_IA_VA; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IB | ADE9000_SCAN_POS_VB: + wfb_cfg_val = ADE9000_WFB_CFG_IB_VB; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IC | ADE9000_SCAN_POS_VC: + wfb_cfg_val = ADE9000_WFB_CFG_IC_VC; + st->wfb_nr_activ_chan = 2; + break; + case ADE9000_SCAN_POS_IA: + wfb_cfg_val = ADE9000_WFB_CFG_IA; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VA: + wfb_cfg_val = ADE9000_WFB_CFG_VA; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_IB: + wfb_cfg_val = ADE9000_WFB_CFG_IB; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VB: + wfb_cfg_val = ADE9000_WFB_CFG_VB; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_IC: + wfb_cfg_val = ADE9000_WFB_CFG_IC; + st->wfb_nr_activ_chan = 1; + break; + case ADE9000_SCAN_POS_VC: + wfb_cfg_val = ADE9000_WFB_CFG_VC; + st->wfb_nr_activ_chan = 1; + break; + case (ADE9000_SCAN_POS_IA | ADE9000_SCAN_POS_VA | ADE9000_SCAN_POS_IB | + ADE9000_SCAN_POS_VB | ADE9000_SCAN_POS_IC | ADE9000_SCAN_POS_VC): + wfb_cfg_val = ADE9000_WFB_CFG_ALL_CHAN; + st->wfb_nr_activ_chan = 6; + break; + default: + dev_err(&st->spi->dev, "Unsupported combination of scans\n"); + return -EINVAL; + } + + wfb_cfg_val |= FIELD_PREP(ADE9000_WF_SRC_MASK, st->wf_src); + + return regmap_write(st->regmap, ADE9000_REG_WFB_CFG, wfb_cfg_val); +} + +static int ade9000_waveform_buffer_interrupt_setup(struct ade9000_state *st) +{ + int ret; + + ret = regmap_write(st->regmap, ADE9000_REG_WFB_TRG_CFG, 0x0); + if (ret) + return ret; + + /* Always use streaming mode setup */ + ret = regmap_write(st->regmap, ADE9000_REG_WFB_PG_IRQEN, + ADE9000_MIDDLE_PAGE_BIT); + if (ret) + return ret; + + ret = regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0)); + if (ret) + return ret; + + return regmap_set_bits(st->regmap, ADE9000_REG_MASK0, + ADE9000_ST0_PAGE_FULL_BIT); +} + +static int ade9000_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + int ret; + + ret = ade9000_waveform_buffer_config(indio_dev); + if (ret) + return ret; + + st->wfb_nr_samples = ADE9000_WFB_MAX_SAMPLES_CHAN * st->wfb_nr_activ_chan; + + ade9000_configure_scan(indio_dev, ADE9000_REG_WF_BUFF); + + ret = ade9000_waveform_buffer_interrupt_setup(st); + if (ret) + return ret; + + ret = regmap_set_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK); + if (ret) { + dev_err(&st->spi->dev, "Post-enable waveform buffer enable fail\n"); + return ret; + } + + return 0; +} + +static int ade9000_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ade9000_state *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + u32 interrupts; + int ret; + + ret = regmap_clear_bits(st->regmap, ADE9000_REG_WFB_CFG, + ADE9000_WF_CAP_EN_MASK); + if (ret) { + dev_err(dev, "Post-disable waveform buffer disable fail\n"); + return ret; + } + + ret = regmap_write(st->regmap, ADE9000_REG_WFB_TRG_CFG, 0x0); + if (ret) + return ret; + + interrupts = ADE9000_ST0_WFB_TRIG_BIT | ADE9000_ST0_PAGE_FULL_BIT; + + ret = regmap_clear_bits(st->regmap, ADE9000_REG_MASK0, interrupts); + if (ret) { + dev_err(dev, "Post-disable update maks0 fail\n"); + return ret; + } + + return regmap_write(st->regmap, ADE9000_REG_STATUS0, GENMASK(31, 0)); +} + +static const struct iio_buffer_setup_ops ade9000_buffer_ops = { + .preenable = &ade9000_buffer_preenable, + .postdisable = &ade9000_buffer_postdisable, +}; + +static int ade9000_reset(struct ade9000_state *st) +{ + struct device *dev = &st->spi->dev; + struct gpio_desc *gpio_reset; + int ret; + + gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio_reset)) + return PTR_ERR(gpio_reset); + + /* Software reset via register if no GPIO available */ + if (!gpio_reset) { + ret = regmap_set_bits(st->regmap, ADE9000_REG_CONFIG1, + ADE9000_SWRST_BIT); + if (ret) + return ret; + fsleep(90); + return 0; + } + + /* Hardware reset via GPIO */ + fsleep(10); + gpiod_set_value_cansleep(gpio_reset, 0); + fsleep(50000); + + /* Only wait for completion if IRQ1 is available to signal reset done */ + if (fwnode_irq_get_byname(dev_fwnode(dev), "irq1") >= 0) { + if (!wait_for_completion_timeout(&st->reset_completion, + msecs_to_jiffies(1000))) { + dev_err(dev, "Reset timeout after 1s\n"); + return -ETIMEDOUT; + } + } + /* If no IRQ available, reset is already complete after the 50ms delay above */ + + return 0; +} + +static int ade9000_setup(struct ade9000_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + ret = regmap_multi_reg_write(st->regmap, ade9000_initialization_sequence, + ARRAY_SIZE(ade9000_initialization_sequence)); + if (ret) + return dev_err_probe(dev, ret, "Failed to write register sequence"); + + fsleep(2000); + + return 0; +} + +static const struct iio_info ade9000_info = { + .read_raw = ade9000_read_raw, + .write_raw = ade9000_write_raw, + .debugfs_reg_access = ade9000_reg_access, + .write_event_config = ade9000_write_event_config, + .read_event_config = ade9000_read_event_config, + .write_event_value = ade9000_write_event_value, + .read_event_value = ade9000_read_event_value, +}; + +static const struct regmap_config ade9000_regmap_config = { + .reg_bits = 16, + .val_bits = 32, + .max_register = 0x6bc, + .zero_flag_mask = true, + .cache_type = REGCACHE_RBTREE, + .reg_read = ade9000_spi_read_reg, + .reg_write = ade9000_spi_write_reg, + .volatile_reg = ade9000_is_volatile_reg, +}; + +static int ade9000_setup_clkout(struct device *dev, struct ade9000_state *st) +{ + struct clk_hw *clkout_hw; + int ret; + + if (!IS_ENABLED(CONFIG_COMMON_CLK)) + return 0; + + /* + * Only provide clock output when using external CMOS clock. + * When using crystal, CLKOUT is connected to crystal and shouldn't + * be used as clock provider for other devices. + */ + if (!device_property_present(dev, "#clock-cells") || !st->clkin) + return 0; + + /* CLKOUT passes through CLKIN with divider of 1 */ + clkout_hw = devm_clk_hw_register_divider(dev, "clkout", __clk_get_name(st->clkin), + CLK_SET_RATE_PARENT, NULL, 0, 1, 0, NULL); + if (IS_ERR(clkout_hw)) + return dev_err_probe(dev, PTR_ERR(clkout_hw), "Failed to register clkout"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, clkout_hw); + if (ret) + return dev_err_probe(dev, ret, "Failed to add clock provider"); + + return 0; +} + +static int ade9000_request_irq(struct device *dev, const char *name, + irq_handler_t handler, void *dev_id) +{ + int irq, ret; + + irq = fwnode_irq_get_byname(dev_fwnode(dev), name); + if (irq == -EINVAL) + return 0; /* interrupts are optional */ + if (irq < 0) + return dev_err_probe(dev, irq, "Failed to get %s irq", name); + + ret = devm_request_threaded_irq(dev, irq, NULL, handler, + IRQF_ONESHOT, KBUILD_MODNAME, dev_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to request %s irq", name); + + return 0; +} + +static int ade9000_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ade9000_state *st; + struct regmap *regmap; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + regmap = devm_regmap_init(dev, NULL, st, &ade9000_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Unable to allocate ADE9000 regmap"); + + st->regmap = regmap; + st->spi = spi; + + init_completion(&st->reset_completion); + + ret = ade9000_request_irq(dev, "irq0", ade9000_irq0_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "irq1", ade9000_irq1_thread, indio_dev); + if (ret) + return ret; + + ret = ade9000_request_irq(dev, "dready", ade9000_dready_thread, indio_dev); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + /* External CMOS clock input (optional - crystal can be used instead) */ + st->clkin = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(st->clkin)) + return dev_err_probe(dev, PTR_ERR(st->clkin), "Failed to get and enable clkin"); + + ret = ade9000_setup_clkout(dev, st); + if (ret) + return ret; + + indio_dev->name = "ade9000"; + indio_dev->info = &ade9000_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->setup_ops = &ade9000_buffer_ops; + + ret = devm_regulator_get_enable(&spi->dev, "vdd"); + if (ret) + return dev_err_probe(&spi->dev, ret, + "Failed to get and enable vdd regulator\n"); + + indio_dev->channels = ade9000_channels; + indio_dev->num_channels = ARRAY_SIZE(ade9000_channels); + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &ade9000_buffer_ops); + if (ret) + return dev_err_probe(dev, ret, "Failed to setup IIO buffer"); + + ret = ade9000_reset(st); + if (ret) + return ret; + + /* Configure reference selection if vref regulator is available */ + ret = devm_regulator_get_enable_optional(dev, "vref"); + if (ret != -ENODEV && ret >= 0) { + ret = regmap_set_bits(st->regmap, ADE9000_REG_CONFIG1, + ADE9000_EXT_REF_MASK); + if (ret) + return ret; + } else if (ret < 0 && ret != -ENODEV) { + return dev_err_probe(dev, ret, + "Failed to get and enable vref regulator\n"); + } + + ret = ade9000_setup(st); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +}; + +static const struct spi_device_id ade9000_id[] = { + { "ade9000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ade9000_id); + +static const struct of_device_id ade9000_of_match[] = { + { .compatible = "adi,ade9000" }, + { } +}; +MODULE_DEVICE_TABLE(of, ade9000_of_match); + +static struct spi_driver ade9000_driver = { + .driver = { + .name = "ade9000", + .of_match_table = ade9000_of_match, + }, + .probe = ade9000_probe, + .id_table = ade9000_id, +}; +module_spi_driver(ade9000_driver); + +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>"); +MODULE_DESCRIPTION("Analog Devices ADE9000"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index eb42e29960e4..14fa4238c2b9 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -618,6 +618,7 @@ static const struct iio_backend_ops adi_axi_adc_ops = { .chan_status = axi_adc_chan_status, .interface_type_get = axi_adc_interface_type_get, .oversampling_ratio_set = axi_adc_oversampling_ratio_set, + .num_lanes_set = axi_adc_num_lanes_set, .debugfs_reg_access = iio_backend_debugfs_ptr(axi_adc_reg_access), .debugfs_print_chan_status = iio_backend_debugfs_ptr(axi_adc_debugfs_print_chan_status), }; diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c index c3450246730e..b4c36e6a7490 100644 --- a/drivers/iio/adc/at91-sama5d2_adc.c +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -896,7 +896,6 @@ static int at91_adc_config_emr(struct at91_adc_state *st, emr |= osr | AT91_SAMA5D2_TRACKX(trackx); at91_adc_writel(st, EMR, emr); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); st->oversampling_ratio = oversampling_ratio; @@ -971,7 +970,6 @@ static int at91_adc_configure_touch(struct at91_adc_state *st, bool state) AT91_SAMA5D2_IER_PEN | AT91_SAMA5D2_IER_NOPEN); at91_adc_writel(st, TSMR, 0); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return 0; } @@ -1142,10 +1140,8 @@ static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) at91_adc_configure_trigger_registers(st, state); - if (!state) { - pm_runtime_mark_last_busy(st->dev); + if (!state) pm_runtime_put_autosuspend(st->dev); - } return 0; } @@ -1336,7 +1332,6 @@ static int at91_adc_buffer_prepare(struct iio_dev *indio_dev) at91_adc_writel(st, IER, AT91_SAMA5D2_IER_DRDY); pm_runtime_put: - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return ret; } @@ -1394,7 +1389,6 @@ static int at91_adc_buffer_postdisable(struct iio_dev *indio_dev) if (st->dma_st.dma_chan) dmaengine_terminate_sync(st->dma_st.dma_chan); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return 0; @@ -1603,7 +1597,6 @@ static void at91_adc_setup_samp_freq(struct iio_dev *indio_dev, unsigned freq, mr |= AT91_SAMA5D2_MR_TRACKTIM(tracktim); at91_adc_writel(st, MR, mr); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); dev_dbg(&indio_dev->dev, "freq: %u, startup: %u, prescal: %u, tracktim=%u\n", @@ -1809,7 +1802,6 @@ static int at91_adc_read_info_raw(struct iio_dev *indio_dev, at91_adc_readl(st, LCDR); pm_runtime_put: - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return ret; } @@ -1890,7 +1882,6 @@ static int at91_adc_read_temp(struct iio_dev *indio_dev, restore_config: /* Revert previous settings. */ at91_adc_temp_sensor_configure(st, false); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); if (ret < 0) return ret; @@ -2465,7 +2456,6 @@ static int at91_adc_probe(struct platform_device *pdev) dev_info(&pdev->dev, "version: %x\n", readl_relaxed(st->base + st->soc_info.platform->layout->VERSION)); - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return 0; @@ -2567,7 +2557,6 @@ static int at91_adc_resume(struct device *dev) at91_adc_configure_trigger_registers(st, true); } - pm_runtime_mark_last_busy(st->dev); pm_runtime_put_autosuspend(st->dev); return 0; diff --git a/drivers/iio/adc/bcm_iproc_adc.c b/drivers/iio/adc/bcm_iproc_adc.c index f258668b0dc7..6426c9e6ccc9 100644 --- a/drivers/iio/adc/bcm_iproc_adc.c +++ b/drivers/iio/adc/bcm_iproc_adc.c @@ -511,10 +511,8 @@ static int iproc_adc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_priv)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed to allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } adc_priv = iio_priv(indio_dev); platform_set_drvdata(pdev, indio_dev); diff --git a/drivers/iio/adc/cpcap-adc.c b/drivers/iio/adc/cpcap-adc.c index ba7cbd3b4822..d9ee2ea116a7 100644 --- a/drivers/iio/adc/cpcap-adc.c +++ b/drivers/iio/adc/cpcap-adc.c @@ -953,11 +953,9 @@ static int cpcap_adc_probe(struct platform_device *pdev) int error; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ddata)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed to allocate iio device\n"); - + if (!indio_dev) return -ENOMEM; - } + ddata = iio_priv(indio_dev); ddata->ato = device_get_match_data(&pdev->dev); if (!ddata->ato) diff --git a/drivers/iio/adc/da9150-gpadc.c b/drivers/iio/adc/da9150-gpadc.c index b99291ce2a45..625e3a8e4d03 100644 --- a/drivers/iio/adc/da9150-gpadc.c +++ b/drivers/iio/adc/da9150-gpadc.c @@ -308,10 +308,9 @@ static int da9150_gpadc_probe(struct platform_device *pdev) int irq, ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*gpadc)); - if (!indio_dev) { - dev_err(&pdev->dev, "Failed to allocate IIO device\n"); + if (!indio_dev) return -ENOMEM; - } + gpadc = iio_priv(indio_dev); gpadc->da9150 = da9150; diff --git a/drivers/iio/adc/dln2-adc.c b/drivers/iio/adc/dln2-adc.c index 5aea7644780f..eb902a946efe 100644 --- a/drivers/iio/adc/dln2-adc.c +++ b/drivers/iio/adc/dln2-adc.c @@ -584,10 +584,8 @@ static int dln2_adc_probe(struct platform_device *pdev) int i, ret, chans; indio_dev = devm_iio_device_alloc(dev, sizeof(*dln2)); - if (!indio_dev) { - dev_err(dev, "failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } dln2 = iio_priv(indio_dev); dln2->pdev = pdev; @@ -628,10 +626,9 @@ static int dln2_adc_probe(struct platform_device *pdev) dln2->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); - if (!dln2->trig) { - dev_err(dev, "failed to allocate trigger\n"); + if (!dln2->trig) return -ENOMEM; - } + iio_trigger_set_drvdata(dln2->trig, dln2); ret = devm_iio_trigger_register(dev, dln2->trig); if (ret) { diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index 4614cf848535..1484adff00df 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -19,11 +19,9 @@ #include <linux/clk.h> #include <linux/completion.h> #include <linux/of.h> -#include <linux/of_irq.h> #include <linux/regulator/consumer.h> #include <linux/of_platform.h> #include <linux/err.h> -#include <linux/input.h> #include <linux/iio/iio.h> #include <linux/iio/machine.h> @@ -31,21 +29,14 @@ #include <linux/mfd/syscon.h> #include <linux/regmap.h> -#include <linux/platform_data/touchscreen-s3c2410.h> - /* S3C/EXYNOS4412/5250 ADC_V1 registers definitions */ #define ADC_V1_CON(x) ((x) + 0x00) -#define ADC_V1_TSC(x) ((x) + 0x04) #define ADC_V1_DLY(x) ((x) + 0x08) #define ADC_V1_DATX(x) ((x) + 0x0C) #define ADC_V1_DATY(x) ((x) + 0x10) #define ADC_V1_UPDN(x) ((x) + 0x14) #define ADC_V1_INTCLR(x) ((x) + 0x18) #define ADC_V1_MUX(x) ((x) + 0x1c) -#define ADC_V1_CLRINTPNDNUP(x) ((x) + 0x20) - -/* S3C2410 ADC registers definitions */ -#define ADC_S3C2410_MUX(x) ((x) + 0x18) /* Future ADC_V2 registers definitions */ #define ADC_V2_CON1(x) ((x) + 0x00) @@ -61,13 +52,8 @@ #define ADC_V1_CON_PRSCLV(x) (((x) & 0xFF) << 6) #define ADC_V1_CON_STANDBY (1u << 2) -/* Bit definitions for S3C2410 ADC */ +/* Bit definitions for S3C2410 / S3C6410 ADC */ #define ADC_S3C2410_CON_SELMUX(x) (((x) & 7) << 3) -#define ADC_S3C2410_DATX_MASK 0x3FF -#define ADC_S3C2416_CON_RES_SEL (1u << 3) - -/* touch screen always uses channel 0 */ -#define ADC_S3C2410_MUX_TS 0 /* ADCTSC Register Bits */ #define ADC_S3C2443_TSC_UD_SEN (1u << 8) @@ -75,8 +61,6 @@ #define ADC_S3C2410_TSC_YP_SEN (1u << 6) #define ADC_S3C2410_TSC_XM_SEN (1u << 5) #define ADC_S3C2410_TSC_XP_SEN (1u << 4) -#define ADC_S3C2410_TSC_PULL_UP_DISABLE (1u << 3) -#define ADC_S3C2410_TSC_AUTO_PST (1u << 2) #define ADC_S3C2410_TSC_XY_PST(x) (((x) & 0x3) << 0) #define ADC_TSC_WAIT4INT (ADC_S3C2410_TSC_YM_SEN | \ @@ -84,12 +68,6 @@ ADC_S3C2410_TSC_XP_SEN | \ ADC_S3C2410_TSC_XY_PST(3)) -#define ADC_TSC_AUTOPST (ADC_S3C2410_TSC_YM_SEN | \ - ADC_S3C2410_TSC_YP_SEN | \ - ADC_S3C2410_TSC_XP_SEN | \ - ADC_S3C2410_TSC_AUTO_PST | \ - ADC_S3C2410_TSC_XY_PST(0)) - /* Bit definitions for ADC_V2 */ #define ADC_V2_CON1_SOFT_RESET (1u << 2) @@ -121,14 +99,11 @@ struct exynos_adc { struct exynos_adc_data *data; struct device *dev; - struct input_dev *input; void __iomem *regs; struct regmap *pmu_map; struct clk *clk; struct clk *sclk; unsigned int irq; - unsigned int tsirq; - unsigned int delay; struct regulator *vdd; struct completion completion; @@ -136,12 +111,6 @@ struct exynos_adc { u32 value; unsigned int version; - bool ts_enabled; - - bool read_ts; - u32 ts_x; - u32 ts_y; - /* * Lock to protect from potential concurrent access to the * completion callback during a manual conversion. For this driver @@ -241,7 +210,7 @@ static void exynos_adc_v1_init_hw(struct exynos_adc *info) writel(con1, ADC_V1_CON(info->regs)); /* set touchscreen delay */ - writel(info->delay, ADC_V1_DLY(info->regs)); + writel(10000, ADC_V1_DLY(info->regs)); } static void exynos_adc_v1_exit_hw(struct exynos_adc *info) @@ -307,53 +276,6 @@ static const struct exynos_adc_data exynos_adc_s5pv210_data = { .start_conv = exynos_adc_v1_start_conv, }; -static void exynos_adc_s3c2416_start_conv(struct exynos_adc *info, - unsigned long addr) -{ - u32 con1; - - /* Enable 12 bit ADC resolution */ - con1 = readl(ADC_V1_CON(info->regs)); - con1 |= ADC_S3C2416_CON_RES_SEL; - writel(con1, ADC_V1_CON(info->regs)); - - /* Select channel for S3C2416 */ - writel(addr, ADC_S3C2410_MUX(info->regs)); - - con1 = readl(ADC_V1_CON(info->regs)); - writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); -} - -static struct exynos_adc_data const exynos_adc_s3c2416_data = { - .num_channels = MAX_ADC_V1_CHANNELS, - .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ - - .init_hw = exynos_adc_v1_init_hw, - .exit_hw = exynos_adc_v1_exit_hw, - .start_conv = exynos_adc_s3c2416_start_conv, -}; - -static void exynos_adc_s3c2443_start_conv(struct exynos_adc *info, - unsigned long addr) -{ - u32 con1; - - /* Select channel for S3C2433 */ - writel(addr, ADC_S3C2410_MUX(info->regs)); - - con1 = readl(ADC_V1_CON(info->regs)); - writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); -} - -static struct exynos_adc_data const exynos_adc_s3c2443_data = { - .num_channels = MAX_ADC_V1_CHANNELS, - .mask = ADC_S3C2410_DATX_MASK, /* 10 bit ADC resolution */ - - .init_hw = exynos_adc_v1_init_hw, - .exit_hw = exynos_adc_v1_exit_hw, - .start_conv = exynos_adc_s3c2443_start_conv, -}; - static void exynos_adc_s3c64xx_start_conv(struct exynos_adc *info, unsigned long addr) { @@ -365,15 +287,6 @@ static void exynos_adc_s3c64xx_start_conv(struct exynos_adc *info, writel(con1 | ADC_CON_EN_START, ADC_V1_CON(info->regs)); } -static struct exynos_adc_data const exynos_adc_s3c24xx_data = { - .num_channels = MAX_ADC_V1_CHANNELS, - .mask = ADC_S3C2410_DATX_MASK, /* 10 bit ADC resolution */ - - .init_hw = exynos_adc_v1_init_hw, - .exit_hw = exynos_adc_v1_exit_hw, - .start_conv = exynos_adc_s3c64xx_start_conv, -}; - static struct exynos_adc_data const exynos_adc_s3c64xx_data = { .num_channels = MAX_ADC_V1_CHANNELS, .mask = ADC_DATX_MASK, /* 12 bit ADC resolution */ @@ -486,18 +399,6 @@ static const struct exynos_adc_data exynos7_adc_data = { static const struct of_device_id exynos_adc_match[] = { { - .compatible = "samsung,s3c2410-adc", - .data = &exynos_adc_s3c24xx_data, - }, { - .compatible = "samsung,s3c2416-adc", - .data = &exynos_adc_s3c2416_data, - }, { - .compatible = "samsung,s3c2440-adc", - .data = &exynos_adc_s3c24xx_data, - }, { - .compatible = "samsung,s3c2443-adc", - .data = &exynos_adc_s3c2443_data, - }, { .compatible = "samsung,s3c6410-adc", .data = &exynos_adc_s3c64xx_data, }, { @@ -580,55 +481,13 @@ static int exynos_read_raw(struct iio_dev *indio_dev, return ret; } -static int exynos_read_s3c64xx_ts(struct iio_dev *indio_dev, int *x, int *y) -{ - struct exynos_adc *info = iio_priv(indio_dev); - unsigned long time_left; - int ret; - - mutex_lock(&info->lock); - info->read_ts = true; - - reinit_completion(&info->completion); - - writel(ADC_S3C2410_TSC_PULL_UP_DISABLE | ADC_TSC_AUTOPST, - ADC_V1_TSC(info->regs)); - - /* Select the ts channel to be used and Trigger conversion */ - info->data->start_conv(info, ADC_S3C2410_MUX_TS); - - time_left = wait_for_completion_timeout(&info->completion, - EXYNOS_ADC_TIMEOUT); - if (time_left == 0) { - dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); - if (info->data->init_hw) - info->data->init_hw(info); - ret = -ETIMEDOUT; - } else { - *x = info->ts_x; - *y = info->ts_y; - ret = 0; - } - - info->read_ts = false; - mutex_unlock(&info->lock); - - return ret; -} - static irqreturn_t exynos_adc_isr(int irq, void *dev_id) { struct exynos_adc *info = dev_id; u32 mask = info->data->mask; /* Read value */ - if (info->read_ts) { - info->ts_x = readl(ADC_V1_DATX(info->regs)); - info->ts_y = readl(ADC_V1_DATY(info->regs)); - writel(ADC_TSC_WAIT4INT | ADC_S3C2443_TSC_UD_SEN, ADC_V1_TSC(info->regs)); - } else { - info->value = readl(ADC_V1_DATX(info->regs)) & mask; - } + info->value = readl(ADC_V1_DATX(info->regs)) & mask; /* clear irq */ if (info->data->clear_irq) @@ -639,46 +498,6 @@ static irqreturn_t exynos_adc_isr(int irq, void *dev_id) return IRQ_HANDLED; } -/* - * Here we (ab)use a threaded interrupt handler to stay running - * for as long as the touchscreen remains pressed, we report - * a new event with the latest data and then sleep until the - * next timer tick. This mirrors the behavior of the old - * driver, with much less code. - */ -static irqreturn_t exynos_ts_isr(int irq, void *dev_id) -{ - struct exynos_adc *info = dev_id; - struct iio_dev *dev = dev_get_drvdata(info->dev); - u32 x, y; - bool pressed; - int ret; - - while (READ_ONCE(info->ts_enabled)) { - ret = exynos_read_s3c64xx_ts(dev, &x, &y); - if (ret == -ETIMEDOUT) - break; - - pressed = x & y & ADC_DATX_PRESSED; - if (!pressed) { - input_report_key(info->input, BTN_TOUCH, 0); - input_sync(info->input); - break; - } - - input_report_abs(info->input, ABS_X, x & ADC_DATX_MASK); - input_report_abs(info->input, ABS_Y, y & ADC_DATY_MASK); - input_report_key(info->input, BTN_TOUCH, 1); - input_sync(info->input); - - usleep_range(1000, 1100); - } - - writel(0, ADC_V1_CLRINTPNDNUP(info->regs)); - - return IRQ_HANDLED; -} - static int exynos_adc_reg_access(struct iio_dev *indio_dev, unsigned reg, unsigned writeval, unsigned *readval) @@ -730,78 +549,17 @@ static int exynos_adc_remove_devices(struct device *dev, void *c) return 0; } -static int exynos_adc_ts_open(struct input_dev *dev) -{ - struct exynos_adc *info = input_get_drvdata(dev); - - WRITE_ONCE(info->ts_enabled, true); - enable_irq(info->tsirq); - - return 0; -} - -static void exynos_adc_ts_close(struct input_dev *dev) -{ - struct exynos_adc *info = input_get_drvdata(dev); - - WRITE_ONCE(info->ts_enabled, false); - disable_irq(info->tsirq); -} - -static int exynos_adc_ts_init(struct exynos_adc *info) -{ - int ret; - - if (info->tsirq <= 0) - return -ENODEV; - - info->input = input_allocate_device(); - if (!info->input) - return -ENOMEM; - - info->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); - info->input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); - - input_set_abs_params(info->input, ABS_X, 0, 0x3FF, 0, 0); - input_set_abs_params(info->input, ABS_Y, 0, 0x3FF, 0, 0); - - info->input->name = "S3C24xx TouchScreen"; - info->input->id.bustype = BUS_HOST; - info->input->open = exynos_adc_ts_open; - info->input->close = exynos_adc_ts_close; - - input_set_drvdata(info->input, info); - - ret = input_register_device(info->input); - if (ret) { - input_free_device(info->input); - return ret; - } - - ret = request_threaded_irq(info->tsirq, NULL, exynos_ts_isr, - IRQF_ONESHOT | IRQF_NO_AUTOEN, - "touchscreen", info); - if (ret) - input_unregister_device(info->input); - - return ret; -} - static int exynos_adc_probe(struct platform_device *pdev) { struct exynos_adc *info = NULL; struct device_node *np = pdev->dev.of_node; - struct s3c2410_ts_mach_info *pdata = dev_get_platdata(&pdev->dev); struct iio_dev *indio_dev = NULL; - bool has_ts = false; int ret; int irq; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct exynos_adc)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } info = iio_priv(indio_dev); @@ -826,27 +584,10 @@ static int exynos_adc_probe(struct platform_device *pdev) } } - /* leave out any TS related code if unreachable */ - if (IS_REACHABLE(CONFIG_INPUT)) { - has_ts = of_property_read_bool(pdev->dev.of_node, - "has-touchscreen") || pdata; - } - irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; info->irq = irq; - - if (has_ts) { - irq = platform_get_irq(pdev, 1); - if (irq == -EPROBE_DEFER) - return irq; - - info->tsirq = irq; - } else { - info->tsirq = -1; - } - info->dev = &pdev->dev; init_completion(&info->completion); @@ -910,16 +651,6 @@ static int exynos_adc_probe(struct platform_device *pdev) if (info->data->init_hw) info->data->init_hw(info); - if (pdata) - info->delay = pdata->delay; - else - info->delay = 10000; - - if (has_ts) - ret = exynos_adc_ts_init(info); - if (ret) - goto err_iio; - ret = of_platform_populate(np, exynos_adc_match, NULL, &indio_dev->dev); if (ret < 0) { dev_err(&pdev->dev, "failed adding child nodes\n"); @@ -931,11 +662,6 @@ static int exynos_adc_probe(struct platform_device *pdev) err_of_populate: device_for_each_child(&indio_dev->dev, NULL, exynos_adc_remove_devices); - if (has_ts) { - input_unregister_device(info->input); - free_irq(info->tsirq, info); - } -err_iio: iio_device_unregister(indio_dev); err_irq: free_irq(info->irq, info); @@ -955,10 +681,6 @@ static void exynos_adc_remove(struct platform_device *pdev) struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct exynos_adc *info = iio_priv(indio_dev); - if (IS_REACHABLE(CONFIG_INPUT) && info->input) { - free_irq(info->tsirq, info); - input_unregister_device(info->input); - } device_for_each_child(&indio_dev->dev, NULL, exynos_adc_remove_devices); iio_device_unregister(indio_dev); diff --git a/drivers/iio/adc/hx711.c b/drivers/iio/adc/hx711.c index 7235fa9e13d5..1db8b68a8f64 100644 --- a/drivers/iio/adc/hx711.c +++ b/drivers/iio/adc/hx711.c @@ -465,7 +465,7 @@ static int hx711_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(dev, sizeof(struct hx711_data)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, "failed to allocate IIO device\n"); + return -ENOMEM; hx711_data = iio_priv(indio_dev); hx711_data->dev = dev; diff --git a/drivers/iio/adc/imx7d_adc.c b/drivers/iio/adc/imx7d_adc.c index 09ce71f6e941..039c0387da23 100644 --- a/drivers/iio/adc/imx7d_adc.c +++ b/drivers/iio/adc/imx7d_adc.c @@ -482,10 +482,8 @@ static int imx7d_adc_probe(struct platform_device *pdev) int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); - if (!indio_dev) { - dev_err(&pdev->dev, "Failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } info = iio_priv(indio_dev); info->dev = dev; diff --git a/drivers/iio/adc/imx8qxp-adc.c b/drivers/iio/adc/imx8qxp-adc.c index be13a6ed7e00..6fc50394ad90 100644 --- a/drivers/iio/adc/imx8qxp-adc.c +++ b/drivers/iio/adc/imx8qxp-adc.c @@ -229,7 +229,6 @@ static int imx8qxp_adc_read_raw(struct iio_dev *indio_dev, ret = wait_for_completion_interruptible_timeout(&adc->completion, IMX8QXP_ADC_TIMEOUT); - pm_runtime_mark_last_busy(dev); pm_runtime_put_sync_autosuspend(dev); if (ret == 0) { @@ -295,7 +294,6 @@ static int imx8qxp_adc_reg_access(struct iio_dev *indio_dev, unsigned int reg, *readval = readl(adc->regs + reg); - pm_runtime_mark_last_busy(dev); pm_runtime_put_sync_autosuspend(dev); return 0; @@ -315,10 +313,8 @@ static int imx8qxp_adc_probe(struct platform_device *pdev) int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); - if (!indio_dev) { - dev_err(dev, "Failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } adc = iio_priv(indio_dev); adc->dev = dev; diff --git a/drivers/iio/adc/imx93_adc.c b/drivers/iio/adc/imx93_adc.c index 7feaafd2316f..787e80db5de3 100644 --- a/drivers/iio/adc/imx93_adc.c +++ b/drivers/iio/adc/imx93_adc.c @@ -32,12 +32,13 @@ #define IMX93_ADC_PCDR0 0x100 #define IMX93_ADC_PCDR1 0x104 #define IMX93_ADC_PCDR2 0x108 -#define IMX93_ADC_PCDR3 0x10c +#define IMX93_ADC_PCDR3 0x10C #define IMX93_ADC_PCDR4 0x110 #define IMX93_ADC_PCDR5 0x114 #define IMX93_ADC_PCDR6 0x118 -#define IMX93_ADC_PCDR7 0x11c +#define IMX93_ADC_PCDR7 0x11C #define IMX93_ADC_CALSTAT 0x39C +#define IMX93_ADC_CALCFG0 0x3A0 /* ADC bit shift */ #define IMX93_ADC_MCR_MODE_MASK BIT(29) @@ -58,6 +59,8 @@ #define IMX93_ADC_IMR_ECH_MASK BIT(0) #define IMX93_ADC_PCDR_CDATA_MASK GENMASK(11, 0) +#define IMX93_ADC_CALCFG0_LDFAIL_MASK BIT(4) + /* ADC status */ #define IMX93_ADC_MSR_ADCSTATUS_IDLE 0 #define IMX93_ADC_MSR_ADCSTATUS_POWER_DOWN 1 @@ -145,7 +148,7 @@ static void imx93_adc_config_ad_clk(struct imx93_adc *adc) static int imx93_adc_calibration(struct imx93_adc *adc) { - u32 mcr, msr; + u32 mcr, msr, calcfg; int ret; /* make sure ADC in power down mode */ @@ -158,6 +161,11 @@ static int imx93_adc_calibration(struct imx93_adc *adc) imx93_adc_power_up(adc); + /* Enable loading of calibrated values even in fail condition */ + calcfg = readl(adc->regs + IMX93_ADC_CALCFG0); + calcfg |= IMX93_ADC_CALCFG0_LDFAIL_MASK; + writel(calcfg, adc->regs + IMX93_ADC_CALCFG0); + /* * TODO: we use the default TSAMP/NRSMPL/AVGEN in MCR, * can add the setting of these bit if need in future. @@ -180,9 +188,13 @@ static int imx93_adc_calibration(struct imx93_adc *adc) /* check whether calbration is success or not */ msr = readl(adc->regs + IMX93_ADC_MSR); if (msr & IMX93_ADC_MSR_CALFAIL_MASK) { + /* + * Only give warning here, this means the noise of the + * reference voltage do not meet the requirement: + * ADC reference voltage Noise < 1.8V * 1/2^ENOB + * And the resault of ADC is not that accurate. + */ dev_warn(adc->dev, "ADC calibration failed!\n"); - imx93_adc_power_down(adc); - return -EAGAIN; } return 0; @@ -248,7 +260,6 @@ static int imx93_adc_read_raw(struct iio_dev *indio_dev, mutex_lock(&adc->lock); ret = imx93_adc_read_channel_conversion(adc, chan->channel, val); mutex_unlock(&adc->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_sync_autosuspend(dev); if (ret < 0) return ret; @@ -308,8 +319,7 @@ static int imx93_adc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "Failed allocating iio device\n"); + return -ENOMEM; adc = iio_priv(indio_dev); adc->dev = dev; diff --git a/drivers/iio/adc/intel_dc_ti_adc.c b/drivers/iio/adc/intel_dc_ti_adc.c new file mode 100644 index 000000000000..0fe34f1c338e --- /dev/null +++ b/drivers/iio/adc/intel_dc_ti_adc.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Dollar Cove TI PMIC GPADC Driver + * + * Copyright (C) 2014 Intel Corporation (Ramakrishna Pallala <ramakrishna.pallala@intel.com>) + * Copyright (C) 2024 - 2025 Hans de Goede <hansg@kernel.org> + */ + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/wait.h> + +#include <linux/iio/driver.h> +#include <linux/iio/iio.h> +#include <linux/iio/machine.h> + +#define DC_TI_ADC_CNTL_REG 0x50 +#define DC_TI_ADC_START BIT(0) +#define DC_TI_ADC_CH_SEL GENMASK(2, 1) +#define DC_TI_ADC_EN BIT(5) +#define DC_TI_ADC_EN_EXT_BPTH_BIAS BIT(6) + +#define DC_TI_VBAT_ZSE_GE_REG 0x53 +#define DC_TI_VBAT_GE GENMASK(3, 0) +#define DC_TI_VBAT_ZSE GENMASK(7, 4) + +/* VBAT GE gain correction is in 0.0015 increments, ZSE is in 1.0 increments */ +#define DC_TI_VBAT_GE_STEP 15 +#define DC_TI_VBAT_GE_DIV 10000 + +#define DC_TI_ADC_DATA_REG_CH(x) (0x54 + 2 * (x)) + +enum dc_ti_adc_id { + DC_TI_ADC_VBAT, + DC_TI_ADC_PMICTEMP, + DC_TI_ADC_BATTEMP, + DC_TI_ADC_SYSTEMP0, +}; + +struct dc_ti_adc_info { + struct mutex lock; /* Protects against concurrent accesses to the ADC */ + wait_queue_head_t wait; + struct device *dev; + struct regmap *regmap; + int vbat_zse; + int vbat_ge; + bool conversion_done; +}; + +static const struct iio_chan_spec dc_ti_adc_channels[] = { + { + .indexed = 1, + .type = IIO_VOLTAGE, + .channel = DC_TI_ADC_VBAT, + .address = DC_TI_ADC_DATA_REG_CH(0), + .datasheet_name = "CH0", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_PROCESSED), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_PMICTEMP, + .address = DC_TI_ADC_DATA_REG_CH(1), + .datasheet_name = "CH1", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_BATTEMP, + .address = DC_TI_ADC_DATA_REG_CH(2), + .datasheet_name = "CH2", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, { + .indexed = 1, + .type = IIO_TEMP, + .channel = DC_TI_ADC_SYSTEMP0, + .address = DC_TI_ADC_DATA_REG_CH(3), + .datasheet_name = "CH3", + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + } +}; + +static struct iio_map dc_ti_adc_default_maps[] = { + IIO_MAP("CH0", "chtdc_ti_battery", "VBAT"), + IIO_MAP("CH1", "chtdc_ti_battery", "PMICTEMP"), + IIO_MAP("CH2", "chtdc_ti_battery", "BATTEMP"), + IIO_MAP("CH3", "chtdc_ti_battery", "SYSTEMP0"), + { } +}; + +static irqreturn_t dc_ti_adc_isr(int irq, void *data) +{ + struct dc_ti_adc_info *info = data; + + info->conversion_done = true; + wake_up(&info->wait); + return IRQ_HANDLED; +} + +static int dc_ti_adc_scale(struct dc_ti_adc_info *info, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + if (chan->channel != DC_TI_ADC_VBAT) + return -EINVAL; + + /* Vbat ADC scale is 4.6875 mV / unit */ + *val = 4; + *val2 = 687500; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int dc_ti_adc_raw_to_processed(struct dc_ti_adc_info *info, + struct iio_chan_spec const *chan, + int raw, int *val, int *val2) +{ + if (chan->channel != DC_TI_ADC_VBAT) + return -EINVAL; + + /* Apply calibration */ + raw -= info->vbat_zse; + raw = raw * (DC_TI_VBAT_GE_DIV - info->vbat_ge * DC_TI_VBAT_GE_STEP) / + DC_TI_VBAT_GE_DIV; + /* Vbat ADC scale is 4.6875 mV / unit */ + raw *= 46875; + + /* raw is now in 10000 units / mV, convert to milli + milli/1e6 */ + *val = raw / 10000; + *val2 = (raw % 10000) * 100; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int dc_ti_adc_sample(struct dc_ti_adc_info *info, + struct iio_chan_spec const *chan, int *val) +{ + int ret, ch = chan->channel; + __be16 buf; + + info->conversion_done = false; + + /* + * As per TI (PMIC Vendor), the ADC enable and ADC start commands should + * not be sent together. Hence send the commands separately. + */ + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, DC_TI_ADC_EN); + if (ret) + return ret; + + ret = regmap_update_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_CH_SEL, + FIELD_PREP(DC_TI_ADC_CH_SEL, ch)); + if (ret) + return ret; + + /* + * As per PMIC Vendor, a minimum of 50 ųs delay is required between ADC + * Enable and ADC START commands. This is also recommended by Intel + * Hardware team after the timing analysis of GPADC signals. Since the + * I2C Write transaction to set the channel number also imparts 25 ųs + * delay, we need to wait for another 25 ųs before issuing ADC START. + */ + fsleep(25); + + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_START); + if (ret) + return ret; + + /* TI (PMIC Vendor) recommends 5 s timeout for conversion */ + ret = wait_event_timeout(info->wait, info->conversion_done, 5 * HZ); + if (ret == 0) { + ret = -ETIMEDOUT; + goto disable_adc; + } + + ret = regmap_bulk_read(info->regmap, chan->address, &buf, sizeof(buf)); + if (ret) + goto disable_adc; + + /* The ADC values are 10 bits wide */ + *val = be16_to_cpu(buf) & GENMASK(9, 0); + +disable_adc: + regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_START | DC_TI_ADC_EN); + return ret; +} + +static int dc_ti_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dc_ti_adc_info *info = iio_priv(indio_dev); + int ret; + + if (mask == IIO_CHAN_INFO_SCALE) + return dc_ti_adc_scale(info, chan, val, val2); + + guard(mutex)(&info->lock); + + /* + * If channel BPTHERM has been selected, first enable the BPTHERM BIAS + * which provides the VREF Voltage reference to convert BPTHERM Input + * voltage to temperature. + */ + if (chan->channel == DC_TI_ADC_BATTEMP) { + ret = regmap_set_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_EN_EXT_BPTH_BIAS); + if (ret) + return ret; + /* + * As per PMIC Vendor specifications, BPTHERM BIAS should be + * enabled 35 ms before ADC_EN command. + */ + msleep(35); + } + + ret = dc_ti_adc_sample(info, chan, val); + + if (chan->channel == DC_TI_ADC_BATTEMP) + regmap_clear_bits(info->regmap, DC_TI_ADC_CNTL_REG, + DC_TI_ADC_EN_EXT_BPTH_BIAS); + + if (ret) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + return dc_ti_adc_raw_to_processed(info, chan, *val, val, val2); + } + + return -EINVAL; +} + +static const struct iio_info dc_ti_adc_iio_info = { + .read_raw = dc_ti_adc_read_raw, +}; + +static int dc_ti_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent); + struct dc_ti_adc_info *info; + struct iio_dev *indio_dev; + unsigned int val; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + ret = devm_mutex_init(dev, &info->lock); + if (ret) + return ret; + + init_waitqueue_head(&info->wait); + + info->dev = dev; + info->regmap = pmic->regmap; + + indio_dev->name = "dc_ti_adc"; + indio_dev->channels = dc_ti_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(dc_ti_adc_channels); + indio_dev->info = &dc_ti_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = regmap_read(info->regmap, DC_TI_VBAT_ZSE_GE_REG, &val); + if (ret) + return ret; + + info->vbat_zse = sign_extend32(FIELD_GET(DC_TI_VBAT_ZSE, val), 3); + info->vbat_ge = sign_extend32(FIELD_GET(DC_TI_VBAT_GE, val), 3); + + dev_dbg(dev, "vbat-zse %d vbat-ge %d\n", info->vbat_zse, info->vbat_ge); + + ret = devm_iio_map_array_register(dev, indio_dev, dc_ti_adc_default_maps); + if (ret) + return ret; + + ret = devm_request_threaded_irq(dev, irq, NULL, dc_ti_adc_isr, + IRQF_ONESHOT, indio_dev->name, info); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct platform_device_id dc_ti_adc_ids[] = { + { .name = "chtdc_ti_adc" }, + { } +}; +MODULE_DEVICE_TABLE(platform, dc_ti_adc_ids); + +static struct platform_driver dc_ti_adc_driver = { + .driver = { + .name = "dc_ti_adc", + }, + .probe = dc_ti_adc_probe, + .id_table = dc_ti_adc_ids, +}; +module_platform_driver(dc_ti_adc_driver); + +MODULE_AUTHOR("Ramakrishna Pallala (Intel)"); +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_DESCRIPTION("Intel Dollar Cove (TI) GPADC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/mcp3564.c b/drivers/iio/adc/mcp3564.c index a68f1cd6883e..cd679ff10a97 100644 --- a/drivers/iio/adc/mcp3564.c +++ b/drivers/iio/adc/mcp3564.c @@ -1019,7 +1019,7 @@ static int mcp3564_parse_fw_children(struct iio_dev *indio_dev) channels = devm_kcalloc(dev, num_ch, sizeof(*channels), GFP_KERNEL); if (!channels) - return dev_err_probe(dev, -ENOMEM, "Can't allocate memory\n"); + return -ENOMEM; device_for_each_child_node_scoped(dev, child) { node_name = fwnode_get_name(child); diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c index 4ff88603e4fc..f7e7172ef4f6 100644 --- a/drivers/iio/adc/meson_saradc.c +++ b/drivers/iio/adc/meson_saradc.c @@ -1357,7 +1357,7 @@ static int meson_sar_adc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, "failed allocating iio device\n"); + return -ENOMEM; priv = iio_priv(indio_dev); init_completion(&priv->done); diff --git a/drivers/iio/adc/mt6577_auxadc.c b/drivers/iio/adc/mt6577_auxadc.c index 3343b54e8e44..fe9e3ece3fda 100644 --- a/drivers/iio/adc/mt6577_auxadc.c +++ b/drivers/iio/adc/mt6577_auxadc.c @@ -297,8 +297,7 @@ static int mt6577_auxadc_probe(struct platform_device *pdev) ret = devm_add_action_or_reset(&pdev->dev, mt6577_power_off, adc_dev); if (ret) - return dev_err_probe(&pdev->dev, ret, - "Failed to add action to managed power off\n"); + return ret; ret = devm_iio_device_register(&pdev->dev, indio_dev); if (ret < 0) diff --git a/drivers/iio/adc/mxs-lradc-adc.c b/drivers/iio/adc/mxs-lradc-adc.c index 92baf3f5f560..dda5182a5076 100644 --- a/drivers/iio/adc/mxs-lradc-adc.c +++ b/drivers/iio/adc/mxs-lradc-adc.c @@ -697,10 +697,8 @@ static int mxs_lradc_adc_probe(struct platform_device *pdev) /* Allocate the IIO device. */ iio = devm_iio_device_alloc(dev, sizeof(*adc)); - if (!iio) { - dev_err(dev, "Failed to allocate IIO device\n"); + if (!iio) return -ENOMEM; - } adc = iio_priv(iio); adc->lradc = lradc; diff --git a/drivers/iio/adc/pac1921.c b/drivers/iio/adc/pac1921.c index 72aa4ca2e5a4..35433250b008 100644 --- a/drivers/iio/adc/pac1921.c +++ b/drivers/iio/adc/pac1921.c @@ -1279,8 +1279,7 @@ static int pac1921_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, pac1921_regulator_disable, priv->vdd); if (ret) - return dev_err_probe(dev, ret, - "Cannot add action for vdd regulator disposal\n"); + return ret; msleep(PAC1921_POWERUP_TIME_MS); diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c index 09fe88eb3fb0..48df16509260 100644 --- a/drivers/iio/adc/pac1934.c +++ b/drivers/iio/adc/pac1934.c @@ -88,6 +88,7 @@ #define PAC1934_VPOWER_3_ADDR 0x19 #define PAC1934_VPOWER_4_ADDR 0x1A #define PAC1934_REFRESH_V_REG_ADDR 0x1F +#define PAC1934_SLOW_REG_ADDR 0x20 #define PAC1934_CTRL_STAT_REGS_ADDR 0x1C #define PAC1934_PID_REG_ADDR 0xFD #define PAC1934_MID_REG_ADDR 0xFE @@ -1265,8 +1266,23 @@ static int pac1934_chip_configure(struct pac1934_chip_info *info) /* no SLOW triggered REFRESH, clear POR */ regs[PAC1934_SLOW_REG_OFF] = 0; - ret = i2c_smbus_write_block_data(client, PAC1934_CTRL_STAT_REGS_ADDR, - ARRAY_SIZE(regs), (u8 *)regs); + /* + * Write the three bytes sequentially, as the device does not support + * block write. + */ + ret = i2c_smbus_write_byte_data(client, PAC1934_CTRL_STAT_REGS_ADDR, + regs[PAC1934_CHANNEL_DIS_REG_OFF]); + if (ret) + return ret; + + ret = i2c_smbus_write_byte_data(client, + PAC1934_CTRL_STAT_REGS_ADDR + PAC1934_NEG_PWR_REG_OFF, + regs[PAC1934_NEG_PWR_REG_OFF]); + if (ret) + return ret; + + ret = i2c_smbus_write_byte_data(client, PAC1934_SLOW_REG_ADDR, + regs[PAC1934_SLOW_REG_OFF]); if (ret) return ret; @@ -1455,13 +1471,6 @@ static int pac1934_prep_custom_attributes(struct pac1934_chip_info *info, return 0; } -static void pac1934_mutex_destroy(void *data) -{ - struct mutex *lock = data; - - mutex_destroy(lock); -} - static const struct iio_info pac1934_info = { .read_raw = pac1934_read_raw, .write_raw = pac1934_write_raw, @@ -1520,9 +1529,7 @@ static int pac1934_probe(struct i2c_client *client) return dev_err_probe(dev, ret, "parameter parsing returned an error\n"); - mutex_init(&info->lock); - ret = devm_add_action_or_reset(dev, pac1934_mutex_destroy, - &info->lock); + ret = devm_mutex_init(dev, &info->lock); if (ret < 0) return ret; diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c index 7c01e33be04c..3f433064618e 100644 --- a/drivers/iio/adc/palmas_gpadc.c +++ b/drivers/iio/adc/palmas_gpadc.c @@ -885,10 +885,8 @@ static int palmas_gpadc_probe(struct platform_device *pdev) return -EINVAL; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); - if (!indio_dev) { - dev_err(&pdev->dev, "iio_device_alloc failed\n"); + if (!indio_dev) return -ENOMEM; - } adc = iio_priv(indio_dev); adc->dev = &pdev->dev; diff --git a/drivers/iio/adc/rcar-gyroadc.c b/drivers/iio/adc/rcar-gyroadc.c index cc326f21d398..3a17b3898bf6 100644 --- a/drivers/iio/adc/rcar-gyroadc.c +++ b/drivers/iio/adc/rcar-gyroadc.c @@ -163,12 +163,10 @@ static int rcar_gyroadc_set_power(struct rcar_gyroadc *priv, bool on) { struct device *dev = priv->dev; - if (on) { + if (on) return pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); - return pm_runtime_put_autosuspend(dev); - } + + return pm_runtime_put_autosuspend(dev); } static int rcar_gyroadc_read_raw(struct iio_dev *indio_dev, diff --git a/drivers/iio/adc/rn5t618-adc.c b/drivers/iio/adc/rn5t618-adc.c index d6f6b351f2af..f78fc795b69a 100644 --- a/drivers/iio/adc/rn5t618-adc.c +++ b/drivers/iio/adc/rn5t618-adc.c @@ -199,10 +199,8 @@ static int rn5t618_adc_probe(struct platform_device *pdev) struct rn5t618 *rn5t618 = dev_get_drvdata(pdev->dev.parent); iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); - if (!iio_dev) { - dev_err(&pdev->dev, "failed allocating iio device\n"); + if (!iio_dev) return -ENOMEM; - } adc = iio_priv(iio_dev); adc->dev = &pdev->dev; diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c index bd62daea0a3e..6721da0ed7bb 100644 --- a/drivers/iio/adc/rockchip_saradc.c +++ b/drivers/iio/adc/rockchip_saradc.c @@ -466,8 +466,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); if (!indio_dev) - return dev_err_probe(&pdev->dev, -ENOMEM, - "failed allocating iio device\n"); + return -ENOMEM; info = iio_priv(indio_dev); @@ -527,8 +526,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev) ret = devm_add_action_or_reset(&pdev->dev, rockchip_saradc_regulator_disable, info); if (ret) - return dev_err_probe(&pdev->dev, ret, - "failed to register devm action\n"); + return ret; ret = regulator_get_voltage(info->vref); if (ret < 0) diff --git a/drivers/iio/adc/rohm-bd79112.c b/drivers/iio/adc/rohm-bd79112.c new file mode 100644 index 000000000000..d15e06c8b94d --- /dev/null +++ b/drivers/iio/adc/rohm-bd79112.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ROHM ADC driver for BD79112 signal monitoring hub. + * Copyright (C) 2025, ROHM Semiconductor. + * + * SPI communication derived from ad7923.c and ti-ads7950.c + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/bits.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/gpio/driver.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 <asm/byteorder.h> + +#include <linux/iio/adc-helpers.h> +#include <linux/iio/iio.h> + +#define BD79112_MAX_NUM_CHANNELS 32 + +struct bd79112_data { + struct spi_device *spi; + struct regmap *map; + struct device *dev; + struct gpio_chip gc; + unsigned long gpio_valid_mask; + unsigned int vref_mv; + struct spi_transfer read_xfer[2]; + struct spi_transfer write_xfer; + struct spi_message read_msg; + struct spi_message write_msg; + /* 16-bit TX, valid data in high byte */ + u8 read_tx[2] __aligned(IIO_DMA_MINALIGN); + /* 8-bit address followed by 8-bit data */ + u8 reg_write_tx[2]; + /* 12-bit of ADC data or 8 bit of reg data */ + __be16 read_rx; +}; + +/* + * The ADC data is read issuing SPI-command matching the channel number. + * We treat this as a register address. + */ +#define BD79112_REG_AGIO0A 0x00 +#define BD79112_REG_AGIO15B 0x1f + +/* + * ADC STATUS_FLAG appended to ADC data will be set, if the ADC result is being + * read for a channel, which input pin is muxed to be a GPIO. + */ +#define BD79112_ADC_STATUS_FLAG BIT(14) + +/* + * The BD79112 requires "R/W bit" to be set for SPI register (not ADC data) + * reads and an "IOSET bit" to be set for read/write operations (which aren't + * reading the ADC data). + */ +#define BD79112_BIT_RW BIT(4) +#define BD79112_BIT_IO BIT(5) + +#define BD79112_REG_GPI_VALUE_B8_15 (BD79112_BIT_IO | 0x0) +#define BD79112_REG_GPI_VALUE_B0_B7 (BD79112_BIT_IO | 0x1) +#define BD79112_REG_GPI_VALUE_A8_15 (BD79112_BIT_IO | 0x2) +#define BD79112_REG_GPI_VALUE_A0_A7 (BD79112_BIT_IO | 0x3) + +#define BD79112_REG_GPI_EN_B7_B15 (BD79112_BIT_IO | 0x4) +#define BD79112_REG_GPI_EN_B0_B7 (BD79112_BIT_IO | 0x5) +#define BD79112_REG_GPI_EN_A8_A15 (BD79112_BIT_IO | 0x6) +#define BD79112_REG_GPI_EN_A0_A7 (BD79112_BIT_IO | 0x7) + +#define BD79112_REG_GPO_EN_B7_B15 (BD79112_BIT_IO | 0x8) +#define BD79112_REG_GPO_EN_B0_B7 (BD79112_BIT_IO | 0x9) +#define BD79112_REG_GPO_EN_A8_A15 (BD79112_BIT_IO | 0xa) +#define BD79112_REG_GPO_EN_A0_A7 (BD79112_BIT_IO | 0xb) + +#define BD79112_NUM_GPIO_EN_REGS 8 +#define BD79112_FIRST_GPIO_EN_REG BD79112_REG_GPI_EN_B7_B15 + +#define BD79112_REG_GPO_VALUE_B8_15 (BD79112_BIT_IO | 0xc) +#define BD79112_REG_GPO_VALUE_B0_B7 (BD79112_BIT_IO | 0xd) +#define BD79112_REG_GPO_VALUE_A8_15 (BD79112_BIT_IO | 0xe) +#define BD79112_REG_GPO_VALUE_A0_A7 (BD79112_BIT_IO | 0xf) + +#define BD79112_REG_MAX BD79112_REG_GPO_VALUE_A0_A7 + +/* + * Read transaction consists of two 16-bit sequences separated by CSB. + * For register read, 'IOSET' bit must be set. For ADC read, IOSET is cleared + * and ADDR equals the channel number (0 ... 31). + * + * First 16-bit sequence, MOSI as below, MISO data ignored: + * - SCK: | 1 | 2 | 3 | 4 | 5 .. 8 | 9 .. 16 | + * - MOSI:| 0 | 0 | IOSET | RW (1) | ADDR | 8'b0 | + * + * CSB released and re-acquired between these sequences + * + * Second 16-bit sequence, MISO as below, MOSI data ignored: + * For Register read data is 8 bits: + * - SCK: | 1 .. 8 | 9 .. 16 | + * - MISO:| 8'b0 | 8-bit data | + * + * For ADC read data is 12 bits: + * - SCK: | 1 | 2 | 3 4 | 4 .. 16 | + * - MISO:| 0 | STATUS_FLAG | 2'b0 | 12-bit data | + * The 'STATUS_FLAG' is set if the read input pin was configured as a GPIO. + */ +static int bd79112_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct bd79112_data *data = context; + int ret; + + if (reg & BD79112_BIT_IO) + reg |= BD79112_BIT_RW; + + data->read_tx[0] = reg; + + ret = spi_sync(data->spi, &data->read_msg); + if (!ret) + *val = be16_to_cpu(data->read_rx); + + return ret; +} + +/* + * Write, single 16-bit sequence (broken down below): + * + * First 8-bit, MOSI as below, MISO data ignored: + * - SCK: | 1 | 2 | 3 | 4 | 5 .. 8 | + * - MOSI:| 0 | 0 |IOSET| RW(0) | ADDR | + * + * Last 8 SCK cycles (b8 ... b15), MISO contains register data, MOSI ignored. + * - SCK: | 9 .. 16 | + * - MISO:| data | + */ +static int bd79112_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct bd79112_data *data = context; + + data->reg_write_tx[0] = reg; + data->reg_write_tx[1] = val; + + return spi_sync(data->spi, &data->write_msg); +} + +static int _get_gpio_reg(unsigned int offset, unsigned int base) +{ + int regoffset = offset / 8; + + if (offset > 31) + return -EINVAL; + + return base - regoffset; +} + +#define GET_GPIO_BIT(offset) BIT((offset) % 8) +#define GET_GPO_EN_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPO_EN_A0_A7) +#define GET_GPI_EN_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPI_EN_A0_A7) +#define GET_GPO_VAL_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPO_VALUE_A0_A7) +#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, + }, +}; + +static const struct regmap_access_table bd79112_volatile_regs = { + .yes_ranges = &bd71815_volatile_ro_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bd71815_volatile_ro_ranges), +}; + +static const struct regmap_access_table bd79112_ro_regs = { + .no_ranges = &bd71815_volatile_ro_ranges[0], + .n_no_ranges = ARRAY_SIZE(bd71815_volatile_ro_ranges), +}; + +static const struct regmap_config bd79112_regmap = { + .reg_read = bd79112_reg_read, + .reg_write = bd79112_reg_write, + .volatile_table = &bd79112_volatile_regs, + .wr_table = &bd79112_ro_regs, + .cache_type = REGCACHE_MAPLE, + .max_register = BD79112_REG_MAX, +}; + +static int bd79112_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long m) +{ + struct bd79112_data *data = iio_priv(indio_dev); + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->map, chan->channel, val); + if (ret < 0) + return ret; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = data->vref_mv; + *val2 = 12; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info bd79112_info = { + .read_raw = bd79112_read_raw, +}; + +static const struct iio_chan_spec bd79112_chan_template = { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, +}; + +static int bd79112_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + + *valid_mask = data->gpio_valid_mask; + + return 0; +} + +static int bd79112_gpio_dir_get(struct gpio_chip *gc, unsigned int offset) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned int reg, bit, val; + int ret; + + bit = GET_GPIO_BIT(offset); + reg = GET_GPO_EN_REG(offset); + + ret = regmap_read(data->map, reg, &val); + if (ret) + return ret; + + if (bit & val) + return GPIO_LINE_DIRECTION_OUT; + + reg = GET_GPI_EN_REG(offset); + ret = regmap_read(data->map, reg, &val); + if (ret) + return ret; + + if (bit & val) + return GPIO_LINE_DIRECTION_IN; + + /* + * Ouch. Seems the pin is ADC input - shouldn't happen as changing mux + * at runtime is not supported and non GPIO pins should be invalidated + * by the valid_mask at probe. Maybe someone wrote a register bypassing + * the driver? + */ + dev_err(data->dev, "Pin not a GPIO\n"); + + return -EINVAL; +} + +static int bd79112_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned int reg, bit, val; + int ret; + + bit = GET_GPIO_BIT(offset); + reg = GET_GPI_VAL_REG(offset); + + ret = regmap_read(data->map, reg, &val); + if (ret) + return ret; + + return !!(val & bit); +} + +static int bd79112_gpio_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned int reg, bit; + + bit = GET_GPIO_BIT(offset); + reg = GET_GPO_VAL_REG(offset); + + return regmap_assign_bits(data->map, reg, bit, value); +} + +static int bd79112_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + unsigned long i, bank_mask; + + for_each_set_clump8(i, bank_mask, mask, gc->ngpio) { + unsigned long bank_bits; + unsigned int reg; + int ret; + + bank_bits = bitmap_get_value8(bits, i); + reg = BD79112_REG_GPO_VALUE_A0_A7 - i / 8; + ret = regmap_update_bits(data->map, reg, bank_mask, bank_bits); + if (ret) + return ret; + } + + return 0; +} + +static int bd79112_gpio_dir_set(struct bd79112_data *data, unsigned int offset, + int dir) +{ + unsigned int gpi_reg, gpo_reg, bit; + int ret; + + bit = GET_GPIO_BIT(offset); + gpi_reg = GET_GPI_EN_REG(offset); + gpo_reg = GET_GPO_EN_REG(offset); + + if (dir == GPIO_LINE_DIRECTION_OUT) { + ret = regmap_clear_bits(data->map, gpi_reg, bit); + if (ret) + return ret; + + return regmap_set_bits(data->map, gpo_reg, bit); + } + + ret = regmap_set_bits(data->map, gpi_reg, bit); + if (ret) + return ret; + + return regmap_clear_bits(data->map, gpo_reg, bit); +} + +static int bd79112_gpio_input(struct gpio_chip *gc, unsigned int offset) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + + return bd79112_gpio_dir_set(data, offset, GPIO_LINE_DIRECTION_IN); +} + +static int bd79112_gpio_output(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct bd79112_data *data = gpiochip_get_data(gc); + int ret; + + ret = bd79112_gpio_set(gc, offset, value); + if (ret) + return ret; + + return bd79112_gpio_dir_set(data, offset, GPIO_LINE_DIRECTION_OUT); +} + +static const struct gpio_chip bd79112_gpio_chip = { + .label = "bd79112-gpio", + .get_direction = bd79112_gpio_dir_get, + .direction_input = bd79112_gpio_input, + .direction_output = bd79112_gpio_output, + .get = bd79112_gpio_get, + .set = bd79112_gpio_set, + .set_multiple = bd79112_gpio_set_multiple, + .init_valid_mask = bd79112_gpio_init_valid_mask, + .can_sleep = true, + .ngpio = 32, + .base = -1, +}; + +static unsigned int bd79112_get_gpio_pins(const struct iio_chan_spec *cs, int num_channels) +{ + unsigned int i, gpio_channels; + + /* + * Let's initialize the mux config to say that all 32 channels are + * GPIOs. Then we can just loop through the iio_chan_spec and clear the + * bits for found ADC channels. + */ + gpio_channels = GENMASK(31, 0); + for (i = 0; i < num_channels; i++) + gpio_channels &= ~BIT(cs[i].channel); + + return gpio_channels; +} + +/* ADC channels as named in the data-sheet */ +static const char * const bd79112_chan_names[] = { + "AGIO0A", "AGIO1A", "AGIO2A", "AGIO3A", /* 0 - 3 */ + "AGIO4A", "AGIO5A", "AGIO6A", "AGIO7A", /* 4 - 7 */ + "AGIO8A", "AGIO9A", "AGIO10A", "AGIO11A", /* 8 - 11 */ + "AGIO12A", "AGIO13A", "AGIO14A", "AGIO15A", /* 12 - 15 */ + "AGIO0B", "AGIO1B", "AGIO2B", "AGIO3B", /* 16 - 19 */ + "AGIO4B", "AGIO5B", "AGIO6B", "AGIO7B", /* 20 - 23 */ + "AGIO8B", "AGIO9B", "AGIO10B", "AGIO11B", /* 24 - 27 */ + "AGIO12B", "AGIO13B", "AGIO14B", "AGIO15B", /* 28 - 31 */ +}; + +static int bd79112_probe(struct spi_device *spi) +{ + struct bd79112_data *data; + struct iio_dev *iio_dev; + struct iio_chan_spec *cs; + struct device *dev = &spi->dev; + unsigned long gpio_pins, pin; + unsigned int i; + int ret; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!iio_dev) + return -ENOMEM; + + data = iio_priv(iio_dev); + data->spi = spi; + data->dev = dev; + data->map = devm_regmap_init(dev, NULL, data, &bd79112_regmap); + if (IS_ERR(data->map)) + return dev_err_probe(dev, PTR_ERR(data->map), + "Failed to initialize Regmap\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "vdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get the Vdd\n"); + + data->vref_mv = ret / 1000; + + ret = devm_regulator_get_enable(dev, "iovdd"); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to enable I/O voltage\n"); + + data->read_xfer[0].tx_buf = &data->read_tx[0]; + data->read_xfer[0].len = sizeof(data->read_tx); + data->read_xfer[0].cs_change = 1; + data->read_xfer[1].rx_buf = &data->read_rx; + data->read_xfer[1].len = sizeof(data->read_rx); + spi_message_init_with_transfers(&data->read_msg, data->read_xfer, 2); + ret = devm_spi_optimize_message(dev, spi, &data->read_msg); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to optimize SPI read message\n"); + + data->write_xfer.tx_buf = &data->reg_write_tx[0]; + data->write_xfer.len = sizeof(data->reg_write_tx); + spi_message_init_with_transfers(&data->write_msg, &data->write_xfer, 1); + ret = devm_spi_optimize_message(dev, spi, &data->write_msg); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to optimize SPI write message\n"); + + ret = devm_iio_adc_device_alloc_chaninfo_se(dev, &bd79112_chan_template, + BD79112_MAX_NUM_CHANNELS - 1, + &cs); + + /* Register all pins as GPIOs if there are no ADC channels */ + if (ret == -ENOENT) + goto register_gpios; + + if (ret < 0) + return ret; + + iio_dev->num_channels = ret; + iio_dev->channels = cs; + + for (i = 0; i < iio_dev->num_channels; i++) + cs[i].datasheet_name = bd79112_chan_names[cs[i].channel]; + + iio_dev->info = &bd79112_info; + iio_dev->name = "bd79112"; + iio_dev->modes = INDIO_DIRECT_MODE; + + /* + * Ensure all channels are ADCs. This allows us to register the IIO + * device early (before checking which pins are to be used for GPIO) + * without having to worry about some pins being initially used for + * GPIO. + */ + for (i = 0; i < BD79112_NUM_GPIO_EN_REGS; i++) { + ret = regmap_write(data->map, BD79112_FIRST_GPIO_EN_REG + i, 0); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize channels\n"); + } + + ret = devm_iio_device_register(data->dev, iio_dev); + if (ret) + return dev_err_probe(data->dev, ret, "Failed to register ADC\n"); + +register_gpios: + gpio_pins = bd79112_get_gpio_pins(iio_dev->channels, + iio_dev->num_channels); + + /* If all channels are reserved for ADC, then we're done. */ + if (!gpio_pins) + return 0; + + /* Default all the GPIO pins to GPI */ + for_each_set_bit(pin, &gpio_pins, BD79112_MAX_NUM_CHANNELS) { + ret = bd79112_gpio_dir_set(data, pin, GPIO_LINE_DIRECTION_IN); + if (ret) + return dev_err_probe(dev, ret, + "Failed to mark pin as GPI\n"); + } + + data->gpio_valid_mask = gpio_pins; + data->gc = bd79112_gpio_chip; + data->gc.parent = dev; + + return devm_gpiochip_add_data(dev, &data->gc, data); +} + +static const struct of_device_id bd79112_of_match[] = { + { .compatible = "rohm,bd79112" }, + { } +}; +MODULE_DEVICE_TABLE(of, bd79112_of_match); + +static const struct spi_device_id bd79112_id[] = { + { "bd79112" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bd79112_id); + +static struct spi_driver bd79112_driver = { + .driver = { + .name = "bd79112", + .of_match_table = bd79112_of_match, + }, + .probe = bd79112_probe, + .id_table = bd79112_id, +}; +module_spi_driver(bd79112_driver); + +MODULE_AUTHOR("Matti Vaittinen <mazziesaccount@gmail.com>"); +MODULE_DESCRIPTION("Driver for ROHM BD79112 ADC/GPIO"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DRIVER"); diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c index cadb0446bc29..1010e0511b3e 100644 --- a/drivers/iio/adc/rzg2l_adc.c +++ b/drivers/iio/adc/rzg2l_adc.c @@ -248,7 +248,6 @@ static int rzg2l_adc_conversion(struct iio_dev *indio_dev, struct rzg2l_adc *adc rzg2l_adc_start_stop(adc, false); rpm_put: - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -410,7 +409,6 @@ static int rzg2l_adc_hw_init(struct device *dev, struct rzg2l_adc *adc) rzg2l_adc_writel(adc, RZG2L_ADM(3), reg); exit_hw_init: - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } diff --git a/drivers/iio/adc/spear_adc.c b/drivers/iio/adc/spear_adc.c index e3a865c79686..50b0a607baeb 100644 --- a/drivers/iio/adc/spear_adc.c +++ b/drivers/iio/adc/spear_adc.c @@ -14,6 +14,7 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/io.h> +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/err.h> #include <linux/completion.h> @@ -29,9 +30,9 @@ /* Bit definitions for SPEAR_ADC_STATUS */ #define SPEAR_ADC_STATUS_START_CONVERSION BIT(0) -#define SPEAR_ADC_STATUS_CHANNEL_NUM(x) ((x) << 1) +#define SPEAR_ADC_STATUS_CHANNEL_NUM_MASK GENMASK(3, 1) #define SPEAR_ADC_STATUS_ADC_ENABLE BIT(4) -#define SPEAR_ADC_STATUS_AVG_SAMPLE(x) ((x) << 5) +#define SPEAR_ADC_STATUS_AVG_SAMPLE_MASK GENMASK(8, 5) #define SPEAR_ADC_STATUS_VREF_INTERNAL BIT(9) #define SPEAR_ADC_DATA_MASK 0x03ff @@ -157,8 +158,8 @@ static int spear_adc_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_RAW: mutex_lock(&st->lock); - status = SPEAR_ADC_STATUS_CHANNEL_NUM(chan->channel) | - SPEAR_ADC_STATUS_AVG_SAMPLE(st->avg_samples) | + status = FIELD_PREP(SPEAR_ADC_STATUS_CHANNEL_NUM_MASK, chan->channel) | + FIELD_PREP(SPEAR_ADC_STATUS_AVG_SAMPLE_MASK, st->avg_samples) | SPEAR_ADC_STATUS_START_CONVERSION | SPEAR_ADC_STATUS_ADC_ENABLE; if (st->vref_external == 0) @@ -274,8 +275,7 @@ static int spear_adc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(dev, sizeof(struct spear_adc_state)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "failed allocating iio device\n"); + return -ENOMEM; st = iio_priv(indio_dev); st->dev = dev; diff --git a/drivers/iio/adc/stm32-adc-core.c b/drivers/iio/adc/stm32-adc-core.c index 3d800762c5fc..e39a4c0db25e 100644 --- a/drivers/iio/adc/stm32-adc-core.c +++ b/drivers/iio/adc/stm32-adc-core.c @@ -794,7 +794,6 @@ static int stm32_adc_probe(struct platform_device *pdev) goto err_irq_remove; } - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c index b9f93116e114..2d7f88459c7c 100644 --- a/drivers/iio/adc/stm32-adc.c +++ b/drivers/iio/adc/stm32-adc.c @@ -1528,7 +1528,6 @@ static int stm32_adc_single_conv(struct iio_dev *indio_dev, stm32_adc_conv_irq_disable(adc); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -1564,7 +1563,6 @@ static int stm32_adc_write_raw(struct iio_dev *indio_dev, adc->cfg->set_ovs(indio_dev, idx); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); adc->ovs_idx = idx; @@ -1759,7 +1757,6 @@ static int stm32_adc_update_scan_mode(struct iio_dev *indio_dev, adc->num_conv = bitmap_weight(scan_mask, iio_get_masklength(indio_dev)); ret = stm32_adc_conf_scan_seq(indio_dev, scan_mask); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -1808,7 +1805,6 @@ static int stm32_adc_debugfs_reg_access(struct iio_dev *indio_dev, else *readval = stm32_adc_readl(adc, reg); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; @@ -1954,7 +1950,6 @@ static int stm32_adc_buffer_postenable(struct iio_dev *indio_dev) err_clr_trig: stm32_adc_set_trig(indio_dev, NULL); err_pm_put: - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -1977,7 +1972,6 @@ static int stm32_adc_buffer_predisable(struct iio_dev *indio_dev) if (stm32_adc_set_trig(indio_dev, NULL)) dev_err(&indio_dev->dev, "Can't clear trigger\n"); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; @@ -2614,7 +2608,6 @@ static int stm32_adc_probe(struct platform_device *pdev) goto err_hw_stop; } - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); if (IS_ENABLED(CONFIG_DEBUG_FS)) diff --git a/drivers/iio/adc/stm32-dfsdm-adc.c b/drivers/iio/adc/stm32-dfsdm-adc.c index c2d21eecafe7..74b1b4dc6e81 100644 --- a/drivers/iio/adc/stm32-dfsdm-adc.c +++ b/drivers/iio/adc/stm32-dfsdm-adc.c @@ -1764,10 +1764,8 @@ static int stm32_dfsdm_adc_probe(struct platform_device *pdev) dev_data = of_device_get_match_data(dev); iio = devm_iio_device_alloc(dev, sizeof(*adc)); - if (!iio) { - dev_err(dev, "%s: Failed to allocate IIO\n", __func__); + if (!iio) return -ENOMEM; - } adc = iio_priv(iio); adc->dfsdm = dev_get_drvdata(dev->parent); diff --git a/drivers/iio/adc/stmpe-adc.c b/drivers/iio/adc/stmpe-adc.c index b0add5a2eab5..8e26c47edc08 100644 --- a/drivers/iio/adc/stmpe-adc.c +++ b/drivers/iio/adc/stmpe-adc.c @@ -267,10 +267,8 @@ static int stmpe_adc_probe(struct platform_device *pdev) return irq_adc; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct stmpe_adc)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } info = iio_priv(indio_dev); mutex_init(&info->lock); diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c index 6b8d6bee1873..479115ea50bf 100644 --- a/drivers/iio/adc/sun4i-gpadc-iio.c +++ b/drivers/iio/adc/sun4i-gpadc-iio.c @@ -154,7 +154,6 @@ static const struct regmap_config sun4i_gpadc_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .fast_io = true, }; static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel, @@ -245,7 +244,6 @@ static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val, *val = info->temp_data; ret = 0; - pm_runtime_mark_last_busy(indio_dev->dev.parent); err: pm_runtime_put_autosuspend(indio_dev->dev.parent); @@ -272,7 +270,6 @@ static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val) regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, val); - pm_runtime_mark_last_busy(indio_dev->dev.parent); pm_runtime_put_autosuspend(indio_dev->dev.parent); return 0; diff --git a/drivers/iio/adc/ti-adc081c.c b/drivers/iio/adc/ti-adc081c.c index 4f514db5c26e..8ef51c57912d 100644 --- a/drivers/iio/adc/ti-adc081c.c +++ b/drivers/iio/adc/ti-adc081c.c @@ -102,27 +102,23 @@ struct adcxx1c_model { int bits; }; -#define ADCxx1C_MODEL(_name, _bits) \ - { \ - .channels = _name ## _channels, \ - .bits = (_bits), \ - } - DEFINE_ADCxx1C_CHANNELS(adc081c, 8); DEFINE_ADCxx1C_CHANNELS(adc101c, 10); DEFINE_ADCxx1C_CHANNELS(adc121c, 12); -/* Model ids are indexes in _models array */ -enum adcxx1c_model_id { - ADC081C = 0, - ADC101C = 1, - ADC121C = 2, +static const struct adcxx1c_model adc081c_model = { + .channels = adc081c_channels, + .bits = 8, +}; + +static const struct adcxx1c_model adc101c_model = { + .channels = adc101c_channels, + .bits = 10, }; -static struct adcxx1c_model adcxx1c_models[] = { - ADCxx1C_MODEL(adc081c, 8), - ADCxx1C_MODEL(adc101c, 10), - ADCxx1C_MODEL(adc121c, 12), +static const struct adcxx1c_model adc121c_model = { + .channels = adc121c_channels, + .bits = 12, }; static const struct iio_info adc081c_info = { @@ -203,24 +199,24 @@ static int adc081c_probe(struct i2c_client *client) } static const struct i2c_device_id adc081c_id[] = { - { "adc081c", (kernel_ulong_t)&adcxx1c_models[ADC081C] }, - { "adc101c", (kernel_ulong_t)&adcxx1c_models[ADC101C] }, - { "adc121c", (kernel_ulong_t)&adcxx1c_models[ADC121C] }, + { "adc081c", (kernel_ulong_t)&adc081c_model }, + { "adc101c", (kernel_ulong_t)&adc101c_model }, + { "adc121c", (kernel_ulong_t)&adc121c_model }, { } }; MODULE_DEVICE_TABLE(i2c, adc081c_id); static const struct acpi_device_id adc081c_acpi_match[] = { /* Used on some AAEON boards */ - { "ADC081C", (kernel_ulong_t)&adcxx1c_models[ADC081C] }, + { "ADC081C", (kernel_ulong_t)&adc081c_model }, { } }; MODULE_DEVICE_TABLE(acpi, adc081c_acpi_match); static const struct of_device_id adc081c_of_match[] = { - { .compatible = "ti,adc081c", .data = &adcxx1c_models[ADC081C] }, - { .compatible = "ti,adc101c", .data = &adcxx1c_models[ADC101C] }, - { .compatible = "ti,adc121c", .data = &adcxx1c_models[ADC121C] }, + { .compatible = "ti,adc081c", .data = &adc081c_model }, + { .compatible = "ti,adc101c", .data = &adc101c_model }, + { .compatible = "ti,adc121c", .data = &adc121c_model }, { } }; MODULE_DEVICE_TABLE(of, adc081c_of_match); diff --git a/drivers/iio/adc/ti-adc084s021.c b/drivers/iio/adc/ti-adc084s021.c index 50a474f4d9f5..a100f770fa1c 100644 --- a/drivers/iio/adc/ti-adc084s021.c +++ b/drivers/iio/adc/ti-adc084s021.c @@ -200,10 +200,8 @@ static int adc084s021_probe(struct spi_device *spi) int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); - if (!indio_dev) { - dev_err(&spi->dev, "Failed to allocate IIO device\n"); + if (!indio_dev) return -ENOMEM; - } adc = iio_priv(indio_dev); adc->spi = spi; diff --git a/drivers/iio/adc/ti-adc12138.c b/drivers/iio/adc/ti-adc12138.c index 9dc465a10ffc..e5ec4b073daa 100644 --- a/drivers/iio/adc/ti-adc12138.c +++ b/drivers/iio/adc/ti-adc12138.c @@ -38,15 +38,13 @@ enum { struct adc12138 { struct spi_device *spi; unsigned int id; - /* conversion clock */ - struct clk *cclk; /* positive analog voltage reference */ struct regulator *vref_p; /* negative analog voltage reference */ struct regulator *vref_n; struct mutex lock; struct completion complete; - /* The number of cclk periods for the S/H's acquisition time */ + /* The number of conversion clock periods for the S/H's acquisition time */ unsigned int acquisition_time; /* * Maximum size needed: 16x 2 bytes ADC data + 8 bytes timestamp. @@ -400,6 +398,7 @@ static int adc12138_probe(struct spi_device *spi) { struct iio_dev *indio_dev; struct adc12138 *adc; + struct clk *cclk; int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); @@ -435,9 +434,14 @@ static int adc12138_probe(struct spi_device *spi) if (ret) adc->acquisition_time = 10; - adc->cclk = devm_clk_get(&spi->dev, NULL); - if (IS_ERR(adc->cclk)) - return PTR_ERR(adc->cclk); + ret = devm_request_irq(&spi->dev, spi->irq, adc12138_eoc_handler, + IRQF_TRIGGER_RISING, indio_dev->name, indio_dev); + if (ret) + return ret; + + cclk = devm_clk_get_enabled(&spi->dev, NULL); + if (IS_ERR(cclk)) + return PTR_ERR(cclk); adc->vref_p = devm_regulator_get(&spi->dev, "vref-p"); if (IS_ERR(adc->vref_p)) @@ -454,18 +458,9 @@ static int adc12138_probe(struct spi_device *spi) return ret; } - ret = devm_request_irq(&spi->dev, spi->irq, adc12138_eoc_handler, - IRQF_TRIGGER_RISING, indio_dev->name, indio_dev); - if (ret) - return ret; - - ret = clk_prepare_enable(adc->cclk); - if (ret) - return ret; - ret = regulator_enable(adc->vref_p); if (ret) - goto err_clk_disable; + return ret; if (!IS_ERR(adc->vref_n)) { ret = regulator_enable(adc->vref_n); @@ -496,8 +491,6 @@ err_vref_n_disable: regulator_disable(adc->vref_n); err_vref_p_disable: regulator_disable(adc->vref_p); -err_clk_disable: - clk_disable_unprepare(adc->cclk); return ret; } @@ -512,7 +505,6 @@ static void adc12138_remove(struct spi_device *spi) if (!IS_ERR(adc->vref_n)) regulator_disable(adc->vref_n); regulator_disable(adc->vref_p); - clk_disable_unprepare(adc->cclk); } static const struct of_device_id adc12138_dt_ids[] = { diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c index 1b46a8155803..4ae65793ad9b 100644 --- a/drivers/iio/adc/ti-adc128s052.c +++ b/drivers/iio/adc/ti-adc128s052.c @@ -99,51 +99,83 @@ static int adc128_read_raw(struct iio_dev *indio_dev, .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ } -static const struct iio_chan_spec adc128s052_channels[] = { +static const struct iio_chan_spec simple_1chan_adc_channels[] = { + ADC128_VOLTAGE_CHANNEL(0), +}; + +static const struct iio_chan_spec simple_2chan_adc_channels[] = { ADC128_VOLTAGE_CHANNEL(0), ADC128_VOLTAGE_CHANNEL(1), - ADC128_VOLTAGE_CHANNEL(2), - ADC128_VOLTAGE_CHANNEL(3), - ADC128_VOLTAGE_CHANNEL(4), - ADC128_VOLTAGE_CHANNEL(5), - ADC128_VOLTAGE_CHANNEL(6), - ADC128_VOLTAGE_CHANNEL(7), }; -static const struct iio_chan_spec adc122s021_channels[] = { +static const struct iio_chan_spec simple_4chan_adc_channels[] = { ADC128_VOLTAGE_CHANNEL(0), ADC128_VOLTAGE_CHANNEL(1), + ADC128_VOLTAGE_CHANNEL(2), + ADC128_VOLTAGE_CHANNEL(3), }; -static const struct iio_chan_spec adc124s021_channels[] = { +static const struct iio_chan_spec simple_8chan_adc_channels[] = { ADC128_VOLTAGE_CHANNEL(0), ADC128_VOLTAGE_CHANNEL(1), ADC128_VOLTAGE_CHANNEL(2), ADC128_VOLTAGE_CHANNEL(3), + ADC128_VOLTAGE_CHANNEL(4), + ADC128_VOLTAGE_CHANNEL(5), + ADC128_VOLTAGE_CHANNEL(6), + ADC128_VOLTAGE_CHANNEL(7), }; static const char * const bd79104_regulators[] = { "iovdd" }; -static const struct adc128_configuration adc128_config[] = { - { - .channels = adc128s052_channels, - .num_channels = ARRAY_SIZE(adc128s052_channels), - .refname = "vref", - }, { - .channels = adc122s021_channels, - .num_channels = ARRAY_SIZE(adc122s021_channels), - .refname = "vref", - }, { - .channels = adc124s021_channels, - .num_channels = ARRAY_SIZE(adc124s021_channels), - .refname = "vref", - }, { - .channels = adc128s052_channels, - .num_channels = ARRAY_SIZE(adc128s052_channels), - .refname = "vdd", - .other_regulators = &bd79104_regulators, - .num_other_regulators = 1, - }, +static const struct adc128_configuration adc122s_config = { + .channels = simple_2chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_2chan_adc_channels), + .refname = "vref", +}; + +static const struct adc128_configuration adc124s_config = { + .channels = simple_4chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_4chan_adc_channels), + .refname = "vref", +}; + +static const struct adc128_configuration adc128s_config = { + .channels = simple_8chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_8chan_adc_channels), + .refname = "vref", +}; + +static const struct adc128_configuration bd79100_config = { + .channels = simple_1chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_1chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, +}; + +static const struct adc128_configuration bd79101_config = { + .channels = simple_2chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_2chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, +}; + +static const struct adc128_configuration bd79102_config = { + .channels = simple_4chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_4chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, +}; + +static const struct adc128_configuration bd79104_config = { + .channels = simple_8chan_adc_channels, + .num_channels = ARRAY_SIZE(simple_8chan_adc_channels), + .refname = "vdd", + .other_regulators = &bd79104_regulators, + .num_other_regulators = 1, }; static const struct iio_info adc128_info = { @@ -199,33 +231,41 @@ static int adc128_probe(struct spi_device *spi) } static const struct of_device_id adc128_of_match[] = { - { .compatible = "ti,adc128s052", .data = &adc128_config[0] }, - { .compatible = "ti,adc122s021", .data = &adc128_config[1] }, - { .compatible = "ti,adc122s051", .data = &adc128_config[1] }, - { .compatible = "ti,adc122s101", .data = &adc128_config[1] }, - { .compatible = "ti,adc124s021", .data = &adc128_config[2] }, - { .compatible = "ti,adc124s051", .data = &adc128_config[2] }, - { .compatible = "ti,adc124s101", .data = &adc128_config[2] }, - { .compatible = "rohm,bd79104", .data = &adc128_config[3] }, + { .compatible = "ti,adc128s052", .data = &adc128s_config }, + { .compatible = "ti,adc122s021", .data = &adc122s_config }, + { .compatible = "ti,adc122s051", .data = &adc122s_config }, + { .compatible = "ti,adc122s101", .data = &adc122s_config }, + { .compatible = "ti,adc124s021", .data = &adc124s_config }, + { .compatible = "ti,adc124s051", .data = &adc124s_config }, + { .compatible = "ti,adc124s101", .data = &adc124s_config }, + { .compatible = "rohm,bd79100", .data = &bd79100_config }, + { .compatible = "rohm,bd79101", .data = &bd79101_config }, + { .compatible = "rohm,bd79102", .data = &bd79102_config }, + { .compatible = "rohm,bd79103", .data = &bd79104_config }, + { .compatible = "rohm,bd79104", .data = &bd79104_config }, { } }; MODULE_DEVICE_TABLE(of, adc128_of_match); static const struct spi_device_id adc128_id[] = { - { "adc128s052", (kernel_ulong_t)&adc128_config[0] }, - { "adc122s021", (kernel_ulong_t)&adc128_config[1] }, - { "adc122s051", (kernel_ulong_t)&adc128_config[1] }, - { "adc122s101", (kernel_ulong_t)&adc128_config[1] }, - { "adc124s021", (kernel_ulong_t)&adc128_config[2] }, - { "adc124s051", (kernel_ulong_t)&adc128_config[2] }, - { "adc124s101", (kernel_ulong_t)&adc128_config[2] }, - { "bd79104", (kernel_ulong_t)&adc128_config[3] }, + { "adc128s052", (kernel_ulong_t)&adc128s_config }, + { "adc122s021", (kernel_ulong_t)&adc122s_config }, + { "adc122s051", (kernel_ulong_t)&adc122s_config }, + { "adc122s101", (kernel_ulong_t)&adc122s_config }, + { "adc124s021", (kernel_ulong_t)&adc124s_config }, + { "adc124s051", (kernel_ulong_t)&adc124s_config }, + { "adc124s101", (kernel_ulong_t)&adc124s_config }, + { "bd79100", (kernel_ulong_t)&bd79100_config }, + { "bd79101", (kernel_ulong_t)&bd79101_config }, + { "bd79102", (kernel_ulong_t)&bd79102_config }, + { "bd79103", (kernel_ulong_t)&bd79104_config }, + { "bd79104", (kernel_ulong_t)&bd79104_config }, { } }; MODULE_DEVICE_TABLE(spi, adc128_id); static const struct acpi_device_id adc128_acpi_match[] = { - { "AANT1280", (kernel_ulong_t)&adc128_config[2] }, + { "AANT1280", (kernel_ulong_t)&adc124s_config }, { } }; MODULE_DEVICE_TABLE(acpi, adc128_acpi_match); diff --git a/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c index 48549d617e5f..f2a93c63ca14 100644 --- a/drivers/iio/adc/ti-ads1015.c +++ b/drivers/iio/adc/ti-ads1015.c @@ -374,12 +374,10 @@ static int ads1015_set_power_state(struct ads1015_data *data, bool on) int ret; struct device *dev = regmap_get_device(data->regmap); - if (on) { + if (on) ret = pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); + else ret = pm_runtime_put_autosuspend(dev); - } return ret < 0 ? ret : 0; } diff --git a/drivers/iio/adc/ti-ads1100.c b/drivers/iio/adc/ti-ads1100.c index b0790e300b18..aa8946063c7d 100644 --- a/drivers/iio/adc/ti-ads1100.c +++ b/drivers/iio/adc/ti-ads1100.c @@ -105,7 +105,6 @@ static int ads1100_get_adc_result(struct ads1100_data *data, int chan, int *val) ret = i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer)); - pm_runtime_mark_last_busy(&data->client->dev); pm_runtime_put_autosuspend(&data->client->dev); if (ret < 0) { diff --git a/drivers/iio/adc/ti-ads1119.c b/drivers/iio/adc/ti-ads1119.c index d2f86e1ec656..c9cedc59cdcd 100644 --- a/drivers/iio/adc/ti-ads1119.c +++ b/drivers/iio/adc/ti-ads1119.c @@ -291,7 +291,6 @@ static int ads1119_single_conversion(struct ads1119_state *st, *val = sign_extend32(sample, chan->scan_type.realbits - 1); ret = IIO_VAL_INT; pdown: - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -470,7 +469,6 @@ static int ads1119_triggered_buffer_postdisable(struct iio_dev *indio_dev) if (ret) return ret; - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; @@ -693,8 +691,7 @@ static int ads1119_probe(struct i2c_client *client) indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "Failed to allocate IIO device\n"); + return -ENOMEM; st = iio_priv(indio_dev); st->client = client; @@ -750,8 +747,7 @@ static int ads1119_probe(struct i2c_client *client) indio_dev->name, iio_device_id(indio_dev)); if (!st->trig) - return dev_err_probe(dev, -ENOMEM, - "Failed to allocate IIO trigger\n"); + return -ENOMEM; st->trig->ops = &ads1119_trigger_ops; iio_trigger_set_drvdata(st->trig, indio_dev); @@ -778,8 +774,7 @@ static int ads1119_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, ads1119_powerdown, st); if (ret) - return dev_err_probe(dev, ret, - "Failed to add powerdown action\n"); + return ret; return devm_iio_device_register(dev, indio_dev); } diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c index b18f30d3fdbe..742acc6d8cf9 100644 --- a/drivers/iio/adc/ti-ads131e08.c +++ b/drivers/iio/adc/ti-ads131e08.c @@ -807,10 +807,8 @@ static int ads131e08_probe(struct spi_device *spi) } indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); - if (!indio_dev) { - dev_err(&spi->dev, "failed to allocate IIO device\n"); + if (!indio_dev) return -ENOMEM; - } st = iio_priv(indio_dev); st->info = info; @@ -841,10 +839,8 @@ static int ads131e08_probe(struct spi_device *spi) st->trig = devm_iio_trigger_alloc(&spi->dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); - if (!st->trig) { - dev_err(&spi->dev, "failed to allocate IIO trigger\n"); + if (!st->trig) return -ENOMEM; - } st->trig->ops = &ads131e08_trigger_ops; st->trig->dev.parent = &spi->dev; diff --git a/drivers/iio/adc/ti-ads7924.c b/drivers/iio/adc/ti-ads7924.c index b1f745f75dbe..bbcc4fc22b6e 100644 --- a/drivers/iio/adc/ti-ads7924.c +++ b/drivers/iio/adc/ti-ads7924.c @@ -355,8 +355,7 @@ static int ads7924_probe(struct i2c_client *client) indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "failed to allocate iio device\n"); + return -ENOMEM; data = iio_priv(indio_dev); @@ -399,8 +398,7 @@ static int ads7924_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, ads7924_reg_disable, data->vref_reg); if (ret) - return dev_err_probe(dev, ret, - "failed to add regulator disable action\n"); + return ret; ret = ads7924_reset(indio_dev); if (ret < 0) @@ -414,8 +412,7 @@ static int ads7924_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, ads7924_set_idle_mode, data); if (ret) - return dev_err_probe(dev, ret, - "failed to add idle mode action\n"); + return ret; /* Use minimum signal acquire time. */ ret = regmap_update_bits(data->regmap, ADS7924_ACQCONFIG_REG, diff --git a/drivers/iio/adc/ti-tsc2046.c b/drivers/iio/adc/ti-tsc2046.c index 74471f08662e..8eb717b11cff 100644 --- a/drivers/iio/adc/ti-tsc2046.c +++ b/drivers/iio/adc/ti-tsc2046.c @@ -535,8 +535,7 @@ static enum hrtimer_restart tsc2046_adc_timer(struct hrtimer *hrtimer) if (priv->poll_cnt < TI_TSC2046_POLL_CNT) { priv->poll_cnt++; hrtimer_start(&priv->trig_timer, - ns_to_ktime(priv->scan_interval_us * - NSEC_PER_USEC), + us_to_ktime(priv->scan_interval_us), HRTIMER_MODE_REL_SOFT); if (priv->poll_cnt >= TI_TSC2046_MIN_POLL_CNT) { @@ -605,8 +604,7 @@ static void tsc2046_adc_reenable_trigger(struct iio_trigger *trig) * many samples. Reduce the sample rate for default (touchscreen) use * case. */ - tim = ns_to_ktime((priv->scan_interval_us - priv->time_per_scan_us) * - NSEC_PER_USEC); + tim = us_to_ktime(priv->scan_interval_us - priv->time_per_scan_us); hrtimer_start(&priv->trig_timer, tim, HRTIMER_MODE_REL_SOFT); } diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c index fe1509d3b1e7..99f274adc870 100644 --- a/drivers/iio/adc/ti_am335x_adc.c +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -631,10 +631,9 @@ static int tiadc_probe(struct platform_device *pdev) } indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_dev)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed to allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } + adc_dev = iio_priv(indio_dev); adc_dev->mfd_tscadc = ti_tscadc_dev_get(pdev); diff --git a/drivers/iio/adc/twl4030-madc.c b/drivers/iio/adc/twl4030-madc.c index 0ea51ddeaa0a..fe3b31ec976e 100644 --- a/drivers/iio/adc/twl4030-madc.c +++ b/drivers/iio/adc/twl4030-madc.c @@ -758,10 +758,8 @@ static int twl4030_madc_probe(struct platform_device *pdev) } iio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*madc)); - if (!iio_dev) { - dev_err(&pdev->dev, "failed allocating iio device\n"); + if (!iio_dev) return -ENOMEM; - } madc = iio_priv(iio_dev); madc->dev = &pdev->dev; diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c index 1b3b1843a801..d7182ed0d2a7 100644 --- a/drivers/iio/adc/vf610_adc.c +++ b/drivers/iio/adc/vf610_adc.c @@ -832,7 +832,7 @@ static int vf610_adc_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct vf610_adc)); if (!indio_dev) - return dev_err_probe(&pdev->dev, -ENOMEM, "Failed allocating iio device\n"); + return -ENOMEM; info = iio_priv(indio_dev); info->dev = &pdev->dev; diff --git a/drivers/iio/adc/viperboard_adc.c b/drivers/iio/adc/viperboard_adc.c index 1028b101cf56..9bb0b83c8f67 100644 --- a/drivers/iio/adc/viperboard_adc.c +++ b/drivers/iio/adc/viperboard_adc.c @@ -113,10 +113,8 @@ static int vprbrd_adc_probe(struct platform_device *pdev) /* registering iio */ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); - if (!indio_dev) { - dev_err(&pdev->dev, "failed allocating iio device\n"); + if (!indio_dev) return -ENOMEM; - } adc = iio_priv(indio_dev); adc->vb = vb; diff --git a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c index 76dd0343f5f7..124470c92529 100644 --- a/drivers/iio/adc/xilinx-ams.c +++ b/drivers/iio/adc/xilinx-ams.c @@ -118,7 +118,7 @@ #define AMS_ALARM_THRESHOLD_OFF_10 0x10 #define AMS_ALARM_THRESHOLD_OFF_20 0x20 -#define AMS_ALARM_THR_DIRECT_MASK BIT(1) +#define AMS_ALARM_THR_DIRECT_MASK BIT(0) #define AMS_ALARM_THR_MIN 0x0000 #define AMS_ALARM_THR_MAX (BIT(16) - 1) @@ -389,6 +389,29 @@ static void ams_update_pl_alarm(struct ams *ams, unsigned long alarm_mask) ams_pl_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK, cfg); } +static void ams_unmask(struct ams *ams) +{ + unsigned int status, unmask; + + status = readl(ams->base + AMS_ISR_0); + + /* Clear those bits which are not active anymore */ + unmask = (ams->current_masked_alarm ^ status) & ams->current_masked_alarm; + + /* Clear status of disabled alarm */ + unmask |= ams->intr_mask; + + ams->current_masked_alarm &= status; + + /* Also clear those which are masked out anyway */ + ams->current_masked_alarm &= ~ams->intr_mask; + + /* Clear the interrupts before we unmask them */ + writel(unmask, ams->base + AMS_ISR_0); + + ams_update_intrmask(ams, ~AMS_ALARM_MASK, ~AMS_ALARM_MASK); +} + static void ams_update_alarm(struct ams *ams, unsigned long alarm_mask) { unsigned long flags; @@ -401,6 +424,7 @@ static void ams_update_alarm(struct ams *ams, unsigned long alarm_mask) spin_lock_irqsave(&ams->intr_lock, flags); ams_update_intrmask(ams, AMS_ISR0_ALARM_MASK, ~alarm_mask); + ams_unmask(ams); spin_unlock_irqrestore(&ams->intr_lock, flags); } @@ -1035,28 +1059,9 @@ static void ams_handle_events(struct iio_dev *indio_dev, unsigned long events) static void ams_unmask_worker(struct work_struct *work) { struct ams *ams = container_of(work, struct ams, ams_unmask_work.work); - unsigned int status, unmask; spin_lock_irq(&ams->intr_lock); - - status = readl(ams->base + AMS_ISR_0); - - /* Clear those bits which are not active anymore */ - unmask = (ams->current_masked_alarm ^ status) & ams->current_masked_alarm; - - /* Clear status of disabled alarm */ - unmask |= ams->intr_mask; - - ams->current_masked_alarm &= status; - - /* Also clear those which are masked out anyway */ - ams->current_masked_alarm &= ~ams->intr_mask; - - /* Clear the interrupts before we unmask them */ - writel(unmask, ams->base + AMS_ISR_0); - - ams_update_intrmask(ams, ~AMS_ALARM_MASK, ~AMS_ALARM_MASK); - + ams_unmask(ams); spin_unlock_irq(&ams->intr_lock); /* If still pending some alarm re-trigger the timer */ diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c index 4befc9f55201..3e27385069ed 100644 --- a/drivers/iio/buffer/industrialio-buffer-cb.c +++ b/drivers/iio/buffer/industrialio-buffer-cb.c @@ -68,7 +68,6 @@ struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, cb_buff->private = private; cb_buff->cb = cb; cb_buff->buffer.access = &iio_cb_access; - INIT_LIST_HEAD(&cb_buff->buffer.demux_list); cb_buff->channels = iio_channel_get_all(dev); if (IS_ERR(cb_buff->channels)) { diff --git a/drivers/iio/chemical/atlas-sensor.c b/drivers/iio/chemical/atlas-sensor.c index 1daaa36f87a9..8bbba85af699 100644 --- a/drivers/iio/chemical/atlas-sensor.c +++ b/drivers/iio/chemical/atlas-sensor.c @@ -425,7 +425,6 @@ static int atlas_buffer_predisable(struct iio_dev *indio_dev) if (ret) return ret; - pm_runtime_mark_last_busy(&data->client->dev); ret = pm_runtime_put_autosuspend(&data->client->dev); if (ret) return ret; @@ -491,7 +490,6 @@ static int atlas_read_measurement(struct atlas_data *data, int reg, __be32 *val) ret = regmap_bulk_read(data->regmap, reg, val, sizeof(*val)); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; diff --git a/drivers/iio/chemical/bme680_core.c b/drivers/iio/chemical/bme680_core.c index 61d446fd456c..70f81c4a96ba 100644 --- a/drivers/iio/chemical/bme680_core.c +++ b/drivers/iio/chemical/bme680_core.c @@ -950,7 +950,6 @@ static int bme680_read_raw(struct iio_dev *indio_dev, return ret; ret = __bme680_read_raw(indio_dev, chan, val, val2, mask); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -1021,7 +1020,6 @@ static int bme680_write_raw(struct iio_dev *indio_dev, return ret; ret = __bme680_write_raw(indio_dev, chan, val, val2, mask); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -1140,7 +1138,6 @@ static int bme680_buffer_postdisable(struct iio_dev *indio_dev) struct bme680_data *data = iio_priv(indio_dev); struct device *dev = regmap_get_device(data->regmap); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; } diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c index 6cec60074827..86bde4a91bf7 100644 --- a/drivers/iio/chemical/ens160_core.c +++ b/drivers/iio/chemical/ens160_core.c @@ -305,8 +305,7 @@ static int ens160_setup_trigger(struct iio_dev *indio_dev, int irq) trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); if (!trig) - return dev_err_probe(dev, -ENOMEM, - "failed to allocate trigger\n"); + return -ENOMEM; trig->ops = &ens160_trigger_ops; iio_trigger_set_drvdata(trig, indio_dev); diff --git a/drivers/iio/chemical/scd30_core.c b/drivers/iio/chemical/scd30_core.c index 5df1926cd5d9..a665fcb78806 100644 --- a/drivers/iio/chemical/scd30_core.c +++ b/drivers/iio/chemical/scd30_core.c @@ -635,7 +635,7 @@ static int scd30_setup_trigger(struct iio_dev *indio_dev) trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); if (!trig) - return dev_err_probe(dev, -ENOMEM, "failed to allocate trigger\n"); + return -ENOMEM; trig->ops = &scd30_trigger_ops; iio_trigger_set_drvdata(trig, indio_dev); diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c index 48193937275b..5540e2d28f4a 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -163,7 +163,6 @@ int hid_sensor_power_state(struct hid_sensor_common *st, bool state) ret = pm_runtime_resume_and_get(&st->pdev->dev); } else { atomic_dec(&st->user_requested_state); - pm_runtime_mark_last_busy(&st->pdev->dev); pm_runtime_use_autosuspend(&st->pdev->dev); ret = pm_runtime_put_autosuspend(&st->pdev->dev); } diff --git a/drivers/iio/common/scmi_sensors/scmi_iio.c b/drivers/iio/common/scmi_sensors/scmi_iio.c index da516c46e057..39c61c47022a 100644 --- a/drivers/iio/common/scmi_sensors/scmi_iio.c +++ b/drivers/iio/common/scmi_sensors/scmi_iio.c @@ -521,9 +521,9 @@ static int scmi_iio_set_sampling_freq_avail(struct iio_dev *iio_dev) int i; sensor->freq_avail = - devm_kzalloc(&iio_dev->dev, - sizeof(*sensor->freq_avail) * - (sensor->sensor_info->intervals.count * 2), + devm_kcalloc(&iio_dev->dev, + array_size(sensor->sensor_info->intervals.count, 2), + sizeof(*sensor->freq_avail), GFP_KERNEL); if (!sensor->freq_avail) return -ENOMEM; @@ -597,8 +597,8 @@ scmi_alloc_iiodev(struct scmi_device *sdev, iiodev->info = &scmi_iio_info; iio_channels = - devm_kzalloc(dev, - sizeof(*iio_channels) * (iiodev->num_channels), + devm_kcalloc(dev, iiodev->num_channels, + sizeof(*iio_channels), GFP_KERNEL); if (!iio_channels) return ERR_PTR(-ENOMEM); diff --git a/drivers/iio/dac/ad5360.c b/drivers/iio/dac/ad5360.c index a57b0a093112..8271849b1c83 100644 --- a/drivers/iio/dac/ad5360.c +++ b/drivers/iio/dac/ad5360.c @@ -262,7 +262,7 @@ static int ad5360_update_ctrl(struct iio_dev *indio_dev, unsigned int set, unsigned int clr) { struct ad5360_state *st = iio_priv(indio_dev); - unsigned int ret; + int ret; mutex_lock(&st->lock); diff --git a/drivers/iio/dac/ad5380.c b/drivers/iio/dac/ad5380.c index 0ddce7b218e3..8b813cee7625 100644 --- a/drivers/iio/dac/ad5380.c +++ b/drivers/iio/dac/ad5380.c @@ -371,10 +371,8 @@ static int ad5380_probe(struct device *dev, struct regmap *regmap, int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); - if (indio_dev == NULL) { - dev_err(dev, "Failed to allocate iio device\n"); + if (indio_dev == NULL) return -ENOMEM; - } st = iio_priv(indio_dev); diff --git a/drivers/iio/dac/ad5421.c b/drivers/iio/dac/ad5421.c index 1462ee640b16..d9d7031c4432 100644 --- a/drivers/iio/dac/ad5421.c +++ b/drivers/iio/dac/ad5421.c @@ -186,7 +186,7 @@ static int ad5421_update_ctrl(struct iio_dev *indio_dev, unsigned int set, unsigned int clr) { struct ad5421_state *st = iio_priv(indio_dev); - unsigned int ret; + int ret; mutex_lock(&st->lock); diff --git a/drivers/iio/dac/ad5764.c b/drivers/iio/dac/ad5764.c index 26c049d5b73a..fbbd7105a80c 100644 --- a/drivers/iio/dac/ad5764.c +++ b/drivers/iio/dac/ad5764.c @@ -278,10 +278,8 @@ static int ad5764_probe(struct spi_device *spi) int ret; indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); - if (indio_dev == NULL) { - dev_err(&spi->dev, "Failed to allocate iio device\n"); + if (indio_dev == NULL) return -ENOMEM; - } st = iio_priv(indio_dev); spi_set_drvdata(spi, indio_dev); diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c index 41582f2b90fb..ae7297f08398 100644 --- a/drivers/iio/dac/ad5791.c +++ b/drivers/iio/dac/ad5791.c @@ -80,8 +80,6 @@ struct ad5791_chip_info { /** * struct ad5791_state - driver instance specific data * @spi: spi_device - * @reg_vdd: positive supply regulator - * @reg_vss: negative supply regulator * @gpio_reset: reset gpio * @gpio_clear: clear gpio * @gpio_ldac: load dac gpio @@ -100,8 +98,6 @@ struct ad5791_chip_info { */ struct ad5791_state { struct spi_device *spi; - struct regulator *reg_vdd; - struct regulator *reg_vss; struct gpio_desc *gpio_reset; struct gpio_desc *gpio_clear; struct gpio_desc *gpio_ldac; diff --git a/drivers/iio/dac/ds4424.c b/drivers/iio/dac/ds4424.c index a26a99753418..a8198ba4f98a 100644 --- a/drivers/iio/dac/ds4424.c +++ b/drivers/iio/dac/ds4424.c @@ -221,10 +221,8 @@ static int ds4424_probe(struct i2c_client *client) int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio dev alloc failed.\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); diff --git a/drivers/iio/dac/stm32-dac.c b/drivers/iio/dac/stm32-dac.c index 344388338d9b..b860e18d52a1 100644 --- a/drivers/iio/dac/stm32-dac.c +++ b/drivers/iio/dac/stm32-dac.c @@ -82,9 +82,11 @@ static int stm32_dac_set_enable_state(struct iio_dev *indio_dev, int ch, ret = regmap_update_bits(dac->common->regmap, STM32_DAC_CR, msk, en); mutex_unlock(&dac->lock); - if (ret < 0) { + if (ret) { dev_err(&indio_dev->dev, "%s failed\n", str_enable_disable(en)); - goto err_put_pm; + if (enable) + pm_runtime_put_autosuspend(dev); + return ret; } /* @@ -95,20 +97,10 @@ static int stm32_dac_set_enable_state(struct iio_dev *indio_dev, int ch, if (en && dac->common->hfsel) udelay(1); - if (!enable) { - pm_runtime_mark_last_busy(dev); + if (!enable) pm_runtime_put_autosuspend(dev); - } return 0; - -err_put_pm: - if (enable) { - pm_runtime_mark_last_busy(dev); - pm_runtime_put_autosuspend(dev); - } - - return ret; } static int stm32_dac_get_value(struct stm32_dac *dac, int channel, int *val) @@ -349,7 +341,6 @@ static int stm32_dac_probe(struct platform_device *pdev) if (ret) goto err_pm_put; - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return 0; diff --git a/drivers/iio/dac/ti-dac7311.c b/drivers/iio/dac/ti-dac7311.c index 3d2ce61f0db6..5c1c5213962f 100644 --- a/drivers/iio/dac/ti-dac7311.c +++ b/drivers/iio/dac/ti-dac7311.c @@ -242,10 +242,8 @@ static int ti_dac_probe(struct spi_device *spi) int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(*ti_dac)); - if (!indio_dev) { - dev_err(dev, "can not allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } spi->mode = SPI_MODE_1; spi->bits_per_word = 16; diff --git a/drivers/iio/frequency/adf4350.c b/drivers/iio/frequency/adf4350.c index 47f1c7e9efa9..ed1741165f55 100644 --- a/drivers/iio/frequency/adf4350.c +++ b/drivers/iio/frequency/adf4350.c @@ -149,6 +149,19 @@ static int adf4350_set_freq(struct adf4350_state *st, unsigned long long freq) if (freq > ADF4350_MAX_OUT_FREQ || freq < st->min_out_freq) return -EINVAL; + st->r4_rf_div_sel = 0; + + /* + * !\TODO: The below computation is making sure we get a power of 2 + * shift (st->r4_rf_div_sel) so that freq becomes higher or equal to + * ADF4350_MIN_VCO_FREQ. This might be simplified with fls()/fls_long() + * and friends. + */ + while (freq < ADF4350_MIN_VCO_FREQ) { + freq <<= 1; + st->r4_rf_div_sel++; + } + if (freq > ADF4350_MAX_FREQ_45_PRESC) { prescaler = ADF4350_REG1_PRESCALER; mdiv = 75; @@ -157,13 +170,6 @@ static int adf4350_set_freq(struct adf4350_state *st, unsigned long long freq) mdiv = 23; } - st->r4_rf_div_sel = 0; - - while (freq < ADF4350_MIN_VCO_FREQ) { - freq <<= 1; - st->r4_rf_div_sel++; - } - /* * Allow a predefined reference division factor * if not set, compute our own @@ -673,8 +679,7 @@ static int adf4350_probe(struct spi_device *spi) ret = devm_add_action_or_reset(&spi->dev, adf4350_power_down, indio_dev); if (ret) - return dev_err_probe(&spi->dev, ret, - "Failed to add action to managed power down\n"); + return ret; return devm_iio_device_register(&spi->dev, indio_dev); } diff --git a/drivers/iio/gyro/bmg160_core.c b/drivers/iio/gyro/bmg160_core.c index 781d3e96645f..38394b5f3275 100644 --- a/drivers/iio/gyro/bmg160_core.c +++ b/drivers/iio/gyro/bmg160_core.c @@ -309,10 +309,8 @@ static int bmg160_set_power_state(struct bmg160_data *data, bool on) if (on) ret = pm_runtime_get_sync(dev); - else { - pm_runtime_mark_last_busy(dev); + else ret = pm_runtime_put_autosuspend(dev); - } if (ret < 0) { dev_err(dev, "Failed: bmg160_set_power_state for %d\n", on); diff --git a/drivers/iio/gyro/fxas21002c_core.c b/drivers/iio/gyro/fxas21002c_core.c index 754c8a564ba4..a88670207cec 100644 --- a/drivers/iio/gyro/fxas21002c_core.c +++ b/drivers/iio/gyro/fxas21002c_core.c @@ -373,8 +373,6 @@ static int fxas21002c_pm_put(struct fxas21002c_data *data) { struct device *dev = regmap_get_device(data->regmap); - pm_runtime_mark_last_busy(dev); - return pm_runtime_put_autosuspend(dev); } diff --git a/drivers/iio/gyro/mpu3050-core.c b/drivers/iio/gyro/mpu3050-core.c index 16553948c5c3..67ae7d1012bc 100644 --- a/drivers/iio/gyro/mpu3050-core.c +++ b/drivers/iio/gyro/mpu3050-core.c @@ -370,7 +370,6 @@ static int mpu3050_read_raw(struct iio_dev *indio_dev, out_read_raw_unlock: mutex_unlock(&mpu3050->lock); - pm_runtime_mark_last_busy(mpu3050->dev); pm_runtime_put_autosuspend(mpu3050->dev); return ret; @@ -662,7 +661,6 @@ static int mpu3050_buffer_postdisable(struct iio_dev *indio_dev) { struct mpu3050 *mpu3050 = iio_priv(indio_dev); - pm_runtime_mark_last_busy(mpu3050->dev); pm_runtime_put_autosuspend(mpu3050->dev); return 0; @@ -976,7 +974,6 @@ static int mpu3050_drdy_trigger_set_state(struct iio_trigger *trig, if (ret) dev_err(mpu3050->dev, "error resetting FIFO\n"); - pm_runtime_mark_last_busy(mpu3050->dev); pm_runtime_put_autosuspend(mpu3050->dev); mpu3050->hw_irq_trigger = false; diff --git a/drivers/iio/gyro/mpu3050-i2c.c b/drivers/iio/gyro/mpu3050-i2c.c index 8e284f47242c..092878f2c886 100644 --- a/drivers/iio/gyro/mpu3050-i2c.c +++ b/drivers/iio/gyro/mpu3050-i2c.c @@ -27,7 +27,6 @@ static int mpu3050_i2c_bypass_deselect(struct i2c_mux_core *mux, u32 chan_id) { struct mpu3050 *mpu3050 = i2c_mux_priv(mux); - pm_runtime_mark_last_busy(mpu3050->dev); pm_runtime_put_autosuspend(mpu3050->dev); return 0; } diff --git a/drivers/iio/health/afe4403.c b/drivers/iio/health/afe4403.c index 30d3f984b032..0e5a512e3bb8 100644 --- a/drivers/iio/health/afe4403.c +++ b/drivers/iio/health/afe4403.c @@ -58,7 +58,6 @@ static const struct reg_field afe4403_reg_fields[] = { /** * struct afe4403_data - AFE4403 device instance data - * @dev: Device structure * @spi: SPI device handle * @regmap: Register map of the device * @fields: Register fields of the device @@ -68,7 +67,6 @@ static const struct reg_field afe4403_reg_fields[] = { * @buffer: Used to construct data layout to push into IIO buffer. */ struct afe4403_data { - struct device *dev; struct spi_device *spi; struct regmap *regmap; struct regmap_field *fields[F_MAX_FIELDS]; @@ -460,63 +458,63 @@ static DEFINE_SIMPLE_DEV_PM_OPS(afe4403_pm_ops, afe4403_suspend, static int afe4403_probe(struct spi_device *spi) { + struct device *dev = &spi->dev; struct iio_dev *indio_dev; struct afe4403_data *afe; int i, ret; - indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*afe)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*afe)); if (!indio_dev) return -ENOMEM; afe = iio_priv(indio_dev); spi_set_drvdata(spi, indio_dev); - afe->dev = &spi->dev; afe->spi = spi; afe->irq = spi->irq; afe->regmap = devm_regmap_init_spi(spi, &afe4403_regmap_config); if (IS_ERR(afe->regmap)) { - dev_err(afe->dev, "Unable to allocate register map\n"); + dev_err(dev, "Unable to allocate register map\n"); return PTR_ERR(afe->regmap); } for (i = 0; i < F_MAX_FIELDS; i++) { - afe->fields[i] = devm_regmap_field_alloc(afe->dev, afe->regmap, + afe->fields[i] = devm_regmap_field_alloc(dev, afe->regmap, afe4403_reg_fields[i]); if (IS_ERR(afe->fields[i])) { - dev_err(afe->dev, "Unable to allocate regmap fields\n"); + dev_err(dev, "Unable to allocate regmap fields\n"); return PTR_ERR(afe->fields[i]); } } - afe->regulator = devm_regulator_get(afe->dev, "tx_sup"); + afe->regulator = devm_regulator_get(dev, "tx_sup"); if (IS_ERR(afe->regulator)) - return dev_err_probe(afe->dev, PTR_ERR(afe->regulator), + return dev_err_probe(dev, PTR_ERR(afe->regulator), "Unable to get regulator\n"); ret = regulator_enable(afe->regulator); if (ret) { - dev_err(afe->dev, "Unable to enable regulator\n"); + dev_err(dev, "Unable to enable regulator\n"); return ret; } - ret = devm_add_action_or_reset(afe->dev, afe4403_regulator_disable, afe->regulator); + ret = devm_add_action_or_reset(dev, afe4403_regulator_disable, afe->regulator); if (ret) { - dev_err(afe->dev, "Unable to add regulator disable action\n"); + dev_err(dev, "Unable to add regulator disable action\n"); return ret; } ret = regmap_write(afe->regmap, AFE440X_CONTROL0, AFE440X_CONTROL0_SW_RESET); if (ret) { - dev_err(afe->dev, "Unable to reset device\n"); + dev_err(dev, "Unable to reset device\n"); return ret; } ret = regmap_multi_reg_write(afe->regmap, afe4403_reg_sequences, ARRAY_SIZE(afe4403_reg_sequences)); if (ret) { - dev_err(afe->dev, "Unable to set register defaults\n"); + dev_err(dev, "Unable to set register defaults\n"); return ret; } @@ -527,45 +525,43 @@ static int afe4403_probe(struct spi_device *spi) indio_dev->info = &afe4403_iio_info; if (afe->irq > 0) { - afe->trig = devm_iio_trigger_alloc(afe->dev, + afe->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); - if (!afe->trig) { - dev_err(afe->dev, "Unable to allocate IIO trigger\n"); + if (!afe->trig) return -ENOMEM; - } iio_trigger_set_drvdata(afe->trig, indio_dev); - ret = devm_iio_trigger_register(afe->dev, afe->trig); + ret = devm_iio_trigger_register(dev, afe->trig); if (ret) { - dev_err(afe->dev, "Unable to register IIO trigger\n"); + dev_err(dev, "Unable to register IIO trigger\n"); return ret; } - ret = devm_request_threaded_irq(afe->dev, afe->irq, + ret = devm_request_threaded_irq(dev, afe->irq, iio_trigger_generic_data_rdy_poll, NULL, IRQF_ONESHOT, AFE4403_DRIVER_NAME, afe->trig); if (ret) { - dev_err(afe->dev, "Unable to request IRQ\n"); + dev_err(dev, "Unable to request IRQ\n"); return ret; } } - ret = devm_iio_triggered_buffer_setup(afe->dev, indio_dev, + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, &iio_pollfunc_store_time, afe4403_trigger_handler, NULL); if (ret) { - dev_err(afe->dev, "Unable to setup buffer\n"); + dev_err(dev, "Unable to setup buffer\n"); return ret; } - ret = devm_iio_device_register(afe->dev, indio_dev); + ret = devm_iio_device_register(dev, indio_dev); if (ret) { - dev_err(afe->dev, "Unable to register IIO device\n"); + dev_err(dev, "Unable to register IIO device\n"); return ret; } diff --git a/drivers/iio/health/afe4404.c b/drivers/iio/health/afe4404.c index b2727effecaa..768d794e574b 100644 --- a/drivers/iio/health/afe4404.c +++ b/drivers/iio/health/afe4404.c @@ -77,7 +77,6 @@ static const struct reg_field afe4404_reg_fields[] = { /** * struct afe4404_data - AFE4404 device instance data - * @dev: Device structure * @regmap: Register map of the device * @fields: Register fields of the device * @regulator: Pointer to the regulator for the IC @@ -86,7 +85,6 @@ static const struct reg_field afe4404_reg_fields[] = { * @buffer: Used to construct a scan to push to the iio buffer. */ struct afe4404_data { - struct device *dev; struct regmap *regmap; struct regmap_field *fields[F_MAX_FIELDS]; struct regulator *regulator; @@ -468,62 +466,62 @@ static DEFINE_SIMPLE_DEV_PM_OPS(afe4404_pm_ops, afe4404_suspend, static int afe4404_probe(struct i2c_client *client) { + struct device *dev = &client->dev; struct iio_dev *indio_dev; struct afe4404_data *afe; int i, ret; - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*afe)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*afe)); if (!indio_dev) return -ENOMEM; afe = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); - afe->dev = &client->dev; afe->irq = client->irq; afe->regmap = devm_regmap_init_i2c(client, &afe4404_regmap_config); if (IS_ERR(afe->regmap)) { - dev_err(afe->dev, "Unable to allocate register map\n"); + dev_err(dev, "Unable to allocate register map\n"); return PTR_ERR(afe->regmap); } for (i = 0; i < F_MAX_FIELDS; i++) { - afe->fields[i] = devm_regmap_field_alloc(afe->dev, afe->regmap, + afe->fields[i] = devm_regmap_field_alloc(dev, afe->regmap, afe4404_reg_fields[i]); if (IS_ERR(afe->fields[i])) { - dev_err(afe->dev, "Unable to allocate regmap fields\n"); + dev_err(dev, "Unable to allocate regmap fields\n"); return PTR_ERR(afe->fields[i]); } } - afe->regulator = devm_regulator_get(afe->dev, "tx_sup"); + afe->regulator = devm_regulator_get(dev, "tx_sup"); if (IS_ERR(afe->regulator)) - return dev_err_probe(afe->dev, PTR_ERR(afe->regulator), + return dev_err_probe(dev, PTR_ERR(afe->regulator), "Unable to get regulator\n"); ret = regulator_enable(afe->regulator); if (ret) { - dev_err(afe->dev, "Unable to enable regulator\n"); + dev_err(dev, "Unable to enable regulator\n"); return ret; } - ret = devm_add_action_or_reset(afe->dev, afe4404_regulator_disable, afe->regulator); + ret = devm_add_action_or_reset(dev, afe4404_regulator_disable, afe->regulator); if (ret) { - dev_err(afe->dev, "Unable to enable regulator\n"); + dev_err(dev, "Unable to enable regulator\n"); return ret; } ret = regmap_write(afe->regmap, AFE440X_CONTROL0, AFE440X_CONTROL0_SW_RESET); if (ret) { - dev_err(afe->dev, "Unable to reset device\n"); + dev_err(dev, "Unable to reset device\n"); return ret; } ret = regmap_multi_reg_write(afe->regmap, afe4404_reg_sequences, ARRAY_SIZE(afe4404_reg_sequences)); if (ret) { - dev_err(afe->dev, "Unable to set register defaults\n"); + dev_err(dev, "Unable to set register defaults\n"); return ret; } @@ -534,45 +532,43 @@ static int afe4404_probe(struct i2c_client *client) indio_dev->info = &afe4404_iio_info; if (afe->irq > 0) { - afe->trig = devm_iio_trigger_alloc(afe->dev, + afe->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); - if (!afe->trig) { - dev_err(afe->dev, "Unable to allocate IIO trigger\n"); + if (!afe->trig) return -ENOMEM; - } iio_trigger_set_drvdata(afe->trig, indio_dev); - ret = devm_iio_trigger_register(afe->dev, afe->trig); + ret = devm_iio_trigger_register(dev, afe->trig); if (ret) { - dev_err(afe->dev, "Unable to register IIO trigger\n"); + dev_err(dev, "Unable to register IIO trigger\n"); return ret; } - ret = devm_request_threaded_irq(afe->dev, afe->irq, + ret = devm_request_threaded_irq(dev, afe->irq, iio_trigger_generic_data_rdy_poll, NULL, IRQF_ONESHOT, AFE4404_DRIVER_NAME, afe->trig); if (ret) { - dev_err(afe->dev, "Unable to request IRQ\n"); + dev_err(dev, "Unable to request IRQ\n"); return ret; } } - ret = devm_iio_triggered_buffer_setup(afe->dev, indio_dev, + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, &iio_pollfunc_store_time, afe4404_trigger_handler, NULL); if (ret) { - dev_err(afe->dev, "Unable to setup buffer\n"); + dev_err(dev, "Unable to setup buffer\n"); return ret; } - ret = devm_iio_device_register(afe->dev, indio_dev); + ret = devm_iio_device_register(dev, indio_dev); if (ret) { - dev_err(afe->dev, "Unable to register IIO device\n"); + dev_err(dev, "Unable to register IIO device\n"); return ret; } diff --git a/drivers/iio/humidity/am2315.c b/drivers/iio/humidity/am2315.c index f021c3e6d886..02ca23eb8991 100644 --- a/drivers/iio/humidity/am2315.c +++ b/drivers/iio/humidity/am2315.c @@ -224,10 +224,8 @@ static int am2315_probe(struct i2c_client *client) struct am2315_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c index 73d2033954e7..980cb946bbf7 100644 --- a/drivers/iio/humidity/dht11.c +++ b/drivers/iio/humidity/dht11.c @@ -294,10 +294,8 @@ static int dht11_probe(struct platform_device *pdev) struct iio_dev *iio; iio = devm_iio_device_alloc(dev, sizeof(*dht11)); - if (!iio) { - dev_err(dev, "Failed to allocate IIO device\n"); + if (!iio) return -ENOMEM; - } dht11 = iio_priv(iio); dht11->dev = dev; diff --git a/drivers/iio/imu/adis16475.c b/drivers/iio/imu/adis16475.c index 924395b7e3b4..ab39bea1e729 100644 --- a/drivers/iio/imu/adis16475.c +++ b/drivers/iio/imu/adis16475.c @@ -1930,7 +1930,6 @@ static int adis16475_config_irq_pin(struct adis16475 *st) return 0; } - static int adis16475_probe(struct spi_device *spi) { struct iio_dev *indio_dev; diff --git a/drivers/iio/imu/bmi270/bmi270_i2c.c b/drivers/iio/imu/bmi270/bmi270_i2c.c index c77839b03a96..b909a421ad01 100644 --- a/drivers/iio/imu/bmi270/bmi270_i2c.c +++ b/drivers/iio/imu/bmi270/bmi270_i2c.c @@ -41,6 +41,8 @@ static const struct i2c_device_id bmi270_i2c_id[] = { static const struct acpi_device_id bmi270_acpi_match[] = { /* GPD Win Mini, Aya Neo AIR Pro, OXP Mini Pro, etc. */ { "BMI0160", (kernel_ulong_t)&bmi260_chip_info }, + /* GPD Win Max 2 2023(sincice BIOS v0.40), etc. */ + { "BMI0260", (kernel_ulong_t)&bmi260_chip_info }, { } }; diff --git a/drivers/iio/imu/bmi323/bmi323_core.c b/drivers/iio/imu/bmi323/bmi323_core.c index fc54d464a3ae..6bcb9a436581 100644 --- a/drivers/iio/imu/bmi323/bmi323_core.c +++ b/drivers/iio/imu/bmi323/bmi323_core.c @@ -2112,8 +2112,7 @@ int bmi323_core_probe(struct device *dev) indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "Failed to allocate device\n"); + return -ENOMEM; ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), regulator_names); diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600.h b/drivers/iio/imu/inv_icm42600/inv_icm42600.h index 1430ab4f1dea..c8b48a5c5ed0 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600.h +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600.h @@ -167,7 +167,6 @@ struct inv_icm42600_state { enum inv_icm42600_chip chip; const char *name; struct regmap *map; - struct regulator *vdd_supply; struct regulator *vddio_supply; int irq; struct iio_mount_matrix orientation; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c index 7a28051330b7..54760d8f92a2 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c @@ -315,7 +315,6 @@ static int inv_icm42600_accel_read_sensor(struct iio_dev *indio_dev, ret = -EINVAL; exit: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -562,12 +561,10 @@ static int inv_icm42600_accel_write_scale(struct iio_dev *indio_dev, conf.fs = idx / 2; pm_runtime_get_sync(dev); - mutex_lock(&st->lock); - ret = inv_icm42600_set_accel_conf(st, &conf, NULL); + scoped_guard(mutex, &st->lock) + ret = inv_icm42600_set_accel_conf(st, &conf, NULL); - mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -675,7 +672,6 @@ static int inv_icm42600_accel_write_odr(struct iio_dev *indio_dev, out_unlock: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -727,7 +723,6 @@ static int inv_icm42600_accel_read_offset(struct inv_icm42600_state *st, memcpy(data, st->buffer, sizeof(data)); mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); if (ret) return ret; @@ -865,7 +860,6 @@ static int inv_icm42600_accel_write_offset(struct inv_icm42600_state *st, out_unlock: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -991,16 +985,11 @@ static int inv_icm42600_accel_hwfifo_set_watermark(struct iio_dev *indio_dev, unsigned int val) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); st->fifo.watermark.accel = val; - ret = inv_icm42600_buffer_update_watermark(st); - - mutex_unlock(&st->lock); - - return ret; + return inv_icm42600_buffer_update_watermark(st); } static int inv_icm42600_accel_hwfifo_flush(struct iio_dev *indio_dev, @@ -1012,15 +1001,13 @@ static int inv_icm42600_accel_hwfifo_flush(struct iio_dev *indio_dev, if (count == 0) return 0; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); ret = inv_icm42600_buffer_hwfifo_flush(st, count); - if (!ret) - ret = st->fifo.nb.accel; - - mutex_unlock(&st->lock); + if (ret) + return ret; - return ret; + return st->fifo.nb.accel; } static int inv_icm42600_accel_read_event_config(struct iio_dev *indio_dev, diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c index 7c4ed981db04..ada968be954d 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c @@ -5,6 +5,7 @@ #include <linux/kernel.h> #include <linux/device.h> +#include <linux/minmax.h> #include <linux/mutex.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> @@ -100,7 +101,7 @@ ssize_t inv_icm42600_fifo_decode_packet(const void *packet, const void **accel, void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st) { - u32 period_gyro, period_accel, period; + u32 period_gyro, period_accel; if (st->fifo.en & INV_ICM42600_SENSOR_GYRO) period_gyro = inv_icm42600_odr_to_period(st->conf.gyro.odr); @@ -112,12 +113,7 @@ void inv_icm42600_buffer_update_fifo_period(struct inv_icm42600_state *st) else period_accel = U32_MAX; - if (period_gyro <= period_accel) - period = period_gyro; - else - period = period_accel; - - st->fifo.period = period; + st->fifo.period = min(period_gyro, period_accel); } int inv_icm42600_buffer_set_fifo_en(struct inv_icm42600_state *st, @@ -204,7 +200,7 @@ int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st) { size_t packet_size, wm_size; unsigned int wm_gyro, wm_accel, watermark; - u32 period_gyro, period_accel, period; + u32 period_gyro, period_accel; u32 latency_gyro, latency_accel, latency; bool restore; __le16 raw_wm; @@ -237,13 +233,8 @@ int inv_icm42600_buffer_update_watermark(struct inv_icm42600_state *st) latency = latency_gyro - (latency_accel % latency_gyro); else latency = latency_accel - (latency_gyro % latency_accel); - /* use the shortest period */ - if (period_gyro <= period_accel) - period = period_gyro; - else - period = period_accel; /* all this works because periods are multiple of each others */ - watermark = latency / period; + watermark = latency / min(period_gyro, period_accel); if (watermark < 1) watermark = 1; /* update effective watermark */ @@ -292,9 +283,8 @@ static int inv_icm42600_buffer_preenable(struct iio_dev *indio_dev) pm_runtime_get_sync(dev); - mutex_lock(&st->lock); + guard(mutex)(&st->lock); inv_sensors_timestamp_reset(ts); - mutex_unlock(&st->lock); return 0; } @@ -308,43 +298,39 @@ static int inv_icm42600_buffer_postenable(struct iio_dev *indio_dev) struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); - /* exit if FIFO is already on */ if (st->fifo.on) { - ret = 0; - goto out_on; + st->fifo.on++; + return 0; } /* set FIFO threshold interrupt */ ret = regmap_set_bits(st->map, INV_ICM42600_REG_INT_SOURCE0, INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN); if (ret) - goto out_unlock; + return ret; /* flush FIFO data */ ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET, INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH); if (ret) - goto out_unlock; + return ret; /* set FIFO in streaming mode */ ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, INV_ICM42600_FIFO_CONFIG_STREAM); if (ret) - goto out_unlock; + return ret; /* workaround: first read of FIFO count after reset is always 0 */ ret = regmap_bulk_read(st->map, INV_ICM42600_REG_FIFO_COUNT, st->buffer, 2); if (ret) - goto out_unlock; + return ret; -out_on: - /* increase FIFO on counter */ st->fifo.on++; -out_unlock: - mutex_unlock(&st->lock); - return ret; + + return 0; } static int inv_icm42600_buffer_predisable(struct iio_dev *indio_dev) @@ -352,38 +338,34 @@ static int inv_icm42600_buffer_predisable(struct iio_dev *indio_dev) struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); - /* exit if there are several sensors using the FIFO */ if (st->fifo.on > 1) { - ret = 0; - goto out_off; + st->fifo.on--; + return 0; } /* set FIFO in bypass mode */ ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, INV_ICM42600_FIFO_CONFIG_BYPASS); if (ret) - goto out_unlock; + return ret; /* flush FIFO data */ ret = regmap_write(st->map, INV_ICM42600_REG_SIGNAL_PATH_RESET, INV_ICM42600_SIGNAL_PATH_RESET_FIFO_FLUSH); if (ret) - goto out_unlock; + return ret; /* disable FIFO threshold interrupt */ ret = regmap_clear_bits(st->map, INV_ICM42600_REG_INT_SOURCE0, INV_ICM42600_INT_SOURCE0_FIFO_THS_INT1_EN); if (ret) - goto out_unlock; + return ret; -out_off: - /* decrease FIFO on counter */ st->fifo.on--; -out_unlock: - mutex_unlock(&st->lock); - return ret; + + return 0; } static int inv_icm42600_buffer_postdisable(struct iio_dev *indio_dev) @@ -439,7 +421,6 @@ out_unlock: if (sleep) msleep(sleep); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c index a4d42e7e2180..76eb22488e5f 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c @@ -439,18 +439,13 @@ int inv_icm42600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); if (readval) - ret = regmap_read(st->map, reg, readval); - else - ret = regmap_write(st->map, reg, writeval); + return regmap_read(st->map, reg, readval); - mutex_unlock(&st->lock); - - return ret; + return regmap_write(st->map, reg, writeval); } static int inv_icm42600_set_conf(struct inv_icm42600_state *st, @@ -697,34 +692,15 @@ static int inv_icm42600_enable_regulator_vddio(struct inv_icm42600_state *st) return 0; } -static void inv_icm42600_disable_vdd_reg(void *_data) -{ - struct inv_icm42600_state *st = _data; - const struct device *dev = regmap_get_device(st->map); - int ret; - - ret = regulator_disable(st->vdd_supply); - if (ret) - dev_err(dev, "failed to disable vdd error %d\n", ret); -} - static void inv_icm42600_disable_vddio_reg(void *_data) { struct inv_icm42600_state *st = _data; - const struct device *dev = regmap_get_device(st->map); - int ret; - - ret = regulator_disable(st->vddio_supply); - if (ret) - dev_err(dev, "failed to disable vddio error %d\n", ret); -} + struct device *dev = regmap_get_device(st->map); -static void inv_icm42600_disable_pm(void *_data) -{ - struct device *dev = _data; + if (pm_runtime_status_suspended(dev)) + return; - pm_runtime_put_sync(dev); - pm_runtime_disable(dev); + regulator_disable(st->vddio_supply); } int inv_icm42600_core_probe(struct regmap *regmap, int chip, @@ -773,23 +749,17 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip, return ret; } - st->vdd_supply = devm_regulator_get(dev, "vdd"); - if (IS_ERR(st->vdd_supply)) - return PTR_ERR(st->vdd_supply); + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get vdd regulator\n"); + + msleep(INV_ICM42600_POWER_UP_TIME_MS); st->vddio_supply = devm_regulator_get(dev, "vddio"); if (IS_ERR(st->vddio_supply)) return PTR_ERR(st->vddio_supply); - ret = regulator_enable(st->vdd_supply); - if (ret) - return ret; - msleep(INV_ICM42600_POWER_UP_TIME_MS); - - ret = devm_add_action_or_reset(dev, inv_icm42600_disable_vdd_reg, st); - if (ret) - return ret; - ret = inv_icm42600_enable_regulator_vddio(st); if (ret) return ret; @@ -824,16 +794,14 @@ int inv_icm42600_core_probe(struct regmap *regmap, int chip, return ret; /* setup runtime power management */ - ret = pm_runtime_set_active(dev); + ret = devm_pm_runtime_set_active_enabled(dev); if (ret) return ret; - pm_runtime_get_noresume(dev); - pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, INV_ICM42600_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); - pm_runtime_put(dev); - return devm_add_action_or_reset(dev, inv_icm42600_disable_pm, dev); + return ret; } EXPORT_SYMBOL_NS_GPL(inv_icm42600_core_probe, "IIO_ICM42600"); @@ -849,22 +817,20 @@ static int inv_icm42600_suspend(struct device *dev) int accel_conf; int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); st->suspended.gyro = st->conf.gyro.mode; st->suspended.accel = st->conf.accel.mode; st->suspended.temp = st->conf.temp_en; - if (pm_runtime_suspended(dev)) { - ret = 0; - goto out_unlock; - } + if (pm_runtime_suspended(dev)) + return 0; /* disable FIFO data streaming */ if (st->fifo.on) { ret = regmap_write(st->map, INV_ICM42600_REG_FIFO_CONFIG, INV_ICM42600_FIFO_CONFIG_BYPASS); if (ret) - goto out_unlock; + return ret; } /* keep chip on and wake-up capable if APEX and wakeup on */ @@ -880,7 +846,7 @@ static int inv_icm42600_suspend(struct device *dev) if (st->apex.wom.enable) { ret = inv_icm42600_disable_wom(st); if (ret) - goto out_unlock; + return ret; } accel_conf = INV_ICM42600_SENSOR_MODE_OFF; } @@ -888,15 +854,13 @@ static int inv_icm42600_suspend(struct device *dev) ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF, accel_conf, false, NULL); if (ret) - goto out_unlock; + return ret; /* disable vddio regulator if chip is sleeping */ if (!wakeup) regulator_disable(st->vddio_supply); -out_unlock: - mutex_unlock(&st->lock); - return ret; + return 0; } /* @@ -912,7 +876,10 @@ static int inv_icm42600_resume(struct device *dev) bool wakeup; int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); + + if (pm_runtime_suspended(dev)) + return 0; /* check wakeup capability */ accel_dev = &st->indio_accel->dev; @@ -924,25 +891,21 @@ static int inv_icm42600_resume(struct device *dev) } else { ret = inv_icm42600_enable_regulator_vddio(st); if (ret) - goto out_unlock; + return ret; } - pm_runtime_disable(dev); - pm_runtime_set_active(dev); - pm_runtime_enable(dev); - /* restore sensors state */ ret = inv_icm42600_set_pwr_mgmt0(st, st->suspended.gyro, st->suspended.accel, st->suspended.temp, NULL); if (ret) - goto out_unlock; + return ret; /* restore APEX features if disabled */ if (!wakeup && st->apex.wom.enable) { ret = inv_icm42600_enable_wom(st); if (ret) - goto out_unlock; + return ret; } /* restore FIFO data streaming */ @@ -953,9 +916,7 @@ static int inv_icm42600_resume(struct device *dev) INV_ICM42600_FIFO_CONFIG_STREAM); } -out_unlock: - mutex_unlock(&st->lock); - return ret; + return 0; } /* Runtime suspend will turn off sensors that are enabled by iio devices. */ @@ -964,34 +925,28 @@ static int inv_icm42600_runtime_suspend(struct device *dev) struct inv_icm42600_state *st = dev_get_drvdata(dev); int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); /* disable all sensors */ ret = inv_icm42600_set_pwr_mgmt0(st, INV_ICM42600_SENSOR_MODE_OFF, INV_ICM42600_SENSOR_MODE_OFF, false, NULL); if (ret) - goto error_unlock; + return ret; regulator_disable(st->vddio_supply); -error_unlock: - mutex_unlock(&st->lock); - return ret; + return 0; } /* Sensors are enabled by iio devices, no need to turn them back on here. */ static int inv_icm42600_runtime_resume(struct device *dev) { struct inv_icm42600_state *st = dev_get_drvdata(dev); - int ret; - - mutex_lock(&st->lock); - ret = inv_icm42600_enable_regulator_vddio(st); + guard(mutex)(&st->lock); - mutex_unlock(&st->lock); - return ret; + return inv_icm42600_enable_regulator_vddio(st); } EXPORT_NS_GPL_DEV_PM_OPS(inv_icm42600_pm_ops, IIO_ICM42600) = { diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c index 9ba6f13628e6..7ef0a25ec74f 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c @@ -184,7 +184,6 @@ static int inv_icm42600_gyro_read_sensor(struct inv_icm42600_state *st, ret = -EINVAL; exit: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -278,12 +277,10 @@ static int inv_icm42600_gyro_write_scale(struct iio_dev *indio_dev, conf.fs = idx / 2; pm_runtime_get_sync(dev); - mutex_lock(&st->lock); - ret = inv_icm42600_set_gyro_conf(st, &conf, NULL); + scoped_guard(mutex, &st->lock) + ret = inv_icm42600_set_gyro_conf(st, &conf, NULL); - mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -378,7 +375,6 @@ static int inv_icm42600_gyro_write_odr(struct iio_dev *indio_dev, out_unlock: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; @@ -430,7 +426,6 @@ static int inv_icm42600_gyro_read_offset(struct inv_icm42600_state *st, memcpy(data, st->buffer, sizeof(data)); mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); if (ret) return ret; @@ -567,7 +562,6 @@ static int inv_icm42600_gyro_write_offset(struct inv_icm42600_state *st, out_unlock: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; } @@ -693,16 +687,11 @@ static int inv_icm42600_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev, unsigned int val) { struct inv_icm42600_state *st = iio_device_get_drvdata(indio_dev); - int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); st->fifo.watermark.gyro = val; - ret = inv_icm42600_buffer_update_watermark(st); - - mutex_unlock(&st->lock); - - return ret; + return inv_icm42600_buffer_update_watermark(st); } static int inv_icm42600_gyro_hwfifo_flush(struct iio_dev *indio_dev, @@ -714,15 +703,13 @@ static int inv_icm42600_gyro_hwfifo_flush(struct iio_dev *indio_dev, if (count == 0) return 0; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); ret = inv_icm42600_buffer_hwfifo_flush(st, count); - if (!ret) - ret = st->fifo.nb.gyro; - - mutex_unlock(&st->lock); + if (ret) + return ret; - return ret; + return st->fifo.nb.gyro; } static const struct iio_info inv_icm42600_gyro_info = { diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c index 271a4788604a..30f6a9595eea 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c @@ -41,7 +41,6 @@ static int inv_icm42600_temp_read(struct inv_icm42600_state *st, s16 *temp) exit: mutex_unlock(&st->lock); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); return ret; diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index 39eb516acc73..b2fa1f4957a5 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -735,7 +735,6 @@ static int inv_mpu6050_read_channel_data(struct iio_dev *indio_dev, break; } - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); return ret; @@ -938,7 +937,6 @@ static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, break; } - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); error_write_raw_unlock: mutex_unlock(&st->lock); @@ -1146,14 +1144,12 @@ static int inv_mpu6050_enable_wom(struct inv_mpu6050_state *st, bool en) st->chip_config.wom_en = false; } - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); } return result; error_suspend: - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); return result; } @@ -1249,7 +1245,6 @@ static int inv_mpu6050_write_event_value(struct iio_dev *indio_dev, value = (u64)val * 1000000ULL + (u64)val2; result = inv_mpu6050_set_wom_threshold(st, value, INV_MPU6050_FREQ_DIVIDER(st)); - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); return result; @@ -1357,7 +1352,6 @@ inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, if (result) goto fifo_rate_fail_power_off; - pm_runtime_mark_last_busy(pdev); fifo_rate_fail_power_off: pm_runtime_put_autosuspend(pdev); fifo_rate_fail_unlock: diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c index 5b1088cc3704..10a473342075 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -194,7 +194,6 @@ static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable) result = inv_mpu6050_prepare_fifo(st, false); if (result) goto error_power_off; - pm_runtime_mark_last_busy(pdev); pm_runtime_put_autosuspend(pdev); } diff --git a/drivers/iio/imu/kmx61.c b/drivers/iio/imu/kmx61.c index 55c82891e08c..3cd91d8a89ee 100644 --- a/drivers/iio/imu/kmx61.c +++ b/drivers/iio/imu/kmx61.c @@ -747,12 +747,10 @@ static int kmx61_set_power_state(struct kmx61_data *data, bool on, u8 device) data->mag_ps = on; } - if (on) { + if (on) ret = pm_runtime_resume_and_get(&data->client->dev); - } else { - pm_runtime_mark_last_busy(&data->client->dev); + else ret = pm_runtime_put_autosuspend(&data->client->dev); - } if (ret < 0) { dev_err(&data->client->dev, "Failed: kmx61_set_power_state for %d, ret %d\n", diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index c65ad49829e7..d8cb4b0218d5 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -2035,10 +2035,10 @@ st_lsm6dsx_sysfs_sampling_frequency_avail(struct device *dev, odr_table = &sensor->hw->settings->odr_table[sensor->id]; for (i = 0; i < odr_table->odr_len; i++) - len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%03d ", - odr_table->odr_avl[i].milli_hz / 1000, - odr_table->odr_avl[i].milli_hz % 1000); - buf[len - 1] = '\n'; + len += sysfs_emit_at(buf, len, "%d.%03d%c", + odr_table->odr_avl[i].milli_hz / 1000, + odr_table->odr_avl[i].milli_hz % 1000, + (i == odr_table->odr_len - 1) ? '\n' : ' '); return len; } @@ -2054,9 +2054,9 @@ static ssize_t st_lsm6dsx_sysfs_scale_avail(struct device *dev, fs_table = &hw->settings->fs_table[sensor->id]; for (i = 0; i < fs_table->fs_len; i++) - len += scnprintf(buf + len, PAGE_SIZE - len, "0.%09u ", - fs_table->fs_avl[i].gain); - buf[len - 1] = '\n'; + len += sysfs_emit_at(buf, len, "0.%09u%c", + fs_table->fs_avl[i].gain, + (i == fs_table->fs_len - 1) ? '\n' : ' '); return len; } diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 159d6c5ca3ce..88c3d585a1bd 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -97,6 +97,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_COLORTEMP] = "colortemp", [IIO_CHROMATICITY] = "chromaticity", [IIO_ATTENTION] = "attention", + [IIO_ALTCURRENT] = "altcurrent", }; static const char * const iio_modifier_names[] = { @@ -152,6 +153,10 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_PITCH] = "pitch", [IIO_MOD_YAW] = "yaw", [IIO_MOD_ROLL] = "roll", + [IIO_MOD_RMS] = "rms", + [IIO_MOD_ACTIVE] = "active", + [IIO_MOD_REACTIVE] = "reactive", + [IIO_MOD_APPARENT] = "apparent", }; /* relies on pairs of these shared then separate */ @@ -189,6 +194,7 @@ static const char * const iio_chan_info_postfix[] = { [IIO_CHAN_INFO_ZEROPOINT] = "zeropoint", [IIO_CHAN_INFO_TROUGH] = "trough_raw", [IIO_CHAN_INFO_CONVDELAY] = "convdelay", + [IIO_CHAN_INFO_POWERFACTOR] = "powerfactor", }; /** * iio_device_id() - query the unique ID for the device @@ -790,6 +796,7 @@ static ssize_t iio_format_list(char *buf, const int *vals, int type, int length, switch (type) { case IIO_VAL_INT: + case IIO_VAL_CHAR: stride = 1; break; default: @@ -1243,7 +1250,7 @@ static int iio_device_add_channel_label(struct iio_dev *indio_dev, static int iio_device_add_info_mask_type(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, enum iio_shared_by shared_by, - const long *infomask) + const unsigned long *infomask) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); int i, ret, attrcount = 0; @@ -1273,7 +1280,7 @@ static int iio_device_add_info_mask_type(struct iio_dev *indio_dev, static int iio_device_add_info_mask_type_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, enum iio_shared_by shared_by, - const long *infomask) + const unsigned long *infomask) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); int i, ret, attrcount = 0; diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c index c174ebb7d5e6..1e5eb5a41271 100644 --- a/drivers/iio/inkern.c +++ b/drivers/iio/inkern.c @@ -11,6 +11,7 @@ #include <linux/mutex.h> #include <linux/property.h> #include <linux/slab.h> +#include <linux/units.h> #include <linux/iio/iio.h> #include <linux/iio/iio-opaque.h> @@ -598,6 +599,42 @@ int iio_read_channel_average_raw(struct iio_channel *chan, int *val) } EXPORT_SYMBOL_GPL(iio_read_channel_average_raw); +int iio_multiply_value(int *result, s64 multiplier, + unsigned int type, int val, int val2) +{ + s64 denominator; + + switch (type) { + case IIO_VAL_INT: + *result = multiplier * val; + return IIO_VAL_INT; + case IIO_VAL_INT_PLUS_MICRO: + case IIO_VAL_INT_PLUS_NANO: + switch (type) { + case IIO_VAL_INT_PLUS_MICRO: + denominator = MICRO; + break; + case IIO_VAL_INT_PLUS_NANO: + denominator = NANO; + break; + } + *result = multiplier * abs(val); + *result += div_s64(multiplier * abs(val2), denominator); + if (val < 0 || val2 < 0) + *result *= -1; + return IIO_VAL_INT; + case IIO_VAL_FRACTIONAL: + *result = div_s64(multiplier * val, val2); + return IIO_VAL_INT; + case IIO_VAL_FRACTIONAL_LOG2: + *result = (multiplier * val) >> val2; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL_NS_GPL(iio_multiply_value, "IIO_UNIT_TEST"); + static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan, int raw, int *processed, unsigned int scale) @@ -605,6 +642,7 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan, int scale_type, scale_val, scale_val2; int offset_type, offset_val, offset_val2; s64 raw64 = raw; + int ret; offset_type = iio_channel_read(chan, &offset_val, &offset_val2, IIO_CHAN_INFO_OFFSET); @@ -639,40 +677,14 @@ static int iio_convert_raw_to_processed_unlocked(struct iio_channel *chan, * If no channel scaling is available apply consumer scale to * raw value and return. */ - *processed = raw * scale; + *processed = raw64 * scale; return 0; } - switch (scale_type) { - case IIO_VAL_INT: - *processed = raw64 * scale_val * scale; - break; - case IIO_VAL_INT_PLUS_MICRO: - if (scale_val2 < 0) - *processed = -raw64 * scale_val * scale; - else - *processed = raw64 * scale_val * scale; - *processed += div_s64(raw64 * (s64)scale_val2 * scale, - 1000000LL); - break; - case IIO_VAL_INT_PLUS_NANO: - if (scale_val2 < 0) - *processed = -raw64 * scale_val * scale; - else - *processed = raw64 * scale_val * scale; - *processed += div_s64(raw64 * (s64)scale_val2 * scale, - 1000000000LL); - break; - case IIO_VAL_FRACTIONAL: - *processed = div_s64(raw64 * (s64)scale_val * scale, - scale_val2); - break; - case IIO_VAL_FRACTIONAL_LOG2: - *processed = (raw64 * (s64)scale_val * scale) >> scale_val2; - break; - default: - return -EINVAL; - } + ret = iio_multiply_value(processed, raw64 * scale, + scale_type, scale_val, scale_val2); + if (ret < 0) + return ret; return 0; } @@ -714,20 +726,19 @@ int iio_read_channel_processed_scale(struct iio_channel *chan, int *val, unsigned int scale) { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(chan->indio_dev); - int ret; + int ret, pval, pval2; guard(mutex)(&iio_dev_opaque->info_exist_lock); if (!chan->indio_dev->info) return -ENODEV; if (iio_channel_has_info(chan->channel, IIO_CHAN_INFO_PROCESSED)) { - ret = iio_channel_read(chan, val, NULL, + ret = iio_channel_read(chan, &pval, &pval2, IIO_CHAN_INFO_PROCESSED); if (ret < 0) return ret; - *val *= scale; - return ret; + return iio_multiply_value(val, scale, ret, pval, pval2); } else { ret = iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW); if (ret < 0) diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 4a7d983c9cd4..ac1408d374c9 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -724,6 +724,19 @@ config VEML6040 To compile this driver as a module, choose M here: the module will be called veml6040. +config VEML6046X00 + tristate "VEML6046X00 RGBIR color sensor" + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6046X00 + high accuracy RGBIR color sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6046x00. + config VEML6070 tristate "VEML6070 UV A light sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 8229ebe6edc4..c0048e0d5ca8 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_VCNL4035) += vcnl4035.o obj-$(CONFIG_VEML3235) += veml3235.o obj-$(CONFIG_VEML6030) += veml6030.o obj-$(CONFIG_VEML6040) += veml6040.o +obj-$(CONFIG_VEML6046X00) += veml6046x00.o obj-$(CONFIG_VEML6070) += veml6070.o obj-$(CONFIG_VEML6075) += veml6075.o obj-$(CONFIG_VL6180) += vl6180.o diff --git a/drivers/iio/light/acpi-als.c b/drivers/iio/light/acpi-als.c index 032e6cae8b80..d5d1a8b9c035 100644 --- a/drivers/iio/light/acpi-als.c +++ b/drivers/iio/light/acpi-als.c @@ -49,20 +49,10 @@ static const struct iio_chan_spec acpi_als_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(1), }; -/* - * The event buffer contains timestamp and all the data from - * the ACPI0008 block. There are multiple, but so far we only - * support _ALI (illuminance): One channel, padding and timestamp. - */ -#define ACPI_ALS_EVT_BUFFER_SIZE \ - (sizeof(s32) + sizeof(s32) + sizeof(s64)) - struct acpi_als { struct acpi_device *device; struct mutex lock; struct iio_trigger *trig; - - s32 evt_buffer[ACPI_ALS_EVT_BUFFER_SIZE / sizeof(s32)] __aligned(8); }; /* @@ -152,7 +142,10 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p) struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct acpi_als *als = iio_priv(indio_dev); - s32 *buffer = als->evt_buffer; + struct { + s32 light; + aligned_s64 ts; + } scan = { }; s32 val; int ret; @@ -161,7 +154,7 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p) ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val); if (ret < 0) goto out; - *buffer = val; + scan.light = val; /* * When coming from own trigger via polls, set polling function @@ -174,7 +167,7 @@ static irqreturn_t acpi_als_trigger_handler(int irq, void *p) if (!pf->timestamp) pf->timestamp = iio_get_time_ns(indio_dev); - iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); out: mutex_unlock(&als->lock); iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/light/adjd_s311.c b/drivers/iio/light/adjd_s311.c index cf96e3dd8bc6..edb3d9dc8bed 100644 --- a/drivers/iio/light/adjd_s311.c +++ b/drivers/iio/light/adjd_s311.c @@ -54,10 +54,6 @@ struct adjd_s311_data { struct i2c_client *client; - struct { - s16 chans[4]; - aligned_s64 ts; - } scan; }; enum adjd_s311_channel_idx { @@ -120,6 +116,10 @@ static irqreturn_t adjd_s311_trigger_handler(int irq, void *p) struct adjd_s311_data *data = iio_priv(indio_dev); s64 time_ns = iio_get_time_ns(indio_dev); int i, j = 0; + struct { + s16 chans[4]; + aligned_s64 ts; + } scan = { }; int ret = adjd_s311_req_data(indio_dev); if (ret < 0) @@ -131,10 +131,10 @@ static irqreturn_t adjd_s311_trigger_handler(int irq, void *p) if (ret < 0) goto done; - data->scan.chans[j++] = ret & ADJD_S311_DATA_MASK; + scan.chans[j++] = ret & ADJD_S311_DATA_MASK; } - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, time_ns); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); done: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/light/al3000a.c b/drivers/iio/light/al3000a.c index 6f301c067045..9871096cbab3 100644 --- a/drivers/iio/light/al3000a.c +++ b/drivers/iio/light/al3000a.c @@ -94,7 +94,7 @@ static int al3000a_init(struct al3000a_data *data) ret = devm_add_action_or_reset(dev, al3000a_set_pwr_off, data); if (ret) - return dev_err_probe(dev, ret, "failed to add action\n"); + return ret; ret = regmap_write(data->regmap, AL3000A_REG_SYSTEM, AL3000A_CONFIG_RESET); if (ret) diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c index f676da245aa7..389125675caa 100644 --- a/drivers/iio/light/apds9306.c +++ b/drivers/iio/light/apds9306.c @@ -537,7 +537,6 @@ static int apds9306_read_data(struct apds9306_data *data, int *val, int reg) *val = get_unaligned_le24(&buff); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return 0; @@ -1121,7 +1120,6 @@ static int apds9306_write_event_config(struct iio_dev *indio_dev, if (ret) return ret; - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return 0; @@ -1309,7 +1307,7 @@ static int apds9306_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, apds9306_powerdown, data); if (ret) - return dev_err_probe(dev, ret, "failed to add action or reset\n"); + return ret; ret = devm_iio_device_register(dev, indio_dev); if (ret) diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c index b92d0fce5aec..79b202c59a0f 100644 --- a/drivers/iio/light/apds9960.c +++ b/drivers/iio/light/apds9960.c @@ -495,7 +495,6 @@ static int apds9960_set_power_state(struct apds9960_data *data, bool on) usleep_range(data->als_adc_int_us, APDS9960_MAX_INT_TIME_IN_US); } else { - pm_runtime_mark_last_busy(dev); ret = pm_runtime_put_autosuspend(dev); } diff --git a/drivers/iio/light/bh1745.c b/drivers/iio/light/bh1745.c index 4e9bd8f831f7..10b00344bbed 100644 --- a/drivers/iio/light/bh1745.c +++ b/drivers/iio/light/bh1745.c @@ -755,8 +755,8 @@ static irqreturn_t bh1745_trigger_handler(int interrupt, void *p) scan.chans[j++] = value; } - iio_push_to_buffers_with_timestamp(indio_dev, &scan, - iio_get_time_ns(indio_dev)); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); err: iio_trigger_notify_done(indio_dev->trig); @@ -814,8 +814,7 @@ static int bh1745_init(struct bh1745_data *data) ret = devm_add_action_or_reset(dev, bh1745_power_off, data); if (ret) - return dev_err_probe(dev, ret, - "Failed to add action or reset\n"); + return ret; return 0; } diff --git a/drivers/iio/light/bh1780.c b/drivers/iio/light/bh1780.c index c7c877d2fe67..5d3c6d5276ba 100644 --- a/drivers/iio/light/bh1780.c +++ b/drivers/iio/light/bh1780.c @@ -111,7 +111,6 @@ static int bh1780_read_raw(struct iio_dev *indio_dev, value = bh1780_read_word(bh1780, BH1780_REG_DLOW); if (value < 0) return value; - pm_runtime_mark_last_busy(&bh1780->client->dev); pm_runtime_put_autosuspend(&bh1780->client->dev); *val = value; diff --git a/drivers/iio/light/gp2ap002.c b/drivers/iio/light/gp2ap002.c index 42859e5b1089..a0d8a58f2704 100644 --- a/drivers/iio/light/gp2ap002.c +++ b/drivers/iio/light/gp2ap002.c @@ -271,7 +271,6 @@ static int gp2ap002_read_raw(struct iio_dev *indio_dev, } out: - pm_runtime_mark_last_busy(gp2ap002->dev); pm_runtime_put_autosuspend(gp2ap002->dev); return ret; @@ -353,7 +352,6 @@ static int gp2ap002_write_event_config(struct iio_dev *indio_dev, pm_runtime_get_sync(gp2ap002->dev); gp2ap002->enabled = true; } else { - pm_runtime_mark_last_busy(gp2ap002->dev); pm_runtime_put_autosuspend(gp2ap002->dev); gp2ap002->enabled = false; } diff --git a/drivers/iio/light/hid-sensor-als.c b/drivers/iio/light/hid-sensor-als.c index 830e5ae7f34a..384572844162 100644 --- a/drivers/iio/light/hid-sensor-als.c +++ b/drivers/iio/light/hid-sensor-als.c @@ -262,8 +262,9 @@ static int als_proc_event(struct hid_sensor_hub_device *hsdev, if (!als_state->timestamp) als_state->timestamp = iio_get_time_ns(indio_dev); - iio_push_to_buffers_with_timestamp(indio_dev, &als_state->scan, - als_state->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &als_state->scan, + sizeof(als_state->scan), + als_state->timestamp); als_state->timestamp = 0; } diff --git a/drivers/iio/light/isl29028.c b/drivers/iio/light/isl29028.c index 0e4284823d44..374bccad9119 100644 --- a/drivers/iio/light/isl29028.c +++ b/drivers/iio/light/isl29028.c @@ -336,16 +336,11 @@ static int isl29028_ir_get(struct isl29028_chip *chip, int *ir_data) static int isl29028_set_pm_runtime_busy(struct isl29028_chip *chip, bool on) { struct device *dev = regmap_get_device(chip->regmap); - int ret; - if (on) { - ret = pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); - ret = pm_runtime_put_autosuspend(dev); - } + if (on) + return pm_runtime_resume_and_get(dev); - return ret; + return pm_runtime_put_autosuspend(dev); } /* Channel IO */ diff --git a/drivers/iio/light/isl29125.c b/drivers/iio/light/isl29125.c index 6bc23b164cc5..3acb8a4f1d12 100644 --- a/drivers/iio/light/isl29125.c +++ b/drivers/iio/light/isl29125.c @@ -51,11 +51,6 @@ struct isl29125_data { struct i2c_client *client; u8 conf1; - /* Ensure timestamp is naturally aligned */ - struct { - u16 chans[3]; - aligned_s64 timestamp; - } scan; }; #define ISL29125_CHANNEL(_color, _si) { \ @@ -179,6 +174,11 @@ static irqreturn_t isl29125_trigger_handler(int irq, void *p) struct iio_dev *indio_dev = pf->indio_dev; struct isl29125_data *data = iio_priv(indio_dev); int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[3]; + aligned_s64 timestamp; + } scan = { }; iio_for_each_active_channel(indio_dev, i) { int ret = i2c_smbus_read_word_data(data->client, @@ -186,10 +186,10 @@ static irqreturn_t isl29125_trigger_handler(int irq, void *p) if (ret < 0) goto done; - data->scan.chans[j++] = ret; + scan.chans[j++] = ret; } - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), iio_get_time_ns(indio_dev)); done: diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c index ee59bbb8aa09..a2b804e9089a 100644 --- a/drivers/iio/light/ltr390.c +++ b/drivers/iio/light/ltr390.c @@ -26,6 +26,7 @@ #include <linux/math.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/pm_runtime.h> #include <linux/regmap.h> #include <linux/iio/iio.h> @@ -38,12 +39,21 @@ #define LTR390_ALS_UVS_GAIN 0x05 #define LTR390_PART_ID 0x06 #define LTR390_MAIN_STATUS 0x07 + #define LTR390_ALS_DATA 0x0D +#define LTR390_ALS_DATA_BYTE(n) (LTR390_ALS_DATA + (n)) + #define LTR390_UVS_DATA 0x10 +#define LTR390_UVS_DATA_BYTE(n) (LTR390_UVS_DATA + (n)) + #define LTR390_INT_CFG 0x19 #define LTR390_INT_PST 0x1A + #define LTR390_THRESH_UP 0x21 +#define LTR390_THRESH_UP_BYTE(n) (LTR390_THRESH_UP + (n)) + #define LTR390_THRESH_LOW 0x24 +#define LTR390_THRESH_LOW_BYTE(n) (LTR390_THRESH_LOW + (n)) #define LTR390_PART_NUMBER_ID 0xb #define LTR390_ALS_UVS_GAIN_MASK GENMASK(2, 0) @@ -96,6 +106,32 @@ struct ltr390_data { enum ltr390_mode mode; int gain; int int_time_us; + bool irq_enabled; +}; + +static const struct regmap_range ltr390_readable_reg_ranges[] = { + regmap_reg_range(LTR390_MAIN_CTRL, LTR390_MAIN_CTRL), + regmap_reg_range(LTR390_ALS_UVS_MEAS_RATE, LTR390_MAIN_STATUS), + regmap_reg_range(LTR390_ALS_DATA_BYTE(0), LTR390_UVS_DATA_BYTE(2)), + regmap_reg_range(LTR390_INT_CFG, LTR390_INT_PST), + regmap_reg_range(LTR390_THRESH_UP_BYTE(0), LTR390_THRESH_LOW_BYTE(2)), +}; + +static const struct regmap_access_table ltr390_readable_reg_table = { + .yes_ranges = ltr390_readable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(ltr390_readable_reg_ranges), +}; + +static const struct regmap_range ltr390_writeable_reg_ranges[] = { + regmap_reg_range(LTR390_MAIN_CTRL, LTR390_MAIN_CTRL), + regmap_reg_range(LTR390_ALS_UVS_MEAS_RATE, LTR390_ALS_UVS_GAIN), + regmap_reg_range(LTR390_INT_CFG, LTR390_INT_PST), + regmap_reg_range(LTR390_THRESH_UP_BYTE(0), LTR390_THRESH_LOW_BYTE(2)), +}; + +static const struct regmap_access_table ltr390_writeable_reg_table = { + .yes_ranges = ltr390_writeable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(ltr390_writeable_reg_ranges), }; static const struct regmap_config ltr390_regmap_config = { @@ -103,6 +139,9 @@ static const struct regmap_config ltr390_regmap_config = { .reg_bits = 8, .reg_stride = 1, .val_bits = 8, + .max_register = LTR390_THRESH_LOW_BYTE(2), + .rd_table = <r390_readable_reg_table, + .wr_table = <r390_writeable_reg_table, }; /* Sampling frequency is in mili Hz and mili Seconds */ @@ -178,9 +217,10 @@ static int ltr390_get_samp_freq_or_period(struct ltr390_data *data, return ltr390_samp_freq_table[value][option]; } -static int ltr390_read_raw(struct iio_dev *iio_device, - struct iio_chan_spec const *chan, int *val, - int *val2, long mask) + +static int ltr390_do_read_raw(struct iio_dev *iio_device, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) { int ret; struct ltr390_data *data = iio_priv(iio_device); @@ -243,6 +283,27 @@ static int ltr390_read_raw(struct iio_dev *iio_device, } } +static int ltr390_read_raw(struct iio_dev *iio_device, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct ltr390_data *data = iio_priv(iio_device); + struct device *dev = &data->client->dev; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "runtime PM failed to resume: %d\n", ret); + return ret; + } + + ret = ltr390_do_read_raw(iio_device, chan, val, val2, mask); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + /* integration time in us */ static const int ltr390_int_time_map_us[] = { 400000, 200000, 100000, 50000, 25000, 12500 }; static const int ltr390_gain_map[] = { 1, 3, 6, 9, 18 }; @@ -549,11 +610,11 @@ static int ltr390_read_event_config(struct iio_dev *indio_dev, return FIELD_GET(LTR390_LS_INT_EN, status); } -static int ltr390_write_event_config(struct iio_dev *indio_dev, - const struct iio_chan_spec *chan, - enum iio_event_type type, - enum iio_event_direction dir, - bool state) +static int ltr390_do_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) { struct ltr390_data *data = iio_priv(indio_dev); int ret; @@ -561,7 +622,6 @@ static int ltr390_write_event_config(struct iio_dev *indio_dev, if (!state) return regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); - guard(mutex)(&data->lock); ret = regmap_set_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); if (ret < 0) return ret; @@ -586,6 +646,51 @@ static int ltr390_write_event_config(struct iio_dev *indio_dev, } } +static int ltr390_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + int ret; + struct ltr390_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + + guard(mutex)(&data->lock); + + if (state && !data->irq_enabled) { + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "runtime PM failed to resume: %d\n", ret); + return ret; + } + data->irq_enabled = true; + } + + ret = ltr390_do_event_config(indio_dev, chan, type, dir, state); + + if (!state && data->irq_enabled) { + data->irq_enabled = false; + pm_runtime_put_autosuspend(dev); + } + + return ret; +} + +static int ltr390_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ltr390_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + if (readval) + return regmap_read(data->regmap, reg, readval); + + return regmap_write(data->regmap, reg, writeval); +} + static const struct iio_info ltr390_info = { .read_raw = ltr390_read_raw, .write_raw = ltr390_write_raw, @@ -594,6 +699,7 @@ static const struct iio_info ltr390_info = { .read_event_config = ltr390_read_event_config, .write_event_value = ltr390_write_event_value, .write_event_config = ltr390_write_event_config, + .debugfs_reg_access = ltr390_debugfs_reg_access, }; static irqreturn_t ltr390_interrupt_handler(int irq, void *private) @@ -628,6 +734,43 @@ static irqreturn_t ltr390_interrupt_handler(int irq, void *private) return IRQ_HANDLED; } +static void ltr390_powerdown(void *priv) +{ + struct ltr390_data *data = priv; + struct device *dev = &data->client->dev; + int ret; + + guard(mutex)(&data->lock); + + /* Ensure that power off and interrupts are disabled */ + if (data->irq_enabled) { + ret = regmap_clear_bits(data->regmap, LTR390_INT_CFG, LTR390_LS_INT_EN); + if (ret < 0) + dev_err(dev, "failed to disable interrupts\n"); + + data->irq_enabled = false; + pm_runtime_put_autosuspend(dev); + } + + ret = regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); + if (ret < 0) + dev_err(dev, "failed to disable sensor\n"); +} + +static int ltr390_pm_init(struct ltr390_data *data) +{ + int ret; + struct device *dev = &data->client->dev; + + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return dev_err_probe(dev, ret, "failed to enable runtime PM\n"); + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + return 0; +} + static int ltr390_probe(struct i2c_client *client) { struct ltr390_data *data; @@ -640,8 +783,9 @@ static int ltr390_probe(struct i2c_client *client) if (!indio_dev) return -ENOMEM; - data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data = iio_priv(indio_dev); data->regmap = devm_regmap_init_i2c(client, <r390_regmap_config); if (IS_ERR(data->regmap)) return dev_err_probe(dev, PTR_ERR(data->regmap), @@ -654,6 +798,8 @@ static int ltr390_probe(struct i2c_client *client) data->gain = 3; /* default mode for ltr390 is ALS mode */ data->mode = LTR390_SET_ALS_MODE; + /* default value of irq_enabled is false */ + data->irq_enabled = false; mutex_init(&data->lock); @@ -681,6 +827,10 @@ static int ltr390_probe(struct i2c_client *client) if (ret) return dev_err_probe(dev, ret, "failed to enable the sensor\n"); + ret = devm_add_action_or_reset(dev, ltr390_powerdown, data); + if (ret) + return dev_err_probe(dev, ret, "failed to add action or reset\n"); + if (client->irq) { ret = devm_request_threaded_irq(dev, client->irq, NULL, ltr390_interrupt_handler, @@ -692,6 +842,10 @@ static int ltr390_probe(struct i2c_client *client) "request irq (%d) failed\n", client->irq); } + ret = ltr390_pm_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize runtime PM\n"); + return devm_iio_device_register(dev, indio_dev); } @@ -713,7 +867,26 @@ static int ltr390_resume(struct device *dev) LTR390_SENSOR_ENABLE); } -static DEFINE_SIMPLE_DEV_PM_OPS(ltr390_pm_ops, ltr390_suspend, ltr390_resume); +static int ltr390_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); +} + +static int ltr390_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ltr390_data *data = iio_priv(indio_dev); + + return regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); +} + +static const struct dev_pm_ops ltr390_pm_ops = { + SYSTEM_SLEEP_PM_OPS(ltr390_suspend, ltr390_resume) + RUNTIME_PM_OPS(ltr390_runtime_suspend, ltr390_runtime_resume, NULL) +}; static const struct i2c_device_id ltr390_id[] = { { "ltr390" }, @@ -731,7 +904,7 @@ static struct i2c_driver ltr390_driver = { .driver = { .name = "ltr390", .of_match_table = ltr390_of_table, - .pm = pm_sleep_ptr(<r390_pm_ops), + .pm = pm_ptr(<r390_pm_ops), }, .probe = ltr390_probe, .id_table = ltr390_id, diff --git a/drivers/iio/light/ltr501.c b/drivers/iio/light/ltr501.c index debf57a52d1c..022e0693983b 100644 --- a/drivers/iio/light/ltr501.c +++ b/drivers/iio/light/ltr501.c @@ -1315,8 +1315,8 @@ static irqreturn_t ltr501_trigger_handler(int irq, void *p) scan.channels[j++] = psdata & LTR501_PS_DATA_MASK; } - iio_push_to_buffers_with_timestamp(indio_dev, &scan, - iio_get_time_ns(indio_dev)); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); done: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/light/ltrf216a.c b/drivers/iio/light/ltrf216a.c index 61f57a82b872..5f27f754fe1c 100644 --- a/drivers/iio/light/ltrf216a.c +++ b/drivers/iio/light/ltrf216a.c @@ -208,7 +208,6 @@ static int ltrf216a_set_power_state(struct ltrf216a_data *data, bool on) return ret; } } else { - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); } diff --git a/drivers/iio/light/max44000.c b/drivers/iio/light/max44000.c index e8b767680133..039d45af3a7f 100644 --- a/drivers/iio/light/max44000.c +++ b/drivers/iio/light/max44000.c @@ -75,11 +75,6 @@ struct max44000_data { struct mutex lock; struct regmap *regmap; - /* Ensure naturally aligned timestamp */ - struct { - u16 channels[2]; - aligned_s64 ts; - } scan; }; /* Default scale is set to the minimum of 0.03125 or 1 / (1 << 5) lux */ @@ -496,24 +491,29 @@ static irqreturn_t max44000_trigger_handler(int irq, void *p) int index = 0; unsigned int regval; int ret; + struct { + u16 channels[2]; + aligned_s64 ts; + } scan = { }; + mutex_lock(&data->lock); if (test_bit(MAX44000_SCAN_INDEX_ALS, indio_dev->active_scan_mask)) { ret = max44000_read_alsval(data); if (ret < 0) goto out_unlock; - data->scan.channels[index++] = ret; + scan.channels[index++] = ret; } if (test_bit(MAX44000_SCAN_INDEX_PRX, indio_dev->active_scan_mask)) { ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, ®val); if (ret < 0) goto out_unlock; - data->scan.channels[index] = regval; + scan.channels[index] = regval; } mutex_unlock(&data->lock); - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, - iio_get_time_ns(indio_dev)); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); iio_trigger_notify_done(indio_dev->trig); return IRQ_HANDLED; diff --git a/drivers/iio/light/opt4001.c b/drivers/iio/light/opt4001.c index ba4eb82d9bc2..95167273bb90 100644 --- a/drivers/iio/light/opt4001.c +++ b/drivers/iio/light/opt4001.c @@ -428,8 +428,7 @@ static int opt4001_probe(struct i2c_client *client) opt4001_chip_off_action, chip); if (ret < 0) - return dev_err_probe(&client->dev, ret, - "Failed to setup power off action\n"); + return ret; return devm_iio_device_register(&client->dev, indio_dev); } diff --git a/drivers/iio/light/opt4060.c b/drivers/iio/light/opt4060.c index 566f1bb8fe2a..981c704e7df5 100644 --- a/drivers/iio/light/opt4060.c +++ b/drivers/iio/light/opt4060.c @@ -1104,7 +1104,7 @@ static irqreturn_t opt4060_trigger_handler(int irq, void *p) } } - iio_push_to_buffers_with_timestamp(idev, &raw, pf->timestamp); + iio_push_to_buffers_with_ts(idev, &raw, sizeof(raw), pf->timestamp); err_read: iio_trigger_notify_done(idev->trig); return IRQ_HANDLED; @@ -1212,7 +1212,7 @@ static int opt4060_setup_trigger(struct opt4060_chip *chip, struct iio_dev *idev name = devm_kasprintf(chip->dev, GFP_KERNEL, "%s-opt4060", dev_name(chip->dev)); if (!name) - return dev_err_probe(chip->dev, -ENOMEM, "Failed to alloc chip name\n"); + return -ENOMEM; ret = devm_request_threaded_irq(chip->dev, chip->irq, NULL, opt4060_irq_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, @@ -1299,8 +1299,7 @@ static int opt4060_probe(struct i2c_client *client) ret = devm_add_action_or_reset(dev, opt4060_chip_off_action, chip); if (ret < 0) - return dev_err_probe(dev, ret, - "Failed to setup power off action\n"); + return ret; ret = opt4060_setup_buffer(chip, indio_dev); if (ret) diff --git a/drivers/iio/light/pa12203001.c b/drivers/iio/light/pa12203001.c index 8885852bef22..98a1f1624c75 100644 --- a/drivers/iio/light/pa12203001.c +++ b/drivers/iio/light/pa12203001.c @@ -185,15 +185,10 @@ static int pa12203001_set_power_state(struct pa12203001_data *data, bool on, mutex_unlock(&data->lock); } - if (on) { - ret = pm_runtime_resume_and_get(&data->client->dev); + if (on) + return pm_runtime_resume_and_get(&data->client->dev); - } else { - pm_runtime_mark_last_busy(&data->client->dev); - ret = pm_runtime_put_autosuspend(&data->client->dev); - } - - return ret; + return pm_runtime_put_autosuspend(&data->client->dev); err: mutex_unlock(&data->lock); diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c index 7cec5e943373..28d111ac8c0a 100644 --- a/drivers/iio/light/rohm-bu27034.c +++ b/drivers/iio/light/rohm-bu27034.c @@ -1193,7 +1193,8 @@ static int bu27034_buffer_thread(void *arg) */ data->scan.mlux = (u32)mlux; } - iio_push_to_buffers_with_timestamp(idev, &data->scan, tstamp); + iio_push_to_buffers_with_ts(idev, &data->scan, + sizeof(data->scan), tstamp); } return 0; diff --git a/drivers/iio/light/rpr0521.c b/drivers/iio/light/rpr0521.c index c50183f07240..9341c1d58cbe 100644 --- a/drivers/iio/light/rpr0521.c +++ b/drivers/iio/light/rpr0521.c @@ -358,12 +358,10 @@ static int rpr0521_set_power_state(struct rpr0521_data *data, bool on, * Note: If either measurement is re-enabled before _suspend(), * both stay enabled until _suspend(). */ - if (on) { + if (on) ret = pm_runtime_resume_and_get(&data->client->dev); - } else { - pm_runtime_mark_last_busy(&data->client->dev); + else ret = pm_runtime_put_autosuspend(&data->client->dev); - } if (ret < 0) { dev_err(&data->client->dev, "Failed: rpr0521_set_power_state for %d, ret %d\n", @@ -457,8 +455,8 @@ static irqreturn_t rpr0521_trigger_consumer_handler(int irq, void *p) data->scan.channels, (3 * 2) + 1); /* 3 * 16-bit + (discarded) int clear reg. */ if (!err) - iio_push_to_buffers_with_timestamp(indio_dev, - &data->scan, pf->timestamp); + iio_push_to_buffers_with_ts(indio_dev, &data->scan, + sizeof(data->scan), pf->timestamp); else dev_err(&data->client->dev, "Trigger consumer can't read from sensor.\n"); diff --git a/drivers/iio/light/si1145.c b/drivers/iio/light/si1145.c index 4aa02afd853e..f8eb251eca8d 100644 --- a/drivers/iio/light/si1145.c +++ b/drivers/iio/light/si1145.c @@ -494,8 +494,9 @@ static irqreturn_t si1145_trigger_handler(int irq, void *private) goto done; } - iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, - iio_get_time_ns(indio_dev)); + iio_push_to_buffers_with_ts(indio_dev, data->buffer, + sizeof(data->buffer), + iio_get_time_ns(indio_dev)); done: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/light/st_uvis25.h b/drivers/iio/light/st_uvis25.h index 1f93e3dc45c2..78bc56aad129 100644 --- a/drivers/iio/light/st_uvis25.h +++ b/drivers/iio/light/st_uvis25.h @@ -27,11 +27,6 @@ struct st_uvis25_hw { struct iio_trigger *trig; bool enabled; int irq; - /* Ensure timestamp is naturally aligned */ - struct { - u8 chan; - aligned_s64 ts; - } scan; }; extern const struct dev_pm_ops st_uvis25_pm_ops; diff --git a/drivers/iio/light/st_uvis25_core.c b/drivers/iio/light/st_uvis25_core.c index 124a8f9204a9..bcd729a9924e 100644 --- a/drivers/iio/light/st_uvis25_core.c +++ b/drivers/iio/light/st_uvis25_core.c @@ -234,15 +234,21 @@ static irqreturn_t st_uvis25_buffer_handler_thread(int irq, void *p) struct st_uvis25_hw *hw = iio_priv(iio_dev); unsigned int val; int err; + /* Ensure timestamp is naturally aligned */ + struct { + u8 chan; + aligned_s64 ts; + } scan = { }; + err = regmap_read(hw->regmap, ST_UVIS25_REG_OUT_ADDR, &val); if (err < 0) goto out; - hw->scan.chan = val; + scan.chan = val; - iio_push_to_buffers_with_timestamp(iio_dev, &hw->scan, - iio_get_time_ns(iio_dev)); + iio_push_to_buffers_with_ts(iio_dev, &scan, sizeof(scan), + iio_get_time_ns(iio_dev)); out: iio_trigger_notify_done(hw->trig); diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index 81dd2bfc22c0..a75a83594a7e 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -607,10 +607,8 @@ static int stk3310_probe(struct i2c_client *client) struct stk3310_data *data; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); - if (!indio_dev) { - dev_err(&client->dev, "iio allocation failed!\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->client = client; diff --git a/drivers/iio/light/tcs3414.c b/drivers/iio/light/tcs3414.c index 39268f855c77..5be461e6dbdb 100644 --- a/drivers/iio/light/tcs3414.c +++ b/drivers/iio/light/tcs3414.c @@ -53,11 +53,6 @@ struct tcs3414_data { u8 control; u8 gain; u8 timing; - /* Ensure timestamp is naturally aligned */ - struct { - u16 chans[4]; - aligned_s64 timestamp; - } scan; }; #define TCS3414_CHANNEL(_color, _si, _addr) { \ @@ -204,6 +199,12 @@ static irqreturn_t tcs3414_trigger_handler(int irq, void *p) struct iio_dev *indio_dev = pf->indio_dev; struct tcs3414_data *data = iio_priv(indio_dev); int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[4]; + aligned_s64 timestamp; + } scan = { }; + iio_for_each_active_channel(indio_dev, i) { int ret = i2c_smbus_read_word_data(data->client, @@ -211,10 +212,10 @@ static irqreturn_t tcs3414_trigger_handler(int irq, void *p) if (ret < 0) goto done; - data->scan.chans[j++] = ret; + scan.chans[j++] = ret; } - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), iio_get_time_ns(indio_dev)); done: diff --git a/drivers/iio/light/tcs3472.c b/drivers/iio/light/tcs3472.c index 0f8bf8503edd..12429a3261b3 100644 --- a/drivers/iio/light/tcs3472.c +++ b/drivers/iio/light/tcs3472.c @@ -64,11 +64,6 @@ struct tcs3472_data { u8 control; u8 atime; u8 apers; - /* Ensure timestamp is naturally aligned */ - struct { - u16 chans[4]; - aligned_s64 timestamp; - } scan; }; static const struct iio_event_spec tcs3472_events[] = { @@ -377,6 +372,11 @@ static irqreturn_t tcs3472_trigger_handler(int irq, void *p) struct iio_dev *indio_dev = pf->indio_dev; struct tcs3472_data *data = iio_priv(indio_dev); int i, j = 0; + /* Ensure timestamp is naturally aligned */ + struct { + u16 chans[4]; + aligned_s64 timestamp; + } scan = { }; int ret = tcs3472_req_data(data); if (ret < 0) @@ -388,10 +388,10 @@ static irqreturn_t tcs3472_trigger_handler(int irq, void *p) if (ret < 0) goto done; - data->scan.chans[j++] = ret; + scan.chans[j++] = ret; } - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), iio_get_time_ns(indio_dev)); done: diff --git a/drivers/iio/light/tsl2583.c b/drivers/iio/light/tsl2583.c index fc3b0c4226be..8801a491de77 100644 --- a/drivers/iio/light/tsl2583.c +++ b/drivers/iio/light/tsl2583.c @@ -641,16 +641,10 @@ static const struct iio_chan_spec tsl2583_channels[] = { static int tsl2583_set_pm_runtime_busy(struct tsl2583_chip *chip, bool on) { - int ret; + if (on) + return pm_runtime_resume_and_get(&chip->client->dev); - if (on) { - ret = pm_runtime_resume_and_get(&chip->client->dev); - } else { - pm_runtime_mark_last_busy(&chip->client->dev); - ret = pm_runtime_put_autosuspend(&chip->client->dev); - } - - return ret; + return pm_runtime_put_autosuspend(&chip->client->dev); } static int tsl2583_read_raw(struct iio_dev *indio_dev, diff --git a/drivers/iio/light/tsl2591.c b/drivers/iio/light/tsl2591.c index 08476f193a44..c5557867ea43 100644 --- a/drivers/iio/light/tsl2591.c +++ b/drivers/iio/light/tsl2591.c @@ -772,7 +772,6 @@ static int tsl2591_read_raw(struct iio_dev *indio_dev, err_unlock: mutex_unlock(&chip->als_mutex); - pm_runtime_mark_last_busy(&client->dev); pm_runtime_put_autosuspend(&client->dev); return ret; @@ -995,7 +994,6 @@ static int tsl2591_write_event_config(struct iio_dev *indio_dev, pm_runtime_get_sync(&client->dev); } else if (!state && chip->events_enabled) { chip->events_enabled = false; - pm_runtime_mark_last_busy(&client->dev); pm_runtime_put_autosuspend(&client->dev); } diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c index 61a0957317a1..d2f5a44892a8 100644 --- a/drivers/iio/light/us5182d.c +++ b/drivers/iio/light/us5182d.c @@ -361,19 +361,13 @@ static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) static int us5182d_set_power_state(struct us5182d_data *data, bool on) { - int ret; - if (data->power_mode == US5182D_ONESHOT) return 0; - if (on) { - ret = pm_runtime_resume_and_get(&data->client->dev); - } else { - pm_runtime_mark_last_busy(&data->client->dev); - ret = pm_runtime_put_autosuspend(&data->client->dev); - } + if (on) + return pm_runtime_resume_and_get(&data->client->dev); - return ret; + return pm_runtime_put_autosuspend(&data->client->dev); } static int us5182d_read_value(struct us5182d_data *data, diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c index 90e7d4421abf..4dbb2294a843 100644 --- a/drivers/iio/light/vcnl4000.c +++ b/drivers/iio/light/vcnl4000.c @@ -576,16 +576,11 @@ static bool vcnl4010_is_in_periodic_mode(struct vcnl4000_data *data) static int vcnl4000_set_pm_runtime_state(struct vcnl4000_data *data, bool on) { struct device *dev = &data->client->dev; - int ret; - if (on) { - ret = pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); - ret = pm_runtime_put_autosuspend(dev); - } + if (on) + return pm_runtime_resume_and_get(dev); - return ret; + return pm_runtime_put_autosuspend(dev); } static int vcnl4040_read_als_it(struct vcnl4000_data *data, int *val, int *val2) @@ -1662,7 +1657,10 @@ static irqreturn_t vcnl4010_trigger_handler(int irq, void *p) struct iio_dev *indio_dev = pf->indio_dev; struct vcnl4000_data *data = iio_priv(indio_dev); const unsigned long *active_scan_mask = indio_dev->active_scan_mask; - u16 buffer[8] __aligned(8) = {0}; /* 1x16-bit + naturally aligned ts */ + struct { + u16 chan; + aligned_s64 ts; + } scan = { }; bool data_read = false; unsigned long isr; int val = 0; @@ -1682,7 +1680,7 @@ static irqreturn_t vcnl4010_trigger_handler(int irq, void *p) if (ret < 0) goto end; - buffer[0] = val; + scan.chan = val; data_read = true; } } @@ -1695,8 +1693,8 @@ static irqreturn_t vcnl4010_trigger_handler(int irq, void *p) if (!data_read) goto end; - iio_push_to_buffers_with_timestamp(indio_dev, buffer, - iio_get_time_ns(indio_dev)); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + iio_get_time_ns(indio_dev)); end: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/light/vcnl4035.c b/drivers/iio/light/vcnl4035.c index 01bc99564f98..963747927425 100644 --- a/drivers/iio/light/vcnl4035.c +++ b/drivers/iio/light/vcnl4035.c @@ -141,17 +141,12 @@ static const struct iio_trigger_ops vcnl4035_trigger_ops = { static int vcnl4035_set_pm_runtime_state(struct vcnl4035_data *data, bool on) { - int ret; struct device *dev = &data->client->dev; - if (on) { - ret = pm_runtime_resume_and_get(dev); - } else { - pm_runtime_mark_last_busy(dev); - ret = pm_runtime_put_autosuspend(dev); - } + if (on) + return pm_runtime_resume_and_get(dev); - return ret; + return pm_runtime_put_autosuspend(dev); } static int vcnl4035_read_info_raw(struct iio_dev *indio_dev, diff --git a/drivers/iio/light/veml6030.c b/drivers/iio/light/veml6030.c index 0945f146bedb..6bcacae3863c 100644 --- a/drivers/iio/light/veml6030.c +++ b/drivers/iio/light/veml6030.c @@ -903,7 +903,7 @@ static irqreturn_t veml6030_trigger_handler(int irq, void *p) scan.chans[i++] = reg; } - iio_push_to_buffers_with_timestamp(iio, &scan, pf->timestamp); + iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), pf->timestamp); done: iio_trigger_notify_done(iio->trig); diff --git a/drivers/iio/light/veml6040.c b/drivers/iio/light/veml6040.c index 71a594b2ec85..f563f9f0ee67 100644 --- a/drivers/iio/light/veml6040.c +++ b/drivers/iio/light/veml6040.c @@ -219,8 +219,7 @@ static int veml6040_probe(struct i2c_client *client) indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "IIO device allocation failed\n"); + return -ENOMEM; regmap = devm_regmap_init_i2c(client, &veml6040_regmap_config); if (IS_ERR(regmap)) diff --git a/drivers/iio/light/veml6046x00.c b/drivers/iio/light/veml6046x00.c new file mode 100644 index 000000000000..e60f24d46e7b --- /dev/null +++ b/drivers/iio/light/veml6046x00.c @@ -0,0 +1,1030 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * VEML6046X00 High Accuracy RGBIR Color Sensor + * + * Copyright (c) 2025 Andreas Klinger <ak@it-klinger.de> + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <asm/byteorder.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* + * Device registers + * Those which are accessed as bulk io are omitted + */ +#define VEML6046X00_REG_CONF0 0x00 +#define VEML6046X00_REG_CONF1 0x01 +#define VEML6046X00_REG_THDH 0x04 +#define VEML6046X00_REG_THDL 0x06 +#define VEML6046X00_REG_R 0x10 +#define VEML6046X00_REG_G 0x12 +#define VEML6046X00_REG_B 0x14 +#define VEML6046X00_REG_IR 0x16 +#define VEML6046X00_REG_ID 0x18 +#define VEML6046X00_REG_INT 0x1A +#define VEML6046X00_REG_INT_H 0x1B + +/* Bit masks for specific functionality */ +#define VEML6046X00_CONF0_ON_0 BIT(0) +#define VEML6046X00_CONF0_INT BIT(1) +#define VEML6046X00_CONF0_AF_TRIG BIT(2) +#define VEML6046X00_CONF0_AF BIT(3) +#define VEML6046X00_CONF0_IT GENMASK(6, 4) +#define VEML6046X00_CONF1_CAL BIT(0) +#define VEML6046X00_CONF1_PERS GENMASK(2, 1) +#define VEML6046X00_CONF1_GAIN GENMASK(4, 3) +#define VEML6046X00_CONF1_PD_D2 BIT(6) +#define VEML6046X00_CONF1_ON_1 BIT(7) +#define VEML6046X00_INT_TH_H BIT(1) +#define VEML6046X00_INT_TH_L BIT(2) +#define VEML6046X00_INT_DRDY BIT(3) +#define VEML6046X00_INT_MASK \ + (VEML6046X00_INT_TH_H | VEML6046X00_INT_TH_L | VEML6046X00_INT_DRDY) + +#define VEML6046X00_GAIN_1 0x0 +#define VEML6046X00_GAIN_2 0x1 +#define VEML6046X00_GAIN_0_66 0x2 +#define VEML6046X00_GAIN_0_5 0x3 + +#define VEML6046X00_PD_2_2 0x0 +#define VEML6046X00_PD_1_2 BIT(6) + +/* Autosuspend delay */ +#define VEML6046X00_AUTOSUSPEND_MS (3 * MSEC_PER_SEC) + +enum veml6046x00_scan { + VEML6046X00_SCAN_R, + VEML6046X00_SCAN_G, + VEML6046X00_SCAN_B, + VEML6046X00_SCAN_IR, + VEML6046X00_SCAN_TIMESTAMP, +}; + +/** + * struct veml6046x00_rf - Regmap field of configuration registers. + * @int_en: Interrupt enable of green channel. + * @mode: Mode of operation. + * Driver uses always Active force mode. + * @trig: Trigger to be set in active force mode for starting + * measurement. + * @it: Integration time. + * @pers: Persistense - Number of threshold crossing for triggering + * interrupt. + */ +struct veml6046x00_rf { + struct regmap_field *int_en; + struct regmap_field *mode; + struct regmap_field *trig; + struct regmap_field *it; + struct regmap_field *pers; +}; + +/** + * struct veml6046x00_data - Private data of driver. + * @regmap: Regmap definition of sensor. + * @trig: Industrial-IO trigger. + * @rf: Regmap field of configuration. + */ +struct veml6046x00_data { + struct regmap *regmap; + struct iio_trigger *trig; + struct veml6046x00_rf rf; +}; + +/** + * DOC: Valid integration times (IT) + * + * static const int veml6046x00_it contains the array with valid IT. + * + * Register value to be read or written in regmap_field it on veml6046x00 is + * identical with array index. + * This means there is no separate translation table between valid integration + * times and register values needed. The index of the array is identical with + * the register value. + * + * The array is in the form as expected by the callback of the sysfs attribute + * integration_time_available (IIO_CHAN_INFO_INT_TIME). So there is no + * additional conversion needed. + */ +static const int veml6046x00_it[][2] = { + { 0, 3125 }, + { 0, 6250 }, + { 0, 12500 }, + { 0, 25000 }, + { 0, 50000 }, + { 0, 100000 }, + { 0, 200000 }, + { 0, 400000 }, +}; + +/** + * DOC: Handling of gain and photodiode size (PD) + * + * Gains here in the driver are not exactly the same as in the datasheet of the + * sensor. The gain in the driver is a combination of the gain of the sensor + * with the photodiode size (PD). + * The following combinations are possible: + * gain(driver) = gain(sensor) * PD + * 0.25 = x0.5 * 1/2 + * 0.33 = x0.66 * 1/2 + * 0.5 = x0.5 * 2/2 + * 0.66 = x0.66 * 2/2 + * 1 = x1 * 2/2 + * 2 = x2 * 2/2 + */ + +/** + * struct veml6046x00_gain_pd - Translation of gain and photodiode size (PD). + * @gain_sen: Gain used in the sensor as described in the datasheet of the + * sensor + * @pd: Photodiode size in the sensor + * + * This is the translation table from the gain used in the driver (and also used + * by the userspace interface in sysfs) to the gain and PD used in the sensor + * hardware. + * + * There are six gain values visible to the user (0.25 .. 2) which translate to + * two different gains in the sensor hardware (x0.5 .. x2) and two PD (1/2 and + * 2/2). Theoretical are there eight combinations, but gain values 0.5 and 1 are + * doubled and therefore the combination with the larger PD (2/2) is taken as + * more photodiode cells are supposed to deliver a more precise result. + */ +struct veml6046x00_gain_pd { + unsigned int gain_sen; + unsigned int pd; +}; + +static const struct veml6046x00_gain_pd veml6046x00_gain_pd[] = { + { .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_1_2 }, + { .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_1_2 }, + { .gain_sen = VEML6046X00_GAIN_0_5, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_0_66, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_1, .pd = VEML6046X00_PD_2_2 }, + { .gain_sen = VEML6046X00_GAIN_2, .pd = VEML6046X00_PD_2_2 }, +}; + +/** + * DOC: Factors for calculation of lux + * + * static const int veml6046x00_it_gains contains the factors for calculation of + * lux. + * + * Depending on the set up integration time (IT), gain and photodiode size (PD) + * the measured raw values are different if the light is constant. As the gain + * and PD are already coupled in the driver (see &struct veml6046x00_gain_pd) + * there are two dimensions remaining: IT and gain(driver). + * + * The array of available factors for a certain IT are grouped together in the + * same form as expected by the callback of scale_available + * (IIO_CHAN_INFO_SCALE). + * + * Factors for lux / raw count are taken directly from the datasheet. + */ +static const int veml6046x00_it_gains[][6][2] = { + /* integration time: 3.125 ms */ + { + { 5, 376000 }, /* gain: x0.25 */ + { 4, 72700 }, /* gain: x0.33 */ + { 2, 688000 }, /* gain: x0.5 */ + { 2, 36400 }, /* gain: x0.66 */ + { 1, 344000 }, /* gain: x1 */ + { 0, 672000 }, /* gain: x2 */ + }, + /* integration time: 6.25 ms */ + { + { 2, 688000 }, /* gain: x0.25 */ + { 2, 36350 }, /* gain: x0.33 */ + { 1, 344000 }, /* gain: x0.5 */ + { 1, 18200 }, /* gain: x0.66 */ + { 0, 672000 }, /* gain: x1 */ + { 0, 336000 }, /* gain: x2 */ + }, + /* integration time: 12.5 ms */ + { + { 1, 344000 }, /* gain: x0.25 */ + { 1, 18175 }, /* gain: x0.33 */ + { 0, 672000 }, /* gain: x0.5 */ + { 0, 509100 }, /* gain: x0.66 */ + { 0, 336000 }, /* gain: x1 */ + { 0, 168000 }, /* gain: x2 */ + }, + /* integration time: 25 ms */ + { + { 0, 672000 }, /* gain: x0.25 */ + { 0, 509087 }, /* gain: x0.33 */ + { 0, 336000 }, /* gain: x0.5 */ + { 0, 254550 }, /* gain: x0.66 */ + { 0, 168000 }, /* gain: x1 */ + { 0, 84000 }, /* gain: x2 */ + }, + /* integration time: 50 ms */ + { + { 0, 336000 }, /* gain: x0.25 */ + { 0, 254543 }, /* gain: x0.33 */ + { 0, 168000 }, /* gain: x0.5 */ + { 0, 127275 }, /* gain: x0.66 */ + { 0, 84000 }, /* gain: x1 */ + { 0, 42000 }, /* gain: x2 */ + }, + /* integration time: 100 ms */ + { + { 0, 168000 }, /* gain: x0.25 */ + { 0, 127271 }, /* gain: x0.33 */ + { 0, 84000 }, /* gain: x0.5 */ + { 0, 63637 }, /* gain: x0.66 */ + { 0, 42000 }, /* gain: x1 */ + { 0, 21000 }, /* gain: x2 */ + }, + /* integration time: 200 ms */ + { + { 0, 84000 }, /* gain: x0.25 */ + { 0, 63635 }, /* gain: x0.33 */ + { 0, 42000 }, /* gain: x0.5 */ + { 0, 31818 }, /* gain: x0.66 */ + { 0, 21000 }, /* gain: x1 */ + { 0, 10500 }, /* gain: x2 */ + }, + /* integration time: 400 ms */ + { + { 0, 42000 }, /* gain: x0.25 */ + { 0, 31817 }, /* gain: x0.33 */ + { 0, 21000 }, /* gain: x0.5 */ + { 0, 15909 }, /* gain: x0.66 */ + { 0, 10500 }, /* gain: x1 */ + { 0, 5250 }, /* gain: x2 */ + }, +}; + +/* + * Two bits (RGB_ON_0 and RGB_ON_1) must be cleared to power on the device. + */ +static int veml6046x00_power_on(struct veml6046x00_data *data) +{ + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF0, + VEML6046X00_CONF0_ON_0); + if (ret) { + dev_err(dev, "Failed to set bit for power on %d\n", ret); + return ret; + } + + return regmap_clear_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_ON_1); +} + +/* + * Two bits (RGB_ON_0 and RGB_ON_1) must be set to power off the device. + */ +static int veml6046x00_shutdown(struct veml6046x00_data *data) +{ + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_set_bits(data->regmap, VEML6046X00_REG_CONF0, + VEML6046X00_CONF0_ON_0); + if (ret) { + dev_err(dev, "Failed to set bit for shutdown %d\n", ret); + return ret; + } + + return regmap_set_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_ON_1); +} + +static void veml6046x00_shutdown_action(void *data) +{ + veml6046x00_shutdown(data); +} + +static const struct iio_chan_spec veml6046x00_channels[] = { + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_R, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_RED, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_R, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_G, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_GREEN, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_G, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_B, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BLUE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_B, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + { + .type = IIO_INTENSITY, + .address = VEML6046X00_REG_IR, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = VEML6046X00_SCAN_IR, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_LE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(VEML6046X00_SCAN_TIMESTAMP), +}; + +static const struct regmap_config veml6046x00_regmap_config = { + .name = "veml6046x00_regm", + .reg_bits = 8, + .val_bits = 8, + .max_register = VEML6046X00_REG_INT_H, +}; + +static const struct reg_field veml6046x00_rf_int_en = + REG_FIELD(VEML6046X00_REG_CONF0, 1, 1); + +static const struct reg_field veml6046x00_rf_trig = + REG_FIELD(VEML6046X00_REG_CONF0, 2, 2); + +static const struct reg_field veml6046x00_rf_mode = + REG_FIELD(VEML6046X00_REG_CONF0, 3, 3); + +static const struct reg_field veml6046x00_rf_it = + REG_FIELD(VEML6046X00_REG_CONF0, 4, 6); + +static const struct reg_field veml6046x00_rf_pers = + REG_FIELD(VEML6046X00_REG_CONF1, 1, 2); + +static int veml6046x00_regfield_init(struct veml6046x00_data *data) +{ + struct regmap *regmap = data->regmap; + struct device *dev = regmap_get_device(data->regmap); + struct regmap_field *rm_field; + struct veml6046x00_rf *rf = &data->rf; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_int_en); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->int_en = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_mode); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->mode = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_trig); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->trig = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_it); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->it = rm_field; + + rm_field = devm_regmap_field_alloc(dev, regmap, veml6046x00_rf_pers); + if (IS_ERR(rm_field)) + return PTR_ERR(rm_field); + rf->pers = rm_field; + + return 0; +} + +static int veml6046x00_get_it_index(struct veml6046x00_data *data) +{ + int ret; + unsigned int reg; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + /* register value is identical with index of array */ + if (reg >= ARRAY_SIZE(veml6046x00_it)) + return -EINVAL; + + return reg; +} + +static int veml6046x00_get_it_usec(struct veml6046x00_data *data, unsigned int *it_usec) +{ + int ret; + unsigned int reg; + + ret = regmap_field_read(data->rf.it, ®); + if (ret) + return ret; + + if (reg >= ARRAY_SIZE(veml6046x00_it)) + return -EINVAL; + + *it_usec = veml6046x00_it[reg][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int veml6046x00_set_it(struct iio_dev *iio, int val, int val2) +{ + struct veml6046x00_data *data = iio_priv(iio); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_it); i++) { + if ((veml6046x00_it[i][0] == val) && + (veml6046x00_it[i][1] == val2)) + return regmap_field_write(data->rf.it, i); + } + + return -EINVAL; +} + +static int veml6046x00_get_val_gain_idx(struct veml6046x00_data *data, int val, + int val2) +{ + unsigned int i; + int it_idx; + + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_it_gains[it_idx]); i++) { + if ((veml6046x00_it_gains[it_idx][i][0] == val) && + (veml6046x00_it_gains[it_idx][i][1] == val2)) + return i; + } + + return -EINVAL; +} + +static int veml6046x00_get_gain_idx(struct veml6046x00_data *data) +{ + int ret; + unsigned int i, reg, reg_gain, reg_pd; + + ret = regmap_read(data->regmap, VEML6046X00_REG_CONF1, ®); + if (ret) + return ret; + + reg_gain = FIELD_GET(VEML6046X00_CONF1_GAIN, reg); + reg_pd = reg & VEML6046X00_CONF1_PD_D2; + + for (i = 0; i < ARRAY_SIZE(veml6046x00_gain_pd); i++) { + if ((veml6046x00_gain_pd[i].gain_sen == reg_gain) && + (veml6046x00_gain_pd[i].pd == reg_pd)) + return i; + } + + return -EINVAL; +} + +static int veml6046x00_set_scale(struct iio_dev *iio, int val, int val2) +{ + struct veml6046x00_data *data = iio_priv(iio); + unsigned int new_scale; + int gain_idx; + + gain_idx = veml6046x00_get_val_gain_idx(data, val, val2); + if (gain_idx < 0) + return gain_idx; + + new_scale = FIELD_PREP(VEML6046X00_CONF1_GAIN, + veml6046x00_gain_pd[gain_idx].gain_sen) | + veml6046x00_gain_pd[gain_idx].pd; + + return regmap_update_bits(data->regmap, VEML6046X00_REG_CONF1, + VEML6046X00_CONF1_GAIN | + VEML6046X00_CONF1_PD_D2, + new_scale); +} + +static int veml6046x00_get_scale(struct veml6046x00_data *data, + int *val, int *val2) +{ + int gain_idx, it_idx; + + gain_idx = veml6046x00_get_gain_idx(data); + if (gain_idx < 0) + return gain_idx; + + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + + *val = veml6046x00_it_gains[it_idx][gain_idx][0]; + *val2 = veml6046x00_it_gains[it_idx][gain_idx][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +/** + * veml6046x00_read_data_ready() - Read data ready bit + * @data: Private data. + * + * Helper function for reading data ready bit from interrupt register. + * + * Return: + * * %1 - Data is available (AF_DATA_READY is set) + * * %0 - No data available + * * %-EIO - Error during bulk read + */ +static int veml6046x00_read_data_ready(struct veml6046x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + int ret; + u8 reg[2]; + + /* + * Note from the vendor, but not explicitly in the datasheet: we + * should always read both registers together. + */ + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT, + ®, sizeof(reg)); + if (ret) { + dev_err(dev, "Failed to read interrupt register %d\n", ret); + return -EIO; + } + + if (reg[1] & VEML6046X00_INT_DRDY) + return 1; + + return 0; +} + +/** + * veml6046x00_wait_data_available() - Wait until data is available + * @iio: Industrial IO. + * @usecs: Microseconds to wait for data. + * + * This function waits for a certain bit in the interrupt register which signals + * that there is data to be read available. + * + * It tries it two times with a waiting time of usecs in between. + * + * Return: + * * %1 - Data is available (AF_DATA_READY is set) + * * %0 - Timeout, no data available after usecs timeout + * * %-EIO - Error during bulk read + */ +static int veml6046x00_wait_data_available(struct iio_dev *iio, unsigned int usecs) +{ + struct veml6046x00_data *data = iio_priv(iio); + int ret; + + ret = veml6046x00_read_data_ready(data); + if (ret) + return ret; + + fsleep(usecs); + return veml6046x00_read_data_ready(data); +} + +static int veml6046x00_single_read(struct iio_dev *iio, + enum iio_modifier modifier, int *val) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + unsigned int addr, it_usec; + int ret; + __le16 reg; + + switch (modifier) { + case IIO_MOD_LIGHT_RED: + addr = VEML6046X00_REG_R; + break; + case IIO_MOD_LIGHT_GREEN: + addr = VEML6046X00_REG_G; + break; + case IIO_MOD_LIGHT_BLUE: + addr = VEML6046X00_REG_B; + break; + case IIO_MOD_LIGHT_IR: + addr = VEML6046X00_REG_IR; + break; + default: + return -EINVAL; + } + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = veml6046x00_get_it_usec(data, &it_usec); + if (ret < 0) { + dev_err(dev, "Failed to get integration time ret: %d", ret); + goto out; + } + + ret = regmap_field_write(data->rf.mode, 1); + if (ret) { + dev_err(dev, "Failed to write mode ret: %d", ret); + goto out; + } + + ret = regmap_field_write(data->rf.trig, 1); + if (ret) { + dev_err(dev, "Failed to write trigger ret: %d", ret); + goto out; + } + + /* integration time + 12.5 % to ensure completion */ + fsleep(it_usec + it_usec / 8); + + ret = veml6046x00_wait_data_available(iio, it_usec * 4); + if (ret < 0) + goto out; + if (ret == 0) { + ret = -EAGAIN; + goto out; + } + + if (!iio_device_claim_direct(iio)) { + ret = -EBUSY; + goto out; + } + + ret = regmap_bulk_read(data->regmap, addr, ®, sizeof(reg)); + iio_device_release_direct(iio); + if (ret) + goto out; + + *val = le16_to_cpu(reg); + + ret = IIO_VAL_INT; + +out: + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int veml6046x00_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct veml6046x00_data *data = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type != IIO_INTENSITY) + return -EINVAL; + return veml6046x00_single_read(iio, chan->channel2, val); + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + return veml6046x00_get_it_usec(data, val2); + case IIO_CHAN_INFO_SCALE: + return veml6046x00_get_scale(data, val, val2); + default: + return -EINVAL; + } +} + +static int veml6046x00_read_avail(struct iio_dev *iio, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct veml6046x00_data *data = iio_priv(iio); + int it_idx; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *vals = (int *)&veml6046x00_it; + *length = 2 * ARRAY_SIZE(veml6046x00_it); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + it_idx = veml6046x00_get_it_index(data); + if (it_idx < 0) + return it_idx; + *vals = (int *)&veml6046x00_it_gains[it_idx]; + *length = 2 * ARRAY_SIZE(veml6046x00_it_gains[it_idx]); + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int veml6046x00_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + return veml6046x00_set_it(iio, val, val2); + case IIO_CHAN_INFO_SCALE: + return veml6046x00_set_scale(iio, val, val2); + default: + return -EINVAL; + } +} + +static const struct iio_info veml6046x00_info_no_irq = { + .read_raw = veml6046x00_read_raw, + .read_avail = veml6046x00_read_avail, + .write_raw = veml6046x00_write_raw, +}; + +static int veml6046x00_buffer_preenable(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_field_write(data->rf.mode, 0); + if (ret) { + dev_err(dev, "Failed to set mode %d\n", ret); + return ret; + } + + ret = regmap_field_write(data->rf.trig, 0); + if (ret) { + /* + * no unrolling of mode as it is set appropriately with next + * single read. + */ + dev_err(dev, "Failed to set trigger %d\n", ret); + return ret; + } + + return pm_runtime_resume_and_get(dev); +} + +static int veml6046x00_buffer_postdisable(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = regmap_field_write(data->rf.mode, 1); + if (ret) { + dev_err(dev, "Failed to set mode %d\n", ret); + return ret; + } + + pm_runtime_put_autosuspend(dev); + + return 0; +} + +static const struct iio_buffer_setup_ops veml6046x00_buffer_setup_ops = { + .preenable = veml6046x00_buffer_preenable, + .postdisable = veml6046x00_buffer_postdisable, +}; + +static irqreturn_t veml6046x00_trig_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct veml6046x00_data *data = iio_priv(iio); + int ret; + struct { + __le16 chans[4]; + aligned_s64 timestamp; + } scan; + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_R, + &scan.chans, sizeof(scan.chans)); + if (ret) + goto done; + + iio_push_to_buffers_with_ts(iio, &scan, sizeof(scan), + iio_get_time_ns(iio)); + +done: + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int veml6046x00_validate_part_id(struct veml6046x00_data *data) +{ + struct device *dev = regmap_get_device(data->regmap); + unsigned int part_id; + int ret; + __le16 reg; + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_ID, + ®, sizeof(reg)); + if (ret) + return dev_err_probe(dev, ret, "Failed to read ID\n"); + + part_id = le16_to_cpu(reg); + if (part_id != 0x01) + dev_info(dev, "Unknown ID %#04x\n", part_id); + + return 0; +} + +static int veml6046x00_setup_device(struct iio_dev *iio) +{ + struct veml6046x00_data *data = iio_priv(iio); + struct device *dev = regmap_get_device(data->regmap); + int ret; + __le16 reg16; + + reg16 = cpu_to_le16(VEML6046X00_CONF0_AF); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_CONF0, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set configuration\n"); + + reg16 = cpu_to_le16(0); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDL, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set low threshold\n"); + + reg16 = cpu_to_le16(U16_MAX); + ret = regmap_bulk_write(data->regmap, VEML6046X00_REG_THDH, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set high threshold\n"); + + ret = regmap_bulk_read(data->regmap, VEML6046X00_REG_INT, + ®16, sizeof(reg16)); + if (ret) + return dev_err_probe(dev, ret, "Failed to clear interrupts\n"); + + return 0; +} + +static int veml6046x00_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct veml6046x00_data *data; + struct iio_dev *iio; + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &veml6046x00_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to set regmap\n"); + + iio = devm_iio_device_alloc(dev, sizeof(*data)); + if (!iio) + return -ENOMEM; + + data = iio_priv(iio); + /* struct iio_dev is retrieved via dev_get_drvdata(). */ + i2c_set_clientdata(i2c, iio); + data->regmap = regmap; + + ret = veml6046x00_regfield_init(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to init regfield\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + /* bring device in a known state and switch device on */ + ret = veml6046x00_setup_device(iio); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(dev, veml6046x00_shutdown_action, data); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to add shut down action\n"); + + ret = pm_runtime_set_active(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to activate PM runtime\n"); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable PM runtime\n"); + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, VEML6046X00_AUTOSUSPEND_MS); + pm_runtime_use_autosuspend(dev); + + ret = veml6046x00_validate_part_id(data); + if (ret) + return dev_err_probe(dev, ret, "Failed to validate device ID\n"); + + iio->name = "veml6046x00"; + iio->channels = veml6046x00_channels; + iio->num_channels = ARRAY_SIZE(veml6046x00_channels); + iio->modes = INDIO_DIRECT_MODE; + + iio->info = &veml6046x00_info_no_irq; + + ret = devm_iio_triggered_buffer_setup(dev, iio, NULL, + veml6046x00_trig_handler, + &veml6046x00_buffer_setup_ops); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register triggered buffer"); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, iio); + if (ret) + return dev_err_probe(dev, ret, "Failed to register iio device"); + + return 0; +} + +static int veml6046x00_runtime_suspend(struct device *dev) +{ + struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6046x00_shutdown(data); +} + +static int veml6046x00_runtime_resume(struct device *dev) +{ + struct veml6046x00_data *data = iio_priv(dev_get_drvdata(dev)); + + return veml6046x00_power_on(data); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(veml6046x00_pm_ops, + veml6046x00_runtime_suspend, + veml6046x00_runtime_resume, NULL); + +static const struct of_device_id veml6046x00_of_match[] = { + { .compatible = "vishay,veml6046x00" }, + { } +}; +MODULE_DEVICE_TABLE(of, veml6046x00_of_match); + +static const struct i2c_device_id veml6046x00_id[] = { + { "veml6046x00" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6046x00_id); + +static struct i2c_driver veml6046x00_driver = { + .driver = { + .name = "veml6046x00", + .of_match_table = veml6046x00_of_match, + .pm = pm_ptr(&veml6046x00_pm_ops), + }, + .probe = veml6046x00_probe, + .id_table = veml6046x00_id, +}; +module_i2c_driver(veml6046x00_driver); + +MODULE_AUTHOR("Andreas Klinger <ak@it-klinger.de>"); +MODULE_DESCRIPTION("VEML6046X00 RGBIR Color Sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/light/vl6180.c b/drivers/iio/light/vl6180.c index cc4f2e5404aa..c1314b144367 100644 --- a/drivers/iio/light/vl6180.c +++ b/drivers/iio/light/vl6180.c @@ -96,11 +96,6 @@ struct vl6180_data { unsigned int als_it_ms; unsigned int als_meas_rate; unsigned int range_meas_rate; - - struct { - u16 chan[2]; - aligned_s64 timestamp; - } scan; }; enum { VL6180_ALS, VL6180_RANGE, VL6180_PROX }; @@ -545,6 +540,11 @@ static irqreturn_t vl6180_trigger_handler(int irq, void *priv) struct vl6180_data *data = iio_priv(indio_dev); s64 time_ns = iio_get_time_ns(indio_dev); int ret, bit, i = 0; + struct { + u16 chan[2]; + aligned_s64 timestamp; + } scan = { }; + iio_for_each_active_channel(indio_dev, bit) { if (vl6180_chan_regs_table[bit].word) @@ -560,10 +560,10 @@ static irqreturn_t vl6180_trigger_handler(int irq, void *priv) return IRQ_HANDLED; } - data->scan.chan[i++] = ret; + scan.chan[i++] = ret; } - iio_push_to_buffers_with_timestamp(indio_dev, &data->scan, time_ns); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), time_ns); iio_trigger_notify_done(indio_dev->trig); /* Clear the interrupt flag after data read */ @@ -722,7 +722,7 @@ static int vl6180_probe(struct i2c_client *client) IRQF_ONESHOT, indio_dev->name, indio_dev); if (ret) - return dev_err_probe(&client->dev, ret, "devm_request_irq error \n"); + return dev_err_probe(&client->dev, ret, "devm_request_irq error\n"); init_completion(&data->completion); diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index 3debf1320ad1..81b812a29044 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -123,7 +123,7 @@ config HID_SENSOR_MAGNETOMETER_3D select IIO_BUFFER select HID_SENSOR_IIO_COMMON select HID_SENSOR_IIO_TRIGGER - tristate "HID Magenetometer 3D" + tristate "HID Magnetometer 3D" help Say yes here to build support for the HID SENSOR Magnetometer 3D. @@ -173,6 +173,19 @@ config IIO_ST_MAGN_SPI_3AXIS To compile this driver as a module, choose M here. The module will be called st_magn_spi. +config INFINEON_TLV493D + tristate "Infineon TLV493D Low-Power 3D Magnetic Sensor" + depends on I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say Y here to add support for the Infineon TLV493D-A1B6 Low- + Power 3D Magnetic Sensor. + + This driver can also be compiled as a module. + To compile this driver as a module, choose M here: the module + will be called tlv493d. + config SENSORS_HMC5843 tristate select IIO_BUFFER diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile index 9297723a97d8..dfe970fcacb8 100644 --- a/drivers/iio/magnetometer/Makefile +++ b/drivers/iio/magnetometer/Makefile @@ -23,6 +23,8 @@ st_magn-$(CONFIG_IIO_BUFFER) += st_magn_buffer.o obj-$(CONFIG_IIO_ST_MAGN_I2C_3AXIS) += st_magn_i2c.o obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o +obj-$(CONFIG_INFINEON_TLV493D) += tlv493d.o + obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o diff --git a/drivers/iio/magnetometer/ak8974.c b/drivers/iio/magnetometer/ak8974.c index 947fe8a475f2..68ece700c7ce 100644 --- a/drivers/iio/magnetometer/ak8974.c +++ b/drivers/iio/magnetometer/ak8974.c @@ -583,7 +583,6 @@ static int ak8974_measure_channel(struct ak8974 *ak8974, unsigned long address, *val = (s16)le16_to_cpu(hw_values[address]); out_unlock: mutex_unlock(&ak8974->lock); - pm_runtime_mark_last_busy(&ak8974->i2c->dev); pm_runtime_put_autosuspend(&ak8974->i2c->dev); return ret; @@ -678,7 +677,6 @@ static void ak8974_fill_buffer(struct iio_dev *indio_dev) out_unlock: mutex_unlock(&ak8974->lock); - pm_runtime_mark_last_busy(&ak8974->i2c->dev); pm_runtime_put_autosuspend(&ak8974->i2c->dev); } diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index a1e92b2abffd..3fd0171e5d69 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -775,7 +775,6 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) mutex_unlock(&data->lock); - pm_runtime_mark_last_busy(&data->client->dev); pm_runtime_put_autosuspend(&data->client->dev); /* Swap bytes and convert to valid range. */ diff --git a/drivers/iio/magnetometer/als31300.c b/drivers/iio/magnetometer/als31300.c index f72af829715f..2a2677428ed5 100644 --- a/drivers/iio/magnetometer/als31300.c +++ b/drivers/iio/magnetometer/als31300.c @@ -140,7 +140,6 @@ static int als31300_get_measure(struct als31300_data *data, *z = ALS31300_DATA_Z_GET(buf); out: - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return ret; @@ -156,7 +155,6 @@ static int als31300_read_raw(struct iio_dev *indio_dev, int ret; switch (mask) { - case IIO_CHAN_INFO_PROCESSED: case IIO_CHAN_INFO_RAW: ret = als31300_get_measure(data, &t, &x, &y, &z); if (ret) @@ -373,7 +371,7 @@ static int als31300_probe(struct i2c_client *i2c) ret = devm_add_action_or_reset(dev, als31300_power_down, data); if (ret) - return dev_err_probe(dev, ret, "failed to add powerdown action\n"); + return ret; indio_dev->info = &als31300_info; indio_dev->modes = INDIO_DIRECT_MODE; @@ -401,7 +399,6 @@ static int als31300_probe(struct i2c_client *i2c) pm_runtime_set_autosuspend_delay(dev, 200); pm_runtime_use_autosuspend(dev); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); ret = devm_iio_device_register(dev, indio_dev); diff --git a/drivers/iio/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c index 761daead5ada..6a73f6e2f1f0 100644 --- a/drivers/iio/magnetometer/bmc150_magn.c +++ b/drivers/iio/magnetometer/bmc150_magn.c @@ -257,22 +257,17 @@ static int bmc150_magn_set_power_mode(struct bmc150_magn_data *data, static int bmc150_magn_set_power_state(struct bmc150_magn_data *data, bool on) { -#ifdef CONFIG_PM - int ret; + int ret = 0; - if (on) { + if (on) ret = pm_runtime_resume_and_get(data->dev); - } else { - pm_runtime_mark_last_busy(data->dev); - ret = pm_runtime_put_autosuspend(data->dev); - } - + else + pm_runtime_put_autosuspend(data->dev); if (ret < 0) { dev_err(data->dev, "failed to change power state to %d\n", on); return ret; } -#endif return 0; } diff --git a/drivers/iio/magnetometer/tlv493d.c b/drivers/iio/magnetometer/tlv493d.c new file mode 100644 index 000000000000..ec53fd40277b --- /dev/null +++ b/drivers/iio/magnetometer/tlv493d.c @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the Infineon TLV493D Low-Power 3D Magnetic Sensor + * + * Copyright (C) 2025 Dixit Parmar <dixitparmar19@gmail.com> + */ + +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* + * TLV493D sensor I2C communication note: + * + * The sensor supports only direct byte-stream write starting from the + * register address 0x0. So for any modification to be made to any write + * registers, it must be written starting from the register address 0x0. + * I2C write operation should not contain the register address in the I2C + * frame, it should contain only raw byte stream for the write registers. + * I2C Frame: |S|SlaveAddr Wr|Ack|Byte[0]|Ack|Byte[1]|Ack|.....|Sp| + * + * Same as the write operation, reading from the sensor registers is also + * performed starting from the register address 0x0 for as many bytes as + * need to be read. + * I2C read operation should not contain the register address in the I2C frame. + * I2C Frame: |S|SlaveAddr Rd|Ack|Byte[0]|Ack|Byte[1]|Ack|.....|Sp| + */ + +#define TLV493D_RD_REG_BX 0x00 +#define TLV493D_RD_REG_BY 0x01 +#define TLV493D_RD_REG_BZ 0x02 +#define TLV493D_RD_REG_TEMP 0x03 +#define TLV493D_RD_REG_BX2 0x04 +#define TLV493D_RD_REG_BZ2 0x05 +#define TLV493D_RD_REG_TEMP2 0x06 +#define TLV493D_RD_REG_RES1 0x07 +#define TLV493D_RD_REG_RES2 0x08 +#define TLV493D_RD_REG_RES3 0x09 +#define TLV493D_RD_REG_MAX 0x0a + +#define TLV493D_WR_REG_MODE1 0x01 +#define TLV493D_WR_REG_MODE2 0x03 +#define TLV493D_WR_REG_MAX 0x04 + +#define TLV493D_BX_MAG_X_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BX2_MAG_X_AXIS_LSB GENMASK(7, 4) +#define TLV493D_BY_MAG_Y_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BX2_MAG_Y_AXIS_LSB GENMASK(3, 0) +#define TLV493D_BZ_MAG_Z_AXIS_MSB GENMASK(7, 0) +#define TLV493D_BZ2_MAG_Z_AXIS_LSB GENMASK(3, 0) +#define TLV493D_TEMP_TEMP_MSB GENMASK(7, 4) +#define TLV493D_TEMP2_TEMP_LSB GENMASK(7, 0) +#define TLV493D_TEMP_CHANNEL GENMASK(1, 0) +#define TLV493D_MODE1_MOD_LOWFAST GENMASK(1, 0) +#define TLV493D_MODE2_LP_PERIOD BIT(6) +#define TLV493D_RD_REG_RES1_WR_MASK GENMASK(4, 3) +#define TLV493D_RD_REG_RES2_WR_MASK GENMASK(7, 0) +#define TLV493D_RD_REG_RES3_WR_MASK GENMASK(4, 0) + +enum tlv493d_channels { + TLV493D_AXIS_X, + TLV493D_AXIS_Y, + TLV493D_AXIS_Z, + TLV493D_TEMPERATURE, +}; + +enum tlv493d_op_mode { + TLV493D_OP_MODE_POWERDOWN, + TLV493D_OP_MODE_FAST, + TLV493D_OP_MODE_LOWPOWER, + TLV493D_OP_MODE_ULTRA_LOWPOWER, + TLV493D_OP_MODE_MASTERCONTROLLED, +}; + +struct tlv493d_data { + struct i2c_client *client; + /* protects from simultaneous sensor access and register readings */ + struct mutex lock; + enum tlv493d_op_mode mode; + u8 wr_regs[TLV493D_WR_REG_MAX]; +}; + +/* + * Different mode has different measurement sampling time, this time is + * used in deriving the sleep and timeout while reading the data from + * sensor in polling. + * Power-down mode: No measurement. + * Fast mode: Freq:3.3 KHz. Measurement time:305 usec. + * Low-power mode: Freq:100 Hz. Measurement time:10 msec. + * Ultra low-power mode: Freq:10 Hz. Measurement time:100 msec. + * Master controlled mode: Freq:3.3 Khz. Measurement time:305 usec. + */ +static const u32 tlv493d_sample_rate_us[] = { + [TLV493D_OP_MODE_POWERDOWN] = 0, + [TLV493D_OP_MODE_FAST] = 305, + [TLV493D_OP_MODE_LOWPOWER] = 10 * USEC_PER_MSEC, + [TLV493D_OP_MODE_ULTRA_LOWPOWER] = 100 * USEC_PER_MSEC, + [TLV493D_OP_MODE_MASTERCONTROLLED] = 305, +}; + +static int tlv493d_write_all_regs(struct tlv493d_data *data) +{ + int ret; + struct device *dev = &data->client->dev; + + ret = i2c_master_send(data->client, data->wr_regs, ARRAY_SIZE(data->wr_regs)); + if (ret < 0) { + dev_err(dev, "i2c write registers failed, error: %d\n", ret); + return ret; + } + + return 0; +} + +static int tlv493d_set_operating_mode(struct tlv493d_data *data, enum tlv493d_op_mode mode) +{ + u8 *mode1_cfg = &data->wr_regs[TLV493D_WR_REG_MODE1]; + u8 *mode2_cfg = &data->wr_regs[TLV493D_WR_REG_MODE2]; + + switch (mode) { + case TLV493D_OP_MODE_POWERDOWN: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 0); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_FAST: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 1); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_LOWPOWER: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 2); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 1); + break; + + case TLV493D_OP_MODE_ULTRA_LOWPOWER: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 2); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + + case TLV493D_OP_MODE_MASTERCONTROLLED: + FIELD_MODIFY(TLV493D_MODE1_MOD_LOWFAST, mode1_cfg, 3); + FIELD_MODIFY(TLV493D_MODE2_LP_PERIOD, mode2_cfg, 0); + break; + } + + return tlv493d_write_all_regs(data); +} + +static s16 tlv493d_get_channel_data(u8 *b, enum tlv493d_channels ch) +{ + u16 val; + + switch (ch) { + case TLV493D_AXIS_X: + val = FIELD_GET(TLV493D_BX_MAG_X_AXIS_MSB, b[TLV493D_RD_REG_BX]) << 4 | + FIELD_GET(TLV493D_BX2_MAG_X_AXIS_LSB, b[TLV493D_RD_REG_BX2]) >> 4; + break; + case TLV493D_AXIS_Y: + val = FIELD_GET(TLV493D_BY_MAG_Y_AXIS_MSB, b[TLV493D_RD_REG_BY]) << 4 | + FIELD_GET(TLV493D_BX2_MAG_Y_AXIS_LSB, b[TLV493D_RD_REG_BX2]); + break; + case TLV493D_AXIS_Z: + val = FIELD_GET(TLV493D_BZ_MAG_Z_AXIS_MSB, b[TLV493D_RD_REG_BZ]) << 4 | + FIELD_GET(TLV493D_BZ2_MAG_Z_AXIS_LSB, b[TLV493D_RD_REG_BZ2]); + break; + case TLV493D_TEMPERATURE: + val = FIELD_GET(TLV493D_TEMP_TEMP_MSB, b[TLV493D_RD_REG_TEMP]) << 8 | + FIELD_GET(TLV493D_TEMP2_TEMP_LSB, b[TLV493D_RD_REG_TEMP2]); + break; + } + + return sign_extend32(val, 11); +} + +static int tlv493d_get_measurements(struct tlv493d_data *data, s16 *x, s16 *y, + s16 *z, s16 *t) +{ + u8 buff[7] = {}; + int err, ret; + struct device *dev = &data->client->dev; + u32 sleep_us = tlv493d_sample_rate_us[data->mode]; + + guard(mutex)(&data->lock); + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* + * Poll until data is valid. + * For a valid data TLV493D_TEMP_CHANNEL bit of TLV493D_RD_REG_TEMP + * should be set to 0. The sampling time depends on the sensor mode. + * Poll 3x the time of the sampling time. + */ + ret = read_poll_timeout(i2c_master_recv, err, + err || !FIELD_GET(TLV493D_TEMP_CHANNEL, buff[TLV493D_RD_REG_TEMP]), + sleep_us, 3 * sleep_us, false, data->client, buff, + ARRAY_SIZE(buff)); + if (ret) { + dev_err(dev, "i2c read poll timeout, error:%d\n", ret); + goto out_put_autosuspend; + } + if (err < 0) { + dev_err(dev, "i2c read data failed, error:%d\n", err); + ret = err; + goto out_put_autosuspend; + } + + *x = tlv493d_get_channel_data(buff, TLV493D_AXIS_X); + *y = tlv493d_get_channel_data(buff, TLV493D_AXIS_Y); + *z = tlv493d_get_channel_data(buff, TLV493D_AXIS_Z); + *t = tlv493d_get_channel_data(buff, TLV493D_TEMPERATURE); + +out_put_autosuspend: + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int tlv493d_init(struct tlv493d_data *data) +{ + int ret; + u8 buff[TLV493D_RD_REG_MAX]; + struct device *dev = &data->client->dev; + + /* + * The sensor initialization requires below steps to be followed, + * 1. Power-up sensor. + * 2. Read and store read-registers map (0x0-0x9). + * 3. Copy values from read reserved registers to write reserved fields + * (0x0-0x3). + * 4. Set operating mode. + * 5. Write to all registers. + */ + ret = i2c_master_recv(data->client, buff, ARRAY_SIZE(buff)); + if (ret < 0) + return dev_err_probe(dev, ret, "i2c read failed\n"); + + /* Write register 0x0 is reserved. Does not require to be updated.*/ + data->wr_regs[0] = 0; + data->wr_regs[1] = buff[TLV493D_RD_REG_RES1] & TLV493D_RD_REG_RES1_WR_MASK; + data->wr_regs[2] = buff[TLV493D_RD_REG_RES2] & TLV493D_RD_REG_RES2_WR_MASK; + data->wr_regs[3] = buff[TLV493D_RD_REG_RES3] & TLV493D_RD_REG_RES3_WR_MASK; + + ret = tlv493d_set_operating_mode(data, data->mode); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to set operating mode\n"); + + return 0; +} + +static int tlv493d_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct tlv493d_data *data = iio_priv(indio_dev); + s16 x, y, z, t; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = tlv493d_get_measurements(data, &x, &y, &z, &t); + if (ret) + return ret; + + switch (chan->address) { + case TLV493D_AXIS_X: + *val = x; + return IIO_VAL_INT; + case TLV493D_AXIS_Y: + *val = y; + return IIO_VAL_INT; + case TLV493D_AXIS_Z: + *val = z; + return IIO_VAL_INT; + case TLV493D_TEMPERATURE: + *val = t; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_MAGN: + /* + * Magnetic field scale: 0.0098 mTesla (i.e. 9.8 µT) + * Magnetic field in Gauss: mT * 10 = 0.098. + */ + *val = 98; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + case IIO_TEMP: + /* + * Temperature scale: 1.1 °C per LSB, expressed as 1100 m°C + * Returned as integer for IIO core to apply: + * temp = (raw + offset) * scale + */ + *val = 1100; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + /* + * Temperature offset includes sensor-specific raw offset + * plus compensation for +25°C bias in formula. + * offset = -raw_offset + (25000 / 1100) + * -340 + 22.72 = -317.28 + */ + *val = -31728; + *val2 = 100; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static irqreturn_t tlv493d_trigger_handler(int irq, void *ptr) +{ + int ret; + s16 x, y, z, t; + struct iio_poll_func *pf = ptr; + struct iio_dev *indio_dev = pf->indio_dev; + struct tlv493d_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + struct { + s16 channels[3]; + s16 temperature; + aligned_s64 timestamp; + } scan; + + ret = tlv493d_get_measurements(data, &x, &y, &z, &t); + if (ret) { + dev_err(dev, "failed to read sensor data\n"); + goto out_trigger_notify; + } + + scan.channels[0] = x; + scan.channels[1] = y; + scan.channels[2] = z; + scan.temperature = t; + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); + +out_trigger_notify: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define TLV493D_AXIS_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + }, \ + } + +static const struct iio_chan_spec tlv493d_channels[] = { + TLV493D_AXIS_CHANNEL(X, TLV493D_AXIS_X), + TLV493D_AXIS_CHANNEL(Y, TLV493D_AXIS_Y), + TLV493D_AXIS_CHANNEL(Z, TLV493D_AXIS_Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = TLV493D_TEMPERATURE, + .scan_index = TLV493D_TEMPERATURE, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct iio_info tlv493d_info = { + .read_raw = tlv493d_read_raw, +}; + +static const unsigned long tlv493d_scan_masks[] = { GENMASK(3, 0), 0 }; + +static int tlv493d_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct tlv493d_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "failed to enable regulator\n"); + + /* + * Setting Sensor default operating mode to Master-Controlled mode since + * it performs measurement cycle only on-request and stays in Power-Down + * state until next cycle is initiated. + */ + data->mode = TLV493D_OP_MODE_MASTERCONTROLLED; + ret = tlv493d_init(data); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize\n"); + + indio_dev->info = &tlv493d_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = client->name; + indio_dev->channels = tlv493d_channels; + indio_dev->num_channels = ARRAY_SIZE(tlv493d_channels); + indio_dev->available_scan_masks = tlv493d_scan_masks; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + tlv493d_trigger_handler, + NULL); + if (ret) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = pm_runtime_set_active(dev); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, 500); + pm_runtime_use_autosuspend(dev); + + pm_runtime_put_autosuspend(dev); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "iio device register failed\n"); + + return 0; +} + +static int tlv493d_runtime_suspend(struct device *dev) +{ + struct tlv493d_data *data = iio_priv(dev_get_drvdata(dev)); + + return tlv493d_set_operating_mode(data, TLV493D_OP_MODE_POWERDOWN); +} + +static int tlv493d_runtime_resume(struct device *dev) +{ + struct tlv493d_data *data = iio_priv(dev_get_drvdata(dev)); + + return tlv493d_set_operating_mode(data, data->mode); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(tlv493d_pm_ops, tlv493d_runtime_suspend, + tlv493d_runtime_resume, NULL); + +static const struct i2c_device_id tlv493d_id[] = { + { "tlv493d" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tlv493d_id); + +static const struct of_device_id tlv493d_of_match[] = { + { .compatible = "infineon,tlv493d-a1b6" }, + { } +}; +MODULE_DEVICE_TABLE(of, tlv493d_of_match); + +static struct i2c_driver tlv493d_driver = { + .driver = { + .name = "tlv493d", + .of_match_table = tlv493d_of_match, + .pm = pm_ptr(&tlv493d_pm_ops), + }, + .probe = tlv493d_probe, + .id_table = tlv493d_id, +}; +module_i2c_driver(tlv493d_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Infineon TLV493D Low-Power 3D Magnetic Sensor"); +MODULE_AUTHOR("Dixit Parmar <dixitparmar19@gmail.com>"); diff --git a/drivers/iio/magnetometer/tmag5273.c b/drivers/iio/magnetometer/tmag5273.c index 2ca5c26f0091..2adc3c036ab4 100644 --- a/drivers/iio/magnetometer/tmag5273.c +++ b/drivers/iio/magnetometer/tmag5273.c @@ -287,7 +287,6 @@ static int tmag5273_read_raw(struct iio_dev *indio_dev, int ret; switch (mask) { - case IIO_CHAN_INFO_PROCESSED: case IIO_CHAN_INFO_RAW: ret = pm_runtime_resume_and_get(data->dev); if (ret < 0) @@ -295,7 +294,6 @@ static int tmag5273_read_raw(struct iio_dev *indio_dev, ret = tmag5273_get_measure(data, &t, &x, &y, &z, &angle, &magnitude); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); if (ret) @@ -642,7 +640,7 @@ static int tmag5273_probe(struct i2c_client *i2c) */ ret = devm_add_action_or_reset(dev, tmag5273_power_down, data); if (ret) - return dev_err_probe(dev, ret, "failed to add powerdown action\n"); + return ret; ret = pm_runtime_set_active(dev); if (ret < 0) @@ -668,7 +666,6 @@ static int tmag5273_probe(struct i2c_client *i2c) indio_dev->channels = tmag5273_channels; indio_dev->num_channels = ARRAY_SIZE(tmag5273_channels); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); ret = devm_iio_device_register(dev, indio_dev); diff --git a/drivers/iio/magnetometer/yamaha-yas530.c b/drivers/iio/magnetometer/yamaha-yas530.c index 340607111d9a..d49e37edcbed 100644 --- a/drivers/iio/magnetometer/yamaha-yas530.c +++ b/drivers/iio/magnetometer/yamaha-yas530.c @@ -623,7 +623,6 @@ static int yas5xx_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_RAW: pm_runtime_get_sync(yas5xx->dev); ret = ci->get_measure(yas5xx, &t, &x, &y, &z); - pm_runtime_mark_last_busy(yas5xx->dev); pm_runtime_put_autosuspend(yas5xx->dev); if (ret) return ret; @@ -664,7 +663,6 @@ static void yas5xx_fill_buffer(struct iio_dev *indio_dev) pm_runtime_get_sync(yas5xx->dev); ret = ci->get_measure(yas5xx, &t, &x, &y, &z); - pm_runtime_mark_last_busy(yas5xx->dev); pm_runtime_put_autosuspend(yas5xx->dev); if (ret) { dev_err(yas5xx->dev, "error refilling buffer\n"); diff --git a/drivers/iio/potentiostat/lmp91000.c b/drivers/iio/potentiostat/lmp91000.c index 030498d0b763..eccc2a34358f 100644 --- a/drivers/iio/potentiostat/lmp91000.c +++ b/drivers/iio/potentiostat/lmp91000.c @@ -321,10 +321,8 @@ static int lmp91000_probe(struct i2c_client *client) data->trig = devm_iio_trigger_alloc(dev, "%s-mux%d", indio_dev->name, iio_device_id(indio_dev)); - if (!data->trig) { - dev_err(dev, "cannot allocate iio trigger.\n"); + if (!data->trig) return -ENOMEM; - } init_completion(&data->completion); diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c index 6cdc8ed53520..c04e8bb4c993 100644 --- a/drivers/iio/pressure/bmp280-core.c +++ b/drivers/iio/pressure/bmp280-core.c @@ -752,7 +752,6 @@ static int bmp280_read_raw(struct iio_dev *indio_dev, pm_runtime_get_sync(data->dev); ret = bmp280_read_raw_impl(indio_dev, chan, val, val2, mask); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return ret; @@ -927,7 +926,6 @@ static int bmp280_write_raw(struct iio_dev *indio_dev, pm_runtime_get_sync(data->dev); ret = bmp280_write_raw_impl(indio_dev, chan, val, val2, mask); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return ret; @@ -2255,7 +2253,6 @@ static int bmp580_nvmem_read(void *priv, unsigned int offset, void *val, pm_runtime_get_sync(data->dev); ret = bmp580_nvmem_read_impl(priv, offset, val, bytes); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return ret; @@ -2330,7 +2327,6 @@ static int bmp580_nvmem_write(void *priv, unsigned int offset, void *val, pm_runtime_get_sync(data->dev); ret = bmp580_nvmem_write_impl(priv, offset, val, bytes); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return ret; @@ -3120,7 +3116,6 @@ static int bmp280_buffer_postdisable(struct iio_dev *indio_dev) { struct bmp280_data *data = iio_priv(indio_dev); - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return 0; @@ -3217,8 +3212,7 @@ int bmp280_common_probe(struct device *dev, return dev_err_probe(dev, PTR_ERR(gpiod), "failed to get reset GPIO\n"); /* Deassert the signal */ - dev_info(dev, "release reset\n"); - gpiod_set_value(gpiod, 0); + gpiod_set_value_cansleep(gpiod, 0); data->regmap = regmap; diff --git a/drivers/iio/pressure/dlhl60d.c b/drivers/iio/pressure/dlhl60d.c index 6a13cf2eaf50..8bad7162fec6 100644 --- a/drivers/iio/pressure/dlhl60d.c +++ b/drivers/iio/pressure/dlhl60d.c @@ -289,10 +289,8 @@ static int dlh_probe(struct i2c_client *client) } indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); - if (!indio_dev) { - dev_err(&client->dev, "failed to allocate iio device\n"); + if (!indio_dev) return -ENOMEM; - } i2c_set_clientdata(client, indio_dev); diff --git a/drivers/iio/pressure/icp10100.c b/drivers/iio/pressure/icp10100.c index 1951c1cc84cf..3d83d0098a57 100644 --- a/drivers/iio/pressure/icp10100.c +++ b/drivers/iio/pressure/icp10100.c @@ -265,7 +265,6 @@ static int icp10100_get_measures(struct icp10100_state *st, (be16_to_cpu(measures[1]) >> 8); *temperature = be16_to_cpu(measures[2]); - pm_runtime_mark_last_busy(&st->client->dev); error_measure: pm_runtime_put_autosuspend(&st->client->dev); return ret; diff --git a/drivers/iio/pressure/mpl115.c b/drivers/iio/pressure/mpl115.c index 71beb28b7f2c..830a5065c008 100644 --- a/drivers/iio/pressure/mpl115.c +++ b/drivers/iio/pressure/mpl115.c @@ -108,7 +108,6 @@ static int mpl115_read_raw(struct iio_dev *indio_dev, ret = mpl115_comp_pressure(data, val, val2); if (ret < 0) return ret; - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); return IIO_VAL_INT_PLUS_MICRO; @@ -118,7 +117,6 @@ static int mpl115_read_raw(struct iio_dev *indio_dev, ret = mpl115_read_temp(data); if (ret < 0) return ret; - pm_runtime_mark_last_busy(data->dev); pm_runtime_put_autosuspend(data->dev); *val = ret >> 6; diff --git a/drivers/iio/pressure/zpa2326.c b/drivers/iio/pressure/zpa2326.c index 6eef37c0952d..4923a558a26a 100644 --- a/drivers/iio/pressure/zpa2326.c +++ b/drivers/iio/pressure/zpa2326.c @@ -697,7 +697,6 @@ static void zpa2326_suspend(struct iio_dev *indio_dev) zpa2326_sleep(indio_dev); - pm_runtime_mark_last_busy(parent); pm_runtime_put_autosuspend(parent); } @@ -708,7 +707,6 @@ static void zpa2326_init_runtime(struct device *parent) pm_runtime_enable(parent); pm_runtime_set_autosuspend_delay(parent, 1000); pm_runtime_use_autosuspend(parent); - pm_runtime_mark_last_busy(parent); pm_runtime_put_autosuspend(parent); } diff --git a/drivers/iio/proximity/d3323aa.c b/drivers/iio/proximity/d3323aa.c index d4c3dbea9bb0..30821f583454 100644 --- a/drivers/iio/proximity/d3323aa.c +++ b/drivers/iio/proximity/d3323aa.c @@ -722,8 +722,7 @@ static int d3323aa_probe(struct platform_device *pdev) indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) - return dev_err_probe(dev, -ENOMEM, - "Could not allocate iio device\n"); + return -ENOMEM; data = iio_priv(indio_dev); data->dev = dev; diff --git a/drivers/iio/proximity/hx9023s.c b/drivers/iio/proximity/hx9023s.c index 33781c314728..2918dfc0df54 100644 --- a/drivers/iio/proximity/hx9023s.c +++ b/drivers/iio/proximity/hx9023s.c @@ -1141,8 +1141,7 @@ static int hx9023s_probe(struct i2c_client *client) indio_dev->name, iio_device_id(indio_dev)); if (!data->trig) - return dev_err_probe(dev, -ENOMEM, - "iio trigger alloc failed\n"); + return -ENOMEM; data->trig->ops = &hx9023s_trigger_ops; iio_trigger_set_drvdata(data->trig, indio_dev); diff --git a/drivers/iio/proximity/irsd200.c b/drivers/iio/proximity/irsd200.c index 253e4aef22fb..65af31d43453 100644 --- a/drivers/iio/proximity/irsd200.c +++ b/drivers/iio/proximity/irsd200.c @@ -862,8 +862,7 @@ static int irsd200_probe(struct i2c_client *client) indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) - return dev_err_probe(&client->dev, -ENOMEM, - "Could not allocate iio device\n"); + return -ENOMEM; data = iio_priv(indio_dev); data->dev = &client->dev; @@ -916,8 +915,7 @@ static int irsd200_probe(struct i2c_client *client) trigger = devm_iio_trigger_alloc(data->dev, "%s-dev%d", indio_dev->name, iio_device_id(indio_dev)); if (!trigger) - return dev_err_probe(data->dev, -ENOMEM, - "Could not allocate iio trigger\n"); + return -ENOMEM; trigger->ops = &irsd200_trigger_ops; iio_trigger_set_drvdata(trigger, data); diff --git a/drivers/iio/proximity/mb1232.c b/drivers/iio/proximity/mb1232.c index 01783486bc7d..34b49c54e68b 100644 --- a/drivers/iio/proximity/mb1232.c +++ b/drivers/iio/proximity/mb1232.c @@ -42,11 +42,6 @@ struct mb1232_data { */ struct completion ranging; int irqnr; - /* Ensure correct alignment of data to push to IIO buffer */ - struct { - s16 distance; - aligned_s64 ts; - } scan; }; static irqreturn_t mb1232_handle_irq(int irq, void *dev_id) @@ -120,12 +115,16 @@ static irqreturn_t mb1232_trigger_handler(int irq, void *p) struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct mb1232_data *data = iio_priv(indio_dev); + struct { + s16 distance; + aligned_s64 ts; + } scan = { }; - data->scan.distance = mb1232_read_distance(data); - if (data->scan.distance < 0) + scan.distance = mb1232_read_distance(data); + if (scan.distance < 0) goto err; - iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); err: diff --git a/drivers/iio/proximity/ping.c b/drivers/iio/proximity/ping.c index c5b4e1378b7d..e3487094d7be 100644 --- a/drivers/iio/proximity/ping.c +++ b/drivers/iio/proximity/ping.c @@ -280,10 +280,8 @@ static int ping_probe(struct platform_device *pdev) struct iio_dev *indio_dev; indio_dev = devm_iio_device_alloc(dev, sizeof(struct ping_data)); - if (!indio_dev) { - dev_err(dev, "failed to allocate IIO device\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->dev = dev; diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c index 1deaf70e92ce..21336b8f122a 100644 --- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -43,12 +43,6 @@ struct lidar_data { int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len); int i2c_enabled; - - /* Ensure timestamp is naturally aligned */ - struct { - u16 chan; - aligned_s64 timestamp; - } scan; }; static const struct iio_chan_spec lidar_channels[] = { @@ -191,7 +185,6 @@ static int lidar_get_measurement(struct lidar_data *data, u16 *reg) } ret = -EIO; } - pm_runtime_mark_last_busy(&client->dev); pm_runtime_put_autosuspend(&client->dev); return ret; @@ -235,11 +228,14 @@ static irqreturn_t lidar_trigger_handler(int irq, void *private) struct iio_dev *indio_dev = pf->indio_dev; struct lidar_data *data = iio_priv(indio_dev); int ret; + struct { + u16 chan; + aligned_s64 timestamp; + } scan = { }; - ret = lidar_get_measurement(data, &data->scan.chan); + ret = lidar_get_measurement(data, &scan.chan); if (!ret) { - iio_push_to_buffers_with_ts(indio_dev, &data->scan, - sizeof(data->scan), + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), iio_get_time_ns(indio_dev)); } else if (ret != -EINVAL) { dev_err(&data->client->dev, "cannot read LIDAR measurement"); diff --git a/drivers/iio/proximity/srf04.c b/drivers/iio/proximity/srf04.c index b059bac1078b..e97f9a20ac7a 100644 --- a/drivers/iio/proximity/srf04.c +++ b/drivers/iio/proximity/srf04.c @@ -117,10 +117,8 @@ static int srf04_read(struct srf04_data *data) udelay(data->cfg->trigger_pulse_us); gpiod_set_value(data->gpiod_trig, 0); - if (data->gpiod_power) { - pm_runtime_mark_last_busy(data->dev); + if (data->gpiod_power) pm_runtime_put_autosuspend(data->dev); - } /* it should not take more than 20 ms until echo is rising */ ret = wait_for_completion_killable_timeout(&data->rising, HZ/50); @@ -253,10 +251,8 @@ static int srf04_probe(struct platform_device *pdev) int ret; indio_dev = devm_iio_device_alloc(dev, sizeof(struct srf04_data)); - if (!indio_dev) { - dev_err(dev, "failed to allocate IIO device\n"); + if (!indio_dev) return -ENOMEM; - } data = iio_priv(indio_dev); data->dev = dev; diff --git a/drivers/iio/proximity/srf08.c b/drivers/iio/proximity/srf08.c index 6e32fdfd161b..d7e4cc48cfbf 100644 --- a/drivers/iio/proximity/srf08.c +++ b/drivers/iio/proximity/srf08.c @@ -63,12 +63,6 @@ struct srf08_data { int range_mm; struct mutex lock; - /* Ensure timestamp is naturally aligned */ - struct { - s16 chan; - aligned_s64 timestamp; - } scan; - /* Sensor-Type */ enum srf08_sensor_type sensor_type; @@ -182,16 +176,18 @@ static irqreturn_t srf08_trigger_handler(int irq, void *p) struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct srf08_data *data = iio_priv(indio_dev); - s16 sensor_data; + struct { + s16 chan; + aligned_s64 timestamp; + } scan = { }; - sensor_data = srf08_read_ranging(data); - if (sensor_data < 0) + scan.chan = srf08_read_ranging(data); + if (scan.chan < 0) goto err; mutex_lock(&data->lock); - data->scan.chan = sensor_data; - iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp); mutex_unlock(&data->lock); diff --git a/drivers/iio/proximity/sx9500.c b/drivers/iio/proximity/sx9500.c index 05844f17a15f..6c67bae7488c 100644 --- a/drivers/iio/proximity/sx9500.c +++ b/drivers/iio/proximity/sx9500.c @@ -88,7 +88,6 @@ struct sx9500_data { bool prox_stat[SX9500_NUM_CHANNELS]; bool event_enabled[SX9500_NUM_CHANNELS]; bool trigger_enabled; - u16 *buffer; /* Remember enabled channels and sample rate during suspend. */ unsigned int suspend_ctrl0; struct completion completion; @@ -578,22 +577,6 @@ out_unlock: return ret; } -static int sx9500_update_scan_mode(struct iio_dev *indio_dev, - const unsigned long *scan_mask) -{ - struct sx9500_data *data = iio_priv(indio_dev); - - mutex_lock(&data->mutex); - kfree(data->buffer); - data->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); - mutex_unlock(&data->mutex); - - if (data->buffer == NULL) - return -ENOMEM; - - return 0; -} - static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( "2.500000 3.333333 5 6.666666 8.333333 11.111111 16.666666 33.333333"); @@ -612,7 +595,6 @@ static const struct iio_info sx9500_info = { .write_raw = &sx9500_write_raw, .read_event_config = &sx9500_read_event_config, .write_event_config = &sx9500_write_event_config, - .update_scan_mode = &sx9500_update_scan_mode, }; static int sx9500_set_trigger_state(struct iio_trigger *trig, @@ -649,6 +631,10 @@ static irqreturn_t sx9500_trigger_handler(int irq, void *private) struct iio_dev *indio_dev = pf->indio_dev; struct sx9500_data *data = iio_priv(indio_dev); int val, bit, ret, i = 0; + struct { + u16 chan[SX9500_NUM_CHANNELS]; + aligned_s64 timestamp; + } scan = { }; mutex_lock(&data->mutex); @@ -658,10 +644,10 @@ static irqreturn_t sx9500_trigger_handler(int irq, void *private) if (ret < 0) goto out; - data->buffer[i++] = val; + scan.chan[i++] = val; } - iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev)); out: @@ -984,7 +970,6 @@ static void sx9500_remove(struct i2c_client *client) iio_triggered_buffer_cleanup(indio_dev); if (client->irq > 0) iio_trigger_unregister(data->trig); - kfree(data->buffer); } static int sx9500_suspend(struct device *dev) diff --git a/drivers/iio/proximity/vl53l0x-i2c.c b/drivers/iio/proximity/vl53l0x-i2c.c index ef4aa7b2835e..ad3e46d47fa8 100644 --- a/drivers/iio/proximity/vl53l0x-i2c.c +++ b/drivers/iio/proximity/vl53l0x-i2c.c @@ -57,11 +57,6 @@ struct vl53l0x_data { struct regulator *vdd_supply; struct gpio_desc *reset_gpio; struct iio_trigger *trig; - - struct { - u16 chan; - aligned_s64 timestamp; - } scan; }; static int vl53l0x_clear_irq(struct vl53l0x_data *data) @@ -84,6 +79,10 @@ static irqreturn_t vl53l0x_trigger_handler(int irq, void *priv) struct vl53l0x_data *data = iio_priv(indio_dev); u8 buffer[12]; int ret; + struct { + u16 chan; + aligned_s64 timestamp; + } scan = { }; ret = i2c_smbus_read_i2c_block_data(data->client, VL_REG_RESULT_RANGE_STATUS, @@ -93,8 +92,8 @@ static irqreturn_t vl53l0x_trigger_handler(int irq, void *priv) else if (ret != 12) return -EREMOTEIO; - data->scan.chan = get_unaligned_be16(&buffer[10]); - iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + scan.chan = get_unaligned_be16(&buffer[10]); + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), iio_get_time_ns(indio_dev)); iio_trigger_notify_done(indio_dev->trig); @@ -312,7 +311,6 @@ static int vl53l0x_probe(struct i2c_client *client) { struct vl53l0x_data *data; struct iio_dev *indio_dev; - int error; int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); @@ -345,15 +343,14 @@ static int vl53l0x_probe(struct i2c_client *client) return dev_err_probe(&client->dev, PTR_ERR(data->reset_gpio), "Cannot get reset GPIO\n"); - error = vl53l0x_power_on(data); - if (error) - return dev_err_probe(&client->dev, error, + ret = vl53l0x_power_on(data); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to power on the chip\n"); - error = devm_add_action_or_reset(&client->dev, vl53l0x_power_off, data); - if (error) - return dev_err_probe(&client->dev, error, - "Failed to install poweroff action\n"); + ret = devm_add_action_or_reset(&client->dev, vl53l0x_power_off, data); + if (ret) + return ret; indio_dev->name = "vl53l0x"; indio_dev->info = &vl53l0x_info; diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig index 1244d8e17d50..9328b2250ace 100644 --- a/drivers/iio/temperature/Kconfig +++ b/drivers/iio/temperature/Kconfig @@ -173,11 +173,13 @@ config MAX31865 will be called max31865. config MCP9600 - tristate "MCP9600 thermocouple EMF converter" + tristate "MCP9600 and similar thermocouple EMF converters" depends on I2C help - If you say yes here you get support for MCP9600 - thermocouple EMF converter connected via I2C. + If you say yes here you get support for... + - MCP9600 + - MCP9601 + ...thermocouple EMF converters connected via I2C. This driver can also be built as a module. If so, the module will be called mcp9600. diff --git a/drivers/iio/temperature/mcp9600.c b/drivers/iio/temperature/mcp9600.c index 6e9108d5cf75..aa42c2b1a369 100644 --- a/drivers/iio/temperature/mcp9600.c +++ b/drivers/iio/temperature/mcp9600.c @@ -22,26 +22,31 @@ #include <linux/iio/events.h> #include <linux/iio/iio.h> +#include <dt-bindings/iio/temperature/thermocouple.h> + /* MCP9600 registers */ -#define MCP9600_HOT_JUNCTION 0x0 -#define MCP9600_COLD_JUNCTION 0x2 -#define MCP9600_STATUS 0x4 +#define MCP9600_HOT_JUNCTION 0x00 +#define MCP9600_COLD_JUNCTION 0x02 +#define MCP9600_STATUS 0x04 #define MCP9600_STATUS_ALERT(x) BIT(x) -#define MCP9600_ALERT_CFG1 0x8 +#define MCP9600_SENSOR_CFG 0x05 +#define MCP9600_SENSOR_TYPE_MASK GENMASK(6, 4) +#define MCP9600_ALERT_CFG1 0x08 #define MCP9600_ALERT_CFG(x) (MCP9600_ALERT_CFG1 + (x - 1)) #define MCP9600_ALERT_CFG_ENABLE BIT(0) #define MCP9600_ALERT_CFG_ACTIVE_HIGH BIT(2) #define MCP9600_ALERT_CFG_FALLING BIT(3) #define MCP9600_ALERT_CFG_COLD_JUNCTION BIT(4) -#define MCP9600_ALERT_HYSTERESIS1 0xc +#define MCP9600_ALERT_HYSTERESIS1 0x0c #define MCP9600_ALERT_HYSTERESIS(x) (MCP9600_ALERT_HYSTERESIS1 + (x - 1)) #define MCP9600_ALERT_LIMIT1 0x10 #define MCP9600_ALERT_LIMIT(x) (MCP9600_ALERT_LIMIT1 + (x - 1)) #define MCP9600_ALERT_LIMIT_MASK GENMASK(15, 2) -#define MCP9600_DEVICE_ID 0x20 +#define MCP9600_DEVICE_ID 0x20 /* MCP9600 device id value */ -#define MCP9600_DEVICE_ID_MCP9600 0x40 +#define MCP9600_DEVICE_ID_MCP9600 0x40 +#define MCP9600_DEVICE_ID_MCP9601 0x41 #define MCP9600_ALERT_COUNT 4 @@ -65,6 +70,30 @@ static const char * const mcp9600_alert_name[MCP9600_ALERT_COUNT] = { [MCP9600_ALERT4] = "alert4", }; +/* Map between dt-bindings enum and the chip's type value */ +static const unsigned int mcp9600_type_map[] = { + [THERMOCOUPLE_TYPE_K] = 0, + [THERMOCOUPLE_TYPE_J] = 1, + [THERMOCOUPLE_TYPE_T] = 2, + [THERMOCOUPLE_TYPE_N] = 3, + [THERMOCOUPLE_TYPE_S] = 4, + [THERMOCOUPLE_TYPE_E] = 5, + [THERMOCOUPLE_TYPE_B] = 6, + [THERMOCOUPLE_TYPE_R] = 7, +}; + +/* Map thermocouple type to a char for iio info in sysfs */ +static const int mcp9600_tc_types[] = { + [THERMOCOUPLE_TYPE_K] = 'K', + [THERMOCOUPLE_TYPE_J] = 'J', + [THERMOCOUPLE_TYPE_T] = 'T', + [THERMOCOUPLE_TYPE_N] = 'N', + [THERMOCOUPLE_TYPE_S] = 'S', + [THERMOCOUPLE_TYPE_E] = 'E', + [THERMOCOUPLE_TYPE_B] = 'B', + [THERMOCOUPLE_TYPE_R] = 'R', +}; + static const struct iio_event_spec mcp9600_events[] = { { .type = IIO_EV_TYPE_THRESH, @@ -82,12 +111,41 @@ static const struct iio_event_spec mcp9600_events[] = { }, }; +struct mcp_chip_info { + u8 chip_id; + const char *chip_name; +}; + +struct mcp9600_data { + struct i2c_client *client; + u32 thermocouple_type; +}; + +static int mcp9600_config(struct mcp9600_data *data) +{ + struct i2c_client *client = data->client; + int ret; + u8 cfg; + + cfg = FIELD_PREP(MCP9600_SENSOR_TYPE_MASK, + mcp9600_type_map[data->thermocouple_type]); + + ret = i2c_smbus_write_byte_data(client, MCP9600_SENSOR_CFG, cfg); + if (ret < 0) { + dev_err(&client->dev, "Failed to set sensor configuration\n"); + return ret; + } + + return 0; +} + #define MCP9600_CHANNELS(hj_num_ev, hj_ev_spec_off, cj_num_ev, cj_ev_spec_off) \ { \ { \ .type = IIO_TEMP, \ .address = MCP9600_HOT_JUNCTION, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_THERMOCOUPLE_TYPE) | \ BIT(IIO_CHAN_INFO_SCALE), \ .event_spec = &mcp9600_events[hj_ev_spec_off], \ .num_event_specs = hj_num_ev, \ @@ -123,10 +181,6 @@ static const struct iio_chan_spec mcp9600_channels[][2] = { MCP9600_CHANNELS(2, 0, 2, 0), /* Alerts: 1 2 3 4 */ }; -struct mcp9600_data { - struct i2c_client *client; -}; - static int mcp9600_read(struct mcp9600_data *data, struct iio_chan_spec const *chan, int *val) { @@ -159,6 +213,9 @@ static int mcp9600_read_raw(struct iio_dev *indio_dev, *val = 62; *val2 = 500000; return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_THERMOCOUPLE_TYPE: + *val = mcp9600_tc_types[data->thermocouple_type]; + return IIO_VAL_CHAR; default: return -EINVAL; } @@ -416,45 +473,93 @@ static int mcp9600_probe_alerts(struct iio_dev *indio_dev) static int mcp9600_probe(struct i2c_client *client) { + struct device *dev = &client->dev; + const struct mcp_chip_info *chip_info; struct iio_dev *indio_dev; struct mcp9600_data *data; - int ret, ch_sel; + int ch_sel, dev_id, ret; + + chip_info = i2c_get_match_data(client); + if (!chip_info) + return dev_err_probe(dev, -ENODEV, + "No chip-info found for device\n"); + + dev_id = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID); + if (dev_id < 0) + return dev_err_probe(dev, dev_id, "Failed to read device ID\n"); + + switch (dev_id) { + case MCP9600_DEVICE_ID_MCP9600: + case MCP9600_DEVICE_ID_MCP9601: + if (dev_id != chip_info->chip_id) + dev_warn(dev, + "Expected id %02x, but device responded with %02x\n", + chip_info->chip_id, dev_id); + break; - ret = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID); - if (ret < 0) - return dev_err_probe(&client->dev, ret, "Failed to read device ID\n"); - if (ret != MCP9600_DEVICE_ID_MCP9600) - dev_warn(&client->dev, "Expected ID %x, got %x\n", - MCP9600_DEVICE_ID_MCP9600, ret); + default: + dev_warn(dev, "Unknown id %x, using %x\n", dev_id, + chip_info->chip_id); + } - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev); data->client = client; + /* Accept type from dt with default of Type-K. */ + data->thermocouple_type = THERMOCOUPLE_TYPE_K; + ret = device_property_read_u32(dev, "thermocouple-type", + &data->thermocouple_type); + if (ret && ret != -EINVAL) + return dev_err_probe(dev, ret, + "Error reading thermocouple-type property\n"); + + if (data->thermocouple_type >= ARRAY_SIZE(mcp9600_type_map)) + return dev_err_probe(dev, -EINVAL, + "Invalid thermocouple-type property %u.\n", + data->thermocouple_type); + + /* Set initial config. */ + ret = mcp9600_config(data); + if (ret) + return ret; + ch_sel = mcp9600_probe_alerts(indio_dev); if (ch_sel < 0) return ch_sel; indio_dev->info = &mcp9600_info; - indio_dev->name = "mcp9600"; + indio_dev->name = chip_info->chip_name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = mcp9600_channels[ch_sel]; indio_dev->num_channels = ARRAY_SIZE(mcp9600_channels[ch_sel]); - return devm_iio_device_register(&client->dev, indio_dev); + return devm_iio_device_register(dev, indio_dev); } +static const struct mcp_chip_info mcp9600_chip_info = { + .chip_id = MCP9600_DEVICE_ID_MCP9600, + .chip_name = "mcp9600", +}; + +static const struct mcp_chip_info mcp9601_chip_info = { + .chip_id = MCP9600_DEVICE_ID_MCP9601, + .chip_name = "mcp9601", +}; + static const struct i2c_device_id mcp9600_id[] = { - { "mcp9600" }, + { "mcp9600", .driver_data = (kernel_ulong_t)&mcp9600_chip_info }, + { "mcp9601", .driver_data = (kernel_ulong_t)&mcp9601_chip_info }, { } }; MODULE_DEVICE_TABLE(i2c, mcp9600_id); static const struct of_device_id mcp9600_of_match[] = { - { .compatible = "microchip,mcp9600" }, + { .compatible = "microchip,mcp9600", .data = &mcp9600_chip_info }, + { .compatible = "microchip,mcp9601", .data = &mcp9601_chip_info }, { } }; MODULE_DEVICE_TABLE(of, mcp9600_of_match); diff --git a/drivers/iio/temperature/mlx90614.c b/drivers/iio/temperature/mlx90614.c index 740018d4b3df..8a44a00bfd5e 100644 --- a/drivers/iio/temperature/mlx90614.c +++ b/drivers/iio/temperature/mlx90614.c @@ -225,7 +225,6 @@ static void mlx90614_power_put(struct mlx90614_data *data) if (!data->wakeup_gpio) return; - pm_runtime_mark_last_busy(&data->client->dev); pm_runtime_put_autosuspend(&data->client->dev); } #else diff --git a/drivers/iio/temperature/mlx90632.c b/drivers/iio/temperature/mlx90632.c index ae4ea587e7f9..b44f7036c2cc 100644 --- a/drivers/iio/temperature/mlx90632.c +++ b/drivers/iio/temperature/mlx90632.c @@ -1043,7 +1043,6 @@ static int mlx90632_read_raw(struct iio_dev *indio_dev, } mlx90632_read_raw_pm: - pm_runtime_mark_last_busy(&data->client->dev); pm_runtime_put_autosuspend(&data->client->dev); return ret; } @@ -1178,10 +1177,8 @@ static int mlx90632_probe(struct i2c_client *client) int ret; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90632)); - if (!indio_dev) { - dev_err(&client->dev, "Failed to allocate device\n"); + if (!indio_dev) return -ENOMEM; - } regmap = devm_regmap_init_i2c(client, &mlx90632_regmap); if (IS_ERR(regmap)) { diff --git a/drivers/iio/temperature/mlx90635.c b/drivers/iio/temperature/mlx90635.c index f7f88498ba0e..1c8948ca54df 100644 --- a/drivers/iio/temperature/mlx90635.c +++ b/drivers/iio/temperature/mlx90635.c @@ -749,7 +749,6 @@ static int mlx90635_read_raw(struct iio_dev *indio_dev, } mlx90635_read_raw_pm: - pm_runtime_mark_last_busy(&data->client->dev); pm_runtime_put_autosuspend(&data->client->dev); return ret; } @@ -939,7 +938,7 @@ static int mlx90635_probe(struct i2c_client *client) indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90635)); if (!indio_dev) - return dev_err_probe(&client->dev, -ENOMEM, "failed to allocate device\n"); + return -ENOMEM; regmap = devm_regmap_init_i2c(client, &mlx90635_regmap); if (IS_ERR(regmap)) @@ -977,8 +976,7 @@ static int mlx90635_probe(struct i2c_client *client) ret = devm_add_action_or_reset(&client->dev, mlx90635_disable_regulator, mlx90635); if (ret < 0) - return dev_err_probe(&client->dev, ret, - "failed to setup regulator cleanup action\n"); + return ret; ret = mlx90635_wakeup(mlx90635); if (ret < 0) @@ -986,8 +984,7 @@ static int mlx90635_probe(struct i2c_client *client) ret = devm_add_action_or_reset(&client->dev, mlx90635_sleep, mlx90635); if (ret < 0) - return dev_err_probe(&client->dev, ret, - "failed to setup low power cleanup\n"); + return ret; ret = regmap_read(mlx90635->regmap_ee, MLX90635_EE_VERSION, &dsp_version); if (ret < 0) diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig index 7a181cac3cc9..6e65e929791c 100644 --- a/drivers/iio/test/Kconfig +++ b/drivers/iio/test/Kconfig @@ -41,3 +41,15 @@ config IIO_FORMAT_KUNIT_TEST to the KUnit documentation in Documentation/dev-tools/kunit/. If unsure, say N. + +config IIO_MULTIPLY_KUNIT_TEST + tristate "Test IIO multiply functions" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + help + build unit tests for the IIO multiply functions. + + For more information on KUnit and unit tests in general, please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile index e9a4cf1ff57f..0c846bc21acd 100644 --- a/drivers/iio/test/Makefile +++ b/drivers/iio/test/Makefile @@ -7,4 +7,5 @@ obj-$(CONFIG_IIO_RESCALE_KUNIT_TEST) += iio-test-rescale.o obj-$(CONFIG_IIO_FORMAT_KUNIT_TEST) += iio-test-format.o obj-$(CONFIG_IIO_GTS_KUNIT_TEST) += iio-test-gts.o +obj-$(CONFIG_IIO_MULTIPLY_KUNIT_TEST) += iio-test-multiply.o CFLAGS_iio-test-format.o += $(DISABLE_STRUCTLEAK_PLUGIN) diff --git a/drivers/iio/test/iio-test-multiply.c b/drivers/iio/test/iio-test-multiply.c new file mode 100644 index 000000000000..432e279ffe5b --- /dev/null +++ b/drivers/iio/test/iio-test-multiply.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Unit tests for IIO multiply functions + * + * Copyright (c) 2025 Hans de Goede <hans@hansg.org> + * Based on iio-test-format.c which is: + * Copyright (c) 2020 Lars-Peter Clausen <lars@metafoo.de> + */ + +#include <kunit/test.h> +#include <linux/iio/consumer.h> +#include <linux/math64.h> +#include <linux/types.h> + +static void __iio_test_iio_multiply_value_integer(struct kunit *test, s64 multiplier) +{ + int ret, result, val; + + val = 42; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, multiplier * val); + + val = -23; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, multiplier * val); + + val = 0; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT, val, 0); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, multiplier * val); +} + +static void iio_test_iio_multiply_value_integer(struct kunit *test) +{ + __iio_test_iio_multiply_value_integer(test, 20); + __iio_test_iio_multiply_value_integer(test, -20); +} + +static void __iio_test_iio_multiply_value_fixedpoint(struct kunit *test, s64 multiplier) +{ + int ret, result, val, val2; + + /* positive >= 1 (1.5) */ + val = 1; + val2 = 500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 15, 10)); + + val = 1; + val2 = 500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 15, 10)); + + /* positive < 1 (0.5) */ + val = 0; + val2 = 500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 5, 10)); + + val = 0; + val2 = 500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * 5, 10)); + + /* negative <= -1 (-1.5) */ + val = -1; + val2 = 500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -15, 10)); + + val = -1; + val2 = 500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -15, 10)); + + /* negative > -1 (-0.5) */ + val = 0; + val2 = -500000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_MICRO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -5, 10)); + + val = 0; + val2 = -500000000; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_INT_PLUS_NANO, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * -5, 10)); +} + +static void iio_test_iio_multiply_value_fixedpoint(struct kunit *test) +{ + __iio_test_iio_multiply_value_fixedpoint(test, 20); + __iio_test_iio_multiply_value_fixedpoint(test, -20); +} + +static void __iio_test_iio_multiply_value_fractional(struct kunit *test, s64 multiplier) +{ + int ret, result, val, val2; + + /* positive < 1 (1/10)*/ + val = 1; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* positive >= 1 (100/3)*/ + val = 100; + val2 = 3; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* negative > -1 (-1/10) */ + val = -1; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* negative <= -1 (-200/3)*/ + val = -200; + val2 = 3; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); + + /* Zero (0/-10) */ + val = 0; + val2 = -10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, div_s64(multiplier * val, val2)); +} + +static void iio_test_iio_multiply_value_fractional(struct kunit *test) +{ + __iio_test_iio_multiply_value_fractional(test, 20); + __iio_test_iio_multiply_value_fractional(test, -20); +} + +static void __iio_test_iio_multiply_value_fractional_log2(struct kunit *test, s64 multiplier) +{ + int ret, result, val, val2; + + /* positive < 1 (123/1024) */ + val = 123; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* positive >= 1 (1234567/1024) */ + val = 1234567; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* negative > -1 (-123/1024) */ + val = -123; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* negative <= -1 (-1234567/1024) */ + val = -1234567; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); + + /* Zero (0/1024) */ + val = 0; + val2 = 10; + ret = iio_multiply_value(&result, multiplier, IIO_VAL_FRACTIONAL_LOG2, val, val2); + KUNIT_EXPECT_EQ(test, ret, IIO_VAL_INT); + KUNIT_EXPECT_EQ(test, result, (multiplier * val) >> val2); +} + +static void iio_test_iio_multiply_value_fractional_log2(struct kunit *test) +{ + __iio_test_iio_multiply_value_fractional_log2(test, 20); + __iio_test_iio_multiply_value_fractional_log2(test, -20); +} + +static struct kunit_case iio_multiply_test_cases[] = { + KUNIT_CASE(iio_test_iio_multiply_value_integer), + KUNIT_CASE(iio_test_iio_multiply_value_fixedpoint), + KUNIT_CASE(iio_test_iio_multiply_value_fractional), + KUNIT_CASE(iio_test_iio_multiply_value_fractional_log2), + { } +}; + +static struct kunit_suite iio_multiply_test_suite = { + .name = "iio-multiply", + .test_cases = iio_multiply_test_cases, +}; +kunit_test_suite(iio_multiply_test_suite); + +MODULE_AUTHOR("Hans de Goede <hans@hansg.org>"); +MODULE_DESCRIPTION("Test IIO multiply functions"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_UNIT_TEST"); diff --git a/drivers/interconnect/core.c b/drivers/interconnect/core.c index 3ebf37ddfc18..6cc979b26151 100644 --- a/drivers/interconnect/core.c +++ b/drivers/interconnect/core.c @@ -385,7 +385,7 @@ struct icc_node_data *of_icc_get_from_provider(const struct of_phandle_args *spe mutex_lock(&icc_lock); list_for_each_entry(provider, &icc_providers, provider_list) { - if (provider->dev->of_node == spec->np) { + if (device_match_of_node(provider->dev, spec->np)) { if (provider->xlate_extended) { data = provider->xlate_extended(spec, provider->data); if (!IS_ERR(data)) { diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig index 31dc4781abef..5b4bb9f1382b 100644 --- a/drivers/interconnect/qcom/Kconfig +++ b/drivers/interconnect/qcom/Kconfig @@ -8,6 +8,15 @@ config INTERCONNECT_QCOM config INTERCONNECT_QCOM_BCM_VOTER tristate +config INTERCONNECT_QCOM_GLYMUR + tristate "Qualcomm GLYMUR interconnect driver" + depends on INTERCONNECT_QCOM_RPMH_POSSIBLE + select INTERCONNECT_QCOM_RPMH + select INTERCONNECT_QCOM_BCM_VOTER + help + This is a driver for the Qualcomm Network-on-Chip on glymur-based + platforms. + config INTERCONNECT_QCOM_MSM8909 tristate "Qualcomm MSM8909 interconnect driver" depends on INTERCONNECT_QCOM diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile index f16ac242eba5..cf8cba73ee3e 100644 --- a/drivers/interconnect/qcom/Makefile +++ b/drivers/interconnect/qcom/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_INTERCONNECT_QCOM) += interconnect_qcom.o interconnect_qcom-y := icc-common.o icc-bcm-voter-objs := bcm-voter.o +qnoc-glymur-objs := glymur.o qnoc-milos-objs := milos.o qnoc-msm8909-objs := msm8909.o qnoc-msm8916-objs := msm8916.o @@ -46,6 +47,7 @@ qnoc-x1e80100-objs := x1e80100.o icc-smd-rpm-objs := smd-rpm.o icc-rpm.o icc-rpm-clocks.o obj-$(CONFIG_INTERCONNECT_QCOM_BCM_VOTER) += icc-bcm-voter.o +obj-$(CONFIG_INTERCONNECT_QCOM_GLYMUR) += qnoc-glymur.o obj-$(CONFIG_INTERCONNECT_QCOM_MILOS) += qnoc-milos.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8909) += qnoc-msm8909.o obj-$(CONFIG_INTERCONNECT_QCOM_MSM8916) += qnoc-msm8916.o diff --git a/drivers/interconnect/qcom/glymur.c b/drivers/interconnect/qcom/glymur.c new file mode 100644 index 000000000000..cf20b5752dbb --- /dev/null +++ b/drivers/interconnect/qcom/glymur.c @@ -0,0 +1,2543 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. + * + */ + +#include <linux/device.h> +#include <linux/interconnect.h> +#include <linux/interconnect-provider.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <dt-bindings/interconnect/qcom,glymur-rpmh.h> + +#include "bcm-voter.h" +#include "icc-rpmh.h" + +static struct qcom_icc_node qup0_core_slave = { + .name = "qup0_core_slave", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qup1_core_slave = { + .name = "qup1_core_slave", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qup2_core_slave = { + .name = "qup2_core_slave", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ahb2phy0 = { + .name = "qhs_ahb2phy0", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ahb2phy1 = { + .name = "qhs_ahb2phy1", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ahb2phy2 = { + .name = "qhs_ahb2phy2", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ahb2phy3 = { + .name = "qhs_ahb2phy3", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_av1_enc_cfg = { + .name = "qhs_av1_enc_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_camera_cfg = { + .name = "qhs_camera_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_clk_ctl = { + .name = "qhs_clk_ctl", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_crypto0_cfg = { + .name = "qhs_crypto0_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_display_cfg = { + .name = "qhs_display_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_gpuss_cfg = { + .name = "qhs_gpuss_cfg", + .channels = 1, + .buswidth = 8, +}; + +static struct qcom_icc_node qhs_imem_cfg = { + .name = "qhs_imem_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie0_cfg = { + .name = "qhs_pcie0_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie1_cfg = { + .name = "qhs_pcie1_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie2_cfg = { + .name = "qhs_pcie2_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie3a_cfg = { + .name = "qhs_pcie3a_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie3b_cfg = { + .name = "qhs_pcie3b_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie4_cfg = { + .name = "qhs_pcie4_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie5_cfg = { + .name = "qhs_pcie5_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie6_cfg = { + .name = "qhs_pcie6_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pcie_rscc = { + .name = "qhs_pcie_rscc", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_pdm = { + .name = "qhs_pdm", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_prng = { + .name = "qhs_prng", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_qdss_cfg = { + .name = "qhs_qdss_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_qspi = { + .name = "qhs_qspi", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_qup0 = { + .name = "qhs_qup0", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_qup1 = { + .name = "qhs_qup1", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_qup2 = { + .name = "qhs_qup2", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_sdc2 = { + .name = "qhs_sdc2", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_sdc4 = { + .name = "qhs_sdc4", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_smmuv3_cfg = { + .name = "qhs_smmuv3_cfg", + .channels = 1, + .buswidth = 8, +}; + +static struct qcom_icc_node qhs_tcsr = { + .name = "qhs_tcsr", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_tlmm = { + .name = "qhs_tlmm", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ufs_mem_cfg = { + .name = "qhs_ufs_mem_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb2_0_cfg = { + .name = "qhs_usb2_0_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb3_0_cfg = { + .name = "qhs_usb3_0_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb3_1_cfg = { + .name = "qhs_usb3_1_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb3_2_cfg = { + .name = "qhs_usb3_2_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb3_mp_cfg = { + .name = "qhs_usb3_mp_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb4_0_cfg = { + .name = "qhs_usb4_0_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb4_1_cfg = { + .name = "qhs_usb4_1_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_usb4_2_cfg = { + .name = "qhs_usb4_2_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_venus_cfg = { + .name = "qhs_venus_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qss_lpass_qtb_cfg = { + .name = "qss_lpass_qtb_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qss_nsp_qtb_cfg = { + .name = "qss_nsp_qtb_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node xs_qdss_stm = { + .name = "xs_qdss_stm", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node xs_sys_tcu_cfg = { + .name = "xs_sys_tcu_cfg", + .channels = 1, + .buswidth = 8, +}; + +static struct qcom_icc_node qhs_aoss = { + .name = "qhs_aoss", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_ipc_router = { + .name = "qhs_ipc_router", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_soccp = { + .name = "qhs_soccp", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_tme_cfg = { + .name = "qhs_tme_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qns_apss = { + .name = "qns_apss", + .channels = 1, + .buswidth = 8, +}; + +static struct qcom_icc_node qxs_boot_imem = { + .name = "qxs_boot_imem", + .channels = 1, + .buswidth = 16, +}; + +static struct qcom_icc_node qxs_imem = { + .name = "qxs_imem", + .channels = 1, + .buswidth = 8, +}; + +static struct qcom_icc_node ebi = { + .name = "ebi", + .channels = 12, + .buswidth = 4, +}; + +static struct qcom_icc_node srvc_mnoc = { + .name = "srvc_mnoc", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node srvc_nsinoc = { + .name = "srvc_nsinoc", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node srvc_pcie_east_aggre_noc = { + .name = "srvc_pcie_east_aggre_noc", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_hscnoc_pcie_east_ms_mpu_cfg = { + .name = "qhs_hscnoc_pcie_east_ms_mpu_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node srvc_pcie_east = { + .name = "srvc_pcie_east", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node xs_pcie_0 = { + .name = "xs_pcie_0", + .channels = 1, + .buswidth = 16, +}; + +static struct qcom_icc_node xs_pcie_1 = { + .name = "xs_pcie_1", + .channels = 1, + .buswidth = 32, +}; + +static struct qcom_icc_node xs_pcie_5 = { + .name = "xs_pcie_5", + .channels = 1, + .buswidth = 32, +}; + +static struct qcom_icc_node srvc_pcie_west_aggre_noc = { + .name = "srvc_pcie_west_aggre_noc", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node qhs_hscnoc_pcie_west_ms_mpu_cfg = { + .name = "qhs_hscnoc_pcie_west_ms_mpu_cfg", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node srvc_pcie_west = { + .name = "srvc_pcie_west", + .channels = 1, + .buswidth = 4, +}; + +static struct qcom_icc_node xs_pcie_2 = { + .name = "xs_pcie_2", + .channels = 1, + .buswidth = 16, +}; + +static struct qcom_icc_node xs_pcie_3a = { + .name = "xs_pcie_3a", + .channels = 1, + .buswidth = 64, +}; + +static struct qcom_icc_node xs_pcie_3b = { + .name = "xs_pcie_3b", + .channels = 1, + .buswidth = 32, +}; + +static struct qcom_icc_node xs_pcie_4 = { + .name = "xs_pcie_4", + .channels = 1, + .buswidth = 16, +}; + +static struct qcom_icc_node xs_pcie_6 = { + .name = "xs_pcie_6", + .channels = 1, + .buswidth = 16, +}; + +static struct qcom_icc_node qup0_core_master = { + .name = "qup0_core_master", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qup0_core_slave }, +}; + +static struct qcom_icc_node qup1_core_master = { + .name = "qup1_core_master", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qup1_core_slave }, +}; + +static struct qcom_icc_node qup2_core_master = { + .name = "qup2_core_master", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qup2_core_slave }, +}; + +static struct qcom_icc_node llcc_mc = { + .name = "llcc_mc", + .channels = 12, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &ebi }, +}; + +static struct qcom_icc_node qsm_mnoc_cfg = { + .name = "qsm_mnoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &srvc_mnoc }, +}; + +static struct qcom_icc_node qsm_pcie_east_anoc_cfg = { + .name = "qsm_pcie_east_anoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &srvc_pcie_east_aggre_noc }, +}; + +static struct qcom_icc_node qnm_hscnoc_pcie_east = { + .name = "qnm_hscnoc_pcie_east", + .channels = 1, + .buswidth = 32, + .num_links = 3, + .link_nodes = (struct qcom_icc_node *[]) { &xs_pcie_0, &xs_pcie_1, + &xs_pcie_5 }, +}; + +static struct qcom_icc_node qsm_cnoc_pcie_east_slave_cfg = { + .name = "qsm_cnoc_pcie_east_slave_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qhs_hscnoc_pcie_east_ms_mpu_cfg, + &srvc_pcie_east }, +}; + +static struct qcom_icc_node qsm_pcie_west_anoc_cfg = { + .name = "qsm_pcie_west_anoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &srvc_pcie_west_aggre_noc }, +}; + +static struct qcom_icc_node qnm_hscnoc_pcie_west = { + .name = "qnm_hscnoc_pcie_west", + .channels = 1, + .buswidth = 32, + .num_links = 5, + .link_nodes = (struct qcom_icc_node *[]) { &xs_pcie_2, &xs_pcie_3a, + &xs_pcie_3b, &xs_pcie_4, + &xs_pcie_6 }, +}; + +static struct qcom_icc_node qsm_cnoc_pcie_west_slave_cfg = { + .name = "qsm_cnoc_pcie_west_slave_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qhs_hscnoc_pcie_west_ms_mpu_cfg, + &srvc_pcie_west }, +}; + +static struct qcom_icc_node qss_cnoc_pcie_slave_east_cfg = { + .name = "qss_cnoc_pcie_slave_east_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_cnoc_pcie_east_slave_cfg }, +}; + +static struct qcom_icc_node qss_cnoc_pcie_slave_west_cfg = { + .name = "qss_cnoc_pcie_slave_west_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_cnoc_pcie_west_slave_cfg }, +}; + +static struct qcom_icc_node qss_mnoc_cfg = { + .name = "qss_mnoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_mnoc_cfg }, +}; + +static struct qcom_icc_node qss_pcie_east_anoc_cfg = { + .name = "qss_pcie_east_anoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_pcie_east_anoc_cfg }, +}; + +static struct qcom_icc_node qss_pcie_west_anoc_cfg = { + .name = "qss_pcie_west_anoc_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_pcie_west_anoc_cfg }, +}; + +static struct qcom_icc_node qns_llcc = { + .name = "qns_llcc", + .channels = 12, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &llcc_mc }, +}; + +static struct qcom_icc_node qns_pcie_east = { + .name = "qns_pcie_east", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_hscnoc_pcie_east }, +}; + +static struct qcom_icc_node qns_pcie_west = { + .name = "qns_pcie_west", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_hscnoc_pcie_west }, +}; + +static struct qcom_icc_node qsm_cfg = { + .name = "qsm_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 51, + .link_nodes = (struct qcom_icc_node *[]) { &qhs_ahb2phy0, &qhs_ahb2phy1, + &qhs_ahb2phy2, &qhs_ahb2phy3, + &qhs_av1_enc_cfg, &qhs_camera_cfg, + &qhs_clk_ctl, &qhs_crypto0_cfg, + &qhs_display_cfg, &qhs_gpuss_cfg, + &qhs_imem_cfg, &qhs_pcie0_cfg, + &qhs_pcie1_cfg, &qhs_pcie2_cfg, + &qhs_pcie3a_cfg, &qhs_pcie3b_cfg, + &qhs_pcie4_cfg, &qhs_pcie5_cfg, + &qhs_pcie6_cfg, &qhs_pcie_rscc, + &qhs_pdm, &qhs_prng, + &qhs_qdss_cfg, &qhs_qspi, + &qhs_qup0, &qhs_qup1, + &qhs_qup2, &qhs_sdc2, + &qhs_sdc4, &qhs_smmuv3_cfg, + &qhs_tcsr, &qhs_tlmm, + &qhs_ufs_mem_cfg, &qhs_usb2_0_cfg, + &qhs_usb3_0_cfg, &qhs_usb3_1_cfg, + &qhs_usb3_2_cfg, &qhs_usb3_mp_cfg, + &qhs_usb4_0_cfg, &qhs_usb4_1_cfg, + &qhs_usb4_2_cfg, &qhs_venus_cfg, + &qss_cnoc_pcie_slave_east_cfg, &qss_cnoc_pcie_slave_west_cfg, + &qss_lpass_qtb_cfg, &qss_mnoc_cfg, + &qss_nsp_qtb_cfg, &qss_pcie_east_anoc_cfg, + &qss_pcie_west_anoc_cfg, &xs_qdss_stm, + &xs_sys_tcu_cfg }, +}; + +static struct qcom_icc_node xm_gic = { + .name = "xm_gic", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x33000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_llcc }, +}; + +static struct qcom_icc_node qss_cfg = { + .name = "qss_cfg", + .channels = 1, + .buswidth = 4, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qsm_cfg }, +}; + +static struct qcom_icc_node qnm_hscnoc_cnoc = { + .name = "qnm_hscnoc_cnoc", + .channels = 1, + .buswidth = 16, + .num_links = 8, + .link_nodes = (struct qcom_icc_node *[]) { &qhs_aoss, &qhs_ipc_router, + &qhs_soccp, &qhs_tme_cfg, + &qns_apss, &qss_cfg, + &qxs_boot_imem, &qxs_imem }, +}; + +static struct qcom_icc_node qns_hscnoc_cnoc = { + .name = "qns_hscnoc_cnoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_hscnoc_cnoc }, +}; + +static struct qcom_icc_node alm_gpu_tcu = { + .name = "alm_gpu_tcu", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x933000 }, + .prio = 1, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc }, +}; + +static struct qcom_icc_node alm_pcie_qtc = { + .name = "alm_pcie_qtc", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x51f000 }, + .prio = 3, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc }, +}; + +static struct qcom_icc_node alm_sys_tcu = { + .name = "alm_sys_tcu", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x51f080 }, + .prio = 6, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc }, +}; + +static struct qcom_icc_node chm_apps = { + .name = "chm_apps", + .channels = 6, + .buswidth = 32, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_aggre_noc_east = { + .name = "qnm_aggre_noc_east", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x934000 }, + .prio = 2, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_gpu = { + .name = "qnm_gpu", + .channels = 4, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 4, + .port_offsets = { 0x935000, 0x936000, 0x937000, 0x938000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_lpass = { + .name = "qnm_lpass", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x939000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_mnoc_hf = { + .name = "qnm_mnoc_hf", + .channels = 2, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 2, + .port_offsets = { 0x721000, 0x721080 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_mnoc_sf = { + .name = "qnm_mnoc_sf", + .channels = 2, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 2, + .port_offsets = { 0x721100, 0x721180 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_nsp_noc = { + .name = "qnm_nsp_noc", + .channels = 4, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 4, + .port_offsets = { 0x816000, 0x816080, 0x816100, 0x816180 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qnm_pcie_east = { + .name = "qnm_pcie_east", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x93a000 }, + .prio = 2, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc }, +}; + +static struct qcom_icc_node qnm_pcie_west = { + .name = "qnm_pcie_west", + .channels = 1, + .buswidth = 64, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x721200 }, + .prio = 2, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc }, +}; + +static struct qcom_icc_node qnm_snoc_sf = { + .name = "qnm_snoc_sf", + .channels = 1, + .buswidth = 64, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x51f100 }, + .prio = 2, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qxm_wlan_q6 = { + .name = "qxm_wlan_q6", + .channels = 1, + .buswidth = 8, + .num_links = 4, + .link_nodes = (struct qcom_icc_node *[]) { &qns_hscnoc_cnoc, &qns_llcc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_node qns_a4noc_hscnoc = { + .name = "qns_a4noc_hscnoc", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre_noc_east }, +}; + +static struct qcom_icc_node qns_lpass_ag_noc_gemnoc = { + .name = "qns_lpass_ag_noc_gemnoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_lpass }, +}; + +static struct qcom_icc_node qns_mem_noc_hf = { + .name = "qns_mem_noc_hf", + .channels = 2, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_mnoc_hf }, +}; + +static struct qcom_icc_node qns_mem_noc_sf = { + .name = "qns_mem_noc_sf", + .channels = 2, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_mnoc_sf }, +}; + +static struct qcom_icc_node qns_nsp_hscnoc = { + .name = "qns_nsp_hscnoc", + .channels = 4, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_nsp_noc }, +}; + +static struct qcom_icc_node qns_pcie_east_mem_noc = { + .name = "qns_pcie_east_mem_noc", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_pcie_east }, +}; + +static struct qcom_icc_node qns_pcie_west_mem_noc = { + .name = "qns_pcie_west_mem_noc", + .channels = 1, + .buswidth = 64, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_pcie_west }, +}; + +static struct qcom_icc_node qns_gemnoc_sf = { + .name = "qns_gemnoc_sf", + .channels = 1, + .buswidth = 64, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_snoc_sf }, +}; + +static struct qcom_icc_node xm_usb3_0 = { + .name = "xm_usb3_0", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xa000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc }, +}; + +static struct qcom_icc_node xm_usb3_1 = { + .name = "xm_usb3_1", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xb000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc }, +}; + +static struct qcom_icc_node xm_usb4_0 = { + .name = "xm_usb4_0", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xc000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc }, +}; + +static struct qcom_icc_node xm_usb4_1 = { + .name = "xm_usb4_1", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a4noc_hscnoc }, +}; + +static struct qcom_icc_node qnm_lpiaon_noc = { + .name = "qnm_lpiaon_noc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_lpass_ag_noc_gemnoc }, +}; + +static struct qcom_icc_node qnm_av1_enc = { + .name = "qnm_av1_enc", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x30000 }, + .prio = 4, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_camnoc_hf = { + .name = "qnm_camnoc_hf", + .channels = 2, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 2, + .port_offsets = { 0x29000, 0x2a000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_hf }, +}; + +static struct qcom_icc_node qnm_camnoc_icp = { + .name = "qnm_camnoc_icp", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x2b000 }, + .prio = 4, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_camnoc_sf = { + .name = "qnm_camnoc_sf", + .channels = 2, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 2, + .port_offsets = { 0x2c000, 0x2d000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_eva = { + .name = "qnm_eva", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x34000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_mdp = { + .name = "qnm_mdp", + .channels = 2, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 2, + .port_offsets = { 0x2e000, 0x2f000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_hf }, +}; + +static struct qcom_icc_node qnm_vapss_hcp = { + .name = "qnm_vapss_hcp", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_video = { + .name = "qnm_video", + .channels = 4, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 4, + .port_offsets = { 0x31000, 0x32000, 0x37000, 0x38000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_video_cv_cpu = { + .name = "qnm_video_cv_cpu", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x33000 }, + .prio = 4, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_video_v_cpu = { + .name = "qnm_video_v_cpu", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x35000 }, + .prio = 4, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_mem_noc_sf }, +}; + +static struct qcom_icc_node qnm_nsp = { + .name = "qnm_nsp", + .channels = 4, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_nsp_hscnoc }, +}; + +static struct qcom_icc_node xm_pcie_0 = { + .name = "xm_pcie_0", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xb000 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_east_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_1 = { + .name = "xm_pcie_1", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xc000 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_east_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_5 = { + .name = "xm_pcie_5", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd000 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_east_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_2 = { + .name = "xm_pcie_2", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd000 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_3a = { + .name = "xm_pcie_3a", + .channels = 1, + .buswidth = 64, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd200 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_3b = { + .name = "xm_pcie_3b", + .channels = 1, + .buswidth = 32, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd400 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_4 = { + .name = "xm_pcie_4", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd600 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc }, +}; + +static struct qcom_icc_node xm_pcie_6 = { + .name = "xm_pcie_6", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd800 }, + .prio = 2, + .urg_fwd = 0, + .prio_fwd_disable = 0, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_pcie_west_mem_noc }, +}; + +static struct qcom_icc_node qnm_aggre1_noc = { + .name = "qnm_aggre1_noc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_node qnm_aggre2_noc = { + .name = "qnm_aggre2_noc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_node qnm_aggre3_noc = { + .name = "qnm_aggre3_noc", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_node qnm_nsi_noc = { + .name = "qnm_nsi_noc", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x1c000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_node qnm_oobmss = { + .name = "qnm_oobmss", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x1b000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_node qns_a1noc_snoc = { + .name = "qns_a1noc_snoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre1_noc }, +}; + +static struct qcom_icc_node qns_a2noc_snoc = { + .name = "qns_a2noc_snoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre2_noc }, +}; + +static struct qcom_icc_node qns_a3noc_snoc = { + .name = "qns_a3noc_snoc", + .channels = 1, + .buswidth = 32, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_aggre3_noc }, +}; + +static struct qcom_icc_node qns_lpass_aggnoc = { + .name = "qns_lpass_aggnoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_lpiaon_noc }, +}; + +static struct qcom_icc_node qns_system_noc = { + .name = "qns_system_noc", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_nsi_noc }, +}; + +static struct qcom_icc_node qns_oobmss_snoc = { + .name = "qns_oobmss_snoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_oobmss }, +}; + +static struct qcom_icc_node qxm_crypto = { + .name = "qxm_crypto", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xb000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc }, +}; + +static struct qcom_icc_node qxm_soccp = { + .name = "qxm_soccp", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xe000 }, + .prio = 0, + .urg_fwd = 1, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc }, +}; + +static struct qcom_icc_node xm_qdss_etr_0 = { + .name = "xm_qdss_etr_0", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xc000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc }, +}; + +static struct qcom_icc_node xm_qdss_etr_1 = { + .name = "xm_qdss_etr_1", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xd000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a1noc_snoc }, +}; + +static struct qcom_icc_node xm_ufs_mem = { + .name = "xm_ufs_mem", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0xa000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a2noc_snoc }, +}; + +static struct qcom_icc_node xm_usb3_2 = { + .name = "xm_usb3_2", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x8000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a2noc_snoc }, +}; + +static struct qcom_icc_node xm_usb4_2 = { + .name = "xm_usb4_2", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x9000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a2noc_snoc }, +}; + +static struct qcom_icc_node qhm_qspi = { + .name = "qhm_qspi", + .channels = 1, + .buswidth = 4, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x10000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node qhm_qup0 = { + .name = "qhm_qup0", + .channels = 1, + .buswidth = 4, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x11000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node qhm_qup1 = { + .name = "qhm_qup1", + .channels = 1, + .buswidth = 4, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x12000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node qhm_qup2 = { + .name = "qhm_qup2", + .channels = 1, + .buswidth = 4, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x13000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node qxm_sp = { + .name = "qxm_sp", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node xm_sdc2 = { + .name = "xm_sdc2", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x18000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node xm_sdc4 = { + .name = "xm_sdc4", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x14000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node xm_usb2_0 = { + .name = "xm_usb2_0", + .channels = 1, + .buswidth = 8, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x15000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node xm_usb3_mp = { + .name = "xm_usb3_mp", + .channels = 1, + .buswidth = 16, + .qosbox = &(const struct qcom_icc_qosbox) { + .num_ports = 1, + .port_offsets = { 0x16000 }, + .prio = 0, + .urg_fwd = 0, + .prio_fwd_disable = 1, + }, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_a3noc_snoc }, +}; + +static struct qcom_icc_node qnm_lpass_lpinoc = { + .name = "qnm_lpass_lpinoc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_lpass_aggnoc }, +}; + +static struct qcom_icc_node xm_cpucp = { + .name = "xm_cpucp", + .channels = 1, + .buswidth = 8, + .num_links = 2, + .link_nodes = (struct qcom_icc_node *[]) { &qns_system_noc, &srvc_nsinoc }, +}; + +static struct qcom_icc_node xm_mem_sp = { + .name = "xm_mem_sp", + .channels = 1, + .buswidth = 8, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_oobmss_snoc }, +}; + +static struct qcom_icc_node qns_lpi_aon_noc = { + .name = "qns_lpi_aon_noc", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qnm_lpass_lpinoc }, +}; + +static struct qcom_icc_node qnm_lpinoc_dsp_qns4m = { + .name = "qnm_lpinoc_dsp_qns4m", + .channels = 1, + .buswidth = 16, + .num_links = 1, + .link_nodes = (struct qcom_icc_node *[]) { &qns_lpi_aon_noc }, +}; + +static struct qcom_icc_bcm bcm_acv = { + .name = "ACV", + .enable_mask = BIT(3), + .num_nodes = 1, + .nodes = { &ebi }, +}; + +static struct qcom_icc_bcm bcm_ce0 = { + .name = "CE0", + .num_nodes = 1, + .nodes = { &qxm_crypto }, +}; + +static struct qcom_icc_bcm bcm_cn0 = { + .name = "CN0", + .keepalive = true, + .enable_mask = BIT(0), + .num_nodes = 60, + .nodes = { &qsm_cfg, &qhs_ahb2phy0, + &qhs_ahb2phy1, &qhs_ahb2phy2, + &qhs_ahb2phy3, &qhs_av1_enc_cfg, + &qhs_camera_cfg, &qhs_clk_ctl, + &qhs_crypto0_cfg, &qhs_gpuss_cfg, + &qhs_imem_cfg, &qhs_pcie0_cfg, + &qhs_pcie1_cfg, &qhs_pcie2_cfg, + &qhs_pcie3a_cfg, &qhs_pcie3b_cfg, + &qhs_pcie4_cfg, &qhs_pcie5_cfg, + &qhs_pcie6_cfg, &qhs_pcie_rscc, + &qhs_pdm, &qhs_prng, + &qhs_qdss_cfg, &qhs_qspi, + &qhs_qup0, &qhs_qup1, + &qhs_qup2, &qhs_sdc2, + &qhs_sdc4, &qhs_smmuv3_cfg, + &qhs_tcsr, &qhs_tlmm, + &qhs_ufs_mem_cfg, &qhs_usb2_0_cfg, + &qhs_usb3_0_cfg, &qhs_usb3_1_cfg, + &qhs_usb3_2_cfg, &qhs_usb3_mp_cfg, + &qhs_usb4_0_cfg, &qhs_usb4_1_cfg, + &qhs_usb4_2_cfg, &qhs_venus_cfg, + &qss_cnoc_pcie_slave_east_cfg, &qss_cnoc_pcie_slave_west_cfg, + &qss_lpass_qtb_cfg, &qss_mnoc_cfg, + &qss_nsp_qtb_cfg, &qss_pcie_east_anoc_cfg, + &qss_pcie_west_anoc_cfg, &xs_qdss_stm, + &xs_sys_tcu_cfg, &qnm_hscnoc_cnoc, + &qhs_aoss, &qhs_ipc_router, + &qhs_soccp, &qhs_tme_cfg, + &qns_apss, &qss_cfg, + &qxs_boot_imem, &qxs_imem }, +}; + +static struct qcom_icc_bcm bcm_cn1 = { + .name = "CN1", + .num_nodes = 1, + .nodes = { &qhs_display_cfg }, +}; + +static struct qcom_icc_bcm bcm_co0 = { + .name = "CO0", + .enable_mask = BIT(0), + .num_nodes = 2, + .nodes = { &qnm_nsp, &qns_nsp_hscnoc }, +}; + +static struct qcom_icc_bcm bcm_lp0 = { + .name = "LP0", + .num_nodes = 2, + .nodes = { &qnm_lpass_lpinoc, &qns_lpass_aggnoc }, +}; + +static struct qcom_icc_bcm bcm_mc0 = { + .name = "MC0", + .keepalive = true, + .num_nodes = 1, + .nodes = { &ebi }, +}; + +static struct qcom_icc_bcm bcm_mm0 = { + .name = "MM0", + .num_nodes = 1, + .nodes = { &qns_mem_noc_hf }, +}; + +static struct qcom_icc_bcm bcm_mm1 = { + .name = "MM1", + .enable_mask = BIT(0), + .num_nodes = 11, + .nodes = { &qnm_av1_enc, &qnm_camnoc_hf, + &qnm_camnoc_icp, &qnm_camnoc_sf, + &qnm_eva, &qnm_mdp, + &qnm_vapss_hcp, &qnm_video, + &qnm_video_cv_cpu, &qnm_video_v_cpu, + &qns_mem_noc_sf }, +}; + +static struct qcom_icc_bcm bcm_qup0 = { + .name = "QUP0", + .vote_scale = 1, + .keepalive = true, + .num_nodes = 1, + .nodes = { &qup0_core_slave }, +}; + +static struct qcom_icc_bcm bcm_qup1 = { + .name = "QUP1", + .vote_scale = 1, + .keepalive = true, + .num_nodes = 1, + .nodes = { &qup1_core_slave }, +}; + +static struct qcom_icc_bcm bcm_qup2 = { + .name = "QUP2", + .vote_scale = 1, + .keepalive = true, + .num_nodes = 1, + .nodes = { &qup2_core_slave }, +}; + +static struct qcom_icc_bcm bcm_sh0 = { + .name = "SH0", + .keepalive = true, + .num_nodes = 1, + .nodes = { &qns_llcc }, +}; + +static struct qcom_icc_bcm bcm_sh1 = { + .name = "SH1", + .enable_mask = BIT(0), + .num_nodes = 18, + .nodes = { &alm_gpu_tcu, &alm_pcie_qtc, + &alm_sys_tcu, &chm_apps, + &qnm_aggre_noc_east, &qnm_gpu, + &qnm_lpass, &qnm_mnoc_hf, + &qnm_mnoc_sf, &qnm_nsp_noc, + &qnm_pcie_east, &qnm_pcie_west, + &qnm_snoc_sf, &qxm_wlan_q6, + &xm_gic, &qns_hscnoc_cnoc, + &qns_pcie_east, &qns_pcie_west }, +}; + +static struct qcom_icc_bcm bcm_sn0 = { + .name = "SN0", + .keepalive = true, + .num_nodes = 1, + .nodes = { &qns_gemnoc_sf }, +}; + +static struct qcom_icc_bcm bcm_sn1 = { + .name = "SN1", + .enable_mask = BIT(0), + .num_nodes = 1, + .nodes = { &qnm_oobmss }, +}; + +static struct qcom_icc_bcm bcm_sn2 = { + .name = "SN2", + .num_nodes = 1, + .nodes = { &qnm_aggre1_noc }, +}; + +static struct qcom_icc_bcm bcm_sn3 = { + .name = "SN3", + .num_nodes = 1, + .nodes = { &qnm_aggre2_noc }, +}; + +static struct qcom_icc_bcm bcm_sn4 = { + .name = "SN4", + .num_nodes = 1, + .nodes = { &qnm_aggre3_noc }, +}; + +static struct qcom_icc_bcm bcm_sn5 = { + .name = "SN5", + .num_nodes = 1, + .nodes = { &qns_a4noc_hscnoc }, +}; + +static struct qcom_icc_bcm bcm_sn6 = { + .name = "SN6", + .num_nodes = 4, + .nodes = { &qns_pcie_east_mem_noc, &qnm_hscnoc_pcie_east, + &qns_pcie_west_mem_noc, &qnm_hscnoc_pcie_west }, +}; + +static struct qcom_icc_bcm * const aggre1_noc_bcms[] = { + &bcm_ce0, +}; + +static struct qcom_icc_node * const aggre1_noc_nodes[] = { + [MASTER_CRYPTO] = &qxm_crypto, + [MASTER_SOCCP_PROC] = &qxm_soccp, + [MASTER_QDSS_ETR] = &xm_qdss_etr_0, + [MASTER_QDSS_ETR_1] = &xm_qdss_etr_1, + [SLAVE_A1NOC_SNOC] = &qns_a1noc_snoc, +}; + +static const struct regmap_config glymur_aggre1_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x14400, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_aggre1_noc = { + .config = &glymur_aggre1_noc_regmap_config, + .nodes = aggre1_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre1_noc_nodes), + .bcms = aggre1_noc_bcms, + .num_bcms = ARRAY_SIZE(aggre1_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_node * const aggre2_noc_nodes[] = { + [MASTER_UFS_MEM] = &xm_ufs_mem, + [MASTER_USB3_2] = &xm_usb3_2, + [MASTER_USB4_2] = &xm_usb4_2, + [SLAVE_A2NOC_SNOC] = &qns_a2noc_snoc, +}; + +static const struct regmap_config glymur_aggre2_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x14400, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_aggre2_noc = { + .config = &glymur_aggre2_noc_regmap_config, + .nodes = aggre2_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre2_noc_nodes), + .alloc_dyn_id = true, + .qos_requires_clocks = true, +}; + +static struct qcom_icc_node * const aggre3_noc_nodes[] = { + [MASTER_QSPI_0] = &qhm_qspi, + [MASTER_QUP_0] = &qhm_qup0, + [MASTER_QUP_1] = &qhm_qup1, + [MASTER_QUP_2] = &qhm_qup2, + [MASTER_SP] = &qxm_sp, + [MASTER_SDCC_2] = &xm_sdc2, + [MASTER_SDCC_4] = &xm_sdc4, + [MASTER_USB2] = &xm_usb2_0, + [MASTER_USB3_MP] = &xm_usb3_mp, + [SLAVE_A3NOC_SNOC] = &qns_a3noc_snoc, +}; + +static const struct regmap_config glymur_aggre3_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1d400, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_aggre3_noc = { + .config = &glymur_aggre3_noc_regmap_config, + .nodes = aggre3_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre3_noc_nodes), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const aggre4_noc_bcms[] = { + &bcm_sn5, +}; + +static struct qcom_icc_node * const aggre4_noc_nodes[] = { + [MASTER_USB3_0] = &xm_usb3_0, + [MASTER_USB3_1] = &xm_usb3_1, + [MASTER_USB4_0] = &xm_usb4_0, + [MASTER_USB4_1] = &xm_usb4_1, + [SLAVE_A4NOC_HSCNOC] = &qns_a4noc_hscnoc, +}; + +static const struct regmap_config glymur_aggre4_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x14400, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_aggre4_noc = { + .config = &glymur_aggre4_noc_regmap_config, + .nodes = aggre4_noc_nodes, + .num_nodes = ARRAY_SIZE(aggre4_noc_nodes), + .bcms = aggre4_noc_bcms, + .num_bcms = ARRAY_SIZE(aggre4_noc_bcms), + .alloc_dyn_id = true, + .qos_requires_clocks = true, +}; + +static struct qcom_icc_bcm * const clk_virt_bcms[] = { + &bcm_qup0, + &bcm_qup1, + &bcm_qup2, +}; + +static struct qcom_icc_node * const clk_virt_nodes[] = { + [MASTER_QUP_CORE_0] = &qup0_core_master, + [MASTER_QUP_CORE_1] = &qup1_core_master, + [MASTER_QUP_CORE_2] = &qup2_core_master, + [SLAVE_QUP_CORE_0] = &qup0_core_slave, + [SLAVE_QUP_CORE_1] = &qup1_core_slave, + [SLAVE_QUP_CORE_2] = &qup2_core_slave, +}; + +static const struct qcom_icc_desc glymur_clk_virt = { + .nodes = clk_virt_nodes, + .num_nodes = ARRAY_SIZE(clk_virt_nodes), + .bcms = clk_virt_bcms, + .num_bcms = ARRAY_SIZE(clk_virt_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const cnoc_cfg_bcms[] = { + &bcm_cn0, + &bcm_cn1, +}; + +static struct qcom_icc_node * const cnoc_cfg_nodes[] = { + [MASTER_CNOC_CFG] = &qsm_cfg, + [SLAVE_AHB2PHY_SOUTH] = &qhs_ahb2phy0, + [SLAVE_AHB2PHY_NORTH] = &qhs_ahb2phy1, + [SLAVE_AHB2PHY_2] = &qhs_ahb2phy2, + [SLAVE_AHB2PHY_3] = &qhs_ahb2phy3, + [SLAVE_AV1_ENC_CFG] = &qhs_av1_enc_cfg, + [SLAVE_CAMERA_CFG] = &qhs_camera_cfg, + [SLAVE_CLK_CTL] = &qhs_clk_ctl, + [SLAVE_CRYPTO_0_CFG] = &qhs_crypto0_cfg, + [SLAVE_DISPLAY_CFG] = &qhs_display_cfg, + [SLAVE_GFX3D_CFG] = &qhs_gpuss_cfg, + [SLAVE_IMEM_CFG] = &qhs_imem_cfg, + [SLAVE_PCIE_0_CFG] = &qhs_pcie0_cfg, + [SLAVE_PCIE_1_CFG] = &qhs_pcie1_cfg, + [SLAVE_PCIE_2_CFG] = &qhs_pcie2_cfg, + [SLAVE_PCIE_3A_CFG] = &qhs_pcie3a_cfg, + [SLAVE_PCIE_3B_CFG] = &qhs_pcie3b_cfg, + [SLAVE_PCIE_4_CFG] = &qhs_pcie4_cfg, + [SLAVE_PCIE_5_CFG] = &qhs_pcie5_cfg, + [SLAVE_PCIE_6_CFG] = &qhs_pcie6_cfg, + [SLAVE_PCIE_RSCC] = &qhs_pcie_rscc, + [SLAVE_PDM] = &qhs_pdm, + [SLAVE_PRNG] = &qhs_prng, + [SLAVE_QDSS_CFG] = &qhs_qdss_cfg, + [SLAVE_QSPI_0] = &qhs_qspi, + [SLAVE_QUP_0] = &qhs_qup0, + [SLAVE_QUP_1] = &qhs_qup1, + [SLAVE_QUP_2] = &qhs_qup2, + [SLAVE_SDCC_2] = &qhs_sdc2, + [SLAVE_SDCC_4] = &qhs_sdc4, + [SLAVE_SMMUV3_CFG] = &qhs_smmuv3_cfg, + [SLAVE_TCSR] = &qhs_tcsr, + [SLAVE_TLMM] = &qhs_tlmm, + [SLAVE_UFS_MEM_CFG] = &qhs_ufs_mem_cfg, + [SLAVE_USB2] = &qhs_usb2_0_cfg, + [SLAVE_USB3_0] = &qhs_usb3_0_cfg, + [SLAVE_USB3_1] = &qhs_usb3_1_cfg, + [SLAVE_USB3_2] = &qhs_usb3_2_cfg, + [SLAVE_USB3_MP] = &qhs_usb3_mp_cfg, + [SLAVE_USB4_0] = &qhs_usb4_0_cfg, + [SLAVE_USB4_1] = &qhs_usb4_1_cfg, + [SLAVE_USB4_2] = &qhs_usb4_2_cfg, + [SLAVE_VENUS_CFG] = &qhs_venus_cfg, + [SLAVE_CNOC_PCIE_SLAVE_EAST_CFG] = &qss_cnoc_pcie_slave_east_cfg, + [SLAVE_CNOC_PCIE_SLAVE_WEST_CFG] = &qss_cnoc_pcie_slave_west_cfg, + [SLAVE_LPASS_QTB_CFG] = &qss_lpass_qtb_cfg, + [SLAVE_CNOC_MNOC_CFG] = &qss_mnoc_cfg, + [SLAVE_NSP_QTB_CFG] = &qss_nsp_qtb_cfg, + [SLAVE_PCIE_EAST_ANOC_CFG] = &qss_pcie_east_anoc_cfg, + [SLAVE_PCIE_WEST_ANOC_CFG] = &qss_pcie_west_anoc_cfg, + [SLAVE_QDSS_STM] = &xs_qdss_stm, + [SLAVE_TCU] = &xs_sys_tcu_cfg, +}; + +static const struct regmap_config glymur_cnoc_cfg_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x6600, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_cnoc_cfg = { + .config = &glymur_cnoc_cfg_regmap_config, + .nodes = cnoc_cfg_nodes, + .num_nodes = ARRAY_SIZE(cnoc_cfg_nodes), + .bcms = cnoc_cfg_bcms, + .num_bcms = ARRAY_SIZE(cnoc_cfg_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const cnoc_main_bcms[] = { + &bcm_cn0, +}; + +static struct qcom_icc_node * const cnoc_main_nodes[] = { + [MASTER_HSCNOC_CNOC] = &qnm_hscnoc_cnoc, + [SLAVE_AOSS] = &qhs_aoss, + [SLAVE_IPC_ROUTER_CFG] = &qhs_ipc_router, + [SLAVE_SOCCP] = &qhs_soccp, + [SLAVE_TME_CFG] = &qhs_tme_cfg, + [SLAVE_APPSS] = &qns_apss, + [SLAVE_CNOC_CFG] = &qss_cfg, + [SLAVE_BOOT_IMEM] = &qxs_boot_imem, + [SLAVE_IMEM] = &qxs_imem, +}; + +static const struct regmap_config glymur_cnoc_main_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x17080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_cnoc_main = { + .config = &glymur_cnoc_main_regmap_config, + .nodes = cnoc_main_nodes, + .num_nodes = ARRAY_SIZE(cnoc_main_nodes), + .bcms = cnoc_main_bcms, + .num_bcms = ARRAY_SIZE(cnoc_main_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const hscnoc_bcms[] = { + &bcm_sh0, + &bcm_sh1, +}; + +static struct qcom_icc_node * const hscnoc_nodes[] = { + [MASTER_GPU_TCU] = &alm_gpu_tcu, + [MASTER_PCIE_TCU] = &alm_pcie_qtc, + [MASTER_SYS_TCU] = &alm_sys_tcu, + [MASTER_APPSS_PROC] = &chm_apps, + [MASTER_AGGRE_NOC_EAST] = &qnm_aggre_noc_east, + [MASTER_GFX3D] = &qnm_gpu, + [MASTER_LPASS_GEM_NOC] = &qnm_lpass, + [MASTER_MNOC_HF_MEM_NOC] = &qnm_mnoc_hf, + [MASTER_MNOC_SF_MEM_NOC] = &qnm_mnoc_sf, + [MASTER_COMPUTE_NOC] = &qnm_nsp_noc, + [MASTER_PCIE_EAST] = &qnm_pcie_east, + [MASTER_PCIE_WEST] = &qnm_pcie_west, + [MASTER_SNOC_SF_MEM_NOC] = &qnm_snoc_sf, + [MASTER_WLAN_Q6] = &qxm_wlan_q6, + [MASTER_GIC] = &xm_gic, + [SLAVE_HSCNOC_CNOC] = &qns_hscnoc_cnoc, + [SLAVE_LLCC] = &qns_llcc, + [SLAVE_PCIE_EAST] = &qns_pcie_east, + [SLAVE_PCIE_WEST] = &qns_pcie_west, +}; + +static const struct regmap_config glymur_hscnoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x93a080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_hscnoc = { + .config = &glymur_hscnoc_regmap_config, + .nodes = hscnoc_nodes, + .num_nodes = ARRAY_SIZE(hscnoc_nodes), + .bcms = hscnoc_bcms, + .num_bcms = ARRAY_SIZE(hscnoc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_node * const lpass_ag_noc_nodes[] = { + [MASTER_LPIAON_NOC] = &qnm_lpiaon_noc, + [SLAVE_LPASS_GEM_NOC] = &qns_lpass_ag_noc_gemnoc, +}; + +static const struct regmap_config glymur_lpass_ag_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xe080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_lpass_ag_noc = { + .config = &glymur_lpass_ag_noc_regmap_config, + .nodes = lpass_ag_noc_nodes, + .num_nodes = ARRAY_SIZE(lpass_ag_noc_nodes), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const lpass_lpiaon_noc_bcms[] = { + &bcm_lp0, +}; + +static struct qcom_icc_node * const lpass_lpiaon_noc_nodes[] = { + [MASTER_LPASS_LPINOC] = &qnm_lpass_lpinoc, + [SLAVE_LPIAON_NOC_LPASS_AG_NOC] = &qns_lpass_aggnoc, +}; + +static const struct regmap_config glymur_lpass_lpiaon_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x19080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_lpass_lpiaon_noc = { + .config = &glymur_lpass_lpiaon_noc_regmap_config, + .nodes = lpass_lpiaon_noc_nodes, + .num_nodes = ARRAY_SIZE(lpass_lpiaon_noc_nodes), + .bcms = lpass_lpiaon_noc_bcms, + .num_bcms = ARRAY_SIZE(lpass_lpiaon_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_node * const lpass_lpicx_noc_nodes[] = { + [MASTER_LPASS_PROC] = &qnm_lpinoc_dsp_qns4m, + [SLAVE_LPICX_NOC_LPIAON_NOC] = &qns_lpi_aon_noc, +}; + +static const struct regmap_config glymur_lpass_lpicx_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x44080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_lpass_lpicx_noc = { + .config = &glymur_lpass_lpicx_noc_regmap_config, + .nodes = lpass_lpicx_noc_nodes, + .num_nodes = ARRAY_SIZE(lpass_lpicx_noc_nodes), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const mc_virt_bcms[] = { + &bcm_acv, + &bcm_mc0, +}; + +static struct qcom_icc_node * const mc_virt_nodes[] = { + [MASTER_LLCC] = &llcc_mc, + [SLAVE_EBI1] = &ebi, +}; + +static const struct qcom_icc_desc glymur_mc_virt = { + .nodes = mc_virt_nodes, + .num_nodes = ARRAY_SIZE(mc_virt_nodes), + .bcms = mc_virt_bcms, + .num_bcms = ARRAY_SIZE(mc_virt_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const mmss_noc_bcms[] = { + &bcm_mm0, + &bcm_mm1, +}; + +static struct qcom_icc_node * const mmss_noc_nodes[] = { + [MASTER_AV1_ENC] = &qnm_av1_enc, + [MASTER_CAMNOC_HF] = &qnm_camnoc_hf, + [MASTER_CAMNOC_ICP] = &qnm_camnoc_icp, + [MASTER_CAMNOC_SF] = &qnm_camnoc_sf, + [MASTER_EVA] = &qnm_eva, + [MASTER_MDP] = &qnm_mdp, + [MASTER_CDSP_HCP] = &qnm_vapss_hcp, + [MASTER_VIDEO] = &qnm_video, + [MASTER_VIDEO_CV_PROC] = &qnm_video_cv_cpu, + [MASTER_VIDEO_V_PROC] = &qnm_video_v_cpu, + [MASTER_CNOC_MNOC_CFG] = &qsm_mnoc_cfg, + [SLAVE_MNOC_HF_MEM_NOC] = &qns_mem_noc_hf, + [SLAVE_MNOC_SF_MEM_NOC] = &qns_mem_noc_sf, + [SLAVE_SERVICE_MNOC] = &srvc_mnoc, +}; + +static const struct regmap_config glymur_mmss_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x5b800, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_mmss_noc = { + .config = &glymur_mmss_noc_regmap_config, + .nodes = mmss_noc_nodes, + .num_nodes = ARRAY_SIZE(mmss_noc_nodes), + .bcms = mmss_noc_bcms, + .num_bcms = ARRAY_SIZE(mmss_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_node * const nsinoc_nodes[] = { + [MASTER_CPUCP] = &xm_cpucp, + [SLAVE_NSINOC_SYSTEM_NOC] = &qns_system_noc, + [SLAVE_SERVICE_NSINOC] = &srvc_nsinoc, +}; + +static const struct regmap_config glymur_nsinoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x14080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_nsinoc = { + .config = &glymur_nsinoc_regmap_config, + .nodes = nsinoc_nodes, + .num_nodes = ARRAY_SIZE(nsinoc_nodes), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const nsp_noc_bcms[] = { + &bcm_co0, +}; + +static struct qcom_icc_node * const nsp_noc_nodes[] = { + [MASTER_CDSP_PROC] = &qnm_nsp, + [SLAVE_NSP0_HSC_NOC] = &qns_nsp_hscnoc, +}; + +static const struct regmap_config glymur_nsp_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x21280, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_nsp_noc = { + .config = &glymur_nsp_noc_regmap_config, + .nodes = nsp_noc_nodes, + .num_nodes = ARRAY_SIZE(nsp_noc_nodes), + .bcms = nsp_noc_bcms, + .num_bcms = ARRAY_SIZE(nsp_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_node * const oobm_ss_noc_nodes[] = { + [MASTER_OOBMSS_SP_PROC] = &xm_mem_sp, + [SLAVE_OOBMSS_SNOC] = &qns_oobmss_snoc, +}; + +static const struct regmap_config glymur_oobm_ss_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1e080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_oobm_ss_noc = { + .config = &glymur_oobm_ss_noc_regmap_config, + .nodes = oobm_ss_noc_nodes, + .num_nodes = ARRAY_SIZE(oobm_ss_noc_nodes), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const pcie_east_anoc_bcms[] = { + &bcm_sn6, +}; + +static struct qcom_icc_node * const pcie_east_anoc_nodes[] = { + [MASTER_PCIE_EAST_ANOC_CFG] = &qsm_pcie_east_anoc_cfg, + [MASTER_PCIE_0] = &xm_pcie_0, + [MASTER_PCIE_1] = &xm_pcie_1, + [MASTER_PCIE_5] = &xm_pcie_5, + [SLAVE_PCIE_EAST_MEM_NOC] = &qns_pcie_east_mem_noc, + [SLAVE_SERVICE_PCIE_EAST_AGGRE_NOC] = &srvc_pcie_east_aggre_noc, +}; + +static const struct regmap_config glymur_pcie_east_anoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xf300, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_pcie_east_anoc = { + .config = &glymur_pcie_east_anoc_regmap_config, + .nodes = pcie_east_anoc_nodes, + .num_nodes = ARRAY_SIZE(pcie_east_anoc_nodes), + .bcms = pcie_east_anoc_bcms, + .num_bcms = ARRAY_SIZE(pcie_east_anoc_bcms), + .alloc_dyn_id = true, + .qos_requires_clocks = true, +}; + +static struct qcom_icc_bcm * const pcie_east_slv_noc_bcms[] = { + &bcm_sn6, +}; + +static struct qcom_icc_node * const pcie_east_slv_noc_nodes[] = { + [MASTER_HSCNOC_PCIE_EAST] = &qnm_hscnoc_pcie_east, + [MASTER_CNOC_PCIE_EAST_SLAVE_CFG] = &qsm_cnoc_pcie_east_slave_cfg, + [SLAVE_HSCNOC_PCIE_EAST_MS_MPU_CFG] = &qhs_hscnoc_pcie_east_ms_mpu_cfg, + [SLAVE_SERVICE_PCIE_EAST] = &srvc_pcie_east, + [SLAVE_PCIE_0] = &xs_pcie_0, + [SLAVE_PCIE_1] = &xs_pcie_1, + [SLAVE_PCIE_5] = &xs_pcie_5, +}; + +static const struct regmap_config glymur_pcie_east_slv_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xe080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_pcie_east_slv_noc = { + .config = &glymur_pcie_east_slv_noc_regmap_config, + .nodes = pcie_east_slv_noc_nodes, + .num_nodes = ARRAY_SIZE(pcie_east_slv_noc_nodes), + .bcms = pcie_east_slv_noc_bcms, + .num_bcms = ARRAY_SIZE(pcie_east_slv_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const pcie_west_anoc_bcms[] = { + &bcm_sn6, +}; + +static struct qcom_icc_node * const pcie_west_anoc_nodes[] = { + [MASTER_PCIE_WEST_ANOC_CFG] = &qsm_pcie_west_anoc_cfg, + [MASTER_PCIE_2] = &xm_pcie_2, + [MASTER_PCIE_3A] = &xm_pcie_3a, + [MASTER_PCIE_3B] = &xm_pcie_3b, + [MASTER_PCIE_4] = &xm_pcie_4, + [MASTER_PCIE_6] = &xm_pcie_6, + [SLAVE_PCIE_WEST_MEM_NOC] = &qns_pcie_west_mem_noc, + [SLAVE_SERVICE_PCIE_WEST_AGGRE_NOC] = &srvc_pcie_west_aggre_noc, +}; + +static const struct regmap_config glymur_pcie_west_anoc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xf580, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_pcie_west_anoc = { + .config = &glymur_pcie_west_anoc_regmap_config, + .nodes = pcie_west_anoc_nodes, + .num_nodes = ARRAY_SIZE(pcie_west_anoc_nodes), + .bcms = pcie_west_anoc_bcms, + .num_bcms = ARRAY_SIZE(pcie_west_anoc_bcms), + .alloc_dyn_id = true, + .qos_requires_clocks = true, +}; + +static struct qcom_icc_bcm * const pcie_west_slv_noc_bcms[] = { + &bcm_sn6, +}; + +static struct qcom_icc_node * const pcie_west_slv_noc_nodes[] = { + [MASTER_HSCNOC_PCIE_WEST] = &qnm_hscnoc_pcie_west, + [MASTER_CNOC_PCIE_WEST_SLAVE_CFG] = &qsm_cnoc_pcie_west_slave_cfg, + [SLAVE_HSCNOC_PCIE_WEST_MS_MPU_CFG] = &qhs_hscnoc_pcie_west_ms_mpu_cfg, + [SLAVE_SERVICE_PCIE_WEST] = &srvc_pcie_west, + [SLAVE_PCIE_2] = &xs_pcie_2, + [SLAVE_PCIE_3A] = &xs_pcie_3a, + [SLAVE_PCIE_3B] = &xs_pcie_3b, + [SLAVE_PCIE_4] = &xs_pcie_4, + [SLAVE_PCIE_6] = &xs_pcie_6, +}; + +static const struct regmap_config glymur_pcie_west_slv_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xf180, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_pcie_west_slv_noc = { + .config = &glymur_pcie_west_slv_noc_regmap_config, + .nodes = pcie_west_slv_noc_nodes, + .num_nodes = ARRAY_SIZE(pcie_west_slv_noc_nodes), + .bcms = pcie_west_slv_noc_bcms, + .num_bcms = ARRAY_SIZE(pcie_west_slv_noc_bcms), + .alloc_dyn_id = true, +}; + +static struct qcom_icc_bcm * const system_noc_bcms[] = { + &bcm_sn0, + &bcm_sn1, + &bcm_sn2, + &bcm_sn3, + &bcm_sn4, +}; + +static struct qcom_icc_node * const system_noc_nodes[] = { + [MASTER_A1NOC_SNOC] = &qnm_aggre1_noc, + [MASTER_A2NOC_SNOC] = &qnm_aggre2_noc, + [MASTER_A3NOC_SNOC] = &qnm_aggre3_noc, + [MASTER_NSINOC_SNOC] = &qnm_nsi_noc, + [MASTER_OOBMSS] = &qnm_oobmss, + [SLAVE_SNOC_GEM_NOC_SF] = &qns_gemnoc_sf, +}; + +static const struct regmap_config glymur_system_noc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1c080, + .fast_io = true, +}; + +static const struct qcom_icc_desc glymur_system_noc = { + .config = &glymur_system_noc_regmap_config, + .nodes = system_noc_nodes, + .num_nodes = ARRAY_SIZE(system_noc_nodes), + .bcms = system_noc_bcms, + .num_bcms = ARRAY_SIZE(system_noc_bcms), + .alloc_dyn_id = true, +}; + +static const struct of_device_id qnoc_of_match[] = { + { .compatible = "qcom,glymur-aggre1-noc", .data = &glymur_aggre1_noc}, + { .compatible = "qcom,glymur-aggre2-noc", .data = &glymur_aggre2_noc}, + { .compatible = "qcom,glymur-aggre3-noc", .data = &glymur_aggre3_noc}, + { .compatible = "qcom,glymur-aggre4-noc", .data = &glymur_aggre4_noc}, + { .compatible = "qcom,glymur-clk-virt", .data = &glymur_clk_virt}, + { .compatible = "qcom,glymur-cnoc-cfg", .data = &glymur_cnoc_cfg}, + { .compatible = "qcom,glymur-cnoc-main", .data = &glymur_cnoc_main}, + { .compatible = "qcom,glymur-hscnoc", .data = &glymur_hscnoc}, + { .compatible = "qcom,glymur-lpass-ag-noc", .data = &glymur_lpass_ag_noc}, + { .compatible = "qcom,glymur-lpass-lpiaon-noc", .data = &glymur_lpass_lpiaon_noc}, + { .compatible = "qcom,glymur-lpass-lpicx-noc", .data = &glymur_lpass_lpicx_noc}, + { .compatible = "qcom,glymur-mc-virt", .data = &glymur_mc_virt}, + { .compatible = "qcom,glymur-mmss-noc", .data = &glymur_mmss_noc}, + { .compatible = "qcom,glymur-nsinoc", .data = &glymur_nsinoc}, + { .compatible = "qcom,glymur-nsp-noc", .data = &glymur_nsp_noc}, + { .compatible = "qcom,glymur-oobm-ss-noc", .data = &glymur_oobm_ss_noc}, + { .compatible = "qcom,glymur-pcie-east-anoc", .data = &glymur_pcie_east_anoc}, + { .compatible = "qcom,glymur-pcie-east-slv-noc", .data = &glymur_pcie_east_slv_noc}, + { .compatible = "qcom,glymur-pcie-west-anoc", .data = &glymur_pcie_west_anoc}, + { .compatible = "qcom,glymur-pcie-west-slv-noc", .data = &glymur_pcie_west_slv_noc}, + { .compatible = "qcom,glymur-system-noc", .data = &glymur_system_noc}, + { } +}; +MODULE_DEVICE_TABLE(of, qnoc_of_match); + +static struct platform_driver qnoc_driver = { + .probe = qcom_icc_rpmh_probe, + .remove = qcom_icc_rpmh_remove, + .driver = { + .name = "qnoc-glymur", + .of_match_table = qnoc_of_match, + .sync_state = icc_sync_state, + }, +}; + +static int __init qnoc_driver_init(void) +{ + return platform_driver_register(&qnoc_driver); +} +core_initcall(qnoc_driver_init); + +static void __exit qnoc_driver_exit(void) +{ + platform_driver_unregister(&qnoc_driver); +} +module_exit(qnoc_driver_exit); + +MODULE_DESCRIPTION("GLYMUR NoC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/interconnect/qcom/icc-rpmh.h b/drivers/interconnect/qcom/icc-rpmh.h index bd8d730249b1..307f48412563 100644 --- a/drivers/interconnect/qcom/icc-rpmh.h +++ b/drivers/interconnect/qcom/icc-rpmh.h @@ -53,7 +53,7 @@ struct bcm_db { u8 reserved; }; -#define MAX_PORTS 2 +#define MAX_PORTS 4 /** * struct qcom_icc_qosbox - Qualcomm specific QoS config diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index e2e66f5f4fb8..b32a2597d246 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -23,7 +23,6 @@ obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o obj-$(CONFIG_SENSORS_APDS990X) += apds990x.o obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o obj-$(CONFIG_KGDB_TESTS) += kgdbts.o -obj-$(CONFIG_TEST_MISC_MINOR) += misc_minor_kunit.o obj-$(CONFIG_SGI_XP) += sgi-xp/ obj-$(CONFIG_SGI_GRU) += sgi-gru/ obj-$(CONFIG_SMPRO_ERRMON) += smpro-errmon.o diff --git a/drivers/misc/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c index 756ef6912b5a..04683b981e54 100644 --- a/drivers/misc/ad525x_dpot.c +++ b/drivers/misc/ad525x_dpot.c @@ -73,6 +73,7 @@ #include <linux/kernel.h> #include <linux/delay.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include "ad525x_dpot.h" @@ -418,10 +419,8 @@ static ssize_t sysfs_show_reg(struct device *dev, s32 value; if (reg & DPOT_ADDR_OTP_EN) - return sprintf(buf, "%s\n", - test_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask) ? - "enabled" : "disabled"); - + return sprintf(buf, "%s\n", str_enabled_disabled( + test_bit(DPOT_RDAC_MASK & reg, data->otp_en_mask))); mutex_lock(&data->update_lock); value = dpot_read(data, reg); diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig index 4840831c84ca..4aae0733d0fc 100644 --- a/drivers/misc/amd-sbi/Kconfig +++ b/drivers/misc/amd-sbi/Kconfig @@ -2,6 +2,7 @@ config AMD_SBRMI_I2C tristate "AMD side band RMI support" depends on I2C + select REGMAP_I2C help Side band RMI over I2C support for AMD out of band management. diff --git a/drivers/misc/apds990x.c b/drivers/misc/apds990x.c index e7d73c972f65..58946c4ff1a5 100644 --- a/drivers/misc/apds990x.c +++ b/drivers/misc/apds990x.c @@ -984,7 +984,6 @@ static ssize_t apds990x_power_state_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", !pm_runtime_suspended(dev)); - return 0; } static ssize_t apds990x_power_state_store(struct device *dev, diff --git a/drivers/misc/cardreader/rts5227.c b/drivers/misc/cardreader/rts5227.c index cd512284bfb3..46444bb47f65 100644 --- a/drivers/misc/cardreader/rts5227.c +++ b/drivers/misc/cardreader/rts5227.c @@ -79,6 +79,10 @@ static void rts5227_fetch_vendor_settings(struct rtsx_pcr *pcr) pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg); if (rtsx_reg_check_reverse_socket(reg)) pcr->flags |= PCR_REVERSE_SOCKET; + if (rtsx_reg_check_cd_reverse(reg)) + pcr->option.sd_cd_reverse_en = 1; + if (rtsx_reg_check_wp_reverse(reg)) + pcr->option.sd_wp_reverse_en = 1; } static void rts5227_init_from_cfg(struct rtsx_pcr *pcr) @@ -127,8 +131,10 @@ static int rts5227_extra_init_hw(struct rtsx_pcr *pcr) /* Configure force_clock_req */ if (pcr->flags & PCR_REVERSE_SOCKET) rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x30, 0x30); - else - rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x30, 0x00); + else { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x20, option->sd_cd_reverse_en << 5); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x10, option->sd_wp_reverse_en << 4); + } if (CHK_PCI_PID(pcr, 0x522A)) rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, RTS522A_AUTOLOAD_CFG1, @@ -350,6 +356,8 @@ void rts5227_init_params(struct rtsx_pcr *pcr) pcr->ms_pull_ctl_disable_tbl = rts5227_ms_pull_ctl_disable_tbl; pcr->reg_pm_ctrl3 = PM_CTRL3; + pcr->option.sd_cd_reverse_en = 0; + pcr->option.sd_wp_reverse_en = 0; } static int rts522a_optimize_phy(struct rtsx_pcr *pcr) @@ -508,5 +516,4 @@ void rts522a_init_params(struct rtsx_pcr *pcr) pcr->hw_param.interrupt_en |= SD_OC_INT_EN; pcr->hw_param.ocp_glitch = SD_OCP_GLITCH_10M; pcr->option.sd_800mA_ocp_thd = RTS522A_OCP_THD_800; - } diff --git a/drivers/misc/cardreader/rts5228.c b/drivers/misc/cardreader/rts5228.c index 0c7f10bcf6f1..db7e735ac24f 100644 --- a/drivers/misc/cardreader/rts5228.c +++ b/drivers/misc/cardreader/rts5228.c @@ -84,6 +84,10 @@ static void rtsx5228_fetch_vendor_settings(struct rtsx_pcr *pcr) pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg); if (rtsx_reg_check_reverse_socket(reg)) pcr->flags |= PCR_REVERSE_SOCKET; + if (rtsx_reg_check_cd_reverse(reg)) + pcr->option.sd_cd_reverse_en = 1; + if (rtsx_reg_check_wp_reverse(reg)) + pcr->option.sd_wp_reverse_en = 1; } static int rts5228_optimize_phy(struct rtsx_pcr *pcr) @@ -432,8 +436,10 @@ static int rts5228_extra_init_hw(struct rtsx_pcr *pcr) if (pcr->flags & PCR_REVERSE_SOCKET) rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x30); - else - rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x00); + else { + rtsx_pci_write_register(pcr, PETXCFG, 0x20, option->sd_cd_reverse_en << 5); + rtsx_pci_write_register(pcr, PETXCFG, 0x10, option->sd_wp_reverse_en << 4); + } /* * If u_force_clkreq_0 is enabled, CLKREQ# PIN will be forced @@ -720,4 +726,6 @@ void rts5228_init_params(struct rtsx_pcr *pcr) hw_param->interrupt_en |= SD_OC_INT_EN; hw_param->ocp_glitch = SD_OCP_GLITCH_800U; option->sd_800mA_ocp_thd = RTS5228_LDO1_OCP_THD_930; + option->sd_cd_reverse_en = 0; + option->sd_wp_reverse_en = 0; } diff --git a/drivers/misc/cardreader/rts5249.c b/drivers/misc/cardreader/rts5249.c index 6c81040e18be..38aefd8db452 100644 --- a/drivers/misc/cardreader/rts5249.c +++ b/drivers/misc/cardreader/rts5249.c @@ -60,6 +60,7 @@ static void rtsx_base_fetch_vendor_settings(struct rtsx_pcr *pcr) pci_read_config_dword(pdev, PCR_SETTING_REG1, ®); pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", PCR_SETTING_REG1, reg); + pci_write_config_dword(pdev, 0x718, 0x0007C000); if (!rtsx_vendor_setting_valid(reg)) { pcr_dbg(pcr, "skip fetch vendor setting\n"); @@ -82,6 +83,10 @@ static void rtsx_base_fetch_vendor_settings(struct rtsx_pcr *pcr) pcr->sd30_drive_sel_3v3 = rtsx_reg_to_sd30_drive_sel_3v3(reg); if (rtsx_reg_check_reverse_socket(reg)) pcr->flags |= PCR_REVERSE_SOCKET; + if (rtsx_reg_check_cd_reverse(reg)) + pcr->option.sd_cd_reverse_en = 1; + if (rtsx_reg_check_wp_reverse(reg)) + pcr->option.sd_wp_reverse_en = 1; } static void rts5249_init_from_cfg(struct rtsx_pcr *pcr) @@ -254,9 +259,11 @@ static int rts5249_extra_init_hw(struct rtsx_pcr *pcr) /* Configure driving */ rts5249_fill_driving(pcr, OUTPUT_3V3); if (pcr->flags & PCR_REVERSE_SOCKET) - rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB0, 0xB0); - else - rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0xB0, 0x80); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x30, 0x30); + else { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x20, option->sd_cd_reverse_en << 5); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x10, option->sd_wp_reverse_en << 4); + } rtsx_pci_send_cmd(pcr, CMD_TIMEOUT_DEF); @@ -572,6 +579,9 @@ void rts5249_init_params(struct rtsx_pcr *pcr) option->ltr_l1off_sspwrgate = LTR_L1OFF_SSPWRGATE_5249_DEF; option->ltr_l1off_snooze_sspwrgate = LTR_L1OFF_SNOOZE_SSPWRGATE_5249_DEF; + + option->sd_cd_reverse_en = 0; + option->sd_wp_reverse_en = 0; } static int rts524a_write_phy(struct rtsx_pcr *pcr, u8 addr, u16 val) diff --git a/drivers/misc/cardreader/rts5264.c b/drivers/misc/cardreader/rts5264.c index d050c9fff7ac..99a2d5ea6421 100644 --- a/drivers/misc/cardreader/rts5264.c +++ b/drivers/misc/cardreader/rts5264.c @@ -527,8 +527,16 @@ static void rts5264_init_from_hw(struct rtsx_pcr *pcr) pcr->rtd3_en = rts5264_reg_to_rtd3(lval2); - if (rts5264_reg_check_reverse_socket(lval2)) - pcr->flags |= PCR_REVERSE_SOCKET; + if (rts5264_reg_check_reverse_socket(lval2)) { + if (is_version_higher_than(pcr, PID_5264, RTS5264_IC_VER_B)) + pcr->option.sd_cd_reverse_en = 1; + else + pcr->flags |= PCR_REVERSE_SOCKET; + } + + if (rts5264_reg_check_wp_reverse(lval2) && + is_version_higher_than(pcr, PID_5264, RTS5264_IC_VER_B)) + pcr->option.sd_wp_reverse_en = 1; pci_read_config_dword(pdev, setting_reg1, &lval1); pcr_dbg(pcr, "Cfg 0x%x: 0x%x\n", setting_reg1, lval1); @@ -622,8 +630,10 @@ static int rts5264_extra_init_hw(struct rtsx_pcr *pcr) if (pcr->flags & PCR_REVERSE_SOCKET) rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x30); - else - rtsx_pci_write_register(pcr, PETXCFG, 0x30, 0x00); + else { + rtsx_pci_write_register(pcr, PETXCFG, 0x20, option->sd_cd_reverse_en << 5); + rtsx_pci_write_register(pcr, PETXCFG, 0x10, option->sd_wp_reverse_en << 4); + } /* * If u_force_clkreq_0 is enabled, CLKREQ# PIN will be forced @@ -957,4 +967,6 @@ void rts5264_init_params(struct rtsx_pcr *pcr) hw_param->interrupt_en |= (SD_OC_INT_EN | SD_OVP_INT_EN); hw_param->ocp_glitch = SD_OCP_GLITCH_800U | SDVIO_OCP_GLITCH_800U; option->sd_800mA_ocp_thd = RTS5264_LDO1_OCP_THD_1150; + option->sd_cd_reverse_en = 0; + option->sd_wp_reverse_en = 0; } diff --git a/drivers/misc/cardreader/rts5264.h b/drivers/misc/cardreader/rts5264.h index f3e81daa708d..611ee253367c 100644 --- a/drivers/misc/cardreader/rts5264.h +++ b/drivers/misc/cardreader/rts5264.h @@ -14,6 +14,7 @@ #define rts5264_reg_to_aspm(reg) \ (((~(reg) >> 28) & 0x02) | (((reg) >> 28) & 0x01)) #define rts5264_reg_check_reverse_socket(reg) ((reg) & 0x04) +#define rts5264_reg_check_wp_reverse(reg) ((reg) & 0x8000) #define rts5264_reg_to_sd30_drive_sel_1v8(reg) (((reg) >> 22) & 0x03) #define rts5264_reg_to_sd30_drive_sel_3v3(reg) (((reg) >> 16) & 0x03) #define rts5264_reg_to_rtd3(reg) ((reg) & 0x08) diff --git a/drivers/misc/cardreader/rtsx_pcr.h b/drivers/misc/cardreader/rtsx_pcr.h index 8e5951b61143..40562ff2be13 100644 --- a/drivers/misc/cardreader/rtsx_pcr.h +++ b/drivers/misc/cardreader/rtsx_pcr.h @@ -100,6 +100,8 @@ static inline u8 map_sd_drive(int idx) #define rtsx_reg_to_sd30_drive_sel_3v3(reg) (((reg) >> 5) & 0x03) #define rtsx_reg_to_card_drive_sel(reg) ((((reg) >> 25) & 0x01) << 6) #define rtsx_reg_check_reverse_socket(reg) ((reg) & 0x4000) +#define rtsx_reg_check_cd_reverse(reg) ((reg) & 0x800000) +#define rtsx_reg_check_wp_reverse(reg) ((reg) & 0x400000) #define rts5209_reg_to_aspm(reg) (((reg) >> 5) & 0x03) #define rts5209_reg_check_ms_pmos(reg) (!((reg) & 0x08)) #define rts5209_reg_to_sd30_drive_sel_1v8(reg) (((reg) >> 3) & 0x07) diff --git a/drivers/misc/dw-xdata-pcie.c b/drivers/misc/dw-xdata-pcie.c index efd0ca8cc925..a604c0e9c038 100644 --- a/drivers/misc/dw-xdata-pcie.c +++ b/drivers/misc/dw-xdata-pcie.c @@ -16,6 +16,7 @@ #include <linux/mutex.h> #include <linux/delay.h> #include <linux/pci.h> +#include <linux/string_choices.h> #define DW_XDATA_DRIVER_NAME "dw-xdata-pcie" @@ -132,7 +133,7 @@ static void dw_xdata_start(struct dw_xdata *dw, bool write) if (!(status & STATUS_DONE)) dev_dbg(dev, "xData: started %s direction\n", - write ? "write" : "read"); + str_write_read(write)); } static void dw_xdata_perf_meas(struct dw_xdata *dw, u64 *data, bool write) @@ -195,7 +196,7 @@ static void dw_xdata_perf(struct dw_xdata *dw, u64 *rate, bool write) mutex_unlock(&dw->mutex); dev_dbg(dev, "xData: time=%llu us, %s=%llu MB/s\n", - diff, write ? "write" : "read", *rate); + diff, str_write_read(write), *rate); } static struct dw_xdata *misc_dev_to_dw(struct miscdevice *misc_dev) diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig index 0bef5b93bd6d..4d0ce47aa282 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig @@ -120,4 +120,22 @@ config EEPROM_EE1004 This driver can also be built as a module. If so, the module will be called ee1004. +config EEPROM_M24LR + tristate "STMicroelectronics M24LR RFID/NFC EEPROM support" + depends on I2C && SYSFS + select REGMAP_I2C + select NVMEM + select NVMEM_SYSFS + help + This enables support for STMicroelectronics M24LR RFID/NFC EEPROM + chips. These dual-interface devices expose two I2C addresses: + one for EEPROM memory access and another for control and system + configuration (e.g. UID, password handling). + + This driver provides a sysfs interface for control functions and + integrates with the nvmem subsystem for EEPROM access. + + To compile this driver as a module, choose M here: the + module will be called m24lr. + endmenu diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile index 65794e526d5d..8f311fd6a4ce 100644 --- a/drivers/misc/eeprom/Makefile +++ b/drivers/misc/eeprom/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o obj-$(CONFIG_EEPROM_IDT_89HPESX) += idt_89hpesx.o obj-$(CONFIG_EEPROM_EE1004) += ee1004.o +obj-$(CONFIG_EEPROM_M24LR) += m24lr.o diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 2d0492867054..e2868f7bdb03 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -379,37 +379,49 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) struct at25_data *at25 = container_of(chip, struct at25_data, chip); u8 sernum[FM25_SN_LEN]; u8 id[FM25_ID_LEN]; + u32 val; int i; strscpy(chip->name, "fm25", sizeof(chip->name)); - /* Get ID of chip */ - fm25_aux_read(at25, id, FM25_RDID, FM25_ID_LEN); - /* There are inside-out FRAM variations, detect them and reverse the ID bytes */ - if (id[6] == 0x7f && id[2] == 0xc2) - for (i = 0; i < ARRAY_SIZE(id) / 2; i++) { - u8 tmp = id[i]; - int j = ARRAY_SIZE(id) - i - 1; + if (!device_property_read_u32(dev, "size", &val)) { + chip->byte_len = val; + } else { + /* Get ID of chip */ + fm25_aux_read(at25, id, FM25_RDID, FM25_ID_LEN); + /* There are inside-out FRAM variations, detect them and reverse the ID bytes */ + if (id[6] == 0x7f && id[2] == 0xc2) + for (i = 0; i < ARRAY_SIZE(id) / 2; i++) { + u8 tmp = id[i]; + int j = ARRAY_SIZE(id) - i - 1; + + id[i] = id[j]; + id[j] = tmp; + } + if (id[6] != 0xc2) { + dev_err(dev, "Error: no Cypress FRAM with device ID (manufacturer ID bank 7: %02x)\n", id[6]); + return -ENODEV; + } - id[i] = id[j]; - id[j] = tmp; + switch (id[7]) { + case 0x21 ... 0x26: + chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; + break; + case 0x2a ... 0x30: + /* CY15B116QN ... CY15B116QN */ + chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13); + break; + default: + dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]); + return -ENODEV; } - if (id[6] != 0xc2) { - dev_err(dev, "Error: no Cypress FRAM (id %02x)\n", id[6]); - return -ENODEV; - } - switch (id[7]) { - case 0x21 ... 0x26: - chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; - break; - case 0x2a ... 0x30: - /* CY15B116QN ... CY15B116QN */ - chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13); - break; - default: - dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]); - return -ENODEV; + if (id[8]) { + fm25_aux_read(at25, sernum, FM25_RDSN, FM25_SN_LEN); + /* Swap byte order */ + for (i = 0; i < FM25_SN_LEN; i++) + at25->sernum[i] = sernum[FM25_SN_LEN - 1 - i]; + } } if (chip->byte_len > 64 * 1024) @@ -417,13 +429,6 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) else chip->flags |= EE_ADDR2; - if (id[8]) { - fm25_aux_read(at25, sernum, FM25_RDSN, FM25_SN_LEN); - /* Swap byte order */ - for (i = 0; i < FM25_SN_LEN; i++) - at25->sernum[i] = sernum[FM25_SN_LEN - 1 - i]; - } - chip->page_size = PAGE_SIZE; return 0; } diff --git a/drivers/misc/eeprom/m24lr.c b/drivers/misc/eeprom/m24lr.c new file mode 100644 index 000000000000..7a9fd45a8e46 --- /dev/null +++ b/drivers/misc/eeprom/m24lr.c @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * m24lr.c - Sysfs control interface for ST M24LR series RFID/NFC chips + * + * Copyright (c) 2025 Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> + * + * This driver implements both the sysfs-based control interface and EEPROM + * access for STMicroelectronics M24LR series chips (e.g., M24LR04E-R). + * It provides access to control registers for features such as password + * authentication, memory protection, and device configuration. In addition, + * it manages read and write operations to the EEPROM region of the chip. + */ + +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> + +#define M24LR_WRITE_TIMEOUT 25u +#define M24LR_READ_TIMEOUT (M24LR_WRITE_TIMEOUT) + +/** + * struct m24lr_chip - describes chip-specific sysfs layout + * @sss_len: the length of the sss region + * @page_size: chip-specific limit on the maximum number of bytes allowed + * in a single write operation. + * @eeprom_size: size of the EEPROM in byte + * + * Supports multiple M24LR chip variants (e.g., M24LRxx) by allowing each + * to define its own set of sysfs attributes, depending on its available + * registers and features. + */ +struct m24lr_chip { + unsigned int sss_len; + unsigned int page_size; + unsigned int eeprom_size; +}; + +/** + * struct m24lr - core driver data for M24LR chip control + * @uid: 64 bits unique identifier stored in the device + * @sss_len: the length of the sss region + * @page_size: chip-specific limit on the maximum number of bytes allowed + * in a single write operation. + * @eeprom_size: size of the EEPROM in byte + * @ctl_regmap: regmap interface for accessing the system parameter sector + * @eeprom_regmap: regmap interface for accessing the EEPROM + * @lock: mutex to synchronize operations to the device + * + * Central data structure holding the state and resources used by the + * M24LR device driver. + */ +struct m24lr { + u64 uid; + unsigned int sss_len; + unsigned int page_size; + unsigned int eeprom_size; + struct regmap *ctl_regmap; + struct regmap *eeprom_regmap; + struct mutex lock; /* synchronize operations to the device */ +}; + +static const struct regmap_range m24lr_ctl_vo_ranges[] = { + regmap_reg_range(0, 63), +}; + +static const struct regmap_access_table m24lr_ctl_vo_table = { + .yes_ranges = m24lr_ctl_vo_ranges, + .n_yes_ranges = ARRAY_SIZE(m24lr_ctl_vo_ranges), +}; + +static const struct regmap_config m24lr_ctl_regmap_conf = { + .name = "m24lr_ctl", + .reg_stride = 1, + .reg_bits = 16, + .val_bits = 8, + .disable_locking = false, + .cache_type = REGCACHE_RBTREE,/* Flat can't be used, there's huge gap */ + .volatile_table = &m24lr_ctl_vo_table, +}; + +/* Chip descriptor for M24LR04E-R variant */ +static const struct m24lr_chip m24lr04e_r_chip = { + .page_size = 4, + .eeprom_size = 512, + .sss_len = 4, +}; + +/* Chip descriptor for M24LR16E-R variant */ +static const struct m24lr_chip m24lr16e_r_chip = { + .page_size = 4, + .eeprom_size = 2048, + .sss_len = 16, +}; + +/* Chip descriptor for M24LR64E-R variant */ +static const struct m24lr_chip m24lr64e_r_chip = { + .page_size = 4, + .eeprom_size = 8192, + .sss_len = 64, +}; + +static const struct i2c_device_id m24lr_ids[] = { + { "m24lr04e-r", (kernel_ulong_t)&m24lr04e_r_chip}, + { "m24lr16e-r", (kernel_ulong_t)&m24lr16e_r_chip}, + { "m24lr64e-r", (kernel_ulong_t)&m24lr64e_r_chip}, + { } +}; +MODULE_DEVICE_TABLE(i2c, m24lr_ids); + +static const struct of_device_id m24lr_of_match[] = { + { .compatible = "st,m24lr04e-r", .data = &m24lr04e_r_chip}, + { .compatible = "st,m24lr16e-r", .data = &m24lr16e_r_chip}, + { .compatible = "st,m24lr64e-r", .data = &m24lr64e_r_chip}, + { } +}; +MODULE_DEVICE_TABLE(of, m24lr_of_match); + +/** + * m24lr_regmap_read - read data using regmap with retry on failure + * @regmap: regmap instance for the device + * @buf: buffer to store the read data + * @size: number of bytes to read + * @offset: starting register address + * + * Attempts to read a block of data from the device with retries and timeout. + * Some M24LR chips may transiently NACK reads (e.g., during internal write + * cycles), so this function retries with a short sleep until the timeout + * expires. + * + * Returns: + * Number of bytes read on success, + * -ETIMEDOUT if the read fails within the timeout window. + */ +static ssize_t m24lr_regmap_read(struct regmap *regmap, u8 *buf, + size_t size, unsigned int offset) +{ + int err; + unsigned long timeout, read_time; + ssize_t ret = -ETIMEDOUT; + + timeout = jiffies + msecs_to_jiffies(M24LR_READ_TIMEOUT); + do { + read_time = jiffies; + + err = regmap_bulk_read(regmap, offset, buf, size); + if (!err) { + ret = size; + break; + } + + usleep_range(1000, 2000); + } while (time_before(read_time, timeout)); + + return ret; +} + +/** + * m24lr_regmap_write - write data using regmap with retry on failure + * @regmap: regmap instance for the device + * @buf: buffer containing the data to write + * @size: number of bytes to write + * @offset: starting register address + * + * Attempts to write a block of data to the device with retries and a timeout. + * Some M24LR devices may NACK I2C writes while an internal write operation + * is in progress. This function retries the write operation with a short delay + * until it succeeds or the timeout is reached. + * + * Returns: + * Number of bytes written on success, + * -ETIMEDOUT if the write fails within the timeout window. + */ +static ssize_t m24lr_regmap_write(struct regmap *regmap, const u8 *buf, + size_t size, unsigned int offset) +{ + int err; + unsigned long timeout, write_time; + ssize_t ret = -ETIMEDOUT; + + timeout = jiffies + msecs_to_jiffies(M24LR_WRITE_TIMEOUT); + + do { + write_time = jiffies; + + err = regmap_bulk_write(regmap, offset, buf, size); + if (!err) { + ret = size; + break; + } + + usleep_range(1000, 2000); + } while (time_before(write_time, timeout)); + + return ret; +} + +static ssize_t m24lr_read(struct m24lr *m24lr, u8 *buf, size_t size, + unsigned int offset, bool is_eeprom) +{ + struct regmap *regmap; + ssize_t ret; + + if (is_eeprom) + regmap = m24lr->eeprom_regmap; + else + regmap = m24lr->ctl_regmap; + + mutex_lock(&m24lr->lock); + ret = m24lr_regmap_read(regmap, buf, size, offset); + mutex_unlock(&m24lr->lock); + + return ret; +} + +/** + * m24lr_write - write buffer to M24LR device with page alignment handling + * @m24lr: pointer to driver context + * @buf: data buffer to write + * @size: number of bytes to write + * @offset: target register address in the device + * @is_eeprom: true if the write should target the EEPROM, + * false if it should target the system parameters sector. + * + * Writes data to the M24LR device using regmap, split into chunks no larger + * than page_size to respect device-specific write limitations (e.g., page + * size or I2C hold-time concerns). Each chunk is aligned to the page boundary + * defined by page_size. + * + * Returns: + * Total number of bytes written on success, + * A negative error code if any write fails. + */ +static ssize_t m24lr_write(struct m24lr *m24lr, const u8 *buf, size_t size, + unsigned int offset, bool is_eeprom) +{ + unsigned int n, next_sector; + struct regmap *regmap; + ssize_t ret = 0; + ssize_t err; + + if (is_eeprom) + regmap = m24lr->eeprom_regmap; + else + regmap = m24lr->ctl_regmap; + + n = min_t(unsigned int, size, m24lr->page_size); + next_sector = roundup(offset + 1, m24lr->page_size); + if (offset + n > next_sector) + n = next_sector - offset; + + mutex_lock(&m24lr->lock); + while (n) { + err = m24lr_regmap_write(regmap, buf + offset, n, offset); + if (IS_ERR_VALUE(err)) { + if (!ret) + ret = err; + + break; + } + + offset += n; + size -= n; + ret += n; + n = min_t(unsigned int, size, m24lr->page_size); + } + mutex_unlock(&m24lr->lock); + + return ret; +} + +/** + * m24lr_write_pass - Write password to M24LR043-R using secure format + * @m24lr: Pointer to device control structure + * @buf: Input buffer containing hex-encoded password + * @count: Number of bytes in @buf + * @code: Operation code to embed between password copies + * + * This function parses a 4-byte password, encodes it in big-endian format, + * and constructs a 9-byte sequence of the form: + * + * [BE(password), code, BE(password)] + * + * The result is written to register 0x0900 (2304), which is the password + * register in M24LR04E-R chip. + * + * Return: Number of bytes written on success, or negative error code on failure + */ +static ssize_t m24lr_write_pass(struct m24lr *m24lr, const char *buf, + size_t count, u8 code) +{ + __be32 be_pass; + u8 output[9]; + ssize_t ret; + u32 pass; + int err; + + if (!count) + return -EINVAL; + + if (count > 8) + return -EINVAL; + + err = kstrtou32(buf, 16, &pass); + if (err) + return err; + + be_pass = cpu_to_be32(pass); + + memcpy(output, &be_pass, sizeof(be_pass)); + output[4] = code; + memcpy(output + 5, &be_pass, sizeof(be_pass)); + + mutex_lock(&m24lr->lock); + ret = m24lr_regmap_write(m24lr->ctl_regmap, output, 9, 2304); + mutex_unlock(&m24lr->lock); + + return ret; +} + +static ssize_t m24lr_read_reg_le(struct m24lr *m24lr, u64 *val, + unsigned int reg_addr, + unsigned int reg_size) +{ + ssize_t ret; + __le64 input = 0; + + ret = m24lr_read(m24lr, (u8 *)&input, reg_size, reg_addr, false); + if (IS_ERR_VALUE(ret)) + return ret; + + if (ret != reg_size) + return -EINVAL; + + switch (reg_size) { + case 1: + *val = *(u8 *)&input; + break; + case 2: + *val = le16_to_cpu((__le16)input); + break; + case 4: + *val = le32_to_cpu((__le32)input); + break; + case 8: + *val = le64_to_cpu((__le64)input); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int m24lr_nvmem_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + ssize_t err; + struct m24lr *m24lr = priv; + + if (!bytes) + return bytes; + + if (offset + bytes > m24lr->eeprom_size) + return -EINVAL; + + err = m24lr_read(m24lr, val, bytes, offset, true); + if (IS_ERR_VALUE(err)) + return err; + + return 0; +} + +static int m24lr_nvmem_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + ssize_t err; + struct m24lr *m24lr = priv; + + if (!bytes) + return -EINVAL; + + if (offset + bytes > m24lr->eeprom_size) + return -EINVAL; + + err = m24lr_write(m24lr, val, bytes, offset, true); + if (IS_ERR_VALUE(err)) + return err; + + return 0; +} + +static ssize_t m24lr_ctl_sss_read(struct file *filep, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t offset, size_t count) +{ + struct m24lr *m24lr = attr->private; + + if (!count) + return count; + + if (size_add(offset, count) > m24lr->sss_len) + return -EINVAL; + + return m24lr_read(m24lr, buf, count, offset, false); +} + +static ssize_t m24lr_ctl_sss_write(struct file *filep, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t offset, size_t count) +{ + struct m24lr *m24lr = attr->private; + + if (!count) + return -EINVAL; + + if (size_add(offset, count) > m24lr->sss_len) + return -EINVAL; + + return m24lr_write(m24lr, buf, count, offset, false); +} +static BIN_ATTR(sss, 0600, m24lr_ctl_sss_read, m24lr_ctl_sss_write, 0); + +static ssize_t new_pass_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return m24lr_write_pass(m24lr, buf, count, 7); +} +static DEVICE_ATTR_WO(new_pass); + +static ssize_t unlock_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return m24lr_write_pass(m24lr, buf, count, 9); +} +static DEVICE_ATTR_WO(unlock); + +static ssize_t uid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "%llx\n", m24lr->uid); +} +static DEVICE_ATTR_RO(uid); + +static ssize_t total_sectors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "%x\n", m24lr->sss_len); +} +static DEVICE_ATTR_RO(total_sectors); + +static struct attribute *m24lr_ctl_dev_attrs[] = { + &dev_attr_unlock.attr, + &dev_attr_new_pass.attr, + &dev_attr_uid.attr, + &dev_attr_total_sectors.attr, + NULL, +}; + +static const struct m24lr_chip *m24lr_get_chip(struct device *dev) +{ + const struct m24lr_chip *ret; + const struct i2c_device_id *id; + + id = i2c_match_id(m24lr_ids, to_i2c_client(dev)); + + if (dev->of_node && of_match_device(m24lr_of_match, dev)) + ret = of_device_get_match_data(dev); + else if (id) + ret = (void *)id->driver_data; + else + ret = acpi_device_get_match_data(dev); + + return ret; +} + +static int m24lr_probe(struct i2c_client *client) +{ + struct regmap_config eeprom_regmap_conf = {0}; + struct nvmem_config nvmem_conf = {0}; + struct device *dev = &client->dev; + struct i2c_client *eeprom_client; + const struct m24lr_chip *chip; + struct regmap *eeprom_regmap; + struct nvmem_device *nvmem; + struct regmap *ctl_regmap; + struct m24lr *m24lr; + u32 regs[2]; + long err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + chip = m24lr_get_chip(dev); + if (!chip) + return -ENODEV; + + m24lr = devm_kzalloc(dev, sizeof(struct m24lr), GFP_KERNEL); + if (!m24lr) + return -ENOMEM; + + err = device_property_read_u32_array(dev, "reg", regs, ARRAY_SIZE(regs)); + if (err) + return dev_err_probe(dev, err, "Failed to read 'reg' property\n"); + + /* Create a second I2C client for the eeprom interface */ + eeprom_client = devm_i2c_new_dummy_device(dev, client->adapter, regs[1]); + if (IS_ERR(eeprom_client)) + return dev_err_probe(dev, PTR_ERR(eeprom_client), + "Failed to create dummy I2C client for the EEPROM\n"); + + ctl_regmap = devm_regmap_init_i2c(client, &m24lr_ctl_regmap_conf); + if (IS_ERR(ctl_regmap)) + return dev_err_probe(dev, PTR_ERR(ctl_regmap), + "Failed to init regmap\n"); + + eeprom_regmap_conf.name = "m24lr_eeprom"; + eeprom_regmap_conf.reg_bits = 16; + eeprom_regmap_conf.val_bits = 8; + eeprom_regmap_conf.disable_locking = true; + eeprom_regmap_conf.max_register = chip->eeprom_size - 1; + + eeprom_regmap = devm_regmap_init_i2c(eeprom_client, + &eeprom_regmap_conf); + if (IS_ERR(eeprom_regmap)) + return dev_err_probe(dev, PTR_ERR(eeprom_regmap), + "Failed to init regmap\n"); + + mutex_init(&m24lr->lock); + m24lr->sss_len = chip->sss_len; + m24lr->page_size = chip->page_size; + m24lr->eeprom_size = chip->eeprom_size; + m24lr->eeprom_regmap = eeprom_regmap; + m24lr->ctl_regmap = ctl_regmap; + + nvmem_conf.dev = &eeprom_client->dev; + nvmem_conf.owner = THIS_MODULE; + nvmem_conf.type = NVMEM_TYPE_EEPROM; + nvmem_conf.reg_read = m24lr_nvmem_read; + nvmem_conf.reg_write = m24lr_nvmem_write; + nvmem_conf.size = chip->eeprom_size; + nvmem_conf.word_size = 1; + nvmem_conf.stride = 1; + nvmem_conf.priv = m24lr; + + nvmem = devm_nvmem_register(dev, &nvmem_conf); + if (IS_ERR(nvmem)) + return dev_err_probe(dev, PTR_ERR(nvmem), + "Failed to register nvmem\n"); + + i2c_set_clientdata(client, m24lr); + i2c_set_clientdata(eeprom_client, m24lr); + + bin_attr_sss.size = chip->sss_len; + bin_attr_sss.private = m24lr; + err = sysfs_create_bin_file(&dev->kobj, &bin_attr_sss); + if (err) + return dev_err_probe(dev, err, + "Failed to create sss bin file\n"); + + /* test by reading the uid, if success store it */ + err = m24lr_read_reg_le(m24lr, &m24lr->uid, 2324, sizeof(m24lr->uid)); + if (IS_ERR_VALUE(err)) + goto remove_bin_file; + + return 0; + +remove_bin_file: + sysfs_remove_bin_file(&dev->kobj, &bin_attr_sss); + + return err; +} + +static void m24lr_remove(struct i2c_client *client) +{ + sysfs_remove_bin_file(&client->dev.kobj, &bin_attr_sss); +} + +ATTRIBUTE_GROUPS(m24lr_ctl_dev); + +static struct i2c_driver m24lr_driver = { + .driver = { + .name = "m24lr", + .of_match_table = m24lr_of_match, + .dev_groups = m24lr_ctl_dev_groups, + }, + .probe = m24lr_probe, + .remove = m24lr_remove, + .id_table = m24lr_ids, +}; +module_i2c_driver(m24lr_driver); + +MODULE_AUTHOR("Abd-Alrhman Masalkhi"); +MODULE_DESCRIPTION("st m24lr control driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 53e88a1bc430..8e1d97873423 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -27,8 +27,7 @@ #define MDSP_DOMAIN_ID (1) #define SDSP_DOMAIN_ID (2) #define CDSP_DOMAIN_ID (3) -#define CDSP1_DOMAIN_ID (4) -#define FASTRPC_DEV_MAX 5 /* adsp, mdsp, slpi, cdsp, cdsp1 */ +#define GDSP_DOMAIN_ID (4) #define FASTRPC_MAX_SESSIONS 14 #define FASTRPC_MAX_VMIDS 16 #define FASTRPC_ALIGN 128 @@ -106,8 +105,6 @@ #define miscdev_to_fdevice(d) container_of(d, struct fastrpc_device, miscdev) -static const char *domains[FASTRPC_DEV_MAX] = { "adsp", "mdsp", - "sdsp", "cdsp", "cdsp1" }; struct fastrpc_phy_page { u64 addr; /* physical address */ u64 size; /* size of contiguous region */ @@ -1723,7 +1720,6 @@ static int fastrpc_get_info_from_kernel(struct fastrpc_ioctl_capability *cap, uint32_t attribute_id = cap->attribute_id; uint32_t *dsp_attributes; unsigned long flags; - uint32_t domain = cap->domain; int err; spin_lock_irqsave(&cctx->lock, flags); @@ -1741,7 +1737,7 @@ static int fastrpc_get_info_from_kernel(struct fastrpc_ioctl_capability *cap, err = fastrpc_get_info_from_dsp(fl, dsp_attributes, FASTRPC_MAX_DSP_ATTRIBUTES); if (err == DSP_UNSUPPORTED_API) { dev_info(&cctx->rpdev->dev, - "Warning: DSP capabilities not supported on domain: %d\n", domain); + "Warning: DSP capabilities not supported\n"); kfree(dsp_attributes); return -EOPNOTSUPP; } else if (err) { @@ -1769,17 +1765,6 @@ static int fastrpc_get_dsp_info(struct fastrpc_user *fl, char __user *argp) return -EFAULT; cap.capability = 0; - if (cap.domain >= FASTRPC_DEV_MAX) { - dev_err(&fl->cctx->rpdev->dev, "Error: Invalid domain id:%d, err:%d\n", - cap.domain, err); - return -ECHRNG; - } - - /* Fastrpc Capablities does not support modem domain */ - if (cap.domain == MDSP_DOMAIN_ID) { - dev_err(&fl->cctx->rpdev->dev, "Error: modem not supported %d\n", err); - return -ECHRNG; - } if (cap.attribute_id >= FASTRPC_MAX_DSP_ATTRIBUTES) { dev_err(&fl->cctx->rpdev->dev, "Error: invalid attribute: %d, err: %d\n", @@ -2255,6 +2240,22 @@ static int fastrpc_device_register(struct device *dev, struct fastrpc_channel_ct return err; } +static int fastrpc_get_domain_id(const char *domain) +{ + if (!strncmp(domain, "adsp", 4)) + return ADSP_DOMAIN_ID; + else if (!strncmp(domain, "cdsp", 4)) + return CDSP_DOMAIN_ID; + else if (!strncmp(domain, "mdsp", 4)) + return MDSP_DOMAIN_ID; + else if (!strncmp(domain, "sdsp", 4)) + return SDSP_DOMAIN_ID; + else if (!strncmp(domain, "gdsp", 4)) + return GDSP_DOMAIN_ID; + + return -EINVAL; +} + static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) { struct device *rdev = &rpdev->dev; @@ -2270,15 +2271,10 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) return err; } - for (i = 0; i < FASTRPC_DEV_MAX; i++) { - if (!strcmp(domains[i], domain)) { - domain_id = i; - break; - } - } + domain_id = fastrpc_get_domain_id(domain); if (domain_id < 0) { - dev_info(rdev, "FastRPC Invalid Domain ID %d\n", domain_id); + dev_info(rdev, "FastRPC Domain %s not supported\n", domain); return -EINVAL; } @@ -2325,21 +2321,21 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) case ADSP_DOMAIN_ID: case MDSP_DOMAIN_ID: case SDSP_DOMAIN_ID: - /* Unsigned PD offloading is only supported on CDSP and CDSP1 */ + /* Unsigned PD offloading is only supported on CDSP and GDSP */ data->unsigned_support = false; - err = fastrpc_device_register(rdev, data, secure_dsp, domains[domain_id]); + err = fastrpc_device_register(rdev, data, secure_dsp, domain); if (err) goto err_free_data; break; case CDSP_DOMAIN_ID: - case CDSP1_DOMAIN_ID: + case GDSP_DOMAIN_ID: data->unsigned_support = true; /* Create both device nodes so that we can allow both Signed and Unsigned PD */ - err = fastrpc_device_register(rdev, data, true, domains[domain_id]); + err = fastrpc_device_register(rdev, data, true, domain); if (err) goto err_free_data; - err = fastrpc_device_register(rdev, data, false, domains[domain_id]); + err = fastrpc_device_register(rdev, data, false, domain); if (err) goto err_deregister_fdev; break; diff --git a/drivers/misc/genwqe/card_ddcb.c b/drivers/misc/genwqe/card_ddcb.c index 500b1feaf1f6..fd7d5cd50d39 100644 --- a/drivers/misc/genwqe/card_ddcb.c +++ b/drivers/misc/genwqe/card_ddcb.c @@ -923,7 +923,7 @@ int __genwqe_execute_raw_ddcb(struct genwqe_dev *cd, } if (cmd->asv_length > DDCB_ASV_LENGTH) { dev_err(&pci_dev->dev, "[%s] err: wrong asv_length of %d\n", - __func__, cmd->asiv_length); + __func__, cmd->asv_length); return -EINVAL; } rc = __genwqe_enqueue_ddcb(cd, req, f_flags); diff --git a/drivers/misc/hisi_hikey_usb.c b/drivers/misc/hisi_hikey_usb.c index ffe7b945a298..2c6e448a47f1 100644 --- a/drivers/misc/hisi_hikey_usb.c +++ b/drivers/misc/hisi_hikey_usb.c @@ -18,6 +18,7 @@ #include <linux/property.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/usb/role.h> #define DEVICE_DRIVER_NAME "hisi_hikey_usb" @@ -67,7 +68,7 @@ static void hub_power_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, int value) if (ret) dev_err(hisi_hikey_usb->dev, "Can't switch regulator state to %s\n", - value ? "enabled" : "disabled"); + str_enabled_disabled(value)); } static void usb_switch_ctrl(struct hisi_hikey_usb *hisi_hikey_usb, diff --git a/drivers/misc/ibmasm/ibmasmfs.c b/drivers/misc/ibmasm/ibmasmfs.c index 5372ed2a363e..b26c930e3edb 100644 --- a/drivers/misc/ibmasm/ibmasmfs.c +++ b/drivers/misc/ibmasm/ibmasmfs.c @@ -525,15 +525,9 @@ static ssize_t remote_settings_file_write(struct file *file, const char __user * if (*offset != 0) return 0; - buff = kzalloc (count + 1, GFP_KERNEL); - if (!buff) - return -ENOMEM; - - - if (copy_from_user(buff, ubuff, count)) { - kfree(buff); - return -EFAULT; - } + buff = memdup_user_nul(ubuff, count); + if (IS_ERR(buff)) + return PTR_ERR(buff); value = simple_strtoul(buff, NULL, 10); writel(value, address); diff --git a/drivers/misc/lis3lv02d/Kconfig b/drivers/misc/lis3lv02d/Kconfig index 56005243a230..9d546a42a563 100644 --- a/drivers/misc/lis3lv02d/Kconfig +++ b/drivers/misc/lis3lv02d/Kconfig @@ -4,7 +4,7 @@ # config SENSORS_LIS3_SPI - tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" + tristate "STMicroelectronics LIS3LV02Dx three-axis digital accelerometer (SPI)" depends on !ACPI && SPI_MASTER && INPUT select SENSORS_LIS3LV02D help @@ -20,7 +20,7 @@ config SENSORS_LIS3_SPI is called lis3lv02d_spi. config SENSORS_LIS3_I2C - tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" + tristate "STMicroelectronics LIS3LV02Dx three-axis digital accelerometer (I2C)" depends on I2C && INPUT select SENSORS_LIS3LV02D help diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 90dba20b2de7..e6a1d3534663 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -386,7 +386,7 @@ static int mei_nfc_if_version(struct mei_cl *cl, ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(cmd), 0, MEI_CL_IO_TX_BLOCKING); if (ret < 0) { - dev_err(bus->dev, "Could not send IF version cmd ret = %d\n", ret); + dev_err(&bus->dev, "Could not send IF version cmd ret = %d\n", ret); return ret; } @@ -401,14 +401,14 @@ static int mei_nfc_if_version(struct mei_cl *cl, bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length, &vtag, 0, 0); if (bytes_recv < 0 || (size_t)bytes_recv < if_version_length) { - dev_err(bus->dev, "Could not read IF version ret = %d\n", bytes_recv); + dev_err(&bus->dev, "Could not read IF version ret = %d\n", bytes_recv); ret = -EIO; goto err; } memcpy(ver, reply->data, sizeof(*ver)); - dev_info(bus->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", + dev_info(&bus->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", ver->fw_ivn, ver->vendor_id, ver->radio_type); err: diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 09aae8f9d225..2c810ab12e62 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -650,7 +650,7 @@ EXPORT_SYMBOL_GPL(mei_cldev_enabled); */ static bool mei_cl_bus_module_get(struct mei_cl_device *cldev) { - return try_module_get(cldev->bus->dev->driver->owner); + return try_module_get(cldev->bus->parent->driver->owner); } /** @@ -660,7 +660,7 @@ static bool mei_cl_bus_module_get(struct mei_cl_device *cldev) */ static void mei_cl_bus_module_put(struct mei_cl_device *cldev) { - module_put(cldev->bus->dev->driver->owner); + module_put(cldev->bus->parent->driver->owner); } /** @@ -827,7 +827,7 @@ int mei_cldev_enable(struct mei_cl_device *cldev) ret = mei_cl_connect(cl, cldev->me_cl, NULL); if (ret < 0) { - dev_err(&cldev->dev, "cannot connect\n"); + dev_dbg(&cldev->dev, "cannot connect\n"); mei_cl_bus_vtag_free(cldev); } @@ -1298,16 +1298,20 @@ static const struct bus_type mei_cl_bus_type = { static struct mei_device *mei_dev_bus_get(struct mei_device *bus) { - if (bus) - get_device(bus->dev); + if (bus) { + get_device(&bus->dev); + get_device(bus->parent); + } return bus; } static void mei_dev_bus_put(struct mei_device *bus) { - if (bus) - put_device(bus->dev); + if (bus) { + put_device(bus->parent); + put_device(&bus->dev); + } } static void mei_cl_bus_dev_release(struct device *dev) @@ -1341,7 +1345,7 @@ static const struct device_type mei_cl_device_type = { static inline void mei_cl_bus_set_name(struct mei_cl_device *cldev) { dev_set_name(&cldev->dev, "%s-%pUl", - dev_name(cldev->bus->dev), + dev_name(cldev->bus->parent), mei_me_cl_uuid(cldev->me_cl)); } @@ -1370,7 +1374,7 @@ static struct mei_cl_device *mei_cl_bus_dev_alloc(struct mei_device *bus, } device_initialize(&cldev->dev); - cldev->dev.parent = bus->dev; + cldev->dev.parent = bus->parent; cldev->dev.bus = &mei_cl_bus_type; cldev->dev.type = &mei_cl_device_type; cldev->bus = mei_dev_bus_get(bus); @@ -1505,7 +1509,7 @@ static void mei_cl_bus_dev_init(struct mei_device *bus, WARN_ON(!mutex_is_locked(&bus->cl_bus_lock)); - dev_dbg(bus->dev, "initializing %pUl", mei_me_cl_uuid(me_cl)); + dev_dbg(&bus->dev, "initializing %pUl", mei_me_cl_uuid(me_cl)); if (me_cl->bus_added) return; @@ -1556,7 +1560,7 @@ static void mei_cl_bus_rescan(struct mei_device *bus) } mutex_unlock(&bus->cl_bus_lock); - dev_dbg(bus->dev, "rescan end"); + dev_dbg(&bus->dev, "rescan end"); } void mei_cl_bus_rescan_work(struct work_struct *work) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 3db07d2a881f..159e8b841564 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -262,7 +262,7 @@ void mei_me_cl_rm_by_uuid(struct mei_device *dev, const uuid_le *uuid) { struct mei_me_client *me_cl; - dev_dbg(dev->dev, "remove %pUl\n", uuid); + dev_dbg(&dev->dev, "remove %pUl\n", uuid); down_write(&dev->me_clients_rwsem); me_cl = __mei_me_cl_by_uuid(dev, uuid); @@ -635,12 +635,12 @@ int mei_cl_link(struct mei_cl *cl) id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX); if (id >= MEI_CLIENTS_MAX) { - dev_err(dev->dev, "id exceeded %d", MEI_CLIENTS_MAX); + dev_err(&dev->dev, "id exceeded %d", MEI_CLIENTS_MAX); return -EMFILE; } if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { - dev_err(dev->dev, "open_handle_count exceeded %d", + dev_err(&dev->dev, "open_handle_count exceeded %d", MEI_MAX_OPEN_HANDLE_COUNT); return -EMFILE; } @@ -709,9 +709,9 @@ void mei_host_client_init(struct mei_device *dev) schedule_work(&dev->bus_rescan_work); - pm_runtime_mark_last_busy(dev->dev); - dev_dbg(dev->dev, "rpm: autosuspend\n"); - pm_request_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + dev_dbg(&dev->dev, "rpm: autosuspend\n"); + pm_request_autosuspend(dev->parent); } /** @@ -724,12 +724,12 @@ bool mei_hbuf_acquire(struct mei_device *dev) { if (mei_pg_state(dev) == MEI_PG_ON || mei_pg_in_transition(dev)) { - dev_dbg(dev->dev, "device is in pg\n"); + dev_dbg(&dev->dev, "device is in pg\n"); return false; } if (!dev->hbuf_is_ready) { - dev_dbg(dev->dev, "hbuf is not ready\n"); + dev_dbg(&dev->dev, "hbuf is not ready\n"); return false; } @@ -981,9 +981,9 @@ int mei_cl_disconnect(struct mei_cl *cl) return 0; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -991,8 +991,8 @@ int mei_cl_disconnect(struct mei_cl *cl) rets = __mei_cl_disconnect(cl); cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); return rets; } @@ -1118,9 +1118,9 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, goto nortpm; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); goto nortpm; } @@ -1167,8 +1167,8 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, rets = cl->status; out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); @@ -1517,9 +1517,9 @@ int mei_cl_notify_request(struct mei_cl *cl, if (!mei_cl_is_connected(cl)) return -ENODEV; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -1554,8 +1554,8 @@ int mei_cl_notify_request(struct mei_cl *cl, out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); return rets; @@ -1683,9 +1683,9 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp) mei_cl_set_read_by_fp(cl, fp); - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); goto nortpm; } @@ -1702,8 +1702,8 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp) out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); nortpm: if (rets) mei_io_cb_free(cb); @@ -1972,9 +1972,9 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, unsigned long time blocking = cb->blocking; data = buf->data; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %zd\n", rets); goto free; } @@ -2092,8 +2092,8 @@ out: rets = buf_len; err: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); free: mei_io_cb_free(cb); @@ -2119,8 +2119,8 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) if (waitqueue_active(&cl->tx_wait)) { wake_up_interruptible(&cl->tx_wait); } else { - pm_runtime_mark_last_busy(dev->dev); - pm_request_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_request_autosuspend(dev->parent); } break; @@ -2251,7 +2251,7 @@ int mei_cl_irq_dma_unmap(struct mei_cl *cl, struct mei_cl_cb *cb, static int mei_cl_dma_alloc(struct mei_cl *cl, u8 buf_id, size_t size) { - cl->dma.vaddr = dmam_alloc_coherent(cl->dev->dev, size, + cl->dma.vaddr = dmam_alloc_coherent(&cl->dev->dev, size, &cl->dma.daddr, GFP_KERNEL); if (!cl->dma.vaddr) return -ENOMEM; @@ -2265,7 +2265,7 @@ static int mei_cl_dma_alloc(struct mei_cl *cl, u8 buf_id, size_t size) static void mei_cl_dma_free(struct mei_cl *cl) { cl->dma.buffer_id = 0; - dmam_free_coherent(cl->dev->dev, + dmam_free_coherent(&cl->dev->dev, cl->dma.size, cl->dma.vaddr, cl->dma.daddr); cl->dma.size = 0; cl->dma.vaddr = NULL; @@ -2321,16 +2321,16 @@ int mei_cl_dma_alloc_and_map(struct mei_cl *cl, const struct file *fp, return -EPROTO; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } rets = mei_cl_dma_alloc(cl, buffer_id, size); if (rets) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); return rets; } @@ -2366,8 +2366,8 @@ out: mei_cl_dma_free(cl); cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); return rets; @@ -2406,9 +2406,9 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp) if (!cl->dma_mapped) return -EPROTO; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -2444,8 +2444,8 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp) mei_cl_dma_free(cl); out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); return rets; diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 01ed26a148c4..031114478bcb 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -275,12 +275,12 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp); #define MEI_CL_PRM(cl) (cl)->host_client_id, mei_cl_me_id(cl) #define cl_dbg(dev, cl, format, arg...) \ - dev_dbg((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + dev_dbg(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) #define cl_warn(dev, cl, format, arg...) \ - dev_warn((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + dev_warn(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) #define cl_err(dev, cl, format, arg...) \ - dev_err((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + dev_err(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) #endif /* _MEI_CLIENT_H_ */ diff --git a/drivers/misc/mei/dma-ring.c b/drivers/misc/mei/dma-ring.c index 651e77ef82bd..6277c4a5b0fd 100644 --- a/drivers/misc/mei/dma-ring.c +++ b/drivers/misc/mei/dma-ring.c @@ -30,7 +30,7 @@ static int mei_dmam_dscr_alloc(struct mei_device *dev, if (dscr->vaddr) return 0; - dscr->vaddr = dmam_alloc_coherent(dev->dev, dscr->size, &dscr->daddr, + dscr->vaddr = dmam_alloc_coherent(dev->parent, dscr->size, &dscr->daddr, GFP_KERNEL); if (!dscr->vaddr) return -ENOMEM; @@ -50,7 +50,7 @@ static void mei_dmam_dscr_free(struct mei_device *dev, if (!dscr->vaddr) return; - dmam_free_coherent(dev->dev, dscr->size, dscr->vaddr, dscr->daddr); + dmam_free_coherent(dev->parent, dscr->size, dscr->vaddr, dscr->daddr); dscr->vaddr = NULL; } @@ -177,7 +177,7 @@ void mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len) if (WARN_ON(!ctrl)) return; - dev_dbg(dev->dev, "reading from dma %u bytes\n", len); + dev_dbg(&dev->dev, "reading from dma %u bytes\n", len); if (!len) return; @@ -254,7 +254,7 @@ void mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len) if (WARN_ON(!ctrl)) return; - dev_dbg(dev->dev, "writing to dma %u bytes\n", len); + dev_dbg(&dev->dev, "writing to dma %u bytes\n", len); hbuf_depth = mei_dma_ring_hbuf_depth(dev); wr_idx = READ_ONCE(ctrl->hbuf_wr_idx) & (hbuf_depth - 1); slots = mei_data2slots(len); diff --git a/drivers/misc/mei/gsc-me.c b/drivers/misc/mei/gsc-me.c index 5a8c26c3df13..93cba090ea08 100644 --- a/drivers/misc/mei/gsc-me.c +++ b/drivers/misc/mei/gsc-me.c @@ -106,11 +106,15 @@ static int mei_gsc_probe(struct auxiliary_device *aux_dev, } } + ret = mei_register(dev, device); + if (ret) + goto deinterrupt; + pm_runtime_get_noresume(device); pm_runtime_set_active(device); pm_runtime_enable(device); - /* Continue to char device setup in spite of firmware handshake failure. + /* Continue in spite of firmware handshake failure. * In order to provide access to the firmware status registers to the user * space via sysfs. */ @@ -120,18 +124,12 @@ static int mei_gsc_probe(struct auxiliary_device *aux_dev, pm_runtime_set_autosuspend_delay(device, MEI_GSC_RPM_TIMEOUT); pm_runtime_use_autosuspend(device); - ret = mei_register(dev, device); - if (ret) - goto register_err; - pm_runtime_put_noidle(device); return 0; -register_err: - mei_stop(dev); +deinterrupt: if (!mei_me_hw_use_polling(hw)) devm_free_irq(device, hw->irq, dev); - err: dev_err(device, "probe failed: %d\n", ret); dev_set_drvdata(device, NULL); @@ -152,13 +150,13 @@ static void mei_gsc_remove(struct auxiliary_device *aux_dev) if (mei_me_hw_use_polling(hw)) kthread_stop(hw->polling_thread); - mei_deregister(dev); - pm_runtime_disable(&aux_dev->dev); mei_disable_interrupts(dev); if (!mei_me_hw_use_polling(hw)) devm_free_irq(&aux_dev->dev, hw->irq, dev); + + mei_deregister(dev); } static int __maybe_unused mei_gsc_pm_suspend(struct device *device) @@ -252,7 +250,7 @@ static int __maybe_unused mei_gsc_pm_runtime_resume(struct device *device) irq_ret = mei_me_irq_thread_handler(1, dev); if (irq_ret != IRQ_HANDLED) - dev_err(dev->dev, "thread handler fail %d\n", irq_ret); + dev_err(&dev->dev, "thread handler fail %d\n", irq_ret); return 0; } diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 4fe9a2752d43..ccd9df5d1c7d 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -239,7 +239,7 @@ int mei_hbm_start_wait(struct mei_device *dev) if (ret == 0 && (dev->hbm_state <= MEI_HBM_STARTING)) { dev->hbm_state = MEI_HBM_IDLE; - dev_err(dev->dev, "waiting for mei start failed\n"); + dev_err(&dev->dev, "waiting for mei start failed\n"); return -ETIME; } return 0; @@ -271,8 +271,7 @@ int mei_hbm_start_req(struct mei_device *dev) dev->hbm_state = MEI_HBM_IDLE; ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "version message write failed: ret = %d\n", - ret); + dev_err(&dev->dev, "version message write failed: ret = %d\n", ret); return ret; } @@ -312,8 +311,7 @@ static int mei_hbm_dma_setup_req(struct mei_device *dev) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "dma setup request write failed: ret = %d.\n", - ret); + dev_err(&dev->dev, "dma setup request write failed: ret = %d.\n", ret); return ret; } @@ -351,8 +349,7 @@ static int mei_hbm_capabilities_req(struct mei_device *dev) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, - "capabilities request write failed: ret = %d.\n", ret); + dev_err(&dev->dev, "capabilities request write failed: ret = %d.\n", ret); return ret; } @@ -386,8 +383,7 @@ static int mei_hbm_enum_clients_req(struct mei_device *dev) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "enumeration request write failed: ret = %d.\n", - ret); + dev_err(&dev->dev, "enumeration request write failed: ret = %d.\n", ret); return ret; } dev->hbm_state = MEI_HBM_ENUM_CLIENTS; @@ -443,7 +439,7 @@ static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status) struct hbm_add_client_response resp; int ret; - dev_dbg(dev->dev, "adding client response\n"); + dev_dbg(&dev->dev, "adding client response\n"); mei_hbm_hdr(&mei_hdr, sizeof(resp)); @@ -454,8 +450,7 @@ static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status) ret = mei_hbm_write_message(dev, &mei_hdr, &resp); if (ret) - dev_err(dev->dev, "add client response write failed: ret = %d\n", - ret); + dev_err(&dev->dev, "add client response write failed: ret = %d\n", ret); return ret; } @@ -752,7 +747,7 @@ static int mei_hbm_prop_req(struct mei_device *dev, unsigned long start_idx) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "properties request write failed: ret = %d\n", + dev_err(&dev->dev, "properties request write failed: ret = %d\n", ret); return ret; } @@ -788,7 +783,7 @@ int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) - dev_err(dev->dev, "power gate command write failed.\n"); + dev_err(&dev->dev, "power gate command write failed.\n"); return ret; } EXPORT_SYMBOL_GPL(mei_hbm_pg); @@ -847,7 +842,7 @@ static int mei_hbm_add_single_tx_flow_ctrl_creds(struct mei_device *dev, me_cl = mei_me_cl_by_id(dev, fctrl->me_addr); if (!me_cl) { - dev_err(dev->dev, "no such me client %d\n", fctrl->me_addr); + dev_err(&dev->dev, "no such me client %d\n", fctrl->me_addr); return -ENOENT; } @@ -857,7 +852,7 @@ static int mei_hbm_add_single_tx_flow_ctrl_creds(struct mei_device *dev, } me_cl->tx_flow_ctrl_creds++; - dev_dbg(dev->dev, "recv flow ctrl msg ME %d (single) creds = %d.\n", + dev_dbg(&dev->dev, "recv flow ctrl msg ME %d (single) creds = %d.\n", fctrl->me_addr, me_cl->tx_flow_ctrl_creds); rets = 0; @@ -1085,7 +1080,7 @@ static int mei_hbm_pg_enter_res(struct mei_device *dev) { if (mei_pg_state(dev) != MEI_PG_OFF || dev->pg_event != MEI_PG_EVENT_WAIT) { - dev_err(dev->dev, "hbm: pg entry response: state mismatch [%s, %d]\n", + dev_err(&dev->dev, "hbm: pg entry response: state mismatch [%s, %d]\n", mei_pg_state_str(mei_pg_state(dev)), dev->pg_event); return -EPROTO; } @@ -1103,7 +1098,7 @@ static int mei_hbm_pg_enter_res(struct mei_device *dev) */ void mei_hbm_pg_resume(struct mei_device *dev) { - pm_request_resume(dev->dev); + pm_request_resume(dev->parent); } EXPORT_SYMBOL_GPL(mei_hbm_pg_resume); @@ -1119,7 +1114,7 @@ static int mei_hbm_pg_exit_res(struct mei_device *dev) if (mei_pg_state(dev) != MEI_PG_ON || (dev->pg_event != MEI_PG_EVENT_WAIT && dev->pg_event != MEI_PG_EVENT_IDLE)) { - dev_err(dev->dev, "hbm: pg exit response: state mismatch [%s, %d]\n", + dev_err(&dev->dev, "hbm: pg exit response: state mismatch [%s, %d]\n", mei_pg_state_str(mei_pg_state(dev)), dev->pg_event); return -EPROTO; } @@ -1276,19 +1271,19 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) * hbm is put to idle during system reset */ if (dev->hbm_state == MEI_HBM_IDLE) { - dev_dbg(dev->dev, "hbm: state is idle ignore spurious messages\n"); + dev_dbg(&dev->dev, "hbm: state is idle ignore spurious messages\n"); return 0; } switch (mei_msg->hbm_cmd) { case HOST_START_RES_CMD: - dev_dbg(dev->dev, "hbm: start: response message received.\n"); + dev_dbg(&dev->dev, "hbm: start: response message received.\n"); dev->init_clients_timer = 0; version_res = (struct hbm_host_version_response *)mei_msg; - dev_dbg(dev->dev, "HBM VERSION: DRIVER=%02d:%02d DEVICE=%02d:%02d\n", + dev_dbg(&dev->dev, "HBM VERSION: DRIVER=%02d:%02d DEVICE=%02d:%02d\n", HBM_MAJOR_VERSION, HBM_MINOR_VERSION, version_res->me_max_version.major_version, version_res->me_max_version.minor_version); @@ -1304,11 +1299,11 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) } if (!mei_hbm_version_is_supported(dev)) { - dev_warn(dev->dev, "hbm: start: version mismatch - stopping the driver.\n"); + dev_warn(&dev->dev, "hbm: start: version mismatch - stopping the driver.\n"); dev->hbm_state = MEI_HBM_STOPPED; if (mei_hbm_stop_req(dev)) { - dev_err(dev->dev, "hbm: start: failed to send stop request\n"); + dev_err(&dev->dev, "hbm: start: failed to send stop request\n"); return -EIO; } break; @@ -1320,10 +1315,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->hbm_state != MEI_HBM_STARTING) { if (dev->dev_state == MEI_DEV_POWER_DOWN || dev->dev_state == MEI_DEV_POWERING_DOWN) { - dev_dbg(dev->dev, "hbm: start: on shutdown, ignoring\n"); + dev_dbg(&dev->dev, "hbm: start: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: start: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: start: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1337,7 +1332,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) if (dev->hbm_f_dr_supported) { if (mei_dmam_ring_alloc(dev)) - dev_info(dev->dev, "running w/o dma ring\n"); + dev_info(&dev->dev, "running w/o dma ring\n"); if (mei_dma_ring_is_allocated(dev)) { if (mei_hbm_dma_setup_req(dev)) return -EIO; @@ -1357,7 +1352,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case MEI_HBM_CAPABILITIES_RES_CMD: - dev_dbg(dev->dev, "hbm: capabilities response: message received.\n"); + dev_dbg(&dev->dev, "hbm: capabilities response: message received.\n"); dev->init_clients_timer = 0; @@ -1365,10 +1360,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->hbm_state != MEI_HBM_CAP_SETUP) { if (dev->dev_state == MEI_DEV_POWER_DOWN || dev->dev_state == MEI_DEV_POWERING_DOWN) { - dev_dbg(dev->dev, "hbm: capabilities response: on shutdown, ignoring\n"); + dev_dbg(&dev->dev, "hbm: capabilities response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: capabilities response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: capabilities response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1384,7 +1379,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) if (dev->hbm_f_dr_supported) { if (mei_dmam_ring_alloc(dev)) - dev_info(dev->dev, "running w/o dma ring\n"); + dev_info(&dev->dev, "running w/o dma ring\n"); if (mei_dma_ring_is_allocated(dev)) { if (mei_hbm_dma_setup_req(dev)) return -EIO; @@ -1400,7 +1395,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case MEI_HBM_DMA_SETUP_RES_CMD: - dev_dbg(dev->dev, "hbm: dma setup response: message received.\n"); + dev_dbg(&dev->dev, "hbm: dma setup response: message received.\n"); dev->init_clients_timer = 0; @@ -1408,10 +1403,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->hbm_state != MEI_HBM_DR_SETUP) { if (dev->dev_state == MEI_DEV_POWER_DOWN || dev->dev_state == MEI_DEV_POWERING_DOWN) { - dev_dbg(dev->dev, "hbm: dma setup response: on shutdown, ignoring\n"); + dev_dbg(&dev->dev, "hbm: dma setup response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: dma setup response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: dma setup response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1422,9 +1417,9 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) u8 status = dma_setup_res->status; if (status == MEI_HBMS_NOT_ALLOWED) { - dev_dbg(dev->dev, "hbm: dma setup not allowed\n"); + dev_dbg(&dev->dev, "hbm: dma setup not allowed\n"); } else { - dev_info(dev->dev, "hbm: dma setup response: failure = %d %s\n", + dev_info(&dev->dev, "hbm: dma setup response: failure = %d %s\n", status, mei_hbm_status_str(status)); } @@ -1437,38 +1432,38 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case CLIENT_CONNECT_RES_CMD: - dev_dbg(dev->dev, "hbm: client connect response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client connect response: message received.\n"); mei_hbm_cl_res(dev, cl_cmd, MEI_FOP_CONNECT); break; case CLIENT_DISCONNECT_RES_CMD: - dev_dbg(dev->dev, "hbm: client disconnect response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client disconnect response: message received.\n"); mei_hbm_cl_res(dev, cl_cmd, MEI_FOP_DISCONNECT); break; case MEI_FLOW_CONTROL_CMD: - dev_dbg(dev->dev, "hbm: client flow control response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client flow control response: message received.\n"); fctrl = (struct hbm_flow_control *)mei_msg; mei_hbm_cl_tx_flow_ctrl_creds_res(dev, fctrl); break; case MEI_PG_ISOLATION_ENTRY_RES_CMD: - dev_dbg(dev->dev, "hbm: power gate isolation entry response received\n"); + dev_dbg(&dev->dev, "hbm: power gate isolation entry response received\n"); ret = mei_hbm_pg_enter_res(dev); if (ret) return ret; break; case MEI_PG_ISOLATION_EXIT_REQ_CMD: - dev_dbg(dev->dev, "hbm: power gate isolation exit request received\n"); + dev_dbg(&dev->dev, "hbm: power gate isolation exit request received\n"); ret = mei_hbm_pg_exit_res(dev); if (ret) return ret; break; case HOST_CLIENT_PROPERTIES_RES_CMD: - dev_dbg(dev->dev, "hbm: properties response: message received.\n"); + dev_dbg(&dev->dev, "hbm: properties response: message received.\n"); dev->init_clients_timer = 0; @@ -1476,10 +1471,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->hbm_state != MEI_HBM_CLIENT_PROPERTIES) { if (dev->dev_state == MEI_DEV_POWER_DOWN || dev->dev_state == MEI_DEV_POWERING_DOWN) { - dev_dbg(dev->dev, "hbm: properties response: on shutdown, ignoring\n"); + dev_dbg(&dev->dev, "hbm: properties response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: properties response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: properties response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1487,10 +1482,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) props_res = (struct hbm_props_response *)mei_msg; if (props_res->status == MEI_HBMS_CLIENT_NOT_FOUND) { - dev_dbg(dev->dev, "hbm: properties response: %d CLIENT_NOT_FOUND\n", + dev_dbg(&dev->dev, "hbm: properties response: %d CLIENT_NOT_FOUND\n", props_res->me_addr); } else if (props_res->status) { - dev_err(dev->dev, "hbm: properties response: wrong status = %d %s\n", + dev_err(&dev->dev, "hbm: properties response: wrong status = %d %s\n", props_res->status, mei_hbm_status_str(props_res->status)); return -EPROTO; @@ -1505,7 +1500,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case HOST_ENUM_RES_CMD: - dev_dbg(dev->dev, "hbm: enumeration response: message received\n"); + dev_dbg(&dev->dev, "hbm: enumeration response: message received\n"); dev->init_clients_timer = 0; @@ -1519,10 +1514,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->hbm_state != MEI_HBM_ENUM_CLIENTS) { if (dev->dev_state == MEI_DEV_POWER_DOWN || dev->dev_state == MEI_DEV_POWERING_DOWN) { - dev_dbg(dev->dev, "hbm: enumeration response: on shutdown, ignoring\n"); + dev_dbg(&dev->dev, "hbm: enumeration response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: enumeration response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: enumeration response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1536,77 +1531,77 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case HOST_STOP_RES_CMD: - dev_dbg(dev->dev, "hbm: stop response: message received\n"); + dev_dbg(&dev->dev, "hbm: stop response: message received\n"); dev->init_clients_timer = 0; if (dev->hbm_state != MEI_HBM_STOPPED) { - dev_err(dev->dev, "hbm: stop response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: stop response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } mei_set_devstate(dev, MEI_DEV_POWER_DOWN); - dev_info(dev->dev, "hbm: stop response: resetting.\n"); + dev_info(&dev->dev, "hbm: stop response: resetting.\n"); /* force the reset */ return -EPROTO; case CLIENT_DISCONNECT_REQ_CMD: - dev_dbg(dev->dev, "hbm: disconnect request: message received\n"); + dev_dbg(&dev->dev, "hbm: disconnect request: message received\n"); disconnect_req = (struct hbm_client_connect_request *)mei_msg; mei_hbm_fw_disconnect_req(dev, disconnect_req); break; case ME_STOP_REQ_CMD: - dev_dbg(dev->dev, "hbm: stop request: message received\n"); + dev_dbg(&dev->dev, "hbm: stop request: message received\n"); dev->hbm_state = MEI_HBM_STOPPED; if (mei_hbm_stop_req(dev)) { - dev_err(dev->dev, "hbm: stop request: failed to send stop request\n"); + dev_err(&dev->dev, "hbm: stop request: failed to send stop request\n"); return -EIO; } break; case MEI_HBM_ADD_CLIENT_REQ_CMD: - dev_dbg(dev->dev, "hbm: add client request received\n"); + dev_dbg(&dev->dev, "hbm: add client request received\n"); /* * after the host receives the enum_resp * message clients may be added or removed */ if (dev->hbm_state <= MEI_HBM_ENUM_CLIENTS || dev->hbm_state >= MEI_HBM_STOPPED) { - dev_err(dev->dev, "hbm: add client: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: add client: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } add_cl_req = (struct hbm_add_client_request *)mei_msg; ret = mei_hbm_fw_add_cl_req(dev, add_cl_req); if (ret) { - dev_err(dev->dev, "hbm: add client: failed to send response %d\n", + dev_err(&dev->dev, "hbm: add client: failed to send response %d\n", ret); return -EIO; } - dev_dbg(dev->dev, "hbm: add client request processed\n"); + dev_dbg(&dev->dev, "hbm: add client request processed\n"); break; case MEI_HBM_NOTIFY_RES_CMD: - dev_dbg(dev->dev, "hbm: notify response received\n"); + dev_dbg(&dev->dev, "hbm: notify response received\n"); mei_hbm_cl_res(dev, cl_cmd, notify_res_to_fop(cl_cmd)); break; case MEI_HBM_NOTIFICATION_CMD: - dev_dbg(dev->dev, "hbm: notification\n"); + dev_dbg(&dev->dev, "hbm: notification\n"); mei_hbm_cl_notify(dev, cl_cmd); break; case MEI_HBM_CLIENT_DMA_MAP_RES_CMD: - dev_dbg(dev->dev, "hbm: client dma map response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client dma map response: message received.\n"); client_dma_res = (struct hbm_client_dma_response *)mei_msg; mei_hbm_cl_dma_map_res(dev, client_dma_res); break; case MEI_HBM_CLIENT_DMA_UNMAP_RES_CMD: - dev_dbg(dev->dev, "hbm: client dma unmap response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client dma unmap response: message received.\n"); client_dma_res = (struct hbm_client_dma_response *)mei_msg; mei_hbm_cl_dma_unmap_res(dev, client_dma_res); break; diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index d11a0740b47c..d4612c659784 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -84,7 +84,7 @@ static inline u32 mei_me_mecsr_read(const struct mei_device *dev) u32 reg; reg = mei_me_reg_read(to_me_hw(dev), ME_CSR_HA); - trace_mei_reg_read(dev->dev, "ME_CSR_HA", ME_CSR_HA, reg); + trace_mei_reg_read(&dev->dev, "ME_CSR_HA", ME_CSR_HA, reg); return reg; } @@ -101,7 +101,7 @@ static inline u32 mei_hcsr_read(const struct mei_device *dev) u32 reg; reg = mei_me_reg_read(to_me_hw(dev), H_CSR); - trace_mei_reg_read(dev->dev, "H_CSR", H_CSR, reg); + trace_mei_reg_read(&dev->dev, "H_CSR", H_CSR, reg); return reg; } @@ -114,7 +114,7 @@ static inline u32 mei_hcsr_read(const struct mei_device *dev) */ static inline void mei_hcsr_write(struct mei_device *dev, u32 reg) { - trace_mei_reg_write(dev->dev, "H_CSR", H_CSR, reg); + trace_mei_reg_write(&dev->dev, "H_CSR", H_CSR, reg); mei_me_reg_write(to_me_hw(dev), H_CSR, reg); } @@ -156,7 +156,7 @@ static inline u32 mei_me_d0i3c_read(const struct mei_device *dev) u32 reg; reg = mei_me_reg_read(to_me_hw(dev), H_D0I3C); - trace_mei_reg_read(dev->dev, "H_D0I3C", H_D0I3C, reg); + trace_mei_reg_read(&dev->dev, "H_D0I3C", H_D0I3C, reg); return reg; } @@ -169,7 +169,7 @@ static inline u32 mei_me_d0i3c_read(const struct mei_device *dev) */ static inline void mei_me_d0i3c_write(struct mei_device *dev, u32 reg) { - trace_mei_reg_write(dev->dev, "H_D0I3C", H_D0I3C, reg); + trace_mei_reg_write(&dev->dev, "H_D0I3C", H_D0I3C, reg); mei_me_reg_write(to_me_hw(dev), H_D0I3C, reg); } @@ -189,7 +189,7 @@ static int mei_me_trc_status(struct mei_device *dev, u32 *trc) return -EOPNOTSUPP; *trc = mei_me_reg_read(hw, ME_TRC); - trace_mei_reg_read(dev->dev, "ME_TRC", ME_TRC, *trc); + trace_mei_reg_read(&dev->dev, "ME_TRC", ME_TRC, *trc); return 0; } @@ -217,7 +217,7 @@ static int mei_me_fw_status(struct mei_device *dev, for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { ret = hw->read_fws(dev, fw_src->status[i], &fw_status->status[i]); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_X", + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_X", fw_src->status[i], fw_status->status[i]); if (ret) @@ -251,7 +251,7 @@ static int mei_me_hw_config(struct mei_device *dev) reg = 0; hw->read_fws(dev, PCI_CFG_HFS_1, ®); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg); + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg); hw->d0i3_supported = ((reg & PCI_CFG_HFS_1_D0I3_MSK) == PCI_CFG_HFS_1_D0I3_MSK); @@ -447,7 +447,7 @@ static void mei_gsc_pxp_check(struct mei_device *dev) return; hw->read_fws(dev, PCI_CFG_HFS_5, &fwsts5); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_5", PCI_CFG_HFS_5, fwsts5); + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_5", PCI_CFG_HFS_5, fwsts5); if ((fwsts5 & GSC_CFG_HFS_5_BOOT_TYPE_MSK) == GSC_CFG_HFS_5_BOOT_TYPE_PXP) { if (dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_DEFAULT) @@ -460,10 +460,10 @@ static void mei_gsc_pxp_check(struct mei_device *dev) return; if ((fwsts5 & GSC_CFG_HFS_5_BOOT_TYPE_MSK) == GSC_CFG_HFS_5_BOOT_TYPE_PXP) { - dev_dbg(dev->dev, "pxp mode is ready 0x%08x\n", fwsts5); + dev_dbg(&dev->dev, "pxp mode is ready 0x%08x\n", fwsts5); dev->pxp_mode = MEI_DEV_PXP_READY; } else { - dev_dbg(dev->dev, "pxp mode is not ready 0x%08x\n", fwsts5); + dev_dbg(&dev->dev, "pxp mode is not ready 0x%08x\n", fwsts5); } } @@ -482,7 +482,7 @@ static int mei_me_hw_ready_wait(struct mei_device *dev) dev->timeouts.hw_ready); mutex_lock(&dev->device_lock); if (!dev->recvd_hw_ready) { - dev_err(dev->dev, "wait hw ready failed\n"); + dev_err(&dev->dev, "wait hw ready failed\n"); return -ETIME; } @@ -494,43 +494,6 @@ static int mei_me_hw_ready_wait(struct mei_device *dev) } /** - * mei_me_check_fw_reset - check for the firmware reset error and exception conditions - * - * @dev: mei device - */ -static void mei_me_check_fw_reset(struct mei_device *dev) -{ - struct mei_fw_status fw_status; - char fw_sts_str[MEI_FW_STATUS_STR_SZ] = {0}; - int ret; - u32 fw_pm_event = 0; - - if (!dev->saved_fw_status_flag) - goto end; - - if (dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_PERFORMED) { - ret = mei_fw_status(dev, &fw_status); - if (!ret) { - fw_pm_event = fw_status.status[1] & PCI_CFG_HFS_2_PM_EVENT_MASK; - if (fw_pm_event != PCI_CFG_HFS_2_PM_CMOFF_TO_CMX_ERROR && - fw_pm_event != PCI_CFG_HFS_2_PM_CM_RESET_ERROR) - goto end; - } else { - dev_err(dev->dev, "failed to read firmware status: %d\n", ret); - } - } - - mei_fw_status2str(&dev->saved_fw_status, fw_sts_str, sizeof(fw_sts_str)); - dev_warn(dev->dev, "unexpected reset: fw_pm_event = 0x%x, dev_state = %u fw status = %s\n", - fw_pm_event, dev->saved_dev_state, fw_sts_str); - -end: - if (dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_PERFORMED) - dev->gsc_reset_to_pxp = MEI_DEV_RESET_TO_PXP_DONE; - dev->saved_fw_status_flag = false; -} - -/** * mei_me_hw_start - hw start routine * * @dev: mei device @@ -540,11 +503,12 @@ static int mei_me_hw_start(struct mei_device *dev) { int ret = mei_me_hw_ready_wait(dev); - if (kind_is_gsc(dev) || kind_is_gscfi(dev)) - mei_me_check_fw_reset(dev); + if ((kind_is_gsc(dev) || kind_is_gscfi(dev)) && + dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_PERFORMED) + dev->gsc_reset_to_pxp = MEI_DEV_RESET_TO_PXP_DONE; if (ret) return ret; - dev_dbg(dev->dev, "hw is ready\n"); + dev_dbg(&dev->dev, "hw is ready\n"); mei_me_host_set_ready(dev); return ret; @@ -644,14 +608,14 @@ static int mei_me_hbuf_write(struct mei_device *dev, return -EINVAL; if (!data && data_len) { - dev_err(dev->dev, "wrong parameters null data with data_len = %zu\n", data_len); + dev_err(&dev->dev, "wrong parameters null data with data_len = %zu\n", data_len); return -EINVAL; } - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); + dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); empty_slots = mei_hbuf_empty_slots(dev); - dev_dbg(dev->dev, "empty slots = %d.\n", empty_slots); + dev_dbg(&dev->dev, "empty slots = %d.\n", empty_slots); if (empty_slots < 0) return -EOVERFLOW; @@ -706,7 +670,7 @@ static int mei_me_count_full_read_slots(struct mei_device *dev) if (filled_slots > buffer_depth) return -EOVERFLOW; - dev_dbg(dev->dev, "filled_slots =%08x\n", filled_slots); + dev_dbg(&dev->dev, "filled_slots =%08x\n", filled_slots); return (int)filled_slots; } @@ -748,11 +712,11 @@ static void mei_me_pg_set(struct mei_device *dev) u32 reg; reg = mei_me_reg_read(hw, H_HPG_CSR); - trace_mei_reg_read(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_read(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); reg |= H_HPG_CSR_PGI; - trace_mei_reg_write(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_write(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); mei_me_reg_write(hw, H_HPG_CSR, reg); } @@ -767,13 +731,13 @@ static void mei_me_pg_unset(struct mei_device *dev) u32 reg; reg = mei_me_reg_read(hw, H_HPG_CSR); - trace_mei_reg_read(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_read(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); WARN(!(reg & H_HPG_CSR_PGI), "PGI is not set\n"); reg |= H_HPG_CSR_PGIHEXR; - trace_mei_reg_write(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_write(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); mei_me_reg_write(hw, H_HPG_CSR, reg); } @@ -905,7 +869,7 @@ static bool mei_me_pg_is_enabled(struct mei_device *dev) return true; notsupported: - dev_dbg(dev->dev, "pg: not supported: d0i3 = %d HGP = %d hbm version %d.%d ?= %d.%d\n", + dev_dbg(&dev->dev, "pg: not supported: d0i3 = %d HGP = %d hbm version %d.%d ?= %d.%d\n", hw->d0i3_supported, !!(reg & ME_PGIC_HRA), dev->version.major_version, @@ -974,7 +938,7 @@ static int mei_me_d0i3_enter_sync(struct mei_device *dev) reg = mei_me_d0i3c_read(dev); if (reg & H_D0I3C_I3) { /* we are in d0i3, nothing to do */ - dev_dbg(dev->dev, "d0i3 set not needed\n"); + dev_dbg(&dev->dev, "d0i3 set not needed\n"); ret = 0; goto on; } @@ -1003,7 +967,7 @@ static int mei_me_d0i3_enter_sync(struct mei_device *dev) reg = mei_me_d0i3_set(dev, true); if (!(reg & H_D0I3C_CIP)) { - dev_dbg(dev->dev, "d0i3 enter wait not needed\n"); + dev_dbg(&dev->dev, "d0i3 enter wait not needed\n"); ret = 0; goto on; } @@ -1027,7 +991,7 @@ on: hw->pg_state = MEI_PG_ON; out: dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "d0i3 enter ret = %d\n", ret); + dev_dbg(&dev->dev, "d0i3 enter ret = %d\n", ret); return ret; } @@ -1049,7 +1013,7 @@ static int mei_me_d0i3_enter(struct mei_device *dev) reg = mei_me_d0i3c_read(dev); if (reg & H_D0I3C_I3) { /* we are in d0i3, nothing to do */ - dev_dbg(dev->dev, "already d0i3 : set not needed\n"); + dev_dbg(&dev->dev, "already d0i3 : set not needed\n"); goto on; } @@ -1057,7 +1021,7 @@ static int mei_me_d0i3_enter(struct mei_device *dev) on: hw->pg_state = MEI_PG_ON; dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "d0i3 enter\n"); + dev_dbg(&dev->dev, "d0i3 enter\n"); return 0; } @@ -1079,14 +1043,14 @@ static int mei_me_d0i3_exit_sync(struct mei_device *dev) reg = mei_me_d0i3c_read(dev); if (!(reg & H_D0I3C_I3)) { /* we are not in d0i3, nothing to do */ - dev_dbg(dev->dev, "d0i3 exit not needed\n"); + dev_dbg(&dev->dev, "d0i3 exit not needed\n"); ret = 0; goto off; } reg = mei_me_d0i3_unset(dev); if (!(reg & H_D0I3C_CIP)) { - dev_dbg(dev->dev, "d0i3 exit wait not needed\n"); + dev_dbg(&dev->dev, "d0i3 exit wait not needed\n"); ret = 0; goto off; } @@ -1111,7 +1075,7 @@ off: out: dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "d0i3 exit ret = %d\n", ret); + dev_dbg(&dev->dev, "d0i3 exit ret = %d\n", ret); return ret; } @@ -1154,7 +1118,7 @@ static void mei_me_d0i3_intr(struct mei_device *dev, u32 intr_source) * force H_RDY because it could be * wiped off during PG */ - dev_dbg(dev->dev, "d0i3 set host ready\n"); + dev_dbg(&dev->dev, "d0i3 set host ready\n"); mei_me_host_set_ready(dev); } } else { @@ -1170,7 +1134,7 @@ static void mei_me_d0i3_intr(struct mei_device *dev, u32 intr_source) * we got here because of HW initiated exit from D0i3. * Start runtime pm resume sequence to exit low power state. */ - dev_dbg(dev->dev, "d0i3 want resume\n"); + dev_dbg(&dev->dev, "d0i3 want resume\n"); mei_hbm_pg_resume(dev); } } @@ -1250,7 +1214,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) } } - pm_runtime_set_active(dev->dev); + pm_runtime_set_active(dev->parent); hcsr = mei_hcsr_read(dev); /* H_RST may be found lit before reset is started, @@ -1259,7 +1223,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) * we need to clean H_RST bit to start a successful reset sequence. */ if ((hcsr & H_RST) == H_RST) { - dev_warn(dev->dev, "H_RST is set = 0x%08X", hcsr); + dev_warn(&dev->dev, "H_RST is set = 0x%08X", hcsr); hcsr &= ~H_RST; mei_hcsr_set(dev, hcsr); hcsr = mei_hcsr_read(dev); @@ -1280,10 +1244,10 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) hcsr = mei_hcsr_read(dev); if ((hcsr & H_RST) == 0) - dev_warn(dev->dev, "H_RST is not set = 0x%08X", hcsr); + dev_warn(&dev->dev, "H_RST is not set = 0x%08X", hcsr); if ((hcsr & H_RDY) == H_RDY) - dev_warn(dev->dev, "H_RDY is not cleared 0x%08X", hcsr); + dev_warn(&dev->dev, "H_RDY is not cleared 0x%08X", hcsr); if (!intr_enable) { mei_me_hw_reset_release(dev); @@ -1313,7 +1277,7 @@ irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id) if (!me_intr_src(hcsr)) return IRQ_NONE; - dev_dbg(dev->dev, "interrupt source 0x%08X\n", me_intr_src(hcsr)); + dev_dbg(&dev->dev, "interrupt source 0x%08X\n", me_intr_src(hcsr)); /* disable interrupts on device */ me_intr_disable(dev, hcsr); @@ -1339,7 +1303,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) u32 hcsr; int rets = 0; - dev_dbg(dev->dev, "function called after ISR to handle the interrupt processing.\n"); + dev_dbg(&dev->dev, "function called after ISR to handle the interrupt processing.\n"); /* initialize our complete list */ mutex_lock(&dev->device_lock); @@ -1351,10 +1315,10 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) /* check if ME wants a reset */ if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETTING) { if (kind_is_gsc(dev) || kind_is_gscfi(dev)) { - dev_dbg(dev->dev, "FW not ready: resetting: dev_state = %d\n", + dev_dbg(&dev->dev, "FW not ready: resetting: dev_state = %d\n", dev->dev_state); } else { - dev_warn(dev->dev, "FW not ready: resetting: dev_state = %d\n", + dev_warn(&dev->dev, "FW not ready: resetting: dev_state = %d\n", dev->dev_state); } if (dev->dev_state == MEI_DEV_POWERING_DOWN || @@ -1373,18 +1337,29 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) /* check if we need to start the dev */ if (!mei_host_is_ready(dev)) { if (mei_hw_is_ready(dev)) { - dev_dbg(dev->dev, "we need to start the dev.\n"); - dev->recvd_hw_ready = true; - wake_up(&dev->wait_hw_ready); + /* synchronized by dev mutex */ + if (waitqueue_active(&dev->wait_hw_ready)) { + dev_dbg(&dev->dev, "we need to start the dev.\n"); + dev->recvd_hw_ready = true; + wake_up(&dev->wait_hw_ready); + } else if (dev->dev_state != MEI_DEV_UNINITIALIZED && + dev->dev_state != MEI_DEV_POWERING_DOWN && + dev->dev_state != MEI_DEV_POWER_DOWN) { + dev_dbg(&dev->dev, "Force link reset.\n"); + schedule_work(&dev->reset_work); + } else { + dev_dbg(&dev->dev, "Ignore this interrupt in state = %d\n", + dev->dev_state); + } } else { - dev_dbg(dev->dev, "Spurious Interrupt\n"); + dev_dbg(&dev->dev, "Spurious Interrupt\n"); } goto end; } /* check slots available for reading */ slots = mei_count_full_read_slots(dev); while (slots > 0) { - dev_dbg(dev->dev, "slots to read = %08x\n", slots); + dev_dbg(&dev->dev, "slots to read = %08x\n", slots); rets = mei_irq_read_handler(dev, &cmpl_list, &slots); /* There is a race between ME write and interrupt delivery: * Not all data is always available immediately after the @@ -1394,7 +1369,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) break; if (rets) { - dev_err(dev->dev, "mei_irq_read_handler ret = %d, state = %d.\n", + dev_err(&dev->dev, "mei_irq_read_handler ret = %d, state = %d.\n", rets, dev->dev_state); if (dev->dev_state != MEI_DEV_RESETTING && dev->dev_state != MEI_DEV_DISABLED && @@ -1421,7 +1396,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) mei_irq_compl_handler(dev, &cmpl_list); end: - dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets); + dev_dbg(&dev->dev, "interrupt thread end ret = %d\n", rets); mei_me_intr_enable(dev); mutex_unlock(&dev->device_lock); return IRQ_HANDLED; @@ -1453,7 +1428,7 @@ int mei_me_polling_thread(void *_dev) irqreturn_t irq_ret; long polling_timeout = MEI_POLLING_TIMEOUT_ACTIVE; - dev_dbg(dev->dev, "kernel thread is running\n"); + dev_dbg(&dev->dev, "kernel thread is running\n"); while (!kthread_should_stop()) { struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr; @@ -1470,7 +1445,7 @@ int mei_me_polling_thread(void *_dev) polling_timeout = MEI_POLLING_TIMEOUT_ACTIVE; irq_ret = mei_me_irq_thread_handler(1, dev); if (irq_ret != IRQ_HANDLED) - dev_err(dev->dev, "irq_ret %d\n", irq_ret); + dev_err(&dev->dev, "irq_ret %d\n", irq_ret); } else { /* * Increase timeout by MEI_POLLING_TIMEOUT_ACTIVE @@ -1804,7 +1779,7 @@ struct mei_device *mei_me_dev_init(struct device *parent, struct mei_me_hw *hw; int i; - dev = devm_kzalloc(parent, sizeof(*dev) + sizeof(*hw), GFP_KERNEL); + dev = kzalloc(sizeof(*dev) + sizeof(*hw), GFP_KERNEL); if (!dev) return NULL; diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index e9476f9ae25d..e4688c391027 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -160,7 +160,7 @@ static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req) struct mei_txe_hw *hw = to_txe_hw(dev); bool do_req = hw->aliveness != req; - dev_dbg(dev->dev, "Aliveness current=%d request=%d\n", + dev_dbg(&dev->dev, "Aliveness current=%d request=%d\n", hw->aliveness, req); if (do_req) { dev->pg_event = MEI_PG_EVENT_WAIT; @@ -227,7 +227,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) hw->aliveness = mei_txe_aliveness_get(dev); if (hw->aliveness == expected) { dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "aliveness settled after %lld usecs\n", + dev_dbg(&dev->dev, "aliveness settled after %lld usecs\n", ktime_to_us(ktime_sub(ktime_get(), start))); return 0; } @@ -235,7 +235,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) } while (ktime_before(ktime_get(), stop)); dev->pg_event = MEI_PG_EVENT_IDLE; - dev_err(dev->dev, "aliveness timed out\n"); + dev_err(&dev->dev, "aliveness timed out\n"); return -ETIME; } @@ -270,10 +270,10 @@ static int mei_txe_aliveness_wait(struct mei_device *dev, u32 expected) ret = hw->aliveness == expected ? 0 : -ETIME; if (ret) - dev_warn(dev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", + dev_warn(&dev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", err, hw->aliveness, dev->pg_event); else - dev_dbg(dev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", + dev_dbg(&dev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", jiffies_to_msecs(timeout - err), hw->aliveness, dev->pg_event); @@ -438,7 +438,7 @@ static void mei_txe_intr_enable(struct mei_device *dev) */ static void mei_txe_synchronize_irq(struct mei_device *dev) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); synchronize_irq(pdev->irq); } @@ -464,7 +464,7 @@ static bool mei_txe_pending_interrupts(struct mei_device *dev) TXE_INTR_OUT_DB)); if (ret) { - dev_dbg(dev->dev, + dev_dbg(&dev->dev, "Pending Interrupts InReady=%01d Readiness=%01d, Aliveness=%01d, OutDoor=%01d\n", !!(hw->intr_cause & TXE_INTR_IN_READY), !!(hw->intr_cause & TXE_INTR_READINESS), @@ -612,7 +612,7 @@ static int mei_txe_readiness_wait(struct mei_device *dev) msecs_to_jiffies(SEC_RESET_WAIT_TIMEOUT)); mutex_lock(&dev->device_lock); if (!dev->recvd_hw_ready) { - dev_err(dev->dev, "wait for readiness failed\n"); + dev_err(&dev->dev, "wait for readiness failed\n"); return -ETIME; } @@ -638,7 +638,7 @@ static int mei_txe_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status) { const struct mei_fw_status *fw_src = &mei_txe_fw_sts; - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); int ret; int i; @@ -649,7 +649,7 @@ static int mei_txe_fw_status(struct mei_device *dev, for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { ret = pci_read_config_dword(pdev, fw_src->status[i], &fw_status->status[i]); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HSF_X", + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HSF_X", fw_src->status[i], fw_status->status[i]); if (ret) @@ -677,7 +677,7 @@ static int mei_txe_hw_config(struct mei_device *dev) hw->aliveness = mei_txe_aliveness_get(dev); hw->readiness = mei_txe_readiness_get(dev); - dev_dbg(dev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n", + dev_dbg(&dev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n", hw->aliveness, hw->readiness); return 0; @@ -708,7 +708,7 @@ static int mei_txe_write(struct mei_device *dev, if (WARN_ON(!hdr || !data || hdr_len & 0x3)) return -EINVAL; - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); + dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); dw_cnt = mei_data2slots(hdr_len + data_len); if (dw_cnt > slots) @@ -724,7 +724,7 @@ static int mei_txe_write(struct mei_device *dev, char fw_sts_str[MEI_FW_STATUS_STR_SZ]; mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ); - dev_err(dev->dev, "Input is not ready %s\n", fw_sts_str); + dev_err(&dev->dev, "Input is not ready %s\n", fw_sts_str); return -EAGAIN; } @@ -828,13 +828,13 @@ static int mei_txe_read(struct mei_device *dev, reg_buf = (u32 *)buf; rem = len & 0x3; - dev_dbg(dev->dev, "buffer-length = %lu buf[0]0x%08X\n", + dev_dbg(&dev->dev, "buffer-length = %lu buf[0]0x%08X\n", len, mei_txe_out_data_read(dev, 0)); for (i = 0; i < len / MEI_SLOT_SIZE; i++) { /* skip header: index starts from 1 */ reg = mei_txe_out_data_read(dev, i + 1); - dev_dbg(dev->dev, "buf[%d] = 0x%08X\n", i, reg); + dev_dbg(&dev->dev, "buf[%d] = 0x%08X\n", i, reg); *reg_buf++ = reg; } @@ -879,7 +879,7 @@ static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable) */ if (aliveness_req != hw->aliveness) if (mei_txe_aliveness_poll(dev, aliveness_req) < 0) { - dev_err(dev->dev, "wait for aliveness settle failed ... bailing out\n"); + dev_err(&dev->dev, "wait for aliveness settle failed ... bailing out\n"); return -EIO; } @@ -889,7 +889,7 @@ static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable) if (aliveness_req) { mei_txe_aliveness_set(dev, 0); if (mei_txe_aliveness_poll(dev, 0) < 0) { - dev_err(dev->dev, "wait for aliveness failed ... bailing out\n"); + dev_err(&dev->dev, "wait for aliveness failed ... bailing out\n"); return -EIO; } } @@ -921,7 +921,7 @@ static int mei_txe_hw_start(struct mei_device *dev) ret = mei_txe_readiness_wait(dev); if (ret < 0) { - dev_err(dev->dev, "waiting for readiness failed\n"); + dev_err(&dev->dev, "waiting for readiness failed\n"); return ret; } @@ -937,11 +937,11 @@ static int mei_txe_hw_start(struct mei_device *dev) ret = mei_txe_aliveness_set_sync(dev, 1); if (ret < 0) { - dev_err(dev->dev, "wait for aliveness failed ... bailing out\n"); + dev_err(&dev->dev, "wait for aliveness failed ... bailing out\n"); return ret; } - pm_runtime_set_active(dev->dev); + pm_runtime_set_active(dev->parent); /* enable input ready interrupts: * SEC_IPC_HOST_INT_MASK.IPC_INPUT_READY_INT_MASK @@ -1049,7 +1049,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) s32 slots; int rets = 0; - dev_dbg(dev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n", + dev_dbg(&dev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n", mei_txe_br_reg_read(hw, HHISR_REG), mei_txe_br_reg_read(hw, HISR_REG), mei_txe_sec_reg_read_silent(hw, SEC_IPC_HOST_INT_STATUS_REG)); @@ -1059,7 +1059,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) mutex_lock(&dev->device_lock); INIT_LIST_HEAD(&cmpl_list); - if (pci_dev_msi_enabled(to_pci_dev(dev->dev))) + if (pci_dev_msi_enabled(to_pci_dev(dev->parent))) mei_txe_check_and_ack_intrs(dev, true); /* show irq events */ @@ -1073,17 +1073,17 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) * or TXE driver resetting the HECI interface. */ if (test_and_clear_bit(TXE_INTR_READINESS_BIT, &hw->intr_cause)) { - dev_dbg(dev->dev, "Readiness Interrupt was received...\n"); + dev_dbg(&dev->dev, "Readiness Interrupt was received...\n"); /* Check if SeC is going through reset */ if (mei_txe_readiness_is_sec_rdy(hw->readiness)) { - dev_dbg(dev->dev, "we need to start the dev.\n"); + dev_dbg(&dev->dev, "we need to start the dev.\n"); dev->recvd_hw_ready = true; } else { dev->recvd_hw_ready = false; if (dev->dev_state != MEI_DEV_RESETTING) { - dev_warn(dev->dev, "FW not ready: resetting.\n"); + dev_warn(&dev->dev, "FW not ready: resetting.\n"); schedule_work(&dev->reset_work); goto end; @@ -1100,7 +1100,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) if (test_and_clear_bit(TXE_INTR_ALIVENESS_BIT, &hw->intr_cause)) { /* Clear the interrupt cause */ - dev_dbg(dev->dev, + dev_dbg(&dev->dev, "Aliveness Interrupt: Status: %d\n", hw->aliveness); dev->pg_event = MEI_PG_EVENT_RECEIVED; if (waitqueue_active(&hw->wait_aliveness_resp)) @@ -1118,7 +1118,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) if (rets && (dev->dev_state != MEI_DEV_RESETTING && dev->dev_state != MEI_DEV_POWER_DOWN)) { - dev_err(dev->dev, + dev_err(&dev->dev, "mei_irq_read_handler ret = %d.\n", rets); schedule_work(&dev->reset_work); @@ -1136,7 +1136,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) dev->hbuf_is_ready = mei_hbuf_is_ready(dev); rets = mei_irq_write_handler(dev, &cmpl_list); if (rets && rets != -EMSGSIZE) - dev_err(dev->dev, "mei_irq_write_handler ret = %d.\n", + dev_err(&dev->dev, "mei_irq_write_handler ret = %d.\n", rets); dev->hbuf_is_ready = mei_hbuf_is_ready(dev); } @@ -1144,7 +1144,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) mei_irq_compl_handler(dev, &cmpl_list); end: - dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets); + dev_dbg(&dev->dev, "interrupt thread end ret = %d\n", rets); mutex_unlock(&dev->device_lock); @@ -1197,7 +1197,7 @@ struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) struct mei_device *dev; struct mei_txe_hw *hw; - dev = devm_kzalloc(&pdev->dev, sizeof(*dev) + sizeof(*hw), GFP_KERNEL); + dev = kzalloc(sizeof(*dev) + sizeof(*hw), GFP_KERNEL); if (!dev) return NULL; diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 2e9cf6f4efb6..3771aa09c592 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -27,6 +27,8 @@ #define MKHI_RCV_TIMEOUT 500 /* receive timeout in msec */ #define MKHI_RCV_TIMEOUT_SLOW 10000 /* receive timeout in msec, slow FW */ +#define MEI_LINK_RESET_WAIT_TIMEOUT_MSEC 500 /* Max wait timeout for link reset, in msec */ + /* * FW page size for DMA allocations */ diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 8ef2b1df8ac7..b789c4d9c709 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -89,22 +89,6 @@ void mei_cancel_work(struct mei_device *dev) } EXPORT_SYMBOL_GPL(mei_cancel_work); -static void mei_save_fw_status(struct mei_device *dev) -{ - struct mei_fw_status fw_status; - int ret; - - ret = mei_fw_status(dev, &fw_status); - if (ret) { - dev_err(dev->dev, "failed to read firmware status: %d\n", ret); - return; - } - - dev->saved_dev_state = dev->dev_state; - dev->saved_fw_status_flag = true; - memcpy(&dev->saved_fw_status, &fw_status, sizeof(fw_status)); -} - /** * mei_reset - resets host and fw. * @@ -126,11 +110,10 @@ int mei_reset(struct mei_device *dev) mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ); if (kind_is_gsc(dev) || kind_is_gscfi(dev)) { - dev_dbg(dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", + dev_dbg(&dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", mei_dev_state_str(state), fw_sts_str); - mei_save_fw_status(dev); } else { - dev_warn(dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", + dev_warn(&dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", mei_dev_state_str(state), fw_sts_str); } } @@ -150,7 +133,7 @@ int mei_reset(struct mei_device *dev) dev->reset_count++; if (dev->reset_count > MEI_MAX_CONSEC_RESET) { - dev_err(dev->dev, "reset: reached maximal consecutive resets: disabling the device\n"); + dev_err(&dev->dev, "reset: reached maximal consecutive resets: disabling the device\n"); mei_set_devstate(dev, MEI_DEV_DISABLED); return -ENODEV; } @@ -170,12 +153,12 @@ int mei_reset(struct mei_device *dev) memset(dev->rd_msg_hdr, 0, sizeof(dev->rd_msg_hdr)); if (ret) { - dev_err(dev->dev, "hw_reset failed ret = %d\n", ret); + dev_err(&dev->dev, "hw_reset failed ret = %d\n", ret); return ret; } if (state == MEI_DEV_POWER_DOWN) { - dev_dbg(dev->dev, "powering down: end of reset\n"); + dev_dbg(&dev->dev, "powering down: end of reset\n"); mei_set_devstate(dev, MEI_DEV_DISABLED); return 0; } @@ -185,21 +168,21 @@ int mei_reset(struct mei_device *dev) char fw_sts_str[MEI_FW_STATUS_STR_SZ]; mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ); - dev_err(dev->dev, "hw_start failed ret = %d fw status = %s\n", ret, fw_sts_str); + dev_err(&dev->dev, "hw_start failed ret = %d fw status = %s\n", ret, fw_sts_str); return ret; } if (dev->dev_state != MEI_DEV_RESETTING) { - dev_dbg(dev->dev, "wrong state = %d on link start\n", dev->dev_state); + dev_dbg(&dev->dev, "wrong state = %d on link start\n", dev->dev_state); return 0; } - dev_dbg(dev->dev, "link is established start sending messages.\n"); + dev_dbg(&dev->dev, "link is established start sending messages.\n"); mei_set_devstate(dev, MEI_DEV_INIT_CLIENTS); ret = mei_hbm_start_req(dev); if (ret) { - dev_err(dev->dev, "hbm_start failed ret = %d\n", ret); + dev_err(&dev->dev, "hbm_start failed ret = %d\n", ret); mei_set_devstate(dev, MEI_DEV_RESETTING); return ret; } @@ -228,7 +211,7 @@ int mei_start(struct mei_device *dev) if (ret) goto err; - dev_dbg(dev->dev, "reset in start the mei device.\n"); + dev_dbg(&dev->dev, "reset in start the mei device.\n"); dev->reset_count = 0; do { @@ -236,27 +219,27 @@ int mei_start(struct mei_device *dev) ret = mei_reset(dev); if (ret == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) { - dev_err(dev->dev, "reset failed ret = %d", ret); + dev_err(&dev->dev, "reset failed ret = %d", ret); goto err; } } while (ret); if (mei_hbm_start_wait(dev)) { - dev_err(dev->dev, "HBM haven't started"); + dev_err(&dev->dev, "HBM haven't started"); goto err; } if (!mei_hbm_version_is_supported(dev)) { - dev_dbg(dev->dev, "MEI start failed.\n"); + dev_dbg(&dev->dev, "MEI start failed.\n"); goto err; } - dev_dbg(dev->dev, "link layer has been established.\n"); + dev_dbg(&dev->dev, "link layer has been established.\n"); mutex_unlock(&dev->device_lock); return 0; err: - dev_err(dev->dev, "link layer initialization failed.\n"); + dev_err(&dev->dev, "link layer initialization failed.\n"); mei_set_devstate(dev, MEI_DEV_DISABLED); mutex_unlock(&dev->device_lock); return -ENODEV; @@ -284,7 +267,7 @@ int mei_restart(struct mei_device *dev) mutex_unlock(&dev->device_lock); if (err == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) { - dev_err(dev->dev, "device disabled = %d\n", err); + dev_err(&dev->dev, "device disabled = %d\n", err); return -ENODEV; } @@ -313,7 +296,7 @@ static void mei_reset_work(struct work_struct *work) mutex_unlock(&dev->device_lock); if (dev->dev_state == MEI_DEV_DISABLED) { - dev_err(dev->dev, "device disabled = %d\n", ret); + dev_err(&dev->dev, "device disabled = %d\n", ret); return; } @@ -324,7 +307,7 @@ static void mei_reset_work(struct work_struct *work) void mei_stop(struct mei_device *dev) { - dev_dbg(dev->dev, "stopping the device.\n"); + dev_dbg(&dev->dev, "stopping the device.\n"); mutex_lock(&dev->device_lock); mei_set_devstate(dev, MEI_DEV_POWERING_DOWN); @@ -365,7 +348,7 @@ bool mei_write_is_idle(struct mei_device *dev) list_empty(&dev->write_list) && list_empty(&dev->write_waiting_list)); - dev_dbg(dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n", + dev_dbg(&dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n", idle, mei_dev_state_str(dev->dev_state), list_empty(&dev->ctrl_wr_list), @@ -380,12 +363,12 @@ EXPORT_SYMBOL_GPL(mei_write_is_idle); * mei_device_init - initialize mei_device structure * * @dev: the mei device - * @device: the device structure + * @parent: the parent device * @slow_fw: configure longer timeouts as FW is slow * @hw_ops: hw operations */ void mei_device_init(struct mei_device *dev, - struct device *device, + struct device *parent, bool slow_fw, const struct mei_hw_ops *hw_ops) { @@ -399,7 +382,8 @@ void mei_device_init(struct mei_device *dev, init_waitqueue_head(&dev->wait_hw_ready); init_waitqueue_head(&dev->wait_pg); init_waitqueue_head(&dev->wait_hbm_start); - dev->dev_state = MEI_DEV_INITIALIZING; + dev->dev_state = MEI_DEV_UNINITIALIZED; + init_waitqueue_head(&dev->wait_dev_state); dev->reset_count = 0; INIT_LIST_HEAD(&dev->write_list); @@ -426,7 +410,7 @@ void mei_device_init(struct mei_device *dev, dev->pg_event = MEI_PG_EVENT_IDLE; dev->ops = hw_ops; - dev->dev = device; + dev->parent = parent; dev->timeouts.hw_ready = mei_secs_to_jiffies(MEI_HW_READY_TIMEOUT); dev->timeouts.connect = MEI_CONNECT_TIMEOUT; @@ -442,6 +426,6 @@ void mei_device_init(struct mei_device *dev, dev->timeouts.hbm = mei_secs_to_jiffies(MEI_HBM_TIMEOUT); dev->timeouts.mkhi_recv = msecs_to_jiffies(MKHI_RCV_TIMEOUT); } + dev->timeouts.link_reset_wait = msecs_to_jiffies(MEI_LINK_RESET_WAIT_TIMEOUT_MSEC); } EXPORT_SYMBOL_GPL(mei_device_init); - diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index d472f6bbe767..3aa66b6b0d36 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -76,7 +76,7 @@ static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr, * that length fits into rd_msg_buf */ mei_read_slots(dev, dev->rd_msg_buf, discard_len); - dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", + dev_dbg(&dev->dev, "discarding message " MEI_HDR_FMT "\n", MEI_HDR_PRM(hdr)); } @@ -229,8 +229,8 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl, cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx); list_move_tail(&cb->list, cmpl_list); } else { - pm_runtime_mark_last_busy(dev->dev); - pm_request_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_request_autosuspend(dev->parent); } return 0; @@ -310,8 +310,8 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb, return ret; } - pm_runtime_mark_last_busy(dev->dev); - pm_request_autosuspend(dev->dev); + pm_runtime_mark_last_busy(dev->parent); + pm_request_autosuspend(dev->parent); list_move_tail(&cb->list, &cl->rd_pending); @@ -373,21 +373,21 @@ int mei_irq_read_handler(struct mei_device *dev, dev->rd_msg_hdr[0] = mei_read_hdr(dev); dev->rd_msg_hdr_count = 1; (*slots)--; - dev_dbg(dev->dev, "slots =%08x.\n", *slots); + dev_dbg(&dev->dev, "slots =%08x.\n", *slots); ret = hdr_is_valid(dev->rd_msg_hdr[0]); if (ret) { - dev_err(dev->dev, "corrupted message header 0x%08X\n", + dev_err(&dev->dev, "corrupted message header 0x%08X\n", dev->rd_msg_hdr[0]); goto end; } } mei_hdr = (struct mei_msg_hdr *)dev->rd_msg_hdr; - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); + dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); if (mei_slots2data(*slots) < mei_hdr->length) { - dev_err(dev->dev, "less data available than length=%08x.\n", + dev_err(&dev->dev, "less data available than length=%08x.\n", *slots); /* we can't read the message */ ret = -ENODATA; @@ -402,18 +402,18 @@ int mei_irq_read_handler(struct mei_device *dev, dev->rd_msg_hdr[1] = mei_read_hdr(dev); dev->rd_msg_hdr_count++; (*slots)--; - dev_dbg(dev->dev, "extended header is %08x\n", dev->rd_msg_hdr[1]); + dev_dbg(&dev->dev, "extended header is %08x\n", dev->rd_msg_hdr[1]); } meta_hdr = ((struct mei_ext_meta_hdr *)&dev->rd_msg_hdr[1]); if (check_add_overflow((u32)sizeof(*meta_hdr), mei_slots2data(meta_hdr->size), &hdr_size_ext)) { - dev_err(dev->dev, "extended message size too big %d\n", + dev_err(&dev->dev, "extended message size too big %d\n", meta_hdr->size); return -EBADMSG; } if (hdr_size_left < hdr_size_ext) { - dev_err(dev->dev, "corrupted message header len %d\n", + dev_err(&dev->dev, "corrupted message header len %d\n", mei_hdr->length); return -EBADMSG; } @@ -422,7 +422,7 @@ int mei_irq_read_handler(struct mei_device *dev, ext_hdr_end = meta_hdr->size + 2; for (i = dev->rd_msg_hdr_count; i < ext_hdr_end; i++) { dev->rd_msg_hdr[i] = mei_read_hdr(dev); - dev_dbg(dev->dev, "extended header %d is %08x\n", i, + dev_dbg(&dev->dev, "extended header %d is %08x\n", i, dev->rd_msg_hdr[i]); dev->rd_msg_hdr_count++; (*slots)--; @@ -431,7 +431,7 @@ int mei_irq_read_handler(struct mei_device *dev, if (mei_hdr->dma_ring) { if (hdr_size_left != sizeof(dev->rd_msg_hdr[ext_hdr_end])) { - dev_err(dev->dev, "corrupted message header len %d\n", + dev_err(&dev->dev, "corrupted message header len %d\n", mei_hdr->length); return -EBADMSG; } @@ -446,8 +446,7 @@ int mei_irq_read_handler(struct mei_device *dev, if (hdr_is_hbm(mei_hdr)) { ret = mei_hbm_dispatch(dev, mei_hdr); if (ret) { - dev_dbg(dev->dev, "mei_hbm_dispatch failed ret = %d\n", - ret); + dev_dbg(&dev->dev, "mei_hbm_dispatch failed ret = %d\n", ret); goto end; } goto reset_slots; @@ -474,7 +473,7 @@ int mei_irq_read_handler(struct mei_device *dev, ret = 0; goto reset_slots; } - dev_err(dev->dev, "no destination client found 0x%08X\n", dev->rd_msg_hdr[0]); + dev_err(&dev->dev, "no destination client found 0x%08X\n", dev->rd_msg_hdr[0]); ret = -EBADMSG; goto end; @@ -485,7 +484,7 @@ reset_slots: *slots = mei_count_full_read_slots(dev); if (*slots == -EOVERFLOW) { /* overflow - reset */ - dev_err(dev->dev, "resetting due to slots overflow.\n"); + dev_err(&dev->dev, "resetting due to slots overflow.\n"); /* set the event since message has been read */ ret = -ERANGE; goto end; @@ -525,7 +524,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) return -EMSGSIZE; /* complete all waiting for write CB */ - dev_dbg(dev->dev, "complete all waiting for write cb.\n"); + dev_dbg(&dev->dev, "complete all waiting for write cb.\n"); list_for_each_entry_safe(cb, next, &dev->write_waiting_list, list) { cl = cb->cl; @@ -537,7 +536,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) } /* complete control write list CB */ - dev_dbg(dev->dev, "complete control write list cb.\n"); + dev_dbg(&dev->dev, "complete control write list cb.\n"); list_for_each_entry_safe(cb, next, &dev->ctrl_wr_list, list) { cl = cb->cl; switch (cb->fop_type) { @@ -591,7 +590,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) } /* complete write list CB */ - dev_dbg(dev->dev, "complete write list cb.\n"); + dev_dbg(&dev->dev, "complete write list cb.\n"); list_for_each_entry_safe(cb, next, &dev->write_list, list) { cl = cb->cl; ret = mei_cl_irq_write(cl, cb, cmpl_list); @@ -656,7 +655,7 @@ void mei_timer(struct work_struct *work) if (dev->init_clients_timer) { if (--dev->init_clients_timer == 0) { - dev_err(dev->dev, "timer: init clients timeout hbm_state = %d.\n", + dev_err(&dev->dev, "timer: init clients timeout hbm_state = %d.\n", dev->hbm_state); mei_reset(dev); goto out; @@ -672,7 +671,7 @@ void mei_timer(struct work_struct *work) list_for_each_entry(cl, &dev->file_list, link) { if (cl->timer_count) { if (--cl->timer_count == 0) { - dev_err(dev->dev, "timer: connect/disconnect timeout.\n"); + dev_err(&dev->dev, "timer: connect/disconnect timeout.\n"); mei_connect_timeout(cl); goto out; } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 8a149a15b861..86a73684a373 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -51,12 +51,15 @@ static int mei_open(struct inode *inode, struct file *file) int err; - dev = container_of(inode->i_cdev, struct mei_device, cdev); + dev = idr_find(&mei_idr, iminor(inode)); + if (!dev) + return -ENODEV; + get_device(&dev->dev); mutex_lock(&dev->device_lock); if (dev->dev_state != MEI_DEV_ENABLED) { - dev_dbg(dev->dev, "dev_state != MEI_ENABLED dev_state = %s\n", + dev_dbg(&dev->dev, "dev_state != MEI_ENABLED dev_state = %s\n", mei_dev_state_str(dev->dev_state)); err = -ENODEV; goto err_unlock; @@ -77,6 +80,7 @@ static int mei_open(struct inode *inode, struct file *file) err_unlock: mutex_unlock(&dev->device_lock); + put_device(&dev->dev); return err; } @@ -152,6 +156,7 @@ out: file->private_data = NULL; mutex_unlock(&dev->device_lock); + put_device(&dev->dev); return rets; } @@ -418,6 +423,7 @@ static int mei_ioctl_connect_client(struct file *file, cl->state != MEI_FILE_DISCONNECTED) return -EBUSY; +retry: /* find ME client we're trying to connect to */ me_cl = mei_me_cl_by_uuid(dev, in_client_uuid); if (!me_cl) { @@ -449,6 +455,28 @@ static int mei_ioctl_connect_client(struct file *file, rets = mei_cl_connect(cl, me_cl, file); + if (rets && cl->status == -EFAULT && + (dev->dev_state == MEI_DEV_RESETTING || + dev->dev_state == MEI_DEV_INIT_CLIENTS)) { + /* in link reset, wait for it completion */ + mutex_unlock(&dev->device_lock); + rets = wait_event_interruptible_timeout(dev->wait_dev_state, + dev->dev_state == MEI_DEV_ENABLED, + dev->timeouts.link_reset_wait); + mutex_lock(&dev->device_lock); + if (rets < 0) { + if (signal_pending(current)) + rets = -EINTR; + goto end; + } + if (dev->dev_state != MEI_DEV_ENABLED) { + rets = -ETIME; + goto end; + } + mei_me_cl_put(me_cl); + goto retry; + } + end: mei_me_cl_put(me_cl); return rets; @@ -477,7 +505,7 @@ static int mei_vt_support_check(struct mei_device *dev, const uuid_le *uuid) me_cl = mei_me_cl_by_uuid(dev, uuid); if (!me_cl) { - dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n", + dev_dbg(&dev->dev, "Cannot connect to FW Client UUID = %pUl\n", uuid); return -ENOTTY; } @@ -641,7 +669,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) struct mei_cl *cl = file->private_data; struct mei_connect_client_data conn; struct mei_connect_client_data_vtag conn_vtag; - const uuid_le *cl_uuid; + uuid_le cl_uuid; struct mei_client *props; u8 vtag; u32 notify_get, notify_req; @@ -669,18 +697,18 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) rets = -EFAULT; goto out; } - cl_uuid = &conn.in_client_uuid; + cl_uuid = conn.in_client_uuid; props = &conn.out_client_properties; vtag = 0; - rets = mei_vt_support_check(dev, cl_uuid); + rets = mei_vt_support_check(dev, &cl_uuid); if (rets == -ENOTTY) goto out; if (!rets) - rets = mei_ioctl_connect_vtag(file, cl_uuid, props, + rets = mei_ioctl_connect_vtag(file, &cl_uuid, props, vtag); else - rets = mei_ioctl_connect_client(file, cl_uuid, props); + rets = mei_ioctl_connect_client(file, &cl_uuid, props); if (rets) goto out; @@ -702,14 +730,14 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) goto out; } - cl_uuid = &conn_vtag.connect.in_client_uuid; + cl_uuid = conn_vtag.connect.in_client_uuid; props = &conn_vtag.out_client_properties; vtag = conn_vtag.connect.vtag; - rets = mei_vt_support_check(dev, cl_uuid); + rets = mei_vt_support_check(dev, &cl_uuid); if (rets == -EOPNOTSUPP) cl_dbg(dev, cl, "FW Client %pUl does not support vtags\n", - cl_uuid); + &cl_uuid); if (rets) goto out; @@ -719,7 +747,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) goto out; } - rets = mei_ioctl_connect_vtag(file, cl_uuid, props, vtag); + rets = mei_ioctl_connect_vtag(file, &cl_uuid, props, vtag); if (rets) goto out; @@ -1115,7 +1143,12 @@ void mei_set_devstate(struct mei_device *dev, enum mei_dev_state state) dev->dev_state = state; - clsdev = class_find_device_by_devt(&mei_class, dev->cdev.dev); + wake_up_interruptible_all(&dev->wait_dev_state); + + if (!dev->cdev) + return; + + clsdev = class_find_device_by_devt(&mei_class, dev->cdev->dev); if (clsdev) { sysfs_notify(&clsdev->kobj, NULL, "dev_state"); put_device(clsdev); @@ -1191,7 +1224,7 @@ static int mei_minor_get(struct mei_device *dev) if (ret >= 0) dev->minor = ret; else if (ret == -ENOSPC) - dev_err(dev->dev, "too many mei devices\n"); + dev_err(&dev->dev, "too many mei devices\n"); mutex_unlock(&mei_minor_lock); return ret; @@ -1200,56 +1233,81 @@ static int mei_minor_get(struct mei_device *dev) /** * mei_minor_free - mark device minor number as free * - * @dev: device pointer + * @minor: minor number to free */ -static void mei_minor_free(struct mei_device *dev) +static void mei_minor_free(int minor) { mutex_lock(&mei_minor_lock); - idr_remove(&mei_idr, dev->minor); + idr_remove(&mei_idr, minor); mutex_unlock(&mei_minor_lock); } +static void mei_device_release(struct device *dev) +{ + kfree(dev_get_drvdata(dev)); +} + int mei_register(struct mei_device *dev, struct device *parent) { - struct device *clsdev; /* class device */ int ret, devno; + int minor; ret = mei_minor_get(dev); if (ret < 0) return ret; + minor = dev->minor; + /* Fill in the data structures */ devno = MKDEV(MAJOR(mei_devt), dev->minor); - cdev_init(&dev->cdev, &mei_fops); - dev->cdev.owner = parent->driver->owner; + + device_initialize(&dev->dev); + dev->dev.devt = devno; + dev->dev.class = &mei_class; + dev->dev.parent = parent; + dev->dev.groups = mei_groups; + dev->dev.release = mei_device_release; + dev_set_drvdata(&dev->dev, dev); + + dev->cdev = cdev_alloc(); + if (!dev->cdev) { + ret = -ENOMEM; + goto err; + } + dev->cdev->ops = &mei_fops; + dev->cdev->owner = parent->driver->owner; + cdev_set_parent(dev->cdev, &dev->dev.kobj); /* Add the device */ - ret = cdev_add(&dev->cdev, devno, 1); + ret = cdev_add(dev->cdev, devno, 1); if (ret) { - dev_err(parent, "unable to add device %d:%d\n", + dev_err(parent, "unable to add cdev for device %d:%d\n", MAJOR(mei_devt), dev->minor); - goto err_dev_add; + goto err_del_cdev; } - clsdev = device_create_with_groups(&mei_class, parent, devno, - dev, mei_groups, - "mei%d", dev->minor); + ret = dev_set_name(&dev->dev, "mei%d", dev->minor); + if (ret) { + dev_err(parent, "unable to set name to device %d:%d ret = %d\n", + MAJOR(mei_devt), dev->minor, ret); + goto err_del_cdev; + } - if (IS_ERR(clsdev)) { - dev_err(parent, "unable to create device %d:%d\n", - MAJOR(mei_devt), dev->minor); - ret = PTR_ERR(clsdev); - goto err_dev_create; + ret = device_add(&dev->dev); + if (ret) { + dev_err(parent, "unable to add device %d:%d ret = %d\n", + MAJOR(mei_devt), dev->minor, ret); + goto err_del_cdev; } - mei_dbgfs_register(dev, dev_name(clsdev)); + mei_dbgfs_register(dev, dev_name(&dev->dev)); return 0; -err_dev_create: - cdev_del(&dev->cdev); -err_dev_add: - mei_minor_free(dev); +err_del_cdev: + cdev_del(dev->cdev); +err: + mei_minor_free(minor); return ret; } EXPORT_SYMBOL_GPL(mei_register); @@ -1257,15 +1315,16 @@ EXPORT_SYMBOL_GPL(mei_register); void mei_deregister(struct mei_device *dev) { int devno; + int minor = dev->minor; - devno = dev->cdev.dev; - cdev_del(&dev->cdev); + devno = dev->cdev->dev; + cdev_del(dev->cdev); mei_dbgfs_deregister(dev); device_destroy(&mei_class, devno); - mei_minor_free(dev); + mei_minor_free(minor); } EXPORT_SYMBOL_GPL(mei_deregister); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 37d7fb15cad7..0bf8d552c3ea 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -57,7 +57,8 @@ enum file_state { /* MEI device states */ enum mei_dev_state { - MEI_DEV_INITIALIZING = 0, + MEI_DEV_UNINITIALIZED = 0, + MEI_DEV_INITIALIZING, MEI_DEV_INIT_CLIENTS, MEI_DEV_ENABLED, MEI_DEV_RESETTING, @@ -465,13 +466,15 @@ struct mei_dev_timeouts { unsigned int d0i3; /* D0i3 set/unset max response time, in jiffies */ unsigned long hbm; /* HBM operation timeout, in jiffies */ unsigned long mkhi_recv; /* receive timeout, in jiffies */ + unsigned long link_reset_wait; /* link reset wait timeout, in jiffies */ }; /** * struct mei_device - MEI private device struct * - * @dev : device on a bus - * @cdev : character device + * @parent : device on a bus + * @dev : device object + * @cdev : character device pointer * @minor : minor number allocated for device * * @write_list : write pending list @@ -494,6 +497,7 @@ struct mei_dev_timeouts { * * @reset_count : number of consecutive resets * @dev_state : device state + * @wait_dev_state: wait queue for device state change * @hbm_state : state of host bus message protocol * @pxp_mode : PXP device mode * @init_clients_timer : HBM init handshake timeout @@ -547,17 +551,15 @@ struct mei_dev_timeouts { * * @dbgfs_dir : debugfs mei root directory * - * @saved_fw_status : saved firmware status - * @saved_dev_state : saved device state - * @saved_fw_status_flag : flag indicating that firmware status was saved * @gsc_reset_to_pxp : state of reset to the PXP mode * * @ops: : hw specific operations * @hw : hw specific data */ struct mei_device { - struct device *dev; - struct cdev cdev; + struct device *parent; + struct device dev; + struct cdev *cdev; int minor; struct list_head write_list; @@ -585,6 +587,7 @@ struct mei_device { */ unsigned long reset_count; enum mei_dev_state dev_state; + wait_queue_head_t wait_dev_state; enum mei_hbm_state hbm_state; enum mei_dev_pxp_mode pxp_mode; u16 init_clients_timer; @@ -648,9 +651,6 @@ struct mei_device { struct dentry *dbgfs_dir; #endif /* CONFIG_DEBUG_FS */ - struct mei_fw_status saved_fw_status; - enum mei_dev_state saved_dev_state; - bool saved_fw_status_flag; enum mei_dev_reset_to_pxp gsc_reset_to_pxp; const struct mei_hw_ops *ops; @@ -703,7 +703,7 @@ static inline u32 mei_slots2data(int slots) * mei init function prototypes */ void mei_device_init(struct mei_device *dev, - struct device *device, + struct device *parent, bool slow_fw, const struct mei_hw_ops *hw_ops); int mei_reset(struct mei_device *dev); diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 3f9c60b579ae..b108a7c22388 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -143,7 +143,7 @@ static inline void mei_me_unset_pm_domain(struct mei_device *dev) {} static int mei_me_read_fws(const struct mei_device *dev, int where, u32 *val) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); return pci_read_config_dword(pdev, where, val); } @@ -238,19 +238,19 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto end; } + err = mei_register(dev, &pdev->dev); + if (err) + goto release_irq; + if (mei_start(dev)) { dev_err(&pdev->dev, "init hw failure.\n"); err = -ENODEV; - goto release_irq; + goto deregister; } pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_ME_RPM_TIMEOUT); pm_runtime_use_autosuspend(&pdev->dev); - err = mei_register(dev, &pdev->dev); - if (err) - goto stop; - pci_set_drvdata(pdev, dev); /* @@ -280,8 +280,8 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) return 0; -stop: - mei_stop(dev); +deregister: + mei_deregister(dev); release_irq: mei_cancel_work(dev); mei_disable_interrupts(dev); @@ -475,7 +475,7 @@ static int mei_me_pm_runtime_resume(struct device *device) */ static inline void mei_me_set_pm_domain(struct mei_device *dev) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); if (pdev->dev.bus && pdev->dev.bus->pm) { dev->pg_domain.ops = *pdev->dev.bus->pm; @@ -496,7 +496,7 @@ static inline void mei_me_set_pm_domain(struct mei_device *dev) static inline void mei_me_unset_pm_domain(struct mei_device *dev) { /* stop using pm callbacks if any */ - dev_pm_domain_set(dev->dev, NULL); + dev_pm_domain_set(dev->parent, NULL); } static const struct dev_pm_ops mei_me_pm_ops = { diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index 2a584104ba38..c9eb5c5393e4 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -321,7 +321,7 @@ static int mei_txe_pm_runtime_resume(struct device *device) */ static inline void mei_txe_set_pm_domain(struct mei_device *dev) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); if (pdev->dev.bus && pdev->dev.bus->pm) { dev->pg_domain.ops = *pdev->dev.bus->pm; @@ -342,7 +342,7 @@ static inline void mei_txe_set_pm_domain(struct mei_device *dev) static inline void mei_txe_unset_pm_domain(struct mei_device *dev) { /* stop using pm callbacks if any */ - dev_pm_domain_set(dev->dev, NULL); + dev_pm_domain_set(dev->parent, NULL); } static const struct dev_pm_ops mei_txe_pm_ops = { diff --git a/drivers/misc/mei/platform-vsc.c b/drivers/misc/mei/platform-vsc.c index b2b5a20ae3fa..288e7b72e942 100644 --- a/drivers/misc/mei/platform-vsc.c +++ b/drivers/misc/mei/platform-vsc.c @@ -152,7 +152,7 @@ static int mei_vsc_hw_start(struct mei_device *mei_dev) MEI_VSC_POLL_TIMEOUT_US, true, hw, &buf, sizeof(buf)); if (ret) { - dev_err(mei_dev->dev, "wait fw ready failed: %d\n", ret); + dev_err(&mei_dev->dev, "wait fw ready failed: %d\n", ret); return ret; } @@ -259,7 +259,7 @@ static int mei_vsc_hw_reset(struct mei_device *mei_dev, bool intr_enable) if (!intr_enable) return 0; - return vsc_tp_init(hw->tp, mei_dev->dev); + return vsc_tp_init(hw->tp, mei_dev->parent); } static const struct mei_hw_ops mei_vsc_hw_ops = { @@ -325,7 +325,7 @@ static void mei_vsc_event_cb(void *context) mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev); ret = mei_irq_write_handler(mei_dev, &cmpl_list); if (ret) - dev_err(mei_dev->dev, "dispatch write request failed: %d\n", ret); + dev_err(&mei_dev->dev, "dispatch write request failed: %d\n", ret); mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev); mei_irq_compl_handler(mei_dev, &cmpl_list); @@ -343,12 +343,12 @@ static int mei_vsc_probe(struct platform_device *pdev) if (!tp) return dev_err_probe(dev, -ENODEV, "no platform data\n"); - mei_dev = devm_kzalloc(dev, size_add(sizeof(*mei_dev), sizeof(*hw)), - GFP_KERNEL); + mei_dev = kzalloc(size_add(sizeof(*mei_dev), sizeof(*hw)), GFP_KERNEL); if (!mei_dev) return -ENOMEM; mei_device_init(mei_dev, dev, false, &mei_vsc_hw_ops); + mei_dev->fw_f_fw_ver_supported = 0; mei_dev->kind = "ivsc"; @@ -360,22 +360,22 @@ static int mei_vsc_probe(struct platform_device *pdev) vsc_tp_register_event_cb(tp, mei_vsc_event_cb, mei_dev); + ret = mei_register(mei_dev, dev); + if (ret) + goto err_dereg; + ret = mei_start(mei_dev); if (ret) { dev_err_probe(dev, ret, "init hw failed\n"); goto err_cancel; } - ret = mei_register(mei_dev, dev); - if (ret) - goto err_stop; - - pm_runtime_enable(mei_dev->dev); + pm_runtime_enable(mei_dev->parent); return 0; -err_stop: - mei_stop(mei_dev); +err_dereg: + mei_deregister(mei_dev); err_cancel: mei_cancel_work(mei_dev); @@ -392,7 +392,7 @@ static void mei_vsc_remove(struct platform_device *pdev) struct mei_device *mei_dev = platform_get_drvdata(pdev); struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); - pm_runtime_disable(mei_dev->dev); + pm_runtime_disable(mei_dev->parent); mei_stop(mei_dev); diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index edd811444ce5..e0d88d3199c1 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -28,6 +28,17 @@ source "drivers/nvmem/layouts/Kconfig" # Devices +config NVMEM_AN8855_EFUSE + tristate "Airoha AN8855 eFuse support" + depends on MFD_AIROHA_AN8855 || COMPILE_TEST + help + Say y here to enable support for reading eFuses on Airoha AN8855 + Switch. These are e.g. used to store factory programmed + calibration data required for the PHY. + + This driver can also be built as a module. If so, the module will + be called nvmem-an8855-efuse. + config NVMEM_APPLE_EFUSES tristate "Apple eFuse support" depends on ARCH_APPLE || COMPILE_TEST @@ -240,6 +251,16 @@ config NVMEM_NINTENDO_OTP This driver can also be built as a module. If so, the module will be called nvmem-nintendo-otp. +config NVMEM_S32G_OCOTP + tristate "S32G SoC OCOTP support" + depends on ARCH_S32 + help + This is a driver for the 'OCOTP' peripheral available on S32G + platforms. + + If you say Y here, you will get support for the One Time + Programmable memory pages. + config NVMEM_QCOM_QFPROM tristate "QCOM QFPROM Support" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 2021d59688db..70a4464dcb1e 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -10,6 +10,8 @@ nvmem_layouts-y := layouts.o obj-y += layouts/ # Devices +obj-$(CONFIG_NVMEM_AN8855_EFUSE) += nvmem-an8855-efuse.o +nvmem-an8855-efuse-y := an8855-efuse.o obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o nvmem-apple-efuses-y := apple-efuses.o obj-$(CONFIG_NVMEM_APPLE_SPMI) += apple_nvmem_spmi.o @@ -79,6 +81,8 @@ obj-$(CONFIG_NVMEM_SUNPLUS_OCOTP) += nvmem_sunplus_ocotp.o nvmem_sunplus_ocotp-y := sunplus-ocotp.o obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem_sunxi_sid.o nvmem_sunxi_sid-y := sunxi_sid.o +obj-$(CONFIG_NVMEM_S32G_OCOTP) += nvmem-s32g-ocotp-nvmem.o +nvmem-s32g-ocotp-nvmem-y := s32g-ocotp-nvmem.o obj-$(CONFIG_NVMEM_U_BOOT_ENV) += nvmem_u-boot-env.o nvmem_u-boot-env-y := u-boot-env.o obj-$(CONFIG_NVMEM_UNIPHIER_EFUSE) += nvmem-uniphier-efuse.o diff --git a/drivers/nvmem/an8855-efuse.c b/drivers/nvmem/an8855-efuse.c new file mode 100644 index 000000000000..d1afde6f623f --- /dev/null +++ b/drivers/nvmem/an8855-efuse.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha AN8855 Switch EFUSE Driver + */ + +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define AN8855_EFUSE_CELL 50 + +#define AN8855_EFUSE_DATA0 0x1000a500 +#define AN8855_EFUSE_R50O GENMASK(30, 24) + +static int an8855_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct regmap *regmap = context; + + return regmap_bulk_read(regmap, AN8855_EFUSE_DATA0 + offset, + val, bytes / sizeof(u32)); +} + +static int an8855_efuse_probe(struct platform_device *pdev) +{ + struct nvmem_config an8855_nvmem_config = { + .name = "an8855-efuse", + .size = AN8855_EFUSE_CELL * sizeof(u32), + .stride = sizeof(u32), + .word_size = sizeof(u32), + .reg_read = an8855_efuse_read, + }; + struct device *dev = &pdev->dev; + struct nvmem_device *nvmem; + struct regmap *regmap; + + /* Assign NVMEM priv to MFD regmap */ + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENOENT; + + an8855_nvmem_config.priv = regmap; + an8855_nvmem_config.dev = dev; + nvmem = devm_nvmem_register(dev, &an8855_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id an8855_efuse_of_match[] = { + { .compatible = "airoha,an8855-efuse", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, an8855_efuse_of_match); + +static struct platform_driver an8855_efuse_driver = { + .probe = an8855_efuse_probe, + .driver = { + .name = "an8855-efuse", + .of_match_table = an8855_efuse_of_match, + }, +}; +module_platform_driver(an8855_efuse_driver); + +MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>"); +MODULE_DESCRIPTION("Driver for AN8855 Switch EFUSE"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/s32g-ocotp-nvmem.c b/drivers/nvmem/s32g-ocotp-nvmem.c new file mode 100644 index 000000000000..119871ab3a94 --- /dev/null +++ b/drivers/nvmem/s32g-ocotp-nvmem.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2023-2025 NXP + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +struct s32g_ocotp_priv { + struct device *dev; + void __iomem *base; +}; + +static int s32g_ocotp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct s32g_ocotp_priv *s32g_data = context; + u32 *dst = val; + + while (bytes >= sizeof(u32)) { + *dst++ = ioread32(s32g_data->base + offset); + + bytes -= sizeof(u32); + offset += sizeof(u32); + } + + return 0; +} + +static struct nvmem_keepout s32g_keepouts[] = { + { .start = 0, .end = 520 }, + { .start = 540, .end = 564 }, + { .start = 596, .end = 664 }, + { .start = 668, .end = 676 }, + { .start = 684, .end = 732 }, + { .start = 744, .end = 864 }, + { .start = 908, .end = 924 }, + { .start = 928, .end = 936 }, + { .start = 948, .end = 964 }, + { .start = 968, .end = 976 }, + { .start = 984, .end = 1012 }, +}; + +static struct nvmem_config s32g_ocotp_nvmem_config = { + .name = "s32g-ocotp", + .add_legacy_fixed_of_cells = true, + .read_only = true, + .word_size = 4, + .reg_read = s32g_ocotp_read, + .keepout = s32g_keepouts, + .nkeepout = ARRAY_SIZE(s32g_keepouts), +}; + +static const struct of_device_id ocotp_of_match[] = { + { .compatible = "nxp,s32g2-ocotp" }, + { /* sentinel */ } +}; + +static int s32g_ocotp_probe(struct platform_device *pdev) +{ + struct s32g_ocotp_priv *s32g_data; + struct device *dev = &pdev->dev; + struct nvmem_device *nvmem; + struct resource *res; + + s32g_data = devm_kzalloc(dev, sizeof(*s32g_data), GFP_KERNEL); + if (!s32g_data) + return -ENOMEM; + + s32g_data->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(s32g_data->base)) + return dev_err_probe(dev, PTR_ERR(s32g_data->base), + "Cannot map OCOTP device.\n"); + + s32g_data->dev = dev; + s32g_ocotp_nvmem_config.dev = dev; + s32g_ocotp_nvmem_config.priv = s32g_data; + s32g_ocotp_nvmem_config.size = resource_size(res); + + nvmem = devm_nvmem_register(dev, &s32g_ocotp_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static struct platform_driver s32g_ocotp_driver = { + .probe = s32g_ocotp_probe, + .driver = { + .name = "s32g-ocotp", + .of_match_table = ocotp_of_match, + }, +}; +module_platform_driver(s32g_ocotp_driver); +MODULE_AUTHOR("NXP"); +MODULE_DESCRIPTION("S32G OCOTP driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/parisc/eisa_eeprom.c b/drivers/parisc/eisa_eeprom.c index 443b15422fc1..601cbb22574f 100644 --- a/drivers/parisc/eisa_eeprom.c +++ b/drivers/parisc/eisa_eeprom.c @@ -15,8 +15,6 @@ #include <linux/uaccess.h> #include <asm/eisa_eeprom.h> -#define EISA_EEPROM_MINOR 241 - static loff_t eisa_eeprom_llseek(struct file *file, loff_t offset, int origin) { return fixed_size_llseek(file, offset, origin, HPEE_MAX_LENGTH); diff --git a/drivers/peci/controller/peci-npcm.c b/drivers/peci/controller/peci-npcm.c index c77591ca583d..931868991241 100644 --- a/drivers/peci/controller/peci-npcm.c +++ b/drivers/peci/controller/peci-npcm.c @@ -221,7 +221,6 @@ static const struct regmap_config npcm_peci_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = NPCM_PECI_MAX_REG, - .fast_io = true, }; static const struct peci_controller_ops npcm_ops = { diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c index 92d1b62ea239..e9389876229e 100644 --- a/drivers/pps/kapi.c +++ b/drivers/pps/kapi.c @@ -109,16 +109,13 @@ struct pps_device *pps_register_source(struct pps_source_info *info, if (err < 0) { pr_err("%s: unable to create char device\n", info->name); - goto kfree_pps; + goto pps_register_source_exit; } dev_dbg(&pps->dev, "new PPS source %s\n", info->name); return pps; -kfree_pps: - kfree(pps); - pps_register_source_exit: pr_err("%s: unable to register source\n", info->name); diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index 9463232af8d2..c6b8b6478276 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c @@ -374,6 +374,7 @@ int pps_register_cdev(struct pps_device *pps) pps->info.name); err = -EBUSY; } + kfree(pps); goto out_unlock; } pps->id = err; @@ -383,13 +384,11 @@ int pps_register_cdev(struct pps_device *pps) pps->dev.devt = MKDEV(pps_major, pps->id); dev_set_drvdata(&pps->dev, pps); dev_set_name(&pps->dev, "pps%d", pps->id); + pps->dev.release = pps_device_destruct; err = device_register(&pps->dev); if (err) goto free_idr; - /* Override the release function with our own */ - pps->dev.release = pps_device_destruct; - pr_debug("source %s got cdev (%d:%d)\n", pps->info.name, pps_major, pps->id); diff --git a/drivers/siox/siox-bus-gpio.c b/drivers/siox/siox-bus-gpio.c index d6f936464063..413d5f92311c 100644 --- a/drivers/siox/siox-bus-gpio.c +++ b/drivers/siox/siox-bus-gpio.c @@ -93,8 +93,7 @@ static int siox_gpio_probe(struct platform_device *pdev) smaster = devm_siox_master_alloc(dev, sizeof(*ddata)); if (!smaster) - return dev_err_probe(dev, -ENOMEM, - "failed to allocate siox master\n"); + return -ENOMEM; platform_set_drvdata(pdev, smaster); ddata = siox_master_get_devdata(smaster); diff --git a/drivers/slimbus/Kconfig b/drivers/slimbus/Kconfig index a0fdf9d792cb..60b0dcbc0ebb 100644 --- a/drivers/slimbus/Kconfig +++ b/drivers/slimbus/Kconfig @@ -13,13 +13,6 @@ menuconfig SLIMBUS if SLIMBUS # SLIMbus controllers -config SLIM_QCOM_CTRL - tristate "Qualcomm SLIMbus Manager Component" - depends on HAS_IOMEM - help - Select driver if Qualcomm's SLIMbus Manager Component is - programmed using Linux kernel. - config SLIM_QCOM_NGD_CTRL tristate "Qualcomm SLIMbus Satellite Non-Generic Device Component" depends on HAS_IOMEM && DMA_ENGINE && NET diff --git a/drivers/slimbus/Makefile b/drivers/slimbus/Makefile index d9aa011b6804..3cfb41c3b592 100644 --- a/drivers/slimbus/Makefile +++ b/drivers/slimbus/Makefile @@ -6,8 +6,5 @@ obj-$(CONFIG_SLIMBUS) += slimbus.o slimbus-y := core.o messaging.o sched.o stream.o #Controllers -obj-$(CONFIG_SLIM_QCOM_CTRL) += slim-qcom-ctrl.o -slim-qcom-ctrl-y := qcom-ctrl.o - obj-$(CONFIG_SLIM_QCOM_NGD_CTRL) += slim-qcom-ngd-ctrl.o slim-qcom-ngd-ctrl-y := qcom-ngd-ctrl.o diff --git a/drivers/slimbus/messaging.c b/drivers/slimbus/messaging.c index 6f01d944f9c6..e2dbe4a66b70 100644 --- a/drivers/slimbus/messaging.c +++ b/drivers/slimbus/messaging.c @@ -143,8 +143,6 @@ int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn) if (!txn->msg->comp) txn->comp = &done; - else - txn->comp = txn->comp; } ret = ctrl->xfer_msg(ctrl, txn); @@ -224,7 +222,7 @@ static u16 slim_slicesize(int code) /** * slim_xfer_msg() - Transfer a value info message on slim device * - * @sbdev: slim device to which this msg has to be transfered + * @sbdev: slim device to which this msg has to be transferred * @msg: value info message pointer * @mc: message code of the message * diff --git a/drivers/slimbus/qcom-ctrl.c b/drivers/slimbus/qcom-ctrl.c deleted file mode 100644 index ab344f7472f2..000000000000 --- a/drivers/slimbus/qcom-ctrl.c +++ /dev/null @@ -1,735 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (c) 2011-2017, The Linux Foundation - */ - -#include <linux/irq.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/slab.h> -#include <linux/io.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/delay.h> -#include <linux/clk.h> -#include <linux/of.h> -#include <linux/pm_runtime.h> -#include "slimbus.h" - -/* Manager registers */ -#define MGR_CFG 0x200 -#define MGR_STATUS 0x204 -#define MGR_INT_EN 0x210 -#define MGR_INT_STAT 0x214 -#define MGR_INT_CLR 0x218 -#define MGR_TX_MSG 0x230 -#define MGR_RX_MSG 0x270 -#define MGR_IE_STAT 0x2F0 -#define MGR_VE_STAT 0x300 -#define MGR_CFG_ENABLE 1 - -/* Framer registers */ -#define FRM_CFG 0x400 -#define FRM_STAT 0x404 -#define FRM_INT_EN 0x410 -#define FRM_INT_STAT 0x414 -#define FRM_INT_CLR 0x418 -#define FRM_WAKEUP 0x41C -#define FRM_CLKCTL_DONE 0x420 -#define FRM_IE_STAT 0x430 -#define FRM_VE_STAT 0x440 - -/* Interface registers */ -#define INTF_CFG 0x600 -#define INTF_STAT 0x604 -#define INTF_INT_EN 0x610 -#define INTF_INT_STAT 0x614 -#define INTF_INT_CLR 0x618 -#define INTF_IE_STAT 0x630 -#define INTF_VE_STAT 0x640 - -/* Interrupt status bits */ -#define MGR_INT_TX_NACKED_2 BIT(25) -#define MGR_INT_MSG_BUF_CONTE BIT(26) -#define MGR_INT_RX_MSG_RCVD BIT(30) -#define MGR_INT_TX_MSG_SENT BIT(31) - -/* Framer config register settings */ -#define FRM_ACTIVE 1 -#define CLK_GEAR 7 -#define ROOT_FREQ 11 -#define REF_CLK_GEAR 15 -#define INTR_WAKE 19 - -#define SLIM_MSG_ASM_FIRST_WORD(l, mt, mc, dt, ad) \ - ((l) | ((mt) << 5) | ((mc) << 8) | ((dt) << 15) | ((ad) << 16)) - -#define SLIM_ROOT_FREQ 24576000 -#define QCOM_SLIM_AUTOSUSPEND 1000 - -/* MAX message size over control channel */ -#define SLIM_MSGQ_BUF_LEN 40 -#define QCOM_TX_MSGS 2 -#define QCOM_RX_MSGS 8 -#define QCOM_BUF_ALLOC_RETRIES 10 - -#define CFG_PORT(r, v) ((v) ? CFG_PORT_V2(r) : CFG_PORT_V1(r)) - -/* V2 Component registers */ -#define CFG_PORT_V2(r) ((r ## _V2)) -#define COMP_CFG_V2 4 -#define COMP_TRUST_CFG_V2 0x3000 - -/* V1 Component registers */ -#define CFG_PORT_V1(r) ((r ## _V1)) -#define COMP_CFG_V1 0 -#define COMP_TRUST_CFG_V1 0x14 - -/* Resource group info for manager, and non-ported generic device-components */ -#define EE_MGR_RSC_GRP (1 << 10) -#define EE_NGD_2 (2 << 6) -#define EE_NGD_1 0 - -struct slim_ctrl_buf { - void *base; - spinlock_t lock; - int head; - int tail; - int sl_sz; - int n; -}; - -struct qcom_slim_ctrl { - struct slim_controller ctrl; - struct slim_framer framer; - struct device *dev; - void __iomem *base; - void __iomem *slew_reg; - - struct slim_ctrl_buf rx; - struct slim_ctrl_buf tx; - - struct completion **wr_comp; - int irq; - struct workqueue_struct *rxwq; - struct work_struct wd; - struct clk *rclk; - struct clk *hclk; -}; - -static void qcom_slim_queue_tx(struct qcom_slim_ctrl *ctrl, void *buf, - u8 len, u32 tx_reg) -{ - int count = (len + 3) >> 2; - - __iowrite32_copy(ctrl->base + tx_reg, buf, count); - - /* Ensure Oder of subsequent writes */ - mb(); -} - -static void *slim_alloc_rxbuf(struct qcom_slim_ctrl *ctrl) -{ - unsigned long flags; - int idx; - - spin_lock_irqsave(&ctrl->rx.lock, flags); - if ((ctrl->rx.tail + 1) % ctrl->rx.n == ctrl->rx.head) { - spin_unlock_irqrestore(&ctrl->rx.lock, flags); - dev_err(ctrl->dev, "RX QUEUE full!"); - return NULL; - } - idx = ctrl->rx.tail; - ctrl->rx.tail = (ctrl->rx.tail + 1) % ctrl->rx.n; - spin_unlock_irqrestore(&ctrl->rx.lock, flags); - - return ctrl->rx.base + (idx * ctrl->rx.sl_sz); -} - -static void slim_ack_txn(struct qcom_slim_ctrl *ctrl, int err) -{ - struct completion *comp; - unsigned long flags; - int idx; - - spin_lock_irqsave(&ctrl->tx.lock, flags); - idx = ctrl->tx.head; - ctrl->tx.head = (ctrl->tx.head + 1) % ctrl->tx.n; - spin_unlock_irqrestore(&ctrl->tx.lock, flags); - - comp = ctrl->wr_comp[idx]; - ctrl->wr_comp[idx] = NULL; - - complete(comp); -} - -static irqreturn_t qcom_slim_handle_tx_irq(struct qcom_slim_ctrl *ctrl, - u32 stat) -{ - int err = 0; - - if (stat & MGR_INT_TX_MSG_SENT) - writel_relaxed(MGR_INT_TX_MSG_SENT, - ctrl->base + MGR_INT_CLR); - - if (stat & MGR_INT_TX_NACKED_2) { - u32 mgr_stat = readl_relaxed(ctrl->base + MGR_STATUS); - u32 mgr_ie_stat = readl_relaxed(ctrl->base + MGR_IE_STAT); - u32 frm_stat = readl_relaxed(ctrl->base + FRM_STAT); - u32 frm_cfg = readl_relaxed(ctrl->base + FRM_CFG); - u32 frm_intr_stat = readl_relaxed(ctrl->base + FRM_INT_STAT); - u32 frm_ie_stat = readl_relaxed(ctrl->base + FRM_IE_STAT); - u32 intf_stat = readl_relaxed(ctrl->base + INTF_STAT); - u32 intf_intr_stat = readl_relaxed(ctrl->base + INTF_INT_STAT); - u32 intf_ie_stat = readl_relaxed(ctrl->base + INTF_IE_STAT); - - writel_relaxed(MGR_INT_TX_NACKED_2, ctrl->base + MGR_INT_CLR); - - dev_err(ctrl->dev, "TX Nack MGR:int:0x%x, stat:0x%x\n", - stat, mgr_stat); - dev_err(ctrl->dev, "TX Nack MGR:ie:0x%x\n", mgr_ie_stat); - dev_err(ctrl->dev, "TX Nack FRM:int:0x%x, stat:0x%x\n", - frm_intr_stat, frm_stat); - dev_err(ctrl->dev, "TX Nack FRM:cfg:0x%x, ie:0x%x\n", - frm_cfg, frm_ie_stat); - dev_err(ctrl->dev, "TX Nack INTF:intr:0x%x, stat:0x%x\n", - intf_intr_stat, intf_stat); - dev_err(ctrl->dev, "TX Nack INTF:ie:0x%x\n", - intf_ie_stat); - err = -ENOTCONN; - } - - slim_ack_txn(ctrl, err); - - return IRQ_HANDLED; -} - -static irqreturn_t qcom_slim_handle_rx_irq(struct qcom_slim_ctrl *ctrl, - u32 stat) -{ - u32 *rx_buf, pkt[10]; - bool q_rx = false; - u8 mc, mt, len; - - pkt[0] = readl_relaxed(ctrl->base + MGR_RX_MSG); - mt = SLIM_HEADER_GET_MT(pkt[0]); - len = SLIM_HEADER_GET_RL(pkt[0]); - mc = SLIM_HEADER_GET_MC(pkt[0]>>8); - - /* - * this message cannot be handled by ISR, so - * let work-queue handle it - */ - if (mt == SLIM_MSG_MT_CORE && mc == SLIM_MSG_MC_REPORT_PRESENT) { - rx_buf = (u32 *)slim_alloc_rxbuf(ctrl); - if (!rx_buf) { - dev_err(ctrl->dev, "dropping RX:0x%x due to RX full\n", - pkt[0]); - goto rx_ret_irq; - } - rx_buf[0] = pkt[0]; - - } else { - rx_buf = pkt; - } - - __ioread32_copy(rx_buf + 1, ctrl->base + MGR_RX_MSG + 4, - DIV_ROUND_UP(len, 4)); - - switch (mc) { - - case SLIM_MSG_MC_REPORT_PRESENT: - q_rx = true; - break; - case SLIM_MSG_MC_REPLY_INFORMATION: - case SLIM_MSG_MC_REPLY_VALUE: - slim_msg_response(&ctrl->ctrl, (u8 *)(rx_buf + 1), - (u8)(*rx_buf >> 24), (len - 4)); - break; - default: - dev_err(ctrl->dev, "unsupported MC,%x MT:%x\n", - mc, mt); - break; - } -rx_ret_irq: - writel(MGR_INT_RX_MSG_RCVD, ctrl->base + - MGR_INT_CLR); - if (q_rx) - queue_work(ctrl->rxwq, &ctrl->wd); - - return IRQ_HANDLED; -} - -static irqreturn_t qcom_slim_interrupt(int irq, void *d) -{ - struct qcom_slim_ctrl *ctrl = d; - u32 stat = readl_relaxed(ctrl->base + MGR_INT_STAT); - int ret = IRQ_NONE; - - if (stat & MGR_INT_TX_MSG_SENT || stat & MGR_INT_TX_NACKED_2) - ret = qcom_slim_handle_tx_irq(ctrl, stat); - - if (stat & MGR_INT_RX_MSG_RCVD) - ret = qcom_slim_handle_rx_irq(ctrl, stat); - - return ret; -} - -static int qcom_clk_pause_wakeup(struct slim_controller *sctrl) -{ - struct qcom_slim_ctrl *ctrl = dev_get_drvdata(sctrl->dev); - - clk_prepare_enable(ctrl->hclk); - clk_prepare_enable(ctrl->rclk); - enable_irq(ctrl->irq); - - writel_relaxed(1, ctrl->base + FRM_WAKEUP); - /* Make sure framer wakeup write goes through before ISR fires */ - mb(); - /* - * HW Workaround: Currently, slave is reporting lost-sync messages - * after SLIMbus comes out of clock pause. - * Transaction with slave fail before slave reports that message - * Give some time for that report to come - * SLIMbus wakes up in clock gear 10 at 24.576MHz. With each superframe - * being 250 usecs, we wait for 5-10 superframes here to ensure - * we get the message - */ - usleep_range(1250, 2500); - return 0; -} - -static void *slim_alloc_txbuf(struct qcom_slim_ctrl *ctrl, - struct slim_msg_txn *txn, - struct completion *done) -{ - unsigned long flags; - int idx; - - spin_lock_irqsave(&ctrl->tx.lock, flags); - if (((ctrl->tx.head + 1) % ctrl->tx.n) == ctrl->tx.tail) { - spin_unlock_irqrestore(&ctrl->tx.lock, flags); - dev_err(ctrl->dev, "controller TX buf unavailable"); - return NULL; - } - idx = ctrl->tx.tail; - ctrl->wr_comp[idx] = done; - ctrl->tx.tail = (ctrl->tx.tail + 1) % ctrl->tx.n; - - spin_unlock_irqrestore(&ctrl->tx.lock, flags); - - return ctrl->tx.base + (idx * ctrl->tx.sl_sz); -} - - -static int qcom_xfer_msg(struct slim_controller *sctrl, - struct slim_msg_txn *txn) -{ - struct qcom_slim_ctrl *ctrl = dev_get_drvdata(sctrl->dev); - DECLARE_COMPLETION_ONSTACK(done); - void *pbuf = slim_alloc_txbuf(ctrl, txn, &done); - unsigned long ms = txn->rl + HZ; - u8 *puc; - int ret = 0, retries = QCOM_BUF_ALLOC_RETRIES; - unsigned long time_left; - u8 la = txn->la; - u32 *head; - /* HW expects length field to be excluded */ - txn->rl--; - - /* spin till buffer is made available */ - if (!pbuf) { - while (retries--) { - usleep_range(10000, 15000); - pbuf = slim_alloc_txbuf(ctrl, txn, &done); - if (pbuf) - break; - } - } - - if (retries < 0 && !pbuf) - return -ENOMEM; - - puc = (u8 *)pbuf; - head = (u32 *)pbuf; - - if (txn->dt == SLIM_MSG_DEST_LOGICALADDR) { - *head = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt, - txn->mc, 0, la); - puc += 3; - } else { - *head = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt, - txn->mc, 1, la); - puc += 2; - } - - if (slim_tid_txn(txn->mt, txn->mc)) - *(puc++) = txn->tid; - - if (slim_ec_txn(txn->mt, txn->mc)) { - *(puc++) = (txn->ec & 0xFF); - *(puc++) = (txn->ec >> 8) & 0xFF; - } - - if (txn->msg && txn->msg->wbuf) - memcpy(puc, txn->msg->wbuf, txn->msg->num_bytes); - - qcom_slim_queue_tx(ctrl, head, txn->rl, MGR_TX_MSG); - time_left = wait_for_completion_timeout(&done, msecs_to_jiffies(ms)); - - if (!time_left) { - dev_err(ctrl->dev, "TX timed out:MC:0x%x,mt:0x%x", txn->mc, - txn->mt); - ret = -ETIMEDOUT; - } - - return ret; - -} - -static int qcom_set_laddr(struct slim_controller *sctrl, - struct slim_eaddr *ead, u8 laddr) -{ - struct qcom_slim_ctrl *ctrl = dev_get_drvdata(sctrl->dev); - struct { - __be16 manf_id; - __be16 prod_code; - u8 dev_index; - u8 instance; - u8 laddr; - } __packed p; - struct slim_val_inf msg = {0}; - DEFINE_SLIM_EDEST_TXN(txn, SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS, - 10, laddr, &msg); - int ret; - - p.manf_id = cpu_to_be16(ead->manf_id); - p.prod_code = cpu_to_be16(ead->prod_code); - p.dev_index = ead->dev_index; - p.instance = ead->instance; - p.laddr = laddr; - - msg.wbuf = (void *)&p; - msg.num_bytes = 7; - ret = slim_do_transfer(&ctrl->ctrl, &txn); - - if (ret) - dev_err(ctrl->dev, "set LA:0x%x failed:ret:%d\n", - laddr, ret); - return ret; -} - -static int slim_get_current_rxbuf(struct qcom_slim_ctrl *ctrl, void *buf) -{ - unsigned long flags; - - spin_lock_irqsave(&ctrl->rx.lock, flags); - if (ctrl->rx.tail == ctrl->rx.head) { - spin_unlock_irqrestore(&ctrl->rx.lock, flags); - return -ENODATA; - } - memcpy(buf, ctrl->rx.base + (ctrl->rx.head * ctrl->rx.sl_sz), - ctrl->rx.sl_sz); - - ctrl->rx.head = (ctrl->rx.head + 1) % ctrl->rx.n; - spin_unlock_irqrestore(&ctrl->rx.lock, flags); - - return 0; -} - -static void qcom_slim_rxwq(struct work_struct *work) -{ - u8 buf[SLIM_MSGQ_BUF_LEN]; - u8 mc, mt; - int ret; - struct qcom_slim_ctrl *ctrl = container_of(work, struct qcom_slim_ctrl, - wd); - - while ((slim_get_current_rxbuf(ctrl, buf)) != -ENODATA) { - mt = SLIM_HEADER_GET_MT(buf[0]); - mc = SLIM_HEADER_GET_MC(buf[1]); - if (mt == SLIM_MSG_MT_CORE && - mc == SLIM_MSG_MC_REPORT_PRESENT) { - struct slim_eaddr ea; - u8 laddr; - - ea.manf_id = be16_to_cpup((__be16 *)&buf[2]); - ea.prod_code = be16_to_cpup((__be16 *)&buf[4]); - ea.dev_index = buf[6]; - ea.instance = buf[7]; - - ret = slim_device_report_present(&ctrl->ctrl, &ea, - &laddr); - if (ret < 0) - dev_err(ctrl->dev, "assign laddr failed:%d\n", - ret); - } else { - dev_err(ctrl->dev, "unexpected message:mc:%x, mt:%x\n", - mc, mt); - } - } -} - -static void qcom_slim_prg_slew(struct platform_device *pdev, - struct qcom_slim_ctrl *ctrl) -{ - if (!ctrl->slew_reg) { - /* SLEW RATE register for this SLIMbus */ - ctrl->slew_reg = devm_platform_ioremap_resource_byname(pdev, "slew"); - if (IS_ERR(ctrl->slew_reg)) - return; - } - - writel_relaxed(1, ctrl->slew_reg); - /* Make sure SLIMbus-slew rate enabling goes through */ - wmb(); -} - -static int qcom_slim_probe(struct platform_device *pdev) -{ - struct qcom_slim_ctrl *ctrl; - struct slim_controller *sctrl; - int ret, ver; - - ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL); - if (!ctrl) - return -ENOMEM; - - ctrl->hclk = devm_clk_get(&pdev->dev, "iface"); - if (IS_ERR(ctrl->hclk)) - return PTR_ERR(ctrl->hclk); - - ctrl->rclk = devm_clk_get(&pdev->dev, "core"); - if (IS_ERR(ctrl->rclk)) - return PTR_ERR(ctrl->rclk); - - ret = clk_set_rate(ctrl->rclk, SLIM_ROOT_FREQ); - if (ret) { - dev_err(&pdev->dev, "ref-clock set-rate failed:%d\n", ret); - return ret; - } - - ctrl->irq = platform_get_irq(pdev, 0); - if (ctrl->irq < 0) - return ctrl->irq; - - sctrl = &ctrl->ctrl; - sctrl->dev = &pdev->dev; - ctrl->dev = &pdev->dev; - platform_set_drvdata(pdev, ctrl); - dev_set_drvdata(ctrl->dev, ctrl); - - ctrl->base = devm_platform_ioremap_resource_byname(pdev, "ctrl"); - if (IS_ERR(ctrl->base)) - return PTR_ERR(ctrl->base); - - sctrl->set_laddr = qcom_set_laddr; - sctrl->xfer_msg = qcom_xfer_msg; - sctrl->wakeup = qcom_clk_pause_wakeup; - ctrl->tx.n = QCOM_TX_MSGS; - ctrl->tx.sl_sz = SLIM_MSGQ_BUF_LEN; - ctrl->rx.n = QCOM_RX_MSGS; - ctrl->rx.sl_sz = SLIM_MSGQ_BUF_LEN; - ctrl->wr_comp = kcalloc(QCOM_TX_MSGS, sizeof(struct completion *), - GFP_KERNEL); - if (!ctrl->wr_comp) - return -ENOMEM; - - spin_lock_init(&ctrl->rx.lock); - spin_lock_init(&ctrl->tx.lock); - INIT_WORK(&ctrl->wd, qcom_slim_rxwq); - ctrl->rxwq = create_singlethread_workqueue("qcom_slim_rx"); - if (!ctrl->rxwq) { - dev_err(ctrl->dev, "Failed to start Rx WQ\n"); - return -ENOMEM; - } - - ctrl->framer.rootfreq = SLIM_ROOT_FREQ / 8; - ctrl->framer.superfreq = - ctrl->framer.rootfreq / SLIM_CL_PER_SUPERFRAME_DIV8; - sctrl->a_framer = &ctrl->framer; - sctrl->clkgear = SLIM_MAX_CLK_GEAR; - - qcom_slim_prg_slew(pdev, ctrl); - - ret = devm_request_irq(&pdev->dev, ctrl->irq, qcom_slim_interrupt, - IRQF_TRIGGER_HIGH, "qcom_slim_irq", ctrl); - if (ret) { - dev_err(&pdev->dev, "request IRQ failed\n"); - goto err_request_irq_failed; - } - - ret = clk_prepare_enable(ctrl->hclk); - if (ret) - goto err_hclk_enable_failed; - - ret = clk_prepare_enable(ctrl->rclk); - if (ret) - goto err_rclk_enable_failed; - - ctrl->tx.base = devm_kcalloc(&pdev->dev, ctrl->tx.n, ctrl->tx.sl_sz, - GFP_KERNEL); - if (!ctrl->tx.base) { - ret = -ENOMEM; - goto err; - } - - ctrl->rx.base = devm_kcalloc(&pdev->dev,ctrl->rx.n, ctrl->rx.sl_sz, - GFP_KERNEL); - if (!ctrl->rx.base) { - ret = -ENOMEM; - goto err; - } - - /* Register with framework before enabling frame, clock */ - ret = slim_register_controller(&ctrl->ctrl); - if (ret) { - dev_err(ctrl->dev, "error adding controller\n"); - goto err; - } - - ver = readl_relaxed(ctrl->base); - /* Version info in 16 MSbits */ - ver >>= 16; - /* Component register initialization */ - writel(1, ctrl->base + CFG_PORT(COMP_CFG, ver)); - writel((EE_MGR_RSC_GRP | EE_NGD_2 | EE_NGD_1), - ctrl->base + CFG_PORT(COMP_TRUST_CFG, ver)); - - writel((MGR_INT_TX_NACKED_2 | - MGR_INT_MSG_BUF_CONTE | MGR_INT_RX_MSG_RCVD | - MGR_INT_TX_MSG_SENT), ctrl->base + MGR_INT_EN); - writel(1, ctrl->base + MGR_CFG); - /* Framer register initialization */ - writel((1 << INTR_WAKE) | (0xA << REF_CLK_GEAR) | - (0xA << CLK_GEAR) | (1 << ROOT_FREQ) | (1 << FRM_ACTIVE) | 1, - ctrl->base + FRM_CFG); - writel(MGR_CFG_ENABLE, ctrl->base + MGR_CFG); - writel(1, ctrl->base + INTF_CFG); - writel(1, ctrl->base + CFG_PORT(COMP_CFG, ver)); - - pm_runtime_use_autosuspend(&pdev->dev); - pm_runtime_set_autosuspend_delay(&pdev->dev, QCOM_SLIM_AUTOSUSPEND); - pm_runtime_set_active(&pdev->dev); - pm_runtime_mark_last_busy(&pdev->dev); - pm_runtime_enable(&pdev->dev); - - dev_dbg(ctrl->dev, "QCOM SB controller is up:ver:0x%x!\n", ver); - return 0; - -err: - clk_disable_unprepare(ctrl->rclk); -err_rclk_enable_failed: - clk_disable_unprepare(ctrl->hclk); -err_hclk_enable_failed: -err_request_irq_failed: - destroy_workqueue(ctrl->rxwq); - return ret; -} - -static void qcom_slim_remove(struct platform_device *pdev) -{ - struct qcom_slim_ctrl *ctrl = platform_get_drvdata(pdev); - - pm_runtime_disable(&pdev->dev); - slim_unregister_controller(&ctrl->ctrl); - clk_disable_unprepare(ctrl->rclk); - clk_disable_unprepare(ctrl->hclk); - destroy_workqueue(ctrl->rxwq); -} - -/* - * If PM_RUNTIME is not defined, these 2 functions become helper - * functions to be called from system suspend/resume. - */ -#ifdef CONFIG_PM -static int qcom_slim_runtime_suspend(struct device *device) -{ - struct qcom_slim_ctrl *ctrl = dev_get_drvdata(device); - int ret; - - dev_dbg(device, "pm_runtime: suspending...\n"); - ret = slim_ctrl_clk_pause(&ctrl->ctrl, false, SLIM_CLK_UNSPECIFIED); - if (ret) { - dev_err(device, "clk pause not entered:%d", ret); - } else { - disable_irq(ctrl->irq); - clk_disable_unprepare(ctrl->hclk); - clk_disable_unprepare(ctrl->rclk); - } - return ret; -} - -static int qcom_slim_runtime_resume(struct device *device) -{ - struct qcom_slim_ctrl *ctrl = dev_get_drvdata(device); - int ret = 0; - - dev_dbg(device, "pm_runtime: resuming...\n"); - ret = slim_ctrl_clk_pause(&ctrl->ctrl, true, 0); - if (ret) - dev_err(device, "clk pause not exited:%d", ret); - return ret; -} -#endif - -#ifdef CONFIG_PM_SLEEP -static int qcom_slim_suspend(struct device *dev) -{ - int ret = 0; - - if (!pm_runtime_enabled(dev) || - (!pm_runtime_suspended(dev))) { - dev_dbg(dev, "system suspend"); - ret = qcom_slim_runtime_suspend(dev); - } - - return ret; -} - -static int qcom_slim_resume(struct device *dev) -{ - if (!pm_runtime_enabled(dev) || !pm_runtime_suspended(dev)) { - int ret; - - dev_dbg(dev, "system resume"); - ret = qcom_slim_runtime_resume(dev); - if (!ret) { - pm_runtime_mark_last_busy(dev); - pm_request_autosuspend(dev); - } - return ret; - - } - return 0; -} -#endif /* CONFIG_PM_SLEEP */ - -static const struct dev_pm_ops qcom_slim_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(qcom_slim_suspend, qcom_slim_resume) - SET_RUNTIME_PM_OPS( - qcom_slim_runtime_suspend, - qcom_slim_runtime_resume, - NULL - ) -}; - -static const struct of_device_id qcom_slim_dt_match[] = { - { .compatible = "qcom,slim", }, - {} -}; -MODULE_DEVICE_TABLE(of, qcom_slim_dt_match); - -static struct platform_driver qcom_slim_driver = { - .probe = qcom_slim_probe, - .remove = qcom_slim_remove, - .driver = { - .name = "qcom_slim_ctrl", - .of_match_table = qcom_slim_dt_match, - .pm = &qcom_slim_dev_pm_ops, - }, -}; -module_platform_driver(qcom_slim_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Qualcomm SLIMbus Controller"); diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c index 4774df778de9..172acf135f3b 100644 --- a/drivers/staging/iio/adc/ad7816.c +++ b/drivers/staging/iio/adc/ad7816.c @@ -359,8 +359,6 @@ static int ad7816_probe(struct spi_device *spi_dev) if (!indio_dev) return -ENOMEM; chip = iio_priv(indio_dev); - /* this is only used for device removal purposes */ - dev_set_drvdata(&spi_dev->dev, indio_dev); chip->spi_dev = spi_dev; for (i = 0; i <= AD7816_CS_MAX; i++) diff --git a/drivers/uio/uio_aec.c b/drivers/uio/uio_aec.c index 8c164e51ff9e..dafcc5f44f24 100644 --- a/drivers/uio/uio_aec.c +++ b/drivers/uio/uio_aec.c @@ -33,7 +33,7 @@ #define MAILBOX 0x0F -static struct pci_device_id ids[] = { +static const struct pci_device_id ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_AEC, PCI_DEVICE_ID_AEC_VITCLTC), }, { 0, } }; diff --git a/drivers/uio/uio_cif.c b/drivers/uio/uio_cif.c index 1cc3b8b5a345..4e4b589ddef1 100644 --- a/drivers/uio/uio_cif.c +++ b/drivers/uio/uio_cif.c @@ -105,7 +105,7 @@ static void hilscher_pci_remove(struct pci_dev *dev) iounmap(info->mem[0].internal_addr); } -static struct pci_device_id hilscher_pci_ids[] = { +static const struct pci_device_id hilscher_pci_ids[] = { { .vendor = PCI_VENDOR_ID_PLX, .device = PCI_DEVICE_ID_PLX_9030, diff --git a/drivers/uio/uio_dmem_genirq.c b/drivers/uio/uio_dmem_genirq.c index 31aa75110ba5..41c18ec62a45 100644 --- a/drivers/uio/uio_dmem_genirq.c +++ b/drivers/uio/uio_dmem_genirq.c @@ -297,28 +297,6 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) return devm_uio_register_device(&pdev->dev, priv->uioinfo); } -static int uio_dmem_genirq_runtime_nop(struct device *dev) -{ - /* Runtime PM callback shared between ->runtime_suspend() - * and ->runtime_resume(). Simply returns success. - * - * In this driver pm_runtime_get_sync() and pm_runtime_put_sync() - * are used at open() and release() time. This allows the - * Runtime PM code to turn off power to the device while the - * device is unused, ie before open() and after release(). - * - * This Runtime PM callback does not need to save or restore - * any registers since user space is responsbile for hardware - * register reinitialization after open(). - */ - return 0; -} - -static const struct dev_pm_ops uio_dmem_genirq_dev_pm_ops = { - .runtime_suspend = uio_dmem_genirq_runtime_nop, - .runtime_resume = uio_dmem_genirq_runtime_nop, -}; - #ifdef CONFIG_OF static const struct of_device_id uio_of_genirq_match[] = { { /* empty for now */ }, @@ -330,7 +308,6 @@ static struct platform_driver uio_dmem_genirq = { .probe = uio_dmem_genirq_probe, .driver = { .name = DRIVER_NAME, - .pm = &uio_dmem_genirq_dev_pm_ops, .of_match_table = of_match_ptr(uio_of_genirq_match), }, }; diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c index f19efad4d6f8..3f8e2e27697f 100644 --- a/drivers/uio/uio_hv_generic.c +++ b/drivers/uio/uio_hv_generic.c @@ -111,7 +111,6 @@ static void hv_uio_channel_cb(void *context) struct hv_device *hv_dev; struct hv_uio_private_data *pdata; - chan->inbound.ring_buffer->interrupt_mask = 1; virt_mb(); /* @@ -183,8 +182,6 @@ hv_uio_new_channel(struct vmbus_channel *new_sc) return; } - /* Disable interrupts on sub channel */ - new_sc->inbound.ring_buffer->interrupt_mask = 1; set_channel_read_mode(new_sc, HV_CALL_ISR); ret = hv_create_ring_sysfs(new_sc, hv_uio_ring_mmap); if (ret) { @@ -227,9 +224,7 @@ hv_uio_open(struct uio_info *info, struct inode *inode) ret = vmbus_connect_ring(dev->channel, hv_uio_channel_cb, dev->channel); - if (ret == 0) - dev->channel->inbound.ring_buffer->interrupt_mask = 1; - else + if (ret) atomic_dec(&pdata->refcnt); return ret; diff --git a/drivers/uio/uio_netx.c b/drivers/uio/uio_netx.c index a1a58802c793..18917b2ac04c 100644 --- a/drivers/uio/uio_netx.c +++ b/drivers/uio/uio_netx.c @@ -127,7 +127,7 @@ static void netx_pci_remove(struct pci_dev *dev) iounmap(info->mem[0].internal_addr); } -static struct pci_device_id netx_pci_ids[] = { +static const struct pci_device_id netx_pci_ids[] = { { .vendor = PCI_VENDOR_ID_HILSCHER, .device = PCI_DEVICE_ID_HILSCHER_NETX, diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c index 2ec7d25e8264..0a1885d1b2e3 100644 --- a/drivers/uio/uio_pdrv_genirq.c +++ b/drivers/uio/uio_pdrv_genirq.c @@ -249,34 +249,11 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) return ret; } -static int uio_pdrv_genirq_runtime_nop(struct device *dev) -{ - /* Runtime PM callback shared between ->runtime_suspend() - * and ->runtime_resume(). Simply returns success. - * - * In this driver pm_runtime_get_sync() and pm_runtime_put_sync() - * are used at open() and release() time. This allows the - * Runtime PM code to turn off power to the device while the - * device is unused, ie before open() and after release(). - * - * This Runtime PM callback does not need to save or restore - * any registers since user space is responsbile for hardware - * register reinitialization after open(). - */ - return 0; -} - -static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = { - .runtime_suspend = uio_pdrv_genirq_runtime_nop, - .runtime_resume = uio_pdrv_genirq_runtime_nop, -}; - #ifdef CONFIG_OF static struct of_device_id uio_of_genirq_match[] = { { /* This is filled with module_parm */ }, { /* Sentinel */ }, }; -MODULE_DEVICE_TABLE(of, uio_of_genirq_match); module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0); MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio"); #endif @@ -285,7 +262,6 @@ static struct platform_driver uio_pdrv_genirq = { .probe = uio_pdrv_genirq_probe, .driver = { .name = DRIVER_NAME, - .pm = &uio_pdrv_genirq_dev_pm_ops, .of_match_table = of_match_ptr(uio_of_genirq_match), }, }; diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c index b93a5f8f4cba..12afc2fa1a0b 100644 --- a/drivers/uio/uio_sercos3.c +++ b/drivers/uio/uio_sercos3.c @@ -191,7 +191,7 @@ static void sercos3_pci_remove(struct pci_dev *dev) } } -static struct pci_device_id sercos3_pci_ids[] = { +static const struct pci_device_id sercos3_pci_ids[] = { { .vendor = PCI_VENDOR_ID_PLX, .device = PCI_DEVICE_ID_PLX_9030, diff --git a/drivers/w1/masters/matrox_w1.c b/drivers/w1/masters/matrox_w1.c index 2852cd2dc67c..146fa7c6e74e 100644 --- a/drivers/w1/masters/matrox_w1.c +++ b/drivers/w1/masters/matrox_w1.c @@ -47,7 +47,6 @@ struct matrox_device { unsigned long phys_addr; void __iomem *virt_addr; - unsigned long found; struct w1_bus_master *bus_master; }; @@ -158,8 +157,6 @@ static int matrox_w1_probe(struct pci_dev *pdev, const struct pci_device_id *ent pci_set_drvdata(pdev, dev); - dev->found = 1; - dev_info(&pdev->dev, "Matrox G400 GPIO transport layer for 1-wire.\n"); return 0; @@ -176,10 +173,9 @@ static void matrox_w1_remove(struct pci_dev *pdev) { struct matrox_device *dev = pci_get_drvdata(pdev); - if (dev->found) { - w1_remove_master_device(dev->bus_master); - iounmap(dev->virt_addr); - } + w1_remove_master_device(dev->bus_master); + iounmap(dev->virt_addr); + kfree(dev); } diff --git a/include/dt-bindings/interconnect/qcom,glymur-rpmh.h b/include/dt-bindings/interconnect/qcom,glymur-rpmh.h new file mode 100644 index 000000000000..6a0e754345e4 --- /dev/null +++ b/include/dt-bindings/interconnect/qcom,glymur-rpmh.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __DT_BINDINGS_INTERCONNECT_QCOM_GLYMUR_H +#define __DT_BINDINGS_INTERCONNECT_QCOM_GLYMUR_H + +#define MASTER_CRYPTO 0 +#define MASTER_SOCCP_PROC 1 +#define MASTER_QDSS_ETR 2 +#define MASTER_QDSS_ETR_1 3 +#define SLAVE_A1NOC_SNOC 4 + +#define MASTER_UFS_MEM 0 +#define MASTER_USB3_2 1 +#define MASTER_USB4_2 2 +#define SLAVE_A2NOC_SNOC 3 + +#define MASTER_QSPI_0 0 +#define MASTER_QUP_0 1 +#define MASTER_QUP_1 2 +#define MASTER_QUP_2 3 +#define MASTER_SP 4 +#define MASTER_SDCC_2 5 +#define MASTER_SDCC_4 6 +#define MASTER_USB2 7 +#define MASTER_USB3_MP 8 +#define SLAVE_A3NOC_SNOC 9 + +#define MASTER_USB3_0 0 +#define MASTER_USB3_1 1 +#define MASTER_USB4_0 2 +#define MASTER_USB4_1 3 +#define SLAVE_A4NOC_HSCNOC 4 + +#define MASTER_QUP_CORE_0 0 +#define MASTER_QUP_CORE_1 1 +#define MASTER_QUP_CORE_2 2 +#define SLAVE_QUP_CORE_0 3 +#define SLAVE_QUP_CORE_1 4 +#define SLAVE_QUP_CORE_2 5 + +#define MASTER_CNOC_CFG 0 +#define SLAVE_AHB2PHY_SOUTH 1 +#define SLAVE_AHB2PHY_NORTH 2 +#define SLAVE_AHB2PHY_2 3 +#define SLAVE_AHB2PHY_3 4 +#define SLAVE_AV1_ENC_CFG 5 +#define SLAVE_CAMERA_CFG 6 +#define SLAVE_CLK_CTL 7 +#define SLAVE_CRYPTO_0_CFG 8 +#define SLAVE_DISPLAY_CFG 9 +#define SLAVE_GFX3D_CFG 10 +#define SLAVE_IMEM_CFG 11 +#define SLAVE_PCIE_0_CFG 12 +#define SLAVE_PCIE_1_CFG 13 +#define SLAVE_PCIE_2_CFG 14 +#define SLAVE_PCIE_3A_CFG 15 +#define SLAVE_PCIE_3B_CFG 16 +#define SLAVE_PCIE_4_CFG 17 +#define SLAVE_PCIE_5_CFG 18 +#define SLAVE_PCIE_6_CFG 19 +#define SLAVE_PCIE_RSCC 20 +#define SLAVE_PDM 21 +#define SLAVE_PRNG 22 +#define SLAVE_QDSS_CFG 23 +#define SLAVE_QSPI_0 24 +#define SLAVE_QUP_0 25 +#define SLAVE_QUP_1 26 +#define SLAVE_QUP_2 27 +#define SLAVE_SDCC_2 28 +#define SLAVE_SDCC_4 29 +#define SLAVE_SMMUV3_CFG 30 +#define SLAVE_TCSR 31 +#define SLAVE_TLMM 32 +#define SLAVE_UFS_MEM_CFG 33 +#define SLAVE_USB2 34 +#define SLAVE_USB3_0 35 +#define SLAVE_USB3_1 36 +#define SLAVE_USB3_2 37 +#define SLAVE_USB3_MP 38 +#define SLAVE_USB4_0 39 +#define SLAVE_USB4_1 40 +#define SLAVE_USB4_2 41 +#define SLAVE_VENUS_CFG 42 +#define SLAVE_CNOC_PCIE_SLAVE_EAST_CFG 43 +#define SLAVE_CNOC_PCIE_SLAVE_WEST_CFG 44 +#define SLAVE_LPASS_QTB_CFG 45 +#define SLAVE_CNOC_MNOC_CFG 46 +#define SLAVE_NSP_QTB_CFG 47 +#define SLAVE_PCIE_EAST_ANOC_CFG 48 +#define SLAVE_PCIE_WEST_ANOC_CFG 49 +#define SLAVE_QDSS_STM 50 +#define SLAVE_TCU 51 + +#define MASTER_HSCNOC_CNOC 0 +#define SLAVE_AOSS 1 +#define SLAVE_IPC_ROUTER_CFG 2 +#define SLAVE_SOCCP 3 +#define SLAVE_TME_CFG 4 +#define SLAVE_APPSS 5 +#define SLAVE_CNOC_CFG 6 +#define SLAVE_BOOT_IMEM 7 +#define SLAVE_IMEM 8 + +#define MASTER_GPU_TCU 0 +#define MASTER_PCIE_TCU 1 +#define MASTER_SYS_TCU 2 +#define MASTER_APPSS_PROC 3 +#define MASTER_AGGRE_NOC_EAST 4 +#define MASTER_GFX3D 5 +#define MASTER_LPASS_GEM_NOC 6 +#define MASTER_MNOC_HF_MEM_NOC 7 +#define MASTER_MNOC_SF_MEM_NOC 8 +#define MASTER_COMPUTE_NOC 9 +#define MASTER_PCIE_EAST 10 +#define MASTER_PCIE_WEST 11 +#define MASTER_SNOC_SF_MEM_NOC 12 +#define MASTER_WLAN_Q6 13 +#define MASTER_GIC 14 +#define SLAVE_HSCNOC_CNOC 15 +#define SLAVE_LLCC 16 +#define SLAVE_PCIE_EAST 17 +#define SLAVE_PCIE_WEST 18 + +#define MASTER_LPIAON_NOC 0 +#define SLAVE_LPASS_GEM_NOC 1 + +#define MASTER_LPASS_LPINOC 0 +#define SLAVE_LPIAON_NOC_LPASS_AG_NOC 1 + +#define MASTER_LPASS_PROC 0 +#define SLAVE_LPICX_NOC_LPIAON_NOC 1 + +#define MASTER_LLCC 0 +#define SLAVE_EBI1 1 + +#define MASTER_AV1_ENC 0 +#define MASTER_CAMNOC_HF 1 +#define MASTER_CAMNOC_ICP 2 +#define MASTER_CAMNOC_SF 3 +#define MASTER_EVA 4 +#define MASTER_MDP 5 +#define MASTER_CDSP_HCP 6 +#define MASTER_VIDEO 7 +#define MASTER_VIDEO_CV_PROC 8 +#define MASTER_VIDEO_V_PROC 9 +#define MASTER_CNOC_MNOC_CFG 10 +#define SLAVE_MNOC_HF_MEM_NOC 11 +#define SLAVE_MNOC_SF_MEM_NOC 12 +#define SLAVE_SERVICE_MNOC 13 + +#define MASTER_CPUCP 0 +#define SLAVE_NSINOC_SYSTEM_NOC 1 +#define SLAVE_SERVICE_NSINOC 2 + +#define MASTER_CDSP_PROC 0 +#define SLAVE_NSP0_HSC_NOC 1 + +#define MASTER_OOBMSS_SP_PROC 0 +#define SLAVE_OOBMSS_SNOC 1 + +#define MASTER_PCIE_EAST_ANOC_CFG 0 +#define MASTER_PCIE_0 1 +#define MASTER_PCIE_1 2 +#define MASTER_PCIE_5 3 +#define SLAVE_PCIE_EAST_MEM_NOC 4 +#define SLAVE_SERVICE_PCIE_EAST_AGGRE_NOC 5 + +#define MASTER_HSCNOC_PCIE_EAST 0 +#define MASTER_CNOC_PCIE_EAST_SLAVE_CFG 1 +#define SLAVE_HSCNOC_PCIE_EAST_MS_MPU_CFG 2 +#define SLAVE_SERVICE_PCIE_EAST 3 +#define SLAVE_PCIE_0 4 +#define SLAVE_PCIE_1 5 +#define SLAVE_PCIE_5 6 + +#define MASTER_PCIE_WEST_ANOC_CFG 0 +#define MASTER_PCIE_2 1 +#define MASTER_PCIE_3A 2 +#define MASTER_PCIE_3B 3 +#define MASTER_PCIE_4 4 +#define MASTER_PCIE_6 5 +#define SLAVE_PCIE_WEST_MEM_NOC 6 +#define SLAVE_SERVICE_PCIE_WEST_AGGRE_NOC 7 + +#define MASTER_HSCNOC_PCIE_WEST 0 +#define MASTER_CNOC_PCIE_WEST_SLAVE_CFG 1 +#define SLAVE_HSCNOC_PCIE_WEST_MS_MPU_CFG 2 +#define SLAVE_SERVICE_PCIE_WEST 3 +#define SLAVE_PCIE_2 4 +#define SLAVE_PCIE_3A 5 +#define SLAVE_PCIE_3B 6 +#define SLAVE_PCIE_4 7 +#define SLAVE_PCIE_6 8 + +#define MASTER_A1NOC_SNOC 0 +#define MASTER_A2NOC_SNOC 1 +#define MASTER_A3NOC_SNOC 2 +#define MASTER_NSINOC_SNOC 3 +#define MASTER_OOBMSS 4 +#define SLAVE_SNOC_GEM_NOC_SF 5 + +#endif diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 4ac65c68bbf4..6de59ce8ef8c 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -474,35 +474,6 @@ static inline bool is_coresight_device(void __iomem *base) return cid == CORESIGHT_CID; } -/* - * Attempt to find and enable "APB clock" for the given device - * - * Returns: - * - * clk - Clock is found and enabled - * NULL - clock is not found - * ERROR - Clock is found but failed to enable - */ -static inline struct clk *coresight_get_enable_apb_pclk(struct device *dev) -{ - struct clk *pclk; - int ret; - - pclk = clk_get(dev, "apb_pclk"); - if (IS_ERR(pclk)) { - pclk = clk_get(dev, "apb"); - if (IS_ERR(pclk)) - return NULL; - } - - ret = clk_prepare_enable(pclk); - if (ret) { - clk_put(pclk); - return ERR_PTR(ret); - } - return pclk; -} - #define CORESIGHT_PIDRn(i) (0xFE0 + ((i) * 4)) static inline u32 coresight_get_pid(struct csdev_access *csa) @@ -733,4 +704,6 @@ void coresight_remove_driver(struct amba_driver *amba_drv, struct platform_driver *pdev_drv); int coresight_etm_get_trace_id(struct coresight_device *csdev, enum cs_mode mode, struct coresight_device *sink); +int coresight_get_enable_clocks(struct device *dev, struct clk **pclk, + struct clk **atclk); #endif /* _LINUX_COREISGHT_H */ diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h index 6a4479616479..a38b277c2c02 100644 --- a/include/linux/iio/consumer.h +++ b/include/linux/iio/consumer.h @@ -382,6 +382,24 @@ int iio_read_channel_scale(struct iio_channel *chan, int *val, int *val2); /** + * iio_multiply_value() - Multiply an IIO value + * @result: Destination pointer for the multiplication result + * @multiplier: Multiplier. + * @type: One of the IIO_VAL_* constants. This decides how the @val and + * @val2 parameters are interpreted. + * @val: Value being multiplied. + * @val2: Value being multiplied. @val2 use depends on type. + * + * Multiply an IIO value with a s64 multiplier storing the result as + * IIO_VAL_INT. This is typically used for scaling. + * + * Returns: + * IIO_VAL_INT on success or a negative error-number on failure. + */ +int iio_multiply_value(int *result, s64 multiplier, + unsigned int type, int val, int val2); + +/** * iio_convert_raw_to_processed() - Converts a raw value to a processed value * @chan: The channel being queried * @raw: The raw IIO to convert diff --git a/include/linux/iio/frequency/adf4350.h b/include/linux/iio/frequency/adf4350.h index de45cf2ee1e4..ce2086f97e3f 100644 --- a/include/linux/iio/frequency/adf4350.h +++ b/include/linux/iio/frequency/adf4350.h @@ -51,7 +51,7 @@ /* REG3 Bit Definitions */ #define ADF4350_REG3_12BIT_CLKDIV(x) ((x) << 3) -#define ADF4350_REG3_12BIT_CLKDIV_MODE(x) ((x) << 16) +#define ADF4350_REG3_12BIT_CLKDIV_MODE(x) ((x) << 15) #define ADF4350_REG3_12BIT_CSR_EN (1 << 18) #define ADF4351_REG3_CHARGE_CANCELLATION_EN (1 << 21) #define ADF4351_REG3_ANTI_BACKLASH_3ns_EN (1 << 22) diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index d11668f14a3e..872ebdf0dd77 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -271,14 +271,14 @@ struct iio_chan_spec { unsigned int num_ext_scan_type; }; }; - long info_mask_separate; - long info_mask_separate_available; - long info_mask_shared_by_type; - long info_mask_shared_by_type_available; - long info_mask_shared_by_dir; - long info_mask_shared_by_dir_available; - long info_mask_shared_by_all; - long info_mask_shared_by_all_available; + unsigned long info_mask_separate; + unsigned long info_mask_separate_available; + unsigned long info_mask_shared_by_type; + unsigned long info_mask_shared_by_type_available; + unsigned long info_mask_shared_by_dir; + unsigned long info_mask_shared_by_dir_available; + unsigned long info_mask_shared_by_all; + unsigned long info_mask_shared_by_all_available; const struct iio_event_spec *event_spec; unsigned int num_event_specs; const struct iio_chan_spec_ext_info *ext_info; @@ -779,7 +779,7 @@ static inline void *iio_device_get_drvdata(const struct iio_dev *indio_dev) * them safe for use with non-coherent DMA. * * A number of drivers also use this on buffers that include a 64-bit timestamp - * that is used with iio_push_to_buffer_with_ts(). Therefore, in the case where + * that is used with iio_push_to_buffers_with_ts(). Therefore, in the case where * DMA alignment is not sufficient for proper timestamp alignment, we align to * 8 bytes instead. */ @@ -794,7 +794,7 @@ static inline void *iio_device_get_drvdata(const struct iio_dev *indio_dev) * @name: identifier name of the buffer * @count: number of elements in the buffer * - * Declares a buffer that is safe to use with iio_push_to_buffer_with_ts(). In + * Declares a buffer that is safe to use with iio_push_to_buffers_with_ts(). In * addition to allocating enough space for @count elements of @type, it also * allocates space for a s64 timestamp at the end of the buffer and ensures * proper alignment of the timestamp. diff --git a/include/linux/iio/types.h b/include/linux/iio/types.h index ad2761efcc83..34eebad12d2c 100644 --- a/include/linux/iio/types.h +++ b/include/linux/iio/types.h @@ -70,6 +70,7 @@ enum iio_chan_info_enum { IIO_CHAN_INFO_ZEROPOINT, IIO_CHAN_INFO_TROUGH, IIO_CHAN_INFO_CONVDELAY, + IIO_CHAN_INFO_POWERFACTOR, }; #endif /* _IIO_TYPES_H_ */ diff --git a/include/linux/mfd/88pm886.h b/include/linux/mfd/88pm886.h index 85eca44f39ab..38892ba7b8a4 100644 --- a/include/linux/mfd/88pm886.h +++ b/include/linux/mfd/88pm886.h @@ -10,6 +10,7 @@ #define PM886_IRQ_ONKEY 0 #define PM886_PAGE_OFFSET_REGULATORS 1 +#define PM886_PAGE_OFFSET_GPADC 2 #define PM886_REG_ID 0x00 @@ -70,6 +71,63 @@ #define PM886_LDO_VSEL_MASK 0x0f #define PM886_BUCK_VSEL_MASK 0x7f +/* GPADC enable/disable registers */ +#define PM886_REG_GPADC_CONFIG(n) (n) + +#define PM886_GPADC_VSC_EN BIT(0) +#define PM886_GPADC_VBAT_EN BIT(1) +#define PM886_GPADC_GNDDET1_EN BIT(3) +#define PM886_GPADC_VBUS_EN BIT(4) +#define PM886_GPADC_VCHG_PWR_EN BIT(5) +#define PM886_GPADC_VCF_OUT_EN BIT(6) +#define PM886_GPADC_CONFIG1_EN_ALL \ + (PM886_GPADC_VSC_EN | \ + PM886_GPADC_VBAT_EN | \ + PM886_GPADC_GNDDET1_EN | \ + PM886_GPADC_VBUS_EN | \ + PM886_GPADC_VCHG_PWR_EN | \ + PM886_GPADC_VCF_OUT_EN) + +#define PM886_GPADC_TINT_EN BIT(0) +#define PM886_GPADC_PMODE_EN BIT(1) +#define PM886_GPADC_GPADC0_EN BIT(2) +#define PM886_GPADC_GPADC1_EN BIT(3) +#define PM886_GPADC_GPADC2_EN BIT(4) +#define PM886_GPADC_GPADC3_EN BIT(5) +#define PM886_GPADC_MIC_DET_EN BIT(6) +#define PM886_GPADC_CONFIG2_EN_ALL \ + (PM886_GPADC_TINT_EN | \ + PM886_GPADC_GPADC0_EN | \ + PM886_GPADC_GPADC1_EN | \ + PM886_GPADC_GPADC2_EN | \ + PM886_GPADC_GPADC3_EN | \ + PM886_GPADC_MIC_DET_EN) + +/* No CONFIG3_EN_ALL because this is the only bit there. */ +#define PM886_GPADC_GND_DET2_EN BIT(0) + +/* GPADC channel registers */ +#define PM886_REG_GPADC_VSC 0x40 +#define PM886_REG_GPADC_VCHG_PWR 0x4c +#define PM886_REG_GPADC_VCF_OUT 0x4e +#define PM886_REG_GPADC_TINT 0x50 +#define PM886_REG_GPADC_GPADC0 0x54 +#define PM886_REG_GPADC_GPADC1 0x56 +#define PM886_REG_GPADC_GPADC2 0x58 +#define PM886_REG_GPADC_VBAT 0xa0 +#define PM886_REG_GPADC_GND_DET1 0xa4 +#define PM886_REG_GPADC_GND_DET2 0xa6 +#define PM886_REG_GPADC_VBUS 0xa8 +#define PM886_REG_GPADC_GPADC3 0xaa +#define PM886_REG_GPADC_MIC_DET 0xac +#define PM886_REG_GPADC_VBAT_SLP 0xb0 + +/* VBAT_SLP is the last register and is 2 bytes wide like other channels. */ +#define PM886_GPADC_MAX_REGISTER (PM886_REG_GPADC_VBAT_SLP + 1) + +#define PM886_GPADC_BIAS_LEVELS 16 +#define PM886_GPADC_INDEX_TO_BIAS_uA(i) (1 + (i) * 5) + struct pm886_chip { struct i2c_client *client; unsigned int chip_id; diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h index 3e6deb00fc85..7d0aa718499c 100644 --- a/include/linux/miscdevice.h +++ b/include/linux/miscdevice.h @@ -70,7 +70,16 @@ #define UHID_MINOR 239 #define USERIO_MINOR 240 #define VHOST_VSOCK_MINOR 241 +#define EISA_EEPROM_MINOR 241 #define RFKILL_MINOR 242 + +/* + * Misc char device minor code space division related to below macro: + * + * < 255 : Fixed minor code + * == 255 : Indicator to request dynamic minor code + * > 255 : Dynamic minor code requested, 1048320 minor codes totally. + */ #define MISC_DYNAMIC_MINOR 255 struct miscdevice { diff --git a/include/linux/platform_data/touchscreen-s3c2410.h b/include/linux/platform_data/touchscreen-s3c2410.h deleted file mode 100644 index bf8d3b9d7c6a..000000000000 --- a/include/linux/platform_data/touchscreen-s3c2410.h +++ /dev/null @@ -1,22 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (c) 2005 Arnaud Patard <arnaud.patard@rtp-net.org> -*/ - -#ifndef __TOUCHSCREEN_S3C2410_H -#define __TOUCHSCREEN_S3C2410_H - -struct s3c2410_ts_mach_info { - int delay; - int presc; - int oversampling_shift; - void (*cfg_gpio)(struct platform_device *dev); -}; - -extern void s3c24xx_ts_set_platdata(struct s3c2410_ts_mach_info *); -extern void s3c64xx_ts_set_platdata(struct s3c2410_ts_mach_info *); - -/* defined by architecture to configure gpio */ -extern void s3c24xx_ts_cfg_gpio(struct platform_device *dev); - -#endif /*__TOUCHSCREEN_S3C2410_H */ diff --git a/include/linux/rtsx_pci.h b/include/linux/rtsx_pci.h index 3b4c36705a9b..3c5689356004 100644 --- a/include/linux/rtsx_pci.h +++ b/include/linux/rtsx_pci.h @@ -1160,6 +1160,8 @@ struct rtsx_cr_option { bool ocp_en; u8 sd_400mA_ocp_thd; u8 sd_800mA_ocp_thd; + u8 sd_cd_reverse_en; + u8 sd_wp_reverse_en; }; /* diff --git a/include/uapi/linux/android/binder.h b/include/uapi/linux/android/binder.h index 1fd92021a573..03ee4c7010d7 100644 --- a/include/uapi/linux/android/binder.h +++ b/include/uapi/linux/android/binder.h @@ -38,7 +38,7 @@ enum { BINDER_TYPE_PTR = B_PACK_CHARS('p', 't', '*', B_TYPE_LARGE), }; -enum { +enum flat_binder_object_flags { FLAT_BINDER_FLAG_PRIORITY_MASK = 0xff, FLAT_BINDER_FLAG_ACCEPTS_FDS = 0x100, diff --git a/include/uapi/linux/android/binder_netlink.h b/include/uapi/linux/android/binder_netlink.h new file mode 100644 index 000000000000..b218f96d6668 --- /dev/null +++ b/include/uapi/linux/android/binder_netlink.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/binder.yaml */ +/* YNL-GEN uapi header */ + +#ifndef _UAPI_LINUX_ANDROID_BINDER_NETLINK_H +#define _UAPI_LINUX_ANDROID_BINDER_NETLINK_H + +#define BINDER_FAMILY_NAME "binder" +#define BINDER_FAMILY_VERSION 1 + +enum { + BINDER_A_REPORT_ERROR = 1, + BINDER_A_REPORT_CONTEXT, + BINDER_A_REPORT_FROM_PID, + BINDER_A_REPORT_FROM_TID, + BINDER_A_REPORT_TO_PID, + BINDER_A_REPORT_TO_TID, + BINDER_A_REPORT_IS_REPLY, + BINDER_A_REPORT_FLAGS, + BINDER_A_REPORT_CODE, + BINDER_A_REPORT_DATA_SIZE, + + __BINDER_A_REPORT_MAX, + BINDER_A_REPORT_MAX = (__BINDER_A_REPORT_MAX - 1) +}; + +enum { + BINDER_CMD_REPORT = 1, + + __BINDER_CMD_MAX, + BINDER_CMD_MAX = (__BINDER_CMD_MAX - 1) +}; + +#define BINDER_MCGRP_REPORT "report" + +#endif /* _UAPI_LINUX_ANDROID_BINDER_NETLINK_H */ diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index 3eb0821af7a4..6d269b844271 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -52,6 +52,7 @@ enum iio_chan_type { IIO_COLORTEMP, IIO_CHROMATICITY, IIO_ATTENTION, + IIO_ALTCURRENT, }; enum iio_modifier { @@ -108,6 +109,10 @@ enum iio_modifier { IIO_MOD_ROLL, IIO_MOD_LIGHT_UVA, IIO_MOD_LIGHT_UVB, + IIO_MOD_RMS, + IIO_MOD_ACTIVE, + IIO_MOD_REACTIVE, + IIO_MOD_APPARENT, }; enum iio_event_type { diff --git a/include/uapi/misc/fastrpc.h b/include/uapi/misc/fastrpc.h index f33d914d8f46..c6e2925f47e6 100644 --- a/include/uapi/misc/fastrpc.h +++ b/include/uapi/misc/fastrpc.h @@ -134,7 +134,7 @@ struct fastrpc_mem_unmap { }; struct fastrpc_ioctl_capability { - __u32 domain; + __u32 unused; /* deprecated, ignored by the kernel */ __u32 attribute_id; __u32 capability; /* dsp capability */ __u32 reserved[4]; diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 5128e2f12038..04b75d4d01c3 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -53,6 +53,7 @@ #include <linux/dma-mapping.h> #include <linux/errname.h> #include <linux/ethtool.h> +#include <linux/fdtable.h> #include <linux/file.h> #include <linux/firmware.h> #include <linux/interrupt.h> @@ -77,6 +78,7 @@ #include <linux/sched.h> #include <linux/security.h> #include <linux/slab.h> +#include <linux/task_work.h> #include <linux/tracepoint.h> #include <linux/wait.h> #include <linux/workqueue.h> @@ -106,3 +108,9 @@ const xa_mark_t RUST_CONST_HELPER_XA_PRESENT = XA_PRESENT; const gfp_t RUST_CONST_HELPER_XA_FLAGS_ALLOC = XA_FLAGS_ALLOC; const gfp_t RUST_CONST_HELPER_XA_FLAGS_ALLOC1 = XA_FLAGS_ALLOC1; + +#if IS_ENABLED(CONFIG_ANDROID_BINDER_IPC_RUST) +#include "../../drivers/android/binder/rust_binder.h" +#include "../../drivers/android/binder/rust_binder_events.h" +#include "../../drivers/android/binder/page_range_helper.h" +#endif diff --git a/rust/helpers/binder.c b/rust/helpers/binder.c new file mode 100644 index 000000000000..224d38a92f1d --- /dev/null +++ b/rust/helpers/binder.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (C) 2025 Google LLC. + */ + +#include <linux/list_lru.h> +#include <linux/task_work.h> + +unsigned long rust_helper_list_lru_count(struct list_lru *lru) +{ + return list_lru_count(lru); +} + +unsigned long rust_helper_list_lru_walk(struct list_lru *lru, + list_lru_walk_cb isolate, void *cb_arg, + unsigned long nr_to_walk) +{ + return list_lru_walk(lru, isolate, cb_arg, nr_to_walk); +} + +void rust_helper_init_task_work(struct callback_head *twork, + task_work_func_t func) +{ + init_task_work(twork, func); +} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 9aa2735d203c..551da6c9b506 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -10,6 +10,7 @@ #include "atomic.c" #include "auxiliary.c" #include "barrier.c" +#include "binder.c" #include "bitmap.c" #include "bitops.c" #include "blk.c" diff --git a/rust/helpers/page.c b/rust/helpers/page.c index b3f2b8fbf87f..7144de5a61db 100644 --- a/rust/helpers/page.c +++ b/rust/helpers/page.c @@ -2,6 +2,7 @@ #include <linux/gfp.h> #include <linux/highmem.h> +#include <linux/mm.h> struct page *rust_helper_alloc_pages(gfp_t gfp_mask, unsigned int order) { @@ -17,3 +18,10 @@ void rust_helper_kunmap_local(const void *addr) { kunmap_local(addr); } + +#ifndef NODE_NOT_IN_PAGE_FLAGS +int rust_helper_page_to_nid(const struct page *page) +{ + return page_to_nid(page); +} +#endif diff --git a/rust/helpers/security.c b/rust/helpers/security.c index 0c4c2065df28..ca22da09548d 100644 --- a/rust/helpers/security.c +++ b/rust/helpers/security.c @@ -17,4 +17,28 @@ void rust_helper_security_release_secctx(struct lsm_context *cp) { security_release_secctx(cp); } + +int rust_helper_security_binder_set_context_mgr(const struct cred *mgr) +{ + return security_binder_set_context_mgr(mgr); +} + +int rust_helper_security_binder_transaction(const struct cred *from, + const struct cred *to) +{ + return security_binder_transaction(from, to); +} + +int rust_helper_security_binder_transfer_binder(const struct cred *from, + const struct cred *to) +{ + return security_binder_transfer_binder(from, to); +} + +int rust_helper_security_binder_transfer_file(const struct cred *from, + const struct cred *to, + const struct file *file) +{ + return security_binder_transfer_file(from, to, file); +} #endif diff --git a/rust/helpers/usb.c b/rust/helpers/usb.c new file mode 100644 index 000000000000..fb2aad0cbf4d --- /dev/null +++ b/rust/helpers/usb.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/usb.h> + +struct usb_device *rust_helper_interface_to_usbdev(struct usb_interface *intf) +{ + return interface_to_usbdev(intf); +} diff --git a/rust/kernel/cred.rs b/rust/kernel/cred.rs index 4a2229542fb7..ffa156b9df37 100644 --- a/rust/kernel/cred.rs +++ b/rust/kernel/cred.rs @@ -50,6 +50,12 @@ impl Credential { unsafe { &*ptr.cast() } } + /// Returns a raw pointer to the inner credential. + #[inline] + pub fn as_ptr(&self) -> *const bindings::cred { + self.0.get() + } + /// Get the id for this security context. #[inline] pub fn get_secid(&self) -> u32 { diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index 0121b38c59e6..6ba6bdf143cb 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -6,3 +6,6 @@ pub mod file; pub use self::file::{File, LocalFile}; + +mod kiocb; +pub use self::kiocb::Kiocb; diff --git a/rust/kernel/fs/kiocb.rs b/rust/kernel/fs/kiocb.rs new file mode 100644 index 000000000000..84c936cd69b0 --- /dev/null +++ b/rust/kernel/fs/kiocb.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2024 Google LLC. + +//! Kernel IO callbacks. +//! +//! C headers: [`include/linux/fs.h`](srctree/include/linux/fs.h) + +use core::marker::PhantomData; +use core::ptr::NonNull; +use kernel::types::ForeignOwnable; + +/// Wrapper for the kernel's `struct kiocb`. +/// +/// Currently this abstractions is incomplete and is essentially just a tuple containing a +/// reference to a file and a file position. +/// +/// The type `T` represents the filesystem or driver specific data associated with the file. +/// +/// # Invariants +/// +/// `inner` points at a valid `struct kiocb` whose file has the type `T` as its private data. +pub struct Kiocb<'a, T> { + inner: NonNull<bindings::kiocb>, + _phantom: PhantomData<&'a T>, +} + +impl<'a, T: ForeignOwnable> Kiocb<'a, T> { + /// Create a `Kiocb` from a raw pointer. + /// + /// # Safety + /// + /// The pointer must reference a valid `struct kiocb` for the duration of `'a`. The private + /// data of the file must be `T`. + pub unsafe fn from_raw(kiocb: *mut bindings::kiocb) -> Self { + Self { + // SAFETY: If a pointer is valid it is not null. + inner: unsafe { NonNull::new_unchecked(kiocb) }, + _phantom: PhantomData, + } + } + + /// Access the underlying `struct kiocb` directly. + pub fn as_raw(&self) -> *mut bindings::kiocb { + self.inner.as_ptr() + } + + /// Get the filesystem or driver specific data associated with the file. + pub fn file(&self) -> <T as ForeignOwnable>::Borrowed<'a> { + // SAFETY: We have shared access to this kiocb and hence the underlying file, so we can + // read the file's private data. + let private = unsafe { (*(*self.as_raw()).ki_filp).private_data }; + // SAFETY: The kiocb has shared access to the private data. + unsafe { <T as ForeignOwnable>::borrow(private) } + } + + /// Gets the current value of `ki_pos`. + pub fn ki_pos(&self) -> i64 { + // SAFETY: We have shared access to the kiocb, so we can read its `ki_pos` field. + unsafe { (*self.as_raw()).ki_pos } + } + + /// Gets a mutable reference to the `ki_pos` field. + pub fn ki_pos_mut(&mut self) -> &mut i64 { + // SAFETY: We have exclusive access to the kiocb, so we can write to `ki_pos`. + unsafe { &mut (*self.as_raw()).ki_pos } + } +} diff --git a/rust/kernel/iov.rs b/rust/kernel/iov.rs new file mode 100644 index 000000000000..43bae8923c46 --- /dev/null +++ b/rust/kernel/iov.rs @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2025 Google LLC. + +//! IO vectors. +//! +//! C headers: [`include/linux/iov_iter.h`](srctree/include/linux/iov_iter.h), +//! [`include/linux/uio.h`](srctree/include/linux/uio.h) + +use crate::{ + alloc::{Allocator, Flags}, + bindings, + prelude::*, + types::Opaque, +}; +use core::{marker::PhantomData, mem::MaybeUninit, ptr, slice}; + +const ITER_SOURCE: bool = bindings::ITER_SOURCE != 0; +const ITER_DEST: bool = bindings::ITER_DEST != 0; + +// Compile-time assertion for the above constants. +const _: () = { + build_assert!( + ITER_SOURCE != ITER_DEST, + "ITER_DEST and ITER_SOURCE should be different." + ); +}; + +/// An IO vector that acts as a source of data. +/// +/// The data may come from many different sources. This includes both things in kernel-space and +/// reading from userspace. It's not necessarily the case that the data source is immutable, so +/// rewinding the IO vector to read the same data twice is not guaranteed to result in the same +/// bytes. It's also possible that the data source is mapped in a thread-local manner using e.g. +/// `kmap_local_page()`, so this type is not `Send` to ensure that the mapping is read from the +/// right context in that scenario. +/// +/// # Invariants +/// +/// Must hold a valid `struct iov_iter` with `data_source` set to `ITER_SOURCE`. For the duration +/// of `'data`, it must be safe to read from this IO vector using the standard C methods for this +/// purpose. +#[repr(transparent)] +pub struct IovIterSource<'data> { + iov: Opaque<bindings::iov_iter>, + /// Represent to the type system that this value contains a pointer to readable data it does + /// not own. + _source: PhantomData<&'data [u8]>, +} + +impl<'data> IovIterSource<'data> { + /// Obtain an `IovIterSource` from a raw pointer. + /// + /// # Safety + /// + /// * The referenced `struct iov_iter` must be valid and must only be accessed through the + /// returned reference for the duration of `'iov`. + /// * The referenced `struct iov_iter` must have `data_source` set to `ITER_SOURCE`. + /// * For the duration of `'data`, it must be safe to read from this IO vector using the + /// standard C methods for this purpose. + #[track_caller] + #[inline] + pub unsafe fn from_raw<'iov>(ptr: *mut bindings::iov_iter) -> &'iov mut IovIterSource<'data> { + // SAFETY: The caller ensures that `ptr` is valid. + let data_source = unsafe { (*ptr).data_source }; + assert_eq!(data_source, ITER_SOURCE); + + // SAFETY: The caller ensures the type invariants for the right durations, and + // `IovIterSource` is layout compatible with `struct iov_iter`. + unsafe { &mut *ptr.cast::<IovIterSource<'data>>() } + } + + /// Access this as a raw `struct iov_iter`. + #[inline] + pub fn as_raw(&mut self) -> *mut bindings::iov_iter { + self.iov.get() + } + + /// Returns the number of bytes available in this IO vector. + /// + /// Note that this may overestimate the number of bytes. For example, reading from userspace + /// memory could fail with `EFAULT`, which will be treated as the end of the IO vector. + #[inline] + pub fn len(&self) -> usize { + // SAFETY: We have shared access to this IO vector, so we can read its `count` field. + unsafe { + (*self.iov.get()) + .__bindgen_anon_1 + .__bindgen_anon_1 + .as_ref() + .count + } + } + + /// Returns whether there are any bytes left in this IO vector. + /// + /// This may return `true` even if there are no more bytes available. For example, reading from + /// userspace memory could fail with `EFAULT`, which will be treated as the end of the IO vector. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Advance this IO vector by `bytes` bytes. + /// + /// If `bytes` is larger than the size of this IO vector, it is advanced to the end. + #[inline] + pub fn advance(&mut self, bytes: usize) { + // SAFETY: By the type invariants, `self.iov` is a valid IO vector. + unsafe { bindings::iov_iter_advance(self.as_raw(), bytes) }; + } + + /// Advance this IO vector backwards by `bytes` bytes. + /// + /// # Safety + /// + /// The IO vector must not be reverted to before its beginning. + #[inline] + pub unsafe fn revert(&mut self, bytes: usize) { + // SAFETY: By the type invariants, `self.iov` is a valid IO vector, and the caller + // ensures that `bytes` is in bounds. + unsafe { bindings::iov_iter_revert(self.as_raw(), bytes) }; + } + + /// Read data from this IO vector. + /// + /// Returns the number of bytes that have been copied. + #[inline] + pub fn copy_from_iter(&mut self, out: &mut [u8]) -> usize { + // SAFETY: `Self::copy_from_iter_raw` guarantees that it will not write any uninitialized + // bytes in the provided buffer, so `out` is still a valid `u8` slice after this call. + let out = unsafe { &mut *(ptr::from_mut(out) as *mut [MaybeUninit<u8>]) }; + + self.copy_from_iter_raw(out).len() + } + + /// Read data from this IO vector and append it to a vector. + /// + /// Returns the number of bytes that have been copied. + #[inline] + pub fn copy_from_iter_vec<A: Allocator>( + &mut self, + out: &mut Vec<u8, A>, + flags: Flags, + ) -> Result<usize> { + out.reserve(self.len(), flags)?; + let len = self.copy_from_iter_raw(out.spare_capacity_mut()).len(); + // SAFETY: + // - `len` is the length of a subslice of the spare capacity, so `len` is at most the + // length of the spare capacity. + // - `Self::copy_from_iter_raw` guarantees that the first `len` bytes of the spare capacity + // have been initialized. + unsafe { out.inc_len(len) }; + Ok(len) + } + + /// Read data from this IO vector into potentially uninitialized memory. + /// + /// Returns the sub-slice of the output that has been initialized. If the returned slice is + /// shorter than the input buffer, then the entire IO vector has been read. + /// + /// This will never write uninitialized bytes to the provided buffer. + #[inline] + pub fn copy_from_iter_raw(&mut self, out: &mut [MaybeUninit<u8>]) -> &mut [u8] { + let capacity = out.len(); + let out = out.as_mut_ptr().cast::<u8>(); + + // GUARANTEES: The C API guarantees that it does not write uninitialized bytes to the + // provided buffer. + // SAFETY: + // * By the type invariants, it is still valid to read from this IO vector. + // * `out` is valid for writing for `capacity` bytes because it comes from a slice of + // that length. + let len = unsafe { bindings::_copy_from_iter(out.cast(), capacity, self.as_raw()) }; + + // SAFETY: The underlying C api guarantees that initialized bytes have been written to the + // first `len` bytes of the spare capacity. + unsafe { slice::from_raw_parts_mut(out, len) } + } +} + +/// An IO vector that acts as a destination for data. +/// +/// IO vectors support many different types of destinations. This includes both buffers in +/// kernel-space and writing to userspace. It's possible that the destination buffer is mapped in a +/// thread-local manner using e.g. `kmap_local_page()`, so this type is not `Send` to ensure that +/// the mapping is written to the right context in that scenario. +/// +/// # Invariants +/// +/// Must hold a valid `struct iov_iter` with `data_source` set to `ITER_DEST`. For the duration of +/// `'data`, it must be safe to write to this IO vector using the standard C methods for this +/// purpose. +#[repr(transparent)] +pub struct IovIterDest<'data> { + iov: Opaque<bindings::iov_iter>, + /// Represent to the type system that this value contains a pointer to writable data it does + /// not own. + _source: PhantomData<&'data mut [u8]>, +} + +impl<'data> IovIterDest<'data> { + /// Obtain an `IovIterDest` from a raw pointer. + /// + /// # Safety + /// + /// * The referenced `struct iov_iter` must be valid and must only be accessed through the + /// returned reference for the duration of `'iov`. + /// * The referenced `struct iov_iter` must have `data_source` set to `ITER_DEST`. + /// * For the duration of `'data`, it must be safe to write to this IO vector using the + /// standard C methods for this purpose. + #[track_caller] + #[inline] + pub unsafe fn from_raw<'iov>(ptr: *mut bindings::iov_iter) -> &'iov mut IovIterDest<'data> { + // SAFETY: The caller ensures that `ptr` is valid. + let data_source = unsafe { (*ptr).data_source }; + assert_eq!(data_source, ITER_DEST); + + // SAFETY: The caller ensures the type invariants for the right durations, and + // `IovIterSource` is layout compatible with `struct iov_iter`. + unsafe { &mut *ptr.cast::<IovIterDest<'data>>() } + } + + /// Access this as a raw `struct iov_iter`. + #[inline] + pub fn as_raw(&mut self) -> *mut bindings::iov_iter { + self.iov.get() + } + + /// Returns the number of bytes available in this IO vector. + /// + /// Note that this may overestimate the number of bytes. For example, reading from userspace + /// memory could fail with EFAULT, which will be treated as the end of the IO vector. + #[inline] + pub fn len(&self) -> usize { + // SAFETY: We have shared access to this IO vector, so we can read its `count` field. + unsafe { + (*self.iov.get()) + .__bindgen_anon_1 + .__bindgen_anon_1 + .as_ref() + .count + } + } + + /// Returns whether there are any bytes left in this IO vector. + /// + /// This may return `true` even if there are no more bytes available. For example, reading from + /// userspace memory could fail with EFAULT, which will be treated as the end of the IO vector. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Advance this IO vector by `bytes` bytes. + /// + /// If `bytes` is larger than the size of this IO vector, it is advanced to the end. + #[inline] + pub fn advance(&mut self, bytes: usize) { + // SAFETY: By the type invariants, `self.iov` is a valid IO vector. + unsafe { bindings::iov_iter_advance(self.as_raw(), bytes) }; + } + + /// Advance this IO vector backwards by `bytes` bytes. + /// + /// # Safety + /// + /// The IO vector must not be reverted to before its beginning. + #[inline] + pub unsafe fn revert(&mut self, bytes: usize) { + // SAFETY: By the type invariants, `self.iov` is a valid IO vector, and the caller + // ensures that `bytes` is in bounds. + unsafe { bindings::iov_iter_revert(self.as_raw(), bytes) }; + } + + /// Write data to this IO vector. + /// + /// Returns the number of bytes that were written. If this is shorter than the provided slice, + /// then no more bytes can be written. + #[inline] + pub fn copy_to_iter(&mut self, input: &[u8]) -> usize { + // SAFETY: + // * By the type invariants, it is still valid to write to this IO vector. + // * `input` is valid for `input.len()` bytes. + unsafe { bindings::_copy_to_iter(input.as_ptr().cast(), input.len(), self.as_raw()) } + } + + /// Utility for implementing `read_iter` given the full contents of the file. + /// + /// The full contents of the file being read from is represented by `contents`. This call will + /// write the appropriate sub-slice of `contents` and update the file position in `ppos` so + /// that the file will appear to contain `contents` even if takes multiple reads to read the + /// entire file. + #[inline] + pub fn simple_read_from_buffer(&mut self, ppos: &mut i64, contents: &[u8]) -> Result<usize> { + if *ppos < 0 { + return Err(EINVAL); + } + let Ok(pos) = usize::try_from(*ppos) else { + return Ok(0); + }; + if pos >= contents.len() { + return Ok(0); + } + + // BOUNDS: We just checked that `pos < contents.len()` above. + let num_written = self.copy_to_iter(&contents[pos..]); + + // OVERFLOW: `pos+num_written <= contents.len() <= isize::MAX <= i64::MAX`. + *ppos = (pos + num_written) as i64; + + Ok(num_written) + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 7e5290caf788..3dd7bebe7888 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -98,6 +98,7 @@ pub mod id_pool; pub mod init; pub mod io; pub mod ioctl; +pub mod iov; pub mod irq; pub mod jump_label; #[cfg(CONFIG_KUNIT)] diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs index d3aa7d25afad..d698cddcb4a5 100644 --- a/rust/kernel/miscdevice.rs +++ b/rust/kernel/miscdevice.rs @@ -13,7 +13,8 @@ use crate::{ device::Device, error::{to_result, Error, Result, VTABLE_DEFAULT_ERROR}, ffi::{c_int, c_long, c_uint, c_ulong}, - fs::File, + fs::{File, Kiocb}, + iov::{IovIterDest, IovIterSource}, mm::virt::VmaNew, prelude::*, seq_file::SeqFile, @@ -141,6 +142,16 @@ pub trait MiscDevice: Sized { build_error!(VTABLE_DEFAULT_ERROR) } + /// Read from this miscdevice. + fn read_iter(_kiocb: Kiocb<'_, Self::Ptr>, _iov: &mut IovIterDest<'_>) -> Result<usize> { + build_error!(VTABLE_DEFAULT_ERROR) + } + + /// Write to this miscdevice. + fn write_iter(_kiocb: Kiocb<'_, Self::Ptr>, _iov: &mut IovIterSource<'_>) -> Result<usize> { + build_error!(VTABLE_DEFAULT_ERROR) + } + /// Handler for ioctls. /// /// The `cmd` argument is usually manipulated using the utilities in [`kernel::ioctl`]. @@ -247,6 +258,46 @@ impl<T: MiscDevice> MiscdeviceVTable<T> { /// # Safety /// + /// `kiocb` must be correspond to a valid file that is associated with a + /// `MiscDeviceRegistration<T>`. `iter` must be a valid `struct iov_iter` for writing. + unsafe extern "C" fn read_iter( + kiocb: *mut bindings::kiocb, + iter: *mut bindings::iov_iter, + ) -> isize { + // SAFETY: The caller provides a valid `struct kiocb` associated with a + // `MiscDeviceRegistration<T>` file. + let kiocb = unsafe { Kiocb::from_raw(kiocb) }; + // SAFETY: This is a valid `struct iov_iter` for writing. + let iov = unsafe { IovIterDest::from_raw(iter) }; + + match T::read_iter(kiocb, iov) { + Ok(res) => res as isize, + Err(err) => err.to_errno() as isize, + } + } + + /// # Safety + /// + /// `kiocb` must be correspond to a valid file that is associated with a + /// `MiscDeviceRegistration<T>`. `iter` must be a valid `struct iov_iter` for writing. + unsafe extern "C" fn write_iter( + kiocb: *mut bindings::kiocb, + iter: *mut bindings::iov_iter, + ) -> isize { + // SAFETY: The caller provides a valid `struct kiocb` associated with a + // `MiscDeviceRegistration<T>` file. + let kiocb = unsafe { Kiocb::from_raw(kiocb) }; + // SAFETY: This is a valid `struct iov_iter` for reading. + let iov = unsafe { IovIterSource::from_raw(iter) }; + + match T::write_iter(kiocb, iov) { + Ok(res) => res as isize, + Err(err) => err.to_errno() as isize, + } + } + + /// # Safety + /// /// `file` must be a valid file that is associated with a `MiscDeviceRegistration<T>`. /// `vma` must be a vma that is currently being mmap'ed with this file. unsafe extern "C" fn mmap( @@ -341,6 +392,16 @@ impl<T: MiscDevice> MiscdeviceVTable<T> { open: Some(Self::open), release: Some(Self::release), mmap: if T::HAS_MMAP { Some(Self::mmap) } else { None }, + read_iter: if T::HAS_READ_ITER { + Some(Self::read_iter) + } else { + None + }, + write_iter: if T::HAS_WRITE_ITER { + Some(Self::write_iter) + } else { + None + }, unlocked_ioctl: if T::HAS_IOCTL { Some(Self::ioctl) } else { diff --git a/rust/kernel/page.rs b/rust/kernel/page.rs index 75ef096075cb..432fc0297d4a 100644 --- a/rust/kernel/page.rs +++ b/rust/kernel/page.rs @@ -170,6 +170,12 @@ impl Page { self.page.as_ptr() } + /// Get the node id containing this page. + pub fn nid(&self) -> i32 { + // SAFETY: Always safe to call with a valid page. + unsafe { bindings::page_to_nid(self.as_ptr()) } + } + /// Runs a piece of code with this page mapped to an address. /// /// The page is unmapped when this call returns. diff --git a/rust/kernel/security.rs b/rust/kernel/security.rs index 0c63e9e7e564..9d271695265f 100644 --- a/rust/kernel/security.rs +++ b/rust/kernel/security.rs @@ -8,9 +8,46 @@ use crate::{ bindings, + cred::Credential, error::{to_result, Result}, + fs::File, }; +/// Calls the security modules to determine if the given task can become the manager of a binder +/// context. +#[inline] +pub fn binder_set_context_mgr(mgr: &Credential) -> Result { + // SAFETY: `mrg.0` is valid because the shared reference guarantees a nonzero refcount. + to_result(unsafe { bindings::security_binder_set_context_mgr(mgr.as_ptr()) }) +} + +/// Calls the security modules to determine if binder transactions are allowed from task `from` to +/// task `to`. +#[inline] +pub fn binder_transaction(from: &Credential, to: &Credential) -> Result { + // SAFETY: `from` and `to` are valid because the shared references guarantee nonzero refcounts. + to_result(unsafe { bindings::security_binder_transaction(from.as_ptr(), to.as_ptr()) }) +} + +/// Calls the security modules to determine if task `from` is allowed to send binder objects +/// (owned by itself or other processes) to task `to` through a binder transaction. +#[inline] +pub fn binder_transfer_binder(from: &Credential, to: &Credential) -> Result { + // SAFETY: `from` and `to` are valid because the shared references guarantee nonzero refcounts. + to_result(unsafe { bindings::security_binder_transfer_binder(from.as_ptr(), to.as_ptr()) }) +} + +/// Calls the security modules to determine if task `from` is allowed to send the given file to +/// task `to` (which would get its own file descriptor) through a binder transaction. +#[inline] +pub fn binder_transfer_file(from: &Credential, to: &Credential, file: &File) -> Result { + // SAFETY: `from`, `to` and `file` are valid because the shared references guarantee nonzero + // refcounts. + to_result(unsafe { + bindings::security_binder_transfer_file(from.as_ptr(), to.as_ptr(), file.as_ptr()) + }) +} + /// A security context string. /// /// # Invariants diff --git a/rust/kernel/usb.rs b/rust/kernel/usb.rs new file mode 100644 index 000000000000..14ddb711bab3 --- /dev/null +++ b/rust/kernel/usb.rs @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd. + +//! Abstractions for the USB bus. +//! +//! C header: [`include/linux/usb.h`](srctree/include/linux/usb.h) + +use crate::{ + bindings, device, + device_id::{RawDeviceId, RawDeviceIdIndex}, + driver, + error::{from_result, to_result, Result}, + prelude::*, + str::CStr, + types::{AlwaysRefCounted, Opaque}, + ThisModule, +}; +use core::{marker::PhantomData, mem::MaybeUninit, ptr::NonNull}; + +/// An adapter for the registration of USB drivers. +pub struct Adapter<T: Driver>(T); + +// SAFETY: A call to `unregister` for a given instance of `RegType` is guaranteed to be valid if +// a preceding call to `register` has been successful. +unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> { + type RegType = bindings::usb_driver; + + unsafe fn register( + udrv: &Opaque<Self::RegType>, + name: &'static CStr, + module: &'static ThisModule, + ) -> Result { + // SAFETY: It's safe to set the fields of `struct usb_driver` on initialization. + unsafe { + (*udrv.get()).name = name.as_char_ptr(); + (*udrv.get()).probe = Some(Self::probe_callback); + (*udrv.get()).disconnect = Some(Self::disconnect_callback); + (*udrv.get()).id_table = T::ID_TABLE.as_ptr(); + } + + // SAFETY: `udrv` is guaranteed to be a valid `RegType`. + to_result(unsafe { + bindings::usb_register_driver(udrv.get(), module.0, name.as_char_ptr()) + }) + } + + unsafe fn unregister(udrv: &Opaque<Self::RegType>) { + // SAFETY: `udrv` is guaranteed to be a valid `RegType`. + unsafe { bindings::usb_deregister(udrv.get()) }; + } +} + +impl<T: Driver + 'static> Adapter<T> { + extern "C" fn probe_callback( + intf: *mut bindings::usb_interface, + id: *const bindings::usb_device_id, + ) -> kernel::ffi::c_int { + // SAFETY: The USB core only ever calls the probe callback with a valid pointer to a + // `struct usb_interface` and `struct usb_device_id`. + // + // INVARIANT: `intf` is valid for the duration of `probe_callback()`. + let intf = unsafe { &*intf.cast::<Interface<device::CoreInternal>>() }; + + from_result(|| { + // SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `struct usb_device_id` and + // does not add additional invariants, so it's safe to transmute. + let id = unsafe { &*id.cast::<DeviceId>() }; + + let info = T::ID_TABLE.info(id.index()); + let data = T::probe(intf, id, info)?; + + let dev: &device::Device<device::CoreInternal> = intf.as_ref(); + dev.set_drvdata(data); + Ok(0) + }) + } + + extern "C" fn disconnect_callback(intf: *mut bindings::usb_interface) { + // SAFETY: The USB core only ever calls the disconnect callback with a valid pointer to a + // `struct usb_interface`. + // + // INVARIANT: `intf` is valid for the duration of `disconnect_callback()`. + let intf = unsafe { &*intf.cast::<Interface<device::CoreInternal>>() }; + + let dev: &device::Device<device::CoreInternal> = intf.as_ref(); + + // SAFETY: `disconnect_callback` is only ever called after a successful call to + // `probe_callback`, hence it's guaranteed that `Device::set_drvdata()` has been called + // and stored a `Pin<KBox<T>>`. + let data = unsafe { dev.drvdata_obtain::<Pin<KBox<T>>>() }; + + T::disconnect(intf, data.as_ref()); + } +} + +/// Abstraction for the USB device ID structure, i.e. [`struct usb_device_id`]. +/// +/// [`struct usb_device_id`]: https://docs.kernel.org/driver-api/basics.html#c.usb_device_id +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct DeviceId(bindings::usb_device_id); + +impl DeviceId { + /// Equivalent to C's `USB_DEVICE` macro. + pub const fn from_id(vendor: u16, product: u16) -> Self { + Self(bindings::usb_device_id { + match_flags: bindings::USB_DEVICE_ID_MATCH_DEVICE as u16, + idVendor: vendor, + idProduct: product, + // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. + ..unsafe { MaybeUninit::zeroed().assume_init() } + }) + } + + /// Equivalent to C's `USB_DEVICE_VER` macro. + pub const fn from_device_ver(vendor: u16, product: u16, bcd_lo: u16, bcd_hi: u16) -> Self { + Self(bindings::usb_device_id { + match_flags: bindings::USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION as u16, + idVendor: vendor, + idProduct: product, + bcdDevice_lo: bcd_lo, + bcdDevice_hi: bcd_hi, + // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. + ..unsafe { MaybeUninit::zeroed().assume_init() } + }) + } + + /// Equivalent to C's `USB_DEVICE_INFO` macro. + pub const fn from_device_info(class: u8, subclass: u8, protocol: u8) -> Self { + Self(bindings::usb_device_id { + match_flags: bindings::USB_DEVICE_ID_MATCH_DEV_INFO as u16, + bDeviceClass: class, + bDeviceSubClass: subclass, + bDeviceProtocol: protocol, + // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. + ..unsafe { MaybeUninit::zeroed().assume_init() } + }) + } + + /// Equivalent to C's `USB_INTERFACE_INFO` macro. + pub const fn from_interface_info(class: u8, subclass: u8, protocol: u8) -> Self { + Self(bindings::usb_device_id { + match_flags: bindings::USB_DEVICE_ID_MATCH_INT_INFO as u16, + bInterfaceClass: class, + bInterfaceSubClass: subclass, + bInterfaceProtocol: protocol, + // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. + ..unsafe { MaybeUninit::zeroed().assume_init() } + }) + } + + /// Equivalent to C's `USB_DEVICE_INTERFACE_CLASS` macro. + pub const fn from_device_interface_class(vendor: u16, product: u16, class: u8) -> Self { + Self(bindings::usb_device_id { + match_flags: (bindings::USB_DEVICE_ID_MATCH_DEVICE + | bindings::USB_DEVICE_ID_MATCH_INT_CLASS) as u16, + idVendor: vendor, + idProduct: product, + bInterfaceClass: class, + // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. + ..unsafe { MaybeUninit::zeroed().assume_init() } + }) + } + + /// Equivalent to C's `USB_DEVICE_INTERFACE_PROTOCOL` macro. + pub const fn from_device_interface_protocol(vendor: u16, product: u16, protocol: u8) -> Self { + Self(bindings::usb_device_id { + match_flags: (bindings::USB_DEVICE_ID_MATCH_DEVICE + | bindings::USB_DEVICE_ID_MATCH_INT_PROTOCOL) as u16, + idVendor: vendor, + idProduct: product, + bInterfaceProtocol: protocol, + // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. + ..unsafe { MaybeUninit::zeroed().assume_init() } + }) + } + + /// Equivalent to C's `USB_DEVICE_INTERFACE_NUMBER` macro. + pub const fn from_device_interface_number(vendor: u16, product: u16, number: u8) -> Self { + Self(bindings::usb_device_id { + match_flags: (bindings::USB_DEVICE_ID_MATCH_DEVICE + | bindings::USB_DEVICE_ID_MATCH_INT_NUMBER) as u16, + idVendor: vendor, + idProduct: product, + bInterfaceNumber: number, + // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. + ..unsafe { MaybeUninit::zeroed().assume_init() } + }) + } + + /// Equivalent to C's `USB_DEVICE_AND_INTERFACE_INFO` macro. + pub const fn from_device_and_interface_info( + vendor: u16, + product: u16, + class: u8, + subclass: u8, + protocol: u8, + ) -> Self { + Self(bindings::usb_device_id { + match_flags: (bindings::USB_DEVICE_ID_MATCH_INT_INFO + | bindings::USB_DEVICE_ID_MATCH_DEVICE) as u16, + idVendor: vendor, + idProduct: product, + bInterfaceClass: class, + bInterfaceSubClass: subclass, + bInterfaceProtocol: protocol, + // SAFETY: It is safe to use all zeroes for the other fields of `usb_device_id`. + ..unsafe { MaybeUninit::zeroed().assume_init() } + }) + } +} + +// SAFETY: `DeviceId` is a `#[repr(transparent)]` wrapper of `usb_device_id` and does not add +// additional invariants, so it's safe to transmute to `RawType`. +unsafe impl RawDeviceId for DeviceId { + type RawType = bindings::usb_device_id; +} + +// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_info` field. +unsafe impl RawDeviceIdIndex for DeviceId { + const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::usb_device_id, driver_info); + + fn index(&self) -> usize { + self.0.driver_info + } +} + +/// [`IdTable`](kernel::device_id::IdTable) type for USB. +pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>; + +/// Create a USB `IdTable` with its alias for modpost. +#[macro_export] +macro_rules! usb_device_table { + ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => { + const $table_name: $crate::device_id::IdArray< + $crate::usb::DeviceId, + $id_info_type, + { $table_data.len() }, + > = $crate::device_id::IdArray::new($table_data); + + $crate::module_device_table!("usb", $module_table_name, $table_name); + }; +} + +/// The USB driver trait. +/// +/// # Examples +/// +///``` +/// # use kernel::{bindings, device::Core, usb}; +/// use kernel::prelude::*; +/// +/// struct MyDriver; +/// +/// kernel::usb_device_table!( +/// USB_TABLE, +/// MODULE_USB_TABLE, +/// <MyDriver as usb::Driver>::IdInfo, +/// [ +/// (usb::DeviceId::from_id(0x1234, 0x5678), ()), +/// (usb::DeviceId::from_id(0xabcd, 0xef01), ()), +/// ] +/// ); +/// +/// impl usb::Driver for MyDriver { +/// type IdInfo = (); +/// const ID_TABLE: usb::IdTable<Self::IdInfo> = &USB_TABLE; +/// +/// fn probe( +/// _interface: &usb::Interface<Core>, +/// _id: &usb::DeviceId, +/// _info: &Self::IdInfo, +/// ) -> Result<Pin<KBox<Self>>> { +/// Err(ENODEV) +/// } +/// +/// fn disconnect(_interface: &usb::Interface<Core>, _data: Pin<&Self>) {} +/// } +///``` +pub trait Driver { + /// The type holding information about each one of the device ids supported by the driver. + type IdInfo: 'static; + + /// The table of device ids supported by the driver. + const ID_TABLE: IdTable<Self::IdInfo>; + + /// USB driver probe. + /// + /// Called when a new USB interface is bound to this driver. + /// Implementers should attempt to initialize the interface here. + fn probe( + interface: &Interface<device::Core>, + id: &DeviceId, + id_info: &Self::IdInfo, + ) -> Result<Pin<KBox<Self>>>; + + /// USB driver disconnect. + /// + /// Called when the USB interface is about to be unbound from this driver. + fn disconnect(interface: &Interface<device::Core>, data: Pin<&Self>); +} + +/// A USB interface. +/// +/// This structure represents the Rust abstraction for a C [`struct usb_interface`]. +/// The implementation abstracts the usage of a C [`struct usb_interface`] passed +/// in from the C side. +/// +/// # Invariants +/// +/// An [`Interface`] instance represents a valid [`struct usb_interface`] created +/// by the C portion of the kernel. +/// +/// [`struct usb_interface`]: https://www.kernel.org/doc/html/latest/driver-api/usb/usb.html#c.usb_interface +#[repr(transparent)] +pub struct Interface<Ctx: device::DeviceContext = device::Normal>( + Opaque<bindings::usb_interface>, + PhantomData<Ctx>, +); + +impl<Ctx: device::DeviceContext> Interface<Ctx> { + fn as_raw(&self) -> *mut bindings::usb_interface { + self.0.get() + } +} + +// SAFETY: `Interface` is a transparent wrapper of a type that doesn't depend on +// `Interface`'s generic argument. +kernel::impl_device_context_deref!(unsafe { Interface }); +kernel::impl_device_context_into_aref!(Interface); + +impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Interface<Ctx> { + fn as_ref(&self) -> &device::Device<Ctx> { + // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid + // `struct usb_interface`. + let dev = unsafe { &raw mut ((*self.as_raw()).dev) }; + + // SAFETY: `dev` points to a valid `struct device`. + unsafe { device::Device::from_raw(dev) } + } +} + +impl<Ctx: device::DeviceContext> AsRef<Device> for Interface<Ctx> { + fn as_ref(&self) -> &Device { + // SAFETY: `self.as_raw()` is valid by the type invariants. + let usb_dev = unsafe { bindings::interface_to_usbdev(self.as_raw()) }; + + // SAFETY: For a valid `struct usb_interface` pointer, the above call to + // `interface_to_usbdev()` guarantees to return a valid pointer to a `struct usb_device`. + unsafe { &*(usb_dev.cast()) } + } +} + +// SAFETY: Instances of `Interface` are always reference-counted. +unsafe impl AlwaysRefCounted for Interface { + fn inc_ref(&self) { + // SAFETY: The invariants of `Interface` guarantee that `self.as_raw()` + // returns a valid `struct usb_interface` pointer, for which we will + // acquire a new refcount. + unsafe { bindings::usb_get_intf(self.as_raw()) }; + } + + unsafe fn dec_ref(obj: NonNull<Self>) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::usb_put_intf(obj.cast().as_ptr()) } + } +} + +// SAFETY: A `Interface` is always reference-counted and can be released from any thread. +unsafe impl Send for Interface {} + +// SAFETY: It is safe to send a &Interface to another thread because we do not +// allow any mutation through a shared reference. +unsafe impl Sync for Interface {} + +/// A USB device. +/// +/// This structure represents the Rust abstraction for a C [`struct usb_device`]. +/// The implementation abstracts the usage of a C [`struct usb_device`] passed in +/// from the C side. +/// +/// # Invariants +/// +/// A [`Device`] instance represents a valid [`struct usb_device`] created by the C portion of the +/// kernel. +/// +/// [`struct usb_device`]: https://www.kernel.org/doc/html/latest/driver-api/usb/usb.html#c.usb_device +#[repr(transparent)] +struct Device<Ctx: device::DeviceContext = device::Normal>( + Opaque<bindings::usb_device>, + PhantomData<Ctx>, +); + +impl<Ctx: device::DeviceContext> Device<Ctx> { + fn as_raw(&self) -> *mut bindings::usb_device { + self.0.get() + } +} + +// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic +// argument. +kernel::impl_device_context_deref!(unsafe { Device }); +kernel::impl_device_context_into_aref!(Device); + +// SAFETY: Instances of `Device` are always reference-counted. +unsafe impl AlwaysRefCounted for Device { + fn inc_ref(&self) { + // SAFETY: The invariants of `Device` guarantee that `self.as_raw()` + // returns a valid `struct usb_device` pointer, for which we will + // acquire a new refcount. + unsafe { bindings::usb_get_dev(self.as_raw()) }; + } + + unsafe fn dec_ref(obj: NonNull<Self>) { + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::usb_put_dev(obj.cast().as_ptr()) } + } +} + +impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Device<Ctx> { + fn as_ref(&self) -> &device::Device<Ctx> { + // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid + // `struct usb_device`. + let dev = unsafe { &raw mut ((*self.as_raw()).dev) }; + + // SAFETY: `dev` points to a valid `struct device`. + unsafe { device::Device::from_raw(dev) } + } +} + +// SAFETY: A `Device` is always reference-counted and can be released from any thread. +unsafe impl Send for Device {} + +// SAFETY: It is safe to send a &Device to another thread because we do not +// allow any mutation through a shared reference. +unsafe impl Sync for Device {} + +/// Declares a kernel module that exposes a single USB driver. +/// +/// # Examples +/// +/// ```ignore +/// module_usb_driver! { +/// type: MyDriver, +/// name: "Module name", +/// author: ["Author name"], +/// description: "Description", +/// license: "GPL v2", +/// } +/// ``` +#[macro_export] +macro_rules! module_usb_driver { + ($($f:tt)*) => { + $crate::module_driver!(<T>, $crate::usb::Adapter<T>, { $($f)* }); + } +} diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h index d4a239cf2a64..06d7d1a2e8da 100644 --- a/rust/uapi/uapi_helper.h +++ b/rust/uapi/uapi_helper.h @@ -10,6 +10,7 @@ #include <uapi/drm/drm.h> #include <uapi/drm/nova_drm.h> #include <uapi/drm/panthor_drm.h> +#include <uapi/linux/android/binder.h> #include <uapi/linux/mdio.h> #include <uapi/linux/mii.h> #include <uapi/linux/ethtool.h> diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 66360cdf048f..c376eb899b7a 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -105,6 +105,17 @@ config SAMPLE_RUST_DRIVER_PLATFORM If unsure, say N. +config SAMPLE_RUST_DRIVER_USB + tristate "USB Driver" + depends on USB = y && BROKEN + help + This option builds the Rust USB driver sample. + + To compile this as a module, choose M here: + the module will be called rust_driver_usb. + + If unsure, say N. + config SAMPLE_RUST_DRIVER_FAUX tristate "Faux Driver" help diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 69ca01497b58..cf8422f8f219 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SAMPLE_RUST_DEBUGFS_SCOPED) += rust_debugfs_scoped.o obj-$(CONFIG_SAMPLE_RUST_DMA) += rust_dma.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI) += rust_driver_pci.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) += rust_driver_platform.o +obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) += rust_driver_usb.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX) += rust_driver_faux.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY) += rust_driver_auxiliary.o obj-$(CONFIG_SAMPLE_RUST_CONFIGFS) += rust_configfs.o diff --git a/samples/rust/rust_driver_usb.rs b/samples/rust/rust_driver_usb.rs new file mode 100644 index 000000000000..5c396f421de7 --- /dev/null +++ b/samples/rust/rust_driver_usb.rs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd. + +//! Rust USB driver sample. + +use kernel::{device, device::Core, prelude::*, sync::aref::ARef, usb}; + +struct SampleDriver { + _intf: ARef<usb::Interface>, +} + +kernel::usb_device_table!( + USB_TABLE, + MODULE_USB_TABLE, + <SampleDriver as usb::Driver>::IdInfo, + [(usb::DeviceId::from_id(0x1234, 0x5678), ()),] +); + +impl usb::Driver for SampleDriver { + type IdInfo = (); + const ID_TABLE: usb::IdTable<Self::IdInfo> = &USB_TABLE; + + fn probe( + intf: &usb::Interface<Core>, + _id: &usb::DeviceId, + _info: &Self::IdInfo, + ) -> Result<Pin<KBox<Self>>> { + let dev: &device::Device<Core> = intf.as_ref(); + dev_info!(dev, "Rust USB driver sample probed\n"); + + let drvdata = KBox::new(Self { _intf: intf.into() }, GFP_KERNEL)?; + Ok(drvdata.into()) + } + + fn disconnect(intf: &usb::Interface<Core>, _data: Pin<&Self>) { + let dev: &device::Device<Core> = intf.as_ref(); + dev_info!(dev, "Rust USB driver sample disconnected\n"); + } +} + +kernel::module_usb_driver! { + type: SampleDriver, + name: "rust_driver_usb", + authors: ["Daniel Almeida"], + description: "Rust USB driver sample", + license: "GPL v2", +} diff --git a/samples/rust/rust_misc_device.rs b/samples/rust/rust_misc_device.rs index e7ab77448f75..d69bc33dbd99 100644 --- a/samples/rust/rust_misc_device.rs +++ b/samples/rust/rust_misc_device.rs @@ -100,13 +100,13 @@ use core::pin::Pin; use kernel::{ c_str, device::Device, - fs::File, + fs::{File, Kiocb}, ioctl::{_IO, _IOC_SIZE, _IOR, _IOW}, + iov::{IovIterDest, IovIterSource}, miscdevice::{MiscDevice, MiscDeviceOptions, MiscDeviceRegistration}, new_mutex, prelude::*, - sync::Mutex, - types::ARef, + sync::{aref::ARef, Mutex}, uaccess::{UserSlice, UserSliceReader, UserSliceWriter}, }; @@ -144,6 +144,7 @@ impl kernel::InPlaceModule for RustMiscDeviceModule { struct Inner { value: i32, + buffer: KVVec<u8>, } #[pin_data(PinnedDrop)] @@ -165,7 +166,10 @@ impl MiscDevice for RustMiscDevice { KBox::try_pin_init( try_pin_init! { RustMiscDevice { - inner <- new_mutex!( Inner{ value: 0_i32 } ), + inner <- new_mutex!(Inner { + value: 0_i32, + buffer: KVVec::new(), + }), dev: dev, } }, @@ -173,6 +177,33 @@ impl MiscDevice for RustMiscDevice { ) } + fn read_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIterDest<'_>) -> Result<usize> { + let me = kiocb.file(); + dev_info!(me.dev, "Reading from Rust Misc Device Sample\n"); + + let inner = me.inner.lock(); + // Read the buffer contents, taking the file position into account. + let read = iov.simple_read_from_buffer(kiocb.ki_pos_mut(), &inner.buffer)?; + + Ok(read) + } + + fn write_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIterSource<'_>) -> Result<usize> { + let me = kiocb.file(); + dev_info!(me.dev, "Writing to Rust Misc Device Sample\n"); + + let mut inner = me.inner.lock(); + + // Replace buffer contents. + inner.buffer.clear(); + let len = iov.copy_from_iter_vec(&mut inner.buffer, GFP_KERNEL)?; + + // Set position to zero so that future `read` calls will see the new contents. + *kiocb.ki_pos_mut() = 0; + + Ok(len) + } + fn ioctl(me: Pin<&RustMiscDevice>, _file: &File, cmd: u32, arg: usize) -> Result<isize> { dev_info!(me.dev, "IOCTLing Rust Misc Device Sample\n"); diff --git a/tools/iio/iio_event_monitor.c b/tools/iio/iio_event_monitor.c index eab7b082f19d..03ca33869ce8 100644 --- a/tools/iio/iio_event_monitor.c +++ b/tools/iio/iio_event_monitor.c @@ -64,6 +64,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_COLORTEMP] = "colortemp", [IIO_CHROMATICITY] = "chromaticity", [IIO_ATTENTION] = "attention", + [IIO_ALTCURRENT] = "altcurrent", }; static const char * const iio_ev_type_text[] = { @@ -140,6 +141,10 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_PITCH] = "pitch", [IIO_MOD_YAW] = "yaw", [IIO_MOD_ROLL] = "roll", + [IIO_MOD_RMS] = "rms", + [IIO_MOD_ACTIVE] = "active", + [IIO_MOD_REACTIVE] = "reactive", + [IIO_MOD_APPARENT] = "apparent", }; static bool event_is_known(struct iio_event_data *event) @@ -187,6 +192,7 @@ static bool event_is_known(struct iio_event_data *event) case IIO_COLORTEMP: case IIO_CHROMATICITY: case IIO_ATTENTION: + case IIO_ALTCURRENT: break; default: return false; @@ -238,6 +244,10 @@ static bool event_is_known(struct iio_event_data *event) case IIO_MOD_PM4: case IIO_MOD_PM10: case IIO_MOD_O2: + case IIO_MOD_RMS: + case IIO_MOD_ACTIVE: + case IIO_MOD_REACTIVE: + case IIO_MOD_APPARENT: break; default: return false; diff --git a/tools/testing/selftests/filesystems/binderfs/binderfs_test.c b/tools/testing/selftests/filesystems/binderfs/binderfs_test.c index 81db85a5cc16..39a68078a79b 100644 --- a/tools/testing/selftests/filesystems/binderfs/binderfs_test.c +++ b/tools/testing/selftests/filesystems/binderfs/binderfs_test.c @@ -65,6 +65,7 @@ static int __do_binderfs_test(struct __test_metadata *_metadata) "oneway_spam_detection", "extended_error", "freeze_notification", + "transaction_report", }; change_mountns(_metadata); |