diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-17 09:11:04 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-17 09:11:04 -0800 |
| commit | 505d195b0f96fd613a51b13dde37aa5ad301eb32 (patch) | |
| tree | 4fe4c2acaf1acde2c49ac53c9634595063ca0c62 | |
| parent | 99dfe2d4da67d863ff8f185d1e8033cce28e4c49 (diff) | |
| parent | 3c4ae63073d84abee5d81ce46d86a94e9dae9c89 (diff) | |
Merge tag 'char-misc-7.0-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull char/misc/IIO driver updates from Greg KH:
"Here is the big set of char/misc/iio and other smaller driver
subsystem changes for 7.0-rc1. Lots of little things in here,
including:
- Loads of iio driver changes and updates and additions
- gpib driver updates
- interconnect driver updates
- i3c driver updates
- hwtracing (coresight and intel) driver updates
- deletion of the obsolete mwave driver
- binder driver updates (rust and c versions)
- mhi driver updates (causing a merge conflict, see below)
- mei driver updates
- fsi driver updates
- eeprom driver updates
- lots of other small char and misc driver updates and cleanups
All of these have been in linux-next for a while, with no reported
issues"
* tag 'char-misc-7.0-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (297 commits)
mux: mmio: fix regmap leak on probe failure
rust_binder: return p from rust_binder_transaction_target_node()
drivers: android: binder: Update ARef imports from sync::aref
rust_binder: fix needless borrow in context.rs
iio: magn: mmc5633: Fix Kconfig for combination of I3C as module and driver builtin
iio: sca3000: Fix a resource leak in sca3000_probe()
iio: proximity: rfd77402: Add interrupt handling support
iio: proximity: rfd77402: Document device private data structure
iio: proximity: rfd77402: Use devm-managed mutex initialization
iio: proximity: rfd77402: Use kernel helper for result polling
iio: proximity: rfd77402: Align polling timeout with datasheet
iio: cros_ec: Allow enabling/disabling calibration mode
iio: frequency: ad9523: correct kernel-doc bad line warning
iio: buffer: buffer_impl.h: fix kernel-doc warnings
iio: gyro: itg3200: Fix unchecked return value in read_raw
MAINTAINERS: add entry for ADE9000 driver
iio: accel: sca3000: remove unused last_timestamp field
iio: accel: adxl372: remove unused int2_bitmask field
iio: adc: ad7766: Use iio_trigger_generic_data_rdy_poll()
iio: magnetometer: Remove IRQF_ONESHOT
...
273 files changed, 15790 insertions, 5384 deletions
diff --git a/Documentation/ABI/stable/sysfs-driver-speakup b/Documentation/ABI/stable/sysfs-driver-speakup index bcb6831aa114..8b508b4a7a00 100644 --- a/Documentation/ABI/stable/sysfs-driver-speakup +++ b/Documentation/ABI/stable/sysfs-driver-speakup @@ -23,8 +23,7 @@ What: /sys/accessibility/speakup/bleep_time KernelVersion: 2.6 Contact: speakup@linux-speakup.org Description: This controls the duration of the PC speaker beeps speakup - produces. - TODO: What are the units? Jiffies? + produces, in milliseconds. What: /sys/accessibility/speakup/cursor_time KernelVersion: 2.6 diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source b/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source index 321e3ee1fc9d..c8c58914116e 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-dummy-source @@ -1,7 +1,7 @@ What: /sys/bus/coresight/devices/dummy_source<N>/enable_source Date: Dec 2024 KernelVersion: 6.14 -Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Contact: Mao Jinlong <jinlong.mao@oss.qualcomm.com> Description: (RW) Enable/disable tracing of dummy source. A sink should be activated before enabling the source. The path of coresight components linking the source to the sink is configured and managed automatically by the @@ -10,7 +10,7 @@ Description: (RW) Enable/disable tracing of dummy source. A sink should be activ What: /sys/bus/coresight/devices/dummy_source<N>/traceid Date: Dec 2024 KernelVersion: 6.14 -Contact: Mao Jinlong <quic_jinlmao@quicinc.com> +Contact: Mao Jinlong <jinlong.mao@oss.qualcomm.com> Description: (R) Show the trace ID that will appear in the trace stream coming from this trace entity. diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda new file mode 100644 index 000000000000..650431feae45 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpda @@ -0,0 +1,69 @@ +What: /sys/bus/coresight/devices/<tpda-name>/trig_async_enable +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com>, Jie Gan <jie.gan@oss.qualcomm.com> +Description: + (RW) Enable/disable cross trigger synchronization sequence interface. + +What: /sys/bus/coresight/devices/<tpda-name>/trig_flag_ts_enable +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com>, Jie Gan <jie.gan@oss.qualcomm.com> +Description: + (RW) Enable/disable cross trigger FLAG packet request interface. + +What: /sys/bus/coresight/devices/<tpda-name>/trig_freq_enable +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com>, Jie Gan <jie.gan@oss.qualcomm.com> +Description: + (RW) Enable/disable cross trigger FREQ packet request interface. + +What: /sys/bus/coresight/devices/<tpda-name>/freq_ts_enable +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com>, Jie Gan <jie.gan@oss.qualcomm.com> +Description: + (RW) Enable/disable the timestamp for all FREQ packets. + +What: /sys/bus/coresight/devices/<tpda-name>/cmbchan_mode +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com>, Jie Gan <jie.gan@oss.qualcomm.com> +Description: + (RW) Configure the CMB/MCMB channel mode for all enabled ports. + Value 0 means raw channel mapping mode. Value 1 means channel pair marking mode. + +What: /sys/bus/coresight/devices/<tpda-name>/global_flush_req +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com>, Jie Gan <jie.gan@oss.qualcomm.com> +Description: + (RW) Set global (all ports) flush request bit. The bit remains set until a + global flush request sequence completes. + +What: /sys/bus/coresight/devices/<tpda-name>/syncr_mode +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com>, Jie Gan <jie.gan@oss.qualcomm.com> +Description: + (RW) Set mode the of the syncr counter. + mode 0 - COUNT[11:0] value represents the approximate number of bytes moved between two ASYNC packet requests + mode 1 - the bits COUNT[11:7] are used as a power of 2. for example, we could insert an async packet every 8K + data by writing a value 13 to the COUNT[11:7] field. + +What: /sys/bus/coresight/devices/<tpda-name>/syncr_count +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com>, Jie Gan <jie.gan@oss.qualcomm.com> +Description: + (RW) Set value the of the syncr counter. + Range: 0-4095 + +What: /sys/bus/coresight/devices/<tpda-name>/port_flush_req +Date: December 2025 +KernelVersion: 6.20 +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com>, Jie Gan <jie.gan@oss.qualcomm.com> +Description: + (RW) Configure the bit i to requests a flush operation of port i on the TPDA. + The requested bit(s) remain set until the flush request completes. diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index 98f1c6545027..f8016df64532 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -1,7 +1,7 @@ What: /sys/bus/coresight/devices/<tpdm-name>/integration_test Date: January 2023 KernelVersion: 6.2 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (Write) Run integration test for tpdm. Integration test will generate test data for tpdm. It can help to make @@ -15,7 +15,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/reset_dataset Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (Write) Reset the dataset of the tpdm. @@ -25,7 +25,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_trig_type Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the trigger type of the DSB for tpdm. @@ -36,7 +36,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_trig_ts Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the trigger timestamp of the DSB for tpdm. @@ -47,7 +47,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_mode Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the programming mode of the DSB for tpdm. @@ -61,7 +61,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_edge/ctrl_idx Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the index number of the edge detection for the DSB subunit TPDM. Since there are at most 256 edge detections, this @@ -70,7 +70,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_edge/ctrl_val Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: Write a data to control the edge detection corresponding to the index number. Before writing data to this sysfs file, @@ -86,7 +86,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_edge/ctrl_mask Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: Write a data to mask the edge detection corresponding to the index number. Before writing data to this sysfs file, "ctrl_idx" should @@ -98,21 +98,21 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_edge/edcr[0:15] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: Read a set of the edge control value of the DSB in TPDM. What: /sys/bus/coresight/devices/<tpdm-name>/dsb_edge/edcmr[0:7] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: Read a set of the edge control mask of the DSB in TPDM. What: /sys/bus/coresight/devices/<tpdm-name>/dsb_trig_patt/xpr[0:7] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the value of the trigger pattern for the DSB subunit TPDM. @@ -120,7 +120,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_trig_patt/xpmr[0:7] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the mask of the trigger pattern for the DSB subunit TPDM. @@ -128,21 +128,21 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_patt/tpr[0:7] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the value of the pattern for the DSB subunit TPDM. What: /sys/bus/coresight/devices/<tpdm-name>/dsb_patt/tpmr[0:7] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the mask of the pattern for the DSB subunit TPDM. What: /sys/bus/coresight/devices/<tpdm-name>/dsb_patt/enable_ts Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (Write) Set the pattern timestamp of DSB tpdm. Read the pattern timestamp of DSB tpdm. @@ -154,7 +154,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_patt/set_type Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (Write) Set the pattern type of DSB tpdm. Read the pattern type of DSB tpdm. @@ -166,7 +166,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_msr/msr[0:31] Date: March 2023 KernelVersion: 6.7 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the MSR(mux select register) for the DSB subunit TPDM. @@ -174,7 +174,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/cmb_mode Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (Write) Set the data collection mode of CMB tpdm. Continuous change creates CMB data set elements on every CMBCLK edge. Trace-on-change creates CMB data set elements only when a new @@ -188,7 +188,7 @@ Description: (Write) Set the data collection mode of CMB tpdm. Continuous What: /sys/bus/coresight/devices/<tpdm-name>/cmb_trig_patt/xpr[0:1] Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the value of the trigger pattern for the CMB subunit TPDM. @@ -196,7 +196,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/cmb_trig_patt/xpmr[0:1] Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the mask of the trigger pattern for the CMB subunit TPDM. @@ -204,21 +204,21 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/dsb_patt/tpr[0:1] Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the value of the pattern for the CMB subunit TPDM. What: /sys/bus/coresight/devices/<tpdm-name>/dsb_patt/tpmr[0:1] Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the mask of the pattern for the CMB subunit TPDM. What: /sys/bus/coresight/devices/<tpdm-name>/cmb_patt/enable_ts Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (Write) Set the pattern timestamp of CMB tpdm. Read the pattern timestamp of CMB tpdm. @@ -230,7 +230,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/cmb_trig_ts Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the trigger timestamp of the CMB for tpdm. @@ -241,7 +241,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/cmb_ts_all Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Read or write the status of timestamp upon all interface. Only value 0 and 1 can be written to this node. Set this node to 1 to request @@ -253,7 +253,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/cmb_msr/msr[0:31] Date: January 2024 KernelVersion: 6.9 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the MSR(mux select register) for the CMB subunit TPDM. @@ -261,7 +261,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/mcmb_trig_lane Date: Feb 2025 KernelVersion 6.15 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get which lane participates in the output pattern match cross trigger mechanism for the MCMB subunit TPDM. @@ -269,7 +269,7 @@ Description: What: /sys/bus/coresight/devices/<tpdm-name>/mcmb_lanes_select Date: Feb 2025 KernelVersion 6.15 -Contact: Jinlong Mao (QUIC) <quic_jinlmao@quicinc.com>, Tao Zhang (QUIC) <quic_taozha@quicinc.com> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Tao Zhang <tao.zhang@oss.qualcomm.com> Description: (RW) Set/Get the enablement of the individual lane. diff --git a/Documentation/ABI/testing/sysfs-bus-iio-cros-ec b/Documentation/ABI/testing/sysfs-bus-iio-cros-ec index 9e3926243797..3de1dfc98389 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio-cros-ec +++ b/Documentation/ABI/testing/sysfs-bus-iio-cros-ec @@ -3,9 +3,12 @@ Date: July 2015 KernelVersion: 4.7 Contact: linux-iio@vger.kernel.org Description: - Writing '1' will perform a FOC (Fast Online Calibration). The - corresponding calibration offsets can be read from `*_calibbias` - entries. + Writing '1' either perform a FOC (Fast Online Calibration) or + enter calibration mode. + Writing '0` exits calibration mode. It is a NOP for FOC enabled + sensors. + The corresponding calibration offsets can be read from `*_calibbias` + entries. What: /sys/bus/iio/devices/iio:deviceX/id Date: September 2017 diff --git a/Documentation/ABI/testing/sysfs-class-spi-eeprom b/Documentation/ABI/testing/sysfs-class-spi-eeprom index 1ff757982079..f4bc7d9454cf 100644 --- a/Documentation/ABI/testing/sysfs-class-spi-eeprom +++ b/Documentation/ABI/testing/sysfs-class-spi-eeprom @@ -17,3 +17,14 @@ Description: from the device. This is a read-only attribute. + +What: /sys/class/spi_master/spi<bus>/spi<bus>.<dev>/jedec_id +Date: January 2026 +KernelVersion: 6.19 +Contact: Patrick Wicki <patrick.wicki@siemens.com> +Description: + Contains the raw JEDEC ID bytes returned by the RDID (0x9f) command. The + bytes are exposed as a hex string in big-endian order as read from the + device. + + This is a read-only attribute. diff --git a/Documentation/admin-guide/devices.txt b/Documentation/admin-guide/devices.txt index c480f230aa4a..440633642fea 100644 --- a/Documentation/admin-guide/devices.txt +++ b/Documentation/admin-guide/devices.txt @@ -352,7 +352,7 @@ 216 = /dev/fujitsu/apanel Fujitsu/Siemens application panel 217 = /dev/ni/natmotn National Instruments Motion 218 = /dev/kchuid Inter-process chuid control - 219 = /dev/modems/mwave MWave modem firmware upload + 219 = 220 = /dev/mptctl Message passing technology (MPT) control 221 = /dev/mvista/hssdsi Montavista PICMG hot swap system driver 222 = /dev/mvista/hasi Montavista PICMG high availability diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml index ed091dc0c10a..206681ccaa4c 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-sink.yaml @@ -31,7 +31,7 @@ maintainers: - Mike Leach <mike.leach@linaro.org> - Suzuki K Poulose <suzuki.poulose@arm.com> - James Clark <james.clark@linaro.org> - - Mao Jinlong <quic_jinlmao@quicinc.com> + - Mao Jinlong <jinlong.mao@oss.qualcomm.com> - Hao Zhang <quic_hazha@quicinc.com> properties: diff --git a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml index 78337be42b55..0b1e12ae95c3 100644 --- a/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml +++ b/Documentation/devicetree/bindings/arm/arm,coresight-dummy-source.yaml @@ -30,7 +30,7 @@ maintainers: - Mike Leach <mike.leach@linaro.org> - Suzuki K Poulose <suzuki.poulose@arm.com> - James Clark <james.clark@linaro.org> - - Mao Jinlong <quic_jinlmao@quicinc.com> + - Mao Jinlong <jinlong.mao@oss.qualcomm.com> - Hao Zhang <quic_hazha@quicinc.com> properties: diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml index c969c16c21ef..e002f87361ad 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-ctcu.yaml @@ -7,9 +7,9 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: CoreSight TMC Control Unit maintainers: - - Yuanfang Zhang <quic_yuanfang@quicinc.com> - - Mao Jinlong <quic_jinlmao@quicinc.com> - - Jie Gan <quic_jiegan@quicinc.com> + - Yuanfang Zhang <yuanfang.zhang@oss.qualcomm.com> + - Mao Jinlong <jinlong.mao@oss.qualcomm.com> + - Jie Gan <jie.gan@oss.qualcomm.com> description: | The Trace Memory Controller(TMC) is used for Embedded Trace Buffer(ETB), @@ -26,8 +26,13 @@ description: | properties: compatible: - enum: - - qcom,sa8775p-ctcu + oneOf: + - items: + - enum: + - qcom,qcs8300-ctcu + - const: qcom,sa8775p-ctcu + - enum: + - qcom,sa8775p-ctcu reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-itnoc.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-itnoc.yaml new file mode 100644 index 000000000000..8936bb7c3e8e --- /dev/null +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-itnoc.yaml @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/arm/qcom,coresight-itnoc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Interconnect Trace Network On Chip - ITNOC + +maintainers: + - Yuanfang Zhang <yuanfang.zhang@oss.qualcomm.com> + +description: + The Interconnect TNOC is a CoreSight graph link that forwards trace data + from a subsystem to the Aggregator TNOC. Compared to Aggregator TNOC, it + does not have aggregation and ATID functionality. + +properties: + $nodename: + pattern: "^itnoc(@[0-9a-f]+)?$" + + compatible: + const: qcom,coresight-itnoc + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: apb + + 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: out connections to aggregator TNOC + $ref: /schemas/graph.yaml#/properties/port + +required: + - compatible + - reg + - clocks + - clock-names + - in-ports + - out-ports + +additionalProperties: false + +examples: + - | + itnoc@109ac000 { + compatible = "qcom,coresight-itnoc"; + reg = <0x109ac000 0x1000>; + + clocks = <&aoss_qmp>; + clock-names = "apb"; + + in-ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + tn_ic_in_tpdm_dcc: endpoint { + remote-endpoint = <&tpdm_dcc_out_tn_ic>; + }; + }; + }; + + out-ports { + port { + tn_ic_out_tnoc_aggr: endpoint { + /* to Aggregator TNOC input */ + remote-endpoint = <&tn_ag_in_tn_ic>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml index ffe613efeabe..e3a32f30551c 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-remote-etm.yaml @@ -7,8 +7,8 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Qualcomm Coresight Remote ETM(Embedded Trace Macrocell) maintainers: - - Jinlong Mao <quic_jinlmao@quicinc.com> - - Tao Zhang <quic_taozha@quicinc.com> + - Jinlong Mao <jinlong.mao@oss.qualcomm.com> + - Tao Zhang <tao.zhang@oss.qualcomm.com> description: Support for ETM trace collection on remote processor using coresight diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml index 9d1c93a9ade3..ef648a15b806 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tnoc.yaml @@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Qualcomm Trace Network On Chip - TNOC maintainers: - - Yuanfang Zhang <quic_yuanfang@quicinc.com> + - Yuanfang Zhang <yuanfang.zhang@oss.qualcomm.com> description: > The Trace Network On Chip (TNOC) is an integration hierarchy hardware diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml index a48c9ac3eaa9..70d297b054c3 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tpda.yaml @@ -33,8 +33,8 @@ description: | to sink. maintainers: - - Mao Jinlong <quic_jinlmao@quicinc.com> - - Tao Zhang <quic_taozha@quicinc.com> + - Mao Jinlong <jinlong.mao@oss.qualcomm.com> + - Tao Zhang <tao.zhang@oss.qualcomm.com> # Need a custom select here or 'arm,primecell' will match on lots of nodes select: diff --git a/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml b/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml index c349306f0d52..152403f548c3 100644 --- a/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml +++ b/Documentation/devicetree/bindings/arm/qcom,coresight-tpdm.yaml @@ -19,8 +19,8 @@ description: | sources and send it to a TPDA for packetization, timestamping, and funneling. maintainers: - - Mao Jinlong <quic_jinlmao@quicinc.com> - - Tao Zhang <quic_taozha@quicinc.com> + - Mao Jinlong <jinlong.mao@oss.qualcomm.com> + - Tao Zhang <tao.zhang@oss.qualcomm.com> # Need a custom select here or 'arm,primecell' will match on lots of nodes select: diff --git a/Documentation/devicetree/bindings/goldfish/pipe.txt b/Documentation/devicetree/bindings/goldfish/pipe.txt deleted file mode 100644 index 5637ce701788..000000000000 --- a/Documentation/devicetree/bindings/goldfish/pipe.txt +++ /dev/null @@ -1,17 +0,0 @@ -Android Goldfish QEMU Pipe - -Android pipe virtual device generated by android emulator. - -Required properties: - -- compatible : should contain "google,android-pipe" to match emulator -- reg : <registers mapping> -- interrupts : <interrupt mapping> - -Example: - - android_pipe@a010000 { - compatible = "google,android-pipe"; - reg = <ff018000 0x2000>; - interrupts = <0x12>; - }; diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml new file mode 100644 index 000000000000..eeb148081663 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2025 Analog Devices Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad4062.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD4062 ADC family device driver + +maintainers: + - Jorge Marques <jorge.marques@analog.com> + +description: | + Analog Devices AD4062 Single Channel Precision SAR ADC family + + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4060.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4062.pdf + +properties: + compatible: + enum: + - adi,ad4060 + - adi,ad4062 + + reg: + maxItems: 1 + + interrupts: + description: + Two pins are available that can be configured as either a general purpose + digital output, device enable signal (used to synchronise other parts of + the signal chain with ADC sampling), device ready (GP1 only) or various + interrupt signals. If intended for use as a GPIO or device enable, will not + present here. + minItems: 1 + items: + - description: + GP0 pin, cannot be configured as DEV_RDY. + - description: + GP1 pin, can be configured to any setting. + + interrupt-names: + minItems: 1 + items: + - const: gp0 + - const: gp1 + + gpio-controller: + description: + Marks the device node as a GPIO controller. GPs not listed as interrupts + are exposed as a GPO. + + '#gpio-cells': + const: 2 + description: + The first cell is the GPIO number and the second cell specifies + GPIO flags, as defined in <dt-bindings/gpio/gpio.h>. + + vdd-supply: + description: Analog power supply. + + vio-supply: + description: Digital interface logic power supply. + + ref-supply: + description: + Reference voltage to set the ADC full-scale range. If not present, + vdd-supply is used as the reference voltage. + +required: + - compatible + - reg + - vdd-supply + - vio-supply + +allOf: + - $ref: /schemas/i3c/i3c.yaml# + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + i3c { + #address-cells = <3>; + #size-cells = <0>; + + adc@0,2ee007c0000 { + reg = <0x0 0x2ee 0x7c0000>; + vdd-supply = <&vdd>; + vio-supply = <&vio>; + ref-supply = <&ref>; + + interrupt-parent = <&gpio>; + interrupts = <0 0 IRQ_TYPE_EDGE_RISING>, + <0 1 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "gp0", "gp1"; + }; + }; + + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + + i3c { + #address-cells = <3>; + #size-cells = <0>; + + adc@0,2ee007c0000 { + reg = <0x0 0x2ee 0x7c0000>; + vdd-supply = <&vdd>; + vio-supply = <&vio>; + ref-supply = <&ref>; + + gpio-controller; + #gpio-cells = <2>; + }; + }; diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4134.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4134.yaml new file mode 100644 index 000000000000..ea6d7e026419 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4134.yaml @@ -0,0 +1,191 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,ad4134.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD4134 ADC + +maintainers: + - Marcelo Schmitt <marcelo.schmitt@analog.com> + +description: | + The AD4134 is a quad channel, low noise, simultaneous sampling, precision + analog-to-digital converter (ADC). + Specifications can be found at: + https://www.analog.com/media/en/technical-documentation/data-sheets/ad4134.pdf + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - adi,ad4134 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 50000000 + + avdd5-supply: + description: A 5V supply that powers the chip's analog circuitry. + + dvdd5-supply: + description: A 5V supply that powers the chip's digital circuitry. + + iovdd-supply: + description: + A 1.8V supply that sets the logic levels for the digital interface pins. + + refin-supply: + description: + A 4.096V or 5V supply that serves as reference for ADC conversions. + + avdd1v8-supply: + description: A 1.8V supply used by the analog circuitry. + + dvdd1v8-supply: + description: A 1.8V supply used by the digital circuitry. + + clkvdd-supply: + description: A 1.8V supply for the chip's clock management circuit. + + ldoin-supply: + description: + A 2.6V to 5.5V supply that generates 1.8V for AVDD1V8, DVDD1V8, and CLKVDD + pins. + + clocks: + maxItems: 1 + description: + Required external clock source. Can specify either a crystal or CMOS clock + source. If an external crystal is set, connect the CLKSEL pin to IOVDD. + Otherwise, connect the CLKSEL pin to IOGND and the external CMOS clock + signal to the XTAL2/CLKIN pin. + + clock-names: + enum: + - xtal + - clkin + default: clkin + + '#clock-cells': + const: 0 + + clock-output-names: + maxItems: 1 + + regulators: + type: object + description: + list of regulators provided by this controller. + + properties: + vcm-output: + $ref: /schemas/regulator/regulator.yaml# + type: object + unevaluatedProperties: false + + additionalProperties: false + + reset-gpios: + maxItems: 1 + + powerdown-gpios: + description: + Active low GPIO connected to the /PDN pin. Forces the device into full + power-down mode when brought low. Pull this input to IOVDD for normal + operation. + maxItems: 1 + + odr-gpios: + description: + GPIO connected to ODR pin. Used to sample ADC data in minimum I/O mode. + maxItems: 1 + + adi,asrc-mode: + $ref: /schemas/types.yaml#/definitions/string + description: + Asynchronous Sample Rate Converter (ASRC) operation mode control input. + Describes whether the MODE pin is set to a high level (for master mode + operation) or to a low level (for slave mode operation). + enum: [ high, low ] + default: low + + adi,dclkio: + description: + DCLK pin I/O direction control for when the device operates in Pin Control + Slave Mode or in SPI Control Mode. Describes if DEC0/DCLKIO pin is at a + high level (which configures DCLK as an output) or to set to a low level + (configuring DCLK for input). + enum: [ out, in ] + default: in + + adi,dclkmode: + description: + DCLK mode control for when the device operates in Pin Control Slave Mode + or in SPI Control Mode. Describes whether the DEC1/DCLKMODE pin is set to + a high level (configuring the DCLK to operate in free running mode) or + to a low level (to configure DCLK to operate in gated mode). + enum: [ free-running, gated ] + default: gated + +required: + - compatible + - reg + - avdd5-supply + - dvdd5-supply + - iovdd-supply + - refin-supply + - clocks + - clock-names + +oneOf: + - required: + - ldoin-supply + - required: + - avdd1v8-supply + - dvdd1v8-supply + - clkvdd-supply + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,ad4134"; + reg = <0>; + + spi-max-frequency = <1000000>; + + reset-gpios = <&gpio0 86 GPIO_ACTIVE_LOW>; + odr-gpios = <&gpio0 87 GPIO_ACTIVE_HIGH>; + powerdown-gpios = <&gpio0 88 GPIO_ACTIVE_LOW>; + + clocks = <&sys_clk>; + clock-names = "clkin"; + + avdd5-supply = <&avdd5>; + dvdd5-supply = <&dvdd5>; + iovdd-supply = <&iovdd>; + refin-supply = <&refin>; + avdd1v8-supply = <&avdd1v8>; + dvdd1v8-supply = <&dvdd1v8>; + clkvdd-supply = <&clkvdd>; + + regulators { + vcm_reg: vcm-output { + regulator-name = "ad4134-vcm"; + }; + }; + + }; + }; +... diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml index c06d0fc791d3..dfa2d7fa9fb3 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7768-1.yaml @@ -4,18 +4,26 @@ $id: http://devicetree.org/schemas/iio/adc/adi,ad7768-1.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Analog Devices AD7768-1 ADC device driver +title: Analog Devices AD7768-1 ADC family maintainers: - Michael Hennerich <michael.hennerich@analog.com> description: | - Datasheet at: - https://www.analog.com/media/en/technical-documentation/data-sheets/ad7768-1.pdf + Analog Devices AD7768-1 24-Bit Single Channel Low Power sigma-delta ADC family + + https://www.analog.com/media/en/technical-documentation/data-sheets/ad7768-1.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/adaq7767-1.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/adaq7768-1.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/adaq7769-1.pdf properties: compatible: - const: adi,ad7768-1 + enum: + - adi,ad7768-1 + - adi,adaq7767-1 + - adi,adaq7768-1 + - adi,adaq7769-1 reg: maxItems: 1 @@ -58,6 +66,25 @@ properties: description: ADC reference voltage supply + adi,aaf-gain-bp: + description: | + Specifies the gain applied by the Analog Anti-Aliasing Filter (AAF) + to the ADC input in basis points (one hundredth of a percent). + The hardware gain is determined by which input pin(s) the signal goes + through into the AAF. The possible connections are: + * For the ADAQ7767-1: Input connected to IN1±, IN2± or IN3±. + * For the ADAQ7769-1: OUT_PGA pin connected to IN1_AAF+, IN2_AAF+, + or IN3_AAF+. + enum: [1430, 3640, 10000] + default: 10000 + + pga-gpios: + description: + GAIN 0, GAIN1 and GAIN2 pins for gain selection. For devices that have + PGA configuration input pins, pga-gpios must be defined. + minItems: 3 + maxItems: 3 + adi,sync-in-gpios: maxItems: 1 description: @@ -147,6 +174,35 @@ patternProperties: allOf: - $ref: /schemas/spi/spi-peripheral-props.yaml# + # AAF Gain property only applies to ADAQ7767-1 and ADAQ7769-1 devices + - if: + properties: + compatible: + contains: + enum: + - adi,adaq7767-1 + - adi,adaq7769-1 + then: + required: + - adi,aaf-gain-bp + else: + properties: + adi,aaf-gain-bp: false + + - if: + properties: + compatible: + contains: + enum: + - adi,adaq7768-1 + - adi,adaq7769-1 + then: + required: + - pga-gpios + else: + properties: + pga-gpios: false + unevaluatedProperties: false examples: diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml index 2606c0c5dfc6..5acfb0eef4d5 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad9467.yaml @@ -18,6 +18,7 @@ description: | All the parts support the register map described by Application Note AN-877 https://www.analog.com/media/en/technical-documentation/application-notes/AN-877.pdf + https://www.analog.com/media/en/technical-documentation/data-sheets/AD9211.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD9265.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD9434.pdf https://www.analog.com/media/en/technical-documentation/data-sheets/AD9467.pdf @@ -25,6 +26,7 @@ description: | properties: compatible: enum: + - adi,ad9211 - adi,ad9265 - adi,ad9434 - adi,ad9467 diff --git a/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml b/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml index 509bfb1007c4..249101b55cf4 100644 --- a/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml @@ -44,6 +44,9 @@ properties: Input clock used to derive the sample clock. Expected to be the SoC's APB clock. + interrupts: + maxItems: 1 + resets: maxItems: 1 diff --git a/Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml b/Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml new file mode 100644 index 000000000000..ec258f224df8 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/nxp,s32g2-sar-adc.yaml @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/nxp,s32g2-sar-adc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP Successive Approximation ADC + +description: + The NXP SAR ADC provides fast and accurate analog-to-digital + conversion using the Successive Approximation Register (SAR) method. + It has 12-bit resolution with 8 input channels. Conversions can be + launched in software or using hardware triggers. It supports + continuous and one-shot modes with separate registers. + +maintainers: + - Daniel Lezcano <daniel.lezcano@kernel.org> + +properties: + compatible: + oneOf: + - const: nxp,s32g2-sar-adc + - items: + - const: nxp,s32g3-sar-adc + - const: nxp,s32g2-sar-adc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + dmas: + maxItems: 1 + + dma-names: + const: rx + +required: + - compatible + - reg + - interrupts + - clocks + - dmas + - dma-names + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + + adc@401f8000 { + compatible = "nxp,s32g2-sar-adc"; + reg = <0x401f8000 0x1000>; + interrupts = <GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks 0x41>; + dmas = <&edma0 0 32>; + dma-names = "rx"; + }; diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml new file mode 100644 index 000000000000..81ee024be2e3 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads1018.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI ADS1018/ADS1118 SPI analog to digital converter + +maintainers: + - Kurt Borja <kuurtb@gmail.com> + +description: | + The ADS1018/ADS1118 is a precision, low-power, 12-bit/16-bit, analog to + digital converter (ADC). It integrates a programmable gain amplifier (PGA), + internal voltage reference, oscillator and high-accuracy temperature sensor. + + Datasheets: + - ADS1018: https://www.ti.com/lit/ds/symlink/ads1018.pdf + - ADS1118: https://www.ti.com/lit/ds/symlink/ads1118.pdf + +properties: + compatible: + enum: + - ti,ads1018 + - ti,ads1118 + + reg: + maxItems: 1 + + vdd-supply: true + + spi-max-frequency: + maximum: 4000000 + + spi-cpha: true + + interrupts: + description: DOUT/DRDY (Data Out/Data Ready) line. + maxItems: 1 + + drdy-gpios: + description: + Extra GPIO line connected to DOUT/DRDY (Data Out/Data Ready). This allows + distinguishing between interrupts triggered by the data-ready signal and + interrupts triggered by an SPI transfer. + maxItems: 1 + + '#io-channel-cells': + const: 1 + +required: + - compatible + - reg + - vdd-supply + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/gpio/gpio.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "ti,ads1118"; + reg = <0>; + + spi-max-frequency = <4000000>; + spi-cpha; + + vdd-supply = <&vdd_3v3_reg>; + + interrupts-extended = <&gpio 14 IRQ_TYPE_EDGE_FALLING>; + drdy-gpios = <&gpio 14 GPIO_ACTIVE_LOW>; + }; + }; diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml new file mode 100644 index 000000000000..5d52bb7dd5d4 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads131m02.yaml @@ -0,0 +1,208 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads131m02.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments ADS131M0x 2-, 3-, 4-, 6- and 8-Channel ADCs + +maintainers: + - Oleksij Rempel <o.rempel@pengutronix.de> + +description: | + The ADS131M0x are a family of multichannel, simultaneous sampling, + 24-bit, delta-sigma, analog-to-digital converters (ADCs) with a + built-in programmable gain amplifier (PGA) and internal reference. + Communication with the ADC chip is via SPI. + + Datasheets: + - ADS131M02: https://www.ti.com/lit/ds/symlink/ads131m02.pdf + - ADS131M03: https://www.ti.com/lit/ds/symlink/ads131m03.pdf + - ADS131M04: https://www.ti.com/lit/ds/symlink/ads131m04.pdf + - ADS131M06: https://www.ti.com/lit/ds/symlink/ads131m06.pdf + - ADS131M08: https://www.ti.com/lit/ds/symlink/ads131m08.pdf + +properties: + compatible: + enum: + - ti,ads131m02 + - ti,ads131m03 + - ti,ads131m04 + - ti,ads131m06 + - ti,ads131m08 + + reg: + description: SPI chip select number. + + clocks: + description: + Phandle to the external clock source required by the ADC's CLKIN pin. + The datasheet recommends specific frequencies based on the desired power + mode (e.g., 8.192 MHz for High-Resolution mode). + maxItems: 1 + + avdd-supply: + description: Analog power supply (AVDD). + + dvdd-supply: + description: Digital power supply (DVDD). + + interrupts: + description: DRDY (Data Ready) output signal. + maxItems: 1 + + reset-gpios: + description: Optional RESET signal. + maxItems: 1 + + clock-names: + description: + Indicates if a crystal oscillator (XTAL) or CMOS signal is connected + (CLKIN). Note that XTAL mode is only supported on ADS131M06 and ADS131M08. + enum: [xtal, clkin] + + refin-supply: + description: Optional external reference supply (REFIN). + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +required: + - compatible + - reg + - clocks + - clock-names + - avdd-supply + - dvdd-supply + +patternProperties: + "^channel@[0-7]$": + type: object + $ref: /schemas/iio/adc/adc.yaml# + description: Properties for a single ADC channel. + + properties: + reg: + description: The channel index (0-7). + minimum: 0 + maximum: 7 # Max channels on ADS131M08 + + label: true + + required: + - reg + + unevaluatedProperties: false + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + + - if: + # 20-pin devices: M02, M03, M04 + # These do not support XTAL or REFIN. + properties: + compatible: + enum: + - ti,ads131m02 + - ti,ads131m03 + - ti,ads131m04 + then: + properties: + clock-names: + const: clkin + refin-supply: false + + - if: + # ADS131M02: 2 channels max (0-1) + properties: + compatible: + contains: + const: ti,ads131m02 + then: + patternProperties: + "^channel@[0-1]$": + properties: + reg: + maximum: 1 + "^channel@[2-7]$": false + + - if: + # ADS131M03: 3 channels max (0-2) + properties: + compatible: + contains: + const: ti,ads131m03 + then: + patternProperties: + "^channel@[0-2]$": + properties: + reg: + maximum: 2 + "^channel@[3-7]$": false + + - if: + # ADS131M04: 4 channels max (0-3) + properties: + compatible: + contains: + const: ti,ads131m04 + then: + patternProperties: + "^channel@[0-3]$": + properties: + reg: + maximum: 3 + "^channel@[4-7]$": false + + - if: + # ADS131M06: 6 channels max (0-5) + properties: + compatible: + contains: + const: ti,ads131m06 + then: + patternProperties: + "^channel@[0-5]$": + properties: + reg: + maximum: 5 + "^channel@[6-7]$": false + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/clock/stm32mp1-clks.h> + + spi1 { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "ti,ads131m02"; + reg = <0>; + spi-max-frequency = <8000000>; + + clocks = <&rcc CK_MCO2>; + clock-names = "clkin"; + + avdd-supply = <&vdd_ana>; + dvdd-supply = <&vdd_dig>; + + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + label = "input_voltage"; + }; + + channel@1 { + reg = <1>; + label = "input_current"; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml b/Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml new file mode 100644 index 000000000000..6b8491d18139 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/amplifiers/adi,adl8113.yaml @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/amplifiers/adi,adl8113.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices ADL8113 Low Noise Amplifier with integrated bypass switches + +maintainers: + - Antoniu Miclaus <antoniu.miclaus@analog.com> + +description: | + The ADL8113 is a 10MHz to 12GHz Low Noise Amplifier with integrated bypass + switches controlled by two GPIO pins (VA and VB). The device supports four + operation modes: + - Internal Amplifier: VA=0, VB=0 - Signal passes through the internal LNA + - Internal Bypass: VA=1, VB=1 - Signal bypasses through internal path + - External Bypass A: VA=0, VB=1 - Signal routes from RFIN to OUT_A and from IN_A to RFOUT + - External Bypass B: VA=1, VB=0 - Signal routes from RFIN to OUT_B and from IN_B to RFOUT + + https://www.analog.com/en/products/adl8113.html + +properties: + compatible: + const: adi,adl8113 + + vdd1-supply: true + + vdd2-supply: true + + vss2-supply: true + + ctrl-gpios: + items: + - description: VA control pin + - description: VB control pin + + adi,external-bypass-a-gain-db: + description: + Gain in dB of external amplifier connected to bypass path A (OUT_A/IN_A). + When specified, this gain value becomes selectable via the hardwaregain + attribute and automatically routes through the external A path. + + adi,external-bypass-b-gain-db: + description: + Gain in dB of external amplifier connected to bypass path B (OUT_B/IN_B). + When specified, this gain value becomes selectable via the hardwaregain + attribute and automatically routes through the external B path. + +required: + - compatible + - ctrl-gpios + - vdd1-supply + - vdd2-supply + - vss2-supply + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + /* Basic configuration with only internal paths */ + amplifier { + compatible = "adi,adl8113"; + ctrl-gpios = <&gpio 22 GPIO_ACTIVE_HIGH>, + <&gpio 23 GPIO_ACTIVE_HIGH>; + vdd1-supply = <&vdd1_5v>; + vdd2-supply = <&vdd2_3v3>; + vss2-supply = <&vss2_neg>; + }; + + - | + #include <dt-bindings/gpio/gpio.h> + + /* Configuration with external bypass amplifiers */ + amplifier { + compatible = "adi,adl8113"; + ctrl-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>, + <&gpio 25 GPIO_ACTIVE_HIGH>; + vdd1-supply = <&vdd1_5v>; + vdd2-supply = <&vdd2_3v3>; + vss2-supply = <&vss2_neg>; + adi,external-bypass-a-gain-db = <20>; /* 20dB external amp on path A */ + adi,external-bypass-b-gain-db = <6>; /* 6dB external amp on path B */ + }; +... diff --git a/Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml b/Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml new file mode 100644 index 000000000000..93d95f6b4c08 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/adi,max22007.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX22007 DAC + +maintainers: + - Janani Sunil <janani.sunil@analog.com> + +description: + The MAX22007 is a quad-channel, 12-bit digital-to-analog converter (DAC) + with integrated precision output amplifiers and current output capability. + Each channel can be independently configured for voltage or current output. + Datasheet available at https://www.analog.com/en/products/max22007.html + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + const: adi,max22007 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 500000 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + vdd-supply: + description: Low-Voltage Power Supply from +2.7V to +5.5V. + + hvdd-supply: + description: + Positive High-Voltage Power Supply from +8V to (HVSS +24V) for + the Output Channels. + + hvss-supply: + description: + Optional Negative High-Voltage Power Supply from -2V to 0V for the Output + Channels. For most applications HVSS can be connected to GND (0V), but for + applications requiring output down to true 0V or 0mA, connect to a -2V supply. + + reset-gpios: + maxItems: 1 + description: + Active low GPIO. + +patternProperties: + "^channel@[0-3]$": + $ref: /schemas/iio/dac/dac.yaml# + type: object + description: + Represents the external channels which are connected to the DAC. + + properties: + reg: + description: Channel number + items: + minimum: 0 + maximum: 3 + + adi,ch-func: + description: + Channel output type. Use CH_FUNC_VOLTAGE_OUTPUT for voltage + output or CH_FUNC_CURRENT_OUTPUT for current output. + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2] + + required: + - reg + - adi,ch-func + + unevaluatedProperties: false + +required: + - compatible + - reg + - vdd-supply + - hvdd-supply + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/iio/addac/adi,ad74413r.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + dac@0 { + compatible = "adi,max22007"; + reg = <0>; + spi-max-frequency = <500000>; + reset-gpios = <&gpio 19 GPIO_ACTIVE_LOW>; + vdd-supply = <&vdd_reg>; + hvdd-supply = <&hvdd_reg>; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + adi,ch-func = <CH_FUNC_VOLTAGE_OUTPUT>; + }; + + channel@1 { + reg = <1>; + adi,ch-func = <CH_FUNC_CURRENT_OUTPUT>; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml b/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml new file mode 100644 index 000000000000..d2466aa6bda2 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml @@ -0,0 +1,302 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/microchip,mcp47feb02.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip MCP47F(E/V)B(0/1/2)(1/2/4/8) DAC with I2C Interface Families + +maintainers: + - Ariana Lazar <ariana.lazar@microchip.com> + +description: | + Datasheet for MCP47FEB01, MCP47FEB11, MCP47FEB21, MCP47FEB02, MCP47FEB12, + MCP47FEB22 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf + Datasheet for MCP47FVB01, MCP47FVB11, MCP47FVB21, MCP47FVB02, MCP47FVB12, + MCP47FVB22 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf + Datasheet for MCP47FEB04, MCP47FEB14, MCP47FEB24, MCP47FEB08, MCP47FEB18, + MCP47FEB28, MCP47FVB04, MCP47FVB14, MCP47FVB24, MCP47FVB08, MCP47FVB18, + MCP47FVB28 can be found here: + https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf + + +------------+--------------+-------------+-------------+------------+ + | Device | Resolution | Channels | Vref number | Memory | + |------------|--------------|-------------|-------------|------------| + | MCP47FEB01 | 8-bit | 1 | 1 | EEPROM | + | MCP47FEB11 | 10-bit | 1 | 1 | EEPROM | + | MCP47FEB21 | 12-bit | 1 | 1 | EEPROM | + |------------|--------------|-------------|-------------|------------| + | MCP47FEB02 | 8-bit | 2 | 1 | EEPROM | + | MCP47FEB12 | 10-bit | 2 | 1 | EEPROM | + | MCP47FEB22 | 12-bit | 2 | 1 | EEPROM | + |------------|--------------|-------------|-------------|------------| + | MCP47FVB01 | 8-bit | 1 | 1 | RAM | + | MCP47FVB11 | 10-bit | 1 | 1 | RAM | + | MCP47FVB21 | 12-bit | 1 | 1 | RAM | + |------------|--------------|-------------|-------------|------------| + | MCP47FVB02 | 8-bit | 2 | 1 | RAM | + | MCP47FVB12 | 10-bit | 2 | 1 | RAM | + | MCP47FVB22 | 12-bit | 2 | 1 | RAM | + |------------|--------------|-------------|-------------|------------| + | MCP47FVB04 | 8-bit | 4 | 2 | RAM | + | MCP47FVB14 | 10-bit | 4 | 2 | RAM | + | MCP47FVB24 | 12-bit | 4 | 2 | RAM | + |------------|--------------|-------------|-------------|------------| + | MCP47FVB08 | 8-bit | 8 | 2 | RAM | + | MCP47FVB18 | 10-bit | 8 | 2 | RAM | + | MCP47FVB28 | 12-bit | 8 | 2 | RAM | + |------------|--------------|-------------|-------------|------------| + | MCP47FEB04 | 8-bit | 4 | 2 | EEPROM | + | MCP47FEB14 | 10-bit | 4 | 2 | EEPROM | + | MCP47FEB24 | 12-bit | 4 | 2 | EEPROM | + |------------|--------------|-------------|-------------|------------| + | MCP47FEB08 | 8-bit | 8 | 2 | EEPROM | + | MCP47FEB18 | 10-bit | 8 | 2 | EEPROM | + | MCP47FEB28 | 12-bit | 8 | 2 | EEPROM | + +------------+--------------+-------------+-------------+------------+ + +properties: + compatible: + enum: + - microchip,mcp47feb01 + - microchip,mcp47feb11 + - microchip,mcp47feb21 + - microchip,mcp47feb02 + - microchip,mcp47feb12 + - microchip,mcp47feb22 + - microchip,mcp47fvb01 + - microchip,mcp47fvb11 + - microchip,mcp47fvb21 + - microchip,mcp47fvb02 + - microchip,mcp47fvb12 + - microchip,mcp47fvb22 + - microchip,mcp47fvb04 + - microchip,mcp47fvb14 + - microchip,mcp47fvb24 + - microchip,mcp47fvb08 + - microchip,mcp47fvb18 + - microchip,mcp47fvb28 + - microchip,mcp47feb04 + - microchip,mcp47feb14 + - microchip,mcp47feb24 + - microchip,mcp47feb08 + - microchip,mcp47feb18 + - microchip,mcp47feb28 + + reg: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + vdd-supply: + description: + Provides power to the chip and it could be used as reference voltage. The + voltage is used to calculate scale. For parts without EEPROM at powerup + this will be the selected as voltage reference. + + vref-supply: + description: | + Vref pin (it could be found as Vref0 into the datasheet) may be used as a + voltage reference when this supply is specified. The internal reference + will be taken into account for voltage reference besides VDD if this supply + does not exist. + + This supply will be voltage reference for the following outputs: + - for single-channel device: Vout0; + - for dual-channel device: Vout0, Vout1; + - for quad-channel device: Vout0, Vout2; + - for octal-channel device: Vout0, Vout2, Vout6, Vout8; + + vref1-supply: + description: | + Vref1 pin may be used as a voltage reference when this supply is specified. + The internal reference will be taken into account for voltage reference + beside VDD if this supply does not exist. + + This supply will be voltage reference for the following outputs: + - for quad-channel device: Vout1, Vout3; + - for octal-channel device: Vout1, Vout3, Vout5, Vout7; + + lat-gpios: + description: + LAT pin to be used as a hardware trigger to synchronously update the DAC + channels. The pin is active Low. It could be also found as LAT0 in + datasheet. + maxItems: 1 + + lat1-gpios: + description: + LAT1 pin to be used as a hardware trigger to synchronously update the odd + DAC channels on devices with 4 and 8 channels. The pin is active Low. + maxItems: 1 + + microchip,vref-buffered: + type: boolean + description: + Enable buffering of the external Vref/Vref0 pin in cases where the + external reference voltage does not have sufficient current capability in + order not to drop it’s voltage when connected to the internal resistor + ladder circuit. + + microchip,vref1-buffered: + type: boolean + description: + Enable buffering of the external Vref1 pin in cases where the external + reference voltage does not have sufficient current capability in order not + to drop it’s voltage when connected to the internal resistor ladder + circuit. + +patternProperties: + "^channel@[0-7]$": + $ref: dac.yaml + type: object + description: Voltage output channel. + + properties: + reg: + description: The channel number. + minItems: 1 + maxItems: 8 + + label: + description: Unique name to identify which channel this is. + + required: + - reg + + unevaluatedProperties: false + +required: + - compatible + - reg + - vdd-supply + +allOf: + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp47feb01 + - microchip,mcp47feb11 + - microchip,mcp47feb21 + - microchip,mcp47fvb01 + - microchip,mcp47fvb11 + - microchip,mcp47fvb21 + then: + properties: + lat1-gpios: false + vref1-supply: false + microchip,vref1-buffered: false + channel@0: + properties: + reg: + const: 0 + patternProperties: + "^channel@[1-7]$": false + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp47feb02 + - microchip,mcp47feb12 + - microchip,mcp47feb22 + - microchip,mcp47fvb02 + - microchip,mcp47fvb12 + - microchip,mcp47fvb22 + then: + properties: + lat1-gpios: false + vref1-supply: false + microchip,vref1-buffered: false + patternProperties: + "^channel@[0-1]$": + properties: + reg: + enum: [0, 1] + "^channel@[2-7]$": false + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp47fvb04 + - microchip,mcp47fvb14 + - microchip,mcp47fvb24 + - microchip,mcp47feb04 + - microchip,mcp47feb14 + - microchip,mcp47feb24 + then: + patternProperties: + "^channel@[0-3]$": + properties: + reg: + enum: [0, 1, 2, 3] + "^channel@[4-7]$": false + - if: + properties: + compatible: + contains: + enum: + - microchip,mcp47fvb08 + - microchip,mcp47fvb18 + - microchip,mcp47fvb28 + - microchip,mcp47feb08 + - microchip,mcp47feb18 + - microchip,mcp47feb28 + then: + patternProperties: + "^channel@[0-7]$": + properties: + reg: + enum: [0, 1, 2, 3, 4, 5, 6, 7] + - if: + not: + required: + - vref-supply + then: + properties: + microchip,vref-buffered: false + - if: + not: + required: + - vref1-supply + then: + properties: + microchip,vref1-buffered: false + +additionalProperties: false + +examples: + - | + i2c { + + #address-cells = <1>; + #size-cells = <0>; + dac@0 { + compatible = "microchip,mcp47feb02"; + reg = <0>; + vdd-supply = <&vdac_vdd>; + vref-supply = <&vref_reg>; + + #address-cells = <1>; + #size-cells = <0>; + channel@0 { + reg = <0>; + label = "Adjustable_voltage_ch0"; + }; + + channel@1 { + reg = <0x1>; + label = "Adjustable_voltage_ch1"; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml index 5f950ee9aec7..be69b9c68e74 100644 --- a/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml +++ b/Documentation/devicetree/bindings/iio/frequency/adi,adf4377.yaml @@ -40,6 +40,12 @@ properties: items: - const: ref_in + '#clock-cells': + const: 0 + + clock-output-names: + maxItems: 1 + chip-enable-gpios: description: GPIO that controls the Chip Enable Pin. @@ -97,6 +103,8 @@ examples: spi-max-frequency = <10000000>; clocks = <&adf4377_ref_in>; clock-names = "ref_in"; + #clock-cells = <0>; + clock-output-names = "adf4377"; }; }; ... diff --git a/Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml b/Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml new file mode 100644 index 000000000000..e82897ffac3b --- /dev/null +++ b/Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/pressure/honeywell,abp2030pa.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Honeywell abp2030pa pressure sensor + +maintainers: + - Petre Rodan <petre.rodan@subdimension.ro> + +description: | + Honeywell pressure sensor of model abp2030pa. + + This sensor has an I2C and SPI interface. + + There are many models with different pressure ranges available. The vendor + calls them "ABP2 series". All of them have an identical programming model and + differ in the pressure range and measurement unit. + + To support different models one needs to specify its pressure triplet. + + For custom silicon chips not covered by the Honeywell ABP2 series datasheet, + the pressure values can be specified manually via honeywell,pmin-pascal and + honeywell,pmax-pascal. + + Specifications about the devices can be found at: + https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/basic-abp2-series/documents/sps-siot-abp2-series-datasheet-32350268-en.pdf + +properties: + compatible: + const: honeywell,abp2030pa + + reg: + maxItems: 1 + + interrupts: + description: + Optional interrupt for indicating end of conversion. + SPI variants of ABP2 chips do not provide this feature. + maxItems: 1 + + honeywell,pressure-triplet: + description: | + Case-sensitive five character string that defines pressure range, unit + and type as part of the device nomenclature. In the unlikely case of a + custom chip, unset and provide pmin-pascal and pmax-pascal instead. + enum: [001BA, 1.6BA, 2.5BA, 004BA, 006BA, 008BA, 010BA, 012BA, 001BD, + 1.6BD, 2.5BD, 004BD, 001BG, 1.6BG, 2.5BG, 004BG, 006BG, 008BG, + 010BG, 012BG, 001GG, 1.2GG, 100KA, 160KA, 250KA, 001KD, 1.6KD, + 2.5KD, 004KD, 006KD, 010KD, 016KD, 025KD, 040KD, 060KD, 100KD, + 160KD, 250KD, 400KD, 001KG, 1.6KG, 2.5KG, 004KG, 006KG, 010KG, + 016KG, 025KG, 040KG, 060KG, 100KG, 160KG, 250KG, 400KG, 600KG, + 800KG, 250LD, 600LD, 600LG, 2.5MD, 006MD, 010MD, 016MD, 025MD, + 040MD, 060MD, 100MD, 160MD, 250MD, 400MD, 600MD, 006MG, 010MG, + 016MG, 025MG, 040MG, 060MG, 100MG, 160MG, 250MG, 400MG, 600MG, + 001ND, 002ND, 004ND, 005ND, 010ND, 020ND, 030ND, 002NG, 004NG, + 005NG, 010NG, 020NG, 030NG, 015PA, 030PA, 060PA, 100PA, 150PA, + 175PA, 001PD, 005PD, 015PD, 030PD, 060PD, 001PG, 005PG, 015PG, + 030PG, 060PG, 100PG, 150PG, 175PG] + $ref: /schemas/types.yaml#/definitions/string + + honeywell,pmin-pascal: + description: + Minimum pressure value the sensor can measure in pascal. + + honeywell,pmax-pascal: + description: + Maximum pressure value the sensor can measure in pascal. + + spi-max-frequency: + maximum: 800000 + + vdd-supply: true + +required: + - compatible + - reg + - vdd-supply + +oneOf: + - required: + - honeywell,pressure-triplet + - required: + - honeywell,pmin-pascal + - honeywell,pmax-pascal + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml + - if: + required: + - honeywell,pressure-triplet + then: + properties: + honeywell,pmin-pascal: false + honeywell,pmax-pascal: false + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pressure@18 { + compatible = "honeywell,abp2030pa"; + reg = <0x18>; + interrupt-parent = <&gpio3>; + interrupts = <21 IRQ_TYPE_EDGE_RISING>; + + honeywell,pressure-triplet = "001BA"; + vdd-supply = <&vcc_3v3>; + }; + }; + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + pressure@0 { + compatible = "honeywell,abp2030pa"; + reg = <0>; + spi-max-frequency = <800000>; + + honeywell,pressure-triplet = "001PD"; + vdd-supply = <&vcc_3v3>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/iio/proximity/rfdigital,rfd77402.yaml b/Documentation/devicetree/bindings/iio/proximity/rfdigital,rfd77402.yaml new file mode 100644 index 000000000000..1ef6326b209e --- /dev/null +++ b/Documentation/devicetree/bindings/iio/proximity/rfdigital,rfd77402.yaml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/proximity/rfdigital,rfd77402.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: RF Digital RFD77402 ToF sensor + +maintainers: + - Shrikant Raskar <raskar.shree97@gmail.com> + +description: + The RF Digital RFD77402 is a Time-of-Flight (ToF) proximity and distance + sensor providing up to 200 mm range measurement over an I2C interface. + +properties: + compatible: + const: rfdigital,rfd77402 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + description: + Interrupt asserted when a new distance measurement is available. + + vdd-supply: + description: Regulator that provides power to the sensor. + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + i2c { + #address-cells = <1>; + #size-cells = <0>; + + proximity@4c { + compatible = "rfdigital,rfd77402"; + reg = <0x4c>; + vdd-supply = <&vdd_3v3>; + interrupt-parent = <&gpio>; + interrupts = <4 IRQ_TYPE_EDGE_FALLING>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/interconnect/mediatek,mt8183-emi.yaml b/Documentation/devicetree/bindings/interconnect/mediatek,mt8183-emi.yaml index 017c8478b2a7..1fb8ccb558fb 100644 --- a/Documentation/devicetree/bindings/interconnect/mediatek,mt8183-emi.yaml +++ b/Documentation/devicetree/bindings/interconnect/mediatek,mt8183-emi.yaml @@ -40,6 +40,7 @@ properties: enum: - mediatek,mt8183-emi - mediatek,mt8195-emi + - mediatek,mt8196-emi '#interconnect-cells': const: 1 diff --git a/Documentation/devicetree/bindings/interconnect/qcom,msm8998-bwmon.yaml b/Documentation/devicetree/bindings/interconnect/qcom,msm8998-bwmon.yaml index 17b09292000e..ce79521bb1ef 100644 --- a/Documentation/devicetree/bindings/interconnect/qcom,msm8998-bwmon.yaml +++ b/Documentation/devicetree/bindings/interconnect/qcom,msm8998-bwmon.yaml @@ -25,6 +25,7 @@ properties: - const: qcom,msm8998-bwmon # BWMON v4 - items: - enum: + - qcom,glymur-cpu-bwmon - qcom,kaanapali-cpu-bwmon - qcom,qcm2290-cpu-bwmon - qcom,qcs615-cpu-bwmon diff --git a/Documentation/devicetree/bindings/interconnect/qcom,qcs615-rpmh.yaml b/Documentation/devicetree/bindings/interconnect/qcom,qcs615-rpmh.yaml index 9d762b2a1fcf..e06404828824 100644 --- a/Documentation/devicetree/bindings/interconnect/qcom,qcs615-rpmh.yaml +++ b/Documentation/devicetree/bindings/interconnect/qcom,qcs615-rpmh.yaml @@ -27,7 +27,6 @@ properties: - qcom,qcs615-config-noc - qcom,qcs615-dc-noc - qcom,qcs615-gem-noc - - qcom,qcs615-ipa-virt - qcom,qcs615-mc-virt - qcom,qcs615-mmss-noc - qcom,qcs615-system-noc @@ -46,7 +45,6 @@ allOf: contains: enum: - qcom,qcs615-camnoc-virt - - qcom,qcs615-ipa-virt - qcom,qcs615-mc-virt then: properties: diff --git a/Documentation/devicetree/bindings/misc/google,android-pipe.yaml b/Documentation/devicetree/bindings/misc/google,android-pipe.yaml new file mode 100644 index 000000000000..9e8046fd358d --- /dev/null +++ b/Documentation/devicetree/bindings/misc/google,android-pipe.yaml @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/misc/google,android-pipe.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Android Goldfish QEMU Pipe + +maintainers: + - Kuan-Wei Chiu <visitorckw@gmail.com> + +description: + Android QEMU pipe virtual device generated by Android emulator. + +properties: + compatible: + const: google,android-pipe + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + pipe@ff018000 { + compatible = "google,android-pipe"; + reg = <0xff018000 0x2000>; + interrupts = <18>; + }; diff --git a/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml b/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml index 3f6199fc9ae6..d8e47db677cc 100644 --- a/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml +++ b/Documentation/devicetree/bindings/misc/qcom,fastrpc.yaml @@ -18,7 +18,9 @@ description: | properties: compatible: - const: qcom,fastrpc + enum: + - qcom,kaanapali-fastrpc + - qcom,fastrpc label: enum: diff --git a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml index 7d1612acca48..839513d4b499 100644 --- a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml +++ b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml @@ -55,6 +55,7 @@ properties: - qcom,sm8450-qfprom - qcom,sm8550-qfprom - qcom,sm8650-qfprom + - qcom,sm8750-qfprom - qcom,x1e80100-qfprom - const: qcom,qfprom diff --git a/Documentation/devicetree/bindings/spmi/mediatek,mt8196-spmi.yaml b/Documentation/devicetree/bindings/spmi/mediatek,mt8196-spmi.yaml new file mode 100644 index 000000000000..7a534f0a1d87 --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/mediatek,mt8196-spmi.yaml @@ -0,0 +1,138 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spmi/mediatek,mt8196-spmi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT8196 SPMI 2.0 Controller + +maintainers: + - Hsin-Hsiung Wang <hsin-hsiung.wang@mediatek.com> + - AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> + +description: + The MediaTek MT8196 SoC features a SPMI version 2.0 compliant controller, + with internal wrapping arbitration logic to allow for multiple on-chip + devices to control up to two SPMI buses. + The main arbiter also acts as an interrupt controller, arbitering also + the interrupts coming from SPMI-connected devices into each of the nested + interrupt controllers from any of the present SPMI buses. + +properties: + compatible: + oneOf: + - enum: + - mediatek,mt8196-spmi + - items: + - enum: + - mediatek,mt6991-spmi + - const: mediatek,mt8196-spmi + + ranges: true + + '#address-cells': + const: 1 + + '#size-cells': + const: 1 + +patternProperties: + "^spmi@[a-f0-9]+$": + type: object + $ref: /schemas/spmi/spmi.yaml + unevaluatedProperties: false + + properties: + reg: + items: + - description: controller interface registers + - description: spmi master controller registers + + reg-names: + items: + - const: pmif + - const: spmimst + + clocks: + items: + - description: controller interface system clock + - description: controller interface timer clock + - description: spmi controller master clock + + clock-names: + items: + - const: pmif_sys_ck + - const: pmif_tmr_ck + - const: spmimst_clk_mux + + interrupts: + maxItems: 1 + + interrupt-names: + const: rcs + + interrupt-controller: true + + "#interrupt-cells": + const: 3 + description: | + cell 1: slave ID for the requested interrupt (0-15) + cell 2: the requested peripheral interrupt (0-7) + cell 3: interrupt flags indicating level-sense information, + as defined in dt-bindings/interrupt-controller/irq.h + required: + - reg + - reg-names + - clocks + - clock-names + - interrupts + - interrupt-names + - interrupt-controller + - "#interrupt-cells" + +required: + - compatible + - ranges + - '#address-cells' + - '#size-cells' + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + + soc { + #address-cells = <2>; + #size-cells = <2>; + + spmi-arbiter@1c018000 { + compatible = "mediatek,mt8196-spmi"; + ranges = <0 0 0x1c018000 0x4900>; + #address-cells = <1>; + #size-cells = <1>; + + spmi@0 { + reg = <0 0x900>, <0x4800 0x100>; + reg-names = "pmif", "spmimst"; + interrupts-extended = <&pio 292 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "rcs"; + interrupt-controller; + #interrupt-cells = <3>; + clocks = <&pmif_sys>, <&pmif_tmr>, <&spmi_mst>; + clock-names = "pmif_sys_ck", "pmif_tmr_ck", "spmimst_clk_mux"; + }; + + spmi@2000 { + reg = <0x2000 0x900>, <0x4000 0x100>; + reg-names = "pmif", "spmimst"; + interrupts-extended = <&pio 291 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "rcs"; + interrupt-controller; + #interrupt-cells = <3>; + clocks = <&pmif_sys>, <&pmif_tmr>, <&spmi_mst>; + clock-names = "pmif_sys_ck", "pmif_tmr_ck", "spmimst_clk_mux"; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/spmi/mtk,spmi-mtk-pmif.yaml b/Documentation/devicetree/bindings/spmi/mtk,spmi-mtk-pmif.yaml index 7f0be0ac644a..dc61d88008a9 100644 --- a/Documentation/devicetree/bindings/spmi/mtk,spmi-mtk-pmif.yaml +++ b/Documentation/devicetree/bindings/spmi/mtk,spmi-mtk-pmif.yaml @@ -26,6 +26,7 @@ properties: - enum: - mediatek,mt8186-spmi - mediatek,mt8188-spmi + - mediatek,mt8189-spmi - const: mediatek,mt8195-spmi reg: diff --git a/Documentation/devicetree/bindings/spmi/qcom,glymur-spmi-pmic-arb.yaml b/Documentation/devicetree/bindings/spmi/qcom,glymur-spmi-pmic-arb.yaml new file mode 100644 index 000000000000..3b5005b96c6d --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/qcom,glymur-spmi-pmic-arb.yaml @@ -0,0 +1,150 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spmi/qcom,glymur-spmi-pmic-arb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies, Inc. Glymur SPMI Controller (PMIC Arbiter v8) + +maintainers: + - David Collins <david.collins@oss.qualcomm.com> + +description: | + The Glymur SPMI PMIC Arbiter implements HW version 8 and it's an SPMI + controller with wrapping arbitration logic to allow for multiple on-chip + devices to control up to 4 SPMI separate buses. + + The PMIC Arbiter can also act as an interrupt controller, providing interrupts + to slave devices. + +allOf: + - $ref: /schemas/spmi/qcom,spmi-pmic-arb-common.yaml + +properties: + compatible: + oneOf: + - items: + - enum: + - qcom,kaanapali-spmi-pmic-arb + - const: qcom,glymur-spmi-pmic-arb + - enum: + - qcom,glymur-spmi-pmic-arb + + reg: + items: + - description: core registers + - description: tx-channel per virtual slave registers + - description: rx-channel (called observer) per virtual slave registers + - description: channel to PMIC peripheral mapping registers + + reg-names: + items: + - const: core + - const: chnls + - const: obsrvr + - const: chnl_map + + ranges: true + + '#address-cells': + const: 2 + + '#size-cells': + const: 2 + +patternProperties: + "^spmi@[a-f0-9]+$": + type: object + $ref: /schemas/spmi/spmi.yaml + unevaluatedProperties: false + + properties: + reg: + items: + - description: configuration registers + - description: interrupt controller registers + - description: channel owner EE mapping registers + + reg-names: + items: + - const: cnfg + - const: intr + - const: chnl_owner + + interrupts: + maxItems: 1 + + interrupt-names: + const: periph_irq + + interrupt-controller: true + + '#interrupt-cells': + const: 4 + description: | + cell 1: slave ID for the requested interrupt (0-15) + cell 2: peripheral ID for requested interrupt (0-255) + cell 3: the requested peripheral interrupt (0-7) + cell 4: interrupt flags indicating level-sense information, + as defined in dt-bindings/interrupt-controller/irq.h + +required: + - compatible + - reg-names + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/arm-gic.h> + + soc { + #address-cells = <2>; + #size-cells = <2>; + + arbiter@c400000 { + compatible = "qcom,glymur-spmi-pmic-arb"; + reg = <0x0 0xc400000 0x0 0x3000>, + <0x0 0xc900000 0x0 0x400000>, + <0x0 0xc4c0000 0x0 0x400000>, + <0x0 0xc403000 0x0 0x8000>; + reg-names = "core", "chnls", "obsrvr", "chnl_map"; + + qcom,ee = <0>; + qcom,channel = <0>; + + #address-cells = <2>; + #size-cells = <2>; + ranges; + + spmi@c426000 { + reg = <0x0 0xc426000 0x0 0x4000>, + <0x0 0xc8c0000 0x0 0x10000>, + <0x0 0xc42a000 0x0 0x8000>; + reg-names = "cnfg", "intr", "chnl_owner"; + + interrupts-extended = <&pdc 1 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "periph_irq"; + interrupt-controller; + #interrupt-cells = <4>; + + #address-cells = <2>; + #size-cells = <0>; + }; + + spmi@c437000 { + reg = <0x0 0xc437000 0x0 0x4000>, + <0x0 0xc8d0000 0x0 0x10000>, + <0x0 0xc43b000 0x0 0x8000>; + reg-names = "cnfg", "intr", "chnl_owner"; + + interrupts-extended = <&pdc 3 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "periph_irq"; + interrupt-controller; + #interrupt-cells = <4>; + + #address-cells = <2>; + #size-cells = <0>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb-common.yaml b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb-common.yaml new file mode 100644 index 000000000000..8c38ed145e74 --- /dev/null +++ b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb-common.yaml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/spmi/qcom,spmi-pmic-arb-common.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies, Inc. SPMI Controller (common) + +maintainers: + - David Collins <david.collins@oss.qualcomm.com> + +description: | + This defines some common properties used to define Qualcomm SPMI controllers + for PMIC arbiter. + +properties: + qcom,ee: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 5 + description: + indicates the active Execution Environment identifier + + qcom,channel: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 5 + description: + which of the PMIC Arb provided channels to use for accesses + +required: + - qcom,ee + - qcom,channel + +additionalProperties: true diff --git a/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml index 51daf1b847a9..d0c683dd5284 100644 --- a/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml +++ b/Documentation/devicetree/bindings/spmi/qcom,spmi-pmic-arb.yaml @@ -19,6 +19,7 @@ description: | allOf: - $ref: spmi.yaml + - $ref: qcom,spmi-pmic-arb-common.yaml properties: compatible: @@ -71,20 +72,6 @@ properties: '#size-cells': true - qcom,ee: - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 0 - maximum: 5 - description: > - indicates the active Execution Environment identifier - - qcom,channel: - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 0 - maximum: 5 - description: > - which of the PMIC Arb provided channels to use for accesses - qcom,bus-id: $ref: /schemas/types.yaml#/definitions/uint32 minimum: 0 @@ -97,8 +84,6 @@ properties: required: - compatible - reg-names - - qcom,ee - - qcom,channel unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml b/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml index 7c3cc20a80d6..08369fdd2161 100644 --- a/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml +++ b/Documentation/devicetree/bindings/spmi/qcom,x1e80100-spmi-pmic-arb.yaml @@ -17,6 +17,9 @@ description: | The PMIC Arbiter can also act as an interrupt controller, providing interrupts to slave devices. +allOf: + - $ref: qcom,spmi-pmic-arb-common.yaml + properties: compatible: oneOf: @@ -45,20 +48,6 @@ properties: '#size-cells': const: 2 - qcom,ee: - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 0 - maximum: 5 - description: > - indicates the active Execution Environment identifier - - qcom,channel: - $ref: /schemas/types.yaml#/definitions/uint32 - minimum: 0 - maximum: 5 - description: > - which of the PMIC Arb provided channels to use for accesses - patternProperties: "^spmi@[a-f0-9]+$": type: object @@ -96,10 +85,8 @@ patternProperties: required: - compatible - reg-names - - qcom,ee - - qcom,channel -additionalProperties: false +unevaluatedProperties: false examples: - | diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 406fe1277b66..a482aeadcd44 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -237,6 +237,10 @@ properties: - meas,tsys01 # MEMSIC magnetometer - memsic,mmc35240 + # MEMSIC 3-axis magnetometer + - memsic,mmc5603 + # MEMSIC 3-axis magnetometer (Support I3C HDR) + - memsic,mmc5633 # MEMSIC 3-axis accelerometer - memsic,mxc4005 # MEMSIC 2-axis 8-bit digital accelerometer diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index edbce100cae4..ee7fd3cfe203 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1373,6 +1373,8 @@ patternProperties: description: Revolution Robotics, Inc. (Revotics) "^rex,.*": description: iMX6 Rex Project + "^rfdigital,.*": + description: RF Digital Corporation "^richtek,.*": description: Richtek Technology Corporation "^ricoh,.*": diff --git a/Documentation/iio/ad4062.rst b/Documentation/iio/ad4062.rst new file mode 100644 index 000000000000..d77287836430 --- /dev/null +++ b/Documentation/iio/ad4062.rst @@ -0,0 +1,148 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +============= +AD4062 driver +============= + +ADC driver for Analog Devices Inc. AD4060/AD4062 devices. The module name is +``ad4062``. + +Supported devices +================= + +The following chips are supported by this driver: + +* `AD4060 <https://www.analog.com/AD4060>`_ +* `AD4062 <https://www.analog.com/AD4062>`_ + +Wiring modes +============ + +The ADC is interfaced through an I3C bus, and contains two programmable GPIOs. + +The ADC convert-start happens on the SDA rising edge of the I3C stop (P) bit +at the end of the read command. + +The two programmable GPIOS are optional and have a role assigned if present in +the devicetree ``interrupt-names`` property: + +- GP0: Is assigned the role of Threshold Either signal. +- GP1: Is assigned the role of Data Ready signal. + +If the property ``gpio-controller`` is present in the devicetree, then the GPO +not present in the ``interrupt-names`` is exposed as a GPO. + +Device attributes +================= + +The ADC contains only one channel with following attributes: + +.. list-table:: Channel attributes + :header-rows: 1 + + * - Attribute + - Description + * - ``in_voltage_calibscale`` + - Sets the gain scaling factor that the hardware applies to the sample, + to compensate for system gain error. + * - ``in_voltage_oversampling_ratio`` + - Sets device's burst averaging mode to over sample using the + internal sample rate. Value 1 disable the burst averaging mode. + * - ``in_voltage_oversampling_ratio_available`` + - List of available oversampling values. + * - ``in_voltage_raw`` + - Returns the raw ADC voltage value. + * - ``in_voltage_scale`` + - Returns the channel scale in reference to the reference voltage + ``ref-supply`` or ``vdd-supply`` if the former not present. + +Also contain the following device attributes: + +.. list-table:: Device attributes + :header-rows: 1 + + * - Attribute + - Description + * - ``sampling_frequency`` + - Sets the duration of a single scan, used in the burst averaging mode. + The duration is described by ``(n_avg - 1) / fosc + tconv``, where + ``n_avg`` is the oversampling ratio, ``fosc`` is the internal sample + rate and ``tconv`` is the ADC conversion time. + * - ``sampling_frequency_available`` + - Lists the available sampling frequencies, computed on the current + oversampling ratio. If the ratio is 1, the frequency is ``1/tconv``. + +Interrupts +========== + +The interrupts are mapped through the ``interrupt-names`` and ``interrupts`` +properties. + +The ``interrupt-names`` ``gp0`` entry sets the role of Threshold signal, and +entry ``gp1`` the role of Data Ready signal. + +If each is not present, the driver fallback to enabling the same role as an +I3C IBI. + +Low-power mode +============== + +The device enters low-power mode on idle to save power. Enabling an event puts +the device out of the low-power since the ADC autonomously samples to assert +the event condition. + +IIO trigger support +=================== + +An IIO trigger ``ad4062-devX`` is registered by the driver to be used by the +same device, to capture samples to a software buffer. It is required to attach +the trigger to the device by setting the ``current_trigger`` before enabling +and reading the buffer. + +The acquisition is sequential and bounded by the protocol timings, software +latency and internal timings, the sample rate is not configurable. The burst +averaging mode does impact the effective sample rate, since it increases the +internal timing to output a single sample. + +Threshold events +================ + +The ADC supports a monitoring mode to raise threshold events. The driver +supports a single interrupt for both rising and falling readings. + +The feature is enabled/disabled by setting ``thresh_either_en``. During monitor +mode, the device continuously operates in autonomous mode. Any register access +puts the device back in configuration mode, due to this, any access disables +monitor mode. + +The following event attributes are available: + +.. list-table:: Event attributes + :header-rows: 1 + + * - Attribute + - Description + * - ``sampling_frequency`` + - Frequency used in the monitoring mode, sets the device internal sample + rate when the mode is activated. + * - ``sampling_frequency_available`` + - List of available sample rates. + * - ``thresh_either_en`` + - Enable monitoring mode. + * - ``thresh_falling_hysteresis`` + - Set the hysteresis value for the minimum threshold. + * - ``thresh_falling_value`` + - Set the minimum threshold value. + * - ``thresh_rising_hysteresis`` + - Set the hysteresis value for the maximum threshold. + * - ``thresh_rising_value`` + - Set the maximum threshold value. + +GPO controller support +====================== + +The device supports using GP0 and GP1 as GPOs. If the devicetree contains the +node ``gpio-controller```, the device is marked as a GPIO controller and the +GPs not listed in ``interrupt-names`` are exposed as a GPO. The GPIO index +matches the pin name, so if GP0 is not exposed but GP1 is, index 0 is masked +out and only index 1 can be set. diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index 315ae37d6fd4..ba3e609c6a13 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -22,6 +22,7 @@ Industrial I/O Kernel Drivers ad3552r ad4000 ad4030 + ad4062 ad4695 ad7191 ad7380 diff --git a/Documentation/trace/coresight/coresight.rst b/Documentation/trace/coresight/coresight.rst index 806699871b80..d461de4e067e 100644 --- a/Documentation/trace/coresight/coresight.rst +++ b/Documentation/trace/coresight/coresight.rst @@ -613,8 +613,20 @@ They are also listed in the folder /sys/bus/event_source/devices/cs_etm/format/ - Session local version of the system wide setting: :ref:`ETM_MODE_RETURNSTACK <coresight-return-stack>` * - timestamp - - Session local version of the system wide setting: :ref:`ETMv4_MODE_TIMESTAMP - <coresight-timestamp>` + - Controls generation and interval of timestamps. + + 0 = off, 1 = minimum interval .. 15 = maximum interval. + + Values 1 - 14 use a counter that decrements every cycle to generate a + timestamp on underflow. The reload value for the counter is 2 ^ (interval + - 1). If the value is 1 then the reload value is 1, if the value is 11 + then the reload value is 1024 etc. + + Setting the maximum interval (15) will disable the counter generated + timestamps, freeing the counter resource, leaving only ones emitted when + a SYNC packet is generated. The sync interval is controlled with + TRCSYNCPR.PERIOD which is every 4096 bytes of trace by default. + * - cc_threshold - Cycle count threshold value. If nothing is provided here or the provided value is 0, then the default value i.e 0x100 will be used. If provided value is less than minimum cycles threshold diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index b5c6447455fd..331223761fff 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -397,7 +397,6 @@ Code Seq# Include File Comments 0xCD 01 linux/reiserfs_fs.h Dead since 6.13 0xCE 01-02 uapi/linux/cxl_mem.h Compute Express Link Memory Devices 0xCF 02 fs/smb/client/cifs_ioctl.h -0xDB 00-0F drivers/char/mwave/mwavepub.h 0xDD 00-3F ZFCP device driver see drivers/s390/scsi/ <mailto:aherrman@de.ibm.com> 0xE5 00-3F linux/fuse.h diff --git a/MAINTAINERS b/MAINTAINERS index eaf55e463bb4..49535541ab4c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1444,6 +1444,14 @@ F: Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml F: Documentation/iio/ad4030.rst F: drivers/iio/adc/ad4030.c +ANALOG DEVICES INC AD4062 DRIVER +M: Jorge Marques <jorge.marques@analog.com> +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ad4062.yaml +F: Documentation/iio/ad4062.rst +F: drivers/iio/adc/ad4062.c + ANALOG DEVICES INC AD4080 DRIVER M: Antoniu Miclaus <antoniu.miclaus@analog.com> L: linux-iio@vger.kernel.org @@ -1461,6 +1469,14 @@ F: Documentation/ABI/testing/sysfs-bus-iio-adc-ad4130 F: Documentation/devicetree/bindings/iio/adc/adi,ad4130.yaml F: drivers/iio/adc/ad4130.c +ANALOG DEVICES INC AD4134 DRIVER +M: Marcelo Schmitt <marcelo.schmitt@analog.com> +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ad4134.yaml +F: drivers/iio/adc/ad4134.c + ANALOG DEVICES INC AD4170-4 DRIVER M: Marcelo Schmitt <marcelo.schmitt@analog.com> L: linux-iio@vger.kernel.org @@ -1605,6 +1621,14 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml F: drivers/iio/dac/ad9739a.c +ANALOG DEVICES INC MAX22007 DRIVER +M: Janani Sunil <janani.sunil@analog.com> +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/dac/adi,max22007.yaml +F: drivers/iio/dac/max22007.c + ANALOG DEVICES INC ADA4250 DRIVER M: Antoniu Miclaus <antoniu.miclaus@analog.com> L: linux-iio@vger.kernel.org @@ -1613,6 +1637,14 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/amplifiers/adi,ada4250.yaml F: drivers/iio/amplifiers/ada4250.c +ANALOG DEVICES INC ADE9000 DRIVER +M: Antoniu Miclaus <antoniu.miclaus@analog.com> +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml +F: drivers/iio/adc/ade9000.c + ANALOG DEVICES INC ADF4377 DRIVER M: Antoniu Miclaus <antoniu.miclaus@analog.com> L: linux-iio@vger.kernel.org @@ -11620,6 +11652,13 @@ F: lib/test_hmm* F: mm/hmm* F: tools/testing/selftests/mm/*hmm* +HONEYWELL ABP2030PA PRESSURE SENSOR SERIES IIO DRIVER +M: Petre Rodan <petre.rodan@subdimension.ro> +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/pressure/honeywell,abp2030pa.yaml +F: drivers/iio/pressure/abp2030pa* + HONEYWELL HSC030PA PRESSURE SENSOR SERIES IIO DRIVER M: Petre Rodan <petre.rodan@subdimension.ro> L: linux-iio@vger.kernel.org @@ -13090,8 +13129,9 @@ S: Orphan F: drivers/ptp/ptp_dfl_tod.c INTEL QUADRATURE ENCODER PERIPHERAL DRIVER -M: Jarkko Nikula <jarkko.nikula@linux.intel.com> +M: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> L: linux-iio@vger.kernel.org +S: Supported F: drivers/counter/intel-qep.c INTEL SCU DRIVERS @@ -15785,6 +15825,13 @@ F: Documentation/ABI/testing/sysfs-bus-iio-potentiometer-mcp4531 F: drivers/iio/potentiometer/mcp4018.c F: drivers/iio/potentiometer/mcp4531.c +MCP47FEB02 MICROCHIP DAC DRIVER +M: Ariana Lazar <ariana.lazar@microchip.com> +L: linux-iio@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/iio/dac/microchip,mcp47feb02.yaml +F: drivers/iio/dac/mcp47feb02.c + MCP4821 DAC DRIVER M: Anshul Dalal <anshulusr@gmail.com> L: linux-iio@vger.kernel.org @@ -26287,6 +26334,13 @@ S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml F: drivers/iio/adc/ti-ads1119.c +TI ADS1018 ADC DRIVER +M: Kurt Borja <kuurtb@gmail.com> +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml +F: drivers/iio/adc/ti-ads1018.c + TI ADS7924 ADC DRIVER M: Hugo Villeneuve <hvilleneuve@dimonoff.com> L: linux-iio@vger.kernel.org diff --git a/drivers/accel/qaic/mhi_controller.c b/drivers/accel/qaic/mhi_controller.c index 13a14c6c6168..4d787f77ce41 100644 --- a/drivers/accel/qaic/mhi_controller.c +++ b/drivers/accel/qaic/mhi_controller.c @@ -39,7 +39,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -55,7 +54,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -71,7 +69,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -87,7 +84,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -103,7 +99,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -119,7 +114,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -135,7 +129,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -151,7 +144,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -167,7 +159,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -183,7 +174,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -199,7 +189,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -215,7 +204,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -231,7 +219,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -247,7 +234,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -263,7 +249,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -279,7 +264,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -295,7 +279,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -311,7 +294,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -327,7 +309,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -343,7 +324,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -359,7 +339,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -375,7 +354,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -391,7 +369,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -407,7 +384,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -423,7 +399,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -439,7 +414,6 @@ static const struct mhi_channel_config aic100_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, .wake_capable = false, }, }; @@ -458,7 +432,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -474,7 +447,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -490,7 +462,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -506,7 +477,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -522,7 +492,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -538,7 +507,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -554,7 +522,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -570,7 +537,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -586,7 +552,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -602,7 +567,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -618,7 +582,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -634,7 +597,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -650,7 +612,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -666,7 +627,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -682,7 +642,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -698,7 +657,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -714,7 +672,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, .wake_capable = false, }, { @@ -730,7 +687,6 @@ static const struct mhi_channel_config aic200_channels[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, .wake_capable = false, }, }; diff --git a/drivers/android/binder.c b/drivers/android/binder.c index bd780d88b468..f43edb11bef4 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -4523,7 +4523,7 @@ static int binder_thread_write(struct binder_proc *proc, } } binder_debug(BINDER_DEBUG_DEAD_BINDER, - "%d:%d BC_DEAD_BINDER_DONE %016llx found %pK\n", + "%d:%d BC_DEAD_BINDER_DONE %016llx found %p\n", proc->pid, thread->pid, (u64)cookie, death); if (death == NULL) { diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs index 3d135ec03ca7..9cf437c025a2 100644 --- a/drivers/android/binder/context.rs +++ b/drivers/android/binder/context.rs @@ -3,8 +3,8 @@ // Copyright (C) 2025 Google LLC. use kernel::{ - error::Error, - list::{List, ListArc, ListLinks}, + alloc::kvec::KVVec, + error::code::*, prelude::*, security, str::{CStr, CString}, @@ -17,22 +17,19 @@ 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(), + contexts: KVVec::new(), }; } pub(crate) struct ContextList { - list: List<Context>, + contexts: KVVec<Arc<Context>>, } -pub(crate) fn get_all_contexts() -> Result<KVec<Arc<Context>>> { +pub(crate) fn get_all_contexts() -> Result<KVVec<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)?; + let mut ctxs = KVVec::with_capacity(lock.contexts.len(), GFP_KERNEL)?; + for ctx in lock.contexts.iter() { + ctxs.push(ctx.clone(), GFP_KERNEL)?; } Ok(ctxs) } @@ -42,7 +39,7 @@ pub(crate) fn get_all_contexts() -> Result<KVec<Arc<Context>>> { struct Manager { node: Option<NodeRef>, uid: Option<Kuid>, - all_procs: List<Process>, + all_procs: KVVec<Arc<Process>>, } /// There is one context per binder file (/dev/binder, /dev/hwbinder, etc) @@ -51,28 +48,16 @@ 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>( + let ctx = Arc::pin_init( try_pin_init!(Context { name, - links <- ListLinks::new(), manager <- kernel::new_mutex!(Manager { - all_procs: List::new(), + all_procs: KVVec::new(), node: None, uid: None, }, "Context::manager"), @@ -80,8 +65,7 @@ impl Context { GFP_KERNEL, )?; - let ctx = list_ctx.clone_arc(); - CONTEXTS.lock().list.push_back(list_ctx); + CONTEXTS.lock().contexts.push(ctx.clone(), GFP_KERNEL)?; Ok(ctx) } @@ -89,27 +73,27 @@ impl Context { /// 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 deregister(self: &Arc<Self>) { + // Safe removal using retain + CONTEXTS.lock().contexts.retain(|c| !Arc::ptr_eq(c, self)); } - pub(crate) fn register_process(self: &Arc<Self>, proc: ListArc<Process>) { + pub(crate) fn register_process(self: &Arc<Self>, proc: Arc<Process>) -> Result { if !Arc::ptr_eq(self, &proc.ctx) { pr_err!("Context::register_process called on the wrong context."); - return; + return Err(EINVAL); } - self.manager.lock().all_procs.push_back(proc); + self.manager.lock().all_procs.push(proc, GFP_KERNEL)?; + Ok(()) } - pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Process) { + pub(crate) fn deregister_process(self: &Arc<Self>, proc: &Arc<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) }; + let mut manager = self.manager.lock(); + manager.all_procs.retain(|p| !Arc::ptr_eq(p, proc)); } pub(crate) fn set_manager_node(&self, node_ref: NodeRef) -> Result { @@ -154,27 +138,27 @@ impl Context { { let lock = self.manager.lock(); for proc in &lock.all_procs { - func(&proc); + func(proc); } } - pub(crate) fn get_all_procs(&self) -> Result<KVec<Arc<Process>>> { + pub(crate) fn get_all_procs(&self) -> Result<KVVec<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)?; + let mut procs = KVVec::with_capacity(lock.all_procs.len(), GFP_KERNEL)?; + for proc in lock.all_procs.iter() { + procs.push(Arc::clone(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)?; + pub(crate) fn get_procs_with_pid(&self, pid: i32) -> Result<KVVec<Arc<Process>>> { + let lock = self.manager.lock(); + let mut matching_procs = KVVec::new(); + for proc in lock.all_procs.iter() { + if proc.task.pid() == pid { + matching_procs.push(Arc::clone(proc), GFP_KERNEL)?; + } } - Ok(backing) + Ok(matching_procs) } } diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index c26d113ede96..69f757ff7461 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -178,6 +178,14 @@ struct NodeInner { refs: List<NodeRefInfo, { NodeRefInfo::LIST_NODE }>, } +use kernel::bindings::rb_node_layout; +use mem::offset_of; +pub(crate) const NODE_LAYOUT: rb_node_layout = rb_node_layout { + arc_offset: Arc::<Node>::DATA_OFFSET + offset_of!(DTRWrap<Node>, wrapped), + debug_id: offset_of!(Node, debug_id), + ptr: offset_of!(Node, ptr), +}; + #[pin_data] pub(crate) struct Node { pub(crate) debug_id: usize, diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 132055b4790f..41de5593197c 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -28,11 +28,11 @@ use kernel::{ seq_print, sync::poll::PollTable, sync::{ + aref::ARef, lock::{spinlock::SpinLockBackend, Guard}, Arc, ArcBorrow, CondVar, CondVarTimeoutResult, Mutex, SpinLock, UniqueArc, }, task::Task, - types::ARef, uaccess::{UserSlice, UserSliceReader}, uapi, workqueue::{self, Work}, @@ -418,6 +418,13 @@ impl ProcessNodeRefs { } } +use core::mem::offset_of; +use kernel::bindings::rb_process_layout; +pub(crate) const PROCESS_LAYOUT: rb_process_layout = rb_process_layout { + arc_offset: Arc::<Process>::DATA_OFFSET, + task: offset_of!(Process, task), +}; + /// A process using binder. /// /// Strictly speaking, there can be multiple of these per process. There is one for each binder fd @@ -496,7 +503,7 @@ impl workqueue::WorkItem for Process { impl Process { fn new(ctx: Arc<Context>, cred: ARef<Credential>) -> Result<Arc<Self>> { let current = kernel::current!(); - let list_process = ListArc::pin_init::<Error>( + let process = Arc::pin_init::<Error>( try_pin_init!(Process { ctx, cred, @@ -512,8 +519,7 @@ impl Process { GFP_KERNEL, )?; - let process = list_process.clone_arc(); - process.ctx.register_process(list_process); + process.ctx.register_process(process.clone())?; Ok(process) } diff --git a/drivers/android/binder/rust_binder.h b/drivers/android/binder/rust_binder.h index 31806890ed1a..d2284726c025 100644 --- a/drivers/android/binder/rust_binder.h +++ b/drivers/android/binder/rust_binder.h @@ -20,4 +20,83 @@ struct inode; struct dentry *rust_binderfs_create_proc_file(struct inode *nodp, int pid); void rust_binderfs_remove_file(struct dentry *dentry); +/* + * 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_transaction; +typedef void *rust_binder_process; +typedef void *rust_binder_node; + +struct rb_process_layout { + size_t arc_offset; + size_t task; +}; + +struct rb_transaction_layout { + size_t debug_id; + size_t code; + size_t flags; + size_t from_thread; + size_t to_proc; + size_t target_node; +}; + +struct rb_node_layout { + size_t arc_offset; + size_t debug_id; + size_t ptr; +}; + +struct rust_binder_layout { + struct rb_transaction_layout t; + struct rb_process_layout p; + struct rb_node_layout n; +}; + +extern const struct rust_binder_layout RUST_BINDER_LAYOUT; + +static inline size_t rust_binder_transaction_debug_id(rust_binder_transaction t) +{ + return *(size_t *) (t + RUST_BINDER_LAYOUT.t.debug_id); +} + +static inline u32 rust_binder_transaction_code(rust_binder_transaction t) +{ + return *(u32 *) (t + RUST_BINDER_LAYOUT.t.code); +} + +static inline u32 rust_binder_transaction_flags(rust_binder_transaction t) +{ + return *(u32 *) (t + RUST_BINDER_LAYOUT.t.flags); +} + +// Nullable! +static inline rust_binder_node rust_binder_transaction_target_node(rust_binder_transaction t) +{ + void *p = *(void **) (t + RUST_BINDER_LAYOUT.t.target_node); + + if (p) + p = p + RUST_BINDER_LAYOUT.n.arc_offset; + return p; +} + +static inline rust_binder_process rust_binder_transaction_to_proc(rust_binder_transaction t) +{ + void *p = *(void **) (t + RUST_BINDER_LAYOUT.t.to_proc); + + return p + RUST_BINDER_LAYOUT.p.arc_offset; +} + +static inline struct task_struct *rust_binder_process_task(rust_binder_process t) +{ + return *(struct task_struct **) (t + RUST_BINDER_LAYOUT.p.task); +} + +static inline size_t rust_binder_node_debug_id(rust_binder_node t) +{ + return *(size_t *) (t + RUST_BINDER_LAYOUT.n.debug_id); +} + #endif diff --git a/drivers/android/binder/rust_binder_events.h b/drivers/android/binder/rust_binder_events.h index 2f3efbf9dba6..8ad785c6bd0f 100644 --- a/drivers/android/binder/rust_binder_events.h +++ b/drivers/android/binder/rust_binder_events.h @@ -30,6 +30,36 @@ TRACE_EVENT(rust_binder_ioctl, TP_printk("cmd=0x%x arg=0x%lx", __entry->cmd, __entry->arg) ); +TRACE_EVENT(rust_binder_transaction, + TP_PROTO(bool reply, rust_binder_transaction t, struct task_struct *thread), + TP_ARGS(reply, t, thread), + TP_STRUCT__entry( + __field(int, debug_id) + __field(int, target_node) + __field(int, to_proc) + __field(int, to_thread) + __field(int, reply) + __field(unsigned int, code) + __field(unsigned int, flags) + ), + TP_fast_assign( + rust_binder_process to = rust_binder_transaction_to_proc(t); + rust_binder_node target_node = rust_binder_transaction_target_node(t); + + __entry->debug_id = rust_binder_transaction_debug_id(t); + __entry->target_node = target_node ? rust_binder_node_debug_id(target_node) : 0; + __entry->to_proc = rust_binder_process_task(to)->pid; + __entry->to_thread = thread ? thread->pid : 0; + __entry->reply = reply; + __entry->code = rust_binder_transaction_code(t); + __entry->flags = rust_binder_transaction_flags(t); + ), + TP_printk("transaction=%d dest_node=%d dest_proc=%d dest_thread=%d reply=%d flags=0x%x code=0x%x", + __entry->debug_id, __entry->target_node, + __entry->to_proc, __entry->to_thread, + __entry->reply, __entry->flags, __entry->code) +); + #endif /* _RUST_BINDER_TRACE_H */ /* This part must be outside protection */ diff --git a/drivers/android/binder/rust_binder_main.rs b/drivers/android/binder/rust_binder_main.rs index 47bfb114cabb..aa5f2a75adb4 100644 --- a/drivers/android/binder/rust_binder_main.rs +++ b/drivers/android/binder/rust_binder_main.rs @@ -87,6 +87,14 @@ module! { license: "GPL", } +use kernel::bindings::rust_binder_layout; +#[no_mangle] +static RUST_BINDER_LAYOUT: rust_binder_layout = rust_binder_layout { + t: transaction::TRANSACTION_LAYOUT, + p: process::PROCESS_LAYOUT, + n: node::NODE_LAYOUT, +}; + fn next_debug_id() -> usize { static NEXT_DEBUG_ID: Atomic<usize> = Atomic::new(0); @@ -286,7 +294,7 @@ impl kernel::Module for BinderModule { pr_warn!("Loaded Rust Binder."); - BINDER_SHRINKER.register(kernel::c_str!("android-binder"))?; + BINDER_SHRINKER.register(c"android-binder")?; // SAFETY: The module is being loaded, so we can initialize binderfs. unsafe { kernel::error::to_result(binderfs::init_rust_binderfs())? }; @@ -312,7 +320,7 @@ pub static rust_binder_fops: AssertSync<kernel::bindings::file_operations> = { owner: THIS_MODULE.as_ptr(), poll: Some(rust_binder_poll), unlocked_ioctl: Some(rust_binder_ioctl), - compat_ioctl: Some(bindings::compat_ptr_ioctl), + compat_ioctl: bindings::compat_ptr_ioctl, mmap: Some(rust_binder_mmap), open: Some(rust_binder_open), release: Some(rust_binder_release), diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 1f1709a6a77a..0b62d24b2118 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -17,9 +17,8 @@ use kernel::{ seq_print, sync::atomic::{ordering::Relaxed, Atomic}, sync::poll::{PollCondVar, PollTable}, - sync::{Arc, SpinLock}, + sync::{aref::ARef, Arc, SpinLock}, task::Task, - types::ARef, uaccess::UserSlice, uapi, }; @@ -1146,6 +1145,7 @@ impl Thread { transaction: &DArc<Transaction>, ) -> bool { if let Ok(transaction) = &reply { + crate::trace::trace_transaction(true, transaction, Some(&self.task)); transaction.set_outstanding(&mut self.process.inner.lock()); } diff --git a/drivers/android/binder/trace.rs b/drivers/android/binder/trace.rs index af0e4392805e..9839901c7151 100644 --- a/drivers/android/binder/trace.rs +++ b/drivers/android/binder/trace.rs @@ -2,11 +2,21 @@ // Copyright (C) 2025 Google LLC. +use crate::transaction::Transaction; + +use kernel::bindings::{rust_binder_transaction, task_struct}; use kernel::ffi::{c_uint, c_ulong}; +use kernel::task::Task; use kernel::tracepoint::declare_trace; declare_trace! { unsafe fn rust_binder_ioctl(cmd: c_uint, arg: c_ulong); + unsafe fn rust_binder_transaction(reply: bool, t: rust_binder_transaction, thread: *mut task_struct); +} + +#[inline] +fn raw_transaction(t: &Transaction) -> rust_binder_transaction { + t as *const Transaction as rust_binder_transaction } #[inline] @@ -14,3 +24,14 @@ pub(crate) fn trace_ioctl(cmd: u32, arg: usize) { // SAFETY: Always safe to call. unsafe { rust_binder_ioctl(cmd, arg as c_ulong) } } + +#[inline] +pub(crate) fn trace_transaction(reply: bool, t: &Transaction, thread: Option<&Task>) { + let thread = match thread { + Some(thread) => thread.as_ptr(), + None => core::ptr::null_mut(), + }; + // SAFETY: The raw transaction is valid for the duration of this call. The thread pointer is + // valid or null. + unsafe { rust_binder_transaction(reply, raw_transaction(t), thread) } +} diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs index 2273a8e9d01c..75e6f5fbaaae 100644 --- a/drivers/android/binder/transaction.rs +++ b/drivers/android/binder/transaction.rs @@ -24,6 +24,17 @@ use crate::{ BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead, }; +use core::mem::offset_of; +use kernel::bindings::rb_transaction_layout; +pub(crate) const TRANSACTION_LAYOUT: rb_transaction_layout = rb_transaction_layout { + debug_id: offset_of!(Transaction, debug_id), + code: offset_of!(Transaction, code), + flags: offset_of!(Transaction, flags), + from_thread: offset_of!(Transaction, from), + to_proc: offset_of!(Transaction, to), + target_node: offset_of!(Transaction, target_node), +}; + #[pin_data(PinnedDrop)] pub(crate) struct Transaction { pub(crate) debug_id: usize, @@ -249,6 +260,7 @@ impl Transaction { if oneway { if let Some(target_node) = self.target_node.clone() { + crate::trace::trace_transaction(false, &self, None); if process_inner.is_frozen.is_frozen() { process_inner.async_recv = true; if self.flags & TF_UPDATE_TXN != 0 { @@ -286,11 +298,13 @@ impl Transaction { } let res = if let Some(thread) = self.find_target_thread() { + crate::trace::trace_transaction(false, &self, Some(&thread.task)); match thread.push_work(self) { PushWorkRes::Ok => Ok(()), PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)), } } else { + crate::trace::trace_transaction(false, &self, None); process_inner.push_work(self) }; drop(process_inner); diff --git a/drivers/android/binder_alloc.c b/drivers/android/binder_alloc.c index 145ed5f14cdb..67a068c075c0 100644 --- a/drivers/android/binder_alloc.c +++ b/drivers/android/binder_alloc.c @@ -81,7 +81,7 @@ static void binder_insert_free_buffer(struct binder_alloc *alloc, new_buffer_size = binder_alloc_buffer_size(alloc, new_buffer); binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: add free buffer, size %zd, at %pK\n", + "%d: add free buffer, size %zd, at %p\n", alloc->pid, new_buffer_size, new_buffer); while (*p) { @@ -572,7 +572,7 @@ static struct binder_buffer *binder_alloc_new_buf_locked( } binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: binder_alloc_buf size %zd got buffer %pK size %zd\n", + "%d: binder_alloc_buf size %zd got buffer %p size %zd\n", alloc->pid, size, buffer, buffer_size); /* @@ -748,7 +748,7 @@ static void binder_free_buf_locked(struct binder_alloc *alloc, ALIGN(buffer->extra_buffers_size, sizeof(void *)); binder_alloc_debug(BINDER_DEBUG_BUFFER_ALLOC, - "%d: binder_free_buf %pK size %zd buffer_size %zd\n", + "%d: binder_free_buf %p size %zd buffer_size %zd\n", alloc->pid, buffer, size, buffer_size); BUG_ON(buffer->free); diff --git a/drivers/bus/fsl-mc/fsl-mc-bus.c b/drivers/bus/fsl-mc/fsl-mc-bus.c index 007223549887..290ce13bcb09 100644 --- a/drivers/bus/fsl-mc/fsl-mc-bus.c +++ b/drivers/bus/fsl-mc/fsl-mc-bus.c @@ -432,10 +432,9 @@ const struct device_type fsl_mc_bus_dpdmai_type = { }; EXPORT_SYMBOL_GPL(fsl_mc_bus_dpdmai_type); -const struct device_type fsl_mc_bus_dpdbg_type = { +static const struct device_type fsl_mc_bus_dpdbg_type = { .name = "fsl_mc_bus_dpdbg" }; -EXPORT_SYMBOL_GPL(fsl_mc_bus_dpdbg_type); static const struct device_type *fsl_mc_get_device_type(const char *type) { diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c index 3c208b5c8446..35922e7a24c1 100644 --- a/drivers/bus/mhi/ep/main.c +++ b/drivers/bus/mhi/ep/main.c @@ -1596,7 +1596,7 @@ void mhi_ep_unregister_controller(struct mhi_ep_cntrl *mhi_cntrl) } EXPORT_SYMBOL_GPL(mhi_ep_unregister_controller); -static int mhi_ep_driver_probe(struct device *dev) +static int mhi_ep_probe(struct device *dev) { struct mhi_ep_device *mhi_dev = to_mhi_ep_device(dev); struct mhi_ep_driver *mhi_drv = to_mhi_ep_driver(dev->driver); @@ -1609,7 +1609,7 @@ static int mhi_ep_driver_probe(struct device *dev) return mhi_drv->probe(mhi_dev, mhi_dev->id); } -static int mhi_ep_driver_remove(struct device *dev) +static void mhi_ep_remove(struct device *dev) { struct mhi_ep_device *mhi_dev = to_mhi_ep_device(dev); struct mhi_ep_driver *mhi_drv = to_mhi_ep_driver(dev->driver); @@ -1619,7 +1619,7 @@ static int mhi_ep_driver_remove(struct device *dev) /* Skip if it is a controller device */ if (mhi_dev->dev_type == MHI_DEVICE_CONTROLLER) - return 0; + return; /* Disconnect the channels associated with the driver */ for (dir = 0; dir < 2; dir++) { @@ -1643,8 +1643,6 @@ static int mhi_ep_driver_remove(struct device *dev) /* Remove the client driver now */ mhi_drv->remove(mhi_dev); - - return 0; } int __mhi_ep_driver_register(struct mhi_ep_driver *mhi_drv, struct module *owner) @@ -1660,8 +1658,6 @@ int __mhi_ep_driver_register(struct mhi_ep_driver *mhi_drv, struct module *owner driver->bus = &mhi_ep_bus_type; driver->owner = owner; - driver->probe = mhi_ep_driver_probe; - driver->remove = mhi_ep_driver_remove; return driver_register(driver); } @@ -1708,6 +1704,8 @@ const struct bus_type mhi_ep_bus_type = { .dev_name = "mhi_ep", .match = mhi_ep_match, .uevent = mhi_ep_uevent, + .probe = mhi_ep_probe, + .remove = mhi_ep_remove, }; static int __init mhi_ep_init(void) diff --git a/drivers/bus/mhi/host/boot.c b/drivers/bus/mhi/host/boot.c index 205d83ac069f..c76db35bc29a 100644 --- a/drivers/bus/mhi/host/boot.c +++ b/drivers/bus/mhi/host/boot.c @@ -584,6 +584,16 @@ skip_req_fw: * device transitioning into MHI READY state */ if (fw_load_type == MHI_FW_LOAD_FBC) { + /* + * Some FW combine two separate ELF images (SBL + WLAN FW) in a single + * file. Hence, check for the existence of the second ELF header after + * SBL. If present, load the second image separately. + */ + if (!memcmp(fw_data + mhi_cntrl->sbl_size, ELFMAG, SELFMAG)) { + fw_data += mhi_cntrl->sbl_size; + fw_sz -= mhi_cntrl->sbl_size; + } + ret = mhi_alloc_bhie_table(mhi_cntrl, &mhi_cntrl->fbc_image, fw_sz); if (ret) { release_firmware(firmware); diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c index 099be8dd1900..c47f66833cda 100644 --- a/drivers/bus/mhi/host/init.c +++ b/drivers/bus/mhi/host/init.c @@ -841,19 +841,9 @@ static int parse_ch_cfg(struct mhi_controller *mhi_cntrl, mhi_chan->lpm_notify = ch_cfg->lpm_notify; mhi_chan->offload_ch = ch_cfg->offload_channel; mhi_chan->db_cfg.reset_req = ch_cfg->doorbell_mode_switch; - mhi_chan->pre_alloc = ch_cfg->auto_queue; mhi_chan->wake_capable = ch_cfg->wake_capable; /* - * If MHI host allocates buffers, then the channel direction - * should be DMA_FROM_DEVICE - */ - if (mhi_chan->pre_alloc && mhi_chan->dir != DMA_FROM_DEVICE) { - dev_err(dev, "Invalid channel configuration\n"); - goto error_chan_cfg; - } - - /* * Bi-directional and direction less channel must be an * offload channel */ @@ -1265,7 +1255,7 @@ struct mhi_device *mhi_alloc_device(struct mhi_controller *mhi_cntrl) return mhi_dev; } -static int mhi_driver_probe(struct device *dev) +static int mhi_probe(struct device *dev) { struct mhi_device *mhi_dev = to_mhi_device(dev); struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; @@ -1341,7 +1331,7 @@ exit_probe: return ret; } -static int mhi_driver_remove(struct device *dev) +static void mhi_remove(struct device *dev) { struct mhi_device *mhi_dev = to_mhi_device(dev); struct mhi_driver *mhi_drv = to_mhi_driver(dev->driver); @@ -1355,7 +1345,7 @@ static int mhi_driver_remove(struct device *dev) /* Skip if it is a controller device */ if (mhi_dev->dev_type == MHI_DEVICE_CONTROLLER) - return 0; + return; /* Reset both channels */ for (dir = 0; dir < 2; dir++) { @@ -1407,8 +1397,6 @@ static int mhi_driver_remove(struct device *dev) while (mhi_dev->dev_wake) mhi_device_put(mhi_dev); - - return 0; } int __mhi_driver_register(struct mhi_driver *mhi_drv, struct module *owner) @@ -1420,8 +1408,6 @@ int __mhi_driver_register(struct mhi_driver *mhi_drv, struct module *owner) driver->bus = &mhi_bus_type; driver->owner = owner; - driver->probe = mhi_driver_probe; - driver->remove = mhi_driver_remove; return driver_register(driver); } @@ -1468,6 +1454,8 @@ const struct bus_type mhi_bus_type = { .dev_name = "mhi", .match = mhi_match, .uevent = mhi_uevent, + .probe = mhi_probe, + .remove = mhi_remove, .dev_groups = mhi_dev_groups, }; diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h index 7937bb1f742c..7b0ee5e3a12d 100644 --- a/drivers/bus/mhi/host/internal.h +++ b/drivers/bus/mhi/host/internal.h @@ -286,7 +286,6 @@ struct mhi_chan { bool lpm_notify; bool configured; bool offload_ch; - bool pre_alloc; bool wake_capable; }; @@ -389,8 +388,6 @@ int mhi_rddm_prepare(struct mhi_controller *mhi_cntrl, struct image_info *img_info); void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl); -/* Automatically allocate and queue inbound buffers */ -#define MHI_CH_INBOUND_ALLOC_BUFS BIT(0) int mhi_init_chan_ctxt(struct mhi_controller *mhi_cntrl, struct mhi_chan *mhi_chan); void mhi_deinit_chan_ctxt(struct mhi_controller *mhi_cntrl, diff --git a/drivers/bus/mhi/host/main.c b/drivers/bus/mhi/host/main.c index 861551274319..53c0ffe30070 100644 --- a/drivers/bus/mhi/host/main.c +++ b/drivers/bus/mhi/host/main.c @@ -664,23 +664,6 @@ static int parse_xfer_event(struct mhi_controller *mhi_cntrl, mhi_cntrl->runtime_put(mhi_cntrl); } - /* - * Recycle the buffer if buffer is pre-allocated, - * if there is an error, not much we can do apart - * from dropping the packet - */ - if (mhi_chan->pre_alloc) { - if (mhi_queue_buf(mhi_chan->mhi_dev, - mhi_chan->dir, - buf_info->cb_buf, - buf_info->len, MHI_EOT)) { - dev_err(dev, - "Error recycling buffer for chan:%d\n", - mhi_chan->chan); - kfree(buf_info->cb_buf); - } - } - read_lock_bh(&mhi_chan->lock); } break; @@ -1177,17 +1160,12 @@ static int mhi_queue(struct mhi_device *mhi_dev, struct mhi_buf_info *buf_info, int mhi_queue_skb(struct mhi_device *mhi_dev, enum dma_data_direction dir, struct sk_buff *skb, size_t len, enum mhi_flags mflags) { - struct mhi_chan *mhi_chan = (dir == DMA_TO_DEVICE) ? mhi_dev->ul_chan : - mhi_dev->dl_chan; struct mhi_buf_info buf_info = { }; buf_info.v_addr = skb->data; buf_info.cb_buf = skb; buf_info.len = len; - if (unlikely(mhi_chan->pre_alloc)) - return -EINVAL; - return mhi_queue(mhi_dev, &buf_info, dir, mflags); } EXPORT_SYMBOL_GPL(mhi_queue_skb); @@ -1472,45 +1450,6 @@ static int mhi_prepare_channel(struct mhi_controller *mhi_cntrl, if (ret) goto error_pm_state; - if (mhi_chan->dir == DMA_FROM_DEVICE) - mhi_chan->pre_alloc = !!(flags & MHI_CH_INBOUND_ALLOC_BUFS); - - /* Pre-allocate buffer for xfer ring */ - if (mhi_chan->pre_alloc) { - int nr_el = get_nr_avail_ring_elements(mhi_cntrl, - &mhi_chan->tre_ring); - size_t len = mhi_cntrl->buffer_len; - - while (nr_el--) { - void *buf; - struct mhi_buf_info info = { }; - - buf = kmalloc(len, GFP_KERNEL); - if (!buf) { - ret = -ENOMEM; - goto error_pre_alloc; - } - - /* Prepare transfer descriptors */ - info.v_addr = buf; - info.cb_buf = buf; - info.len = len; - ret = mhi_gen_tre(mhi_cntrl, mhi_chan, &info, MHI_EOT); - if (ret) { - kfree(buf); - goto error_pre_alloc; - } - } - - read_lock_bh(&mhi_cntrl->pm_lock); - if (MHI_DB_ACCESS_VALID(mhi_cntrl)) { - read_lock_irq(&mhi_chan->lock); - mhi_ring_chan_db(mhi_cntrl, mhi_chan); - read_unlock_irq(&mhi_chan->lock); - } - read_unlock_bh(&mhi_cntrl->pm_lock); - } - mutex_unlock(&mhi_chan->mutex); return 0; @@ -1523,12 +1462,6 @@ error_init_chan: mutex_unlock(&mhi_chan->mutex); return ret; - -error_pre_alloc: - mutex_unlock(&mhi_chan->mutex); - mhi_unprepare_channel(mhi_cntrl, mhi_chan); - - return ret; } static void mhi_mark_stale_events(struct mhi_controller *mhi_cntrl, @@ -1600,12 +1533,8 @@ static void mhi_reset_data_chan(struct mhi_controller *mhi_cntrl, mhi_del_ring_element(mhi_cntrl, buf_ring); mhi_del_ring_element(mhi_cntrl, tre_ring); - if (mhi_chan->pre_alloc) { - kfree(buf_info->cb_buf); - } else { - result.buf_addr = buf_info->cb_buf; - mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); - } + result.buf_addr = buf_info->cb_buf; + mhi_chan->xfer_cb(mhi_chan->mhi_dev, &result); } } @@ -1666,12 +1595,6 @@ int mhi_prepare_for_transfer(struct mhi_device *mhi_dev) } EXPORT_SYMBOL_GPL(mhi_prepare_for_transfer); -int mhi_prepare_for_transfer_autoqueue(struct mhi_device *mhi_dev) -{ - return __mhi_prepare_for_transfer(mhi_dev, MHI_CH_INBOUND_ALLOC_BUFS); -} -EXPORT_SYMBOL_GPL(mhi_prepare_for_transfer_autoqueue); - void mhi_unprepare_from_transfer(struct mhi_device *mhi_dev) { struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; diff --git a/drivers/bus/mhi/host/pci_generic.c b/drivers/bus/mhi/host/pci_generic.c index e3bc737313a2..0884a384b77f 100644 --- a/drivers/bus/mhi/host/pci_generic.c +++ b/drivers/bus/mhi/host/pci_generic.c @@ -94,22 +94,6 @@ struct mhi_pci_dev_info { .doorbell_mode_switch = false, \ } -#define MHI_CHANNEL_CONFIG_DL_AUTOQUEUE(ch_num, ch_name, el_count, ev_ring) \ - { \ - .num = ch_num, \ - .name = ch_name, \ - .num_elements = el_count, \ - .event_ring = ev_ring, \ - .dir = DMA_FROM_DEVICE, \ - .ee_mask = BIT(MHI_EE_AMSS), \ - .pollcfg = 0, \ - .doorbell = MHI_DB_BRST_DISABLE, \ - .lpm_notify = false, \ - .offload_channel = false, \ - .doorbell_mode_switch = false, \ - .auto_queue = true, \ - } - #define MHI_EVENT_CONFIG_CTRL(ev_ring, el_count) \ { \ .num_elements = el_count, \ @@ -329,7 +313,7 @@ static const struct mhi_channel_config modem_qcom_v1_mhi_channels[] = { MHI_CHANNEL_CONFIG_UL(14, "QMI", 4, 0), MHI_CHANNEL_CONFIG_DL(15, "QMI", 4, 0), MHI_CHANNEL_CONFIG_UL(20, "IPCR", 8, 0), - MHI_CHANNEL_CONFIG_DL_AUTOQUEUE(21, "IPCR", 8, 0), + MHI_CHANNEL_CONFIG_DL(21, "IPCR", 8, 0), MHI_CHANNEL_CONFIG_UL_FP(34, "FIREHOSE", 32, 0), MHI_CHANNEL_CONFIG_DL_FP(35, "FIREHOSE", 32, 0), MHI_CHANNEL_CONFIG_UL(46, "IP_SW0", 64, 2), @@ -762,7 +746,7 @@ static const struct mhi_channel_config mhi_telit_fn980_hw_v1_channels[] = { MHI_CHANNEL_CONFIG_UL(14, "QMI", 32, 0), MHI_CHANNEL_CONFIG_DL(15, "QMI", 32, 0), MHI_CHANNEL_CONFIG_UL(20, "IPCR", 16, 0), - MHI_CHANNEL_CONFIG_DL_AUTOQUEUE(21, "IPCR", 16, 0), + MHI_CHANNEL_CONFIG_DL(21, "IPCR", 16, 0), MHI_CHANNEL_CONFIG_HW_UL(100, "IP_HW0", 128, 1), MHI_CHANNEL_CONFIG_HW_DL(101, "IP_HW0", 128, 2), }; diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index d2cfc584e202..2a3a37b2cf3c 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -249,32 +249,6 @@ config SONYPI To compile this driver as a module, choose M here: the module will be called sonypi. -config MWAVE - tristate "ACP Modem (Mwave) support" - depends on X86 && TTY - select SERIAL_8250 - help - The ACP modem (Mwave) for Linux is a WinModem. It is composed of a - kernel driver and a user level application. Together these components - support direct attachment to public switched telephone networks (PSTNs) - and support selected world wide countries. - - This version of the ACP Modem driver supports the IBM Thinkpad 600E, - 600, and 770 that include on board ACP modem hardware. - - The modem also supports the standard communications port interface - (ttySx) and is compatible with the Hayes AT Command Set. - - The user level application needed to use this driver can be found at - the IBM Linux Technology Center (LTC) web site: - <http://www.ibm.com/linux/ltc/>. - - If you own one of the above IBM Thinkpads which has the Mwave chipset - in it, say Y. - - To compile this driver as a module, choose M here: the - module will be called mwave. - config SCx200_GPIO tristate "NatSemi SCx200 GPIO Support" depends on SCx200 diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 1291369b9126..47bdc882797a 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -33,7 +33,6 @@ obj-$(CONFIG_PC8736x_GPIO) += pc8736x_gpio.o obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o obj-$(CONFIG_TELCLOCK) += tlclk.o -obj-$(CONFIG_MWAVE) += mwave/ obj-y += agp/ obj-$(CONFIG_HANGCHECK_TIMER) += hangcheck-timer.o diff --git a/drivers/char/misc_minor_kunit.c b/drivers/char/misc_minor_kunit.c index 6fc8b05169c5..e930c78e1ef9 100644 --- a/drivers/char/misc_minor_kunit.c +++ b/drivers/char/misc_minor_kunit.c @@ -166,7 +166,7 @@ static void __init miscdev_test_can_open(struct kunit *test, struct miscdevice * KUNIT_FAIL(test, "failed to create node\n"); filp = filp_open(devname, O_RDONLY, 0); - if (IS_ERR_OR_NULL(filp)) + if (IS_ERR(filp)) KUNIT_FAIL(test, "failed to open misc device: %ld\n", PTR_ERR(filp)); else fput(filp); diff --git a/drivers/char/mwave/3780i.c b/drivers/char/mwave/3780i.c deleted file mode 100644 index 90f93cefb21c..000000000000 --- a/drivers/char/mwave/3780i.c +++ /dev/null @@ -1,536 +0,0 @@ -/* -* -* 3780i.c -- helper routines for the 3780i DSP -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#define pr_fmt(fmt) "3780i: " fmt - -#include <linux/kernel.h> -#include <linux/unistd.h> -#include <linux/delay.h> -#include <linux/ioport.h> -#include <linux/bitops.h> -#include <linux/sched.h> /* cond_resched() */ - -#include <asm/io.h> -#include <linux/uaccess.h> -#include <asm/irq.h> -#include "smapi.h" -#include "mwavedd.h" -#include "3780i.h" - -static DEFINE_SPINLOCK(dsp_lock); - -static void PaceMsaAccess(unsigned short usDspBaseIO) -{ - cond_resched(); - udelay(100); - cond_resched(); -} - -unsigned short dsp3780I_ReadMsaCfg(unsigned short usDspBaseIO, - unsigned long ulMsaAddr) -{ - unsigned long flags; - unsigned short val; - - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulMsaAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulMsaAddr >> 16)); - val = InWordDsp(DSP_MsaDataDSISHigh); - spin_unlock_irqrestore(&dsp_lock, flags); - - return val; -} - -void dsp3780I_WriteMsaCfg(unsigned short usDspBaseIO, - unsigned long ulMsaAddr, unsigned short usValue) -{ - unsigned long flags; - - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulMsaAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulMsaAddr >> 16)); - OutWordDsp(DSP_MsaDataDSISHigh, usValue); - spin_unlock_irqrestore(&dsp_lock, flags); -} - -static void dsp3780I_WriteGenCfg(unsigned short usDspBaseIO, unsigned uIndex, - unsigned char ucValue) -{ - DSP_ISA_SLAVE_CONTROL rSlaveControl; - DSP_ISA_SLAVE_CONTROL rSlaveControl_Save; - - MKBYTE(rSlaveControl) = InByteDsp(DSP_IsaSlaveControl); - - rSlaveControl_Save = rSlaveControl; - rSlaveControl.ConfigMode = true; - - OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl)); - OutByteDsp(DSP_ConfigAddress, (unsigned char) uIndex); - OutByteDsp(DSP_ConfigData, ucValue); - OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl_Save)); -} - -int dsp3780I_EnableDSP(struct dsp_3780i_config_settings *pSettings, - unsigned short *pIrqMap, - unsigned short *pDmaMap) -{ - unsigned long flags; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - int i; - DSP_UART_CFG_1 rUartCfg1; - DSP_UART_CFG_2 rUartCfg2; - DSP_HBRIDGE_CFG_1 rHBridgeCfg1; - DSP_HBRIDGE_CFG_2 rHBridgeCfg2; - DSP_BUSMASTER_CFG_1 rBusmasterCfg1; - DSP_BUSMASTER_CFG_2 rBusmasterCfg2; - DSP_ISA_PROT_CFG rIsaProtCfg; - DSP_POWER_MGMT_CFG rPowerMgmtCfg; - DSP_HBUS_TIMER_CFG rHBusTimerCfg; - DSP_LBUS_TIMEOUT_DISABLE rLBusTimeoutDisable; - DSP_CHIP_RESET rChipReset; - DSP_CLOCK_CONTROL_1 rClockControl1; - DSP_CLOCK_CONTROL_2 rClockControl2; - DSP_ISA_SLAVE_CONTROL rSlaveControl; - DSP_HBRIDGE_CONTROL rHBridgeControl; - unsigned short tval; - - if (!pSettings->bDSPEnabled) { - pr_err("%s: Error: DSP not enabled. Aborting.\n", __func__); - return -EIO; - } - - if (pSettings->bModemEnabled) { - rUartCfg1.Reserved = rUartCfg2.Reserved = 0; - rUartCfg1.IrqActiveLow = pSettings->bUartIrqActiveLow; - rUartCfg1.IrqPulse = pSettings->bUartIrqPulse; - rUartCfg1.Irq = - (unsigned char) pIrqMap[pSettings->usUartIrq]; - switch (pSettings->usUartBaseIO) { - case 0x03F8: - rUartCfg1.BaseIO = 0; - break; - case 0x02F8: - rUartCfg1.BaseIO = 1; - break; - case 0x03E8: - rUartCfg1.BaseIO = 2; - break; - case 0x02E8: - rUartCfg1.BaseIO = 3; - break; - } - rUartCfg2.Enable = true; - } - - rHBridgeCfg1.Reserved = rHBridgeCfg2.Reserved = 0; - rHBridgeCfg1.IrqActiveLow = pSettings->bDspIrqActiveLow; - rHBridgeCfg1.IrqPulse = pSettings->bDspIrqPulse; - rHBridgeCfg1.Irq = (unsigned char) pIrqMap[pSettings->usDspIrq]; - rHBridgeCfg1.AccessMode = 1; - rHBridgeCfg2.Enable = true; - - - rBusmasterCfg2.Reserved = 0; - rBusmasterCfg1.Dma = (unsigned char) pDmaMap[pSettings->usDspDma]; - rBusmasterCfg1.NumTransfers = - (unsigned char) pSettings->usNumTransfers; - rBusmasterCfg1.ReRequest = (unsigned char) pSettings->usReRequest; - rBusmasterCfg1.MEMCS16 = pSettings->bEnableMEMCS16; - rBusmasterCfg2.IsaMemCmdWidth = - (unsigned char) pSettings->usIsaMemCmdWidth; - - - rIsaProtCfg.Reserved = 0; - rIsaProtCfg.GateIOCHRDY = pSettings->bGateIOCHRDY; - - rPowerMgmtCfg.Reserved = 0; - rPowerMgmtCfg.Enable = pSettings->bEnablePwrMgmt; - - rHBusTimerCfg.LoadValue = - (unsigned char) pSettings->usHBusTimerLoadValue; - - rLBusTimeoutDisable.Reserved = 0; - rLBusTimeoutDisable.DisableTimeout = - pSettings->bDisableLBusTimeout; - - MKWORD(rChipReset) = ~pSettings->usChipletEnable; - - rClockControl1.Reserved1 = rClockControl1.Reserved2 = 0; - rClockControl1.N_Divisor = pSettings->usN_Divisor; - rClockControl1.M_Multiplier = pSettings->usM_Multiplier; - - rClockControl2.Reserved = 0; - rClockControl2.PllBypass = pSettings->bPllBypass; - - /* Issue a soft reset to the chip */ - /* Note: Since we may be coming in with 3780i clocks suspended, we must keep - * soft-reset active for 10ms. - */ - rSlaveControl.ClockControl = 0; - rSlaveControl.SoftReset = true; - rSlaveControl.ConfigMode = false; - rSlaveControl.Reserved = 0; - - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl)); - MKWORD(tval) = InWordDsp(DSP_IsaSlaveControl); - - for (i = 0; i < 11; i++) - udelay(2000); - - rSlaveControl.SoftReset = false; - OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl)); - - MKWORD(tval) = InWordDsp(DSP_IsaSlaveControl); - - /* Program our general configuration registers */ - WriteGenCfg(DSP_HBridgeCfg1Index, MKBYTE(rHBridgeCfg1)); - WriteGenCfg(DSP_HBridgeCfg2Index, MKBYTE(rHBridgeCfg2)); - WriteGenCfg(DSP_BusMasterCfg1Index, MKBYTE(rBusmasterCfg1)); - WriteGenCfg(DSP_BusMasterCfg2Index, MKBYTE(rBusmasterCfg2)); - WriteGenCfg(DSP_IsaProtCfgIndex, MKBYTE(rIsaProtCfg)); - WriteGenCfg(DSP_PowerMgCfgIndex, MKBYTE(rPowerMgmtCfg)); - WriteGenCfg(DSP_HBusTimerCfgIndex, MKBYTE(rHBusTimerCfg)); - - if (pSettings->bModemEnabled) { - WriteGenCfg(DSP_UartCfg1Index, MKBYTE(rUartCfg1)); - WriteGenCfg(DSP_UartCfg2Index, MKBYTE(rUartCfg2)); - } - - - rHBridgeControl.EnableDspInt = false; - rHBridgeControl.MemAutoInc = true; - rHBridgeControl.IoAutoInc = false; - rHBridgeControl.DiagnosticMode = false; - - OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl)); - spin_unlock_irqrestore(&dsp_lock, flags); - WriteMsaCfg(DSP_LBusTimeoutDisable, MKWORD(rLBusTimeoutDisable)); - WriteMsaCfg(DSP_ClockControl_1, MKWORD(rClockControl1)); - WriteMsaCfg(DSP_ClockControl_2, MKWORD(rClockControl2)); - WriteMsaCfg(DSP_ChipReset, MKWORD(rChipReset)); - - ReadMsaCfg(DSP_ChipID); - - return 0; -} - -int dsp3780I_DisableDSP(struct dsp_3780i_config_settings *pSettings) -{ - unsigned long flags; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - DSP_ISA_SLAVE_CONTROL rSlaveControl; - - rSlaveControl.ClockControl = 0; - rSlaveControl.SoftReset = true; - rSlaveControl.ConfigMode = false; - rSlaveControl.Reserved = 0; - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl)); - - udelay(5); - - rSlaveControl.ClockControl = 1; - OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl)); - spin_unlock_irqrestore(&dsp_lock, flags); - - udelay(5); - - return 0; -} - -int dsp3780I_Reset(struct dsp_3780i_config_settings *pSettings) -{ - unsigned long flags; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - DSP_BOOT_DOMAIN rBootDomain; - DSP_HBRIDGE_CONTROL rHBridgeControl; - - spin_lock_irqsave(&dsp_lock, flags); - /* Mask DSP to PC interrupt */ - MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl); - - rHBridgeControl.EnableDspInt = false; - OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Reset the core via the boot domain register */ - rBootDomain.ResetCore = true; - rBootDomain.Halt = true; - rBootDomain.NMI = true; - rBootDomain.Reserved = 0; - - WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain)); - - /* Reset all the chiplets and then reactivate them */ - WriteMsaCfg(DSP_ChipReset, 0xFFFF); - udelay(5); - WriteMsaCfg(DSP_ChipReset, - (unsigned short) (~pSettings->usChipletEnable)); - - return 0; -} - - -int dsp3780I_Run(struct dsp_3780i_config_settings *pSettings) -{ - unsigned long flags; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - DSP_BOOT_DOMAIN rBootDomain; - DSP_HBRIDGE_CONTROL rHBridgeControl; - - /* Transition the core to a running state */ - rBootDomain.ResetCore = true; - rBootDomain.Halt = false; - rBootDomain.NMI = true; - rBootDomain.Reserved = 0; - WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain)); - - udelay(5); - - rBootDomain.ResetCore = false; - WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain)); - udelay(5); - - rBootDomain.NMI = false; - WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain)); - udelay(5); - - /* Enable DSP to PC interrupt */ - spin_lock_irqsave(&dsp_lock, flags); - MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl); - rHBridgeControl.EnableDspInt = true; - - OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl)); - spin_unlock_irqrestore(&dsp_lock, flags); - - return 0; -} - - -int dsp3780I_ReadDStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr) -{ - unsigned long flags; - unsigned short __user *pusBuffer = pvBuffer; - unsigned short val; - - /* Set the initial MSA address. No adjustments need to be made to data store addresses */ - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Transfer the memory block */ - while (uCount-- != 0) { - spin_lock_irqsave(&dsp_lock, flags); - val = InWordDsp(DSP_MsaDataDSISHigh); - spin_unlock_irqrestore(&dsp_lock, flags); - if(put_user(val, pusBuffer++)) - return -EFAULT; - - PaceMsaAccess(usDspBaseIO); - } - - return 0; -} - -int dsp3780I_ReadAndClearDStore(unsigned short usDspBaseIO, - void __user *pvBuffer, unsigned uCount, - unsigned long ulDSPAddr) -{ - unsigned long flags; - unsigned short __user *pusBuffer = pvBuffer; - unsigned short val; - - /* Set the initial MSA address. No adjustments need to be made to data store addresses */ - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Transfer the memory block */ - while (uCount-- != 0) { - spin_lock_irqsave(&dsp_lock, flags); - val = InWordDsp(DSP_ReadAndClear); - spin_unlock_irqrestore(&dsp_lock, flags); - if(put_user(val, pusBuffer++)) - return -EFAULT; - - PaceMsaAccess(usDspBaseIO); - } - - return 0; -} - - -int dsp3780I_WriteDStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr) -{ - unsigned long flags; - unsigned short __user *pusBuffer = pvBuffer; - - /* Set the initial MSA address. No adjustments need to be made to data store addresses */ - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Transfer the memory block */ - while (uCount-- != 0) { - unsigned short val; - if(get_user(val, pusBuffer++)) - return -EFAULT; - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaDataDSISHigh, val); - spin_unlock_irqrestore(&dsp_lock, flags); - - PaceMsaAccess(usDspBaseIO); - } - - return 0; -} - - -int dsp3780I_ReadIStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr) -{ - unsigned long flags; - unsigned short __user *pusBuffer = pvBuffer; - - /* - * Set the initial MSA address. To convert from an instruction store - * address to an MSA address - * shift the address two bits to the left and set bit 22 - */ - ulDSPAddr = (ulDSPAddr << 2) | (1 << 22); - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Transfer the memory block */ - while (uCount-- != 0) { - unsigned short val_lo, val_hi; - spin_lock_irqsave(&dsp_lock, flags); - val_lo = InWordDsp(DSP_MsaDataISLow); - val_hi = InWordDsp(DSP_MsaDataDSISHigh); - spin_unlock_irqrestore(&dsp_lock, flags); - if(put_user(val_lo, pusBuffer++)) - return -EFAULT; - if(put_user(val_hi, pusBuffer++)) - return -EFAULT; - - PaceMsaAccess(usDspBaseIO); - - } - - return 0; -} - - -int dsp3780I_WriteIStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr) -{ - unsigned long flags; - unsigned short __user *pusBuffer = pvBuffer; - - /* - * Set the initial MSA address. To convert from an instruction store - * address to an MSA address - * shift the address two bits to the left and set bit 22 - */ - ulDSPAddr = (ulDSPAddr << 2) | (1 << 22); - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr); - OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16)); - spin_unlock_irqrestore(&dsp_lock, flags); - - /* Transfer the memory block */ - while (uCount-- != 0) { - unsigned short val_lo, val_hi; - if(get_user(val_lo, pusBuffer++)) - return -EFAULT; - if(get_user(val_hi, pusBuffer++)) - return -EFAULT; - spin_lock_irqsave(&dsp_lock, flags); - OutWordDsp(DSP_MsaDataISLow, val_lo); - OutWordDsp(DSP_MsaDataDSISHigh, val_hi); - spin_unlock_irqrestore(&dsp_lock, flags); - - PaceMsaAccess(usDspBaseIO); - } - - return 0; -} - - -int dsp3780I_GetIPCSource(unsigned short usDspBaseIO, - unsigned short *pusIPCSource) -{ - unsigned long flags; - DSP_HBRIDGE_CONTROL rHBridgeControl; - - /* - * Disable DSP to PC interrupts, read the interrupt register, - * clear the pending IPC bits, and reenable DSP to PC interrupts - */ - spin_lock_irqsave(&dsp_lock, flags); - MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl); - rHBridgeControl.EnableDspInt = false; - OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl)); - - *pusIPCSource = InWordDsp(DSP_Interrupt); - OutWordDsp(DSP_Interrupt, (unsigned short) ~(*pusIPCSource)); - - rHBridgeControl.EnableDspInt = true; - OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl)); - spin_unlock_irqrestore(&dsp_lock, flags); - - return 0; -} diff --git a/drivers/char/mwave/3780i.h b/drivers/char/mwave/3780i.h deleted file mode 100644 index 53dafceb20e0..000000000000 --- a/drivers/char/mwave/3780i.h +++ /dev/null @@ -1,358 +0,0 @@ -/* -* -* 3780i.h -- declarations for 3780i.c -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#ifndef _LINUX_3780I_H -#define _LINUX_3780I_H - -#include <asm/io.h> - -/* DSP I/O port offsets and definitions */ -#define DSP_IsaSlaveControl 0x0000 /* ISA slave control register */ -#define DSP_IsaSlaveStatus 0x0001 /* ISA slave status register */ -#define DSP_ConfigAddress 0x0002 /* General config address register */ -#define DSP_ConfigData 0x0003 /* General config data register */ -#define DSP_HBridgeControl 0x0002 /* HBridge control register */ -#define DSP_MsaAddrLow 0x0004 /* MSP System Address, low word */ -#define DSP_MsaAddrHigh 0x0006 /* MSP System Address, high word */ -#define DSP_MsaDataDSISHigh 0x0008 /* MSA data register: d-store word or high byte of i-store */ -#define DSP_MsaDataISLow 0x000A /* MSA data register: low word of i-store */ -#define DSP_ReadAndClear 0x000C /* MSA read and clear data register */ -#define DSP_Interrupt 0x000E /* Interrupt register (IPC source) */ - -typedef struct { - unsigned char ClockControl:1; /* RW: Clock control: 0=normal, 1=stop 3780i clocks */ - unsigned char SoftReset:1; /* RW: Soft reset 0=normal, 1=soft reset active */ - unsigned char ConfigMode:1; /* RW: Configuration mode, 0=normal, 1=config mode */ - unsigned short Reserved:13; /* 0: Reserved */ -} DSP_ISA_SLAVE_CONTROL; - - -typedef struct { - unsigned short EnableDspInt:1; /* RW: Enable DSP to X86 ISA interrupt 0=mask it, 1=enable it */ - unsigned short MemAutoInc:1; /* RW: Memory address auto increment, 0=disable, 1=enable */ - unsigned short IoAutoInc:1; /* RW: I/O address auto increment, 0=disable, 1=enable */ - unsigned short DiagnosticMode:1; /* RW: Disgnostic mode 0=nromal, 1=diagnostic mode */ - unsigned short IsaPacingTimer:12; /* R: ISA access pacing timer: count of core cycles stolen */ -} DSP_HBRIDGE_CONTROL; - - -/* DSP register indexes used with the configuration register address (index) register */ -#define DSP_UartCfg1Index 0x0003 /* UART config register 1 */ -#define DSP_UartCfg2Index 0x0004 /* UART config register 2 */ -#define DSP_HBridgeCfg1Index 0x0007 /* HBridge config register 1 */ -#define DSP_HBridgeCfg2Index 0x0008 /* HBridge config register 2 */ -#define DSP_BusMasterCfg1Index 0x0009 /* ISA bus master config register 1 */ -#define DSP_BusMasterCfg2Index 0x000A /* ISA bus master config register 2 */ -#define DSP_IsaProtCfgIndex 0x000F /* ISA protocol control register */ -#define DSP_PowerMgCfgIndex 0x0010 /* Low poser suspend/resume enable */ -#define DSP_HBusTimerCfgIndex 0x0011 /* HBUS timer load value */ - -typedef struct { - unsigned char IrqActiveLow:1; /* RW: IRQ active high or low: 0=high, 1=low */ - unsigned char IrqPulse:1; /* RW: IRQ pulse or level: 0=level, 1=pulse */ - unsigned char Irq:3; /* RW: IRQ selection */ - unsigned char BaseIO:2; /* RW: Base I/O selection */ - unsigned char Reserved:1; /* 0: Reserved */ -} DSP_UART_CFG_1; - -typedef struct { - unsigned char Enable:1; /* RW: Enable I/O and IRQ: 0=false, 1=true */ - unsigned char Reserved:7; /* 0: Reserved */ -} DSP_UART_CFG_2; - -typedef struct { - unsigned char IrqActiveLow:1; /* RW: IRQ active high=0 or low=1 */ - unsigned char IrqPulse:1; /* RW: IRQ pulse=1 or level=0 */ - unsigned char Irq:3; /* RW: IRQ selection */ - unsigned char AccessMode:1; /* RW: 16-bit register access method 0=byte, 1=word */ - unsigned char Reserved:2; /* 0: Reserved */ -} DSP_HBRIDGE_CFG_1; - -typedef struct { - unsigned char Enable:1; /* RW: enable I/O and IRQ: 0=false, 1=true */ - unsigned char Reserved:7; /* 0: Reserved */ -} DSP_HBRIDGE_CFG_2; - - -typedef struct { - unsigned char Dma:3; /* RW: DMA channel selection */ - unsigned char NumTransfers:2; /* RW: Maximum # of transfers once being granted the ISA bus */ - unsigned char ReRequest:2; /* RW: Minimum delay between releasing the ISA bus and requesting it again */ - unsigned char MEMCS16:1; /* RW: ISA signal MEMCS16: 0=disabled, 1=enabled */ -} DSP_BUSMASTER_CFG_1; - -typedef struct { - unsigned char IsaMemCmdWidth:2; /* RW: ISA memory command width */ - unsigned char Reserved:6; /* 0: Reserved */ -} DSP_BUSMASTER_CFG_2; - - -typedef struct { - unsigned char GateIOCHRDY:1; /* RW: Enable IOCHRDY gating: 0=false, 1=true */ - unsigned char Reserved:7; /* 0: Reserved */ -} DSP_ISA_PROT_CFG; - -typedef struct { - unsigned char Enable:1; /* RW: Enable low power suspend/resume 0=false, 1=true */ - unsigned char Reserved:7; /* 0: Reserved */ -} DSP_POWER_MGMT_CFG; - -typedef struct { - unsigned char LoadValue:8; /* RW: HBUS timer load value */ -} DSP_HBUS_TIMER_CFG; - - - -/* DSP registers that exist in MSA I/O space */ -#define DSP_ChipID 0x80000000 -#define DSP_MspBootDomain 0x80000580 -#define DSP_LBusTimeoutDisable 0x80000580 -#define DSP_ClockControl_1 0x8000058A -#define DSP_ClockControl_2 0x8000058C -#define DSP_ChipReset 0x80000588 -#define DSP_GpioModeControl_15_8 0x80000082 -#define DSP_GpioDriverEnable_15_8 0x80000076 -#define DSP_GpioOutputData_15_8 0x80000072 - -typedef struct { - unsigned short NMI:1; /* RW: non maskable interrupt */ - unsigned short Halt:1; /* RW: Halt MSP clock */ - unsigned short ResetCore:1; /* RW: Reset MSP core interface */ - unsigned short Reserved:13; /* 0: Reserved */ -} DSP_BOOT_DOMAIN; - -typedef struct { - unsigned short DisableTimeout:1; /* RW: Disable LBus timeout */ - unsigned short Reserved:15; /* 0: Reserved */ -} DSP_LBUS_TIMEOUT_DISABLE; - -typedef struct { - unsigned short Memory:1; /* RW: Reset memory interface */ - unsigned short SerialPort1:1; /* RW: Reset serial port 1 interface */ - unsigned short SerialPort2:1; /* RW: Reset serial port 2 interface */ - unsigned short SerialPort3:1; /* RW: Reset serial port 3 interface */ - unsigned short Gpio:1; /* RW: Reset GPIO interface */ - unsigned short Dma:1; /* RW: Reset DMA interface */ - unsigned short SoundBlaster:1; /* RW: Reset soundblaster interface */ - unsigned short Uart:1; /* RW: Reset UART interface */ - unsigned short Midi:1; /* RW: Reset MIDI interface */ - unsigned short IsaMaster:1; /* RW: Reset ISA master interface */ - unsigned short Reserved:6; /* 0: Reserved */ -} DSP_CHIP_RESET; - -typedef struct { - unsigned short N_Divisor:6; /* RW: (N) PLL output clock divisor */ - unsigned short Reserved1:2; /* 0: reserved */ - unsigned short M_Multiplier:6; /* RW: (M) PLL feedback clock multiplier */ - unsigned short Reserved2:2; /* 0: reserved */ -} DSP_CLOCK_CONTROL_1; - -typedef struct { - unsigned short PllBypass:1; /* RW: PLL Bypass */ - unsigned short Reserved:15; /* 0: Reserved */ -} DSP_CLOCK_CONTROL_2; - -typedef struct { - unsigned short Latch8:1; - unsigned short Latch9:1; - unsigned short Latch10:1; - unsigned short Latch11:1; - unsigned short Latch12:1; - unsigned short Latch13:1; - unsigned short Latch14:1; - unsigned short Latch15:1; - unsigned short Mask8:1; - unsigned short Mask9:1; - unsigned short Mask10:1; - unsigned short Mask11:1; - unsigned short Mask12:1; - unsigned short Mask13:1; - unsigned short Mask14:1; - unsigned short Mask15:1; -} DSP_GPIO_OUTPUT_DATA_15_8; - -typedef struct { - unsigned short Enable8:1; - unsigned short Enable9:1; - unsigned short Enable10:1; - unsigned short Enable11:1; - unsigned short Enable12:1; - unsigned short Enable13:1; - unsigned short Enable14:1; - unsigned short Enable15:1; - unsigned short Mask8:1; - unsigned short Mask9:1; - unsigned short Mask10:1; - unsigned short Mask11:1; - unsigned short Mask12:1; - unsigned short Mask13:1; - unsigned short Mask14:1; - unsigned short Mask15:1; -} DSP_GPIO_DRIVER_ENABLE_15_8; - -typedef struct { - unsigned short GpioMode8:2; - unsigned short GpioMode9:2; - unsigned short GpioMode10:2; - unsigned short GpioMode11:2; - unsigned short GpioMode12:2; - unsigned short GpioMode13:2; - unsigned short GpioMode14:2; - unsigned short GpioMode15:2; -} DSP_GPIO_MODE_15_8; - -/* Component masks that are defined in dspmgr.h */ -#define MW_ADC_MASK 0x0001 -#define MW_AIC2_MASK 0x0006 -#define MW_MIDI_MASK 0x0008 -#define MW_CDDAC_MASK 0x8001 -#define MW_AIC1_MASK 0xE006 -#define MW_UART_MASK 0xE00A -#define MW_ACI_MASK 0xE00B - -/* -* Definition of 3780i configuration structure. Unless otherwise stated, -* these values are provided as input to the 3780i support layer. At present, -* the only values maintained by the 3780i support layer are the saved UART -* registers. -*/ -struct dsp_3780i_config_settings { - - /* Location of base configuration register */ - unsigned short usBaseConfigIO; - - /* Enables for various DSP components */ - int bDSPEnabled; - int bModemEnabled; - int bInterruptClaimed; - - /* IRQ, DMA, and Base I/O addresses for various DSP components */ - unsigned short usDspIrq; - unsigned short usDspDma; - unsigned short usDspBaseIO; - unsigned short usUartIrq; - unsigned short usUartBaseIO; - - /* IRQ modes for various DSP components */ - int bDspIrqActiveLow; - int bUartIrqActiveLow; - int bDspIrqPulse; - int bUartIrqPulse; - - /* Card abilities */ - unsigned uIps; - unsigned uDStoreSize; - unsigned uIStoreSize; - unsigned uDmaBandwidth; - - /* Adapter specific 3780i settings */ - unsigned short usNumTransfers; - unsigned short usReRequest; - int bEnableMEMCS16; - unsigned short usIsaMemCmdWidth; - int bGateIOCHRDY; - int bEnablePwrMgmt; - unsigned short usHBusTimerLoadValue; - int bDisableLBusTimeout; - unsigned short usN_Divisor; - unsigned short usM_Multiplier; - int bPllBypass; - unsigned short usChipletEnable; /* Used with the chip reset register to enable specific chiplets */ - - /* Saved UART registers. These are maintained by the 3780i support layer. */ - int bUartSaved; /* True after a successful save of the UART registers */ - unsigned char ucIER; /* Interrupt enable register */ - unsigned char ucFCR; /* FIFO control register */ - unsigned char ucLCR; /* Line control register */ - unsigned char ucMCR; /* Modem control register */ - unsigned char ucSCR; /* Scratch register */ - unsigned char ucDLL; /* Divisor latch, low byte */ - unsigned char ucDLM; /* Divisor latch, high byte */ -}; - - -/* 3780i support functions */ -int dsp3780I_EnableDSP(struct dsp_3780i_config_settings *pSettings, - unsigned short *pIrqMap, - unsigned short *pDmaMap); -int dsp3780I_DisableDSP(struct dsp_3780i_config_settings *pSettings); -int dsp3780I_Reset(struct dsp_3780i_config_settings *pSettings); -int dsp3780I_Run(struct dsp_3780i_config_settings *pSettings); -int dsp3780I_ReadDStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr); -int dsp3780I_ReadAndClearDStore(unsigned short usDspBaseIO, - void __user *pvBuffer, unsigned uCount, - unsigned long ulDSPAddr); -int dsp3780I_WriteDStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr); -int dsp3780I_ReadIStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr); -int dsp3780I_WriteIStore(unsigned short usDspBaseIO, void __user *pvBuffer, - unsigned uCount, unsigned long ulDSPAddr); -unsigned short dsp3780I_ReadMsaCfg(unsigned short usDspBaseIO, - unsigned long ulMsaAddr); -void dsp3780I_WriteMsaCfg(unsigned short usDspBaseIO, - unsigned long ulMsaAddr, unsigned short usValue); -int dsp3780I_GetIPCSource(unsigned short usDspBaseIO, - unsigned short *pusIPCSource); - -/* I/O port access macros */ -#define MKWORD(var) (*((unsigned short *)(&var))) -#define MKBYTE(var) (*((unsigned char *)(&var))) - -#define WriteMsaCfg(addr,value) dsp3780I_WriteMsaCfg(usDspBaseIO,addr,value) -#define ReadMsaCfg(addr) dsp3780I_ReadMsaCfg(usDspBaseIO,addr) -#define WriteGenCfg(index,value) dsp3780I_WriteGenCfg(usDspBaseIO,index,value) -#define ReadGenCfg(index) dsp3780I_ReadGenCfg(usDspBaseIO,index) - -#define InWordDsp(index) inw(usDspBaseIO+index) -#define InByteDsp(index) inb(usDspBaseIO+index) -#define OutWordDsp(index,value) outw(value,usDspBaseIO+index) -#define OutByteDsp(index,value) outb(value,usDspBaseIO+index) - -#endif diff --git a/drivers/char/mwave/Makefile b/drivers/char/mwave/Makefile deleted file mode 100644 index e56c1a375535..000000000000 --- a/drivers/char/mwave/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Makefile for ACP Modem (Mwave). -# -# See the README file in this directory for more info. <paulsch@us.ibm.com> -# - -obj-$(CONFIG_MWAVE) += mwave.o - -mwave-y := mwavedd.o smapi.o tp3780i.o 3780i.o diff --git a/drivers/char/mwave/README b/drivers/char/mwave/README deleted file mode 100644 index 6224aa814c62..000000000000 --- a/drivers/char/mwave/README +++ /dev/null @@ -1,37 +0,0 @@ -Module options --------------- - -The mwave module takes the following options. Note that these options -are not saved by the BIOS and so do not persist after unload and reload. - - mwave_3780i_irq=5/7/10/11/15 - If the dsp irq has not been setup and stored in bios by the - thinkpad configuration utility then this parameter allows the - irq used by the dsp to be configured. - - mwave_3780i_io=0x130/0x350/0x0070/0xDB0 - If the dsp io range has not been setup and stored in bios by the - thinkpad configuration utility then this parameter allows the - io range used by the dsp to be configured. - - mwave_uart_irq=3/4 - If the mwave's uart irq has not been setup and stored in bios by the - thinkpad configuration utility then this parameter allows the - irq used by the mwave uart to be configured. - - mwave_uart_io=0x3f8/0x2f8/0x3E8/0x2E8 - If the uart io range has not been setup and stored in bios by the - thinkpad configuration utility then this parameter allows the - io range used by the mwave uart to be configured. - -Example to enable the 3780i DSP using ttyS1 resources: - - insmod mwave mwave_3780i_irq=10 mwave_3780i_io=0x0130 mwave_uart_irq=3 mwave_uart_io=0x2f8 - -Accessing the driver --------------------- - -You must also create a node for the driver: - mkdir -p /dev/modems - mknod --mode=660 /dev/modems/mwave c 10 219 - diff --git a/drivers/char/mwave/mwavedd.c b/drivers/char/mwave/mwavedd.c deleted file mode 100644 index 640a9cb0dd8d..000000000000 --- a/drivers/char/mwave/mwavedd.c +++ /dev/null @@ -1,432 +0,0 @@ -/* -* -* mwavedd.c -- mwave device driver -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#define pr_fmt(fmt) "mwavedd: " fmt - -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/fs.h> -#include <linux/init.h> -#include <linux/major.h> -#include <linux/miscdevice.h> -#include <linux/device.h> -#include <linux/serial.h> -#include <linux/sched.h> -#include <linux/spinlock.h> -#include <linux/mutex.h> -#include <linux/delay.h> -#include <linux/serial_8250.h> -#include <linux/nospec.h> -#include "smapi.h" -#include "mwavedd.h" -#include "3780i.h" -#include "tp3780i.h" - -MODULE_DESCRIPTION("3780i Advanced Communications Processor (Mwave) driver"); -MODULE_AUTHOR("Mike Sullivan and Paul Schroeder"); -MODULE_LICENSE("GPL"); - -/* -* These parameters support the setting of MWave resources. Note that no -* checks are made against other devices (ie. superio) for conflicts. -* We'll depend on users using the tpctl utility to do that for now -*/ -static DEFINE_MUTEX(mwave_mutex); -int mwave_3780i_irq = 0; -int mwave_3780i_io = 0; -int mwave_uart_irq = 0; -int mwave_uart_io = 0; -module_param_hw(mwave_3780i_irq, int, irq, 0); -module_param_hw(mwave_3780i_io, int, ioport, 0); -module_param_hw(mwave_uart_irq, int, irq, 0); -module_param_hw(mwave_uart_io, int, ioport, 0); - -struct mwave_device_data mwave_s_mdd; - -static long mwave_ioctl(struct file *file, unsigned int iocmd, - unsigned long ioarg) -{ - unsigned int retval = 0; - struct mwave_device_data *pDrvData = &mwave_s_mdd; - void __user *arg = (void __user *)ioarg; - - switch (iocmd) { - - case IOCTL_MW_RESET: - mutex_lock(&mwave_mutex); - retval = tp3780I_ResetDSP(&pDrvData->rBDData); - mutex_unlock(&mwave_mutex); - break; - - case IOCTL_MW_RUN: - mutex_lock(&mwave_mutex); - retval = tp3780I_StartDSP(&pDrvData->rBDData); - mutex_unlock(&mwave_mutex); - break; - - case IOCTL_MW_DSP_ABILITIES: { - struct mw_abilities rAbilities; - - mutex_lock(&mwave_mutex); - retval = tp3780I_QueryAbilities(&pDrvData->rBDData, - &rAbilities); - mutex_unlock(&mwave_mutex); - if (retval == 0) { - if (copy_to_user(arg, &rAbilities, sizeof(rAbilities))) - return -EFAULT; - } - } - break; - - case IOCTL_MW_READ_DATA: - case IOCTL_MW_READCLEAR_DATA: { - struct mw_readwrite rReadData; - unsigned short __user *pusBuffer = NULL; - - if( copy_from_user(&rReadData, arg, - sizeof(struct mw_readwrite)) ) - return -EFAULT; - pusBuffer = (unsigned short __user *) (rReadData.pBuf); - - mutex_lock(&mwave_mutex); - retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData, - iocmd, - pusBuffer, - rReadData.ulDataLength, - rReadData.usDspAddress); - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_READ_INST: { - struct mw_readwrite rReadData; - unsigned short __user *pusBuffer = NULL; - - if (copy_from_user(&rReadData, arg, sizeof(rReadData))) - return -EFAULT; - pusBuffer = (unsigned short __user *) (rReadData.pBuf); - - mutex_lock(&mwave_mutex); - retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData, - iocmd, pusBuffer, - rReadData.ulDataLength / 2, - rReadData.usDspAddress); - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_WRITE_DATA: { - struct mw_readwrite rWriteData; - unsigned short __user *pusBuffer = NULL; - - if (copy_from_user(&rWriteData, arg, sizeof(rWriteData))) - return -EFAULT; - pusBuffer = (unsigned short __user *) (rWriteData.pBuf); - - mutex_lock(&mwave_mutex); - retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData, - iocmd, pusBuffer, - rWriteData.ulDataLength, - rWriteData.usDspAddress); - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_WRITE_INST: { - struct mw_readwrite rWriteData; - unsigned short __user *pusBuffer = NULL; - - if (copy_from_user(&rWriteData, arg, sizeof(rWriteData))) - return -EFAULT; - pusBuffer = (unsigned short __user *)(rWriteData.pBuf); - - mutex_lock(&mwave_mutex); - retval = tp3780I_ReadWriteDspIStore(&pDrvData->rBDData, - iocmd, pusBuffer, - rWriteData.ulDataLength, - rWriteData.usDspAddress); - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_REGISTER_IPC: { - unsigned int ipcnum = (unsigned int) ioarg; - - if (ipcnum >= ARRAY_SIZE(pDrvData->IPCs)) { - pr_err("%s: IOCTL_MW_REGISTER_IPC: Error: Invalid ipcnum %x\n", - __func__, ipcnum); - return -EINVAL; - } - ipcnum = array_index_nospec(ipcnum, - ARRAY_SIZE(pDrvData->IPCs)); - - mutex_lock(&mwave_mutex); - pDrvData->IPCs[ipcnum].bIsHere = false; - pDrvData->IPCs[ipcnum].bIsEnabled = true; - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_GET_IPC: { - unsigned int ipcnum = (unsigned int) ioarg; - - if (ipcnum >= ARRAY_SIZE(pDrvData->IPCs)) { - pr_err("%s: IOCTL_MW_GET_IPC: Error: Invalid ipcnum %x\n", __func__, - ipcnum); - return -EINVAL; - } - ipcnum = array_index_nospec(ipcnum, - ARRAY_SIZE(pDrvData->IPCs)); - - mutex_lock(&mwave_mutex); - if (pDrvData->IPCs[ipcnum].bIsEnabled == true) { - DECLARE_WAITQUEUE(wait, current); - - add_wait_queue(&pDrvData->IPCs[ipcnum].ipc_wait_queue, &wait); - pDrvData->IPCs[ipcnum].bIsHere = true; - set_current_state(TASK_INTERRUPTIBLE); - /* check whether an event was signalled by */ - /* the interrupt handler while we were gone */ - if (pDrvData->IPCs[ipcnum].usIntCount == 1) { /* first int has occurred (race condition) */ - pDrvData->IPCs[ipcnum].usIntCount = 2; /* first int has been handled */ - } else { /* either 1st int has not yet occurred, or we have already handled the first int */ - schedule(); - if (pDrvData->IPCs[ipcnum].usIntCount == 1) { - pDrvData->IPCs[ipcnum].usIntCount = 2; - } - } - pDrvData->IPCs[ipcnum].bIsHere = false; - remove_wait_queue(&pDrvData->IPCs[ipcnum].ipc_wait_queue, &wait); - set_current_state(TASK_RUNNING); - } - mutex_unlock(&mwave_mutex); - } - break; - - case IOCTL_MW_UNREGISTER_IPC: { - unsigned int ipcnum = (unsigned int) ioarg; - - if (ipcnum >= ARRAY_SIZE(pDrvData->IPCs)) { - pr_err("%s: IOCTL_MW_UNREGISTER_IPC: Error: Invalid ipcnum %x\n", - __func__, ipcnum); - return -EINVAL; - } - ipcnum = array_index_nospec(ipcnum, - ARRAY_SIZE(pDrvData->IPCs)); - mutex_lock(&mwave_mutex); - if (pDrvData->IPCs[ipcnum].bIsEnabled == true) { - pDrvData->IPCs[ipcnum].bIsEnabled = false; - if (pDrvData->IPCs[ipcnum].bIsHere == true) { - wake_up_interruptible(&pDrvData->IPCs[ipcnum].ipc_wait_queue); - } - } - mutex_unlock(&mwave_mutex); - } - break; - - default: - return -ENOTTY; - } /* switch */ - - return retval; -} - -static int register_serial_portandirq(unsigned int port, int irq) -{ - struct uart_8250_port uart; - - switch ( port ) { - case 0x3f8: - case 0x2f8: - case 0x3e8: - case 0x2e8: - /* OK */ - break; - default: - pr_err("%s: Error: Illegal port %x\n", __func__, port); - return -1; - } /* switch */ - /* port is okay */ - - switch ( irq ) { - case 3: - case 4: - case 5: - case 7: - /* OK */ - break; - default: - pr_err("%s: Error: Illegal irq %x\n", __func__, irq); - return -1; - } /* switch */ - /* irq is okay */ - - memset(&uart, 0, sizeof(uart)); - - uart.port.uartclk = 1843200; - uart.port.iobase = port; - uart.port.irq = irq; - uart.port.iotype = UPIO_PORT; - uart.port.flags = UPF_SHARE_IRQ; - return serial8250_register_8250_port(&uart); -} - -static const struct file_operations mwave_fops = { - .owner = THIS_MODULE, - .unlocked_ioctl = mwave_ioctl, - .llseek = default_llseek, -}; - -static struct miscdevice mwave_misc_dev = { MWAVE_MINOR, "mwave", &mwave_fops }; - -/* -* mwave_init is called on module load -* -* mwave_exit is called on module unload -* mwave_exit is also used to clean up after an aborted mwave_init -*/ -static void mwave_exit(void) -{ - struct mwave_device_data *pDrvData = &mwave_s_mdd; - - if ( pDrvData->sLine >= 0 ) { - serial8250_unregister_port(pDrvData->sLine); - } - if (pDrvData->bMwaveDevRegistered) { - misc_deregister(&mwave_misc_dev); - } - if (pDrvData->bDSPEnabled) { - tp3780I_DisableDSP(&pDrvData->rBDData); - } - if (pDrvData->bResourcesClaimed) { - tp3780I_ReleaseResources(&pDrvData->rBDData); - } - if (pDrvData->bBDInitialized) { - tp3780I_Cleanup(&pDrvData->rBDData); - } -} - -module_exit(mwave_exit); - -static int __init mwave_init(void) -{ - int i; - int retval = 0; - struct mwave_device_data *pDrvData = &mwave_s_mdd; - - memset(&mwave_s_mdd, 0, sizeof(mwave_s_mdd)); - - pDrvData->bBDInitialized = false; - pDrvData->bResourcesClaimed = false; - pDrvData->bDSPEnabled = false; - pDrvData->bDSPReset = false; - pDrvData->bMwaveDevRegistered = false; - pDrvData->sLine = -1; - - for (i = 0; i < ARRAY_SIZE(pDrvData->IPCs); i++) { - pDrvData->IPCs[i].bIsEnabled = false; - pDrvData->IPCs[i].bIsHere = false; - pDrvData->IPCs[i].usIntCount = 0; /* no ints received yet */ - init_waitqueue_head(&pDrvData->IPCs[i].ipc_wait_queue); - } - - retval = tp3780I_InitializeBoardData(&pDrvData->rBDData); - if (retval) { - pr_err("%s: Error: Failed to initialize board data\n", __func__); - goto cleanup_error; - } - pDrvData->bBDInitialized = true; - - retval = tp3780I_CalcResources(&pDrvData->rBDData); - if (retval) { - pr_err("%s: Error: Failed to calculate resources\n", __func__); - goto cleanup_error; - } - - retval = tp3780I_ClaimResources(&pDrvData->rBDData); - if (retval) { - pr_err("%s: Error: Failed to claim resources\n", __func__); - goto cleanup_error; - } - pDrvData->bResourcesClaimed = true; - - retval = tp3780I_EnableDSP(&pDrvData->rBDData); - if (retval) { - pr_err("%s: Error: Failed to enable DSP\n", __func__); - goto cleanup_error; - } - pDrvData->bDSPEnabled = true; - - if (misc_register(&mwave_misc_dev) < 0) { - pr_err("%s: Error: Failed to register misc device\n", __func__); - goto cleanup_error; - } - pDrvData->bMwaveDevRegistered = true; - - pDrvData->sLine = register_serial_portandirq( - pDrvData->rBDData.rDspSettings.usUartBaseIO, - pDrvData->rBDData.rDspSettings.usUartIrq - ); - if (pDrvData->sLine < 0) { - pr_err("%s: Error: Failed to register serial driver\n", __func__); - goto cleanup_error; - } - /* uart is registered */ - - /* SUCCESS! */ - return 0; - -cleanup_error: - pr_err("%s: Error: Failed to initialize\n", __func__); - mwave_exit(); /* clean up */ - - return -EIO; -} - -module_init(mwave_init); - diff --git a/drivers/char/mwave/mwavedd.h b/drivers/char/mwave/mwavedd.h deleted file mode 100644 index e1da1493eec5..000000000000 --- a/drivers/char/mwave/mwavedd.h +++ /dev/null @@ -1,90 +0,0 @@ -/* -* -* mwavedd.h -- declarations for mwave device driver -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#ifndef _LINUX_MWAVEDD_H -#define _LINUX_MWAVEDD_H -#include "3780i.h" -#include "tp3780i.h" -#include "smapi.h" -#include "mwavepub.h" -#include <linux/ioctl.h> -#include <linux/uaccess.h> -#include <linux/wait.h> - -extern int mwave_3780i_irq; -extern int mwave_3780i_io; -extern int mwave_uart_irq; -extern int mwave_uart_io; - -struct mwave_ipc { - unsigned short usIntCount; /* 0=none, 1=first, 2=greater than 1st */ - bool bIsEnabled; - bool bIsHere; - /* entry spin lock */ - wait_queue_head_t ipc_wait_queue; -}; - -struct mwave_device_data { - struct thinkpad_bd_data rBDData; /* board driver's data area */ - unsigned long ulIPCSource_ISR; /* IPC source bits for recently processed intr, set during ISR processing */ - unsigned long ulIPCSource_DPC; /* IPC source bits for recently processed intr, set during DPC processing */ - bool bBDInitialized; - bool bResourcesClaimed; - bool bDSPEnabled; - bool bDSPReset; - struct mwave_ipc IPCs[16]; - bool bMwaveDevRegistered; - short sLine; - int nr_registered_attrs; - int device_registered; - -}; - -extern struct mwave_device_data mwave_s_mdd; - -#endif diff --git a/drivers/char/mwave/mwavepub.h b/drivers/char/mwave/mwavepub.h deleted file mode 100644 index 280327bdaa38..000000000000 --- a/drivers/char/mwave/mwavepub.h +++ /dev/null @@ -1,89 +0,0 @@ -/* -* -* mwavepub.h -- PUBLIC declarations for the mwave driver -* and applications using it -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#ifndef _LINUX_MWAVEPUB_H -#define _LINUX_MWAVEPUB_H - -#include <linux/miscdevice.h> - - -struct mw_abilities { - unsigned long instr_per_sec; - unsigned long data_size; - unsigned long inst_size; - unsigned long bus_dma_bw; - unsigned short uart_enable; - short component_count; - unsigned long component_list[7]; - char mwave_os_name[16]; - char bios_task_name[16]; -}; - - -struct mw_readwrite { - unsigned short usDspAddress; /* The dsp address */ - unsigned long ulDataLength; /* The size in bytes of the data or user buffer */ - void __user *pBuf; /* Input:variable sized buffer */ -}; - -#define IOCTL_MW_RESET _IO(MWAVE_MINOR,1) -#define IOCTL_MW_RUN _IO(MWAVE_MINOR,2) -#define IOCTL_MW_DSP_ABILITIES _IOR(MWAVE_MINOR,3,struct mw_abilities) -#define IOCTL_MW_READ_DATA _IOR(MWAVE_MINOR,4,struct mw_readwrite) -#define IOCTL_MW_READCLEAR_DATA _IOR(MWAVE_MINOR,5,struct mw_readwrite) -#define IOCTL_MW_READ_INST _IOR(MWAVE_MINOR,6,struct mw_readwrite) -#define IOCTL_MW_WRITE_DATA _IOW(MWAVE_MINOR,7,struct mw_readwrite) -#define IOCTL_MW_WRITE_INST _IOW(MWAVE_MINOR,8,struct mw_readwrite) -#define IOCTL_MW_REGISTER_IPC _IOW(MWAVE_MINOR,9,int) -#define IOCTL_MW_UNREGISTER_IPC _IOW(MWAVE_MINOR,10,int) -#define IOCTL_MW_GET_IPC _IOW(MWAVE_MINOR,11,int) -#define IOCTL_MW_TRACE _IOR(MWAVE_MINOR,12,struct mw_readwrite) - - -#endif diff --git a/drivers/char/mwave/smapi.c b/drivers/char/mwave/smapi.c deleted file mode 100644 index df6354b24339..000000000000 --- a/drivers/char/mwave/smapi.c +++ /dev/null @@ -1,404 +0,0 @@ -/* -* -* smapi.c -- SMAPI interface routines -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#define pr_fmt(fmt) "smapi: " fmt - -#include <linux/kernel.h> -#include <linux/mc146818rtc.h> /* CMOS defines */ -#include "smapi.h" -#include "mwavedd.h" - -static unsigned short g_usSmapiPort = 0; - - -static int smapi_request(unsigned short inBX, unsigned short inCX, - unsigned short inDI, unsigned short inSI, - unsigned short *outAX, unsigned short *outBX, - unsigned short *outCX, unsigned short *outDX, - unsigned short *outDI, unsigned short *outSI) -{ - unsigned short myoutAX = 2, *pmyoutAX = &myoutAX; - unsigned short myoutBX = 3, *pmyoutBX = &myoutBX; - unsigned short myoutCX = 4, *pmyoutCX = &myoutCX; - unsigned short myoutDX = 5, *pmyoutDX = &myoutDX; - unsigned short myoutDI = 6, *pmyoutDI = &myoutDI; - unsigned short myoutSI = 7, *pmyoutSI = &myoutSI; - unsigned short usSmapiOK = -EIO, *pusSmapiOK = &usSmapiOK; - unsigned int inBXCX = (inBX << 16) | inCX; - unsigned int inDISI = (inDI << 16) | inSI; - - __asm__ __volatile__("movw $0x5380,%%ax\n\t" - "movl %7,%%ebx\n\t" - "shrl $16, %%ebx\n\t" - "movw %7,%%cx\n\t" - "movl %8,%%edi\n\t" - "shrl $16,%%edi\n\t" - "movw %8,%%si\n\t" - "movw %9,%%dx\n\t" - "out %%al,%%dx\n\t" - "out %%al,$0x4F\n\t" - "cmpb $0x53,%%ah\n\t" - "je 2f\n\t" - "1:\n\t" - "orb %%ah,%%ah\n\t" - "jnz 2f\n\t" - "movw %%ax,%0\n\t" - "movw %%bx,%1\n\t" - "movw %%cx,%2\n\t" - "movw %%dx,%3\n\t" - "movw %%di,%4\n\t" - "movw %%si,%5\n\t" - "movw $1,%6\n\t" - "2:\n\t":"=m"(*(unsigned short *) pmyoutAX), - "=m"(*(unsigned short *) pmyoutBX), - "=m"(*(unsigned short *) pmyoutCX), - "=m"(*(unsigned short *) pmyoutDX), - "=m"(*(unsigned short *) pmyoutDI), - "=m"(*(unsigned short *) pmyoutSI), - "=m"(*(unsigned short *) pusSmapiOK) - :"m"(inBXCX), "m"(inDISI), "m"(g_usSmapiPort) - :"%eax", "%ebx", "%ecx", "%edx", "%edi", - "%esi"); - - *outAX = myoutAX; - *outBX = myoutBX; - *outCX = myoutCX; - *outDX = myoutDX; - *outDI = myoutDI; - *outSI = myoutSI; - - return usSmapiOK == 1 ? 0 : -EIO; -} - - -int smapi_query_DSP_cfg(struct smapi_dsp_settings *pSettings) -{ - int bRC; - unsigned short usAX, usBX, usCX, usDX, usDI, usSI; - static const unsigned short ausDspBases[] = { - 0x0030, 0x4E30, 0x8E30, 0xCE30, - 0x0130, 0x0350, 0x0070, 0x0DB0 }; - static const unsigned short ausUartBases[] = { - 0x03F8, 0x02F8, 0x03E8, 0x02E8 }; - - bRC = smapi_request(0x1802, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) { - pr_err("%s: Error: Could not get DSP Settings. Aborting.\n", __func__); - return bRC; - } - - pSettings->bDSPPresent = ((usBX & 0x0100) != 0); - pSettings->bDSPEnabled = ((usCX & 0x0001) != 0); - pSettings->usDspIRQ = usSI & 0x00FF; - pSettings->usDspDMA = (usSI & 0xFF00) >> 8; - if ((usDI & 0x00FF) < ARRAY_SIZE(ausDspBases)) { - pSettings->usDspBaseIO = ausDspBases[usDI & 0x00FF]; - } else { - pSettings->usDspBaseIO = 0; - } - - /* check for illegal values */ - if ( pSettings->usDspBaseIO == 0 ) - pr_err("%s: Worry: DSP base I/O address is 0\n", __func__); - if ( pSettings->usDspIRQ == 0 ) - pr_err("%s: Worry: DSP IRQ line is 0\n", __func__); - - bRC = smapi_request(0x1804, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) { - pr_err("%s: Error: Could not get DSP modem settings. Aborting.\n", __func__); - return bRC; - } - - pSettings->bModemEnabled = ((usCX & 0x0001) != 0); - pSettings->usUartIRQ = usSI & 0x000F; - if (((usSI & 0xFF00) >> 8) < ARRAY_SIZE(ausUartBases)) { - pSettings->usUartBaseIO = ausUartBases[(usSI & 0xFF00) >> 8]; - } else { - pSettings->usUartBaseIO = 0; - } - - /* check for illegal values */ - if ( pSettings->usUartBaseIO == 0 ) - pr_err("%s: Worry: UART base I/O address is 0\n", __func__); - if ( pSettings->usUartIRQ == 0 ) - pr_err("%s: Worry: UART IRQ line is 0\n", __func__); - - return bRC; -} - - -int smapi_set_DSP_cfg(void) -{ - int bRC = -EIO; - int i; - unsigned short usAX, usBX, usCX, usDX, usDI, usSI; - static const unsigned short ausDspBases[] = { - 0x0030, 0x4E30, 0x8E30, 0xCE30, - 0x0130, 0x0350, 0x0070, 0x0DB0 }; - static const unsigned short ausUartBases[] = { - 0x03F8, 0x02F8, 0x03E8, 0x02E8 }; - static const unsigned short ausDspIrqs[] = { - 5, 7, 10, 11, 15 }; - static const unsigned short ausUartIrqs[] = { - 3, 4 }; - - unsigned short dspio_index = 0, uartio_index = 0; - - if (mwave_3780i_io) { - for (i = 0; i < ARRAY_SIZE(ausDspBases); i++) { - if (mwave_3780i_io == ausDspBases[i]) - break; - } - if (i == ARRAY_SIZE(ausDspBases)) { - pr_err("%s: Error: Invalid mwave_3780i_io address %x. Aborting.\n", - __func__, mwave_3780i_io); - return bRC; - } - dspio_index = i; - } - - if (mwave_3780i_irq) { - for (i = 0; i < ARRAY_SIZE(ausDspIrqs); i++) { - if (mwave_3780i_irq == ausDspIrqs[i]) - break; - } - if (i == ARRAY_SIZE(ausDspIrqs)) { - pr_err("%s: Error: Invalid mwave_3780i_irq %x. Aborting.\n", __func__, - mwave_3780i_irq); - return bRC; - } - } - - if (mwave_uart_io) { - for (i = 0; i < ARRAY_SIZE(ausUartBases); i++) { - if (mwave_uart_io == ausUartBases[i]) - break; - } - if (i == ARRAY_SIZE(ausUartBases)) { - pr_err("%s: Error: Invalid mwave_uart_io address %x. Aborting.\n", __func__, - mwave_uart_io); - return bRC; - } - uartio_index = i; - } - - - if (mwave_uart_irq) { - for (i = 0; i < ARRAY_SIZE(ausUartIrqs); i++) { - if (mwave_uart_irq == ausUartIrqs[i]) - break; - } - if (i == ARRAY_SIZE(ausUartIrqs)) { - pr_err("%s: Error: Invalid mwave_uart_irq %x. Aborting.\n", __func__, - mwave_uart_irq); - return bRC; - } - } - - if (mwave_uart_irq || mwave_uart_io) { - - /* Check serial port A */ - bRC = smapi_request(0x1402, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - /* bRC == 0 */ - if (usBX & 0x0100) { /* serial port A is present */ - if (usCX & 1) { /* serial port is enabled */ - if ((usSI & 0xFF) == mwave_uart_irq) { - pr_err("%s: Serial port A irq %x conflicts with mwave_uart_irq %x\n", - __func__, usSI & 0xFF, mwave_uart_irq); - goto exit_conflict; - } else { - if ((usSI >> 8) == uartio_index) { - pr_err("%s: Serial port A base I/O address %x conflicts with mwave uart I/O %x\n", - __func__, ausUartBases[usSI >> 8], - ausUartBases[uartio_index]); - goto exit_conflict; - } - } - } - } - - /* Check serial port B */ - bRC = smapi_request(0x1404, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - /* bRC == 0 */ - if (usBX & 0x0100) { /* serial port B is present */ - if (usCX & 1) { /* serial port is enabled */ - if ((usSI & 0xFF) == mwave_uart_irq) { - pr_err("%s: Serial port B irq %x conflicts with mwave_uart_irq %x\n", - __func__, usSI & 0xFF, mwave_uart_irq); - goto exit_conflict; - } else { - if ((usSI >> 8) == uartio_index) { - pr_err("%s: Serial port B base I/O address %x conflicts with mwave uart I/O %x\n", - __func__, ausUartBases[usSI >> 8], - ausUartBases[uartio_index]); - goto exit_conflict; - } - } - } - } - - /* Check IR port */ - bRC = smapi_request(0x1700, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - bRC = smapi_request(0x1704, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - /* bRC == 0 */ - if ((usCX & 0xff) != 0xff) { /* IR port not disabled */ - if ((usCX & 0xff) == mwave_uart_irq) { - pr_err("%s: IR port irq %x conflicts with mwave_uart_irq %x\n", - __func__, usCX & 0xff, mwave_uart_irq); - goto exit_conflict; - } else { - if ((usSI & 0xff) == uartio_index) { - pr_err("%s: IR port base I/O address %x conflicts with mwave uart I/O %x\n", - __func__, ausUartBases[usSI & 0xff], - ausUartBases[uartio_index]); - goto exit_conflict; - } - } - } - } - - bRC = smapi_request(0x1802, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - - if (mwave_3780i_io) { - usDI = dspio_index; - } - if (mwave_3780i_irq) { - usSI = (usSI & 0xff00) | mwave_3780i_irq; - } - - bRC = smapi_request(0x1803, 0x0101, usDI, usSI, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - - bRC = smapi_request(0x1804, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - - if (mwave_uart_io) { - usSI = (usSI & 0x00ff) | (uartio_index << 8); - } - if (mwave_uart_irq) { - usSI = (usSI & 0xff00) | mwave_uart_irq; - } - bRC = smapi_request(0x1805, 0x0101, 0, usSI, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - - bRC = smapi_request(0x1802, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - - bRC = smapi_request(0x1804, 0x0000, 0, 0, - &usAX, &usBX, &usCX, &usDX, &usDI, &usSI); - if (bRC) goto exit_smapi_request_error; - -/* normal exit: */ - return 0; - -exit_conflict: - /* Message has already been printed */ - return -EIO; - -exit_smapi_request_error: - pr_err("%s: exit on smapi_request error bRC %x\n", __func__, bRC); - return bRC; -} - - -int smapi_set_DSP_power_state(bool bOn) -{ - unsigned short usAX, usBX, usCX, usDX, usDI, usSI; - unsigned short usPowerFunction; - - usPowerFunction = (bOn) ? 1 : 0; - - return smapi_request(0x4901, 0x0000, 0, usPowerFunction, &usAX, &usBX, &usCX, &usDX, &usDI, - &usSI); -} - -int smapi_init(void) -{ - int retval = -EIO; - unsigned short usSmapiID = 0; - unsigned long flags; - - spin_lock_irqsave(&rtc_lock, flags); - usSmapiID = CMOS_READ(0x7C); - usSmapiID |= (CMOS_READ(0x7D) << 8); - spin_unlock_irqrestore(&rtc_lock, flags); - - if (usSmapiID == 0x5349) { - spin_lock_irqsave(&rtc_lock, flags); - g_usSmapiPort = CMOS_READ(0x7E); - g_usSmapiPort |= (CMOS_READ(0x7F) << 8); - spin_unlock_irqrestore(&rtc_lock, flags); - if (g_usSmapiPort == 0) { - pr_err("%s: ERROR unable to read from SMAPI port\n", __func__); - } else { - retval = 0; - //SmapiQuerySystemID(); - } - } else { - pr_err("%s: ERROR invalid usSmapiID\n", __func__); - retval = -ENXIO; - } - - return retval; -} diff --git a/drivers/char/mwave/smapi.h b/drivers/char/mwave/smapi.h deleted file mode 100644 index e605b16ed23c..000000000000 --- a/drivers/char/mwave/smapi.h +++ /dev/null @@ -1,76 +0,0 @@ -/* -* -* smapi.h -- declarations for SMAPI interface routines -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#ifndef _LINUX_SMAPI_H -#define _LINUX_SMAPI_H - -struct smapi_dsp_settings { - int bDSPPresent; - int bDSPEnabled; - int bModemEnabled; - int bMIDIEnabled; - int bSblstEnabled; - unsigned short usDspIRQ; - unsigned short usDspDMA; - unsigned short usDspBaseIO; - unsigned short usUartIRQ; - unsigned short usUartBaseIO; - unsigned short usMidiIRQ; - unsigned short usMidiBaseIO; - unsigned short usSndblstIRQ; - unsigned short usSndblstDMA; - unsigned short usSndblstBaseIO; -}; - -int smapi_init(void); -int smapi_query_DSP_cfg(struct smapi_dsp_settings *pSettings); -int smapi_set_DSP_cfg(void); -int smapi_set_DSP_power_state(bool bOn); - - -#endif diff --git a/drivers/char/mwave/tp3780i.c b/drivers/char/mwave/tp3780i.c deleted file mode 100644 index 7363b0f764e0..000000000000 --- a/drivers/char/mwave/tp3780i.c +++ /dev/null @@ -1,477 +0,0 @@ -/* -* -* tp3780i.c -- board driver for 3780i on ThinkPads -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#define pr_fmt(fmt) "tp3780i: " fmt - -#include <linux/interrupt.h> -#include <linux/kernel.h> -#include <linux/ptrace.h> -#include <linux/ioport.h> -#include <asm/io.h> -#include "smapi.h" -#include "mwavedd.h" -#include "tp3780i.h" -#include "3780i.h" -#include "mwavepub.h" - -static unsigned short s_ausThinkpadIrqToField[16] = - { 0xFFFF, 0xFFFF, 0xFFFF, 0x0001, 0x0002, 0x0003, 0xFFFF, 0x0004, - 0xFFFF, 0xFFFF, 0x0005, 0x0006, 0xFFFF, 0xFFFF, 0xFFFF, 0x0007 }; -static unsigned short s_ausThinkpadDmaToField[8] = - { 0x0001, 0x0002, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0003, 0x0004 }; -static unsigned short s_numIrqs = 16, s_numDmas = 8; - - -static void EnableSRAM(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - DSP_GPIO_OUTPUT_DATA_15_8 rGpioOutputData; - DSP_GPIO_DRIVER_ENABLE_15_8 rGpioDriverEnable; - DSP_GPIO_MODE_15_8 rGpioMode; - - MKWORD(rGpioMode) = ReadMsaCfg(DSP_GpioModeControl_15_8); - rGpioMode.GpioMode10 = 0; - WriteMsaCfg(DSP_GpioModeControl_15_8, MKWORD(rGpioMode)); - - MKWORD(rGpioDriverEnable) = 0; - rGpioDriverEnable.Enable10 = true; - rGpioDriverEnable.Mask10 = true; - WriteMsaCfg(DSP_GpioDriverEnable_15_8, MKWORD(rGpioDriverEnable)); - - MKWORD(rGpioOutputData) = 0; - rGpioOutputData.Latch10 = 0; - rGpioOutputData.Mask10 = true; - WriteMsaCfg(DSP_GpioOutputData_15_8, MKWORD(rGpioOutputData)); -} - - -static irqreturn_t UartInterrupt(int irq, void *dev_id) -{ - return IRQ_HANDLED; -} - -static irqreturn_t DspInterrupt(int irq, void *dev_id) -{ - struct mwave_device_data *pDrvData = &mwave_s_mdd; - struct dsp_3780i_config_settings *pSettings = &pDrvData->rBDData.rDspSettings; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - unsigned short usIPCSource = 0, usIsolationMask, usPCNum; - - if (dsp3780I_GetIPCSource(usDspBaseIO, &usIPCSource) == 0) { - usIsolationMask = 1; - for (usPCNum = 1; usPCNum <= 16; usPCNum++) { - if (usIPCSource & usIsolationMask) { - usIPCSource &= ~usIsolationMask; - if (pDrvData->IPCs[usPCNum - 1].usIntCount == 0) { - pDrvData->IPCs[usPCNum - 1].usIntCount = 1; - } - if (pDrvData->IPCs[usPCNum - 1].bIsEnabled == true) { - wake_up_interruptible(&pDrvData->IPCs[usPCNum - 1].ipc_wait_queue); - } - } - if (usIPCSource == 0) - break; - /* try next IPC */ - usIsolationMask = usIsolationMask << 1; - } - } - return IRQ_HANDLED; -} - - -int tp3780I_InitializeBoardData(struct thinkpad_bd_data *pBDData) -{ - int retval = 0; - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - pBDData->bDSPEnabled = false; - pSettings->bInterruptClaimed = false; - - retval = smapi_init(); - if (retval) { - pr_err("%s: Error: SMAPI is not available on this machine\n", __func__); - } else { - if (mwave_3780i_irq || mwave_3780i_io || mwave_uart_irq || mwave_uart_io) { - retval = smapi_set_DSP_cfg(); - } - } - - return retval; -} - -void tp3780I_Cleanup(struct thinkpad_bd_data *pBDData) -{ -} - -int tp3780I_CalcResources(struct thinkpad_bd_data *pBDData) -{ - struct smapi_dsp_settings rSmapiInfo; - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - if (smapi_query_DSP_cfg(&rSmapiInfo)) { - pr_err("%s: Error: Could not query DSP config. Aborting.\n", __func__); - return -EIO; - } - - /* Sanity check */ - if ( - ( rSmapiInfo.usDspIRQ == 0 ) - || ( rSmapiInfo.usDspBaseIO == 0 ) - || ( rSmapiInfo.usUartIRQ == 0 ) - || ( rSmapiInfo.usUartBaseIO == 0 ) - ) { - pr_err("%s: Error: Illegal resource setting. Aborting.\n", __func__); - return -EIO; - } - - pSettings->bDSPEnabled = (rSmapiInfo.bDSPEnabled && rSmapiInfo.bDSPPresent); - pSettings->bModemEnabled = rSmapiInfo.bModemEnabled; - pSettings->usDspIrq = rSmapiInfo.usDspIRQ; - pSettings->usDspDma = rSmapiInfo.usDspDMA; - pSettings->usDspBaseIO = rSmapiInfo.usDspBaseIO; - pSettings->usUartIrq = rSmapiInfo.usUartIRQ; - pSettings->usUartBaseIO = rSmapiInfo.usUartBaseIO; - - pSettings->uDStoreSize = TP_ABILITIES_DATA_SIZE; - pSettings->uIStoreSize = TP_ABILITIES_INST_SIZE; - pSettings->uIps = TP_ABILITIES_INTS_PER_SEC; - - if (pSettings->bDSPEnabled && pSettings->bModemEnabled && pSettings->usDspIrq == pSettings->usUartIrq) { - pBDData->bShareDspIrq = pBDData->bShareUartIrq = 1; - } else { - pBDData->bShareDspIrq = pBDData->bShareUartIrq = 0; - } - - return 0; -} - - -int tp3780I_ClaimResources(struct thinkpad_bd_data *pBDData) -{ - int retval = 0; - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - struct resource *pres; - - pres = request_region(pSettings->usDspBaseIO, 16, "mwave_3780i"); - if ( pres == NULL ) retval = -EIO; - - if (retval) { - pr_err("%s: Error: Could not claim I/O region starting at %x\n", __func__, - pSettings->usDspBaseIO); - return -EIO; - } - - return retval; -} - -int tp3780I_ReleaseResources(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - release_region(pSettings->usDspBaseIO & (~3), 16); - - if (pSettings->bInterruptClaimed) { - free_irq(pSettings->usDspIrq, NULL); - pSettings->bInterruptClaimed = false; - } - - return 0; -} - - - -int tp3780I_EnableDSP(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - bool bDSPPoweredUp = false, bInterruptAllocated = false; - - if (pBDData->bDSPEnabled) { - pr_err("%s: Error: DSP already enabled!\n", __func__); - goto exit_cleanup; - } - - if (!pSettings->bDSPEnabled) { - pr_err("%s: Error: pSettings->bDSPEnabled not set\n", __func__); - goto exit_cleanup; - } - - if ( - (pSettings->usDspIrq >= s_numIrqs) - || (pSettings->usDspDma >= s_numDmas) - || (s_ausThinkpadIrqToField[pSettings->usDspIrq] == 0xFFFF) - || (s_ausThinkpadDmaToField[pSettings->usDspDma] == 0xFFFF) - ) { - pr_err("%s: Error: invalid irq %x\n", __func__, pSettings->usDspIrq); - goto exit_cleanup; - } - - if ( - ((pSettings->usDspBaseIO & 0xF00F) != 0) - || (pSettings->usDspBaseIO & 0x0FF0) == 0 - ) { - pr_err("%s: Error: Invalid DSP base I/O address %x\n", __func__, - pSettings->usDspBaseIO); - goto exit_cleanup; - } - - if (pSettings->bModemEnabled) { - if ( - pSettings->usUartIrq >= s_numIrqs - || s_ausThinkpadIrqToField[pSettings->usUartIrq] == 0xFFFF - ) { - pr_err("%s: Error: Invalid UART IRQ %x\n", __func__, pSettings->usUartIrq); - goto exit_cleanup; - } - switch (pSettings->usUartBaseIO) { - case 0x03F8: - case 0x02F8: - case 0x03E8: - case 0x02E8: - break; - - default: - pr_err("%s: Error: Invalid UART base I/O address %x\n", __func__, - pSettings->usUartBaseIO); - goto exit_cleanup; - } - } - - pSettings->bDspIrqActiveLow = pSettings->bDspIrqPulse = true; - pSettings->bUartIrqActiveLow = pSettings->bUartIrqPulse = true; - - if (pBDData->bShareDspIrq) { - pSettings->bDspIrqActiveLow = false; - } - if (pBDData->bShareUartIrq) { - pSettings->bUartIrqActiveLow = false; - } - - pSettings->usNumTransfers = TP_CFG_NumTransfers; - pSettings->usReRequest = TP_CFG_RerequestTimer; - pSettings->bEnableMEMCS16 = TP_CFG_MEMCS16; - pSettings->usIsaMemCmdWidth = TP_CFG_IsaMemCmdWidth; - pSettings->bGateIOCHRDY = TP_CFG_GateIOCHRDY; - pSettings->bEnablePwrMgmt = TP_CFG_EnablePwrMgmt; - pSettings->usHBusTimerLoadValue = TP_CFG_HBusTimerValue; - pSettings->bDisableLBusTimeout = TP_CFG_DisableLBusTimeout; - pSettings->usN_Divisor = TP_CFG_N_Divisor; - pSettings->usM_Multiplier = TP_CFG_M_Multiplier; - pSettings->bPllBypass = TP_CFG_PllBypass; - pSettings->usChipletEnable = TP_CFG_ChipletEnable; - - if (request_irq(pSettings->usUartIrq, &UartInterrupt, 0, "mwave_uart", NULL)) { - pr_err("%s: Error: Could not get UART IRQ %x\n", __func__, pSettings->usUartIrq); - goto exit_cleanup; - } else { /* no conflict just release */ - free_irq(pSettings->usUartIrq, NULL); - } - - if (request_irq(pSettings->usDspIrq, &DspInterrupt, 0, "mwave_3780i", NULL)) { - pr_err("%s: Error: Could not get 3780i IRQ %x\n", __func__, pSettings->usDspIrq); - goto exit_cleanup; - } else { - bInterruptAllocated = true; - pSettings->bInterruptClaimed = true; - } - - smapi_set_DSP_power_state(false); - if (smapi_set_DSP_power_state(true)) { - pr_err("%s: Error: smapi_set_DSP_power_state(true) failed\n", __func__); - goto exit_cleanup; - } else { - bDSPPoweredUp = true; - } - - if (dsp3780I_EnableDSP(pSettings, s_ausThinkpadIrqToField, s_ausThinkpadDmaToField)) { - pr_err("%s: Error: dsp7880I_EnableDSP() failed\n", __func__); - goto exit_cleanup; - } - - EnableSRAM(pBDData); - - pBDData->bDSPEnabled = true; - - return 0; - -exit_cleanup: - pr_err("%s: Cleaning up\n", __func__); - if (bDSPPoweredUp) - smapi_set_DSP_power_state(false); - if (bInterruptAllocated) { - free_irq(pSettings->usDspIrq, NULL); - pSettings->bInterruptClaimed = false; - } - return -EIO; -} - - -int tp3780I_DisableDSP(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - if (pBDData->bDSPEnabled) { - dsp3780I_DisableDSP(&pBDData->rDspSettings); - if (pSettings->bInterruptClaimed) { - free_irq(pSettings->usDspIrq, NULL); - pSettings->bInterruptClaimed = false; - } - smapi_set_DSP_power_state(false); - pBDData->bDSPEnabled = false; - } - - return 0; -} - - -int tp3780I_ResetDSP(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - if (dsp3780I_Reset(pSettings) == 0) { - EnableSRAM(pBDData); - return 0; - } - return -EIO; -} - - -int tp3780I_StartDSP(struct thinkpad_bd_data *pBDData) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - - if (dsp3780I_Run(pSettings) == 0) { - // @BUG @TBD EnableSRAM(pBDData); - } else { - return -EIO; - } - - return 0; -} - - -int tp3780I_QueryAbilities(struct thinkpad_bd_data *pBDData, struct mw_abilities *pAbilities) -{ - memset(pAbilities, 0, sizeof(*pAbilities)); - /* fill out standard constant fields */ - pAbilities->instr_per_sec = pBDData->rDspSettings.uIps; - pAbilities->data_size = pBDData->rDspSettings.uDStoreSize; - pAbilities->inst_size = pBDData->rDspSettings.uIStoreSize; - pAbilities->bus_dma_bw = pBDData->rDspSettings.uDmaBandwidth; - - /* fill out dynamically determined fields */ - pAbilities->component_list[0] = 0x00010000 | MW_ADC_MASK; - pAbilities->component_list[1] = 0x00010000 | MW_ACI_MASK; - pAbilities->component_list[2] = 0x00010000 | MW_AIC1_MASK; - pAbilities->component_list[3] = 0x00010000 | MW_AIC2_MASK; - pAbilities->component_list[4] = 0x00010000 | MW_CDDAC_MASK; - pAbilities->component_list[5] = 0x00010000 | MW_MIDI_MASK; - pAbilities->component_list[6] = 0x00010000 | MW_UART_MASK; - pAbilities->component_count = 7; - - /* Fill out Mwave OS and BIOS task names */ - - memcpy(pAbilities->mwave_os_name, TP_ABILITIES_MWAVEOS_NAME, - sizeof(TP_ABILITIES_MWAVEOS_NAME)); - memcpy(pAbilities->bios_task_name, TP_ABILITIES_BIOSTASK_NAME, - sizeof(TP_ABILITIES_BIOSTASK_NAME)); - - return 0; -} - -int tp3780I_ReadWriteDspDStore(struct thinkpad_bd_data *pBDData, unsigned int uOpcode, - void __user *pvBuffer, unsigned int uCount, - unsigned long ulDSPAddr) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - bool bRC = 0; - - if (pBDData->bDSPEnabled) { - switch (uOpcode) { - case IOCTL_MW_READ_DATA: - bRC = dsp3780I_ReadDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr); - break; - - case IOCTL_MW_READCLEAR_DATA: - bRC = dsp3780I_ReadAndClearDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr); - break; - - case IOCTL_MW_WRITE_DATA: - bRC = dsp3780I_WriteDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr); - break; - } - } - - return bRC ? -EIO : 0; -} - - -int tp3780I_ReadWriteDspIStore(struct thinkpad_bd_data *pBDData, unsigned int uOpcode, - void __user *pvBuffer, unsigned int uCount, - unsigned long ulDSPAddr) -{ - struct dsp_3780i_config_settings *pSettings = &pBDData->rDspSettings; - unsigned short usDspBaseIO = pSettings->usDspBaseIO; - bool bRC = 0; - - if (pBDData->bDSPEnabled) { - switch (uOpcode) { - case IOCTL_MW_READ_INST: - bRC = dsp3780I_ReadIStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr); - break; - - case IOCTL_MW_WRITE_INST: - bRC = dsp3780I_WriteIStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr); - break; - } - } - - return bRC ? -EIO : 0; -} - diff --git a/drivers/char/mwave/tp3780i.h b/drivers/char/mwave/tp3780i.h deleted file mode 100644 index c0001a344741..000000000000 --- a/drivers/char/mwave/tp3780i.h +++ /dev/null @@ -1,103 +0,0 @@ -/* -* -* tp3780i.h -- declarations for tp3780i.c -* -* -* Written By: Mike Sullivan IBM Corporation -* -* Copyright (C) 1999 IBM Corporation -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* NO WARRANTY -* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR -* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT -* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, -* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is -* solely responsible for determining the appropriateness of using and -* distributing the Program and assumes all risks associated with its -* exercise of rights under this Agreement, including but not limited to -* the risks and costs of program errors, damage to or loss of data, -* programs or equipment, and unavailability or interruption of operations. -* -* DISCLAIMER OF LIABILITY -* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY -* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -* -* -* 10/23/2000 - Alpha Release -* First release to the public -*/ - -#ifndef _LINUX_TP3780I_H -#define _LINUX_TP3780I_H - -#include <asm/io.h> -#include "mwavepub.h" - - -/* DSP abilities constants for 3780i based Thinkpads */ -#define TP_ABILITIES_INTS_PER_SEC 39160800 -#define TP_ABILITIES_DATA_SIZE 32768 -#define TP_ABILITIES_INST_SIZE 32768 -#define TP_ABILITIES_MWAVEOS_NAME "mwaveos0700.dsp" -#define TP_ABILITIES_BIOSTASK_NAME "mwbio701.dsp" - - -/* DSP configuration values for 3780i based Thinkpads */ -#define TP_CFG_NumTransfers 3 /* 16 transfers */ -#define TP_CFG_RerequestTimer 1 /* 2 usec */ -#define TP_CFG_MEMCS16 0 /* Disabled, 16-bit memory assumed */ -#define TP_CFG_IsaMemCmdWidth 3 /* 295 nsec (16-bit) */ -#define TP_CFG_GateIOCHRDY 0 /* No IOCHRDY gating */ -#define TP_CFG_EnablePwrMgmt 1 /* Enable low poser suspend/resume */ -#define TP_CFG_HBusTimerValue 255 /* HBus timer load value */ -#define TP_CFG_DisableLBusTimeout 0 /* Enable LBus timeout */ -#define TP_CFG_N_Divisor 32 /* Clock = 39.1608 Mhz */ -#define TP_CFG_M_Multiplier 37 /* " */ -#define TP_CFG_PllBypass 0 /* don't bypass */ -#define TP_CFG_ChipletEnable 0xFFFF /* Enable all chiplets */ - -struct thinkpad_bd_data { - int bDSPEnabled; - int bShareDspIrq; - int bShareUartIrq; - struct dsp_3780i_config_settings rDspSettings; -}; - -int tp3780I_InitializeBoardData(struct thinkpad_bd_data *pBDData); -int tp3780I_CalcResources(struct thinkpad_bd_data *pBDData); -int tp3780I_ClaimResources(struct thinkpad_bd_data *pBDData); -int tp3780I_ReleaseResources(struct thinkpad_bd_data *pBDData); -int tp3780I_EnableDSP(struct thinkpad_bd_data *pBDData); -int tp3780I_DisableDSP(struct thinkpad_bd_data *pBDData); -int tp3780I_ResetDSP(struct thinkpad_bd_data *pBDData); -int tp3780I_StartDSP(struct thinkpad_bd_data *pBDData); -int tp3780I_QueryAbilities(struct thinkpad_bd_data *pBDData, struct mw_abilities *pAbilities); -void tp3780I_Cleanup(struct thinkpad_bd_data *pBDData); -int tp3780I_ReadWriteDspDStore(struct thinkpad_bd_data *pBDData, unsigned int uOpcode, - void __user *pvBuffer, unsigned int uCount, - unsigned long ulDSPAddr); -int tp3780I_ReadWriteDspIStore(struct thinkpad_bd_data *pBDData, unsigned int uOpcode, - void __user *pvBuffer, unsigned int uCount, - unsigned long ulDSPAddr); - - -#endif diff --git a/drivers/char/xilinx_hwicap/fifo_icap.c b/drivers/char/xilinx_hwicap/fifo_icap.c index 619f3a30ec55..7bd786fa1be8 100644 --- a/drivers/char/xilinx_hwicap/fifo_icap.c +++ b/drivers/char/xilinx_hwicap/fifo_icap.c @@ -48,7 +48,7 @@ #define XHI_GIER_GIE_MASK 0x80000000 /* Global Interrupt enable Mask */ -/** +/* * HwIcap Device Interrupt Status/Enable Registers * * Interrupt Status Register (IPISR) : This register holds the @@ -102,6 +102,8 @@ static inline void fifo_icap_fifo_write(struct hwicap_drvdata *drvdata, * @drvdata: a pointer to the drvdata. * * This function will silently fail if the fifo is empty. + * + * Returns: 32-bit data from the Read FIFO **/ static inline u32 fifo_icap_fifo_read(struct hwicap_drvdata *drvdata) { @@ -156,6 +158,8 @@ static inline void fifo_icap_start_readback(struct hwicap_drvdata *drvdata) * D2 - Always 1 * D1 - Always 1 * D0 - Done bit + * + * Returns: the 32-bit ICAP status register **/ u32 fifo_icap_get_status(struct hwicap_drvdata *drvdata) { @@ -165,8 +169,11 @@ u32 fifo_icap_get_status(struct hwicap_drvdata *drvdata) } /** - * fifo_icap_busy - Return true if the ICAP is still processing a transaction. + * fifo_icap_busy - Check the ICAP busy status. * @drvdata: a pointer to the drvdata. + * + * Returns: %true if the ICAP is still processing a transaction, + * otherwise %false **/ static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) { @@ -178,7 +185,7 @@ static inline u32 fifo_icap_busy(struct hwicap_drvdata *drvdata) * fifo_icap_write_fifo_vacancy - Query the write fifo available space. * @drvdata: a pointer to the drvdata. * - * Return the number of words that can be safely pushed into the write fifo. + * Returns: the number of words that can be safely pushed into the write fifo. **/ static inline u32 fifo_icap_write_fifo_vacancy( struct hwicap_drvdata *drvdata) @@ -190,7 +197,7 @@ static inline u32 fifo_icap_write_fifo_vacancy( * fifo_icap_read_fifo_occupancy - Query the read fifo available data. * @drvdata: a pointer to the drvdata. * - * Return the number of words that can be safely read from the read fifo. + * Returns: the number of words that can be safely read from the read fifo. **/ static inline u32 fifo_icap_read_fifo_occupancy( struct hwicap_drvdata *drvdata) @@ -205,10 +212,12 @@ static inline u32 fifo_icap_read_fifo_occupancy( * ICAP device. * @num_words: the number of words (32 bit) to write to the ICAP * device. - + * * This function writes the given user data to the Write FIFO in * polled mode and starts the transfer of the data to * the ICAP device. + * + * Returns: %0 on success or %-errno on failure. **/ int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, u32 *frame_buffer, u32 num_words) @@ -280,11 +289,13 @@ int fifo_icap_set_configuration(struct hwicap_drvdata *drvdata, /** * fifo_icap_get_configuration - Read configuration data from the device. * @drvdata: a pointer to the drvdata. - * @data: Address of the data representing the partial bitstream - * @size: the size of the partial bitstream in 32 bit words. + * @frame_buffer: Address of the data representing the partial bitstream + * @num_words: the size of the partial bitstream in 32 bit words. * * This function reads the specified number of words from the ICAP device in * the polled mode. + * + * Returns: %0 on success or %-errno on failure. */ int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, u32 *frame_buffer, u32 num_words) @@ -347,7 +358,7 @@ int fifo_icap_get_configuration(struct hwicap_drvdata *drvdata, } /** - * buffer_icap_reset - Reset the logic of the icap device. + * fifo_icap_reset - Reset the logic of the icap device. * @drvdata: a pointer to the drvdata. * * This function forces the software reset of the complete HWICAP device. diff --git a/drivers/comedi/comedi_fops.c b/drivers/comedi/comedi_fops.c index 2c3eb9e89571..25aa4296f3b0 100644 --- a/drivers/comedi/comedi_fops.c +++ b/drivers/comedi/comedi_fops.c @@ -1169,6 +1169,8 @@ static int do_chaninfo_ioctl(struct comedi_device *dev, * COMEDI_BUFINFO ioctl * buffer information * + * Note that the comedi device's mutex has not been locked for this ioctl. + * * arg: * pointer to comedi_bufinfo structure * @@ -1186,24 +1188,42 @@ static int do_bufinfo_ioctl(struct comedi_device *dev, struct comedi_async *async; unsigned int runflags; int retval = 0; + unsigned int old_detach_count; + unsigned int cmd_flags; bool become_nonbusy = false; + bool attach_locked; - lockdep_assert_held(&dev->mutex); if (copy_from_user(&bi, arg, sizeof(bi))) return -EFAULT; - if (bi.subdevice >= dev->n_subdevices) - return -EINVAL; + /* Protect against device detachment during operation. */ + down_read(&dev->attach_lock); + attach_locked = true; + old_detach_count = dev->detach_count; + + if (!dev->attached) { + dev_dbg(dev->class_dev, "no driver attached\n"); + retval = -ENODEV; + goto out; + } + + if (bi.subdevice >= dev->n_subdevices) { + retval = -EINVAL; + goto out; + } s = &dev->subdevices[bi.subdevice]; async = s->async; - if (!async || s->busy != file) - return -EINVAL; + if (!async || s->busy != file) { + retval = -EINVAL; + goto out; + } runflags = comedi_get_subdevice_runflags(s); - if (!(async->cmd.flags & CMDF_WRITE)) { + cmd_flags = async->cmd.flags; + if (!(cmd_flags & CMDF_WRITE)) { /* command was set up in "read" direction */ if (bi.bytes_read) { _comedi_buf_read_alloc(s, bi.bytes_read); @@ -1243,8 +1263,41 @@ static int do_bufinfo_ioctl(struct comedi_device *dev, bi.buf_read_count = async->buf_read_count; bi.buf_read_ptr = async->buf_read_ptr; - if (become_nonbusy) - do_become_nonbusy(dev, s); + if (become_nonbusy) { + struct comedi_subdevice *new_s = NULL; + + /* + * To avoid deadlock, cannot acquire dev->mutex + * while dev->attach_lock is held. + */ + up_read(&dev->attach_lock); + attach_locked = false; + mutex_lock(&dev->mutex); + /* + * Check device hasn't become detached behind our back. + * Checking dev->detach_count is unchanged ought to be + * sufficient, but check the subdevice pointer as well, + * and check the subdevice is still in a suitable state + * to become non-busy. It should still be "busy" after + * running an asynchronous commands, which should now have + * stopped, and for a command in the "read" direction, all + * available data should have been read. + */ + if (dev->attached && old_detach_count == dev->detach_count && + bi.subdevice < dev->n_subdevices) + new_s = &dev->subdevices[bi.subdevice]; + if (s == new_s && new_s->async == async && s->busy == file && + async->cmd.flags == cmd_flags && + !comedi_is_subdevice_running(s) && + ((cmd_flags & CMDF_WRITE) != 0 || + _comedi_buf_read_n_available(s) == 0)) + do_become_nonbusy(dev, s); + mutex_unlock(&dev->mutex); + } + +out: + if (attach_locked) + up_read(&dev->attach_lock); if (retval) return retval; @@ -2225,6 +2278,13 @@ static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd, struct comedi_device *dev = cfp->dev; int rc; + /* Handle COMEDI_BUFINFO without locking the mutex first. */ + if (cmd == COMEDI_BUFINFO) { + return do_bufinfo_ioctl(dev, + (struct comedi_bufinfo __user *)arg, + file); + } + mutex_lock(&dev->mutex); /* @@ -2294,11 +2354,6 @@ static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd, rc = do_rangeinfo_ioctl(dev, &it); break; } - case COMEDI_BUFINFO: - rc = do_bufinfo_ioctl(dev, - (struct comedi_bufinfo __user *)arg, - file); - break; case COMEDI_LOCK: rc = do_lock_ioctl(dev, arg, file); break; diff --git a/drivers/comedi/drivers/comedi_test.c b/drivers/comedi/drivers/comedi_test.c index 7984950f0f99..01aafce20ef8 100644 --- a/drivers/comedi/drivers/comedi_test.c +++ b/drivers/comedi/drivers/comedi_test.c @@ -692,6 +692,44 @@ static int waveform_ao_insn_config(struct comedi_device *dev, return -EINVAL; } +static int waveform_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + u32 driven_low; + u16 wires; + + /* Update the channel outputs. */ + comedi_dio_update_state(s, data); + /* + * We are modelling the outputs as NPN open collector (0 = driven low, + * 1 = high impedance), with the lower 16 channels wired to the upper + * 16 channels in pairs (0 to 16, 1 to 17, ..., 15 to 31), with a + * pull-up resistor on each wire. When reading back each channel, we + * read back the state of the wire to which it is connected. + * + * The state of each wire and the value read back from both channels + * connected to it will be logic 1 unless either channel connected to + * the wire is configured as an output in the logic 0 state. + */ + driven_low = s->io_bits & ~s->state; + wires = 0xFFFF & ~driven_low & ~(driven_low >> 16); + /* Read back the state of the wires for each pair of channels. */ + data[1] = wires | (wires << 16); + + return insn->n; +} + +static int waveform_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + /* configure each channel as input or output individually */ + return comedi_dio_insn_config(dev, s, insn, data, 0); +} + static int waveform_common_attach(struct comedi_device *dev, int amplitude, int period) { @@ -707,7 +745,7 @@ static int waveform_common_attach(struct comedi_device *dev, devpriv->wf_amplitude = amplitude; devpriv->wf_period = period; - ret = comedi_alloc_subdevices(dev, 2); + ret = comedi_alloc_subdevices(dev, 3); if (ret) return ret; @@ -746,6 +784,16 @@ static int waveform_common_attach(struct comedi_device *dev, for (i = 0; i < s->n_chan; i++) devpriv->ao_loopbacks[i] = s->maxdata / 2; + s = &dev->subdevices[2]; + /* digital input/output subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = waveform_dio_insn_bits; + s->insn_config = waveform_dio_insn_config; + devpriv->dev = dev; timer_setup(&devpriv->ai_timer, waveform_ai_timer, 0); timer_setup(&devpriv->ao_timer, waveform_ao_timer, 0); diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index 515b948ff320..dbed404a71fc 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -1317,7 +1317,7 @@ int stratix10_svc_async_send(struct stratix10_svc_chan *chan, void *msg, dev_dbg(ctrl->dev, "Async message sent with transaction_id 0x%02x\n", handle->transaction_id); - *handler = handle; + *handler = handle; return 0; case INTEL_SIP_SMC_STATUS_BUSY: dev_warn(ctrl->dev, "Mailbox is busy, try after some time\n"); @@ -1702,12 +1702,12 @@ int stratix10_svc_send(struct stratix10_svc_chan *chan, void *msg) kthread_run_on_cpu(svc_normal_to_secure_thread, (void *)chan->ctrl, cpu, "svc_smc_hvc_thread"); - if (IS_ERR(chan->ctrl->task)) { - dev_err(chan->ctrl->dev, - "failed to create svc_smc_hvc_thread\n"); - kfree(p_data); - return -EINVAL; - } + if (IS_ERR(chan->ctrl->task)) { + dev_err(chan->ctrl->dev, + "failed to create svc_smc_hvc_thread\n"); + kfree(p_data); + return -EINVAL; + } } pr_debug("%s: sent P-va=%p, P-com=%x, P-size=%u\n", __func__, diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c index 7022657243c0..449c3a082e23 100644 --- a/drivers/fpga/dfl.c +++ b/drivers/fpga/dfl.c @@ -2018,7 +2018,7 @@ static void __exit dfl_fpga_exit(void) bus_unregister(&dfl_bus_type); } -module_init(dfl_fpga_init); +subsys_initcall(dfl_fpga_init); module_exit(dfl_fpga_exit); MODULE_DESCRIPTION("FPGA Device Feature List (DFL) Support"); diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h index 95539f1213cb..9549f63b00eb 100644 --- a/drivers/fpga/dfl.h +++ b/drivers/fpga/dfl.h @@ -82,7 +82,7 @@ #define DFH_TYPE_FIU 4 /* - * DFHv1 Register Offset definitons + * DFHv1 Register Offset definitions * In DHFv1, DFH + GUID + CSR_START + CSR_SIZE_GROUP + PARAM_HDR + PARAM_DATA * as common header registers */ diff --git a/drivers/fpga/of-fpga-region.c b/drivers/fpga/of-fpga-region.c index 43db4bb77138..caa091224dc5 100644 --- a/drivers/fpga/of-fpga-region.c +++ b/drivers/fpga/of-fpga-region.c @@ -83,7 +83,7 @@ static struct fpga_manager *of_fpga_region_get_mgr(struct device_node *np) * done with the bridges. * * Return: 0 for success (even if there are no bridges specified) - * or -EBUSY if any of the bridges are in use. + * or an error code if any of the bridges are not available. */ static int of_fpga_region_get_bridges(struct fpga_region *region) { @@ -130,10 +130,10 @@ static int of_fpga_region_get_bridges(struct fpga_region *region) ®ion->bridge_list); of_node_put(br); - /* If any of the bridges are in use, give up */ - if (ret == -EBUSY) { + /* If any of the bridges are not available, give up */ + if (ret) { fpga_bridges_put(®ion->bridge_list); - return -EBUSY; + return ret; } } diff --git a/drivers/fpga/xilinx-pr-decoupler.c b/drivers/fpga/xilinx-pr-decoupler.c index 822751fad18a..6994d68e9036 100644 --- a/drivers/fpga/xilinx-pr-decoupler.c +++ b/drivers/fpga/xilinx-pr-decoupler.c @@ -173,5 +173,5 @@ module_platform_driver(xlnx_pr_decoupler_driver); MODULE_DESCRIPTION("Xilinx Partial Reconfiguration Decoupler"); MODULE_AUTHOR("Moritz Fischer <mdf@kernel.org>"); -MODULE_AUTHOR("Michal Simek <michal.simek@xilinx.com>"); +MODULE_AUTHOR("Michal Simek <michal.simek@amd.com>"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/zynq-fpga.c b/drivers/fpga/zynq-fpga.c index b7629a0e4813..9d1d599ef718 100644 --- a/drivers/fpga/zynq-fpga.c +++ b/drivers/fpga/zynq-fpga.c @@ -652,6 +652,6 @@ static struct platform_driver zynq_fpga_driver = { module_platform_driver(zynq_fpga_driver); MODULE_AUTHOR("Moritz Fischer <moritz.fischer@ettus.com>"); -MODULE_AUTHOR("Michal Simek <michal.simek@xilinx.com>"); +MODULE_AUTHOR("Michal Simek <michal.simek@amd.com>"); MODULE_DESCRIPTION("Xilinx Zynq FPGA Manager"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index c6c115993ebc..83599a1c548b 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -100,6 +100,61 @@ static int fsi_master_write(struct fsi_master *master, int link, uint8_t slave_id, uint32_t addr, const void *val, size_t size); static int fsi_master_break(struct fsi_master *master, int link); +/* FSI core & Linux bus type definitions */ + +static int fsi_bus_match(struct device *dev, const struct device_driver *drv) +{ + struct fsi_device *fsi_dev = to_fsi_dev(dev); + const struct fsi_driver *fsi_drv = to_fsi_drv(drv); + const struct fsi_device_id *id; + + if (!fsi_drv->id_table) + return 0; + + for (id = fsi_drv->id_table; id->engine_type; id++) { + if (id->engine_type != fsi_dev->engine_type) + continue; + if (id->version == FSI_VERSION_ANY || + id->version == fsi_dev->version) { + if (drv->of_match_table) { + if (of_driver_match_device(dev, drv)) + return 1; + } else { + return 1; + } + } + } + + return 0; +} + +static int fsi_probe(struct device *dev) +{ + struct fsi_device *fsidev = to_fsi_dev(dev); + struct fsi_driver *fsidrv = to_fsi_drv(dev->driver); + + if (fsidrv->probe) + return fsidrv->probe(fsidev); + else + return 0; +} + +static void fsi_remove(struct device *dev) +{ + struct fsi_device *fsidev = to_fsi_dev(dev); + struct fsi_driver *fsidrv = to_fsi_drv(dev->driver); + + if (fsidrv->remove) + fsidrv->remove(fsidev); +} + +static const struct bus_type fsi_bus_type = { + .name = "fsi", + .match = fsi_bus_match, + .probe = fsi_probe, + .remove = fsi_remove, +}; + /* * fsi_device_read() / fsi_device_write() / fsi_device_peek() * @@ -1359,32 +1414,23 @@ void fsi_master_unregister(struct fsi_master *master) } EXPORT_SYMBOL_GPL(fsi_master_unregister); -/* FSI core & Linux bus type definitions */ - -static int fsi_bus_match(struct device *dev, const struct device_driver *drv) +static int fsi_legacy_probe(struct fsi_device *fsidev) { - struct fsi_device *fsi_dev = to_fsi_dev(dev); - const struct fsi_driver *fsi_drv = to_fsi_drv(drv); - const struct fsi_device_id *id; + struct device *dev = &fsidev->dev; + struct device_driver *driver = dev->driver; - if (!fsi_drv->id_table) - return 0; + return driver->probe(dev); +} - for (id = fsi_drv->id_table; id->engine_type; id++) { - if (id->engine_type != fsi_dev->engine_type) - continue; - if (id->version == FSI_VERSION_ANY || - id->version == fsi_dev->version) { - if (drv->of_match_table) { - if (of_driver_match_device(dev, drv)) - return 1; - } else { - return 1; - } - } - } +static void fsi_legacy_remove(struct fsi_device *fsidev) +{ + struct device *dev = &fsidev->dev; + struct device_driver *driver = dev->driver; + int ret; - return 0; + ret = driver->remove(dev); + if (unlikely(ret)) + dev_warn(dev, "Ignoring return value of remove callback (%pe)\n", ERR_PTR(ret)); } int fsi_driver_register(struct fsi_driver *fsi_drv) @@ -1394,6 +1440,17 @@ int fsi_driver_register(struct fsi_driver *fsi_drv) if (!fsi_drv->id_table) return -EINVAL; + fsi_drv->drv.bus = &fsi_bus_type; + + /* + * This driver needs updating. Note that driver_register() warns about + * this, so we're not adding another warning here. + */ + if (!fsi_drv->probe && fsi_drv->drv.probe) + fsi_drv->probe = fsi_legacy_probe; + if (!fsi_drv->remove && fsi_drv->drv.remove) + fsi_drv->remove = fsi_legacy_remove; + return driver_register(&fsi_drv->drv); } EXPORT_SYMBOL_GPL(fsi_driver_register); @@ -1404,12 +1461,6 @@ void fsi_driver_unregister(struct fsi_driver *fsi_drv) } EXPORT_SYMBOL_GPL(fsi_driver_unregister); -const struct bus_type fsi_bus_type = { - .name = "fsi", - .match = fsi_bus_match, -}; -EXPORT_SYMBOL_GPL(fsi_bus_type); - static int __init fsi_init(void) { int rc; diff --git a/drivers/fsi/fsi-master-hub.c b/drivers/fsi/fsi-master-hub.c index 6568fed7db3c..a84955bb23b1 100644 --- a/drivers/fsi/fsi-master-hub.c +++ b/drivers/fsi/fsi-master-hub.c @@ -192,9 +192,9 @@ static int hub_master_init(struct fsi_master_hub *hub) return fsi_device_write(dev, FSI_MRESB0, ®, sizeof(reg)); } -static int hub_master_probe(struct device *dev) +static int hub_master_probe(struct fsi_device *fsi_dev) { - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct device *dev = &fsi_dev->dev; struct fsi_master_hub *hub; uint32_t reg, links; __be32 __reg; @@ -235,7 +235,7 @@ static int hub_master_probe(struct device *dev) hub->master.send_break = hub_master_break; hub->master.link_enable = hub_master_link_enable; - dev_set_drvdata(dev, hub); + fsi_set_drvdata(fsi_dev, hub); hub_master_init(hub); @@ -259,9 +259,9 @@ err_release: return rc; } -static int hub_master_remove(struct device *dev) +static void hub_master_remove(struct fsi_device *fsi_dev) { - struct fsi_master_hub *hub = dev_get_drvdata(dev); + struct fsi_master_hub *hub = fsi_get_drvdata(fsi_dev); fsi_master_unregister(&hub->master); fsi_slave_release_range(hub->upstream->slave, hub->addr, hub->size); @@ -272,8 +272,6 @@ static int hub_master_remove(struct device *dev) * the hub */ put_device(&hub->master.dev); - - return 0; } static const struct fsi_device_id hub_master_ids[] = { @@ -286,11 +284,10 @@ static const struct fsi_device_id hub_master_ids[] = { static struct fsi_driver hub_master_driver = { .id_table = hub_master_ids, + .probe = hub_master_probe, + .remove = hub_master_remove, .drv = { .name = "fsi-master-hub", - .bus = &fsi_bus_type, - .probe = hub_master_probe, - .remove = hub_master_remove, } }; diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c index 0a98517f3959..6ca5817910cd 100644 --- a/drivers/fsi/fsi-sbefifo.c +++ b/drivers/fsi/fsi-sbefifo.c @@ -1022,9 +1022,9 @@ static void sbefifo_free(struct device *dev) * Probe/remove */ -static int sbefifo_probe(struct device *dev) +static int sbefifo_probe(struct fsi_device *fsi_dev) { - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct device *dev = &fsi_dev->dev; struct sbefifo *sbefifo; struct device_node *np; struct platform_device *child; @@ -1045,7 +1045,7 @@ static int sbefifo_probe(struct device *dev) sbefifo->magic = SBEFIFO_MAGIC; sbefifo->fsi_dev = fsi_dev; - dev_set_drvdata(dev, sbefifo); + fsi_set_drvdata(fsi_dev, sbefifo); mutex_init(&sbefifo->lock); sbefifo->timeout_in_cmd_ms = SBEFIFO_TIMEOUT_IN_CMD; sbefifo->timeout_start_rsp_ms = SBEFIFO_TIMEOUT_START_RSP; @@ -1101,9 +1101,10 @@ static int sbefifo_unregister_child(struct device *dev, void *data) return 0; } -static int sbefifo_remove(struct device *dev) +static void sbefifo_remove(struct fsi_device *fsi_dev) { - struct sbefifo *sbefifo = dev_get_drvdata(dev); + struct device *dev = &fsi_dev->dev; + struct sbefifo *sbefifo = fsi_get_drvdata(fsi_dev); dev_dbg(dev, "Removing sbefifo device...\n"); @@ -1117,8 +1118,6 @@ static int sbefifo_remove(struct device *dev) fsi_free_minor(sbefifo->dev.devt); device_for_each_child(dev, NULL, sbefifo_unregister_child); put_device(&sbefifo->dev); - - return 0; } static const struct fsi_device_id sbefifo_ids[] = { @@ -1131,26 +1130,14 @@ static const struct fsi_device_id sbefifo_ids[] = { static struct fsi_driver sbefifo_drv = { .id_table = sbefifo_ids, + .probe = sbefifo_probe, + .remove = sbefifo_remove, .drv = { .name = DEVICE_NAME, - .bus = &fsi_bus_type, - .probe = sbefifo_probe, - .remove = sbefifo_remove, } }; -static int sbefifo_init(void) -{ - return fsi_driver_register(&sbefifo_drv); -} - -static void sbefifo_exit(void) -{ - fsi_driver_unregister(&sbefifo_drv); -} - -module_init(sbefifo_init); -module_exit(sbefifo_exit); +module_fsi_driver(sbefifo_drv); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Brad Bishop <bradleyb@fuzziesquirrel.com>"); MODULE_AUTHOR("Eddie James <eajames@linux.vnet.ibm.com>"); diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index 411ddc018cd8..67cd45605fe4 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -527,16 +527,16 @@ static void scom_free(struct device *dev) kfree(scom); } -static int scom_probe(struct device *dev) +static int scom_probe(struct fsi_device *fsi_dev) { - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct device *dev = &fsi_dev->dev; struct scom_device *scom; int rc, didx; scom = kzalloc(sizeof(*scom), GFP_KERNEL); if (!scom) return -ENOMEM; - dev_set_drvdata(dev, scom); + fsi_set_drvdata(fsi_dev, scom); mutex_init(&scom->lock); /* Grab a reference to the device (parent of our cdev), we'll drop it later */ @@ -574,9 +574,9 @@ static int scom_probe(struct device *dev) return rc; } -static int scom_remove(struct device *dev) +static void scom_remove(struct fsi_device *fsi_dev) { - struct scom_device *scom = dev_get_drvdata(dev); + struct scom_device *scom = fsi_get_drvdata(fsi_dev); mutex_lock(&scom->lock); scom->dead = true; @@ -584,8 +584,6 @@ static int scom_remove(struct device *dev) cdev_device_del(&scom->cdev, &scom->dev); fsi_free_minor(scom->dev.devt); put_device(&scom->dev); - - return 0; } static const struct of_device_id scom_of_ids[] = { @@ -604,26 +602,14 @@ static const struct fsi_device_id scom_ids[] = { static struct fsi_driver scom_drv = { .id_table = scom_ids, + .probe = scom_probe, + .remove = scom_remove, .drv = { .name = "scom", - .bus = &fsi_bus_type, .of_match_table = scom_of_ids, - .probe = scom_probe, - .remove = scom_remove, } }; -static int scom_init(void) -{ - return fsi_driver_register(&scom_drv); -} - -static void scom_exit(void) -{ - fsi_driver_unregister(&scom_drv); -} - -module_init(scom_init); -module_exit(scom_exit); +module_fsi_driver(scom_drv); MODULE_DESCRIPTION("SCOM FSI Client device driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/fsi/i2cr-scom.c b/drivers/fsi/i2cr-scom.c index cb7e02213032..3efca2e944bb 100644 --- a/drivers/fsi/i2cr-scom.c +++ b/drivers/fsi/i2cr-scom.c @@ -81,9 +81,9 @@ static const struct file_operations i2cr_scom_fops = { .write = i2cr_scom_write, }; -static int i2cr_scom_probe(struct device *dev) +static int i2cr_scom_probe(struct fsi_device *fsi_dev) { - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct device *dev = &fsi_dev->dev; struct i2cr_scom *scom; int didx; int ret; @@ -115,14 +115,12 @@ static int i2cr_scom_probe(struct device *dev) return ret; } -static int i2cr_scom_remove(struct device *dev) +static void i2cr_scom_remove(struct fsi_device *fsi_dev) { - struct i2cr_scom *scom = dev_get_drvdata(dev); + struct i2cr_scom *scom = dev_get_drvdata(&fsi_dev->dev); cdev_device_del(&scom->cdev, &scom->dev); fsi_free_minor(scom->dev.devt); - - return 0; } static const struct of_device_id i2cr_scom_of_ids[] = { @@ -137,13 +135,12 @@ static const struct fsi_device_id i2cr_scom_ids[] = { }; static struct fsi_driver i2cr_scom_driver = { + .probe = i2cr_scom_probe, + .remove = i2cr_scom_remove, .id_table = i2cr_scom_ids, .drv = { .name = "i2cr_scom", - .bus = &fsi_bus_type, .of_match_table = i2cr_scom_of_ids, - .probe = i2cr_scom_probe, - .remove = i2cr_scom_remove, } }; diff --git a/drivers/gpib/agilent_82350b/agilent_82350b.c b/drivers/gpib/agilent_82350b/agilent_82350b.c index 01a5bb43cd2d..d55d097aa6f0 100644 --- a/drivers/gpib/agilent_82350b/agilent_82350b.c +++ b/drivers/gpib/agilent_82350b/agilent_82350b.c @@ -599,8 +599,9 @@ static int agilent_82350b_generic_attach(struct gpib_board *board, board->status = 0; - if (agilent_82350b_allocate_private(board)) - return -ENOMEM; + retval = agilent_82350b_allocate_private(board); + if (retval) + return retval; a_priv = board->private_data; a_priv->using_fifos = use_fifos; tms_priv = &a_priv->tms9914_priv; diff --git a/drivers/gpib/agilent_82357a/agilent_82357a.c b/drivers/gpib/agilent_82357a/agilent_82357a.c index 77c8e549b208..26d2830c3fa3 100644 --- a/drivers/gpib/agilent_82357a/agilent_82357a.c +++ b/drivers/gpib/agilent_82357a/agilent_82357a.c @@ -1316,7 +1316,7 @@ static int agilent_82357a_attach(struct gpib_board *board, const struct gpib_boa return -ERESTARTSYS; retval = agilent_82357a_allocate_private(board); - if (retval < 0) { + if (retval) { mutex_unlock(&agilent_82357a_hotplug_lock); return retval; } diff --git a/drivers/gpib/cb7210/cb7210.c b/drivers/gpib/cb7210/cb7210.c index 24c61b151071..e9d5fd19b495 100644 --- a/drivers/gpib/cb7210/cb7210.c +++ b/drivers/gpib/cb7210/cb7210.c @@ -856,11 +856,10 @@ static int cb7210_allocate_private(struct gpib_board *board) { struct cb7210_priv *priv; - board->private_data = kmalloc(sizeof(struct cb7210_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct cb7210_priv), GFP_KERNEL); if (!board->private_data) return -ENOMEM; priv = board->private_data; - memset(priv, 0, sizeof(struct cb7210_priv)); init_nec7210_private(&priv->nec7210_priv); return 0; } @@ -876,11 +875,13 @@ static int cb7210_generic_attach(struct gpib_board *board) { struct cb7210_priv *cb_priv; struct nec7210_priv *nec_priv; + int retval; board->status = 0; - if (cb7210_allocate_private(board)) - return -ENOMEM; + retval = cb7210_allocate_private(board); + if (retval) + return retval; cb_priv = board->private_data; nec_priv = &cb_priv->nec7210_priv; nec_priv->read_byte = nec7210_locking_ioport_read_byte; diff --git a/drivers/gpib/cec/cec_gpib.c b/drivers/gpib/cec/cec_gpib.c index dbf9b95baabc..7e57d65d6c32 100644 --- a/drivers/gpib/cec/cec_gpib.c +++ b/drivers/gpib/cec/cec_gpib.c @@ -220,11 +220,10 @@ static int cec_allocate_private(struct gpib_board *board) { struct cec_priv *priv; - board->private_data = kmalloc(sizeof(struct cec_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct cec_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; priv = board->private_data; - memset(priv, 0, sizeof(struct cec_priv)); init_nec7210_private(&priv->nec7210_priv); return 0; } @@ -239,11 +238,13 @@ static int cec_generic_attach(struct gpib_board *board) { struct cec_priv *cec_priv; struct nec7210_priv *nec_priv; + int retval; board->status = 0; - if (cec_allocate_private(board)) - return -ENOMEM; + retval = cec_allocate_private(board); + if (retval) + return retval; cec_priv = board->private_data; nec_priv = &cec_priv->nec7210_priv; nec_priv->read_byte = nec7210_ioport_read_byte; diff --git a/drivers/gpib/common/iblib.c b/drivers/gpib/common/iblib.c index 7cbb6a467177..b672dd6aad25 100644 --- a/drivers/gpib/common/iblib.c +++ b/drivers/gpib/common/iblib.c @@ -227,11 +227,10 @@ int ibonline(struct gpib_board *board) #ifndef CONFIG_NIOS2 board->autospoll_task = kthread_run(&autospoll_thread, board, "gpib%d_autospoll_kthread", board->minor); - retval = IS_ERR(board->autospoll_task); - if (retval) { + if (IS_ERR(board->autospoll_task)) { dev_err(board->gpib_dev, "failed to create autospoll thread\n"); board->interface->detach(board); - return retval; + return PTR_ERR(board->autospoll_task); } #endif board->online = 1; diff --git a/drivers/gpib/eastwood/fluke_gpib.c b/drivers/gpib/eastwood/fluke_gpib.c index 3ae848e3f738..56153b8a1cb2 100644 --- a/drivers/gpib/eastwood/fluke_gpib.c +++ b/drivers/gpib/eastwood/fluke_gpib.c @@ -853,11 +853,10 @@ static int fluke_allocate_private(struct gpib_board *board) { struct fluke_priv *priv; - board->private_data = kmalloc(sizeof(struct fluke_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct fluke_priv), GFP_KERNEL); if (!board->private_data) return -ENOMEM; priv = board->private_data; - memset(priv, 0, sizeof(struct fluke_priv)); init_nec7210_private(&priv->nec7210_priv); priv->dma_buffer_size = 0x7ff; priv->dma_buffer = kmalloc(priv->dma_buffer_size, GFP_KERNEL); @@ -887,7 +886,7 @@ static int fluke_generic_attach(struct gpib_board *board) board->status = 0; retval = fluke_allocate_private(board); - if (retval < 0) + if (retval) return retval; e_priv = board->private_data; nec_priv = &e_priv->nec7210_priv; diff --git a/drivers/gpib/fmh_gpib/fmh_gpib.c b/drivers/gpib/fmh_gpib/fmh_gpib.c index f7bfb4a8e553..d82ec06b3085 100644 --- a/drivers/gpib/fmh_gpib/fmh_gpib.c +++ b/drivers/gpib/fmh_gpib/fmh_gpib.c @@ -1250,11 +1250,10 @@ static int fmh_gpib_allocate_private(struct gpib_board *board) { struct fmh_priv *priv; - board->private_data = kmalloc(sizeof(struct fmh_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct fmh_priv), GFP_KERNEL); if (!board->private_data) return -ENOMEM; priv = board->private_data; - memset(priv, 0, sizeof(struct fmh_priv)); init_nec7210_private(&priv->nec7210_priv); priv->dma_buffer_size = 0x800; priv->dma_buffer = kmalloc(priv->dma_buffer_size, GFP_KERNEL); @@ -1286,7 +1285,7 @@ static int fmh_gpib_generic_attach(struct gpib_board *board) board->status = 0; retval = fmh_gpib_allocate_private(board); - if (retval < 0) + if (retval) return retval; e_priv = board->private_data; nec_priv = &e_priv->nec7210_priv; diff --git a/drivers/gpib/gpio/gpib_bitbang.c b/drivers/gpib/gpio/gpib_bitbang.c index 374cd61355e9..ba99d6c95ddf 100644 --- a/drivers/gpib/gpio/gpib_bitbang.c +++ b/drivers/gpib/gpio/gpib_bitbang.c @@ -1068,7 +1068,7 @@ static int allocate_private(struct gpib_board *board) { board->private_data = kzalloc(sizeof(struct bb_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; return 0; } @@ -1205,14 +1205,15 @@ static void bb_detach(struct gpib_board *board) static int bb_attach(struct gpib_board *board, const struct gpib_board_config *config) { struct bb_priv *priv; - int retval = 0; + int retval; dbg_printk(2, "%s\n", "Enter ..."); board->status = 0; - if (allocate_private(board)) - return -ENOMEM; + retval = allocate_private(board); + if (retval) + return retval; priv = board->private_data; priv->direction = -1; priv->t1_delay = 2000; diff --git a/drivers/gpib/hp_82335/hp82335.c b/drivers/gpib/hp_82335/hp82335.c index d0e47ef77c87..9baf1d3b6751 100644 --- a/drivers/gpib/hp_82335/hp82335.c +++ b/drivers/gpib/hp_82335/hp82335.c @@ -212,7 +212,7 @@ static int hp82335_allocate_private(struct gpib_board *board) { board->private_data = kzalloc(sizeof(struct hp82335_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; return 0; } @@ -253,8 +253,9 @@ static int hp82335_attach(struct gpib_board *board, const struct gpib_board_conf board->status = 0; - if (hp82335_allocate_private(board)) - return -ENOMEM; + retval = hp82335_allocate_private(board); + if (retval) + return retval; hp_priv = board->private_data; tms_priv = &hp_priv->tms9914_priv; tms_priv->read_byte = hp82335_read_byte; diff --git a/drivers/gpib/hp_82341/hp_82341.c b/drivers/gpib/hp_82341/hp_82341.c index 1a2ad0560e14..5aaf5b15b98b 100644 --- a/drivers/gpib/hp_82341/hp_82341.c +++ b/drivers/gpib/hp_82341/hp_82341.c @@ -693,8 +693,9 @@ static int hp_82341_attach(struct gpib_board *board, const struct gpib_board_con int retval; board->status = 0; - if (hp_82341_allocate_private(board)) - return -ENOMEM; + retval = hp_82341_allocate_private(board); + if (retval) + return retval; hp_priv = board->private_data; tms_priv = &hp_priv->tms9914_priv; tms_priv->read_byte = hp_82341_read_byte; diff --git a/drivers/gpib/ines/ines_gpib.c b/drivers/gpib/ines/ines_gpib.c index a3cf846fd0f9..737de5fef4b3 100644 --- a/drivers/gpib/ines/ines_gpib.c +++ b/drivers/gpib/ines/ines_gpib.c @@ -657,11 +657,10 @@ static int ines_allocate_private(struct gpib_board *board) { struct ines_priv *priv; - board->private_data = kmalloc(sizeof(struct ines_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct ines_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; priv = board->private_data; - memset(priv, 0, sizeof(struct ines_priv)); init_nec7210_private(&priv->nec7210_priv); return 0; } @@ -676,11 +675,13 @@ static int ines_generic_attach(struct gpib_board *board) { struct ines_priv *ines_priv; struct nec7210_priv *nec_priv; + int retval; board->status = 0; - if (ines_allocate_private(board)) - return -ENOMEM; + retval = ines_allocate_private(board); + if (retval) + return retval; ines_priv = board->private_data; nec_priv = &ines_priv->nec7210_priv; nec_priv->read_byte = nec7210_ioport_read_byte; diff --git a/drivers/gpib/ni_usb/ni_usb_gpib.c b/drivers/gpib/ni_usb/ni_usb_gpib.c index 1f8412de9fa3..c6f1a70d44f5 100644 --- a/drivers/gpib/ni_usb/ni_usb_gpib.c +++ b/drivers/gpib/ni_usb/ni_usb_gpib.c @@ -566,7 +566,7 @@ static int ni_usb_write_registers(struct ni_usb_priv *ni_priv, retval, bytes_read); ni_usb_dump_raw_block(in_data, bytes_read); kfree(in_data); - return retval; + return retval ?: -EINVAL; } mutex_unlock(&ni_priv->addressed_transfer_lock); @@ -1659,11 +1659,10 @@ static int ni_usb_allocate_private(struct gpib_board *board) { struct ni_usb_priv *ni_priv; - board->private_data = kmalloc(sizeof(struct ni_usb_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct ni_usb_priv), GFP_KERNEL); if (!board->private_data) return -ENOMEM; ni_priv = board->private_data; - memset(ni_priv, 0, sizeof(struct ni_usb_priv)); mutex_init(&ni_priv->bulk_transfer_lock); mutex_init(&ni_priv->control_transfer_lock); mutex_init(&ni_priv->interrupt_transfer_lock); @@ -1780,7 +1779,7 @@ static int ni_usb_setup_init(struct gpib_board *board, struct ni_usb_register *w i++; if (i > NUM_INIT_WRITES) { dev_err(&usb_dev->dev, "bug!, buffer overrun, i=%i\n", i); - return 0; + return -EINVAL; } return i; } @@ -1799,10 +1798,12 @@ static int ni_usb_init(struct gpib_board *board) return -ENOMEM; writes_len = ni_usb_setup_init(board, writes); - if (writes_len) - retval = ni_usb_write_registers(ni_priv, writes, writes_len, &ibsta); - else - return -EFAULT; + if (writes_len < 0) { + kfree(writes); + return writes_len; + } + + retval = ni_usb_write_registers(ni_priv, writes, writes_len, &ibsta); kfree(writes); if (retval) { dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); @@ -2233,7 +2234,7 @@ static int ni_usb_attach(struct gpib_board *board, const struct gpib_board_confi mutex_lock(&ni_usb_hotplug_lock); retval = ni_usb_allocate_private(board); - if (retval < 0) { + if (retval) { mutex_unlock(&ni_usb_hotplug_lock); return retval; } diff --git a/drivers/gpib/pc2/pc2_gpib.c b/drivers/gpib/pc2/pc2_gpib.c index 9f3943d1df66..1664861d360d 100644 --- a/drivers/gpib/pc2/pc2_gpib.c +++ b/drivers/gpib/pc2/pc2_gpib.c @@ -237,11 +237,10 @@ static int allocate_private(struct gpib_board *board) { struct pc2_priv *priv; - board->private_data = kmalloc(sizeof(struct pc2_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct pc2_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; priv = board->private_data; - memset(priv, 0, sizeof(struct pc2_priv)); init_nec7210_private(&priv->nec7210_priv); return 0; } @@ -257,10 +256,12 @@ static int pc2_generic_attach(struct gpib_board *board, const struct gpib_board_ { struct pc2_priv *pc2_priv; struct nec7210_priv *nec_priv; + int retval; board->status = 0; - if (allocate_private(board)) - return -ENOMEM; + retval = allocate_private(board); + if (retval) + return retval; pc2_priv = board->private_data; nec_priv = &pc2_priv->nec7210_priv; nec_priv->read_byte = nec7210_ioport_read_byte; diff --git a/drivers/gpib/tnt4882/tnt4882_gpib.c b/drivers/gpib/tnt4882/tnt4882_gpib.c index c03a976b7380..6d241509419e 100644 --- a/drivers/gpib/tnt4882/tnt4882_gpib.c +++ b/drivers/gpib/tnt4882/tnt4882_gpib.c @@ -843,11 +843,10 @@ static int tnt4882_allocate_private(struct gpib_board *board) { struct tnt4882_priv *tnt_priv; - board->private_data = kmalloc(sizeof(struct tnt4882_priv), GFP_KERNEL); + board->private_data = kzalloc(sizeof(struct tnt4882_priv), GFP_KERNEL); if (!board->private_data) - return -1; + return -ENOMEM; tnt_priv = board->private_data; - memset(tnt_priv, 0, sizeof(struct tnt4882_priv)); init_nec7210_private(&tnt_priv->nec7210_priv); return 0; } @@ -916,8 +915,9 @@ static int ni_pci_attach(struct gpib_board *board, const struct gpib_board_confi board->status = 0; - if (tnt4882_allocate_private(board)) - return -ENOMEM; + retval = tnt4882_allocate_private(board); + if (retval) + return retval; tnt_priv = board->private_data; nec_priv = &tnt_priv->nec7210_priv; nec_priv->type = TNT4882; @@ -1039,8 +1039,9 @@ static int ni_isa_attach_common(struct gpib_board *board, const struct gpib_boar board->status = 0; - if (tnt4882_allocate_private(board)) - return -ENOMEM; + retval = tnt4882_allocate_private(board); + if (retval) + return retval; tnt_priv = board->private_data; nec_priv = &tnt_priv->nec7210_priv; nec_priv->type = chipset; @@ -1725,8 +1726,9 @@ static int ni_pcmcia_attach(struct gpib_board *board, const struct gpib_board_co board->status = 0; - if (tnt4882_allocate_private(board)) - return -ENOMEM; + retval = tnt4882_allocate_private(board); + if (retval) + return retval; tnt_priv = board->private_data; nec_priv = &tnt_priv->nec7210_priv; diff --git a/drivers/greybus/core.c b/drivers/greybus/core.c index 313eb65cf703..45c5437c460f 100644 --- a/drivers/greybus/core.c +++ b/drivers/greybus/core.c @@ -185,13 +185,6 @@ static void greybus_shutdown(struct device *dev) } } -const struct bus_type greybus_bus_type = { - .name = "greybus", - .match = greybus_match_device, - .uevent = greybus_uevent, - .shutdown = greybus_shutdown, -}; - static int greybus_probe(struct device *dev) { struct greybus_driver *driver = to_greybus_driver(dev->driver); @@ -252,7 +245,7 @@ static int greybus_probe(struct device *dev) return 0; } -static int greybus_remove(struct device *dev) +static void greybus_remove(struct device *dev) { struct greybus_driver *driver = to_greybus_driver(dev->driver); struct gb_bundle *bundle = to_gb_bundle(dev); @@ -291,10 +284,17 @@ static int greybus_remove(struct device *dev) pm_runtime_set_suspended(dev); pm_runtime_dont_use_autosuspend(dev); pm_runtime_put_noidle(dev); - - return 0; } +const struct bus_type greybus_bus_type = { + .name = "greybus", + .match = greybus_match_device, + .uevent = greybus_uevent, + .probe = greybus_probe, + .remove = greybus_remove, + .shutdown = greybus_shutdown, +}; + int greybus_register_driver(struct greybus_driver *driver, struct module *owner, const char *mod_name) { @@ -305,8 +305,6 @@ int greybus_register_driver(struct greybus_driver *driver, struct module *owner, driver->driver.bus = &greybus_bus_type; driver->driver.name = driver->name; - driver->driver.probe = greybus_probe; - driver->driver.remove = greybus_remove; driver->driver.owner = owner; driver->driver.mod_name = mod_name; diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index 17afa0f4cdee..3c8a6f795094 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -13,6 +13,7 @@ #include <linux/mm.h> #include <linux/init.h> #include <linux/perf_event.h> +#include <linux/perf/arm_pmu.h> #include <linux/percpu-defs.h> #include <linux/slab.h> #include <linux/stringhash.h> @@ -50,26 +51,22 @@ struct etm_ctxt { static DEFINE_PER_CPU(struct etm_ctxt, etm_ctxt); static DEFINE_PER_CPU(struct coresight_device *, csdev_src); -/* - * The PMU formats were orignally for ETMv3.5/PTM's ETMCR 'config'; - * now take them as general formats and apply on all ETMs. - */ -PMU_FORMAT_ATTR(branch_broadcast, "config:"__stringify(ETM_OPT_BRANCH_BROADCAST)); -PMU_FORMAT_ATTR(cycacc, "config:" __stringify(ETM_OPT_CYCACC)); -/* contextid1 enables tracing CONTEXTIDR_EL1 for ETMv4 */ -PMU_FORMAT_ATTR(contextid1, "config:" __stringify(ETM_OPT_CTXTID)); -/* contextid2 enables tracing CONTEXTIDR_EL2 for ETMv4 */ -PMU_FORMAT_ATTR(contextid2, "config:" __stringify(ETM_OPT_CTXTID2)); -PMU_FORMAT_ATTR(timestamp, "config:" __stringify(ETM_OPT_TS)); -PMU_FORMAT_ATTR(retstack, "config:" __stringify(ETM_OPT_RETSTK)); +GEN_PMU_FORMAT_ATTR(cycacc); +GEN_PMU_FORMAT_ATTR(timestamp); +GEN_PMU_FORMAT_ATTR(retstack); +GEN_PMU_FORMAT_ATTR(sinkid); + +#if IS_ENABLED(CONFIG_CORESIGHT_SOURCE_ETM4X) +GEN_PMU_FORMAT_ATTR(branch_broadcast); +/* contextid1 enables tracing CONTEXTIDR_EL1*/ +GEN_PMU_FORMAT_ATTR(contextid1); +/* contextid2 enables tracing CONTEXTIDR_EL2*/ +GEN_PMU_FORMAT_ATTR(contextid2); /* preset - if sink ID is used as a configuration selector */ -PMU_FORMAT_ATTR(preset, "config:0-3"); -/* Sink ID - same for all ETMs */ -PMU_FORMAT_ATTR(sinkid, "config2:0-31"); +GEN_PMU_FORMAT_ATTR(preset); /* config ID - set if a system configuration is selected */ -PMU_FORMAT_ATTR(configid, "config2:32-63"); -PMU_FORMAT_ATTR(cc_threshold, "config3:0-11"); - +GEN_PMU_FORMAT_ATTR(configid); +GEN_PMU_FORMAT_ATTR(cc_threshold); /* * contextid always traces the "PID". The PID is in CONTEXTIDR_EL1 @@ -80,29 +77,35 @@ static ssize_t format_attr_contextid_show(struct device *dev, struct device_attribute *attr, char *page) { - int pid_fmt = ETM_OPT_CTXTID; - -#if IS_ENABLED(CONFIG_CORESIGHT_SOURCE_ETM4X) - pid_fmt = is_kernel_in_hyp_mode() ? ETM_OPT_CTXTID2 : ETM_OPT_CTXTID; -#endif - return sprintf(page, "config:%d\n", pid_fmt); + if (is_kernel_in_hyp_mode()) + return contextid2_show(dev, attr, page); + return contextid1_show(dev, attr, page); } static struct device_attribute format_attr_contextid = __ATTR(contextid, 0444, format_attr_contextid_show, NULL); +#endif +/* + * ETMv3 only uses the first 3 attributes for programming itself (see + * ETM3X_SUPPORTED_OPTIONS). Sink ID is also supported for selecting a + * sink in both, but not used for configuring the ETM. The remaining + * attributes are ETMv4 specific. + */ static struct attribute *etm_config_formats_attr[] = { &format_attr_cycacc.attr, - &format_attr_contextid.attr, - &format_attr_contextid1.attr, - &format_attr_contextid2.attr, &format_attr_timestamp.attr, &format_attr_retstack.attr, &format_attr_sinkid.attr, +#if IS_ENABLED(CONFIG_CORESIGHT_SOURCE_ETM4X) + &format_attr_contextid.attr, + &format_attr_contextid1.attr, + &format_attr_contextid2.attr, &format_attr_preset.attr, &format_attr_configid.attr, &format_attr_branch_broadcast.attr, &format_attr_cc_threshold.attr, +#endif NULL, }; @@ -315,7 +318,7 @@ static bool sinks_compatible(struct coresight_device *a, static void *etm_setup_aux(struct perf_event *event, void **pages, int nr_pages, bool overwrite) { - u32 id, cfg_hash; + u32 sink_hash, cfg_hash; int cpu = event->cpu; cpumask_t *mask; struct coresight_device *sink = NULL; @@ -328,13 +331,12 @@ static void *etm_setup_aux(struct perf_event *event, void **pages, INIT_WORK(&event_data->work, free_event_data); /* First get the selected sink from user space. */ - if (event->attr.config2 & GENMASK_ULL(31, 0)) { - id = (u32)event->attr.config2; - sink = user_sink = coresight_get_sink_by_id(id); - } + sink_hash = ATTR_CFG_GET_FLD(&event->attr, sinkid); + if (sink_hash) + sink = user_sink = coresight_get_sink_by_id(sink_hash); /* check if user wants a coresight configuration selected */ - cfg_hash = (u32)((event->attr.config2 & GENMASK_ULL(63, 32)) >> 32); + cfg_hash = ATTR_CFG_GET_FLD(&event->attr, configid); if (cfg_hash) { if (cscfg_activate_config(cfg_hash)) goto err; diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.h b/drivers/hwtracing/coresight/coresight-etm-perf.h index 5febbcdb8696..24d929428633 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.h +++ b/drivers/hwtracing/coresight/coresight-etm-perf.h @@ -20,6 +20,44 @@ struct cscfg_config_desc; */ #define ETM_ADDR_CMP_MAX 8 +#define ATTR_CFG_FLD_preset_CFG config +#define ATTR_CFG_FLD_preset_LO 0 +#define ATTR_CFG_FLD_preset_HI 3 +#define ATTR_CFG_FLD_timestamp_CFG config +#define ATTR_CFG_FLD_timestamp_LO 4 +#define ATTR_CFG_FLD_timestamp_HI 7 +#define ATTR_CFG_FLD_branch_broadcast_CFG config +#define ATTR_CFG_FLD_branch_broadcast_LO 8 +#define ATTR_CFG_FLD_branch_broadcast_HI 8 +#define ATTR_CFG_FLD_cycacc_CFG config +#define ATTR_CFG_FLD_cycacc_LO 12 +#define ATTR_CFG_FLD_cycacc_HI 12 +#define ATTR_CFG_FLD_contextid1_CFG config +#define ATTR_CFG_FLD_contextid1_LO 14 +#define ATTR_CFG_FLD_contextid1_HI 14 +#define ATTR_CFG_FLD_contextid2_CFG config +#define ATTR_CFG_FLD_contextid2_LO 15 +#define ATTR_CFG_FLD_contextid2_HI 15 +/* + * Old position of 'timestamp' and not published in sysfs. Remove at a later + * date if necessary. + */ +#define ATTR_CFG_FLD_deprecated_timestamp_CFG config +#define ATTR_CFG_FLD_deprecated_timestamp_LO 28 +#define ATTR_CFG_FLD_deprecated_timestamp_HI 28 +#define ATTR_CFG_FLD_retstack_CFG config +#define ATTR_CFG_FLD_retstack_LO 29 +#define ATTR_CFG_FLD_retstack_HI 29 +#define ATTR_CFG_FLD_sinkid_CFG config2 +#define ATTR_CFG_FLD_sinkid_LO 0 +#define ATTR_CFG_FLD_sinkid_HI 31 +#define ATTR_CFG_FLD_configid_CFG config2 +#define ATTR_CFG_FLD_configid_LO 32 +#define ATTR_CFG_FLD_configid_HI 63 +#define ATTR_CFG_FLD_cc_threshold_CFG config3 +#define ATTR_CFG_FLD_cc_threshold_LO 0 +#define ATTR_CFG_FLD_cc_threshold_HI 11 + /** * struct etm_filter - single instruction range or start/stop configuration. * @start_addr: The address to start tracing on. diff --git a/drivers/hwtracing/coresight/coresight-etm3x-core.c b/drivers/hwtracing/coresight/coresight-etm3x-core.c index a5e809589d3e..a547a6d2e0bd 100644 --- a/drivers/hwtracing/coresight/coresight-etm3x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm3x-core.c @@ -28,6 +28,7 @@ #include <linux/uaccess.h> #include <linux/clk.h> #include <linux/perf_event.h> +#include <linux/perf/arm_pmu.h> #include <asm/sections.h> #include "coresight-etm.h" @@ -309,6 +310,7 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata, { struct etm_config *config = &drvdata->config; struct perf_event_attr *attr = &event->attr; + u8 ts_level; if (!attr) return -EINVAL; @@ -332,28 +334,31 @@ static int etm_parse_event_config(struct etm_drvdata *drvdata, if (config->mode) etm_config_trace_mode(config); - /* - * At this time only cycle accurate, return stack and timestamp - * options are available. - */ - if (attr->config & ~ETM3X_SUPPORTED_OPTIONS) - return -EINVAL; + config->ctrl = 0; + + if (ATTR_CFG_GET_FLD(attr, cycacc)) + config->ctrl |= ETMCR_CYC_ACC; - config->ctrl = attr->config; + ts_level = max(ATTR_CFG_GET_FLD(attr, timestamp), + ATTR_CFG_GET_FLD(attr, deprecated_timestamp)); + + if (ts_level > 1) { + dev_dbg(&drvdata->csdev->dev, + "timestamp format attribute should be 0 (off) or 1 (on)\n"); + return -EINVAL; + } - /* Don't trace contextID when runs in non-root PID namespace */ - if (!task_is_in_init_pid_ns(current)) - config->ctrl &= ~ETMCR_CTXID_SIZE; + if (ts_level) + config->ctrl |= ETMCR_TIMESTAMP_EN; /* - * Possible to have cores with PTM (supports ret stack) and ETM - * (never has ret stack) on the same SoC. So if we have a request - * for return stack that can't be honoured on this core then - * clear the bit - trace will still continue normally + * Possible to have cores with PTM (supports ret stack) and ETM (never + * has ret stack) on the same SoC. So only enable when it can be honored + * - trace will still continue normally otherwise. */ - if ((config->ctrl & ETMCR_RETURN_STACK) && - !(drvdata->etmccer & ETMCCER_RETSTACK)) - config->ctrl &= ~ETMCR_RETURN_STACK; + if (ATTR_CFG_GET_FLD(attr, retstack) && + (drvdata->etmccer & ETMCCER_RETSTACK)) + config->ctrl |= ETMCR_RETURN_STACK; return 0; } @@ -795,16 +800,16 @@ static int __init etm_hp_setup(void) { int ret; - ret = cpuhp_setup_state_nocalls_cpuslocked(CPUHP_AP_ARM_CORESIGHT_STARTING, - "arm/coresight:starting", - etm_starting_cpu, etm_dying_cpu); + ret = cpuhp_setup_state_nocalls(CPUHP_AP_ARM_CORESIGHT_STARTING, + "arm/coresight:starting", + etm_starting_cpu, etm_dying_cpu); if (ret) return ret; - ret = cpuhp_setup_state_nocalls_cpuslocked(CPUHP_AP_ONLINE_DYN, - "arm/coresight:online", - etm_online_cpu, NULL); + ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, + "arm/coresight:online", + etm_online_cpu, NULL); /* HP dyn state ID returned in ret on success */ if (ret > 0) { diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c index 560975b70474..d565a73f0042 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c @@ -29,6 +29,7 @@ #include <linux/seq_file.h> #include <linux/uaccess.h> #include <linux/perf_event.h> +#include <linux/perf/arm_pmu.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/property.h> @@ -642,18 +643,34 @@ static void etm4_enable_sysfs_smp_call(void *info) * TRCRSCTLR1 (always true) used to get the counter to decrement. From * there a resource selector is configured with the counter and the * timestamp control register to use the resource selector to trigger the - * event that will insert a timestamp packet in the stream. + * event that will insert a timestamp packet in the stream: + * + * +--------------+ + * | Resource 1 | fixed "always-true" resource + * +--------------+ + * | + * +------v-------+ + * | Counter x | (reload to 2 ^ (ts_level - 1) on underflow) + * +--------------+ + * | + * +------v--------------+ + * | Resource Selector y | (trigger on counter x == 0) + * +---------------------+ + * | + * +------v---------------+ + * | Timestamp Generator | (timestamp on resource y) + * +----------------------+ */ -static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata) +static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata, + u8 ts_level) { - int ctridx, ret = -EINVAL; - int counter, rselector; - u32 val = 0; + int ctridx; + int rselector; struct etmv4_config *config = &drvdata->config; /* No point in trying if we don't have at least one counter */ if (!drvdata->nr_cntr) - goto out; + return -EINVAL; /* Find a counter that hasn't been initialised */ for (ctridx = 0; ctridx < drvdata->nr_cntr; ctridx++) @@ -663,15 +680,19 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata) /* All the counters have been configured already, bail out */ if (ctridx == drvdata->nr_cntr) { pr_debug("%s: no available counter found\n", __func__); - ret = -ENOSPC; - goto out; + return -ENOSPC; } /* - * Searching for an available resource selector to use, starting at - * '2' since every implementation has at least 2 resource selector. - * ETMIDR4 gives the number of resource selector _pairs_, - * hence multiply by 2. + * Searching for an available resource selector to use, starting at '2' + * since resource 0 is the fixed 'always returns false' resource and 1 + * is the fixed 'always returns true' resource. See IHI0064H_b '7.3.64 + * TRCRSCTLRn, Resource Selection Control Registers, n=2-31'. If there + * are no resources, there would also be no counters so wouldn't get + * here. + * + * ETMIDR4 gives the number of resource selector _pairs_, hence multiply + * by 2. */ for (rselector = 2; rselector < drvdata->nr_resource * 2; rselector++) if (!config->res_ctrl[rselector]) @@ -680,40 +701,47 @@ static int etm4_config_timestamp_event(struct etmv4_drvdata *drvdata) if (rselector == drvdata->nr_resource * 2) { pr_debug("%s: no available resource selector found\n", __func__); - ret = -ENOSPC; - goto out; + return -ENOSPC; } - /* Remember what counter we used */ - counter = 1 << ctridx; + /* Initialise original and reload counter value. */ + config->cntr_val[ctridx] = config->cntrldvr[ctridx] = 1 << (ts_level - 1); /* - * Initialise original and reload counter value to the smallest - * possible value in order to get as much precision as we can. + * Trace Counter Control Register TRCCNTCTLRn + * + * CNTCHAIN = 0, don't reload on the previous counter + * RLDSELF = true, reload counter automatically on underflow + * RLDEVENT = RES_SEL_FALSE (0), reload on single false resource (never reload) + * CNTEVENT = RES_SEL_TRUE (1), count single fixed 'always true' resource (always decrement) */ - config->cntr_val[ctridx] = 1; - config->cntrldvr[ctridx] = 1; - - /* Set the trace counter control register */ - val = 0x1 << 16 | /* Bit 16, reload counter automatically */ - 0x0 << 7 | /* Select single resource selector */ - 0x1; /* Resource selector 1, i.e always true */ - - config->cntr_ctrl[ctridx] = val; + config->cntr_ctrl[ctridx] = TRCCNTCTLRn_RLDSELF | + FIELD_PREP(TRCCNTCTLRn_RLDEVENT_MASK, + etm4_res_sel_single(ETM4_RES_SEL_FALSE)) | + FIELD_PREP(TRCCNTCTLRn_CNTEVENT_MASK, + etm4_res_sel_single(ETM4_RES_SEL_TRUE)); - val = 0x2 << 16 | /* Group 0b0010 - Counter and sequencers */ - counter << 0; /* Counter to use */ - - config->res_ctrl[rselector] = val; - - val = 0x0 << 7 | /* Select single resource selector */ - rselector; /* Resource selector */ + /* + * Resource Selection Control Register TRCRSCTLRn + * + * PAIRINV = 0, INV = 0, don't invert + * GROUP = 2, SELECT = ctridx, trigger when counter 'ctridx' reaches 0 + * + * Multiple counters can be selected, and each bit signifies a counter, + * so set bit 'ctridx' to select our counter. + */ + config->res_ctrl[rselector] = FIELD_PREP(TRCRSCTLRn_GROUP_MASK, 2) | + FIELD_PREP(TRCRSCTLRn_SELECT_MASK, 1 << ctridx); - config->ts_ctrl = val; + /* + * Global Timestamp Control Register TRCTSCTLR + * + * EVENT = generate timestamp on single resource 'rselector' + */ + config->ts_ctrl = FIELD_PREP(TRCTSCTLR_EVENT_MASK, + etm4_res_sel_single(rselector)); - ret = 0; -out: - return ret; + return 0; } static int etm4_parse_event_config(struct coresight_device *csdev, @@ -722,9 +750,13 @@ static int etm4_parse_event_config(struct coresight_device *csdev, int ret = 0; struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); struct etmv4_config *config = &drvdata->config; + struct perf_event_attr max_timestamp = { + .ATTR_CFG_FLD_timestamp_CFG = U64_MAX, + }; struct perf_event_attr *attr = &event->attr; unsigned long cfg_hash; int preset, cc_threshold; + u8 ts_level; /* Clear configuration from previous run */ memset(config, 0, sizeof(struct etmv4_config)); @@ -750,47 +782,51 @@ static int etm4_parse_event_config(struct coresight_device *csdev, goto out; /* Go from generic option to ETMv4 specifics */ - if (attr->config & BIT(ETM_OPT_CYCACC)) { + if (ATTR_CFG_GET_FLD(attr, cycacc)) { config->cfg |= TRCCONFIGR_CCI; /* TRM: Must program this for cycacc to work */ - cc_threshold = attr->config3 & ETM_CYC_THRESHOLD_MASK; + cc_threshold = ATTR_CFG_GET_FLD(attr, cc_threshold); if (!cc_threshold) cc_threshold = ETM_CYC_THRESHOLD_DEFAULT; if (cc_threshold < drvdata->ccitmin) cc_threshold = drvdata->ccitmin; config->ccctlr = cc_threshold; } - if (attr->config & BIT(ETM_OPT_TS)) { - /* - * Configure timestamps to be emitted at regular intervals in - * order to correlate instructions executed on different CPUs - * (CPU-wide trace scenarios). - */ - ret = etm4_config_timestamp_event(drvdata); + ts_level = max(ATTR_CFG_GET_FLD(attr, timestamp), + ATTR_CFG_GET_FLD(attr, deprecated_timestamp)); + if (ts_level) { /* - * No need to go further if timestamp intervals can't - * be configured. + * Don't do counter generated timestamps when ts_level == MAX. + * Leave only SYNC timestamps from TRCCONFIGR_TS. */ - if (ret) - goto out; + if (ts_level != ATTR_CFG_GET_FLD(&max_timestamp, timestamp)) { + ret = etm4_config_timestamp_event(drvdata, ts_level); + + /* + * Error if user asked for timestamps but there was no + * free counter. + */ + if (ret) + goto out; + } /* bit[11], Global timestamp tracing bit */ config->cfg |= TRCCONFIGR_TS; } /* Only trace contextID when runs in root PID namespace */ - if ((attr->config & BIT(ETM_OPT_CTXTID)) && + if (ATTR_CFG_GET_FLD(attr, contextid1) && task_is_in_init_pid_ns(current)) /* bit[6], Context ID tracing bit */ config->cfg |= TRCCONFIGR_CID; /* - * If set bit ETM_OPT_CTXTID2 in perf config, this asks to trace VMID - * for recording CONTEXTIDR_EL2. Do not enable VMID tracing if the - * kernel is not running in EL2. + * If set bit contextid2 in perf config, this asks to trace VMID for + * recording CONTEXTIDR_EL2. Do not enable VMID tracing if the kernel + * is not running in EL2. */ - if (attr->config & BIT(ETM_OPT_CTXTID2)) { + if (ATTR_CFG_GET_FLD(attr, contextid2)) { if (!is_kernel_in_hyp_mode()) { ret = -EINVAL; goto out; @@ -801,26 +837,22 @@ static int etm4_parse_event_config(struct coresight_device *csdev, } /* return stack - enable if selected and supported */ - if ((attr->config & BIT(ETM_OPT_RETSTK)) && drvdata->retstack) + if (ATTR_CFG_GET_FLD(attr, retstack) && drvdata->retstack) /* bit[12], Return stack enable bit */ config->cfg |= TRCCONFIGR_RS; /* - * Set any selected configuration and preset. - * - * This extracts the values of PMU_FORMAT_ATTR(configid) and PMU_FORMAT_ATTR(preset) - * in the perf attributes defined in coresight-etm-perf.c. - * configid uses bits 63:32 of attr->config2, preset uses bits 3:0 of attr->config. - * A zero configid means no configuration active, preset = 0 means no preset selected. + * Set any selected configuration and preset. A zero configid means no + * configuration active, preset = 0 means no preset selected. */ - if (attr->config2 & GENMASK_ULL(63, 32)) { - cfg_hash = (u32)(attr->config2 >> 32); - preset = attr->config & 0xF; + cfg_hash = ATTR_CFG_GET_FLD(attr, configid); + if (cfg_hash) { + preset = ATTR_CFG_GET_FLD(attr, preset); ret = cscfg_csdev_enable_active_config(csdev, cfg_hash, preset); } /* branch broadcast - enable if selected and supported */ - if (attr->config & BIT(ETM_OPT_BRANCH_BROADCAST)) { + if (ATTR_CFG_GET_FLD(attr, branch_broadcast)) { if (!drvdata->trcbb) { /* * Missing BB support could cause silent decode errors @@ -829,7 +861,7 @@ static int etm4_parse_event_config(struct coresight_device *csdev, ret = -EINVAL; goto out; } else { - config->cfg |= BIT(ETM4_CFG_BIT_BB); + config->cfg |= TRCCONFIGR_BB; } } @@ -1053,11 +1085,8 @@ static int etm4_disable_perf(struct coresight_device *csdev, return -EINVAL; etm4_disable_hw(drvdata); - /* - * The config_id occupies bits 63:32 of the config2 perf event attr - * field. If this is non-zero then we will have enabled a config. - */ - if (attr->config2 & GENMASK_ULL(63, 32)) + /* If configid is non-zero then we will have enabled a config. */ + if (ATTR_CFG_GET_FLD(attr, configid)) cscfg_csdev_disable_active_config(csdev); /* diff --git a/drivers/hwtracing/coresight/coresight-etm4x.h b/drivers/hwtracing/coresight/coresight-etm4x.h index 012c52fd1933..89d81ce4e04e 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.h +++ b/drivers/hwtracing/coresight/coresight-etm4x.h @@ -225,6 +225,50 @@ #define TRCRSCTLRn_GROUP_MASK GENMASK(19, 16) #define TRCRSCTLRn_SELECT_MASK GENMASK(15, 0) +#define TRCCNTCTLRn_CNTCHAIN BIT(17) +#define TRCCNTCTLRn_RLDSELF BIT(16) +#define TRCCNTCTLRn_RLDEVENT_MASK GENMASK(15, 8) +#define TRCCNTCTLRn_CNTEVENT_MASK GENMASK(7, 0) + +#define TRCTSCTLR_EVENT_MASK GENMASK(7, 0) + +#define ETM4_RES_SEL_FALSE 0 /* Fixed function 'always false' resource selector */ +#define ETM4_RES_SEL_TRUE 1 /* Fixed function 'always true' resource selector */ + +#define ETM4_RES_SEL_SINGLE_MASK GENMASK(4, 0) +#define ETM4_RES_SEL_PAIR_MASK GENMASK(3, 0) +#define ETM4_RES_SEL_TYPE_PAIR BIT(7) + +/* + * Utilities for programming EVENT resource selectors, e.g. TRCCNTCTLRn_RLDEVENT. + * + * Resource selectors have a common format across registers: + * + * 7 6 5 4 0 + * +------+------+-------+ + * | TYPE | RES0 | SEL | + * +------+------+-------+ + * + * Where TYPE indicates whether the selector is for a single event or a pair. + * When TYPE is pair, SEL is 4 bits wide and using pair 0 is UNPREDICTABLE. + * Otherwise for single it's 5 bits wide. + */ +static inline u32 etm4_res_sel_single(u8 res_sel_idx) +{ + WARN_ON_ONCE(!FIELD_FIT(ETM4_RES_SEL_SINGLE_MASK, res_sel_idx)); + return FIELD_PREP(ETM4_RES_SEL_SINGLE_MASK, res_sel_idx); +} + +static inline u32 etm4_res_sel_pair(u8 res_sel_idx) +{ + if (__builtin_constant_p(res_sel_idx)) + BUILD_BUG_ON(res_sel_idx == 0); + WARN_ON_ONCE(!FIELD_FIT(ETM4_RES_SEL_PAIR_MASK, res_sel_idx) || + (res_sel_idx == 0)); + return FIELD_PREP(ETM4_RES_SEL_PAIR_MASK, res_sel_idx) | + ETM4_RES_SEL_TYPE_PAIR; +} + /* * System instructions to access ETM registers. * See ETMv4.4 spec ARM IHI0064F section 4.3.6 System instructions @@ -824,8 +868,7 @@ struct etmv4_config { u32 eventctrl0; u32 eventctrl1; u32 stall_ctrl; - u32 ts_ctrl; - u32 syncfreq; + u32 ts_ctrl; /* TRCTSCTLR */ u32 ccctlr; u32 bb_ctrl; u32 vinst_ctrl; @@ -833,15 +876,16 @@ struct etmv4_config { u32 vissctlr; u32 vipcssctlr; u8 seq_idx; + u8 syncfreq; u32 seq_ctrl[ETM_MAX_SEQ_STATES]; u32 seq_rst; u32 seq_state; u8 cntr_idx; - u32 cntrldvr[ETMv4_MAX_CNTR]; - u32 cntr_ctrl[ETMv4_MAX_CNTR]; - u32 cntr_val[ETMv4_MAX_CNTR]; + u32 cntrldvr[ETMv4_MAX_CNTR]; /* TRCCNTRLDVRn */ + u32 cntr_ctrl[ETMv4_MAX_CNTR]; /* TRCCNTCTLRn */ + u32 cntr_val[ETMv4_MAX_CNTR]; /* TRCCNTVRn */ u8 res_idx; - u32 res_ctrl[ETM_MAX_RES_SEL]; + u32 res_ctrl[ETM_MAX_RES_SEL]; /* TRCRSCTLRn */ u8 ss_idx; u32 ss_ctrl[ETM_MAX_SS_CMP]; u32 ss_status[ETM_MAX_SS_CMP]; @@ -1016,27 +1060,27 @@ struct etmv4_drvdata { u8 ns_ex_level; u8 q_support; u8 os_lock_model; - bool sticky_enable; - bool boot_enable; - bool os_unlock; - bool instrp0; - bool q_filt; - bool trcbb; - bool trccond; - bool retstack; - bool trccci; - bool trc_error; - bool syncpr; - bool stallctl; - bool sysstall; - bool nooverflow; - bool atbtrig; - bool lpoverride; + bool sticky_enable : 1; + bool boot_enable : 1; + bool os_unlock : 1; + bool instrp0 : 1; + bool q_filt : 1; + bool trcbb : 1; + bool trccond : 1; + bool retstack : 1; + bool trccci : 1; + bool trc_error : 1; + bool syncpr : 1; + bool stallctl : 1; + bool sysstall : 1; + bool nooverflow : 1; + bool atbtrig : 1; + bool lpoverride : 1; + bool skip_power_up : 1; + bool paused : 1; u64 trfcr; struct etmv4_config config; struct etmv4_save_state *save_state; - bool skip_power_up; - bool paused; DECLARE_BITMAP(arch_features, ETM4_IMPDEF_FEATURE_MAX); }; diff --git a/drivers/hwtracing/coresight/coresight-tmc-etr.c b/drivers/hwtracing/coresight/coresight-tmc-etr.c index e0d83ee01b77..cee82e52c4ea 100644 --- a/drivers/hwtracing/coresight/coresight-tmc-etr.c +++ b/drivers/hwtracing/coresight/coresight-tmc-etr.c @@ -1307,6 +1307,19 @@ static int tmc_enable_etr_sink_sysfs(struct coresight_device *csdev) raw_spin_lock_irqsave(&drvdata->spinlock, flags); /* + * Since the sysfs buffer allocation and the hardware enablement is not + * in the same critical region, it's possible to race with the perf. + */ + if (coresight_get_mode(csdev) == CS_MODE_PERF) { + drvdata->sysfs_buf = NULL; + raw_spin_unlock_irqrestore(&drvdata->spinlock, flags); + + /* Free allocated memory out side of the spinlock */ + tmc_etr_free_sysfs_buf(sysfs_buf); + return -EBUSY; + } + + /* * In sysFS mode we can have multiple writers per sink. Since this * sink is already enabled no memory is needed and the HW need not be * touched, even if the buffer size has changed. @@ -1354,9 +1367,7 @@ EXPORT_SYMBOL_GPL(tmc_etr_get_buffer); /* * alloc_etr_buf: Allocate ETR buffer for use by perf. - * The size of the hardware buffer is dependent on the size configured - * via sysfs and the perf ring buffer size. We prefer to allocate the - * largest possible size, scaling down the size by half until it + * Allocate the largest possible size, scaling down the size by half until it * reaches a minimum limit (1M), beyond which we give up. */ static struct etr_buf * @@ -1365,36 +1376,26 @@ alloc_etr_buf(struct tmc_drvdata *drvdata, struct perf_event *event, { int node; struct etr_buf *etr_buf; - unsigned long size; + ssize_t size; node = (event->cpu == -1) ? NUMA_NO_NODE : cpu_to_node(event->cpu); - /* - * Try to match the perf ring buffer size if it is larger - * than the size requested via sysfs. - */ - if ((nr_pages << PAGE_SHIFT) > drvdata->size) { - etr_buf = tmc_alloc_etr_buf(drvdata, ((ssize_t)nr_pages << PAGE_SHIFT), - 0, node, NULL); - if (!IS_ERR(etr_buf)) - goto done; - } + + /* Use the minimum limit if the required size is smaller */ + size = nr_pages << PAGE_SHIFT; + size = max_t(ssize_t, size, TMC_ETR_PERF_MIN_BUF_SIZE); /* - * Else switch to configured size for this ETR - * and scale down until we hit the minimum limit. + * Try to allocate the required size for this ETR, if failed scale + * down until we hit the minimum limit. */ - size = drvdata->size; do { etr_buf = tmc_alloc_etr_buf(drvdata, size, 0, node, NULL); if (!IS_ERR(etr_buf)) - goto done; + return etr_buf; size /= 2; } while (size >= TMC_ETR_PERF_MIN_BUF_SIZE); return ERR_PTR(-ENOMEM); - -done: - return etr_buf; } static struct etr_buf * diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index 95473d131032..319a354ede9f 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -221,6 +221,7 @@ struct tmc_resrv_buf { * @pid: Process ID of the process that owns the session that is using * this component. For example this would be the pid of the Perf * process. + * @reading: buffer's in the reading through "/dev/xyz.tmc" entry * @stop_on_flush: Stop on flush trigger user configuration. * @buf: Snapshot of the trace data for ETF/ETB. * @etr_buf: details of buffer used in TMC-ETR @@ -233,6 +234,7 @@ struct tmc_resrv_buf { * @trigger_cntr: amount of words to store after a trigger. * @etr_caps: Bitmask of capabilities of the TMC ETR, inferred from the * device configuration register (DEVID) + * @etr_mode: User preferred mode of the ETR device, default auto mode. * @idr: Holds etr_bufs allocated for this ETR. * @idr_mutex: Access serialisation for idr. * @sysfs_buf: SYSFS buffer for ETR. diff --git a/drivers/hwtracing/coresight/coresight-tnoc.c b/drivers/hwtracing/coresight/coresight-tnoc.c index ff9a0a9cfe96..1128612e70a7 100644 --- a/drivers/hwtracing/coresight/coresight-tnoc.c +++ b/drivers/hwtracing/coresight/coresight-tnoc.c @@ -34,6 +34,7 @@ * @base: memory mapped base address for this component. * @dev: device node for trace_noc_drvdata. * @csdev: component vitals needed by the framework. + * @pclk: APB clock if present, otherwise NULL * @spinlock: serialize enable/disable operation. * @atid: id for the trace packet. */ @@ -41,8 +42,9 @@ struct trace_noc_drvdata { void __iomem *base; struct device *dev; struct coresight_device *csdev; + struct clk *pclk; spinlock_t spinlock; - u32 atid; + int atid; }; DEFINE_CORESIGHT_DEVLIST(trace_noc_devs, "traceNoc"); @@ -51,6 +53,12 @@ static void trace_noc_enable_hw(struct trace_noc_drvdata *drvdata) { u32 val; + /* No valid ATID, simply enable the unit */ + if (drvdata->atid == -EOPNOTSUPP) { + writel(TRACE_NOC_CTRL_PORTEN, drvdata->base + TRACE_NOC_CTRL); + return; + } + /* Set ATID */ writel_relaxed(drvdata->atid, drvdata->base + TRACE_NOC_XLD); @@ -124,6 +132,11 @@ static int trace_noc_init_default_data(struct trace_noc_drvdata *drvdata) { int atid; + if (!dev_is_amba(drvdata->dev)) { + drvdata->atid = -EOPNOTSUPP; + return 0; + } + atid = coresight_trace_id_get_system_id(); if (atid < 0) return atid; @@ -149,8 +162,21 @@ static struct attribute *coresight_tnoc_attrs[] = { NULL, }; +static umode_t trace_id_is_visible(struct kobject *kobj, + struct attribute *attr, int idx) +{ + struct device *dev = kobj_to_dev(kobj); + struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (attr == &dev_attr_traceid.attr && drvdata->atid < 0) + return 0; + + return attr->mode; +} + static const struct attribute_group coresight_tnoc_group = { .attrs = coresight_tnoc_attrs, + .is_visible = trace_id_is_visible, }; static const struct attribute_group *coresight_tnoc_groups[] = { @@ -158,9 +184,8 @@ static const struct attribute_group *coresight_tnoc_groups[] = { NULL, }; -static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id) +static int _tnoc_probe(struct device *dev, struct resource *res) { - struct device *dev = &adev->dev; struct coresight_platform_data *pdata; struct trace_noc_drvdata *drvdata; struct coresight_desc desc = { 0 }; @@ -173,16 +198,20 @@ static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id) pdata = coresight_get_platform_data(dev); if (IS_ERR(pdata)) return PTR_ERR(pdata); - adev->dev.platform_data = pdata; + dev->platform_data = pdata; drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); if (!drvdata) return -ENOMEM; - drvdata->dev = &adev->dev; + drvdata->dev = dev; dev_set_drvdata(dev, drvdata); - drvdata->base = devm_ioremap_resource(dev, &adev->res); + ret = coresight_get_enable_clocks(dev, &drvdata->pclk, NULL); + if (ret) + return ret; + + drvdata->base = devm_ioremap_resource(dev, res); if (IS_ERR(drvdata->base)) return PTR_ERR(drvdata->base); @@ -195,20 +224,31 @@ static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id) 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.pdata = pdata; + desc.dev = 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); + if (drvdata->atid > 0) + coresight_trace_id_put_system_id(drvdata->atid); return PTR_ERR(drvdata->csdev); } - pm_runtime_put(&adev->dev); return 0; } +static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret; + + ret = _tnoc_probe(&adev->dev, &adev->res); + if (!ret) + pm_runtime_put(&adev->dev); + + return ret; +} + static void trace_noc_remove(struct amba_device *adev) { struct trace_noc_drvdata *drvdata = dev_get_drvdata(&adev->dev); @@ -240,7 +280,81 @@ static struct amba_driver trace_noc_driver = { .id_table = trace_noc_ids, }; -module_amba_driver(trace_noc_driver); +static int itnoc_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int ret; + + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = _tnoc_probe(&pdev->dev, res); + pm_runtime_put(&pdev->dev); + if (ret) + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static void itnoc_remove(struct platform_device *pdev) +{ + struct trace_noc_drvdata *drvdata = platform_get_drvdata(pdev); + + coresight_unregister(drvdata->csdev); + pm_runtime_disable(&pdev->dev); +} + +#ifdef CONFIG_PM +static int itnoc_runtime_suspend(struct device *dev) +{ + struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev); + + clk_disable_unprepare(drvdata->pclk); + + return 0; +} + +static int itnoc_runtime_resume(struct device *dev) +{ + struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev); + + return clk_prepare_enable(drvdata->pclk); +} +#endif + +static const struct dev_pm_ops itnoc_dev_pm_ops = { + SET_RUNTIME_PM_OPS(itnoc_runtime_suspend, itnoc_runtime_resume, NULL) +}; + +static const struct of_device_id itnoc_of_match[] = { + { .compatible = "qcom,coresight-itnoc" }, + {} +}; +MODULE_DEVICE_TABLE(of, itnoc_of_match); + +static struct platform_driver itnoc_driver = { + .probe = itnoc_probe, + .remove = itnoc_remove, + .driver = { + .name = "coresight-itnoc", + .of_match_table = itnoc_of_match, + .suppress_bind_attrs = true, + .pm = &itnoc_dev_pm_ops, + }, +}; + +static int __init tnoc_init(void) +{ + return coresight_init_driver("tnoc", &trace_noc_driver, &itnoc_driver, THIS_MODULE); +} + +static void __exit tnoc_exit(void) +{ + coresight_remove_driver(&trace_noc_driver, &itnoc_driver); +} +module_init(tnoc_init); +module_exit(tnoc_exit); 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 3a3825d27f86..7055f8f13427 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.c +++ b/drivers/hwtracing/coresight/coresight-tpda.c @@ -137,12 +137,46 @@ static int tpda_get_element_size(struct tpda_drvdata *drvdata, /* Settings pre enabling port control register */ static void tpda_enable_pre_port(struct tpda_drvdata *drvdata) { - u32 val; + u32 val = 0; - val = readl_relaxed(drvdata->base + TPDA_CR); - val &= ~TPDA_CR_ATID; val |= FIELD_PREP(TPDA_CR_ATID, drvdata->atid); + if (drvdata->trig_async) + val |= TPDA_CR_SRIE; + + if (drvdata->trig_flag_ts) + val |= TPDA_CR_FLRIE; + + if (drvdata->trig_freq) + val |= TPDA_CR_FRIE; + + if (drvdata->freq_ts) + val |= TPDA_CR_FREQTS; + + if (drvdata->cmbchan_mode) + val |= TPDA_CR_CMBCHANMODE; + writel_relaxed(val, drvdata->base + TPDA_CR); + + /* + * If FLRIE bit is set, set the master and channel + * id as zero + */ + if (drvdata->trig_flag_ts) + writel_relaxed(0x0, drvdata->base + TPDA_FPID_CR); + + /* Initialize with a value of 0 */ + val = 0; + if (drvdata->syncr_mode) + val |= TPDA_SYNCR_MODE_CTRL_MASK; + + if (drvdata->syncr_count > 0 && + drvdata->syncr_count < TPDA_SYNCR_COUNT_MASK) + val |= drvdata->syncr_count; + else + /* Program the count to its MAX value by default */ + val |= TPDA_SYNCR_COUNT_MASK; + + writel_relaxed(val, drvdata->base + TPDA_SYNCR); } static int tpda_enable_port(struct tpda_drvdata *drvdata, int port) @@ -258,6 +292,248 @@ static const struct coresight_ops tpda_cs_ops = { .link_ops = &tpda_link_ops, }; +/* Read cross-trigger register member */ +static ssize_t tpda_trig_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpda_trig_sysfs_attribute *tpda_attr = + container_of(attr, struct tpda_trig_sysfs_attribute, attr); + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + + guard(spinlock)(&drvdata->spinlock); + switch (tpda_attr->mem) { + case FREQTS: + return sysfs_emit(buf, "%u\n", (unsigned int)drvdata->freq_ts); + case FRIE: + return sysfs_emit(buf, "%u\n", (unsigned int)drvdata->trig_freq); + case FLRIE: + return sysfs_emit(buf, "%u\n", (unsigned int)drvdata->trig_flag_ts); + case SRIE: + return sysfs_emit(buf, "%u\n", (unsigned int)drvdata->trig_async); + case CMBCHANMODE: + return sysfs_emit(buf, "%u\n", (unsigned int)drvdata->cmbchan_mode); + + } + return -EINVAL; +} + +static ssize_t tpda_trig_sysfs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpda_trig_sysfs_attribute *tpda_attr = + container_of(attr, struct tpda_trig_sysfs_attribute, attr); + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + switch (tpda_attr->mem) { + case FREQTS: + drvdata->freq_ts = !!val; + break; + case FRIE: + drvdata->trig_freq = !!val; + break; + case FLRIE: + drvdata->trig_flag_ts = !!val; + break; + case SRIE: + drvdata->trig_async = !!val; + break; + case CMBCHANMODE: + drvdata->cmbchan_mode = !!val; + break; + default: + return -EINVAL; + } + + return size; +} + +static ssize_t global_flush_req_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (!drvdata->csdev->refcnt) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + val = readl_relaxed(drvdata->base + TPDA_CR); + /* read global_flush_req bit */ + val &= TPDA_CR_FLREQ; + + return sysfs_emit(buf, "%lu\n", val); +} + +static ssize_t global_flush_req_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (!drvdata->csdev->refcnt || !val) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + val = readl_relaxed(drvdata->base + TPDA_CR); + /* set global_flush_req bit */ + val |= TPDA_CR_FLREQ; + CS_UNLOCK(drvdata->base); + writel_relaxed(val, drvdata->base + TPDA_CR); + CS_LOCK(drvdata->base); + + return size; +} +static DEVICE_ATTR_RW(global_flush_req); + +static ssize_t syncr_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val, syncr_val; + + if (!drvdata->csdev->refcnt) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + syncr_val = readl_relaxed(drvdata->base + TPDA_SYNCR); + val = FIELD_GET(TPDA_SYNCR_MODE_CTRL_MASK, syncr_val); + + return sysfs_emit(buf, "%lu\n", val); +} + +static ssize_t syncr_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + /* set the mode when first enabling the device */ + drvdata->syncr_mode = !!val; + + return size; +} +static DEVICE_ATTR_RW(syncr_mode); + +static ssize_t syncr_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (!drvdata->csdev->refcnt) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + val = readl_relaxed(drvdata->base + TPDA_SYNCR); + val &= TPDA_SYNCR_COUNT_MASK; + + return sysfs_emit(buf, "%lu\n", val); +} + +static ssize_t syncr_count_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val > TPDA_SYNCR_COUNT_MASK) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + drvdata->syncr_count = val; + + return size; +} +static DEVICE_ATTR_RW(syncr_count); + +static ssize_t port_flush_req_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if (!drvdata->csdev->refcnt) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + val = readl_relaxed(drvdata->base + TPDA_FLUSH_CR); + + return sysfs_emit(buf, "0x%lx\n", val); +} + +static ssize_t port_flush_req_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpda_drvdata *drvdata = dev_get_drvdata(dev->parent); + u32 val; + + if (kstrtou32(buf, 0, &val)) + return -EINVAL; + + if (!drvdata->csdev->refcnt || !val) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + CS_UNLOCK(drvdata->base); + writel_relaxed(val, drvdata->base + TPDA_FLUSH_CR); + CS_LOCK(drvdata->base); + + return size; +} +static DEVICE_ATTR_RW(port_flush_req); + +static struct attribute *tpda_attrs[] = { + &dev_attr_global_flush_req.attr, + &dev_attr_syncr_mode.attr, + &dev_attr_syncr_count.attr, + &dev_attr_port_flush_req.attr, + tpda_trig_sysfs_rw(freq_ts_enable, FREQTS), + tpda_trig_sysfs_rw(trig_freq_enable, FRIE), + tpda_trig_sysfs_rw(trig_flag_ts_enable, FLRIE), + tpda_trig_sysfs_rw(trig_async_enable, SRIE), + tpda_trig_sysfs_rw(cmbchan_mode, CMBCHANMODE), + NULL, +}; + +static struct attribute_group tpda_attr_grp = { + .attrs = tpda_attrs, +}; + +static const struct attribute_group *tpda_attr_grps[] = { + &tpda_attr_grp, + NULL, +}; + static int tpda_init_default_data(struct tpda_drvdata *drvdata) { int atid; @@ -273,6 +549,7 @@ static int tpda_init_default_data(struct tpda_drvdata *drvdata) return atid; drvdata->atid = atid; + drvdata->freq_ts = true; return 0; } @@ -316,6 +593,7 @@ static int tpda_probe(struct amba_device *adev, const struct amba_id *id) desc.ops = &tpda_cs_ops; desc.pdata = adev->dev.platform_data; desc.dev = &adev->dev; + desc.groups = tpda_attr_grps; desc.access = CSDEV_ACCESS_IOMEM(base); drvdata->csdev = coresight_register(&desc); if (IS_ERR(drvdata->csdev)) diff --git a/drivers/hwtracing/coresight/coresight-tpda.h b/drivers/hwtracing/coresight/coresight-tpda.h index c6af3d2da3ef..a090352009bb 100644 --- a/drivers/hwtracing/coresight/coresight-tpda.h +++ b/drivers/hwtracing/coresight/coresight-tpda.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2023,2025 Qualcomm Innovation Center, Inc. All rights reserved. */ #ifndef _CORESIGHT_CORESIGHT_TPDA_H @@ -8,6 +8,29 @@ #define TPDA_CR (0x000) #define TPDA_Pn_CR(n) (0x004 + (n * 4)) +#define TPDA_FPID_CR (0x084) +#define TPDA_SYNCR (0x08C) +#define TPDA_FLUSH_CR (0x090) + +/* Cross trigger global (all ports) flush request bit */ +#define TPDA_CR_FLREQ BIT(0) +/* Cross trigger FREQ packets timestamp bit */ +#define TPDA_CR_FREQTS BIT(2) +/* Cross trigger FREQ packet request bit */ +#define TPDA_CR_FRIE BIT(3) +/* Cross trigger FLAG packet request interface bit */ +#define TPDA_CR_FLRIE BIT(4) +/* Cross trigger synchronization bit */ +#define TPDA_CR_SRIE BIT(5) +/* Bits 6 ~ 12 is for atid value */ +#define TPDA_CR_ATID GENMASK(12, 6) +/* + * Channel mode bit of the packetization of CMB/MCB traffic + * 0 - raw channel mapping mode + * 1 - channel pair marking mode + */ +#define TPDA_CR_CMBCHANMODE BIT(20) + /* Aggregator port enable bit */ #define TPDA_Pn_CR_ENA BIT(0) /* Aggregator port CMB data set element size bit */ @@ -15,10 +38,12 @@ /* Aggregator port DSB data set element size bit */ #define TPDA_Pn_CR_DSBSIZE BIT(8) -#define TPDA_MAX_INPORTS 32 +/* TPDA_SYNCR count mask */ +#define TPDA_SYNCR_COUNT_MASK GENMASK(11, 0) +/* TPDA_SYNCR mode control bit */ +#define TPDA_SYNCR_MODE_CTRL_MASK GENMASK(12, 12) -/* Bits 6 ~ 12 is for atid value */ -#define TPDA_CR_ATID GENMASK(12, 6) +#define TPDA_MAX_INPORTS 32 /** * struct tpda_drvdata - specifics associated to an TPDA component @@ -29,6 +54,13 @@ * @enable: enable status of the component. * @dsb_esize Record the DSB element size. * @cmb_esize Record the CMB element size. + * @trig_async: Enable/disable cross trigger synchronization sequence interface. + * @trig_flag_ts: Enable/disable cross trigger FLAG packet request interface. + * @trig_freq: Enable/disable cross trigger FREQ packet request interface. + * @freq_ts: Enable/disable the timestamp for all FREQ packets. + * @cmbchan_mode: Configure the CMB/MCMB channel mode. + * @syncr_mode: Setting the mode for counting packets. + * @syncr_count: Setting the value of the count. */ struct tpda_drvdata { void __iomem *base; @@ -38,6 +70,42 @@ struct tpda_drvdata { u8 atid; u32 dsb_esize; u32 cmb_esize; + bool trig_async; + bool trig_flag_ts; + bool trig_freq; + bool freq_ts; + bool cmbchan_mode; + bool syncr_mode; + u32 syncr_count; +}; + +/* Enumerate members of global control register(cr) */ +enum tpda_cr_mem { + FREQTS, + FRIE, + FLRIE, + SRIE, + CMBCHANMODE +}; + +/** + * struct tpda_trig_sysfs_attribute - Record the member variables of cross + * trigger register that need to be operated by sysfs file + * @attr: The device attribute + * @mem: The member in the control register data structure + */ +struct tpda_trig_sysfs_attribute { + struct device_attribute attr; + enum tpda_cr_mem mem; }; +#define tpda_trig_sysfs_rw(name, mem) \ + (&((struct tpda_trig_sysfs_attribute[]) { \ + { \ + __ATTR(name, 0644, tpda_trig_sysfs_show, \ + tpda_trig_sysfs_store), \ + mem, \ + } \ + })[0].attr.attr) + #endif /* _CORESIGHT_CORESIGHT_TPDA_H */ diff --git a/drivers/hwtracing/intel_th/pci.c b/drivers/hwtracing/intel_th/pci.c index e3def163d5cf..6cd5ac3c6430 100644 --- a/drivers/hwtracing/intel_th/pci.c +++ b/drivers/hwtracing/intel_th/pci.c @@ -14,6 +14,7 @@ #include <linux/pci.h> #include "intel_th.h" +#include "pci_ids.h" #define DRIVER_NAME "intel_th_pci" @@ -141,225 +142,55 @@ static const struct intel_th_drvdata intel_th_2x = { }; static const struct pci_device_id intel_th_pci_id_table[] = { - { - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x9d26), - .driver_data = (kernel_ulong_t)0, - }, - { - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa126), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Apollo Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a8e), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Broxton */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0a80), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Broxton B-step */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x1a8e), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Kaby Lake PCH-H */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa2a6), - .driver_data = (kernel_ulong_t)&intel_th_1x_multi_is_broken, - }, - { - /* Denverton */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x19e1), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Lewisburg PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa1a6), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Lewisburg PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa226), - .driver_data = (kernel_ulong_t)0, - }, - { - /* Gemini Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x318e), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Cannon Lake H */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa326), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Cannon Lake LP */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x9da6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Cedar Fork PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x18e1), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Ice Lake PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x34a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Comet Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x02a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Comet Lake PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x06a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Comet Lake PCH-V */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa3a6), - .driver_data = (kernel_ulong_t)&intel_th_1x_multi_is_broken, - }, - { - /* Ice Lake NNPI */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x45c5), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Ice Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x8a29), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Tiger Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x9a33), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Tiger Lake PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa0a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Tiger Lake PCH-H */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x43a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Jasper Lake PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4da6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Jasper Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4e29), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Elkhart Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4529), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Elkhart Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4b26), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Emmitsburg PCH */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x1bcc), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Alder Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7aa6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Alder Lake-P */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x51a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Alder Lake-M */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x54a6), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Meteor Lake-P */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7e24), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Meteor Lake-S */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7f26), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Meteor Lake-S CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xae24), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Raptor Lake-S */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7a26), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Raptor Lake-S CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa76f), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Granite Rapids */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0963), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Granite Rapids SOC */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x3256), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Sapphire Rapids SOC */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x3456), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Lunar Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa824), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Arrow Lake */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7724), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Panther Lake-H */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe324), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Panther Lake-P/U */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xe424), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Alder Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x466f), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { - /* Rocket Lake CPU */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4c19), - .driver_data = (kernel_ulong_t)&intel_th_2x, - }, - { 0 }, + { PCI_DEVICE_DATA(INTEL, NPK_CML, &intel_th_2x) }, /* Comet Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_CML_PCH, &intel_th_2x) }, /* Comet Lake PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_GNR, &intel_th_2x) }, /* Granite Rapids */ + { PCI_DEVICE_DATA(INTEL, NPK_BXT, NULL) }, /* Broxton */ + { PCI_DEVICE_DATA(INTEL, NPK_CDF, &intel_th_2x) }, /* Cedar Fork PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_DNV, NULL) }, /* Denverton */ + { PCI_DEVICE_DATA(INTEL, NPK_BXT_B, NULL) }, /* Broxton B-step */ + { PCI_DEVICE_DATA(INTEL, NPK_EBG, &intel_th_2x) }, /* Emmitsburg PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_GLK, &intel_th_2x) }, /* Gemini Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_GNR_SOC, &intel_th_2x) }, /* Granite Rapids SOC */ + { PCI_DEVICE_DATA(INTEL, NPK_SPR, &intel_th_2x) }, /* Sapphire Rapids SOC */ + { PCI_DEVICE_DATA(INTEL, NPK_ICL_PCH, &intel_th_2x) }, /* Ice Lake PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_TGL_PCH_H, &intel_th_2x) }, /* Tiger Lake PCH-H */ + { PCI_DEVICE_DATA(INTEL, NPK_EHL_CPU, &intel_th_2x) }, /* Elkhart Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_ICL_NNPI, &intel_th_2x) }, /* Ice Lake NNPI */ + { PCI_DEVICE_DATA(INTEL, NPK_ADL_CPU, &intel_th_2x) }, /* Alder Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_EHL, &intel_th_2x) }, /* Elkhart Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_RKL, &intel_th_2x) }, /* Rocket Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_JSL_PCH, &intel_th_2x) }, /* Jasper Lake PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_JSL_CPU, &intel_th_2x) }, /* Jasper Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_ADL_P, &intel_th_2x) }, /* Alder Lake-P */ + { PCI_DEVICE_DATA(INTEL, NPK_ADL_M, &intel_th_2x) }, /* Alder Lake-M */ + { PCI_DEVICE_DATA(INTEL, NPK_APL, NULL) }, /* Apollo Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_NVL_PCH, &intel_th_2x) }, /* Nova Lake-PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_ARL, &intel_th_2x) }, /* Arrow Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_RPL_S, &intel_th_2x) }, /* Raptor Lake-S */ + { PCI_DEVICE_DATA(INTEL, NPK_ADL, &intel_th_2x) }, /* Alder Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_MTL_P, &intel_th_2x) }, /* Meteor Lake-P */ + { PCI_DEVICE_DATA(INTEL, NPK_MTL_S, &intel_th_2x) }, /* Meteor Lake-S */ + { PCI_DEVICE_DATA(INTEL, NPK_ICL_CPU, &intel_th_2x) }, /* Ice Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_TGL_CPU, &intel_th_2x) }, /* Tiger Lake CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_0, NULL) }, + { PCI_DEVICE_DATA(INTEL, NPK_CNL_LP, &intel_th_2x) }, /* Cannon Lake LP */ + { PCI_DEVICE_DATA(INTEL, NPK_TGL_PCH, &intel_th_2x) }, /* Tiger Lake PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_1, NULL) }, + { PCI_DEVICE_DATA(INTEL, NPK_LBG_PCH, NULL) }, /* Lewisburg PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_LBG_PCH_2, NULL) }, /* Lewisburg PCH */ + { PCI_DEVICE_DATA(INTEL, NPK_KBL_PCH, &intel_th_1x_multi_is_broken) }, /* Kaby Lake PCH-H */ + { PCI_DEVICE_DATA(INTEL, NPK_CNL_H, &intel_th_2x) }, /* Cannon Lake H */ + { PCI_DEVICE_DATA(INTEL, NPK_CML_PCH_V, &intel_th_1x_multi_is_broken) }, /* Comet Lake PCH-V */ + { PCI_DEVICE_DATA(INTEL, NPK_RPL_S_CPU, &intel_th_2x) }, /* Raptor Lake-S CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_LNL, &intel_th_2x) }, /* Lunar Lake */ + { PCI_DEVICE_DATA(INTEL, NPK_MTL_S_CPU, &intel_th_2x) }, /* Meteor Lake-S CPU */ + { PCI_DEVICE_DATA(INTEL, NPK_NVL_P, &intel_th_2x) }, /* Nova Lake-P */ + { PCI_DEVICE_DATA(INTEL, NPK_NVL_H, &intel_th_2x) }, /* Nova Lake-H */ + { PCI_DEVICE_DATA(INTEL, NPK_NVL_S, &intel_th_2x) }, /* Nova Lake-S */ + { PCI_DEVICE_DATA(INTEL, NPK_PTL_H, &intel_th_2x) }, /* Panther Lake-H */ + { PCI_DEVICE_DATA(INTEL, NPK_PTL_PU, &intel_th_2x) }, /* Panther Lake-P/U */ + { } }; MODULE_DEVICE_TABLE(pci, intel_th_pci_id_table); diff --git a/drivers/hwtracing/intel_th/pci_ids.h b/drivers/hwtracing/intel_th/pci_ids.h new file mode 100644 index 000000000000..4a0c53beac22 --- /dev/null +++ b/drivers/hwtracing/intel_th/pci_ids.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel(R) Trace Hub driver debugging + * + * Copyright (C) 2025 Intel Corporation. + */ + +#ifndef __INTEL_TH_PCI_IDS_H__ +#define __INTEL_TH_PCI_IDS_H__ + +#define PCI_DEVICE_ID_INTEL_NPK_CML 0x02a6 +#define PCI_DEVICE_ID_INTEL_NPK_CML_PCH 0x06a6 +#define PCI_DEVICE_ID_INTEL_NPK_GNR 0x0963 +#define PCI_DEVICE_ID_INTEL_NPK_BXT 0x0a80 +#define PCI_DEVICE_ID_INTEL_NPK_CDF 0x18e1 +#define PCI_DEVICE_ID_INTEL_NPK_DNV 0x19e1 +#define PCI_DEVICE_ID_INTEL_NPK_BXT_B 0x1a8e +#define PCI_DEVICE_ID_INTEL_NPK_EBG 0x1bcc +#define PCI_DEVICE_ID_INTEL_NPK_GLK 0x318e +#define PCI_DEVICE_ID_INTEL_NPK_GNR_SOC 0x3256 +#define PCI_DEVICE_ID_INTEL_NPK_SPR 0x3456 +#define PCI_DEVICE_ID_INTEL_NPK_ICL_PCH 0x34a6 +#define PCI_DEVICE_ID_INTEL_NPK_TGL_PCH_H 0x43a6 +#define PCI_DEVICE_ID_INTEL_NPK_EHL_CPU 0x4529 +#define PCI_DEVICE_ID_INTEL_NPK_ICL_NNPI 0x45c5 +#define PCI_DEVICE_ID_INTEL_NPK_ADL_CPU 0x466f +#define PCI_DEVICE_ID_INTEL_NPK_EHL 0x4b26 +#define PCI_DEVICE_ID_INTEL_NPK_RKL 0x4c19 +#define PCI_DEVICE_ID_INTEL_NPK_JSL_PCH 0x4da6 +#define PCI_DEVICE_ID_INTEL_NPK_JSL_CPU 0x4e29 +#define PCI_DEVICE_ID_INTEL_NPK_ADL_P 0x51a6 +#define PCI_DEVICE_ID_INTEL_NPK_ADL_M 0x54a6 +#define PCI_DEVICE_ID_INTEL_NPK_APL 0x5a8e +#define PCI_DEVICE_ID_INTEL_NPK_NVL_PCH 0x6e26 +#define PCI_DEVICE_ID_INTEL_NPK_ARL 0x7724 +#define PCI_DEVICE_ID_INTEL_NPK_RPL_S 0x7a26 +#define PCI_DEVICE_ID_INTEL_NPK_ADL 0x7aa6 +#define PCI_DEVICE_ID_INTEL_NPK_MTL_P 0x7e24 +#define PCI_DEVICE_ID_INTEL_NPK_MTL_S 0x7f26 +#define PCI_DEVICE_ID_INTEL_NPK_ICL_CPU 0x8a29 +#define PCI_DEVICE_ID_INTEL_NPK_TGL_CPU 0x9a33 +#define PCI_DEVICE_ID_INTEL_NPK_0 0x9d26 +#define PCI_DEVICE_ID_INTEL_NPK_CNL_LP 0x9da6 +#define PCI_DEVICE_ID_INTEL_NPK_TGL_PCH 0xa0a6 +#define PCI_DEVICE_ID_INTEL_NPK_1 0xa126 +#define PCI_DEVICE_ID_INTEL_NPK_LBG_PCH 0xa1a6 +#define PCI_DEVICE_ID_INTEL_NPK_LBG_PCH_2 0xa226 +#define PCI_DEVICE_ID_INTEL_NPK_KBL_PCH 0xa2a6 +#define PCI_DEVICE_ID_INTEL_NPK_CNL_H 0xa326 +#define PCI_DEVICE_ID_INTEL_NPK_CML_PCH_V 0xa3a6 +#define PCI_DEVICE_ID_INTEL_NPK_RPL_S_CPU 0xa76f +#define PCI_DEVICE_ID_INTEL_NPK_LNL 0xa824 +#define PCI_DEVICE_ID_INTEL_NPK_MTL_S_CPU 0xae24 +#define PCI_DEVICE_ID_INTEL_NPK_NVL_P 0xd224 +#define PCI_DEVICE_ID_INTEL_NPK_NVL_H 0xd324 +#define PCI_DEVICE_ID_INTEL_NPK_NVL_S 0xd424 +#define PCI_DEVICE_ID_INTEL_NPK_PTL_H 0xe324 +#define PCI_DEVICE_ID_INTEL_NPK_PTL_PU 0xe424 + +#endif /* __INTEL_TH_PCI_IDS_H__ */ diff --git a/drivers/hwtracing/stm/Kconfig b/drivers/hwtracing/stm/Kconfig index eda6b11d40a1..cd7f0b0f3fbe 100644 --- a/drivers/hwtracing/stm/Kconfig +++ b/drivers/hwtracing/stm/Kconfig @@ -13,7 +13,7 @@ if STM config STM_PROTO_BASIC tristate "Basic STM framing protocol driver" - default CONFIG_STM + default STM help This is a simple framing protocol for sending data over STM devices. This was the protocol that the STM framework used @@ -28,7 +28,7 @@ config STM_PROTO_BASIC config STM_PROTO_SYS_T tristate "MIPI SyS-T STM framing protocol driver" - default CONFIG_STM + default STM help This is an implementation of MIPI SyS-T protocol to be used over the STP transport. In addition to the data payload, it diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c index ae016a9431da..3a7e577e6eac 100644 --- a/drivers/i2c/busses/i2c-fsi.c +++ b/drivers/i2c/busses/i2c-fsi.c @@ -674,8 +674,9 @@ static struct device_node *fsi_i2c_find_port_of_node(struct device_node *fsi, return NULL; } -static int fsi_i2c_probe(struct device *dev) +static int fsi_i2c_probe(struct fsi_device *fsi_dev) { + struct device *dev = &fsi_dev->dev; struct fsi_i2c_ctrl *i2c; struct fsi_i2c_port *port; struct device_node *np; @@ -735,14 +736,14 @@ static int fsi_i2c_probe(struct device *dev) list_add(&port->list, &i2c->ports); } - dev_set_drvdata(dev, i2c); + fsi_set_drvdata(fsi_dev, i2c); return 0; } -static int fsi_i2c_remove(struct device *dev) +static void fsi_i2c_remove(struct fsi_device *fsi_dev) { - struct fsi_i2c_ctrl *i2c = dev_get_drvdata(dev); + struct fsi_i2c_ctrl *i2c = fsi_get_drvdata(fsi_dev); struct fsi_i2c_port *port, *tmp; list_for_each_entry_safe(port, tmp, &i2c->ports, list) { @@ -750,8 +751,6 @@ static int fsi_i2c_remove(struct device *dev) i2c_del_adapter(&port->adapter); kfree(port); } - - return 0; } static const struct fsi_device_id fsi_i2c_ids[] = { @@ -761,11 +760,10 @@ static const struct fsi_device_id fsi_i2c_ids[] = { static struct fsi_driver fsi_i2c_driver = { .id_table = fsi_i2c_ids, + .probe = fsi_i2c_probe, + .remove = fsi_i2c_remove, .drv = { .name = "i2c-fsi", - .bus = &fsi_bus_type, - .probe = fsi_i2c_probe, - .remove = fsi_i2c_remove, }, }; diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 76911278fb21..3d3f8d8673dd 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -64,7 +64,7 @@ config ADXL345 config ADXL345_I2C tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer I2C Driver" - depends on INPUT_ADXL34X=n + depends on !INPUT_ADXL34X depends on I2C select ADXL345 select REGMAP_I2C @@ -74,11 +74,12 @@ config ADXL345_I2C To compile this driver as a module, choose M here: the module will be called adxl345_i2c and you will also get adxl345_core - for the core module. + for the core module. INPUT_ADXL34X share compatibles with this + driver, do not add both modules to the kernel. config ADXL345_SPI tristate "Analog Devices ADXL345 3-Axis Digital Accelerometer SPI Driver" - depends on INPUT_ADXL34X=n + depends on !INPUT_ADXL34X depends on SPI select ADXL345 select REGMAP_SPI @@ -88,7 +89,8 @@ config ADXL345_SPI To compile this driver as a module, choose M here: the module will be called adxl345_spi and you will also get adxl345_core - for the core module. + for the core module. INPUT_ADXL34X share compatibles with this + driver, do not add both modules to the kernel. config ADXL355 tristate diff --git a/drivers/iio/accel/adxl355_core.c b/drivers/iio/accel/adxl355_core.c index 5fc7f814b907..1c1d64d5cbcb 100644 --- a/drivers/iio/accel/adxl355_core.c +++ b/drivers/iio/accel/adxl355_core.c @@ -768,9 +768,8 @@ static int adxl355_probe_trigger(struct iio_dev *indio_dev, int irq) data->dready_trig->ops = &adxl355_trigger_ops; iio_trigger_set_drvdata(data->dready_trig, indio_dev); - ret = devm_request_irq(data->dev, irq, - &iio_trigger_generic_data_rdy_poll, - IRQF_ONESHOT, "adxl355_irq", data->dready_trig); + ret = devm_request_irq(data->dev, irq, &iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, "adxl355_irq", data->dready_trig); if (ret) return dev_err_probe(data->dev, ret, "request irq %d failed\n", irq); diff --git a/drivers/iio/accel/adxl372.c b/drivers/iio/accel/adxl372.c index 46d518a2a029..28a8793a53b6 100644 --- a/drivers/iio/accel/adxl372.c +++ b/drivers/iio/accel/adxl372.c @@ -295,7 +295,6 @@ struct adxl372_state { u32 inact_time_ms; u8 fifo_set_size; unsigned long int1_bitmask; - unsigned long int2_bitmask; u16 watermark; __be16 fifo_buf[ADXL372_FIFO_SIZE]; bool peak_fifo_mode_en; @@ -1247,11 +1246,10 @@ int adxl372_probe(struct device *dev, struct regmap *regmap, indio_dev->trig = iio_trigger_get(st->dready_trig); - ret = devm_request_threaded_irq(dev, st->irq, - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - indio_dev->name, st->dready_trig); + ret = devm_request_irq(dev, st->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + indio_dev->name, st->dready_trig); if (ret < 0) return ret; } diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c index aef5109c1ddd..8fab2fdbe147 100644 --- a/drivers/iio/accel/adxl380.c +++ b/drivers/iio/accel/adxl380.c @@ -232,25 +232,46 @@ bool adxl380_readable_noinc_reg(struct device *dev, unsigned int reg) } EXPORT_SYMBOL_NS_GPL(adxl380_readable_noinc_reg, "IIO_ADXL380"); +static int adxl380_act_inact_enabled(struct adxl380_state *st, bool *enabled) +{ + unsigned int act_inact_ctl; + int ret; + + if (!st->chip_info->has_low_power) { + *enabled = false; + return 0; + } + + ret = regmap_read(st->regmap, ADXL380_ACT_INACT_CTL_REG, &act_inact_ctl); + if (ret) + return ret; + + *enabled = FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) || + FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl); + + return 0; +} + static int adxl380_set_measure_en(struct adxl380_state *st, bool en) { int ret; - unsigned int act_inact_ctl; u8 op_mode = ADXL380_OP_MODE_STANDBY; if (en) { - ret = regmap_read(st->regmap, ADXL380_ACT_INACT_CTL_REG, &act_inact_ctl); + bool act_inact_enabled; + + ret = adxl380_act_inact_enabled(st, &act_inact_enabled); if (ret) return ret; /* * Activity/Inactivity detection available only in VLP/ULP - * mode and for devices that support low power modes. Otherwise - * go straight to measure mode (same bits as ADXL380_OP_MODE_HP). + * mode and for devices that support low power modes. */ - if (st->chip_info->has_low_power && - (FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) || - FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl))) + if (act_inact_enabled) + st->odr = ADXL380_ODR_VLP; + + if (st->odr == ADXL380_ODR_VLP) op_mode = ADXL380_OP_MODE_VLP; else op_mode = ADXL380_OP_MODE_HP; @@ -417,17 +438,7 @@ static int adxl380_read_chn(struct adxl380_state *st, u8 addr) static int adxl380_get_odr(struct adxl380_state *st, int *odr) { - int ret; - unsigned int trig_cfg, odr_idx; - - ret = regmap_read(st->regmap, ADXL380_TRIG_CFG_REG, &trig_cfg); - if (ret) - return ret; - - odr_idx = (FIELD_GET(ADXL380_TRIG_CFG_SINC_RATE_MSK, trig_cfg) << 1) | - (FIELD_GET(ADXL380_TRIG_CFG_DEC_2X_MSK, trig_cfg) & 1); - - *odr = st->chip_info->samp_freq_tbl[odr_idx]; + *odr = st->chip_info->samp_freq_tbl[st->odr]; return 0; } @@ -488,18 +499,24 @@ static int adxl380_set_odr(struct adxl380_state *st, u8 odr) if (ret) return ret; - ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG, - ADXL380_TRIG_CFG_DEC_2X_MSK, - FIELD_PREP(ADXL380_TRIG_CFG_DEC_2X_MSK, odr & 1)); - if (ret) - return ret; + if (odr >= ADXL380_ODR_DSM) { + u8 mul = odr - ADXL380_ODR_DSM; + u8 field; - ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG, - ADXL380_TRIG_CFG_SINC_RATE_MSK, - FIELD_PREP(ADXL380_TRIG_CFG_SINC_RATE_MSK, odr >> 1)); - if (ret) - return ret; + field = FIELD_PREP(ADXL380_TRIG_CFG_DEC_2X_MSK, mul & 1); + ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG, + ADXL380_TRIG_CFG_DEC_2X_MSK, field); + if (ret) + return ret; + field = FIELD_PREP(ADXL380_TRIG_CFG_SINC_RATE_MSK, mul >> 1); + ret = regmap_update_bits(st->regmap, ADXL380_TRIG_CFG_REG, + ADXL380_TRIG_CFG_SINC_RATE_MSK, field); + if (ret) + return ret; + } + + st->odr = odr; ret = adxl380_set_measure_en(st, true); if (ret) return ret; @@ -949,14 +966,13 @@ static irqreturn_t adxl380_irq_handler(int irq, void *p) if (ret) return IRQ_HANDLED; - for (i = 0; i < fifo_entries; i += st->fifo_set_size) { - ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA, - &st->fifo_buf[i], - 2 * st->fifo_set_size); - if (ret) - return IRQ_HANDLED; + fifo_entries = rounddown(fifo_entries, st->fifo_set_size); + ret = regmap_noinc_read(st->regmap, ADXL380_FIFO_DATA, &st->fifo_buf, + sizeof(*st->fifo_buf) * fifo_entries); + if (ret) + return IRQ_HANDLED; + for (i = 0; i < fifo_entries; i += st->fifo_set_size) iio_push_to_buffers(indio_dev, &st->fifo_buf[i]); - } return IRQ_HANDLED; } @@ -1138,6 +1154,32 @@ static const struct iio_buffer_setup_ops adxl380_buffer_ops = { .predisable = adxl380_buffer_predisable, }; +static int adxl380_samp_freq_avail(struct adxl380_state *st, const int **vals, + int *length) +{ + bool act_inact_enabled; + int ret; + + if (!st->chip_info->has_low_power) { + *vals = st->chip_info->samp_freq_tbl + ADXL380_ODR_DSM; + *length = ADXL380_ODR_MAX - ADXL380_ODR_DSM; + return 0; + } + + ret = adxl380_act_inact_enabled(st, &act_inact_enabled); + if (ret) + return 0; + + /* + * Motion detection is only functional in low-power mode, and this + * affects the available sampling frequencies. + */ + *vals = st->chip_info->samp_freq_tbl; + *length = act_inact_enabled ? ADXL380_ODR_DSM : ADXL380_ODR_MAX; + + return 0; +} + static int adxl380_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long info) @@ -1218,6 +1260,7 @@ static int adxl380_read_avail(struct iio_dev *indio_dev, long mask) { struct adxl380_state *st = iio_priv(indio_dev); + int ret; if (chan->type != IIO_ACCEL) return -EINVAL; @@ -1229,9 +1272,11 @@ static int adxl380_read_avail(struct iio_dev *indio_dev, *length = ARRAY_SIZE(st->chip_info->scale_tbl) * 2; return IIO_AVAIL_LIST; case IIO_CHAN_INFO_SAMP_FREQ: - *vals = (const int *)st->chip_info->samp_freq_tbl; + ret = adxl380_samp_freq_avail(st, vals, length); + if (ret) + return ret; + *type = IIO_VAL_INT; - *length = ARRAY_SIZE(st->chip_info->samp_freq_tbl); return IIO_AVAIL_LIST; case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: *vals = (const int *)st->lpf_tbl; @@ -1254,12 +1299,16 @@ static int adxl380_write_raw(struct iio_dev *indio_dev, int val, int val2, long info) { struct adxl380_state *st = iio_priv(indio_dev); - int odr_index, lpf_index, hpf_index, range_index; + const int *freq_vals; + int odr_index, lpf_index, hpf_index, range_index, freq_count, ret; switch (info) { case IIO_CHAN_INFO_SAMP_FREQ: - odr_index = adxl380_find_match_1d_tbl(st->chip_info->samp_freq_tbl, - ARRAY_SIZE(st->chip_info->samp_freq_tbl), + ret = adxl380_samp_freq_avail(st, &freq_vals, &freq_count); + if (ret) + return ret; + + odr_index = adxl380_find_match_1d_tbl(freq_vals, freq_count, val); return adxl380_set_odr(st, odr_index); case IIO_CHAN_INFO_CALIBBIAS: @@ -1621,7 +1670,7 @@ const struct adxl380_chip_info adxl318_chip_info = { [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 }, [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 }, }, - .samp_freq_tbl = { 8000, 16000, 32000 }, + .samp_freq_tbl = { 0, 8000, 16000, 32000 }, /* * The datasheet defines an intercept of 550 LSB at 25 degC * and a sensitivity of 10.2 LSB/C. @@ -1639,7 +1688,7 @@ const struct adxl380_chip_info adxl319_chip_info = { [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 }, [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 }, }, - .samp_freq_tbl = { 16000, 32000, 64000 }, + .samp_freq_tbl = { 0, 16000, 32000, 64000 }, /* * The datasheet defines an intercept of 550 LSB at 25 degC * and a sensitivity of 10.2 LSB/C. @@ -1657,7 +1706,7 @@ const struct adxl380_chip_info adxl380_chip_info = { [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 }, [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 }, }, - .samp_freq_tbl = { 8000, 16000, 32000 }, + .samp_freq_tbl = { 1000, 8000, 16000, 32000 }, /* * The datasheet defines an intercept of 470 LSB at 25 degC * and a sensitivity of 10.2 LSB/C. @@ -1677,7 +1726,7 @@ const struct adxl380_chip_info adxl382_chip_info = { [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 }, [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 }, }, - .samp_freq_tbl = { 16000, 32000, 64000 }, + .samp_freq_tbl = { 1000, 16000, 32000, 64000 }, /* * The datasheet defines an intercept of 570 LSB at 25 degC * and a sensitivity of 10.2 LSB/C. @@ -1916,6 +1965,7 @@ int adxl380_probe(struct device *dev, struct regmap *regmap, st->dev = dev; st->regmap = regmap; st->chip_info = chip_info; + st->odr = ADXL380_ODR_DSM; mutex_init(&st->lock); diff --git a/drivers/iio/accel/adxl380.h b/drivers/iio/accel/adxl380.h index e67c5aab8efc..d2c260c8b2fa 100644 --- a/drivers/iio/accel/adxl380.h +++ b/drivers/iio/accel/adxl380.h @@ -8,10 +8,18 @@ #ifndef _ADXL380_H_ #define _ADXL380_H_ +enum adxl380_odr { + ADXL380_ODR_VLP, + ADXL380_ODR_DSM, + ADXL380_ODR_DSM_2X, + ADXL380_ODR_DSM_4X, + ADXL380_ODR_MAX +}; + struct adxl380_chip_info { const char *name; const int scale_tbl[3][2]; - const int samp_freq_tbl[3]; + const int samp_freq_tbl[ADXL380_ODR_MAX]; const struct iio_info *info; const int temp_offset; const u16 chip_id; diff --git a/drivers/iio/accel/bma180.c b/drivers/iio/accel/bma180.c index 8925f5279e62..7bc6761f5135 100644 --- a/drivers/iio/accel/bma180.c +++ b/drivers/iio/accel/bma180.c @@ -986,8 +986,9 @@ static int bma180_probe(struct i2c_client *client) } ret = devm_request_irq(dev, client->irq, - iio_trigger_generic_data_rdy_poll, IRQF_TRIGGER_RISING, - "bma180_event", data->trig); + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "bma180_event", data->trig); if (ret) { dev_err(dev, "unable to request IRQ\n"); goto err_trigger_free; diff --git a/drivers/iio/accel/mxc4005.c b/drivers/iio/accel/mxc4005.c index ac973d871c8b..a2c3cf13d098 100644 --- a/drivers/iio/accel/mxc4005.c +++ b/drivers/iio/accel/mxc4005.c @@ -486,13 +486,10 @@ static int mxc4005_probe(struct i2c_client *client) if (!data->dready_trig) return -ENOMEM; - ret = devm_request_threaded_irq(&client->dev, client->irq, - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_TRIGGER_FALLING | - IRQF_ONESHOT, - "mxc4005_event", - data->dready_trig); + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, + "mxc4005_event", data->dready_trig); if (ret) { dev_err(&client->dev, "failed to init threaded irq\n"); diff --git a/drivers/iio/accel/sca3000.c b/drivers/iio/accel/sca3000.c index bfa8a3f5a92f..4a827be439a2 100644 --- a/drivers/iio/accel/sca3000.c +++ b/drivers/iio/accel/sca3000.c @@ -153,7 +153,6 @@ * struct sca3000_state - device instance state information * @us: the associated spi device * @info: chip variant information - * @last_timestamp: the timestamp of the last event * @mo_det_use_count: reference counter for the motion detection unit * @lock: lock used to protect elements of sca3000_state * and the underlying device state. @@ -163,7 +162,6 @@ struct sca3000_state { struct spi_device *us; const struct sca3000_chip_info *info; - s64 last_timestamp; int mo_det_use_count; struct mutex lock; /* Can these share a cacheline ? */ @@ -1489,7 +1487,11 @@ static int sca3000_probe(struct spi_device *spi) if (ret) goto error_free_irq; - return iio_device_register(indio_dev); + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_irq; + + return 0; error_free_irq: if (spi->irq) diff --git a/drivers/iio/accel/stk8ba50.c b/drivers/iio/accel/stk8ba50.c index 384f1fbcbcb3..a9ff2a273fe1 100644 --- a/drivers/iio/accel/stk8ba50.c +++ b/drivers/iio/accel/stk8ba50.c @@ -428,13 +428,10 @@ static int stk8ba50_probe(struct i2c_client *client) } if (client->irq > 0) { - ret = devm_request_threaded_irq(&client->dev, client->irq, - stk8ba50_data_rdy_trig_poll, - NULL, - IRQF_TRIGGER_RISING | - IRQF_ONESHOT, - "stk8ba50_event", - indio_dev); + ret = devm_request_irq(&client->dev, client->irq, + stk8ba50_data_rdy_trig_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "stk8ba50_event", indio_dev); if (ret < 0) { dev_err(&client->dev, "request irq %d failed\n", client->irq); diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 58da8255525e..60038ae8dfc4 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -70,6 +70,19 @@ config AD4030 To compile this driver as a module, choose M here: the module will be called ad4030. +config AD4062 + tristate "Analog Devices AD4062 Driver" + depends on I3C + select REGMAP_I3C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for Analog Devices AD4062 I3C analog + to digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4062. + config AD4080 tristate "Analog Devices AD4080 high speed ADC" depends on SPI @@ -99,6 +112,17 @@ config AD4130 To compile this driver as a module, choose M here: the module will be called ad4130. +config AD4134 + tristate "Analog Device AD4134 ADC Driver" + depends on SPI + select REGMAP_SPI + select CRC8 + help + Say yes here to build support for Analog Devices AD4134 SPI analog to + digital converters (ADC). + + To compile this driver as a module, choose M here: the module will be + called ad4134_spi. config AD4170_4 tristate "Analog Device AD4170-4 ADC Driver" @@ -387,6 +411,7 @@ config AD7768_1 depends on SPI select REGULATOR select REGMAP_SPI + select RATIONAL select IIO_BUFFER select IIO_TRIGGER select IIO_TRIGGERED_BUFFER @@ -1222,6 +1247,18 @@ config NPCM_ADC This driver can also be built as a module. If so, the module will be called npcm_adc. +config NXP_SAR_ADC + tristate "NXP S32G SAR-ADC driver" + depends on ARCH_S32 || COMPILE_TEST + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for S32G platforms + analog-to-digital converter. + + This driver can also be built as a module. If so, the module will be + called nxp_sar_adc. + config PAC1921 tristate "Microchip Technology PAC1921 driver" depends on I2C @@ -1664,6 +1701,18 @@ config TI_ADS1015 This driver can also be built as a module. If so, the module will be called ti-ads1015. +config TI_ADS1018 + tristate "Texas Instruments ADS1018 ADC" + depends on SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS1018 and + ADS1118 ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads1018. + config TI_ADS1100 tristate "Texas Instruments ADS1100 and ADS1000 ADC" depends on I2C @@ -1722,6 +1771,17 @@ config TI_ADS131E08 This driver can also be built as a module. If so, the module will be called ti-ads131e08. +config TI_ADS131M02 + tristate "Texas Instruments ADS131M02" + depends on SPI && REGULATOR + select CRC_ITU_T + help + Say yes here to get support for Texas Instruments ADS131M02, ADS131M03, + ADS131M04, ADS131M06 and ADS131M08 chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads131m02. + config TI_ADS7138 tristate "Texas Instruments ADS7128 and ADS7138 ADC driver" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 7cc8f9a12f76..c76550415ff1 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -11,8 +11,10 @@ obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o obj-$(CONFIG_AD4000) += ad4000.o obj-$(CONFIG_AD4030) += ad4030.o +obj-$(CONFIG_AD4062) += ad4062.o obj-$(CONFIG_AD4080) += ad4080.o obj-$(CONFIG_AD4130) += ad4130.o +obj-$(CONFIG_AD4134) += ad4134.o obj-$(CONFIG_AD4170_4) += ad4170-4.o obj-$(CONFIG_AD4695) += ad4695.o obj-$(CONFIG_AD4851) += ad4851.o @@ -108,6 +110,7 @@ obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_NCT7201) += nct7201.o obj-$(CONFIG_NPCM_ADC) += npcm_adc.o +obj-$(CONFIG_NXP_SAR_ADC) += nxp-sar-adc.o obj-$(CONFIG_PAC1921) += pac1921.o obj-$(CONFIG_PAC1934) += pac1934.o obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o @@ -145,11 +148,13 @@ obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o obj-$(CONFIG_TI_ADC161S626) += ti-adc161s626.o obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o +obj-$(CONFIG_TI_ADS1018) += ti-ads1018.o obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o +obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o obj-$(CONFIG_TI_ADS7138) += ti-ads7138.o obj-$(CONFIG_TI_ADS7924) += ti-ads7924.o obj-$(CONFIG_TI_ADS7950) += ti-ads7950.o diff --git a/drivers/iio/adc/ad4062.c b/drivers/iio/adc/ad4062.c new file mode 100644 index 000000000000..dd4ad32aa6f5 --- /dev/null +++ b/drivers/iio/adc/ad4062.c @@ -0,0 +1,1609 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Analog Devices AD4062 I3C ADC driver + * + * Copyright 2025 Analog Devices Inc. + */ +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/devm-helpers.h> +#include <linux/err.h> +#include <linux/gpio/driver.h> +#include <linux/i3c/device.h> +#include <linux/i3c/master.h> +#include <linux/iio/buffer.h> +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/math.h> +#include <linux/minmax.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/units.h> +#include <linux/unaligned.h> +#include <linux/util_macros.h> + +#define AD4062_REG_INTERFACE_CONFIG_A 0x00 +#define AD4062_REG_DEVICE_CONFIG 0x02 +#define AD4062_REG_DEVICE_CONFIG_POWER_MODE_MSK GENMASK(1, 0) +#define AD4062_REG_DEVICE_CONFIG_LOW_POWER_MODE 3 +#define AD4062_REG_PROD_ID_1 0x05 +#define AD4062_REG_DEVICE_GRADE 0x06 +#define AD4062_REG_SCRATCH_PAD 0x0A +#define AD4062_REG_VENDOR_H 0x0D +#define AD4062_REG_STREAM_MODE 0x0E +#define AD4062_REG_INTERFACE_STATUS 0x11 +#define AD4062_REG_MODE_SET 0x20 +#define AD4062_REG_MODE_SET_ENTER_ADC BIT(0) +#define AD4062_REG_ADC_MODES 0x21 +#define AD4062_REG_ADC_MODES_MODE_MSK GENMASK(1, 0) +#define AD4062_REG_ADC_CONFIG 0x22 +#define AD4062_REG_ADC_CONFIG_REF_EN_MSK BIT(5) +#define AD4062_REG_ADC_CONFIG_SCALE_EN_MSK BIT(4) +#define AD4062_REG_AVG_CONFIG 0x23 +#define AD4062_REG_GP_CONF 0x24 +#define AD4062_REG_GP_CONF_MODE_MSK_0 GENMASK(2, 0) +#define AD4062_REG_GP_CONF_MODE_MSK_1 GENMASK(6, 4) +#define AD4062_REG_INTR_CONF 0x25 +#define AD4062_REG_INTR_CONF_EN_MSK_0 GENMASK(1, 0) +#define AD4062_REG_INTR_CONF_EN_MSK_1 GENMASK(5, 4) +#define AD4062_REG_TIMER_CONFIG 0x27 +#define AD4062_REG_TIMER_CONFIG_FS_MASK GENMASK(7, 4) +#define AD4062_REG_MAX_LIMIT 0x29 +#define AD4062_REG_MIN_LIMIT 0x2B +#define AD4062_REG_MAX_HYST 0x2C +#define AD4062_REG_MIN_HYST 0x2D +#define AD4062_REG_MON_VAL 0x2F +#define AD4062_REG_ADC_IBI_EN 0x31 +#define AD4062_REG_ADC_IBI_EN_CONV_TRIGGER BIT(2) +#define AD4062_REG_ADC_IBI_EN_MAX BIT(1) +#define AD4062_REG_ADC_IBI_EN_MIN BIT(0) +#define AD4062_REG_FUSE_CRC 0x40 +#define AD4062_REG_DEVICE_STATUS 0x41 +#define AD4062_REG_DEVICE_STATUS_DEVICE_RESET BIT(6) +#define AD4062_REG_IBI_STATUS 0x48 +#define AD4062_REG_CONV_READ_LSB 0x50 +#define AD4062_REG_CONV_READ_16BITS 0x51 +#define AD4062_REG_CONV_READ_32BITS 0x53 +#define AD4062_REG_CONV_TRIGGER_16BITS 0x57 +#define AD4062_REG_CONV_TRIGGER_32BITS 0x59 +#define AD4062_REG_CONV_AUTO 0x61 +#define AD4062_MAX_REG AD4062_REG_CONV_AUTO + +#define AD4062_MON_VAL_MIDDLE_POINT 0x8000 + +#define AD4062_I3C_VENDOR 0x0177 +#define AD4062_SOFT_RESET 0x81 +#define AD4060_PROD_ID 0x7A +#define AD4062_PROD_ID 0x7C + +#define AD4062_GP_DISABLED 0x0 +#define AD4062_GP_INTR 0x1 +#define AD4062_GP_DRDY 0x2 +#define AD4062_GP_STATIC_LOW 0x5 +#define AD4062_GP_STATIC_HIGH 0x6 + +#define AD4062_LIMIT_BITS 12 + +#define AD4062_INTR_EN_NEITHER 0x0 +#define AD4062_INTR_EN_EITHER 0x3 + +#define AD4062_TCONV_NS 270 + +enum ad4062_operation_mode { + AD4062_SAMPLE_MODE = 0x0, + AD4062_BURST_AVERAGING_MODE = 0x1, + AD4062_MONITOR_MODE = 0x3, +}; + +struct ad4062_chip_info { + const struct iio_chan_spec channels[1]; + const char *name; + u16 prod_id; + u16 avg_max; +}; + +enum { + AD4062_SCAN_TYPE_SAMPLE, + AD4062_SCAN_TYPE_BURST_AVG, +}; + +static const struct iio_scan_type ad4062_scan_type_12_s[] = { + [AD4062_SCAN_TYPE_SAMPLE] = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + .endianness = IIO_BE, + }, + [AD4062_SCAN_TYPE_BURST_AVG] = { + .sign = 's', + .realbits = 14, + .storagebits = 16, + .endianness = IIO_BE, + }, +}; + +static const struct iio_scan_type ad4062_scan_type_16_s[] = { + [AD4062_SCAN_TYPE_SAMPLE] = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + [AD4062_SCAN_TYPE_BURST_AVG] = { + .sign = 's', + .realbits = 20, + .storagebits = 32, + .endianness = IIO_BE, + }, +}; + +static const unsigned int ad4062_conversion_freqs[] = { + 2000000, 1000000, 300000, 100000, /* 0 - 3 */ + 33300, 10000, 3000, 500, /* 4 - 7 */ + 333, 250, 200, 166, /* 8 - 11 */ + 140, 124, 111, /* 12 - 15 */ +}; + +struct ad4062_state { + const struct ad4062_chip_info *chip; + const struct ad4062_bus_ops *ops; + enum ad4062_operation_mode mode; + struct work_struct trig_conv; + struct completion completion; + struct iio_trigger *trigger; + struct iio_dev *indio_dev; + struct i3c_device *i3cdev; + struct regmap *regmap; + bool wait_event; + int vref_uV; + unsigned int samp_freqs[ARRAY_SIZE(ad4062_conversion_freqs)]; + bool gpo_irq[2]; + u16 sampling_frequency; + u16 events_frequency; + u8 oversamp_ratio; + u8 conv_sizeof; + u8 conv_addr; + union { + __be32 be32; + __be16 be16; + } buf __aligned(IIO_DMA_MINALIGN); +}; + +static const struct regmap_range ad4062_regmap_rd_ranges[] = { + regmap_reg_range(AD4062_REG_INTERFACE_CONFIG_A, AD4062_REG_DEVICE_GRADE), + regmap_reg_range(AD4062_REG_SCRATCH_PAD, AD4062_REG_INTERFACE_STATUS), + regmap_reg_range(AD4062_REG_MODE_SET, AD4062_REG_ADC_IBI_EN), + regmap_reg_range(AD4062_REG_FUSE_CRC, AD4062_REG_IBI_STATUS), + regmap_reg_range(AD4062_REG_CONV_READ_LSB, AD4062_REG_CONV_AUTO), +}; + +static const struct regmap_access_table ad4062_regmap_rd_table = { + .yes_ranges = ad4062_regmap_rd_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4062_regmap_rd_ranges), +}; + +static const struct regmap_range ad4062_regmap_wr_ranges[] = { + regmap_reg_range(AD4062_REG_INTERFACE_CONFIG_A, AD4062_REG_DEVICE_CONFIG), + regmap_reg_range(AD4062_REG_SCRATCH_PAD, AD4062_REG_SCRATCH_PAD), + regmap_reg_range(AD4062_REG_STREAM_MODE, AD4062_REG_INTERFACE_STATUS), + regmap_reg_range(AD4062_REG_MODE_SET, AD4062_REG_ADC_IBI_EN), + regmap_reg_range(AD4062_REG_FUSE_CRC, AD4062_REG_DEVICE_STATUS), +}; + +static const struct regmap_access_table ad4062_regmap_wr_table = { + .yes_ranges = ad4062_regmap_wr_ranges, + .n_yes_ranges = ARRAY_SIZE(ad4062_regmap_wr_ranges), +}; + +static const struct iio_event_spec ad4062_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_shared_by_all = BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_HYSTERESIS), + }, +}; + +#define AD4062_CHAN(bits) { \ + .type = IIO_VOLTAGE, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBSCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .channel = 0, \ + .event_spec = ad4062_events, \ + .num_event_specs = ARRAY_SIZE(ad4062_events), \ + .has_ext_scan_type = 1, \ + .ext_scan_type = ad4062_scan_type_##bits##_s, \ + .num_ext_scan_type = ARRAY_SIZE(ad4062_scan_type_##bits##_s), \ +} + +static const struct ad4062_chip_info ad4060_chip_info = { + .name = "ad4060", + .channels = { AD4062_CHAN(12) }, + .prod_id = AD4060_PROD_ID, + .avg_max = 256, +}; + +static const struct ad4062_chip_info ad4062_chip_info = { + .name = "ad4062", + .channels = { AD4062_CHAN(16) }, + .prod_id = AD4062_PROD_ID, + .avg_max = 4096, +}; + +static ssize_t sampling_frequency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ad4062_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d\n", ad4062_conversion_freqs[st->events_frequency]); +} + +static int sampling_frequency_store_dispatch(struct iio_dev *indio_dev, + const char *buf) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int val, ret; + + if (st->wait_event) + return -EBUSY; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + st->events_frequency = find_closest_descending(val, ad4062_conversion_freqs, + ARRAY_SIZE(ad4062_conversion_freqs)); + return 0; +} + +static ssize_t sampling_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = sampling_frequency_store_dispatch(indio_dev, buf); + iio_device_release_direct(indio_dev); + return ret ?: len; +} + +static IIO_DEVICE_ATTR_RW(sampling_frequency, 0); + +static ssize_t sampling_frequency_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret = 0; + + for (u8 i = 0; i < ARRAY_SIZE(ad4062_conversion_freqs); i++) + ret += sysfs_emit_at(buf, ret, "%d%s", ad4062_conversion_freqs[i], + i != (ARRAY_SIZE(ad4062_conversion_freqs) - 1) ? " " : "\n"); + return ret; +} + +static IIO_DEVICE_ATTR_RO(sampling_frequency_available, 0); + +static struct attribute *ad4062_event_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad4062_event_attribute_group = { + .attrs = ad4062_event_attributes, +}; + +static int ad4062_set_oversampling_ratio(struct ad4062_state *st, int val, int val2) +{ + const u32 _max = st->chip->avg_max; + const u32 _min = 1; + int ret; + + if (!in_range(val, _min, _max) || val2 != 0) + return -EINVAL; + + /* 1 disables oversampling */ + val = ilog2(val); + if (val == 0) { + st->mode = AD4062_SAMPLE_MODE; + } else { + st->mode = AD4062_BURST_AVERAGING_MODE; + ret = regmap_write(st->regmap, AD4062_REG_AVG_CONFIG, val - 1); + if (ret) + return ret; + } + st->oversamp_ratio = val; + + return 0; +} + +static int ad4062_get_oversampling_ratio(struct ad4062_state *st, int *val) +{ + int ret, buf; + + if (st->mode == AD4062_SAMPLE_MODE) { + *val = 1; + return 0; + } + + ret = regmap_read(st->regmap, AD4062_REG_AVG_CONFIG, &buf); + if (ret) + return ret; + + *val = BIT(buf + 1); + return 0; +} + +static int ad4062_calc_sampling_frequency(unsigned int fosc, unsigned int oversamp_ratio) +{ + /* From datasheet p.31: (n_avg - 1)/fosc + tconv */ + u32 n_avg = BIT(oversamp_ratio) - 1; + u32 period_ns = NSEC_PER_SEC / fosc; + + /* Result is less than 1 Hz */ + if (n_avg >= fosc) + return 1; + + return NSEC_PER_SEC / (n_avg * period_ns + AD4062_TCONV_NS); +} + +static int ad4062_populate_sampling_frequency(struct ad4062_state *st) +{ + for (u8 i = 0; i < ARRAY_SIZE(ad4062_conversion_freqs); i++) + st->samp_freqs[i] = + ad4062_calc_sampling_frequency(ad4062_conversion_freqs[i], + st->oversamp_ratio); + return 0; +} + +static int ad4062_get_sampling_frequency(struct ad4062_state *st, int *val) +{ + int freq = ad4062_conversion_freqs[st->sampling_frequency]; + + *val = ad4062_calc_sampling_frequency(freq, st->oversamp_ratio); + return IIO_VAL_INT; +} + +static int ad4062_set_sampling_frequency(struct ad4062_state *st, int val, int val2) +{ + int ret; + + if (val2 != 0) + return -EINVAL; + + ret = ad4062_populate_sampling_frequency(st); + if (ret) + return ret; + + st->sampling_frequency = + find_closest_descending(val, st->samp_freqs, + ARRAY_SIZE(ad4062_conversion_freqs)); + return 0; +} + +static int ad4062_check_ids(struct ad4062_state *st) +{ + struct device *dev = &st->i3cdev->dev; + int ret; + u16 val; + + ret = regmap_bulk_read(st->regmap, AD4062_REG_PROD_ID_1, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + val = be16_to_cpu(st->buf.be16); + if (val != st->chip->prod_id) + dev_warn(dev, "Production ID x%x does not match known values", val); + + ret = regmap_bulk_read(st->regmap, AD4062_REG_VENDOR_H, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + val = be16_to_cpu(st->buf.be16); + if (val != AD4062_I3C_VENDOR) { + dev_err(dev, "Vendor ID x%x does not match expected value\n", val); + return -ENODEV; + } + + return 0; +} + +static int ad4062_conversion_frequency_set(struct ad4062_state *st, u8 val) +{ + return regmap_write(st->regmap, AD4062_REG_TIMER_CONFIG, + FIELD_PREP(AD4062_REG_TIMER_CONFIG_FS_MASK, val)); +} + +static int ad4062_set_operation_mode(struct ad4062_state *st, + enum ad4062_operation_mode mode) +{ + const unsigned int samp_freq = mode == AD4062_MONITOR_MODE ? + st->events_frequency : st->sampling_frequency; + int ret; + + ret = ad4062_conversion_frequency_set(st, samp_freq); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_MODES, + AD4062_REG_ADC_MODES_MODE_MSK, mode); + if (ret) + return ret; + + if (mode == AD4062_MONITOR_MODE) { + /* Change address pointer to enter monitor mode */ + struct i3c_xfer xfer_trigger = { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }; + st->conv_addr = AD4062_REG_CONV_TRIGGER_32BITS; + return i3c_device_do_xfers(st->i3cdev, &xfer_trigger, 1, I3C_SDR); + } + + return regmap_write(st->regmap, AD4062_REG_MODE_SET, + AD4062_REG_MODE_SET_ENTER_ADC); +} + +static int ad4062_soft_reset(struct ad4062_state *st) +{ + u8 val = AD4062_SOFT_RESET; + int ret; + + ret = regmap_write(st->regmap, AD4062_REG_INTERFACE_CONFIG_A, val); + if (ret) + return ret; + + /* Wait AD4062 treset time, datasheet p8 */ + ndelay(60); + + return 0; +} + +static int ad4062_setup(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const bool *ref_sel) +{ + struct ad4062_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + int ret; + + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_0, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, + AD4062_GP_INTR)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_1, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, + AD4062_GP_DRDY)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_CONFIG, + AD4062_REG_ADC_CONFIG_REF_EN_MSK, + FIELD_PREP(AD4062_REG_ADC_CONFIG_REF_EN_MSK, + *ref_sel)); + if (ret) + return ret; + + ret = regmap_write(st->regmap, AD4062_REG_DEVICE_STATUS, + AD4062_REG_DEVICE_STATUS_DEVICE_RESET); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_INTR_CONF, + AD4062_REG_INTR_CONF_EN_MSK_0, + FIELD_PREP(AD4062_REG_INTR_CONF_EN_MSK_0, + AD4062_INTR_EN_EITHER)); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, AD4062_REG_INTR_CONF, + AD4062_REG_INTR_CONF_EN_MSK_1, + FIELD_PREP(AD4062_REG_INTR_CONF_EN_MSK_1, + AD4062_INTR_EN_NEITHER)); + if (ret) + return ret; + + st->buf.be16 = cpu_to_be16(AD4062_MON_VAL_MIDDLE_POINT); + return regmap_bulk_write(st->regmap, AD4062_REG_MON_VAL, + &st->buf.be16, sizeof(st->buf.be16)); +} + +static irqreturn_t ad4062_irq_handler_thresh(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(indio_dev)); + + return IRQ_HANDLED; +} + +static irqreturn_t ad4062_irq_handler_drdy(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad4062_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev)) + iio_trigger_poll(st->trigger); + else + complete(&st->completion); + + return IRQ_HANDLED; +} + +static void ad4062_ibi_handler(struct i3c_device *i3cdev, + const struct i3c_ibi_payload *payload) +{ + struct ad4062_state *st = i3cdev_get_drvdata(i3cdev); + + if (st->wait_event) { + iio_push_event(st->indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns(st->indio_dev)); + return; + } + if (iio_buffer_enabled(st->indio_dev)) + iio_trigger_poll_nested(st->trigger); + else + complete(&st->completion); +} + +static void ad4062_trigger_work(struct work_struct *work) +{ + struct ad4062_state *st = + container_of(work, struct ad4062_state, trig_conv); + int ret; + + /* + * Read current conversion, if at reg CONV_READ, stop bit triggers + * next sample and does not need writing the address. + */ + struct i3c_xfer xfer_sample = { + .data.in = &st->buf.be32, + .len = st->conv_sizeof, + .rnw = true, + }; + struct i3c_xfer xfer_trigger = { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }; + + ret = i3c_device_do_xfers(st->i3cdev, &xfer_sample, 1, I3C_SDR); + if (ret) + return; + + iio_push_to_buffers_with_ts(st->indio_dev, &st->buf.be32, st->conv_sizeof, + iio_get_time_ns(st->indio_dev)); + if (st->gpo_irq[1]) + return; + + i3c_device_do_xfers(st->i3cdev, &xfer_trigger, 1, I3C_SDR); +} + +static irqreturn_t ad4062_poll_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad4062_state *st = iio_priv(indio_dev); + + iio_trigger_notify_done(indio_dev->trig); + schedule_work(&st->trig_conv); + + return IRQ_HANDLED; +} + +static void ad4062_disable_ibi(void *data) +{ + struct i3c_device *i3cdev = data; + + i3c_device_disable_ibi(i3cdev); +} + +static void ad4062_free_ibi(void *data) +{ + struct i3c_device *i3cdev = data; + + i3c_device_free_ibi(i3cdev); +} + +static int ad4062_request_ibi(struct i3c_device *i3cdev) +{ + const struct i3c_ibi_setup ibireq = { + .max_payload_len = 1, + .num_slots = 1, + .handler = ad4062_ibi_handler, + }; + int ret; + + ret = i3c_device_request_ibi(i3cdev, &ibireq); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&i3cdev->dev, ad4062_free_ibi, i3cdev); + if (ret) + return ret; + + ret = i3c_device_enable_ibi(i3cdev); + if (ret) + return ret; + + return devm_add_action_or_reset(&i3cdev->dev, ad4062_disable_ibi, i3cdev); +} + +static int ad4062_request_irq(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + struct device *dev = &st->i3cdev->dev; + int ret; + + ret = fwnode_irq_get_byname(dev_fwnode(&st->i3cdev->dev), "gp0"); + if (ret == -EPROBE_DEFER) + return ret; + + if (ret < 0) { + st->gpo_irq[0] = false; + ret = regmap_update_bits(st->regmap, AD4062_REG_ADC_IBI_EN, + AD4062_REG_ADC_IBI_EN_MAX | AD4062_REG_ADC_IBI_EN_MIN, + AD4062_REG_ADC_IBI_EN_MAX | AD4062_REG_ADC_IBI_EN_MIN); + if (ret) + return ret; + } else { + st->gpo_irq[0] = true; + ret = devm_request_threaded_irq(dev, ret, NULL, + ad4062_irq_handler_thresh, + IRQF_ONESHOT, indio_dev->name, + indio_dev); + if (ret) + return ret; + } + + ret = fwnode_irq_get_byname(dev_fwnode(&st->i3cdev->dev), "gp1"); + if (ret == -EPROBE_DEFER) + return ret; + + if (ret < 0) { + st->gpo_irq[1] = false; + return regmap_update_bits(st->regmap, AD4062_REG_ADC_IBI_EN, + AD4062_REG_ADC_IBI_EN_CONV_TRIGGER, + AD4062_REG_ADC_IBI_EN_CONV_TRIGGER); + } + st->gpo_irq[1] = true; + + return devm_request_threaded_irq(dev, ret, + ad4062_irq_handler_drdy, + NULL, IRQF_ONESHOT, indio_dev->name, + indio_dev); +} + +static const struct iio_trigger_ops ad4062_trigger_ops = { + .validate_device = &iio_trigger_validate_own_device, +}; + +static int ad4062_request_trigger(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + struct device *dev = &st->i3cdev->dev; + int ret; + + st->trigger = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trigger) + return -ENOMEM; + + st->trigger->ops = &ad4062_trigger_ops; + iio_trigger_set_drvdata(st->trigger, indio_dev); + + ret = devm_iio_trigger_register(dev, st->trigger); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(st->trigger); + + return 0; +} + +static const int ad4062_oversampling_avail[] = { + 1, 2, 4, 8, 16, 32, 64, 128, /* 0 - 7 */ + 256, 512, 1024, 2048, 4096, /* 8 - 12 */ +}; + +static int ad4062_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *len, long mask) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ad4062_oversampling_avail; + *len = ARRAY_SIZE(ad4062_oversampling_avail); + *len -= st->chip->avg_max == 256 ? 4 : 0; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ad4062_populate_sampling_frequency(st); + if (ret) + return ret; + *vals = st->samp_freqs; + *len = st->oversamp_ratio ? ARRAY_SIZE(ad4062_conversion_freqs) : 1; + *type = IIO_VAL_INT; + + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad4062_get_chan_scale(struct iio_dev *indio_dev, int *val, int *val2) +{ + struct ad4062_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; + + /* + * In burst averaging mode the averaging filter accumulates resulting + * in a sample with increased precision. + */ + scan_type = iio_get_current_scan_type(indio_dev, st->chip->channels); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); + + *val = (st->vref_uV * 2) / (MICRO / MILLI); /* signed */ + *val2 = scan_type->realbits - 1; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int ad4062_get_chan_calibscale(struct ad4062_state *st, int *val, int *val2) +{ + int ret; + + ret = regmap_bulk_read(st->regmap, AD4062_REG_MON_VAL, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + /* From datasheet: code out = code in × mon_val/0x8000 */ + *val = be16_to_cpu(st->buf.be16) * 2; + *val2 = 16; + + return IIO_VAL_FRACTIONAL_LOG2; +} + +static int ad4062_set_chan_calibscale(struct ad4062_state *st, int gain_int, + int gain_frac) +{ + /* Divide numerator and denumerator by known great common divider */ + const u32 mon_val = AD4062_MON_VAL_MIDDLE_POINT / 64; + const u32 micro = MICRO / 64; + const u32 gain_fp = gain_int * MICRO + gain_frac; + const u32 reg_val = DIV_ROUND_CLOSEST(gain_fp * mon_val, micro); + int ret; + + /* Checks if the gain is in range and the value fits the field */ + if (gain_int < 0 || gain_int > 1 || reg_val > BIT(16) - 1) + return -EINVAL; + + st->buf.be16 = cpu_to_be16(reg_val); + ret = regmap_bulk_write(st->regmap, AD4062_REG_MON_VAL, + &st->buf.be16, sizeof(st->buf.be16)); + if (ret) + return ret; + + /* Enable scale if gain is not equal to one */ + return regmap_update_bits(st->regmap, AD4062_REG_ADC_CONFIG, + AD4062_REG_ADC_CONFIG_SCALE_EN_MSK, + FIELD_PREP(AD4062_REG_ADC_CONFIG_SCALE_EN_MSK, + !(gain_int == 1 && gain_frac == 0))); +} + +static int ad4062_read_chan_raw(struct ad4062_state *st, int *val) +{ + struct i3c_device *i3cdev = st->i3cdev; + struct i3c_xfer xfer_trigger = { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }; + struct i3c_xfer xfer_sample = { + .data.in = &st->buf.be32, + .len = sizeof(st->buf.be32), + .rnw = true, + }; + int ret; + + PM_RUNTIME_ACQUIRE(&st->i3cdev->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + ret = ad4062_set_operation_mode(st, st->mode); + if (ret) + return ret; + + reinit_completion(&st->completion); + /* Change address pointer to trigger conversion */ + st->conv_addr = AD4062_REG_CONV_TRIGGER_32BITS; + ret = i3c_device_do_xfers(i3cdev, &xfer_trigger, 1, I3C_SDR); + if (ret) + return ret; + /* + * Single sample read should be used only for oversampling and + * sampling frequency pairs that take less than 1 sec. + */ + ret = wait_for_completion_timeout(&st->completion, + msecs_to_jiffies(1000)); + if (!ret) + return -ETIMEDOUT; + + ret = i3c_device_do_xfers(i3cdev, &xfer_sample, 1, I3C_SDR); + if (ret) + return ret; + *val = be32_to_cpu(st->buf.be32); + return 0; +} + +static int ad4062_read_raw_dispatch(struct ad4062_state *st, + int *val, int *val2, long info) +{ + if (st->wait_event) + return -EBUSY; + + switch (info) { + case IIO_CHAN_INFO_RAW: + return ad4062_read_chan_raw(st, val); + + case IIO_CHAN_INFO_CALIBSCALE: + return ad4062_get_chan_calibscale(st, val, val2); + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return ad4062_get_oversampling_ratio(st, val); + + default: + return -EINVAL; + } +} + +static int ad4062_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_SCALE: + return ad4062_get_chan_scale(indio_dev, val, val2); + + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4062_get_sampling_frequency(st, val); + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_read_raw_dispatch(st, val, val2, info); + iio_device_release_direct(indio_dev); + return ret ?: IIO_VAL_INT; +} + +static int ad4062_write_raw_dispatch(struct ad4062_state *st, int val, int val2, + long info) +{ + if (st->wait_event) + return -EBUSY; + + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return ad4062_set_oversampling_ratio(st, val, val2); + + case IIO_CHAN_INFO_CALIBSCALE: + return ad4062_set_chan_calibscale(st, val, val2); + + default: + return -EINVAL; + } +}; + +static int ad4062_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4062_set_sampling_frequency(st, val, val2); + } + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_write_raw_dispatch(st, val, val2, info); + iio_device_release_direct(indio_dev); + return ret; +} + +static int pm_ad4062_monitor_mode_enable(struct ad4062_state *st) +{ + int ret; + + PM_RUNTIME_ACQUIRE(&st->i3cdev->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + return ad4062_set_operation_mode(st, AD4062_MONITOR_MODE); +} + +static int ad4062_monitor_mode_enable(struct ad4062_state *st) +{ + int ret; + + ret = pm_ad4062_monitor_mode_enable(st); + if (ret) + return ret; + + pm_runtime_get_noresume(&st->i3cdev->dev); + return 0; +} + +static int ad4062_monitor_mode_disable(struct ad4062_state *st) +{ + pm_runtime_put_autosuspend(&st->i3cdev->dev); + return 0; +} + +static int ad4062_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 ad4062_state *st = iio_priv(indio_dev); + + return st->wait_event; +} + +static int ad4062_write_event_config_dispatch(struct iio_dev *indio_dev, + bool state) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + if (st->wait_event == state) + ret = 0; + else if (state) + ret = ad4062_monitor_mode_enable(st); + else + ret = ad4062_monitor_mode_disable(st); + if (ret) + return ret; + + st->wait_event = state; + return 0; +} + +static int ad4062_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; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_write_event_config_dispatch(indio_dev, state); + iio_device_release_direct(indio_dev); + return ret; +} + +static int __ad4062_read_event_info_value(struct ad4062_state *st, + enum iio_event_direction dir, int *val) +{ + int ret; + u8 reg; + + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_LIMIT; + else + reg = AD4062_REG_MIN_LIMIT; + + ret = regmap_bulk_read(st->regmap, reg, &st->buf.be16, + sizeof(st->buf.be16)); + if (ret) + return ret; + + *val = sign_extend32(be16_to_cpu(st->buf.be16), AD4062_LIMIT_BITS - 1); + + return 0; +} + +static int __ad4062_read_event_info_hysteresis(struct ad4062_state *st, + enum iio_event_direction dir, int *val) +{ + u8 reg; + + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_HYST; + else + reg = AD4062_REG_MIN_HYST; + return regmap_read(st->regmap, reg, val); +} + +static int ad4062_read_event_config_dispatch(struct iio_dev *indio_dev, + enum iio_event_direction dir, + enum iio_event_info info, int *val) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + if (st->wait_event) + return -EBUSY; + + switch (info) { + case IIO_EV_INFO_VALUE: + return __ad4062_read_event_info_value(st, dir, val); + case IIO_EV_INFO_HYSTERESIS: + return __ad4062_read_event_info_hysteresis(st, dir, val); + default: + return -EINVAL; + } +} + +static int ad4062_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) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_read_event_config_dispatch(indio_dev, dir, info, val); + iio_device_release_direct(indio_dev); + return ret ?: IIO_VAL_INT; +} + +static int __ad4062_write_event_info_value(struct ad4062_state *st, + enum iio_event_direction dir, int val) +{ + u8 reg; + + if (val != sign_extend32(val, AD4062_LIMIT_BITS - 1)) + return -EINVAL; + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_LIMIT; + else + reg = AD4062_REG_MIN_LIMIT; + st->buf.be16 = cpu_to_be16(val); + + return regmap_bulk_write(st->regmap, reg, &st->buf.be16, + sizeof(st->buf.be16)); +} + +static int __ad4062_write_event_info_hysteresis(struct ad4062_state *st, + enum iio_event_direction dir, int val) +{ + u8 reg; + + if (val > BIT(7) - 1) + return -EINVAL; + if (dir == IIO_EV_DIR_RISING) + reg = AD4062_REG_MAX_HYST; + else + reg = AD4062_REG_MIN_HYST; + + return regmap_write(st->regmap, reg, val); +} + +static int ad4062_write_event_value_dispatch(struct iio_dev *indio_dev, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + if (st->wait_event) + return -EBUSY; + + switch (type) { + case IIO_EV_TYPE_THRESH: + switch (info) { + case IIO_EV_INFO_VALUE: + return __ad4062_write_event_info_value(st, dir, val); + case IIO_EV_INFO_HYSTERESIS: + return __ad4062_write_event_info_hysteresis(st, dir, val); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad4062_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) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad4062_write_event_value_dispatch(indio_dev, type, dir, info, val); + iio_device_release_direct(indio_dev); + return ret; +} + +/* + * The AD4062 in burst averaging mode increases realbits from 16-bits to + * 20-bits, increasing the storagebits from 16-bits to 32-bits. + */ +static inline size_t ad4062_sizeof_storagebits(struct ad4062_state *st) +{ + const struct iio_scan_type *scan_type = + iio_get_current_scan_type(st->indio_dev, st->chip->channels); + + return BITS_TO_BYTES(scan_type->storagebits); +} + +/* Read registers only with realbits (no sign extension bytes) */ +static inline size_t ad4062_get_conv_addr(struct ad4062_state *st, size_t _sizeof) +{ + if (st->gpo_irq[1]) + return _sizeof == sizeof(u32) ? AD4062_REG_CONV_READ_32BITS : + AD4062_REG_CONV_READ_16BITS; + return _sizeof == sizeof(u32) ? AD4062_REG_CONV_TRIGGER_32BITS : + AD4062_REG_CONV_TRIGGER_16BITS; +} + +static int pm_ad4062_triggered_buffer_postenable(struct ad4062_state *st) +{ + int ret; + + PM_RUNTIME_ACQUIRE(&st->i3cdev->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret) + return ret; + + if (st->wait_event) + return -EBUSY; + + ret = ad4062_set_operation_mode(st, st->mode); + if (ret) + return ret; + + st->conv_sizeof = ad4062_sizeof_storagebits(st); + st->conv_addr = ad4062_get_conv_addr(st, st->conv_sizeof); + /* CONV_READ requires read to trigger first sample. */ + struct i3c_xfer xfer_sample[2] = { + { + .data.out = &st->conv_addr, + .len = sizeof(st->conv_addr), + .rnw = false, + }, + { + .data.in = &st->buf.be32, + .len = sizeof(st->buf.be32), + .rnw = true, + } + }; + + return i3c_device_do_xfers(st->i3cdev, xfer_sample, + st->gpo_irq[1] ? 2 : 1, I3C_SDR); +} + +static int ad4062_triggered_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + int ret; + + ret = pm_ad4062_triggered_buffer_postenable(st); + if (ret) + return ret; + + pm_runtime_get_noresume(&st->i3cdev->dev); + return 0; +} + +static int ad4062_triggered_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + pm_runtime_put_autosuspend(&st->i3cdev->dev); + return 0; +} + +static const struct iio_buffer_setup_ops ad4062_triggered_buffer_setup_ops = { + .postenable = &ad4062_triggered_buffer_postenable, + .predisable = &ad4062_triggered_buffer_predisable, +}; + +static int ad4062_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + else + return regmap_write(st->regmap, reg, writeval); +} + +static int ad4062_get_current_scan_type(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ad4062_state *st = iio_priv(indio_dev); + + return st->mode == AD4062_BURST_AVERAGING_MODE ? + AD4062_SCAN_TYPE_BURST_AVG : + AD4062_SCAN_TYPE_SAMPLE; +} + +static const struct iio_info ad4062_info = { + .read_raw = ad4062_read_raw, + .write_raw = ad4062_write_raw, + .read_avail = ad4062_read_avail, + .read_event_config = ad4062_read_event_config, + .write_event_config = ad4062_write_event_config, + .read_event_value = ad4062_read_event_value, + .write_event_value = ad4062_write_event_value, + .event_attrs = &ad4062_event_attribute_group, + .get_current_scan_type = ad4062_get_current_scan_type, + .debugfs_reg_access = ad4062_debugfs_reg_access, +}; + +static const struct regmap_config ad4062_regmap_config = { + .name = "ad4062", + .reg_bits = 8, + .val_bits = 8, + .max_register = AD4062_MAX_REG, + .rd_table = &ad4062_regmap_rd_table, + .wr_table = &ad4062_regmap_wr_table, + .can_sleep = true, +}; + +static int ad4062_regulators_get(struct ad4062_state *st, bool *ref_sel) +{ + struct device *dev = &st->i3cdev->dev; + int ret; + + ret = devm_regulator_get_enable(dev, "vio"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable vio voltage\n"); + + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref"); + *ref_sel = st->vref_uV == -ENODEV; + if (st->vref_uV < 0 && !*ref_sel) + return dev_err_probe(dev, st->vref_uV, + "Failed to enable and read ref voltage\n"); + + if (*ref_sel) { + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vdd"); + if (st->vref_uV < 0) + return dev_err_probe(dev, st->vref_uV, + "Failed to enable and read vdd voltage\n"); + } else { + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable vdd regulator\n"); + } + + return 0; +} + +static int ad4062_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int ad4062_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct ad4062_state *st = gpiochip_get_data(gc); + unsigned int reg_val = value ? AD4062_GP_STATIC_HIGH : AD4062_GP_STATIC_LOW; + + if (offset) + return regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_1, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, reg_val)); + else + return regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_0, + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, reg_val)); +} + +static int ad4062_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct ad4062_state *st = gpiochip_get_data(gc); + unsigned int reg_val; + int ret; + + ret = regmap_read(st->regmap, AD4062_REG_GP_CONF, ®_val); + if (ret) + return ret; + + if (offset) + reg_val = FIELD_GET(AD4062_REG_GP_CONF_MODE_MSK_1, reg_val); + else + reg_val = FIELD_GET(AD4062_REG_GP_CONF_MODE_MSK_0, reg_val); + + return reg_val == AD4062_GP_STATIC_HIGH; +} + +static void ad4062_gpio_disable(void *data) +{ + struct ad4062_state *st = data; + u8 val = FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, AD4062_GP_DISABLED) | + FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, AD4062_GP_DISABLED); + + regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + AD4062_REG_GP_CONF_MODE_MSK_1 | AD4062_REG_GP_CONF_MODE_MSK_0, + val); +} + +static int ad4062_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct ad4062_state *st = gpiochip_get_data(gc); + + bitmap_zero(valid_mask, ngpios); + + for (unsigned int i = 0; i < ARRAY_SIZE(st->gpo_irq); i++) + __assign_bit(i, valid_mask, !st->gpo_irq[i]); + + return 0; +} + +static int ad4062_gpio_init(struct ad4062_state *st) +{ + struct device *dev = &st->i3cdev->dev; + struct gpio_chip *gc; + u8 val, mask; + int ret; + + if (!device_property_read_bool(dev, "gpio-controller")) + return 0; + + gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL); + if (!gc) + return -ENOMEM; + + val = 0; + mask = 0; + if (!st->gpo_irq[0]) { + mask |= AD4062_REG_GP_CONF_MODE_MSK_0; + val |= FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_0, AD4062_GP_STATIC_LOW); + } + if (!st->gpo_irq[1]) { + mask |= AD4062_REG_GP_CONF_MODE_MSK_1; + val |= FIELD_PREP(AD4062_REG_GP_CONF_MODE_MSK_1, AD4062_GP_STATIC_LOW); + } + + ret = regmap_update_bits(st->regmap, AD4062_REG_GP_CONF, + mask, val); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, ad4062_gpio_disable, st); + if (ret) + return ret; + + gc->parent = dev; + gc->label = st->chip->name; + gc->owner = THIS_MODULE; + gc->base = -1; + gc->ngpio = 2; + gc->init_valid_mask = ad4062_gpio_init_valid_mask; + gc->get_direction = ad4062_gpio_get_direction; + gc->set = ad4062_gpio_set; + gc->get = ad4062_gpio_get; + gc->can_sleep = true; + + ret = devm_gpiochip_add_data(dev, gc, st); + if (ret) + return dev_err_probe(dev, ret, "Unable to register GPIO chip\n"); + + return 0; +} + +static const struct i3c_device_id ad4062_id_table[] = { + I3C_DEVICE(AD4062_I3C_VENDOR, AD4060_PROD_ID, &ad4060_chip_info), + I3C_DEVICE(AD4062_I3C_VENDOR, AD4062_PROD_ID, &ad4062_chip_info), + { } +}; +MODULE_DEVICE_TABLE(i3c, ad4062_id_table); + +static int ad4062_probe(struct i3c_device *i3cdev) +{ + const struct i3c_device_id *id = i3c_device_match_id(i3cdev, ad4062_id_table); + const struct ad4062_chip_info *chip = id->data; + struct device *dev = &i3cdev->dev; + struct iio_dev *indio_dev; + struct ad4062_state *st; + bool ref_sel; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->i3cdev = i3cdev; + i3cdev_set_drvdata(i3cdev, st); + init_completion(&st->completion); + + ret = ad4062_regulators_get(st, &ref_sel); + if (ret) + return ret; + + st->regmap = devm_regmap_init_i3c(i3cdev, &ad4062_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + st->mode = AD4062_SAMPLE_MODE; + st->wait_event = false; + st->chip = chip; + st->sampling_frequency = 0; + st->events_frequency = 0; + st->oversamp_ratio = 0; + st->indio_dev = indio_dev; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = 1; + indio_dev->info = &ad4062_info; + indio_dev->name = chip->name; + indio_dev->channels = chip->channels; + + ret = ad4062_soft_reset(st); + if (ret) + return dev_err_probe(dev, ret, "AD4062 failed to soft reset\n"); + + ret = ad4062_check_ids(st); + if (ret) + return ret; + + ret = ad4062_setup(indio_dev, indio_dev->channels, &ref_sel); + if (ret) + return ret; + + ret = ad4062_request_irq(indio_dev); + if (ret) + return ret; + + ret = ad4062_request_trigger(indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(&i3cdev->dev, indio_dev, + iio_pollfunc_store_time, + ad4062_poll_handler, + &ad4062_triggered_buffer_setup_ops); + if (ret) + return ret; + + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable pm_runtime\n"); + + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + + ret = ad4062_request_ibi(i3cdev); + if (ret) + return dev_err_probe(dev, ret, "Failed to request i3c ibi\n"); + + ret = ad4062_gpio_init(st); + if (ret) + return ret; + + ret = devm_work_autocancel(dev, &st->trig_conv, ad4062_trigger_work); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int ad4062_runtime_suspend(struct device *dev) +{ + struct ad4062_state *st = dev_get_drvdata(dev); + + return regmap_write(st->regmap, AD4062_REG_DEVICE_CONFIG, + FIELD_PREP(AD4062_REG_DEVICE_CONFIG_POWER_MODE_MSK, + AD4062_REG_DEVICE_CONFIG_LOW_POWER_MODE)); +} + +static int ad4062_runtime_resume(struct device *dev) +{ + struct ad4062_state *st = dev_get_drvdata(dev); + int ret; + + ret = regmap_clear_bits(st->regmap, AD4062_REG_DEVICE_CONFIG, + AD4062_REG_DEVICE_CONFIG_POWER_MODE_MSK); + if (ret) + return ret; + + /* Wait device functional blocks to power up */ + fsleep(3 * USEC_PER_MSEC); + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(ad4062_pm_ops, + ad4062_runtime_suspend, ad4062_runtime_resume, NULL); + +static struct i3c_driver ad4062_driver = { + .driver = { + .name = "ad4062", + .pm = pm_ptr(&ad4062_pm_ops), + }, + .probe = ad4062_probe, + .id_table = ad4062_id_table, +}; +module_i3c_driver(ad4062_driver); + +MODULE_AUTHOR("Jorge Marques <jorge.marques@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD4062"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/ad4134.c b/drivers/iio/adc/ad4134.c new file mode 100644 index 000000000000..e42ee328fcbf --- /dev/null +++ b/drivers/iio/adc/ad4134.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2026 Analog Devices, Inc. + * Author: Marcelo Schmitt <marcelo.schmitt@analog.com> + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/crc8.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/iio.h> +#include <linux/iio/types.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/spi/spi.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <linux/units.h> + +#define AD4134_RESET_TIME_US (10 * USEC_PER_SEC) + +#define AD4134_REG_READ_MASK BIT(7) +#define AD4134_SPI_MAX_XFER_LEN 3 + +#define AD4134_EXT_CLOCK_MHZ (48 * HZ_PER_MHZ) + +#define AD4134_NUM_CHANNELS 4 +#define AD4134_CHAN_PRECISION_BITS 24 + +#define AD4134_IFACE_CONFIG_A_REG 0x00 +#define AD4134_IFACE_CONFIG_B_REG 0x01 +#define AD4134_IFACE_CONFIG_B_SINGLE_INSTR BIT(7) + +#define AD4134_DEVICE_CONFIG_REG 0x02 +#define AD4134_DEVICE_CONFIG_POWER_MODE_MASK BIT(0) +#define AD4134_POWER_MODE_HIGH_PERF 0x1 + +#define AD4134_SILICON_REV_REG 0x07 +#define AD4134_SCRATCH_PAD_REG 0x0A +#define AD4134_STREAM_MODE_REG 0x0E +#define AD4134_SDO_PIN_SRC_SEL_REG 0x10 +#define AD4134_SDO_PIN_SRC_SEL_SDO_SEL_MASK BIT(2) + +#define AD4134_DATA_PACKET_CONFIG_REG 0x11 +#define AD4134_DATA_PACKET_CONFIG_FRAME_MASK GENMASK(5, 4) +#define AD4134_DATA_PACKET_24BIT_FRAME 0x2 + +#define AD4134_DIG_IF_CFG_REG 0x12 +#define AD4134_DIF_IF_CFG_FORMAT_MASK GENMASK(1, 0) +#define AD4134_DATA_FORMAT_SINGLE_CH_MODE 0x0 + +#define AD4134_PW_DOWN_CTRL_REG 0x13 +#define AD4134_DEVICE_STATUS_REG 0x15 +#define AD4134_ODR_VAL_INT_LSB_REG 0x16 +#define AD4134_CH3_OFFSET_MSB_REG 0x3E +#define AD4134_AIN_OR_ERROR_REG 0x48 + +/* + * AD4134 register map ends at address 0x48 and there is no register for + * retrieving ADC sample data. Though, to make use of Linux regmap API both + * for register access and sample read, we define one virtual register for each + * ADC channel. AD4134_CH_VREG(x) maps a channel number to it's virtual register + * address while AD4134_VREG_CH(x) tells which channel given the address. + */ +#define AD4134_CH_VREG(x) ((x) + 0x50) +#define AD4134_VREG_CH(x) ((x) - 0x50) + +#define AD4134_SPI_CRC_POLYNOM 0x07 +#define AD4134_SPI_CRC_INIT_VALUE 0xA5 +static unsigned char ad4134_spi_crc_table[CRC8_TABLE_SIZE]; + +#define AD4134_CHANNEL(_index) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec ad4134_chan_set[] = { + AD4134_CHANNEL(0), + AD4134_CHANNEL(1), + AD4134_CHANNEL(2), + AD4134_CHANNEL(3), +}; + +struct ad4134_state { + struct spi_device *spi; + struct regmap *regmap; + unsigned long sys_clk_hz; + struct gpio_desc *odr_gpio; + int refin_mv; + /* + * DMA (thus cache coherency maintenance) requires the transfer buffers + * to live in their own cache lines. + */ + u8 rx_buf[AD4134_SPI_MAX_XFER_LEN] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[AD4134_SPI_MAX_XFER_LEN]; +}; + +static const struct regmap_range ad4134_regmap_rd_range[] = { + regmap_reg_range(AD4134_IFACE_CONFIG_A_REG, AD4134_SILICON_REV_REG), + regmap_reg_range(AD4134_SCRATCH_PAD_REG, AD4134_PW_DOWN_CTRL_REG), + regmap_reg_range(AD4134_DEVICE_STATUS_REG, AD4134_AIN_OR_ERROR_REG), + regmap_reg_range(AD4134_CH_VREG(0), AD4134_CH_VREG(AD4134_NUM_CHANNELS)), +}; + +static const struct regmap_range ad4134_regmap_wr_range[] = { + regmap_reg_range(AD4134_IFACE_CONFIG_A_REG, AD4134_DEVICE_CONFIG_REG), + regmap_reg_range(AD4134_SCRATCH_PAD_REG, AD4134_SCRATCH_PAD_REG), + regmap_reg_range(AD4134_STREAM_MODE_REG, AD4134_PW_DOWN_CTRL_REG), + regmap_reg_range(AD4134_ODR_VAL_INT_LSB_REG, AD4134_CH3_OFFSET_MSB_REG), +}; + +static const struct regmap_access_table ad4134_regmap_rd_table = { + .yes_ranges = ad4134_regmap_rd_range, + .n_yes_ranges = ARRAY_SIZE(ad4134_regmap_rd_range), +}; + +static const struct regmap_access_table ad4134_regmap_wr_table = { + .yes_ranges = ad4134_regmap_wr_range, + .n_yes_ranges = ARRAY_SIZE(ad4134_regmap_wr_range), +}; + +static int ad4134_calc_spi_crc(u8 inst, u8 data) +{ + u8 buf[] = { inst, data }; + + return crc8(ad4134_spi_crc_table, buf, ARRAY_SIZE(buf), + AD4134_SPI_CRC_INIT_VALUE); +} + +static void ad4134_prepare_spi_tx_buf(u8 inst, u8 data, u8 *buf) +{ + buf[0] = inst; + buf[1] = data; + buf[2] = ad4134_calc_spi_crc(inst, data); +} + +static int ad4134_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct ad4134_state *st = context; + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + .len = AD4134_SPI_MAX_XFER_LEN, + }; + int ret; + + ad4134_prepare_spi_tx_buf(reg, val, st->tx_buf); + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) + return ret; + + if (st->rx_buf[2] != st->tx_buf[2]) + dev_dbg(&st->spi->dev, "reg write CRC check failed\n"); + + return 0; +} + +static int ad4134_data_read(struct ad4134_state *st, unsigned int reg, + unsigned int *val) +{ + unsigned int i; + int ret; + + /* + * To be able to read data from all 4 channels through a single line, we + * set DOUTx output format to 0 in the digital interface config register + * (0x12). With that, data from all four channels is serialized and + * output on DOUT0. During the probe, we also set SDO_PIN_SRC_SEL in + * DEVICE_CONFIG_1 register to duplicate DOUT0 on the SDO pin. Combined, + * those configurations enable ADC data read through a conventional SPI + * interface. Now we read data from all channels but keep only the bits + * from the requested one. + */ + for (i = 0; i < ARRAY_SIZE(ad4134_chan_set); i++) { + ret = spi_write_then_read(st->spi, NULL, 0, st->rx_buf, + BITS_TO_BYTES(AD4134_CHAN_PRECISION_BITS)); + if (ret) + return ret; + + /* + * AD4134 has a built-in feature that flags when data transfers + * don't run enough clock cycles to read the entire data frame. + * Clock out data from all channels to avoid that. + */ + if (i == AD4134_VREG_CH(reg)) + *val = get_unaligned_be24(st->rx_buf); + } + + return 0; +} + +static int ad4134_register_read(struct ad4134_state *st, unsigned int reg, + unsigned int *val) +{ + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + .len = AD4134_SPI_MAX_XFER_LEN, + }; + unsigned int inst; + int ret; + + inst = AD4134_REG_READ_MASK | reg; + ad4134_prepare_spi_tx_buf(inst, 0, st->tx_buf); + + ret = spi_sync_transfer(st->spi, &xfer, 1); + if (ret) + return ret; + + *val = st->rx_buf[1]; + + /* Check CRC */ + if (st->rx_buf[2] != st->tx_buf[2]) + dev_dbg(&st->spi->dev, "reg read CRC check failed\n"); + + return 0; +} + +static int ad4134_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct ad4134_state *st = context; + + if (reg >= AD4134_CH_VREG(0)) + return ad4134_data_read(st, reg, val); + + return ad4134_register_read(st, reg, val); +} + +static const struct regmap_config ad4134_regmap_config = { + .reg_read = ad4134_reg_read, + .reg_write = ad4134_reg_write, + .rd_table = &ad4134_regmap_rd_table, + .wr_table = &ad4134_regmap_wr_table, + .max_register = AD4134_CH_VREG(ARRAY_SIZE(ad4134_chan_set)), +}; + +static int ad4134_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad4134_state *st = iio_priv(indio_dev); + int ret; + + switch (info) { + case IIO_CHAN_INFO_RAW: + gpiod_set_value_cansleep(st->odr_gpio, 1); + /* + * For slave mode gated DCLK (data sheet page 11), the minimum + * ODR high time is 3 * tDIGCLK. The internal digital clock + * period is tDIGCLK = 1/fDIGCLK = 2/fSYSCLK. + * The System clock frequency (fSYSCLK) is typically 48 MHz. + * Thus, ODR high time = 3 * (2 / (48 * HZ_PER_MHZ)) + * ODR high time = 0.000000125 s = 125 ns + * 1 micro second should be more than enough. Not worth it + * tweaking for shorter dealy since this is not a fast data path. + */ + fsleep(1); + gpiod_set_value_cansleep(st->odr_gpio, 0); + ret = regmap_read(st->regmap, AD4134_CH_VREG(chan->channel), val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->refin_mv; + *val2 = AD4134_CHAN_PRECISION_BITS - 1; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int ad4134_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct ad4134_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int ad4134_min_io_mode_setup(struct ad4134_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + st->odr_gpio = devm_gpiod_get(dev, "odr", GPIOD_OUT_LOW); + if (IS_ERR(st->odr_gpio)) + return dev_err_probe(dev, PTR_ERR(st->odr_gpio), + "failed to get ODR GPIO\n"); + + ret = regmap_update_bits(st->regmap, AD4134_DIG_IF_CFG_REG, + AD4134_DIF_IF_CFG_FORMAT_MASK, + FIELD_PREP(AD4134_DIF_IF_CFG_FORMAT_MASK, + AD4134_DATA_FORMAT_SINGLE_CH_MODE)); + if (ret) + return dev_err_probe(dev, ret, + "failed to set single channel mode\n"); + + ret = regmap_set_bits(st->regmap, AD4134_SDO_PIN_SRC_SEL_REG, + AD4134_SDO_PIN_SRC_SEL_SDO_SEL_MASK); + if (ret) + return dev_err_probe(dev, ret, + "failed to set SDO source selection\n"); + + return regmap_set_bits(st->regmap, AD4134_IFACE_CONFIG_B_REG, + AD4134_IFACE_CONFIG_B_SINGLE_INSTR); +} + +static const struct iio_info ad4134_info = { + .read_raw = ad4134_read_raw, + .debugfs_reg_access = ad4134_debugfs_reg_access, +}; + +static const char * const ad4143_required_regulators[] = { + "avdd5", "dvdd5", "iovdd", +}; + +static const char * const ad4143_optional_regulators[] = { + "avdd1v8", "dvdd1v8", "clkvdd", +}; + +static int ad4134_regulator_setup(struct ad4134_state *st) +{ + struct device *dev = &st->spi->dev; + int ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4143_required_regulators), + ad4143_required_regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to enable power supplies\n"); + + /* Required regulator that we need to read the voltage */ + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get REFIN voltage.\n"); + + st->refin_mv = ret / (MICRO / MILLI); + + ret = devm_regulator_get_enable_optional(dev, "ldoin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to enable ldoin supply\n"); + + /* If ldoin was provided, then use the use the internal LDO regulators */ + if (ret == 0) + return 0; + + /* + * If ldoin is not provided, then avdd1v8, dvdd1v8, and clkvdd are + * required. + */ + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4143_optional_regulators), + ad4143_optional_regulators); + if (ret) + return dev_err_probe(dev, ret, "failed to enable 1V8 power supplies\n"); + + return 0; +} + +static int ad4134_clock_select(struct ad4134_state *st) +{ + struct device *dev = &st->spi->dev; + struct clk *xtal_clk, *clkin_clk; + + /* + * AD4134 requires one external clock source and only one external clock + * source can be provided at a time. Try to get a crystal provided clock. + * If that fails, try to get a CMOS clock. + */ + xtal_clk = devm_clk_get_optional_enabled(dev, "xtal"); + if (!xtal_clk) + xtal_clk = devm_clk_get_optional_enabled(dev, "xtal"); + if (IS_ERR(xtal_clk)) + return dev_err_probe(dev, PTR_ERR(xtal_clk), + "failed to get xtal\n"); + + clkin_clk = devm_clk_get_optional_enabled(dev, "clkin"); + if (!clkin_clk) + clkin_clk = devm_clk_get_optional_enabled(dev, "clkin"); + if (IS_ERR(clkin_clk)) + return dev_err_probe(dev, PTR_ERR(clkin_clk), + "failed to get clkin\n"); + + st->sys_clk_hz = clk_get_rate(xtal_clk) | clk_get_rate(clkin_clk); + if (st->sys_clk_hz != AD4134_EXT_CLOCK_MHZ) + dev_warn(dev, "invalid external clock frequency %lu\n", + st->sys_clk_hz); + + return 0; +} + +static int ad4134_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct reset_control *rst; + struct iio_dev *indio_dev; + struct ad4134_state *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + indio_dev->name = "ad4134"; + indio_dev->channels = ad4134_chan_set; + indio_dev->num_channels = ARRAY_SIZE(ad4134_chan_set); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ad4134_info; + + ret = ad4134_regulator_setup(st); + if (ret) + return ret; + + ret = ad4134_clock_select(st); + if (ret) + return ret; + + rst = devm_reset_control_get_optional_exclusive_deasserted(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), + "failed to get and deassert reset\n"); + + crc8_populate_msb(ad4134_spi_crc_table, AD4134_SPI_CRC_POLYNOM); + + st->regmap = devm_regmap_init(dev, NULL, st, &ad4134_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "failed to initialize regmap"); + + ret = ad4134_min_io_mode_setup(st); + if (ret) + return dev_err_probe(dev, ret, + "failed to setup minimum I/O mode\n"); + + /* Bump precision to 24-bit */ + ret = regmap_update_bits(st->regmap, AD4134_DATA_PACKET_CONFIG_REG, + AD4134_DATA_PACKET_CONFIG_FRAME_MASK, + FIELD_PREP(AD4134_DATA_PACKET_CONFIG_FRAME_MASK, + AD4134_DATA_PACKET_24BIT_FRAME)); + if (ret) + return ret; + + /* Set high performance power mode */ + ret = regmap_update_bits(st->regmap, AD4134_DEVICE_CONFIG_REG, + AD4134_DEVICE_CONFIG_POWER_MODE_MASK, + FIELD_PREP(AD4134_DEVICE_CONFIG_POWER_MODE_MASK, + AD4134_POWER_MODE_HIGH_PERF)); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad4134_id[] = { + { "ad4134" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4134_id); + +static const struct of_device_id ad4134_of_match[] = { + { .compatible = "adi,ad4134" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4134_of_match); + +static struct spi_driver ad4134_driver = { + .driver = { + .name = "ad4134", + .of_match_table = ad4134_of_match, + }, + .probe = ad4134_probe, + .id_table = ad4134_id, +}; +module_spi_driver(ad4134_driver); + +MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD4134 SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_AD4134"); diff --git a/drivers/iio/adc/ad4170-4.c b/drivers/iio/adc/ad4170-4.c index efaed92191f1..82205bfae531 100644 --- a/drivers/iio/adc/ad4170-4.c +++ b/drivers/iio/adc/ad4170-4.c @@ -2973,7 +2973,7 @@ static int ad4170_probe(struct spi_device *spi) if (spi->irq) { ret = devm_request_irq(dev, spi->irq, &ad4170_irq_handler, - IRQF_ONESHOT, indio_dev->name, indio_dev); + IRQF_NO_THREAD, indio_dev->name, indio_dev); if (ret) return ret; diff --git a/drivers/iio/adc/ad7476.c b/drivers/iio/adc/ad7476.c index 1bec6657394c..21d3f6aae972 100644 --- a/drivers/iio/adc/ad7476.c +++ b/drivers/iio/adc/ad7476.c @@ -16,7 +16,6 @@ #include <linux/gpio/consumer.h> #include <linux/err.h> #include <linux/module.h> -#include <linux/bitops.h> #include <linux/delay.h> #include <linux/iio/iio.h> diff --git a/drivers/iio/adc/ad7606_spi.c b/drivers/iio/adc/ad7606_spi.c index f28e4ca37707..7e17ccbcedd0 100644 --- a/drivers/iio/adc/ad7606_spi.c +++ b/drivers/iio/adc/ad7606_spi.c @@ -345,7 +345,7 @@ static int ad7606_spi_update_scan_mode(struct iio_dev *indio_dev, * has no way of demuxing the data to filter out unwanted * channels. */ - if (bitmap_weight(scan_mask, num_adc_ch) != num_adc_ch) + if (!bitmap_full(scan_mask, num_adc_ch)) return -EINVAL; } diff --git a/drivers/iio/adc/ad7766.c b/drivers/iio/adc/ad7766.c index 4d570383ef02..9e4a66477d2d 100644 --- a/drivers/iio/adc/ad7766.c +++ b/drivers/iio/adc/ad7766.c @@ -184,12 +184,6 @@ static const struct iio_info ad7766_info = { .read_raw = &ad7766_read_raw, }; -static irqreturn_t ad7766_irq(int irq, void *private) -{ - iio_trigger_poll(private); - return IRQ_HANDLED; -} - static int ad7766_set_trigger_state(struct iio_trigger *trig, bool enable) { struct ad7766 *ad7766 = iio_trigger_get_drvdata(trig); @@ -260,8 +254,8 @@ static int ad7766_probe(struct spi_device *spi) * Some platforms might not allow the option to power it down so * don't enable the interrupt to avoid extra load on the system */ - ret = devm_request_irq(&spi->dev, spi->irq, ad7766_irq, - IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN, + ret = devm_request_irq(&spi->dev, spi->irq, iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN | IRQF_NO_THREAD, dev_name(&spi->dev), ad7766->trig); if (ret < 0) diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c index d96802b7847a..fcd8aea7152e 100644 --- a/drivers/iio/adc/ad7768-1.c +++ b/drivers/iio/adc/ad7768-1.c @@ -6,6 +6,7 @@ */ #include <linux/array_size.h> #include <linux/bitfield.h> +#include <linux/cleanup.h> #include <linux/clk.h> #include <linux/completion.h> #include <linux/delay.h> @@ -14,8 +15,12 @@ #include <linux/gpio/driver.h> #include <linux/gpio/consumer.h> #include <linux/interrupt.h> +#include <linux/limits.h> +#include <linux/math.h> #include <linux/minmax.h> #include <linux/module.h> +#include <linux/mutex.h> +#include <linux/rational.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/regulator/driver.h> @@ -107,10 +112,15 @@ #define AD7768_VCM_OFF 0x07 +#define ADAQ776X_GAIN_MAX_NANO (128 * NANO) +#define ADAQ776X_MAX_GAIN_MODES 8 + #define AD7768_TRIGGER_SOURCE_SYNC_IDX 0 #define AD7768_MAX_CHANNELS 1 +#define ADAQ7768_PGA_PINS 3 + enum ad7768_conv_mode { AD7768_CONTINUOUS, AD7768_ONE_SHOT, @@ -153,6 +163,51 @@ enum ad7768_scan_type { AD7768_SCAN_TYPE_HIGH_SPEED, }; +enum { + AD7768_PGA_GAIN_0, + AD7768_PGA_GAIN_1, + AD7768_PGA_GAIN_2, + AD7768_PGA_GAIN_3, + AD7768_PGA_GAIN_4, + AD7768_PGA_GAIN_5, + AD7768_PGA_GAIN_6, + AD7768_PGA_GAIN_7, +}; + +enum { + AD7768_AAF_IN1, + AD7768_AAF_IN2, + AD7768_AAF_IN3, +}; + +/* PGA and AAF gains in V/V */ +static const int adaq7768_gains[] = { + [AD7768_PGA_GAIN_0] = 325, /* 0.325 */ + [AD7768_PGA_GAIN_1] = 650, /* 0.650 */ + [AD7768_PGA_GAIN_2] = 1300, /* 1.300 */ + [AD7768_PGA_GAIN_3] = 2600, /* 2.600 */ + [AD7768_PGA_GAIN_4] = 5200, /* 5.200 */ + [AD7768_PGA_GAIN_5] = 10400, /* 10.400 */ + [AD7768_PGA_GAIN_6] = 20800, /* 20.800 */ +}; + +static const int adaq7769_gains[] = { + [AD7768_PGA_GAIN_0] = 1000, /* 1.000 */ + [AD7768_PGA_GAIN_1] = 2000, /* 2.000 */ + [AD7768_PGA_GAIN_2] = 4000, /* 4.000 */ + [AD7768_PGA_GAIN_3] = 8000, /* 8.000 */ + [AD7768_PGA_GAIN_4] = 16000, /* 16.000 */ + [AD7768_PGA_GAIN_5] = 32000, /* 32.000 */ + [AD7768_PGA_GAIN_6] = 64000, /* 64.000 */ + [AD7768_PGA_GAIN_7] = 128000, /* 128.000 */ +}; + +static const int ad7768_aaf_gains_bp[] = { + [AD7768_AAF_IN1] = 10000, /* 1.000 */ + [AD7768_AAF_IN2] = 3640, /* 0.364 */ + [AD7768_AAF_IN3] = 1430, /* 0.143 */ +}; + /* -3dB cutoff frequency multipliers (relative to ODR) for each filter type. */ static const int ad7768_filter_3db_odr_multiplier[] = { [AD7768_FILTER_SINC5] = 204, /* 0.204 */ @@ -213,6 +268,19 @@ static const struct iio_scan_type ad7768_scan_type[] = { }, }; +struct ad7768_chip_info { + const char *name; + const struct iio_chan_spec *channel_spec; + int num_channels; + const int *pga_gains; + int num_pga_modes; + int default_pga_mode; + int pgia_mode2pin_offset; + bool has_pga; + bool has_variable_aaf; + bool has_vcm_regulator; +}; + struct ad7768_state { struct spi_device *spi; struct regmap *regmap; @@ -228,13 +296,19 @@ struct ad7768_state { unsigned int samp_freq; unsigned int samp_freq_avail[ARRAY_SIZE(ad7768_mclk_div_rates)]; unsigned int samp_freq_avail_len; + unsigned int pga_gain_mode; + unsigned int aaf_gain; + int scale_tbl[ADAQ776X_MAX_GAIN_MODES][2]; struct completion completion; struct iio_trigger *trig; + struct gpio_descs *pga_gpios; struct gpio_desc *gpio_sync_in; struct gpio_desc *gpio_reset; const char *labels[AD7768_MAX_CHANNELS]; struct gpio_chip gpiochip; + const struct ad7768_chip_info *chip; bool en_spi_sync; + struct mutex pga_lock; /* protect device internal state (PGA) */ /* * DMA (thus cache coherency maintenance) may require the * transfer buffers to live in their own cache lines. @@ -457,6 +531,42 @@ static int ad7768_reg_access(struct iio_dev *indio_dev, return ret; } +static void ad7768_fill_scale_tbl(struct iio_dev *dev) +{ + struct ad7768_state *st = iio_priv(dev); + const struct iio_scan_type *scan_type; + int val, val2, tmp0, tmp1, i; + struct u32_fract fract; + unsigned long n, d; + u64 tmp2; + + scan_type = iio_get_current_scan_type(dev, &dev->channels[0]); + if (scan_type->sign == 's') + val2 = scan_type->realbits - 1; + else + val2 = scan_type->realbits; + + for (i = 0; i < st->chip->num_pga_modes; i++) { + /* Convert gain to a fraction format */ + fract.numerator = st->chip->pga_gains[i]; + fract.denominator = MILLI; + if (st->chip->has_variable_aaf) { + fract.numerator *= ad7768_aaf_gains_bp[st->aaf_gain]; + fract.denominator *= PERMYRIAD; + } + + rational_best_approximation(fract.numerator, fract.denominator, + INT_MAX, INT_MAX, &n, &d); + + val = mult_frac(st->vref_uv, d, n); + /* Would multiply by NANO here, but value is already in milli */ + tmp2 = ((u64)val * MICRO) >> val2; + tmp0 = div_u64_rem(tmp2, NANO, &tmp1); + st->scale_tbl[i][0] = tmp0; /* Integer part */ + st->scale_tbl[i][1] = abs(tmp1); /* Fractional part */ + } +} + static int ad7768_set_sinc3_dec_rate(struct ad7768_state *st, unsigned int dec_rate) { @@ -558,12 +668,66 @@ static int ad7768_configure_dig_fil(struct iio_dev *dev, st->oversampling_ratio = ad7768_dec_rate_values[dec_rate_idx]; } + /* Update scale table: scale values vary according to the precision */ + ad7768_fill_scale_tbl(dev); + ad7768_fill_samp_freq_tbl(st); /* A sync-in pulse is required after every configuration change */ return ad7768_send_sync_pulse(st); } +static int ad7768_setup_pga(struct device *dev, struct ad7768_state *st) +{ + st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW); + if (IS_ERR(st->pga_gpios)) + return dev_err_probe(dev, PTR_ERR(st->pga_gpios), + "Failed to get PGA gpios.\n"); + + if (st->pga_gpios->ndescs != ADAQ7768_PGA_PINS) + return dev_err_probe(dev, -EINVAL, + "Expected %d GPIOs for PGA control.\n", + ADAQ7768_PGA_PINS); + return 0; +} + +static int ad7768_calc_pga_gain(struct ad7768_state *st, int gain_int, + int gain_fract, int precision) +{ + u64 gain_nano; + u32 tmp; + + gain_nano = gain_int * NANO + gain_fract; + gain_nano = clamp(gain_nano, 0, ADAQ776X_GAIN_MAX_NANO); + tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << precision, NANO); + gain_nano = DIV_ROUND_CLOSEST(st->vref_uv, tmp); + if (st->chip->has_variable_aaf) + gain_nano = DIV_ROUND_CLOSEST_ULL(gain_nano * PERMYRIAD, + ad7768_aaf_gains_bp[st->aaf_gain]); + + return find_closest(gain_nano, st->chip->pga_gains, + (int)st->chip->num_pga_modes); +} + +static int ad7768_set_pga_gain(struct ad7768_state *st, + int gain_mode) +{ + int pgia_pins_value = abs(gain_mode - st->chip->pgia_mode2pin_offset); + DECLARE_BITMAP(bitmap, ADAQ7768_PGA_PINS) = { }; + int ret; + + guard(mutex)(&st->pga_lock); + + bitmap_write(bitmap, pgia_pins_value, 0, ADAQ7768_PGA_PINS); + ret = gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap); + if (ret) + return ret; + + st->pga_gain_mode = gain_mode; + + return 0; +} + static int ad7768_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) { struct iio_dev *indio_dev = gpiochip_get_data(chip); @@ -735,6 +899,19 @@ static int ad7768_get_filter_type_attr(struct iio_dev *dev, return ad7768_filter_regval_to_type[FIELD_GET(mask, mode)]; } +static int ad7768_update_dec_rate(struct iio_dev *dev, unsigned int dec_rate) +{ + struct ad7768_state *st = iio_priv(dev); + int ret; + + ret = ad7768_configure_dig_fil(dev, st->filter_type, dec_rate); + if (ret) + return ret; + + /* Update sampling frequency */ + return ad7768_set_freq(st, st->samp_freq); +} + static const struct iio_enum ad7768_filter_type_iio_enum = { .items = ad7768_filter_enum, .num_items = ARRAY_SIZE(ad7768_filter_enum), @@ -748,24 +925,32 @@ static const struct iio_chan_spec_ext_info ad7768_ext_info[] = { { } }; +#define AD7768_CHAN(_idx, _msk_avail) \ +{ \ + .type = IIO_VOLTAGE, \ + .info_mask_separate_available = _msk_avail, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .ext_info = ad7768_ext_info, \ + .indexed = 1, \ + .channel = _idx, \ + .scan_index = _idx, \ + .has_ext_scan_type = 1, \ + .ext_scan_type = ad7768_scan_type, \ + .num_ext_scan_type = ARRAY_SIZE(ad7768_scan_type), \ +} + static const struct iio_chan_spec ad7768_channels[] = { - { - .type = IIO_VOLTAGE, - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | - BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | - BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), - .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), - .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), - .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), - .ext_info = ad7768_ext_info, - .indexed = 1, - .channel = 0, - .scan_index = 0, - .has_ext_scan_type = 1, - .ext_scan_type = ad7768_scan_type, - .num_ext_scan_type = ARRAY_SIZE(ad7768_scan_type), - }, + AD7768_CHAN(0, 0), +}; + +static const struct iio_chan_spec adaq776x_channels[] = { + AD7768_CHAN(0, BIT(IIO_CHAN_INFO_SCALE)), }; static int ad7768_read_raw(struct iio_dev *indio_dev, @@ -795,7 +980,19 @@ static int ad7768_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - *val = (st->vref_uv * 2) / 1000; + if (st->chip->has_pga) { + guard(mutex)(&st->pga_lock); + + *val = st->scale_tbl[st->pga_gain_mode][0]; + *val2 = st->scale_tbl[st->pga_gain_mode][1]; + return IIO_VAL_INT_PLUS_NANO; + } + + temp = (st->vref_uv * 2) / 1000; + if (st->chip->has_variable_aaf) + temp = (temp * PERMYRIAD) / ad7768_aaf_gains_bp[st->aaf_gain]; + + *val = temp; *val2 = scan_type->realbits; return IIO_VAL_FRACTIONAL_LOG2; @@ -851,31 +1048,24 @@ static int ad7768_read_avail(struct iio_dev *indio_dev, *length = st->samp_freq_avail_len; *type = IIO_VAL_INT; return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + *vals = (int *)st->scale_tbl; + *length = st->chip->num_pga_modes * 2; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_LIST; default: return -EINVAL; } } -static int __ad7768_write_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int val, int val2, long info) +static int ad7768_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) { - struct ad7768_state *st = iio_priv(indio_dev); - int ret; - - switch (info) { - case IIO_CHAN_INFO_SAMP_FREQ: - return ad7768_set_freq(st, val); - - case IIO_CHAN_INFO_OVERSAMPLING_RATIO: - ret = ad7768_configure_dig_fil(indio_dev, st->filter_type, val); - if (ret) - return ret; - - /* Update sampling frequency */ - return ad7768_set_freq(st, st->samp_freq); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; default: - return -EINVAL; + return IIO_VAL_INT_PLUS_MICRO; } } @@ -883,15 +1073,47 @@ static int ad7768_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long info) { + struct ad7768_state *st = iio_priv(indio_dev); + const struct iio_scan_type *scan_type; int ret; - if (!iio_device_claim_direct(indio_dev)) - return -EBUSY; + scan_type = iio_get_current_scan_type(indio_dev, chan); + if (IS_ERR(scan_type)) + return PTR_ERR(scan_type); - ret = __ad7768_write_raw(indio_dev, chan, val, val2, info); - iio_device_release_direct(indio_dev); + switch (info) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; - return ret; + ret = ad7768_set_freq(st, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = ad7768_update_dec_rate(indio_dev, val); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: { + int gain_mode; + + if (!st->chip->has_pga) + return -EOPNOTSUPP; + + if (scan_type->sign == 's') + gain_mode = ad7768_calc_pga_gain(st, val, val2, + scan_type->realbits - 1); + else + gain_mode = ad7768_calc_pga_gain(st, val, val2, + scan_type->realbits); + + return ad7768_set_pga_gain(st, gain_mode); + } + default: + return -EINVAL; + } } static int ad7768_read_label(struct iio_dev *indio_dev, @@ -915,6 +1137,7 @@ static const struct iio_info ad7768_info = { .read_raw = &ad7768_read_raw, .read_avail = &ad7768_read_avail, .write_raw = &ad7768_write_raw, + .write_raw_get_fmt = &ad7768_write_raw_get_fmt, .read_label = ad7768_read_label, .get_current_scan_type = &ad7768_get_current_scan_type, .debugfs_reg_access = &ad7768_reg_access, @@ -1298,8 +1521,9 @@ static const struct regulator_desc vcm_desc = { .owner = THIS_MODULE, }; -static int ad7768_register_regulators(struct device *dev, struct ad7768_state *st, - struct iio_dev *indio_dev) +static int ad7768_register_vcm_regulator(struct device *dev, + struct ad7768_state *st, + struct iio_dev *indio_dev) { struct regulator_config config = { .dev = dev, @@ -1321,6 +1545,79 @@ static int ad7768_register_regulators(struct device *dev, struct ad7768_state *s return 0; } +static int ad7768_parse_aaf_gain(struct device *dev, struct ad7768_state *st) +{ + u32 val; + int ret; + + ret = device_property_read_u32(dev, "adi,aaf-gain-bp", &val); + if (ret == -EINVAL) { + /* If controllable, use default */ + if (st->chip->has_variable_aaf) + st->aaf_gain = AD7768_AAF_IN1; + return 0; + } + if (ret) + return dev_err_probe(dev, ret, "Failed to get AAF gain value\n"); + + if (!st->chip->has_variable_aaf) + return dev_err_probe(dev, -EOPNOTSUPP, + "AAF gain provided, but not supported for %s\n", st->chip->name); + + switch (val) { + case 10000: + st->aaf_gain = AD7768_AAF_IN1; + break; + case 3640: + st->aaf_gain = AD7768_AAF_IN2; + break; + case 1430: + st->aaf_gain = AD7768_AAF_IN3; + break; + default: + return dev_err_probe(dev, -EINVAL, "Invalid firmware provided AAF gain\n"); + } + + return 0; +} + +static const struct ad7768_chip_info ad7768_chip_info = { + .name = "ad7768-1", + .channel_spec = ad7768_channels, + .num_channels = ARRAY_SIZE(ad7768_channels), + .has_vcm_regulator = true, +}; + +static const struct ad7768_chip_info adaq7767_chip_info = { + .name = "adaq7767-1", + .channel_spec = ad7768_channels, + .num_channels = ARRAY_SIZE(ad7768_channels), + .has_variable_aaf = true, +}; + +static const struct ad7768_chip_info adaq7768_chip_info = { + .name = "adaq7768-1", + .channel_spec = adaq776x_channels, + .num_channels = ARRAY_SIZE(adaq776x_channels), + .pga_gains = adaq7768_gains, + .default_pga_mode = AD7768_PGA_GAIN_2, + .num_pga_modes = ARRAY_SIZE(adaq7768_gains), + .pgia_mode2pin_offset = 6, + .has_pga = true, +}; + +static const struct ad7768_chip_info adaq7769_chip_info = { + .name = "adaq7769-1", + .channel_spec = adaq776x_channels, + .num_channels = ARRAY_SIZE(adaq776x_channels), + .pga_gains = adaq7769_gains, + .default_pga_mode = AD7768_PGA_GAIN_0, + .num_pga_modes = ARRAY_SIZE(adaq7769_gains), + .pgia_mode2pin_offset = 0, + .has_pga = true, + .has_variable_aaf = true, +}; + static int ad7768_probe(struct spi_device *spi) { struct ad7768_state *st; @@ -1347,6 +1644,7 @@ static int ad7768_probe(struct spi_device *spi) return ret; } + st->chip = spi_get_device_match_data(spi); st->spi = spi; st->regmap = devm_regmap_init_spi(spi, &ad7768_regmap_config); @@ -1371,14 +1669,20 @@ static int ad7768_probe(struct spi_device *spi) st->mclk_freq = clk_get_rate(st->mclk); - indio_dev->channels = ad7768_channels; - indio_dev->num_channels = ARRAY_SIZE(ad7768_channels); - indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = st->chip->channel_spec; + indio_dev->num_channels = st->chip->num_channels; + indio_dev->name = st->chip->name; indio_dev->info = &ad7768_info; indio_dev->modes = INDIO_DIRECT_MODE; /* Register VCM output regulator */ - ret = ad7768_register_regulators(&spi->dev, st, indio_dev); + if (st->chip->has_vcm_regulator) { + ret = ad7768_register_vcm_regulator(&spi->dev, st, indio_dev); + if (ret) + return ret; + } + + ret = ad7768_parse_aaf_gain(&spi->dev, st); if (ret) return ret; @@ -1389,14 +1693,26 @@ static int ad7768_probe(struct spi_device *spi) } init_completion(&st->completion); + ret = devm_mutex_init(&spi->dev, &st->pga_lock); + if (ret) + return ret; + + if (st->chip->has_pga) { + ret = ad7768_setup_pga(&spi->dev, st); + if (ret) + return ret; + + ret = ad7768_set_pga_gain(st, st->chip->default_pga_mode); + if (ret) + return ret; + } - ret = ad7768_set_channel_label(indio_dev, ARRAY_SIZE(ad7768_channels)); + ret = ad7768_set_channel_label(indio_dev, st->chip->num_channels); if (ret) return ret; - ret = devm_request_irq(&spi->dev, spi->irq, - &ad7768_interrupt, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, + ret = devm_request_irq(&spi->dev, spi->irq, &ad7768_interrupt, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, indio_dev->name, indio_dev); if (ret) return ret; @@ -1409,13 +1725,19 @@ static int ad7768_probe(struct spi_device *spi) } static const struct spi_device_id ad7768_id_table[] = { - { "ad7768-1", 0 }, + { "ad7768-1", (kernel_ulong_t)&ad7768_chip_info }, + { "adaq7767-1", (kernel_ulong_t)&adaq7767_chip_info }, + { "adaq7768-1", (kernel_ulong_t)&adaq7768_chip_info }, + { "adaq7769-1", (kernel_ulong_t)&adaq7769_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ad7768_id_table); static const struct of_device_id ad7768_of_match[] = { - { .compatible = "adi,ad7768-1" }, + { .compatible = "adi,ad7768-1", .data = &ad7768_chip_info }, + { .compatible = "adi,adaq7767-1", .data = &adaq7767_chip_info }, + { .compatible = "adi,adaq7768-1", .data = &adaq7768_chip_info }, + { .compatible = "adi,adaq7769-1", .data = &adaq7769_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ad7768_of_match); diff --git a/drivers/iio/adc/ad7779.c b/drivers/iio/adc/ad7779.c index aac5049c9a07..695cc79e78da 100644 --- a/drivers/iio/adc/ad7779.c +++ b/drivers/iio/adc/ad7779.c @@ -840,7 +840,7 @@ static int ad7779_setup_without_backend(struct ad7779_state *st, struct iio_dev 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, + IRQF_NO_THREAD | IRQF_NO_AUTOEN, indio_dev->name, st->trig); if (ret) return dev_err_probe(dev, ret, "request IRQ %d failed\n", diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c index 2d8f8da3671d..022888545580 100644 --- a/drivers/iio/adc/ad9467.c +++ b/drivers/iio/adc/ad9467.c @@ -5,29 +5,29 @@ * Copyright 2012-2020 Analog Devices Inc. */ +#include <linux/bitfield.h> #include <linux/bitmap.h> #include <linux/bitops.h> #include <linux/cleanup.h> +#include <linux/clk.h> #include <linux/debugfs.h> -#include <linux/module.h> -#include <linux/mutex.h> +#include <linux/delay.h> #include <linux/device.h> -#include <linux/kernel.h> -#include <linux/slab.h> -#include <linux/spi/spi.h> -#include <linux/seq_file.h> #include <linux/err.h> -#include <linux/delay.h> #include <linux/gpio/consumer.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> #include <linux/of.h> - +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/units.h> #include <linux/iio/backend.h> #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> -#include <linux/clk.h> - /* * ADI High-Speed ADC common spi interface registers * See Application-Note AN-877: @@ -73,6 +73,7 @@ #define AN877_ADC_OUTPUT_MODE_OFFSET_BINARY 0x0 #define AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT 0x1 #define AN877_ADC_OUTPUT_MODE_GRAY_CODE 0x2 +#define AN877_ADC_OUTPUT_MODE_MASK GENMASK(1, 0) /* AN877_ADC_REG_OUTPUT_PHASE */ #define AN877_ADC_OUTPUT_EVEN_ODD_MODE_EN 0x20 @@ -82,11 +83,19 @@ #define AN877_ADC_DCO_DELAY_ENABLE 0x80 /* + * Analog Devices AD9211 10-Bit, 200/250/300 MSPS ADC + */ + +#define CHIPID_AD9211 0x06 +#define AD9211_DEF_OUTPUT_MODE 0x01 +#define AD9211_REG_VREF_MASK GENMASK(4, 0) + +/* * Analog Devices AD9265 16-Bit, 125/105/80 MSPS ADC */ #define CHIPID_AD9265 0x64 -#define AD9265_DEF_OUTPUT_MODE 0x40 +#define AD9265_DEF_OUTPUT_MODE 0x41 #define AD9265_REG_VREF_MASK 0xC0 /* @@ -94,7 +103,7 @@ */ #define CHIPID_AD9434 0x6A -#define AD9434_DEF_OUTPUT_MODE 0x00 +#define AD9434_DEF_OUTPUT_MODE 0x01 #define AD9434_REG_VREF_MASK GENMASK(4, 0) /* @@ -102,7 +111,7 @@ */ #define CHIPID_AD9467 0x50 -#define AD9467_DEF_OUTPUT_MODE 0x08 +#define AD9467_DEF_OUTPUT_MODE 0x09 #define AD9467_REG_VREF_MASK 0x0F /* @@ -110,6 +119,7 @@ */ #define CHIPID_AD9643 0x82 +#define AD9643_DEF_OUTPUT_MODE 0x01 #define AD9643_REG_VREF_MASK 0x1F /* @@ -117,6 +127,7 @@ */ #define CHIPID_AD9652 0xC1 +#define AD9652_DEF_OUTPUT_MODE 0x01 #define AD9652_REG_VREF_MASK 0xC0 /* @@ -124,6 +135,7 @@ */ #define CHIPID_AD9649 0x6F +#define AD9649_DEF_OUTPUT_MODE 0x01 #define AD9649_TEST_POINTS 8 #define AD9647_MAX_TEST_POINTS 32 @@ -145,6 +157,7 @@ struct ad9467_chip_info { unsigned int num_lanes; unsigned int dco_en; unsigned int test_points; + const int *offset_range; /* data clock output */ bool has_dco; bool has_dco_invert; @@ -234,6 +247,21 @@ static int ad9467_reg_access(struct iio_dev *indio_dev, unsigned int reg, return 0; } +static const int ad9434_offset_range[] = { + -128, 1, 127, +}; + +static const unsigned int ad9211_scale_table[][2] = { + {980, 0x10}, {1000, 0x11}, {1020, 0x12}, {1040, 0x13}, + {1060, 0x14}, {1080, 0x15}, {1100, 0x16}, {1120, 0x17}, + {1140, 0x18}, {1160, 0x19}, {1180, 0x1A}, {1190, 0x1B}, + {1200, 0x1C}, {1210, 0x1D}, {1220, 0x1E}, {1230, 0x1F}, + {1250, 0x0}, {1270, 0x1}, {1290, 0x2}, {1310, 0x3}, + {1330, 0x4}, {1350, 0x5}, {1370, 0x6}, {1390, 0x7}, + {1410, 0x8}, {1430, 0x9}, {1450, 0xA}, {1460, 0xB}, + {1470, 0xC}, {1480, 0xD}, {1490, 0xE}, {1500, 0xF}, +}; + static const unsigned int ad9265_scale_table[][2] = { {1250, 0x00}, {1500, 0x40}, {1750, 0x80}, {2000, 0xC0}, }; @@ -297,8 +325,29 @@ static void __ad9467_get_scale(struct ad9467_state *st, int index, }, \ } +static const struct iio_chan_spec ad9211_channels[] = { + AD9467_CHAN(0, BIT(IIO_CHAN_INFO_SCALE), 0, 10, 's'), +}; + static const struct iio_chan_spec ad9434_channels[] = { - AD9467_CHAN(0, BIT(IIO_CHAN_INFO_SCALE), 0, 12, 's'), + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_shared_by_type = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_type_available = + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 12, + .storagebits = 16, + }, + }, }; static const struct iio_chan_spec ad9467_channels[] = { @@ -367,6 +416,24 @@ static const struct ad9467_chip_info ad9434_chip_tbl = { .default_output_mode = AD9434_DEF_OUTPUT_MODE, .vref_mask = AD9434_REG_VREF_MASK, .num_lanes = 6, + .offset_range = ad9434_offset_range, +}; + +static const struct ad9467_chip_info ad9211_chip_tbl = { + .name = "ad9211", + .id = CHIPID_AD9211, + .max_rate = 300 * HZ_PER_MHZ, + .scale_table = ad9211_scale_table, + .num_scales = ARRAY_SIZE(ad9211_scale_table), + .channels = ad9211_channels, + .num_channels = ARRAY_SIZE(ad9211_channels), + .test_points = AD9647_MAX_TEST_POINTS, + .test_mask = GENMASK(AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE, + AN877_ADC_TESTMODE_OFF), + .test_mask_len = AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE + 1, + .default_output_mode = AD9211_DEF_OUTPUT_MODE, + .vref_mask = AD9211_REG_VREF_MASK, + .has_dco = true, }; static const struct ad9467_chip_info ad9265_chip_tbl = { @@ -399,6 +466,7 @@ static const struct ad9467_chip_info ad9643_chip_tbl = { .test_mask = BIT(AN877_ADC_TESTMODE_RAMP) | GENMASK(AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY, AN877_ADC_TESTMODE_OFF), .test_mask_len = AN877_ADC_TESTMODE_RAMP + 1, + .default_output_mode = AD9643_DEF_OUTPUT_MODE, .vref_mask = AD9643_REG_VREF_MASK, .has_dco = true, .has_dco_invert = true, @@ -417,6 +485,7 @@ static const struct ad9467_chip_info ad9649_chip_tbl = { .test_mask = GENMASK(AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY, AN877_ADC_TESTMODE_OFF), .test_mask_len = AN877_ADC_TESTMODE_MIXED_BIT_FREQUENCY + 1, + .default_output_mode = AD9649_DEF_OUTPUT_MODE, .has_dco = true, .has_dco_invert = true, .dco_en = AN877_ADC_DCO_DELAY_ENABLE, @@ -434,6 +503,7 @@ static const struct ad9467_chip_info ad9652_chip_tbl = { .test_mask = GENMASK(AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE, AN877_ADC_TESTMODE_OFF), .test_mask_len = AN877_ADC_TESTMODE_ONE_ZERO_TOGGLE + 1, + .default_output_mode = AD9652_DEF_OUTPUT_MODE, .vref_mask = AD9652_REG_VREF_MASK, .has_dco = true, }; @@ -499,6 +569,33 @@ static int ad9467_set_scale(struct ad9467_state *st, int val, int val2) return -EINVAL; } +static int ad9467_get_offset(struct ad9467_state *st, int *val) +{ + int ret; + + ret = ad9467_spi_read(st, AN877_ADC_REG_OFFSET); + if (ret < 0) + return ret; + *val = ret; + + return IIO_VAL_INT; +} + +static int ad9467_set_offset(struct ad9467_state *st, int val) +{ + int ret; + + if (val < st->info->offset_range[0] || val > st->info->offset_range[2]) + return -EINVAL; + + ret = ad9467_spi_write(st, AN877_ADC_REG_OFFSET, val); + if (ret < 0) + return ret; + + return ad9467_spi_write(st, AN877_ADC_REG_TRANSFER, + AN877_ADC_TRANSFER_SYNC); +} + static int ad9467_outputmode_set(struct ad9467_state *st, unsigned int mode) { int ret; @@ -582,10 +679,14 @@ static int ad9467_backend_testmode_off(struct ad9467_state *st, static int ad9647_calibrate_prepare(struct ad9467_state *st) { + unsigned int cmode; unsigned int c; int ret; - ret = ad9467_outputmode_set(st, st->info->default_output_mode); + cmode = st->info->default_output_mode; + FIELD_MODIFY(AN877_ADC_OUTPUT_MODE_MASK, &cmode, + AN877_ADC_OUTPUT_MODE_OFFSET_BINARY); + ret = ad9467_outputmode_set(st, cmode); if (ret) return ret; @@ -689,7 +790,7 @@ static int ad9647_calibrate_stop(struct ad9467_state *st) return ret; } - mode = st->info->default_output_mode | AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT; + mode = st->info->default_output_mode; return ad9467_outputmode_set(st, mode); } @@ -802,6 +903,8 @@ static int ad9467_read_raw(struct iio_dev *indio_dev, struct ad9467_state *st = iio_priv(indio_dev); switch (m) { + case IIO_CHAN_INFO_CALIBBIAS: + return ad9467_get_offset(st, val); case IIO_CHAN_INFO_SCALE: return ad9467_get_scale(st, val, val2); case IIO_CHAN_INFO_SAMP_FREQ: @@ -836,6 +939,8 @@ static int ad9467_write_raw(struct iio_dev *indio_dev, int ret; switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + return ad9467_set_offset(st, val); case IIO_CHAN_INFO_SCALE: return ad9467_set_scale(st, val, val2); case IIO_CHAN_INFO_SAMP_FREQ: @@ -874,6 +979,10 @@ static int ad9467_read_avail(struct iio_dev *indio_dev, const struct ad9467_chip_info *info = st->info; switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + *type = IIO_VAL_INT; + *vals = info->offset_range; + return IIO_AVAIL_RANGE; case IIO_CHAN_INFO_SCALE: *vals = (const int *)st->scales; *type = IIO_VAL_INT_PLUS_MICRO; @@ -1077,12 +1186,17 @@ static ssize_t ad9467_chan_test_mode_write(struct file *file, if (ret) return ret; - out_mode = st->info->default_output_mode | AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT; + out_mode = st->info->default_output_mode; ret = ad9467_outputmode_set(st, out_mode); if (ret) return ret; } else { - ret = ad9467_outputmode_set(st, st->info->default_output_mode); + unsigned int cmode; + + cmode = st->info->default_output_mode; + FIELD_MODIFY(AN877_ADC_OUTPUT_MODE_MASK, &cmode, + AN877_ADC_OUTPUT_MODE_OFFSET_BINARY); + ret = ad9467_outputmode_set(st, cmode); if (ret) return ret; @@ -1264,6 +1378,7 @@ static int ad9467_probe(struct spi_device *spi) } static const struct of_device_id ad9467_of_match[] = { + { .compatible = "adi,ad9211", .data = &ad9211_chip_tbl, }, { .compatible = "adi,ad9265", .data = &ad9265_chip_tbl, }, { .compatible = "adi,ad9434", .data = &ad9434_chip_tbl, }, { .compatible = "adi,ad9467", .data = &ad9467_chip_tbl, }, @@ -1275,6 +1390,7 @@ static const struct of_device_id ad9467_of_match[] = { MODULE_DEVICE_TABLE(of, ad9467_of_match); static const struct spi_device_id ad9467_ids[] = { + { "ad9211", (kernel_ulong_t)&ad9211_chip_tbl }, { "ad9265", (kernel_ulong_t)&ad9265_chip_tbl }, { "ad9434", (kernel_ulong_t)&ad9434_chip_tbl }, { "ad9467", (kernel_ulong_t)&ad9467_chip_tbl }, diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index 2de8a718d62a..db085dc5e526 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -964,7 +964,7 @@ 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)) { + if (iio_device_try_claim_buffer_mode(indio_dev)) { ade9000_iio_push_buffer(indio_dev); iio_device_release_buffer_mode(indio_dev); } diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c index 14fa4238c2b9..5f445e0de9ea 100644 --- a/drivers/iio/adc/adi-axi-adc.c +++ b/drivers/iio/adc/adi-axi-adc.c @@ -591,17 +591,12 @@ static int axi_adc_create_platform_device(struct adi_axi_adc_state *st, .size_data = st->info->pdata_sz, }; struct platform_device *pdev; - int ret; pdev = platform_device_register_full(&pi); if (IS_ERR(pdev)) return PTR_ERR(pdev); - ret = devm_add_action_or_reset(st->dev, axi_adc_child_remove, pdev); - if (ret) - return ret; - - return 0; + return devm_add_action_or_reset(st->dev, axi_adc_child_remove, pdev); } static const struct iio_backend_ops adi_axi_adc_ops = { @@ -674,13 +669,14 @@ static const struct iio_backend_info axi_ad408x = { static int adi_axi_adc_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct adi_axi_adc_state *st; void __iomem *base; unsigned int ver; struct clk *clk; int ret; - st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); if (!st) return -ENOMEM; @@ -688,20 +684,19 @@ static int adi_axi_adc_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); - st->dev = &pdev->dev; - st->regmap = devm_regmap_init_mmio(&pdev->dev, base, - &axi_adc_regmap_config); + st->dev = dev; + st->regmap = devm_regmap_init_mmio(dev, base, &axi_adc_regmap_config); if (IS_ERR(st->regmap)) - return dev_err_probe(&pdev->dev, PTR_ERR(st->regmap), + return dev_err_probe(dev, PTR_ERR(st->regmap), "failed to init register map\n"); - st->info = device_get_match_data(&pdev->dev); + st->info = device_get_match_data(dev); if (!st->info) return -ENODEV; - clk = devm_clk_get_enabled(&pdev->dev, NULL); + clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(clk), + return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n"); /* @@ -716,47 +711,42 @@ static int adi_axi_adc_probe(struct platform_device *pdev) if (ret) return ret; - if (ADI_AXI_PCORE_VER_MAJOR(ver) != - ADI_AXI_PCORE_VER_MAJOR(st->info->version)) { - dev_err(&pdev->dev, - "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", - ADI_AXI_PCORE_VER_MAJOR(st->info->version), - ADI_AXI_PCORE_VER_MINOR(st->info->version), - ADI_AXI_PCORE_VER_PATCH(st->info->version), - ADI_AXI_PCORE_VER_MAJOR(ver), - ADI_AXI_PCORE_VER_MINOR(ver), - ADI_AXI_PCORE_VER_PATCH(ver)); - return -ENODEV; - } - - ret = devm_iio_backend_register(&pdev->dev, st->info->backend_info, st); + if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(st->info->version)) + return dev_err_probe(dev, -ENODEV, + "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", + ADI_AXI_PCORE_VER_MAJOR(st->info->version), + ADI_AXI_PCORE_VER_MINOR(st->info->version), + ADI_AXI_PCORE_VER_PATCH(st->info->version), + ADI_AXI_PCORE_VER_MAJOR(ver), + ADI_AXI_PCORE_VER_MINOR(ver), + ADI_AXI_PCORE_VER_PATCH(ver)); + + ret = devm_iio_backend_register(dev, st->info->backend_info, st); if (ret) - return dev_err_probe(&pdev->dev, ret, - "failed to register iio backend\n"); + return dev_err_probe(dev, ret, "failed to register iio backend\n"); - device_for_each_child_node_scoped(&pdev->dev, child) { + device_for_each_child_node_scoped(dev, child) { int val; if (!st->info->has_child_nodes) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "invalid fdt axi-dac compatible."); /* Processing only reg 0 node */ ret = fwnode_property_read_u32(child, "reg", &val); if (ret) - return dev_err_probe(&pdev->dev, ret, - "invalid reg property."); + return dev_err_probe(dev, ret, "invalid reg property."); if (val != 0) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "invalid node address."); ret = axi_adc_create_platform_device(st, child); if (ret) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "cannot create device."); } - dev_info(&pdev->dev, "AXI ADC IP core (%d.%.2d.%c) probed\n", + dev_info(dev, "AXI ADC IP core (%d.%.2d.%c) probed\n", ADI_AXI_PCORE_VER_MAJOR(ver), ADI_AXI_PCORE_VER_MINOR(ver), ADI_AXI_PCORE_VER_PATCH(ver)); diff --git a/drivers/iio/adc/aspeed_adc.c b/drivers/iio/adc/aspeed_adc.c index bf2bfd6bdc41..4be44c524b4d 100644 --- a/drivers/iio/adc/aspeed_adc.c +++ b/drivers/iio/adc/aspeed_adc.c @@ -472,16 +472,18 @@ static int aspeed_adc_probe(struct platform_device *pdev) struct aspeed_adc_data *data; int ret; u32 adc_engine_control_reg_val; + struct device *dev = &pdev->dev; + struct device_node *np = dev_of_node(dev); unsigned long scaler_flags = 0; char clk_name[32], clk_parent_name[32]; - indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev); - data->dev = &pdev->dev; - data->model_data = of_device_get_match_data(&pdev->dev); + data->dev = dev; + data->model_data = of_device_get_match_data(dev); platform_set_drvdata(pdev, indio_dev); data->base = devm_platform_ioremap_resource(pdev, 0); @@ -491,16 +493,15 @@ static int aspeed_adc_probe(struct platform_device *pdev) /* Register ADC clock prescaler with source specified by device tree. */ spin_lock_init(&data->clk_lock); snprintf(clk_parent_name, ARRAY_SIZE(clk_parent_name), "%s", - of_clk_get_parent_name(pdev->dev.of_node, 0)); + of_clk_get_parent_name(np, 0)); snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-fixed-div", data->model_data->model_name); - data->fixed_div_clk = clk_hw_register_fixed_factor( - &pdev->dev, clk_name, clk_parent_name, 0, 1, 2); + data->fixed_div_clk = clk_hw_register_fixed_factor(dev, clk_name, + clk_parent_name, 0, 1, 2); if (IS_ERR(data->fixed_div_clk)) return PTR_ERR(data->fixed_div_clk); - ret = devm_add_action_or_reset(data->dev, - aspeed_adc_unregister_fixed_divider, + ret = devm_add_action_or_reset(dev, aspeed_adc_unregister_fixed_divider, data->fixed_div_clk); if (ret) return ret; @@ -510,7 +511,7 @@ static int aspeed_adc_probe(struct platform_device *pdev) snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-prescaler", data->model_data->model_name); data->clk_prescaler = devm_clk_hw_register_divider( - &pdev->dev, clk_name, clk_parent_name, 0, + dev, clk_name, clk_parent_name, 0, data->base + ASPEED_REG_CLOCK_CONTROL, 17, 15, 0, &data->clk_lock); if (IS_ERR(data->clk_prescaler)) @@ -526,7 +527,7 @@ static int aspeed_adc_probe(struct platform_device *pdev) snprintf(clk_name, ARRAY_SIZE(clk_name), "%s-scaler", data->model_data->model_name); data->clk_scaler = devm_clk_hw_register_divider( - &pdev->dev, clk_name, clk_parent_name, scaler_flags, + dev, clk_name, clk_parent_name, scaler_flags, data->base + ASPEED_REG_CLOCK_CONTROL, 0, data->model_data->scaler_bit_width, data->model_data->need_prescaler ? CLK_DIVIDER_ONE_BASED : 0, @@ -534,16 +535,14 @@ static int aspeed_adc_probe(struct platform_device *pdev) if (IS_ERR(data->clk_scaler)) return PTR_ERR(data->clk_scaler); - data->rst = devm_reset_control_get_shared(&pdev->dev, NULL); - if (IS_ERR(data->rst)) { - dev_err(&pdev->dev, - "invalid or missing reset controller device tree entry"); - return PTR_ERR(data->rst); - } + data->rst = devm_reset_control_get_shared(dev, NULL); + if (IS_ERR(data->rst)) + return dev_err_probe(dev, PTR_ERR(data->rst), + "invalid or missing reset controller device tree entry"); + reset_control_deassert(data->rst); - ret = devm_add_action_or_reset(data->dev, aspeed_adc_reset_assert, - data->rst); + ret = devm_add_action_or_reset(dev, aspeed_adc_reset_assert, data->rst); if (ret) return ret; @@ -555,7 +554,7 @@ static int aspeed_adc_probe(struct platform_device *pdev) if (ret) return ret; - if (of_property_present(data->dev->of_node, "aspeed,battery-sensing")) { + if (of_property_present(np, "aspeed,battery-sensing")) { if (data->model_data->bat_sense_sup) { data->battery_sensing = 1; if (readl(data->base + ASPEED_REG_ENGINE_CONTROL) & @@ -567,15 +566,13 @@ static int aspeed_adc_probe(struct platform_device *pdev) data->battery_mode_gain.div = 2; } } else - dev_warn(&pdev->dev, - "Failed to enable battery-sensing mode\n"); + dev_warn(dev, "Failed to enable battery-sensing mode\n"); } ret = clk_prepare_enable(data->clk_scaler->clk); if (ret) return ret; - ret = devm_add_action_or_reset(data->dev, - aspeed_adc_clk_disable_unprepare, + ret = devm_add_action_or_reset(dev, aspeed_adc_clk_disable_unprepare, data->clk_scaler->clk); if (ret) return ret; @@ -593,8 +590,7 @@ static int aspeed_adc_probe(struct platform_device *pdev) writel(adc_engine_control_reg_val, data->base + ASPEED_REG_ENGINE_CONTROL); - ret = devm_add_action_or_reset(data->dev, aspeed_adc_power_down, - data); + ret = devm_add_action_or_reset(dev, aspeed_adc_power_down, data); if (ret) return ret; @@ -626,8 +622,7 @@ static int aspeed_adc_probe(struct platform_device *pdev) aspeed_adc_iio_channels; indio_dev->num_channels = data->model_data->num_channels; - ret = devm_iio_device_register(data->dev, indio_dev); - return ret; + return devm_iio_device_register(dev, indio_dev); } static const struct aspeed_adc_trim_locate ast2500_adc_trim = { diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index f2400897818c..c1a0061b16ca 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -543,22 +543,21 @@ static const struct iio_chan_spec exynos_adc_iio_channels[] = { static int exynos_adc_probe(struct platform_device *pdev) { struct exynos_adc *info = NULL; + struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; struct iio_dev *indio_dev = NULL; int ret; int irq; - indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct exynos_adc)); + indio_dev = devm_iio_device_alloc(dev, sizeof(struct exynos_adc)); if (!indio_dev) return -ENOMEM; info = iio_priv(indio_dev); info->data = exynos_adc_get_data(pdev); - if (!info->data) { - dev_err(&pdev->dev, "failed getting exynos_adc_data\n"); - return -EINVAL; - } + if (!info->data) + return dev_err_probe(dev, -EINVAL, "failed getting exynos_adc_data\n"); info->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(info->regs)) @@ -566,44 +565,34 @@ static int exynos_adc_probe(struct platform_device *pdev) if (info->data->needs_adc_phy) { - info->pmu_map = syscon_regmap_lookup_by_phandle( - pdev->dev.of_node, - "samsung,syscon-phandle"); - if (IS_ERR(info->pmu_map)) { - dev_err(&pdev->dev, "syscon regmap lookup failed.\n"); - return PTR_ERR(info->pmu_map); - } + info->pmu_map = syscon_regmap_lookup_by_phandle(np, "samsung,syscon-phandle"); + if (IS_ERR(info->pmu_map)) + return dev_err_probe(dev, PTR_ERR(info->pmu_map), + "syscon regmap lookup failed.\n"); } irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; info->irq = irq; - info->dev = &pdev->dev; + info->dev = dev; init_completion(&info->completion); - info->clk = devm_clk_get(&pdev->dev, "adc"); - if (IS_ERR(info->clk)) { - dev_err(&pdev->dev, "failed getting clock, err = %ld\n", - PTR_ERR(info->clk)); - return PTR_ERR(info->clk); - } + info->clk = devm_clk_get(dev, "adc"); + if (IS_ERR(info->clk)) + return dev_err_probe(dev, PTR_ERR(info->clk), "failed getting clock\n"); if (info->data->needs_sclk) { - info->sclk = devm_clk_get(&pdev->dev, "sclk"); - if (IS_ERR(info->sclk)) { - dev_err(&pdev->dev, - "failed getting sclk clock, err = %ld\n", - PTR_ERR(info->sclk)); - return PTR_ERR(info->sclk); - } + info->sclk = devm_clk_get(dev, "sclk"); + if (IS_ERR(info->sclk)) + return dev_err_probe(dev, PTR_ERR(info->sclk), + "failed getting sclk clock\n"); } - info->vdd = devm_regulator_get(&pdev->dev, "vdd"); + info->vdd = devm_regulator_get(dev, "vdd"); if (IS_ERR(info->vdd)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->vdd), - "failed getting regulator"); + return dev_err_probe(dev, PTR_ERR(info->vdd), "failed getting regulator"); ret = regulator_enable(info->vdd); if (ret) @@ -619,7 +608,7 @@ static int exynos_adc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, indio_dev); - indio_dev->name = dev_name(&pdev->dev); + indio_dev->name = dev_name(dev); indio_dev->info = &exynos_adc_iio_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = exynos_adc_iio_channels; @@ -627,11 +616,9 @@ static int exynos_adc_probe(struct platform_device *pdev) mutex_init(&info->lock); - ret = request_irq(info->irq, exynos_adc_isr, - 0, dev_name(&pdev->dev), info); + ret = request_irq(info->irq, exynos_adc_isr, 0, dev_name(dev), info); if (ret < 0) { - dev_err(&pdev->dev, "failed requesting irq, irq = %d\n", - info->irq); + dev_err(dev, "failed requesting irq, irq = %d\n", info->irq); goto err_disable_clk; } @@ -644,7 +631,7 @@ static int exynos_adc_probe(struct platform_device *pdev) ret = of_platform_populate(np, exynos_adc_match, NULL, &indio_dev->dev); if (ret < 0) { - dev_err(&pdev->dev, "failed adding child nodes\n"); + dev_err(dev, "failed adding child nodes\n"); goto err_of_populate; } diff --git a/drivers/iio/adc/mcp3911.c b/drivers/iio/adc/mcp3911.c index a6f21791c685..ddc3721f3f68 100644 --- a/drivers/iio/adc/mcp3911.c +++ b/drivers/iio/adc/mcp3911.c @@ -815,7 +815,7 @@ static int mcp3911_probe(struct spi_device *spi) * don't enable the interrupt to avoid extra load on the system. */ ret = devm_request_irq(dev, spi->irq, &iio_trigger_generic_data_rdy_poll, - IRQF_NO_AUTOEN | IRQF_ONESHOT, + IRQF_NO_AUTOEN | IRQF_NO_THREAD, indio_dev->name, adc->trig); if (ret) return ret; diff --git a/drivers/iio/adc/men_z188_adc.c b/drivers/iio/adc/men_z188_adc.c index cf8a8c0412ec..90919d282e7b 100644 --- a/drivers/iio/adc/men_z188_adc.c +++ b/drivers/iio/adc/men_z188_adc.c @@ -171,5 +171,4 @@ module_mcb_driver(men_z188_driver); MODULE_AUTHOR("Johannes Thumshirn <johannes.thumshirn@men.de>"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("IIO ADC driver for MEN 16z188 ADC Core"); -MODULE_ALIAS("mcb:16z188"); MODULE_IMPORT_NS("MCB"); diff --git a/drivers/iio/adc/nxp-sar-adc.c b/drivers/iio/adc/nxp-sar-adc.c new file mode 100644 index 000000000000..9efa883c277d --- /dev/null +++ b/drivers/iio/adc/nxp-sar-adc.c @@ -0,0 +1,1016 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP SAR-ADC driver (adapted from Freescale Vybrid vf610 ADC driver + * by Fugang Duan <B38611@freescale.com>) + * + * Copyright 2013 Freescale Semiconductor, Inc. + * Copyright 2017, 2020-2025 NXP + * Copyright 2025, Linaro Ltd + */ +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/circ_buf.h> +#include <linux/cleanup.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/math64.h> +#include <linux/minmax.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <linux/iio/iio.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +/* SAR ADC registers. */ +#define NXP_SAR_ADC_CDR(__base, __channel) (((__base) + 0x100) + ((__channel) * 0x4)) + +#define NXP_SAR_ADC_CDR_CDATA_MASK GENMASK(11, 0) +#define NXP_SAR_ADC_CDR_VALID BIT(19) + +/* Main Configuration Register */ +#define NXP_SAR_ADC_MCR(__base) ((__base) + 0x00) + +#define NXP_SAR_ADC_MCR_PWDN BIT(0) +#define NXP_SAR_ADC_MCR_ACKO BIT(5) +#define NXP_SAR_ADC_MCR_ADCLKSEL BIT(8) +#define NXP_SAR_ADC_MCR_TSAMP_MASK GENMASK(10, 9) +#define NXP_SAR_ADC_MCR_NRSMPL_MASK GENMASK(12, 11) +#define NXP_SAR_ADC_MCR_AVGEN BIT(13) +#define NXP_SAR_ADC_MCR_CALSTART BIT(14) +#define NXP_SAR_ADC_MCR_NSTART BIT(24) +#define NXP_SAR_ADC_MCR_MODE BIT(29) +#define NXP_SAR_ADC_MCR_OWREN BIT(31) + +/* Main Status Register */ +#define NXP_SAR_ADC_MSR(__base) ((__base) + 0x04) + +#define NXP_SAR_ADC_MSR_CALBUSY BIT(29) +#define NXP_SAR_ADC_MSR_CALFAIL BIT(30) + +/* Interrupt Status Register */ +#define NXP_SAR_ADC_ISR(__base) ((__base) + 0x10) + +#define NXP_SAR_ADC_ISR_ECH BIT(0) + +/* Channel Pending Register */ +#define NXP_SAR_ADC_CEOCFR0(__base) ((__base) + 0x14) +#define NXP_SAR_ADC_CEOCFR1(__base) ((__base) + 0x18) + +#define NXP_SAR_ADC_EOC_CH(c) BIT(c) + +/* Interrupt Mask Register */ +#define NXP_SAR_ADC_IMR(__base) ((__base) + 0x20) + +/* Channel Interrupt Mask Register */ +#define NXP_SAR_ADC_CIMR0(__base) ((__base) + 0x24) +#define NXP_SAR_ADC_CIMR1(__base) ((__base) + 0x28) + +/* DMA Setting Register */ +#define NXP_SAR_ADC_DMAE(__base) ((__base) + 0x40) + +#define NXP_SAR_ADC_DMAE_DMAEN BIT(0) +#define NXP_SAR_ADC_DMAE_DCLR BIT(1) + +/* DMA Control register */ +#define NXP_SAR_ADC_DMAR0(__base) ((__base) + 0x44) +#define NXP_SAR_ADC_DMAR1(__base) ((__base) + 0x48) + +/* Conversion Timing Register */ +#define NXP_SAR_ADC_CTR0(__base) ((__base) + 0x94) +#define NXP_SAR_ADC_CTR1(__base) ((__base) + 0x98) + +#define NXP_SAR_ADC_CTR_INPSAMP_MIN 0x08 +#define NXP_SAR_ADC_CTR_INPSAMP_MAX 0xff + +/* Normal Conversion Mask Register */ +#define NXP_SAR_ADC_NCMR0(__base) ((__base) + 0xa4) +#define NXP_SAR_ADC_NCMR1(__base) ((__base) + 0xa8) + +/* Normal Conversion Mask Register field define */ +#define NXP_SAR_ADC_CH_MASK GENMASK(7, 0) + +/* Other field define */ +#define NXP_SAR_ADC_CONV_TIMEOUT (msecs_to_jiffies(100)) +#define NXP_SAR_ADC_CAL_TIMEOUT_US (100 * USEC_PER_MSEC) +#define NXP_SAR_ADC_WAIT_US (2 * USEC_PER_MSEC) +#define NXP_SAR_ADC_RESOLUTION 12 + +/* Duration of conversion phases */ +#define NXP_SAR_ADC_TPT 2 +#define NXP_SAR_ADC_DP 2 +#define NXP_SAR_ADC_CT ((NXP_SAR_ADC_RESOLUTION + 2) * 4) +#define NXP_SAR_ADC_CONV_TIME (NXP_SAR_ADC_TPT + NXP_SAR_ADC_CT + NXP_SAR_ADC_DP) + +#define NXP_SAR_ADC_NR_CHANNELS 8 + +#define NXP_PAGE_SIZE SZ_4K +#define NXP_SAR_ADC_DMA_SAMPLE_SZ DMA_SLAVE_BUSWIDTH_4_BYTES +#define NXP_SAR_ADC_DMA_BUFF_SZ (NXP_PAGE_SIZE * NXP_SAR_ADC_DMA_SAMPLE_SZ) +#define NXP_SAR_ADC_DMA_SAMPLE_CNT (NXP_SAR_ADC_DMA_BUFF_SZ / NXP_SAR_ADC_DMA_SAMPLE_SZ) + +struct nxp_sar_adc { + void __iomem *regs; + phys_addr_t regs_phys; + u8 current_channel; + u8 channels_used; + u16 value; + u32 vref_mV; + + /* Save and restore context. */ + u32 inpsamp; + u32 pwdn; + + struct clk *clk; + struct dma_chan *dma_chan; + struct completion completion; + struct circ_buf dma_buf; + + dma_addr_t rx_dma_buf; + dma_cookie_t cookie; + + /* Protect circular buffers access. */ + spinlock_t lock; + + /* Array of enabled channels. */ + u16 buffered_chan[NXP_SAR_ADC_NR_CHANNELS]; + + /* Buffer to be filled by the DMA. */ + IIO_DECLARE_BUFFER_WITH_TS(u16, buffer, NXP_SAR_ADC_NR_CHANNELS); +}; + +struct nxp_sar_adc_data { + u32 vref_mV; + const char *model; +}; + +#define ADC_CHAN(_idx, _chan_type) { \ + .type = (_chan_type), \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = (_idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + .storagebits = 16, \ + }, \ +} + +static const struct iio_chan_spec nxp_sar_adc_iio_channels[] = { + ADC_CHAN(0, IIO_VOLTAGE), + ADC_CHAN(1, IIO_VOLTAGE), + ADC_CHAN(2, IIO_VOLTAGE), + ADC_CHAN(3, IIO_VOLTAGE), + ADC_CHAN(4, IIO_VOLTAGE), + ADC_CHAN(5, IIO_VOLTAGE), + ADC_CHAN(6, IIO_VOLTAGE), + ADC_CHAN(7, IIO_VOLTAGE), + /* + * The NXP SAR ADC documentation marks the channels 8 to 31 as + * "Reserved". Reflect the same in the driver in case new ADC + * variants comes with more channels. + */ + IIO_CHAN_SOFT_TIMESTAMP(32), +}; + +static void nxp_sar_adc_irq_cfg(struct nxp_sar_adc *info, bool enable) +{ + if (enable) + writel(NXP_SAR_ADC_ISR_ECH, NXP_SAR_ADC_IMR(info->regs)); + else + writel(0, NXP_SAR_ADC_IMR(info->regs)); +} + +static bool nxp_sar_adc_set_enabled(struct nxp_sar_adc *info, bool enable) +{ + u32 mcr; + bool pwdn; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + /* + * Get the current state and return it later. This is used for + * suspend/resume to get the power state + */ + pwdn = FIELD_GET(NXP_SAR_ADC_MCR_PWDN, mcr); + + /* When the enabled flag is not set, we set the power down bit */ + FIELD_MODIFY(NXP_SAR_ADC_MCR_PWDN, &mcr, !enable); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + /* + * Ensure there are at least three cycles between the + * configuration of NCMR and the setting of NSTART. + */ + if (enable) + ndelay(div64_u64(NSEC_PER_SEC, clk_get_rate(info->clk) * 3)); + + return pwdn; +} + +static inline bool nxp_sar_adc_enable(struct nxp_sar_adc *info) +{ + return nxp_sar_adc_set_enabled(info, true); +} + +static inline bool nxp_sar_adc_disable(struct nxp_sar_adc *info) +{ + return nxp_sar_adc_set_enabled(info, false); +} + +static inline void nxp_sar_adc_calibration_start(void __iomem *base) +{ + u32 mcr = readl(NXP_SAR_ADC_MCR(base)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_CALSTART, &mcr, 0x1); + + writel(mcr, NXP_SAR_ADC_MCR(base)); +} + +static inline int nxp_sar_adc_calibration_wait(void __iomem *base) +{ + u32 msr, ret; + + ret = readl_poll_timeout(NXP_SAR_ADC_MSR(base), msr, + !FIELD_GET(NXP_SAR_ADC_MSR_CALBUSY, msr), + NXP_SAR_ADC_WAIT_US, + NXP_SAR_ADC_CAL_TIMEOUT_US); + if (ret) + return ret; + + if (FIELD_GET(NXP_SAR_ADC_MSR_CALFAIL, msr)) { + /* + * If the calibration fails, the status register bit must be + * cleared. + */ + FIELD_MODIFY(NXP_SAR_ADC_MSR_CALFAIL, &msr, 0x0); + writel(msr, NXP_SAR_ADC_MSR(base)); + + return -EAGAIN; + } + + return 0; +} + +static int nxp_sar_adc_calibration(struct nxp_sar_adc *info) +{ + int ret; + + /* Calibration works only if the ADC is powered up. */ + nxp_sar_adc_enable(info); + + /* The calibration operation starts. */ + nxp_sar_adc_calibration_start(info->regs); + + ret = nxp_sar_adc_calibration_wait(info->regs); + + /* + * Calibration works only if the ADC is powered up. However + * the calibration is called from the probe function where the + * iio is not enabled, so we disable after the calibration. + */ + nxp_sar_adc_disable(info); + + return ret; +} + +static void nxp_sar_adc_conversion_timing_set(struct nxp_sar_adc *info, u32 inpsamp) +{ + inpsamp = clamp(inpsamp, NXP_SAR_ADC_CTR_INPSAMP_MIN, NXP_SAR_ADC_CTR_INPSAMP_MAX); + + writel(inpsamp, NXP_SAR_ADC_CTR0(info->regs)); +} + +static u32 nxp_sar_adc_conversion_timing_get(struct nxp_sar_adc *info) +{ + return readl(NXP_SAR_ADC_CTR0(info->regs)); +} + +static void nxp_sar_adc_read_notify(struct nxp_sar_adc *info) +{ + writel(NXP_SAR_ADC_CH_MASK, NXP_SAR_ADC_CEOCFR0(info->regs)); + writel(NXP_SAR_ADC_CH_MASK, NXP_SAR_ADC_CEOCFR1(info->regs)); +} + +static int nxp_sar_adc_read_data(struct nxp_sar_adc *info, unsigned int chan) +{ + u32 ceocfr, cdr; + + ceocfr = readl(NXP_SAR_ADC_CEOCFR0(info->regs)); + + /* + * FIELD_GET() can not be used here because EOC_CH is not constant. + * TODO: Switch to field_get() when it will be available. + */ + if (!(NXP_SAR_ADC_EOC_CH(chan) & ceocfr)) + return -EIO; + + cdr = readl(NXP_SAR_ADC_CDR(info->regs, chan)); + if (!(FIELD_GET(NXP_SAR_ADC_CDR_VALID, cdr))) + return -EIO; + + return FIELD_GET(NXP_SAR_ADC_CDR_CDATA_MASK, cdr); +} + +static void nxp_sar_adc_isr_buffer(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + unsigned int i; + int ret; + + for (i = 0; i < info->channels_used; i++) { + ret = nxp_sar_adc_read_data(info, info->buffered_chan[i]); + if (ret < 0) { + nxp_sar_adc_read_notify(info); + return; + } + + info->buffer[i] = ret; + } + + nxp_sar_adc_read_notify(info); + + iio_push_to_buffers_with_ts(indio_dev, info->buffer, sizeof(info->buffer), + iio_get_time_ns(indio_dev)); + + iio_trigger_notify_done(indio_dev->trig); +} + +static void nxp_sar_adc_isr_read_raw(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + ret = nxp_sar_adc_read_data(info, info->current_channel); + nxp_sar_adc_read_notify(info); + if (ret < 0) + return; + + info->value = ret; + complete(&info->completion); +} + +static irqreturn_t nxp_sar_adc_isr(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct nxp_sar_adc *info = iio_priv(indio_dev); + int isr; + + isr = readl(NXP_SAR_ADC_ISR(info->regs)); + if (!(FIELD_GET(NXP_SAR_ADC_ISR_ECH, isr))) + return IRQ_NONE; + + if (iio_buffer_enabled(indio_dev)) + nxp_sar_adc_isr_buffer(indio_dev); + else + nxp_sar_adc_isr_read_raw(indio_dev); + + writel(NXP_SAR_ADC_ISR_ECH, NXP_SAR_ADC_ISR(info->regs)); + + return IRQ_HANDLED; +} + +static void nxp_sar_adc_channels_disable(struct nxp_sar_adc *info, u32 mask) +{ + u32 ncmr, cimr; + + ncmr = readl(NXP_SAR_ADC_NCMR0(info->regs)); + cimr = readl(NXP_SAR_ADC_CIMR0(info->regs)); + + /* FIELD_MODIFY() can not be used because the mask is not constant */ + ncmr &= ~mask; + cimr &= ~mask; + + writel(ncmr, NXP_SAR_ADC_NCMR0(info->regs)); + writel(cimr, NXP_SAR_ADC_CIMR0(info->regs)); +} + +static void nxp_sar_adc_channels_enable(struct nxp_sar_adc *info, u32 mask) +{ + u32 ncmr, cimr; + + ncmr = readl(NXP_SAR_ADC_NCMR0(info->regs)); + cimr = readl(NXP_SAR_ADC_CIMR0(info->regs)); + + ncmr |= mask; + cimr |= mask; + + writel(ncmr, NXP_SAR_ADC_NCMR0(info->regs)); + writel(cimr, NXP_SAR_ADC_CIMR0(info->regs)); +} + +static void nxp_sar_adc_dma_channels_enable(struct nxp_sar_adc *info, u32 mask) +{ + u32 dmar; + + dmar = readl(NXP_SAR_ADC_DMAR0(info->regs)); + + dmar |= mask; + + writel(dmar, NXP_SAR_ADC_DMAR0(info->regs)); +} + +static void nxp_sar_adc_dma_channels_disable(struct nxp_sar_adc *info, u32 mask) +{ + u32 dmar; + + dmar = readl(NXP_SAR_ADC_DMAR0(info->regs)); + + dmar &= ~mask; + + writel(dmar, NXP_SAR_ADC_DMAR0(info->regs)); +} + +static void nxp_sar_adc_dma_cfg(struct nxp_sar_adc *info, bool enable) +{ + u32 dmae; + + dmae = readl(NXP_SAR_ADC_DMAE(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_DMAE_DMAEN, &dmae, enable); + + writel(dmae, NXP_SAR_ADC_DMAE(info->regs)); +} + +static void nxp_sar_adc_stop_conversion(struct nxp_sar_adc *info) +{ + u32 mcr; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_NSTART, &mcr, 0x0); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + /* + * On disable, we have to wait for the transaction to finish. + * ADC does not abort the transaction if a chain conversion is + * in progress. Wait for the worst case scenario - 80 ADC clk + * cycles. The clock rate is 80MHz, this routine is called + * only when the capture finishes. The delay will be very + * short, usec-ish, which is acceptable in the atomic context. + */ + ndelay(div64_u64(NSEC_PER_SEC, clk_get_rate(info->clk)) * 80); +} + +static int nxp_sar_adc_start_conversion(struct nxp_sar_adc *info, bool raw) +{ + u32 mcr; + + mcr = readl(NXP_SAR_ADC_MCR(info->regs)); + + FIELD_MODIFY(NXP_SAR_ADC_MCR_NSTART, &mcr, 0x1); + FIELD_MODIFY(NXP_SAR_ADC_MCR_MODE, &mcr, raw ? 0 : 1); + + writel(mcr, NXP_SAR_ADC_MCR(info->regs)); + + return 0; +} + +static int nxp_sar_adc_read_channel(struct nxp_sar_adc *info, int channel) +{ + int ret; + + info->current_channel = channel; + nxp_sar_adc_channels_enable(info, BIT(channel)); + nxp_sar_adc_irq_cfg(info, true); + nxp_sar_adc_enable(info); + + reinit_completion(&info->completion); + ret = nxp_sar_adc_start_conversion(info, true); + if (ret < 0) + goto out_disable; + + if (!wait_for_completion_interruptible_timeout(&info->completion, + NXP_SAR_ADC_CONV_TIMEOUT)) + ret = -ETIMEDOUT; + + nxp_sar_adc_stop_conversion(info); + +out_disable: + nxp_sar_adc_channels_disable(info, BIT(channel)); + nxp_sar_adc_irq_cfg(info, false); + nxp_sar_adc_disable(info); + + return ret; +} + +static int nxp_sar_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + u32 inpsamp; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = nxp_sar_adc_read_channel(info, chan->channel); + + iio_device_release_direct(indio_dev); + + if (ret) + return ret; + + *val = info->value; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = info->vref_mV; + *val2 = NXP_SAR_ADC_RESOLUTION; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + inpsamp = nxp_sar_adc_conversion_timing_get(info); + *val = clk_get_rate(info->clk) / (inpsamp + NXP_SAR_ADC_CONV_TIME); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int nxp_sar_adc_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + u32 inpsamp; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * Configures the sample period duration in terms of the SAR + * controller clock. The minimum acceptable value is 8. + * Configuring it to a value lower than 8 sets the sample period + * to 8 cycles. We read the clock value and divide by the + * sampling timing which gives us the number of cycles expected. + * The value is 8-bit wide, consequently the max value is 0xFF. + */ + inpsamp = clk_get_rate(info->clk) / val - NXP_SAR_ADC_CONV_TIME; + nxp_sar_adc_conversion_timing_set(info, inpsamp); + return 0; + + default: + return -EINVAL; + } +} + +static void nxp_sar_adc_dma_cb(void *data) +{ + struct iio_dev *indio_dev = data; + struct nxp_sar_adc *info = iio_priv(indio_dev); + struct dma_tx_state state; + struct circ_buf *dma_buf; + struct device *dev_dma; + u32 *dma_samples; + s64 timestamp; + int idx, ret; + + guard(spinlock_irqsave)(&info->lock); + + dma_buf = &info->dma_buf; + dma_samples = (u32 *)dma_buf->buf; + dev_dma = info->dma_chan->device->dev; + + /* + * DMA in some corner cases might have already be charged for + * the next transfer. Potentially there can be a race where + * the residue changes while the dma engine updates the + * buffer. That could be handled by using the + * callback_result() instead of callback() because the residue + * will be passed as a parameter to the function. However this + * new callback is pretty new and the backend does not update + * the residue. So let's stick to the version other drivers do + * which has proven running well in production since several + * years. + */ + dmaengine_tx_status(info->dma_chan, info->cookie, &state); + + dma_sync_single_for_cpu(dev_dma, info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, DMA_FROM_DEVICE); + + /* Current head position. */ + dma_buf->head = (NXP_SAR_ADC_DMA_BUFF_SZ - state.residue) / + NXP_SAR_ADC_DMA_SAMPLE_SZ; + + /* If everything was transferred, avoid an off by one error. */ + if (!state.residue) + dma_buf->head--; + + /* Something went wrong and nothing transferred. */ + if (state.residue != NXP_SAR_ADC_DMA_BUFF_SZ) { + /* Make sure that head is multiple of info->channels_used. */ + dma_buf->head -= dma_buf->head % info->channels_used; + + /* + * dma_buf->tail != dma_buf->head condition will become false + * because dma_buf->tail will be incremented with 1. + */ + while (dma_buf->tail != dma_buf->head) { + idx = dma_buf->tail % info->channels_used; + info->buffer[idx] = dma_samples[dma_buf->tail]; + dma_buf->tail = (dma_buf->tail + 1) % NXP_SAR_ADC_DMA_SAMPLE_CNT; + if (idx != info->channels_used - 1) + continue; + + /* + * iio_push_to_buffers_with_ts() should not be + * called with dma_samples as parameter. The samples + * will be smashed if timestamp is enabled. + */ + timestamp = iio_get_time_ns(indio_dev); + ret = iio_push_to_buffers_with_ts(indio_dev, info->buffer, + sizeof(info->buffer), + timestamp); + if (ret < 0 && ret != -EBUSY) + dev_err_ratelimited(&indio_dev->dev, + "failed to push iio buffer: %d", + ret); + } + + dma_buf->tail = dma_buf->head; + } + + dma_sync_single_for_device(dev_dma, info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, DMA_FROM_DEVICE); +} + +static int nxp_sar_adc_start_cyclic_dma(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + struct dma_slave_config config; + struct dma_async_tx_descriptor *desc; + int ret; + + info->dma_buf.head = 0; + info->dma_buf.tail = 0; + + config.direction = DMA_DEV_TO_MEM; + config.src_addr_width = NXP_SAR_ADC_DMA_SAMPLE_SZ; + config.src_addr = NXP_SAR_ADC_CDR(info->regs_phys, info->buffered_chan[0]); + config.src_port_window_size = info->channels_used; + config.src_maxburst = info->channels_used; + ret = dmaengine_slave_config(info->dma_chan, &config); + if (ret < 0) + return ret; + + desc = dmaengine_prep_dma_cyclic(info->dma_chan, + info->rx_dma_buf, + NXP_SAR_ADC_DMA_BUFF_SZ, + NXP_SAR_ADC_DMA_BUFF_SZ / 2, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + if (!desc) + return -EINVAL; + + desc->callback = nxp_sar_adc_dma_cb; + desc->callback_param = indio_dev; + info->cookie = dmaengine_submit(desc); + ret = dma_submit_error(info->cookie); + if (ret) { + dmaengine_terminate_async(info->dma_chan); + return ret; + } + + dma_async_issue_pending(info->dma_chan); + + return 0; +} + +static void nxp_sar_adc_buffer_software_do_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + /* + * The ADC DMAEN bit should be cleared before DMA transaction + * is canceled. + */ + nxp_sar_adc_stop_conversion(info); + dmaengine_terminate_sync(info->dma_chan); + nxp_sar_adc_dma_cfg(info, false); + nxp_sar_adc_dma_channels_disable(info, *indio_dev->active_scan_mask); + + dma_release_channel(info->dma_chan); +} + +static int nxp_sar_adc_buffer_software_do_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + nxp_sar_adc_dma_channels_enable(info, *indio_dev->active_scan_mask); + + nxp_sar_adc_dma_cfg(info, true); + + ret = nxp_sar_adc_start_cyclic_dma(indio_dev); + if (ret) + goto out_dma_channels_disable; + + ret = nxp_sar_adc_start_conversion(info, false); + if (ret) + goto out_stop_cyclic_dma; + + return 0; + +out_stop_cyclic_dma: + dmaengine_terminate_sync(info->dma_chan); + +out_dma_channels_disable: + nxp_sar_adc_dma_cfg(info, false); + nxp_sar_adc_dma_channels_disable(info, *indio_dev->active_scan_mask); + + return ret; +} + +static void nxp_sar_adc_buffer_trigger_do_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + nxp_sar_adc_irq_cfg(info, false); +} + +static int nxp_sar_adc_buffer_trigger_do_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + + nxp_sar_adc_irq_cfg(info, true); + + return 0; +} + +static int nxp_sar_adc_buffer_postenable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int current_mode = iio_device_get_current_mode(indio_dev); + unsigned long channel; + int ret; + + info->dma_chan = dma_request_chan(indio_dev->dev.parent, "rx"); + if (IS_ERR(info->dma_chan)) + return PTR_ERR(info->dma_chan); + + info->channels_used = 0; + + /* + * The SAR-ADC has two groups of channels. + * + * - Group #0: + * * bit 0-7 : channel 0 -> channel 7 + * * bit 8-31 : reserved + * + * - Group #32: + * * bit 0-7 : Internal + * * bit 8-31 : reserved + * + * The 8 channels from group #0 are used in this driver for + * ADC as described when declaring the IIO device and the + * mapping is the same. That means the active_scan_mask can be + * used directly to write the channel interrupt mask. + */ + nxp_sar_adc_channels_enable(info, *indio_dev->active_scan_mask); + + for_each_set_bit(channel, indio_dev->active_scan_mask, NXP_SAR_ADC_NR_CHANNELS) + info->buffered_chan[info->channels_used++] = channel; + + nxp_sar_adc_enable(info); + + if (current_mode == INDIO_BUFFER_SOFTWARE) + ret = nxp_sar_adc_buffer_software_do_postenable(indio_dev); + else + ret = nxp_sar_adc_buffer_trigger_do_postenable(indio_dev); + if (ret) + goto out_postenable; + + return 0; + +out_postenable: + nxp_sar_adc_disable(info); + nxp_sar_adc_channels_disable(info, *indio_dev->active_scan_mask); + + return ret; +} + +static int nxp_sar_adc_buffer_predisable(struct iio_dev *indio_dev) +{ + struct nxp_sar_adc *info = iio_priv(indio_dev); + int currentmode = iio_device_get_current_mode(indio_dev); + + if (currentmode == INDIO_BUFFER_SOFTWARE) + nxp_sar_adc_buffer_software_do_predisable(indio_dev); + else + nxp_sar_adc_buffer_trigger_do_predisable(indio_dev); + + nxp_sar_adc_disable(info); + + nxp_sar_adc_channels_disable(info, *indio_dev->active_scan_mask); + + return 0; +} + +static irqreturn_t nxp_sar_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct nxp_sar_adc *info = iio_priv(indio_dev); + int ret; + + ret = nxp_sar_adc_start_conversion(info, true); + if (ret < 0) + dev_dbg(&indio_dev->dev, "Failed to start conversion\n"); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .postenable = nxp_sar_adc_buffer_postenable, + .predisable = nxp_sar_adc_buffer_predisable, +}; + +static const struct iio_info nxp_sar_adc_iio_info = { + .read_raw = nxp_sar_adc_read_raw, + .write_raw = nxp_sar_adc_write_raw, +}; + +static int nxp_sar_adc_dma_probe(struct device *dev, struct nxp_sar_adc *info) +{ + u8 *rx_buf; + + rx_buf = dmam_alloc_coherent(dev, NXP_SAR_ADC_DMA_BUFF_SZ, + &info->rx_dma_buf, GFP_KERNEL); + if (!rx_buf) + return -ENOMEM; + + info->dma_buf.buf = rx_buf; + + return 0; +} + +/* + * The documentation describes the reset values for the registers. + * However some registers do not have these values after a reset. It + * is not a desirable situation. In some other SoC family + * documentation NXP recommends not assuming the default values are + * set and to initialize the registers conforming to the documentation + * reset information to prevent this situation. Assume the same rule + * applies here as there is a discrepancy between what is read from + * the registers at reset time and the documentation. + */ +static void nxp_sar_adc_set_default_values(struct nxp_sar_adc *info) +{ + writel(0x00003901, NXP_SAR_ADC_MCR(info->regs)); + writel(0x00000001, NXP_SAR_ADC_MSR(info->regs)); + writel(0x00000014, NXP_SAR_ADC_CTR0(info->regs)); + writel(0x00000014, NXP_SAR_ADC_CTR1(info->regs)); + writel(0x00000000, NXP_SAR_ADC_CIMR0(info->regs)); + writel(0x00000000, NXP_SAR_ADC_CIMR1(info->regs)); + writel(0x00000000, NXP_SAR_ADC_NCMR0(info->regs)); + writel(0x00000000, NXP_SAR_ADC_NCMR1(info->regs)); +} + +static int nxp_sar_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct nxp_sar_adc_data *data = device_get_match_data(dev); + struct nxp_sar_adc *info; + struct iio_dev *indio_dev; + struct resource *mem; + int irq, ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + info->vref_mV = data->vref_mV; + spin_lock_init(&info->lock); + info->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); + if (IS_ERR(info->regs)) + return dev_err_probe(dev, PTR_ERR(info->regs), + "Failed to get and remap resource"); + + info->regs_phys = mem->start; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, nxp_sar_adc_isr, 0, dev_name(dev), + indio_dev); + if (ret < 0) + return ret; + + info->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(info->clk)) + return dev_err_probe(dev, PTR_ERR(info->clk), + "Failed to get the clock\n"); + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&info->completion); + + indio_dev->name = data->model; + indio_dev->info = &nxp_sar_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->channels = nxp_sar_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(nxp_sar_adc_iio_channels); + + nxp_sar_adc_set_default_values(info); + + ret = nxp_sar_adc_calibration(info); + if (ret) + dev_err_probe(dev, ret, "Calibration failed\n"); + + ret = nxp_sar_adc_dma_probe(dev, info); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize the DMA\n"); + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &nxp_sar_adc_trigger_handler, + &iio_triggered_buffer_setup_ops); + if (ret < 0) + return dev_err_probe(dev, ret, "Couldn't initialise the buffer\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Couldn't register the device\n"); + + return 0; +} + +static int nxp_sar_adc_suspend(struct device *dev) +{ + struct nxp_sar_adc *info = iio_priv(dev_get_drvdata(dev)); + + info->pwdn = nxp_sar_adc_disable(info); + info->inpsamp = nxp_sar_adc_conversion_timing_get(info); + + clk_disable_unprepare(info->clk); + + return 0; +} + +static int nxp_sar_adc_resume(struct device *dev) +{ + struct nxp_sar_adc *info = iio_priv(dev_get_drvdata(dev)); + int ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + return ret; + + nxp_sar_adc_conversion_timing_set(info, info->inpsamp); + + if (!info->pwdn) + nxp_sar_adc_enable(info); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(nxp_sar_adc_pm_ops, nxp_sar_adc_suspend, + nxp_sar_adc_resume); + +static const struct nxp_sar_adc_data s32g2_sar_adc_data = { + .vref_mV = 1800, + .model = "s32g2-sar-adc", +}; + +static const struct of_device_id nxp_sar_adc_match[] = { + { .compatible = "nxp,s32g2-sar-adc", .data = &s32g2_sar_adc_data }, + { } +}; +MODULE_DEVICE_TABLE(of, nxp_sar_adc_match); + +static struct platform_driver nxp_sar_adc_driver = { + .probe = nxp_sar_adc_probe, + .driver = { + .name = "nxp-sar-adc", + .of_match_table = nxp_sar_adc_match, + .pm = pm_sleep_ptr(&nxp_sar_adc_pm_ops), + }, +}; +module_platform_driver(nxp_sar_adc_driver); + +MODULE_AUTHOR("NXP"); +MODULE_DESCRIPTION("NXP SAR-ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/adc/qcom-spmi-rradc.c b/drivers/iio/adc/qcom-spmi-rradc.c index b245416bae12..8e75665204d1 100644 --- a/drivers/iio/adc/qcom-spmi-rradc.c +++ b/drivers/iio/adc/qcom-spmi-rradc.c @@ -934,20 +934,15 @@ static int rradc_probe(struct platform_device *pdev) chip = iio_priv(indio_dev); chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); - if (!chip->regmap) { - dev_err(dev, "Couldn't get parent's regmap\n"); - return -EINVAL; - } + if (!chip->regmap) + return dev_err_probe(dev, -EINVAL, "Couldn't get parent's regmap\n"); chip->dev = dev; mutex_init(&chip->conversion_lock); ret = device_property_read_u32(dev, "reg", &chip->base); - if (ret < 0) { - dev_err(chip->dev, "Couldn't find reg address, ret = %d\n", - ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Couldn't find reg address\n"); batt_id_delay = -1; ret = device_property_read_u32(dev, "qcom,batt-id-delay-ms", @@ -975,10 +970,9 @@ static int rradc_probe(struct platform_device *pdev) /* Get the PMIC revision, we need it to handle some varying coefficients */ chip->pmic = qcom_pmic_get(chip->dev); - if (IS_ERR(chip->pmic)) { - dev_err(chip->dev, "Unable to get reference to PMIC device\n"); - return PTR_ERR(chip->pmic); - } + if (IS_ERR(chip->pmic)) + return dev_err_probe(dev, PTR_ERR(chip->pmic), + "Unable to get reference to PMIC device\n"); switch (chip->pmic->subtype) { case PMI8998_SUBTYPE: diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c index 6721da0ed7bb..0f0bf2906af0 100644 --- a/drivers/iio/adc/rockchip_saradc.c +++ b/drivers/iio/adc/rockchip_saradc.c @@ -456,6 +456,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev) { const struct rockchip_saradc_data *match_data; struct rockchip_saradc *info = NULL; + struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; struct iio_dev *indio_dev = NULL; int ret; @@ -464,23 +465,21 @@ static int rockchip_saradc_probe(struct platform_device *pdev) if (!np) return -ENODEV; - indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); if (!indio_dev) return -ENOMEM; info = iio_priv(indio_dev); - match_data = of_device_get_match_data(&pdev->dev); + match_data = of_device_get_match_data(dev); if (!match_data) - return dev_err_probe(&pdev->dev, -ENODEV, - "failed to match device\n"); + return dev_err_probe(dev, -ENODEV, "failed to match device\n"); info->data = match_data; /* Sanity check for possible later IP variants with more channels */ if (info->data->num_channels > SARADC_MAX_CHANNELS) - return dev_err_probe(&pdev->dev, -EINVAL, - "max channels exceeded"); + return dev_err_probe(dev, -EINVAL, "max channels exceeded"); info->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(info->regs)) @@ -490,12 +489,10 @@ static int rockchip_saradc_probe(struct platform_device *pdev) * The reset should be an optional property, as it should work * with old devicetrees as well */ - info->reset = devm_reset_control_get_optional_exclusive(&pdev->dev, - "saradc-apb"); - if (IS_ERR(info->reset)) { - ret = PTR_ERR(info->reset); - return dev_err_probe(&pdev->dev, ret, "failed to get saradc-apb\n"); - } + info->reset = devm_reset_control_get_optional_exclusive(dev, "saradc-apb"); + if (IS_ERR(info->reset)) + return dev_err_probe(dev, PTR_ERR(info->reset), + "failed to get saradc-apb\n"); init_completion(&info->completion); @@ -503,16 +500,14 @@ static int rockchip_saradc_probe(struct platform_device *pdev) if (irq < 0) return irq; - ret = devm_request_irq(&pdev->dev, irq, rockchip_saradc_isr, + ret = devm_request_irq(dev, irq, rockchip_saradc_isr, 0, dev_name(&pdev->dev), info); - if (ret < 0) { - dev_err(&pdev->dev, "failed requesting irq %d\n", irq); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "failed requesting irq %d\n", irq); - info->vref = devm_regulator_get(&pdev->dev, "vref"); + info->vref = devm_regulator_get(dev, "vref"); if (IS_ERR(info->vref)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->vref), + return dev_err_probe(dev, PTR_ERR(info->vref), "failed to get regulator\n"); if (info->reset) @@ -520,11 +515,9 @@ static int rockchip_saradc_probe(struct platform_device *pdev) ret = regulator_enable(info->vref); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "failed to enable vref regulator\n"); + return dev_err_probe(dev, ret, "failed to enable vref regulator\n"); - ret = devm_add_action_or_reset(&pdev->dev, - rockchip_saradc_regulator_disable, info); + ret = devm_add_action_or_reset(dev, rockchip_saradc_regulator_disable, info); if (ret) return ret; @@ -534,14 +527,13 @@ static int rockchip_saradc_probe(struct platform_device *pdev) info->uv_vref = ret; - info->pclk = devm_clk_get_enabled(&pdev->dev, "apb_pclk"); + info->pclk = devm_clk_get_enabled(dev, "apb_pclk"); if (IS_ERR(info->pclk)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->pclk), - "failed to get pclk\n"); + return dev_err_probe(dev, PTR_ERR(info->pclk), "failed to get pclk\n"); - info->clk = devm_clk_get_enabled(&pdev->dev, "saradc"); + info->clk = devm_clk_get_enabled(dev, "saradc"); if (IS_ERR(info->clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(info->clk), + return dev_err_probe(dev, PTR_ERR(info->clk), "failed to get adc clock\n"); /* * Use a default value for the converter clock. @@ -549,18 +541,17 @@ static int rockchip_saradc_probe(struct platform_device *pdev) */ ret = clk_set_rate(info->clk, info->data->clk_rate); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "failed to set adc clk rate\n"); + return dev_err_probe(dev, ret, "failed to set adc clk rate\n"); platform_set_drvdata(pdev, indio_dev); - indio_dev->name = dev_name(&pdev->dev); + indio_dev->name = dev_name(dev); indio_dev->info = &rockchip_saradc_iio_info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = info->data->channels; indio_dev->num_channels = info->data->num_channels; - ret = devm_iio_triggered_buffer_setup(&indio_dev->dev, indio_dev, NULL, + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, rockchip_saradc_trigger_handler, NULL); if (ret) @@ -571,7 +562,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev) if (ret) return ret; - ret = devm_add_action_or_reset(&pdev->dev, + ret = devm_add_action_or_reset(dev, rockchip_saradc_regulator_unreg_notifier, info); if (ret) @@ -579,7 +570,7 @@ static int rockchip_saradc_probe(struct platform_device *pdev) mutex_init(&info->lock); - return devm_iio_device_register(&pdev->dev, indio_dev); + return devm_iio_device_register(dev, indio_dev); } static int rockchip_saradc_suspend(struct device *dev) diff --git a/drivers/iio/adc/sc27xx_adc.c b/drivers/iio/adc/sc27xx_adc.c index 2535c2c3e60b..6209499c5c37 100644 --- a/drivers/iio/adc/sc27xx_adc.c +++ b/drivers/iio/adc/sc27xx_adc.c @@ -867,10 +867,8 @@ static int sc27xx_adc_probe(struct platform_device *pdev) int ret; pdata = of_device_get_match_data(dev); - if (!pdata) { - dev_err(dev, "No matching driver data found\n"); - return -EINVAL; - } + if (!pdata) + return dev_err_probe(dev, -EINVAL, "No matching driver data found\n"); indio_dev = devm_iio_device_alloc(dev, sizeof(*sc27xx_data)); if (!indio_dev) @@ -879,56 +877,43 @@ static int sc27xx_adc_probe(struct platform_device *pdev) sc27xx_data = iio_priv(indio_dev); sc27xx_data->regmap = dev_get_regmap(dev->parent, NULL); - if (!sc27xx_data->regmap) { - dev_err(dev, "failed to get ADC regmap\n"); - return -ENODEV; - } + if (!sc27xx_data->regmap) + return dev_err_probe(dev, -ENODEV, "failed to get ADC regmap\n"); ret = of_property_read_u32(np, "reg", &sc27xx_data->base); - if (ret) { - dev_err(dev, "failed to get ADC base address\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to get ADC base address\n"); sc27xx_data->irq = platform_get_irq(pdev, 0); if (sc27xx_data->irq < 0) return sc27xx_data->irq; ret = of_hwspin_lock_get_id(np, 0); - if (ret < 0) { - dev_err(dev, "failed to get hwspinlock id\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "failed to get hwspinlock id\n"); sc27xx_data->hwlock = devm_hwspin_lock_request_specific(dev, ret); - if (!sc27xx_data->hwlock) { - dev_err(dev, "failed to request hwspinlock\n"); - return -ENXIO; - } + if (!sc27xx_data->hwlock) + return dev_err_probe(dev, -ENXIO, "failed to request hwspinlock\n"); sc27xx_data->dev = dev; if (pdata->set_volref) { sc27xx_data->volref = devm_regulator_get(dev, "vref"); - if (IS_ERR(sc27xx_data->volref)) { - ret = PTR_ERR(sc27xx_data->volref); - return dev_err_probe(dev, ret, "failed to get ADC volref\n"); - } + if (IS_ERR(sc27xx_data->volref)) + return dev_err_probe(dev, PTR_ERR(sc27xx_data->volref), + "failed to get ADC volref\n"); } sc27xx_data->var_data = pdata; sc27xx_data->var_data->init_scale(sc27xx_data); ret = sc27xx_adc_enable(sc27xx_data); - if (ret) { - dev_err(dev, "failed to enable ADC module\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to enable ADC module\n"); ret = devm_add_action_or_reset(dev, sc27xx_adc_disable, sc27xx_data); - if (ret) { - dev_err(dev, "failed to add ADC disable action\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to add ADC disable action\n"); indio_dev->name = dev_name(dev); indio_dev->modes = INDIO_DIRECT_MODE; diff --git a/drivers/iio/adc/ti-ads1018.c b/drivers/iio/adc/ti-ads1018.c new file mode 100644 index 000000000000..6246b3cab71f --- /dev/null +++ b/drivers/iio/adc/ti-ads1018.c @@ -0,0 +1,739 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Texas Instruments ADS1018 ADC driver + * + * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com> + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitmap.h> +#include <linux/dev_printk.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/math.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <asm/byteorder.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#define ADS1018_CFG_OS_TRIG BIT(15) +#define ADS1018_CFG_TS_MODE_EN BIT(4) +#define ADS1018_CFG_PULL_UP BIT(3) +#define ADS1018_CFG_NOP BIT(1) +#define ADS1018_CFG_VALID (ADS1018_CFG_PULL_UP | ADS1018_CFG_NOP) + +#define ADS1018_CFG_MUX_MASK GENMASK(14, 12) + +#define ADS1018_CFG_PGA_MASK GENMASK(11, 9) +#define ADS1018_PGA_DEFAULT 2 + +#define ADS1018_CFG_MODE_MASK BIT(8) +#define ADS1018_MODE_CONTINUOUS 0 +#define ADS1018_MODE_ONESHOT 1 + +#define ADS1018_CFG_DRATE_MASK GENMASK(7, 5) +#define ADS1018_DRATE_DEFAULT 4 + +#define ADS1018_NUM_PGA_MODES 6 +#define ADS1018_CHANNELS_MAX 10 + +struct ads1018_chan_data { + u8 pga_mode; + u8 data_rate_mode; +}; + +struct ads1018_chip_info { + const char *name; + const struct iio_chan_spec *channels; + unsigned long num_channels; + + /* IIO_VAL_INT */ + const u32 *data_rate_mode_to_hz; + unsigned long num_data_rate_mode_to_hz; + + /* + * Let `res` be the chip's resolution and `fsr` (millivolts) be the + * full-scale range corresponding to the PGA mode given by the array + * index. Then, the gain is calculated using the following formula: + * + * gain = |fsr| / 2^(res - 1) + * + * This value then has to be represented in IIO_VAL_INT_PLUS_NANO + * format. For example if: + * + * gain = 6144 / 2^(16 - 1) = 0.1875 + * + * ...then the formatted value is: + * + * { 0, 187500000 } + */ + const u32 pga_mode_to_gain[ADS1018_NUM_PGA_MODES][2]; + + /* IIO_VAL_INT_PLUS_MICRO */ + const u32 temp_scale[2]; +}; + +struct ads1018 { + struct spi_device *spi; + struct iio_trigger *indio_trig; + + struct gpio_desc *drdy_gpiod; + int drdy_irq; + + struct ads1018_chan_data chan_data[ADS1018_CHANNELS_MAX]; + const struct ads1018_chip_info *chip_info; + + struct spi_message msg_read; + struct spi_transfer xfer; + __be16 tx_buf[2] __aligned(IIO_DMA_MINALIGN); + __be16 rx_buf[2]; +}; + +#define ADS1018_VOLT_DIFF_CHAN(_index, _chan, _chan2, _realbits) { \ + .type = IIO_VOLTAGE, \ + .channel = _chan, \ + .channel2 = _chan2, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = true, \ + .differential = true, \ +} + +#define ADS1018_VOLT_CHAN(_index, _chan, _realbits) { \ + .type = IIO_VOLTAGE, \ + .channel = _chan, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = true, \ +} + +#define ADS1018_TEMP_CHAN(_index, _realbits) { \ + .type = IIO_TEMP, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = _realbits, \ + .storagebits = 16, \ + .shift = 16 - _realbits, \ + .endianness = IIO_BE, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec ads1118_iio_channels[] = { + ADS1018_VOLT_DIFF_CHAN(0, 0, 1, 16), + ADS1018_VOLT_DIFF_CHAN(1, 0, 3, 16), + ADS1018_VOLT_DIFF_CHAN(2, 1, 3, 16), + ADS1018_VOLT_DIFF_CHAN(3, 2, 3, 16), + ADS1018_VOLT_CHAN(4, 0, 16), + ADS1018_VOLT_CHAN(5, 1, 16), + ADS1018_VOLT_CHAN(6, 2, 16), + ADS1018_VOLT_CHAN(7, 3, 16), + ADS1018_TEMP_CHAN(8, 14), + IIO_CHAN_SOFT_TIMESTAMP(9), +}; + +static const struct iio_chan_spec ads1018_iio_channels[] = { + ADS1018_VOLT_DIFF_CHAN(0, 0, 1, 12), + ADS1018_VOLT_DIFF_CHAN(1, 0, 3, 12), + ADS1018_VOLT_DIFF_CHAN(2, 1, 3, 12), + ADS1018_VOLT_DIFF_CHAN(3, 2, 3, 12), + ADS1018_VOLT_CHAN(4, 0, 12), + ADS1018_VOLT_CHAN(5, 1, 12), + ADS1018_VOLT_CHAN(6, 2, 12), + ADS1018_VOLT_CHAN(7, 3, 12), + ADS1018_TEMP_CHAN(8, 12), + IIO_CHAN_SOFT_TIMESTAMP(9), +}; + +/** + * ads1018_calc_delay - Calculates a suitable delay for a single-shot reading + * @hz: Sampling frequency + * + * Calculates an appropriate delay for a single shot reading given a sampling + * frequency. + * + * Return: Delay in microseconds (Always greater than 0). + */ +static u32 ads1018_calc_delay(unsigned int hz) +{ + /* + * Calculate the worst-case sampling rate by subtracting 10% error + * specified in the datasheet... + */ + hz -= DIV_ROUND_UP(hz, 10); + + /* ...Then calculate time per sample in microseconds. */ + return DIV_ROUND_UP(HZ_PER_MHZ, hz); +} + +/** + * ads1018_spi_read_exclusive - Reads a conversion value from the device + * @ads1018: Device data + * @cnv: ADC Conversion value (optional) + * @hold_cs: Keep CS line asserted after the SPI transfer + * + * Reads the most recent ADC conversion value, without updating the + * device's configuration. + * + * Context: Expects SPI bus *exclusive* use. + * + * Return: 0 on success, negative errno on error. + */ +static int ads1018_spi_read_exclusive(struct ads1018 *ads1018, __be16 *cnv, + bool hold_cs) +{ + int ret; + + ads1018->xfer.cs_change = hold_cs; + + ret = spi_sync_locked(ads1018->spi, &ads1018->msg_read); + if (ret) + return ret; + + if (cnv) + *cnv = ads1018->rx_buf[0]; + + return 0; +} + +/** + * ads1018_single_shot - Performs a one-shot reading sequence + * @ads1018: Device data + * @chan: Channel specification + * @cnv: Conversion value + * + * Writes a new configuration, waits an appropriate delay, then reads the most + * recent conversion. + * + * Context: Expects iio_device_claim_direct() is held. + * + * Return: 0 on success, negative errno on error. + */ +static int ads1018_single_shot(struct ads1018 *ads1018, + struct iio_chan_spec const *chan, u16 *cnv) +{ + u8 max_drate_mode = ads1018->chip_info->num_data_rate_mode_to_hz - 1; + u8 drate = ads1018->chip_info->data_rate_mode_to_hz[max_drate_mode]; + u8 pga_mode = ads1018->chan_data[chan->scan_index].pga_mode; + struct spi_transfer xfer[2] = { + { + .tx_buf = ads1018->tx_buf, + .len = sizeof(ads1018->tx_buf[0]), + .delay = { + .value = ads1018_calc_delay(drate), + .unit = SPI_DELAY_UNIT_USECS, + }, + .cs_change = 1, /* 16-bit mode requires CS de-assert */ + }, + { + .rx_buf = ads1018->rx_buf, + .len = sizeof(ads1018->rx_buf[0]), + }, + }; + u16 cfg; + int ret; + + cfg = ADS1018_CFG_VALID | ADS1018_CFG_OS_TRIG; + cfg |= FIELD_PREP(ADS1018_CFG_MUX_MASK, chan->scan_index); + cfg |= FIELD_PREP(ADS1018_CFG_PGA_MASK, pga_mode); + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_ONESHOT); + cfg |= FIELD_PREP(ADS1018_CFG_DRATE_MASK, max_drate_mode); + + if (chan->type == IIO_TEMP) + cfg |= ADS1018_CFG_TS_MODE_EN; + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ret = spi_sync_transfer(ads1018->spi, xfer, ARRAY_SIZE(xfer)); + if (ret) + return ret; + + *cnv = be16_to_cpu(ads1018->rx_buf[0]); + + return 0; +} + +static int +ads1018_read_raw_direct_mode(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *chip_info = ads1018->chip_info; + u8 addr = chan->scan_index; + u8 pga_mode, drate_mode; + u16 cnv; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads1018_single_shot(ads1018, chan, &cnv); + if (ret) + return ret; + + cnv >>= chan->scan_type.shift; + *val = sign_extend32(cnv, chan->scan_type.realbits - 1); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + pga_mode = ads1018->chan_data[addr].pga_mode; + *val = chip_info->pga_mode_to_gain[pga_mode][0]; + *val2 = chip_info->pga_mode_to_gain[pga_mode][1]; + return IIO_VAL_INT_PLUS_NANO; + + case IIO_TEMP: + *val = chip_info->temp_scale[0]; + *val2 = chip_info->temp_scale[1]; + return IIO_VAL_INT_PLUS_MICRO; + + default: + return -EOPNOTSUPP; + } + + case IIO_CHAN_INFO_SAMP_FREQ: + drate_mode = ads1018->chan_data[addr].data_rate_mode; + *val = chip_info->data_rate_mode_to_hz[drate_mode]; + return IIO_VAL_INT; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1018_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ads1018_read_raw_direct_mode(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int +ads1018_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *type = IIO_VAL_INT_PLUS_NANO; + *vals = (const int *)ads1018->chip_info->pga_mode_to_gain; + *length = ADS1018_NUM_PGA_MODES * 2; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT; + *vals = ads1018->chip_info->data_rate_mode_to_hz; + *length = ads1018->chip_info->num_data_rate_mode_to_hz; + return IIO_AVAIL_LIST; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1018_write_raw_direct_mode(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *info = ads1018->chip_info; + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ADS1018_NUM_PGA_MODES; i++) { + if (val == info->pga_mode_to_gain[i][0] && + val2 == info->pga_mode_to_gain[i][1]) + break; + } + if (i == ADS1018_NUM_PGA_MODES) + return -EINVAL; + + ads1018->chan_data[chan->scan_index].pga_mode = i; + return 0; + + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < info->num_data_rate_mode_to_hz; i++) { + if (val == info->data_rate_mode_to_hz[i]) + break; + } + if (i == info->num_data_rate_mode_to_hz) + return -EINVAL; + + ads1018->chan_data[chan->scan_index].data_rate_mode = i; + return 0; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1018_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = ads1018_write_raw_direct_mode(indio_dev, chan, val, val2, mask); + iio_device_release_direct(indio_dev); + + return ret; +} + +static int +ads1018_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static const struct iio_info ads1018_iio_info = { + .read_raw = ads1018_read_raw, + .read_avail = ads1018_read_avail, + .write_raw = ads1018_write_raw, + .write_raw_get_fmt = ads1018_write_raw_get_fmt, +}; + +static void ads1018_set_trigger_enable(struct ads1018 *ads1018) +{ + spi_bus_lock(ads1018->spi->controller); + ads1018_spi_read_exclusive(ads1018, NULL, true); + enable_irq(ads1018->drdy_irq); +} + +static void ads1018_set_trigger_disable(struct ads1018 *ads1018) +{ + disable_irq(ads1018->drdy_irq); + ads1018_spi_read_exclusive(ads1018, NULL, false); + spi_bus_unlock(ads1018->spi->controller); +} + +static int ads1018_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct ads1018 *ads1018 = iio_trigger_get_drvdata(trig); + + /* + * We need to lock the SPI bus and tie CS low (hold_cs) to catch + * data-ready interrupts, otherwise the MISO line enters a Hi-Z state. + */ + + if (state) + ads1018_set_trigger_enable(ads1018); + else + ads1018_set_trigger_disable(ads1018); + + return 0; +} + +static const struct iio_trigger_ops ads1018_trigger_ops = { + .set_trigger_state = ads1018_set_trigger_state, + .validate_device = iio_trigger_validate_own_device, +}; + +static int ads1018_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + const struct ads1018_chip_info *chip_info = ads1018->chip_info; + unsigned int pga, drate, addr; + u16 cfg; + + addr = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + pga = ads1018->chan_data[addr].pga_mode; + drate = ads1018->chan_data[addr].data_rate_mode; + + cfg = ADS1018_CFG_VALID; + cfg |= FIELD_PREP(ADS1018_CFG_MUX_MASK, addr); + cfg |= FIELD_PREP(ADS1018_CFG_PGA_MASK, pga); + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_CONTINUOUS); + cfg |= FIELD_PREP(ADS1018_CFG_DRATE_MASK, drate); + + if (chip_info->channels[addr].type == IIO_TEMP) + cfg |= ADS1018_CFG_TS_MODE_EN; + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ads1018->tx_buf[1] = 0; + + return spi_write(ads1018->spi, ads1018->tx_buf, sizeof(ads1018->tx_buf)); +} + +static int ads1018_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + u16 cfg; + + cfg = ADS1018_CFG_VALID; + cfg |= FIELD_PREP(ADS1018_CFG_MODE_MASK, ADS1018_MODE_ONESHOT); + + ads1018->tx_buf[0] = cpu_to_be16(cfg); + ads1018->tx_buf[1] = 0; + + return spi_write(ads1018->spi, ads1018->tx_buf, sizeof(ads1018->tx_buf)); +} + +static const struct iio_buffer_setup_ops ads1018_buffer_ops = { + .preenable = ads1018_buffer_preenable, + .postdisable = ads1018_buffer_postdisable, + .validate_scan_mask = iio_validate_scan_mask_onehot, +}; + +static irqreturn_t ads1018_irq_handler(int irq, void *dev_id) +{ + struct ads1018 *ads1018 = dev_id; + + /* + * We need to check if the "drdy" pin is actually active or if it's a + * pending interrupt triggered by the SPI transfer. + */ + if (!gpiod_get_value(ads1018->drdy_gpiod)) + return IRQ_HANDLED; + + iio_trigger_poll(ads1018->indio_trig); + + return IRQ_HANDLED; +} + +static irqreturn_t ads1018_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads1018 *ads1018 = iio_priv(indio_dev); + struct { + __be16 conv; + aligned_s64 ts; + } scan = {}; + int ret; + + if (iio_trigger_using_own(indio_dev)) { + disable_irq(ads1018->drdy_irq); + ret = ads1018_spi_read_exclusive(ads1018, &scan.conv, true); + enable_irq(ads1018->drdy_irq); + } else { + ret = spi_read(ads1018->spi, ads1018->rx_buf, sizeof(ads1018->rx_buf)); + scan.conv = ads1018->rx_buf[0]; + } + + if (!ret) + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ads1018_trigger_setup(struct iio_dev *indio_dev) +{ + struct ads1018 *ads1018 = iio_priv(indio_dev); + struct spi_device *spi = ads1018->spi; + struct device *dev = &spi->dev; + const char *con_id = "drdy"; + int ret; + + ads1018->drdy_gpiod = devm_gpiod_get_optional(dev, con_id, GPIOD_IN); + if (IS_ERR(ads1018->drdy_gpiod)) + return dev_err_probe(dev, PTR_ERR(ads1018->drdy_gpiod), + "Failed to get %s GPIO.\n", con_id); + + /* First try to get IRQ from SPI core, then from GPIO */ + if (spi->irq > 0) + ads1018->drdy_irq = spi->irq; + else if (ads1018->drdy_gpiod) + ads1018->drdy_irq = gpiod_to_irq(ads1018->drdy_gpiod); + if (ads1018->drdy_irq < 0) + return dev_err_probe(dev, ads1018->drdy_irq, + "Failed to get IRQ from %s GPIO.\n", con_id); + + /* An IRQ line is only an optional requirement for the IIO trigger */ + if (ads1018->drdy_irq == 0) + return 0; + + ads1018->indio_trig = devm_iio_trigger_alloc(dev, "%s-dev%d-%s", + indio_dev->name, + iio_device_id(indio_dev), + con_id); + if (!ads1018->indio_trig) + return -ENOMEM; + + iio_trigger_set_drvdata(ads1018->indio_trig, ads1018); + ads1018->indio_trig->ops = &ads1018_trigger_ops; + + ret = devm_iio_trigger_register(dev, ads1018->indio_trig); + if (ret) + return ret; + + /* + * The "data-ready" IRQ line is shared with the MOSI pin, thus we need + * to keep it disabled until we actually request data. + */ + return devm_request_irq(dev, ads1018->drdy_irq, ads1018_irq_handler, + IRQF_NO_AUTOEN, ads1018->chip_info->name, ads1018); +} + +static int ads1018_spi_probe(struct spi_device *spi) +{ + const struct ads1018_chip_info *info = spi_get_device_match_data(spi); + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ads1018 *ads1018; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*ads1018)); + if (!indio_dev) + return -ENOMEM; + + ads1018 = iio_priv(indio_dev); + ads1018->spi = spi; + ads1018->chip_info = info; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = info->name; + indio_dev->info = &ads1018_iio_info; + indio_dev->channels = info->channels; + indio_dev->num_channels = info->num_channels; + + for (unsigned int i = 0; i < ADS1018_CHANNELS_MAX; i++) { + ads1018->chan_data[i].data_rate_mode = ADS1018_DRATE_DEFAULT; + ads1018->chan_data[i].pga_mode = ADS1018_PGA_DEFAULT; + } + + ads1018->xfer.rx_buf = ads1018->rx_buf; + ads1018->xfer.len = sizeof(ads1018->rx_buf); + spi_message_init_with_transfers(&ads1018->msg_read, &ads1018->xfer, 1); + + ret = ads1018_trigger_setup(indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ads1018_trigger_handler, + &ads1018_buffer_ops); + if (ret) + return ret; + + return devm_iio_device_register(&spi->dev, indio_dev); +} + +static const unsigned int ads1018_data_rate_table[] = { + 128, 250, 490, 920, 1600, 2400, 3300, +}; + +static const unsigned int ads1118_data_rate_table[] = { + 8, 16, 32, 64, 128, 250, 475, 860, +}; + +static const struct ads1018_chip_info ads1018_chip_info = { + .name = "ads1018", + .channels = ads1018_iio_channels, + .num_channels = ARRAY_SIZE(ads1018_iio_channels), + .data_rate_mode_to_hz = ads1018_data_rate_table, + .num_data_rate_mode_to_hz = ARRAY_SIZE(ads1018_data_rate_table), + .pga_mode_to_gain = { + { 3, 0 }, /* fsr = 6144 mV */ + { 2, 0 }, /* fsr = 4096 mV */ + { 1, 0 }, /* fsr = 2048 mV */ + { 0, 500000000 }, /* fsr = 1024 mV */ + { 0, 250000000 }, /* fsr = 512 mV */ + { 0, 125000000 }, /* fsr = 256 mV */ + }, + .temp_scale = { 125, 0 }, +}; + +static const struct ads1018_chip_info ads1118_chip_info = { + .name = "ads1118", + .channels = ads1118_iio_channels, + .num_channels = ARRAY_SIZE(ads1118_iio_channels), + .data_rate_mode_to_hz = ads1118_data_rate_table, + .num_data_rate_mode_to_hz = ARRAY_SIZE(ads1118_data_rate_table), + .pga_mode_to_gain = { + { 0, 187500000 }, /* fsr = 6144 mV */ + { 0, 125000000 }, /* fsr = 4096 mV */ + { 0, 62500000 }, /* fsr = 2048 mV */ + { 0, 31250000 }, /* fsr = 1024 mV */ + { 0, 15625000 }, /* fsr = 512 mV */ + { 0, 7812500 }, /* fsr = 256 mV */ + }, + .temp_scale = { 31, 250000 }, +}; + +static const struct of_device_id ads1018_of_match[] = { + { .compatible = "ti,ads1018", .data = &ads1018_chip_info }, + { .compatible = "ti,ads1118", .data = &ads1118_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ads1018_of_match); + +static const struct spi_device_id ads1018_spi_match[] = { + { "ads1018", (kernel_ulong_t)&ads1018_chip_info }, + { "ads1118", (kernel_ulong_t)&ads1118_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads1018_spi_match); + +static struct spi_driver ads1018_spi_driver = { + .driver = { + .name = "ads1018", + .of_match_table = ads1018_of_match, + }, + .probe = ads1018_spi_probe, + .id_table = ads1018_spi_match, +}; +module_spi_driver(ads1018_spi_driver); + +MODULE_DESCRIPTION("Texas Instruments ADS1018 ADC Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>"); diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c index c9a20024d6b1..a585621b0bc3 100644 --- a/drivers/iio/adc/ti-ads131e08.c +++ b/drivers/iio/adc/ti-ads131e08.c @@ -827,7 +827,7 @@ static int ads131e08_probe(struct spi_device *spi) if (spi->irq) { ret = devm_request_irq(&spi->dev, spi->irq, ads131e08_interrupt, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, spi->dev.driver->name, indio_dev); if (ret) return dev_err_probe(&spi->dev, ret, diff --git a/drivers/iio/adc/ti-ads131m02.c b/drivers/iio/adc/ti-ads131m02.c new file mode 100644 index 000000000000..07d63bf62c5f --- /dev/null +++ b/drivers/iio/adc/ti-ads131m02.c @@ -0,0 +1,968 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Texas Instruments ADS131M02 family ADC chips. + * + * Copyright (C) 2024 Protonic Holland + * Copyright (C) 2025 Oleksij Rempel <kernel@pengutronix.de>, Pengutronix + * + * Primary Datasheet Reference (used for citations): + * ADS131M08 8-Channel, Simultaneously-Sampling, 24-Bit, Delta-Sigma ADC + * Document SBAS950B, Revised February 2021 + * https://www.ti.com/lit/ds/symlink/ads131m08.pdf + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/clk.h> +#include <linux/crc-itu-t.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/device/devres.h> +#include <linux/err.h> +#include <linux/iio/iio.h> +#include <linux/lockdep.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/spi/spi.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/unaligned.h> + +/* Max channels supported by the largest variant in the family (ADS131M08) */ +#define ADS131M_MAX_CHANNELS 8 + +/* Section 6.7, t_REGACQ (min time after reset) is 5us */ +#define ADS131M_RESET_DELAY_US 5 + +#define ADS131M_WORD_SIZE_BYTES 3 +#define ADS131M_RESPONSE_WORDS 1 +#define ADS131M_CRC_WORDS 1 + +/* + * SPI Frame word count calculation. + * Frame = N channel words + 1 response word + 1 CRC word. + * Word size depends on WLENGTH bits in MODE register (Default 24-bit). + */ +#define ADS131M_FRAME_WORDS(nch) \ + ((nch) + ADS131M_RESPONSE_WORDS + ADS131M_CRC_WORDS) + +/* + * SPI Frame byte size calculation. + * Assumes default word size of 24 bits (3 bytes). + */ +#define ADS131M_FRAME_BYTES(nch) \ + (ADS131M_FRAME_WORDS(nch) * ADS131M_WORD_SIZE_BYTES) + +/* + * Index calculation for the start byte of channel 'x' data within the RX buffer. + * Assumes 24-bit words (3 bytes per word). + * The received frame starts with the response word (e.g., STATUS register + * content when NULL command was sent), followed by data for channels 0 to N-1, + * and finally the output CRC word. + * Response = index 0..2, Chan0 = index 3..5, Chan1 = index 6..8, ... + * Index for ChanX = 3 (response) + x * 3 (channel data size). + */ +#define ADS131M_CHANNEL_INDEX(x) \ + ((x) * ADS131M_WORD_SIZE_BYTES + ADS131M_WORD_SIZE_BYTES) + +#define ADS131M_CMD_NULL 0x0000 +#define ADS131M_CMD_RESET 0x0011 + +#define ADS131M_CMD_ADDR_MASK GENMASK(11, 7) +#define ADS131M_CMD_NUM_MASK GENMASK(6, 0) + +#define ADS131M_CMD_RREG_OP 0xa000 +#define ADS131M_CMD_WREG_OP 0x6000 + +#define ADS131M_CMD_RREG(a, n) \ + (ADS131M_CMD_RREG_OP | \ + FIELD_PREP(ADS131M_CMD_ADDR_MASK, a) | \ + FIELD_PREP(ADS131M_CMD_NUM_MASK, n)) +#define ADS131M_CMD_WREG(a, n) \ + (ADS131M_CMD_WREG_OP | \ + FIELD_PREP(ADS131M_CMD_ADDR_MASK, a) | \ + FIELD_PREP(ADS131M_CMD_NUM_MASK, n)) + +/* STATUS Register (0x01h) bit definitions */ +#define ADS131M_STATUS_CRC_ERR BIT(12) /* Input CRC error */ + +#define ADS131M_REG_MODE 0x02 +#define ADS131M_MODE_RX_CRC_EN BIT(12) /* Enable Input CRC */ +#define ADS131M_MODE_CRC_TYPE_ANSI BIT(11) /* 0 = CCITT, 1 = ANSI */ +#define ADS131M_MODE_RESET_FLAG BIT(10) + +#define ADS131M_REG_CLOCK 0x03 +#define ADS131M_CLOCK_XTAL_DIS BIT(7) +#define ADS131M_CLOCK_EXTREF_EN BIT(6) + +/* 1.2V internal reference, in millivolts, for IIO_VAL_FRACTIONAL_LOG2 */ +#define ADS131M_VREF_INTERNAL_mV 1200 +/* 24-bit resolution */ +#define ADS131M_RESOLUTION_BITS 24 +/* Signed data uses (RESOLUTION_BITS - 1) magnitude bits */ +#define ADS131M_CODE_BITS (ADS131M_RESOLUTION_BITS - 1) + +/* External ref FSR = Vref * 0.96 */ +#define ADS131M_EXTREF_SCALE_NUM 96 +#define ADS131M_EXTREF_SCALE_DEN 100 + +struct ads131m_configuration { + const struct iio_chan_spec *channels; + const char *name; + u16 reset_ack; + u8 num_channels; + u8 supports_extref:1; + u8 supports_xtal:1; +}; + +struct ads131m_priv { + struct iio_dev *indio_dev; + struct spi_device *spi; + const struct ads131m_configuration *config; + + bool use_external_ref; + int scale_val; + int scale_val2; + + struct spi_transfer xfer; + struct spi_message msg; + + /* + * Protects the shared tx_buffer and rx_buffer. More importantly, + * this serializes all SPI communication to ensure the atomicity + * of multi-cycle command sequences (like WREG, RREG, or RESET). + */ + struct mutex lock; + + /* DMA-safe buffers should be placed at the end of the struct. */ + u8 tx_buffer[ADS131M_FRAME_BYTES(ADS131M_MAX_CHANNELS)] + __aligned(IIO_DMA_MINALIGN); + u8 rx_buffer[ADS131M_FRAME_BYTES(ADS131M_MAX_CHANNELS)]; +}; + +/** + * ads131m_tx_frame_unlocked - Sends a command frame with Input CRC + * @priv: Device private data structure. + * @command: The 16-bit command to send (e.g., NULL, RREG, RESET). + * + * This function sends a command in Word 0, and its calculated 16-bit + * CRC in Word 1, as required when Input CRC is enabled. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_tx_frame_unlocked(struct ads131m_priv *priv, u32 command) +{ + struct iio_dev *indio_dev = priv->indio_dev; + u16 crc; + + lockdep_assert_held(&priv->lock); + + memset(priv->tx_buffer, 0, ADS131M_FRAME_BYTES(indio_dev->num_channels)); + + /* Word 0: 16-bit command, MSB-aligned in 24-bit word */ + put_unaligned_be16(command, &priv->tx_buffer[0]); + + /* Word 1: Input CRC. Calculated over the 3 bytes of Word 0. */ + crc = crc_itu_t(0xffff, priv->tx_buffer, 3); + put_unaligned_be16(crc, &priv->tx_buffer[3]); + + return spi_sync(priv->spi, &priv->msg); +} + +/** + * ads131m_rx_frame_unlocked - Receives a full SPI data frame. + * @priv: Device private data structure. + * + * This function sends a NULL command (with its CRC) to clock out a + * full SPI frame from the device (e.g., response + channel data + CRC). + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rx_frame_unlocked(struct ads131m_priv *priv) +{ + return ads131m_tx_frame_unlocked(priv, ADS131M_CMD_NULL); +} + +/** + * ads131m_check_status_crc_err - Checks for an Input CRC error. + * @priv: Device private data structure. + * + * Sends a NULL command to fetch the STATUS register and checks the + * CRC_ERR bit. This is used to verify the integrity of the previous + * command (like RREG or WREG). + * + * Return: 0 on success, -EIO if CRC_ERR bit is set. + */ +static int ads131m_check_status_crc_err(struct ads131m_priv *priv) +{ + struct device *dev = &priv->spi->dev; + u16 status; + int ret; + + lockdep_assert_held(&priv->lock); + + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, + "SPI error on STATUS read for CRC check\n"); + return ret; + } + + status = get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(dev, + "Input CRC error reported in STATUS = 0x%04x\n", + status); + return -EIO; + } + + return 0; +} + +/** + * ads131m_write_reg_unlocked - Writes a single register and verifies the ACK. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @val: The 16-bit value to write. + * + * This function performs the full 3-cycle WREG operation with Input CRC: + * 1. (Cycle 1) Sends WREG command, data, and its calculated CRC. + * 2. (Cycle 2) Sends NULL+CRC to retrieve the response from Cycle 1. + * 3. Verifies the response is the correct ACK for the WREG. + * 4. (Cycle 3) Sends NULL+CRC to retrieve STATUS and check for CRC_ERR. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_write_reg_unlocked(struct ads131m_priv *priv, u8 reg, u16 val) +{ + struct iio_dev *indio_dev = priv->indio_dev; + u16 command, expected_ack, response, crc; + struct device *dev = &priv->spi->dev; + int ret_crc_err = 0; + int ret; + + lockdep_assert_held(&priv->lock); + + command = ADS131M_CMD_WREG(reg, 0); /* n = 0 for 1 register */ + /* + * Per Table 8-11, WREG response is: 010a aaaa ammm mmmm + * For 1 reg (n = 0 -> m = 0): 010a aaaa a000 0000 = 0x4000 | (reg << 7) + */ + expected_ack = 0x4000 | (reg << 7); + + /* Cycle 1: Send WREG Command + Data + Input CRC */ + + memset(priv->tx_buffer, 0, ADS131M_FRAME_BYTES(indio_dev->num_channels)); + + /* Word 0: WREG command, 1 reg (n = 0), MSB-aligned */ + put_unaligned_be16(command, &priv->tx_buffer[0]); + + /* Word 1: Data, MSB-aligned */ + put_unaligned_be16(val, &priv->tx_buffer[3]); + + /* Word 2: Input CRC. Calculated over Word 0 (Cmd) and Word 1 (Data). */ + crc = crc_itu_t(0xffff, priv->tx_buffer, 6); + put_unaligned_be16(crc, &priv->tx_buffer[6]); + + /* Ignore the RX buffer (it's from the previous command) */ + ret = spi_sync(priv->spi, &priv->msg); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on WREG (cycle 1)\n"); + return ret; + } + + /* Cycle 2: Send NULL Command to get the WREG response */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on WREG ACK (cycle 2)\n"); + return ret; + } + + /* + * Response is in the first 2 bytes of the RX buffer + * (MSB-aligned 16-bit response) + */ + response = get_unaligned_be16(&priv->rx_buffer[0]); + if (response != expected_ack) { + dev_err_ratelimited(dev, "WREG(0x%02x) failed, expected ACK 0x%04x, got 0x%04x\n", + reg, expected_ack, response); + ret_crc_err = -EIO; + /* + * Don't return yet, still need to do Cycle 3 to clear + * any potential CRC_ERR flag from this failed command. + */ + } + + /* + * Cycle 3: Check STATUS for Input CRC error. + * This is necessary even if ACK was wrong, to clear the CRC_ERR flag. + */ + ret = ads131m_check_status_crc_err(priv); + if (ret < 0) + return ret; + + return ret_crc_err; +} + +/** + * ads131m_read_reg_unlocked - Reads a single register from the device. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @val: Pointer to store the 16-bit register value. + * + * This function performs the full 3-cycle RREG operation with Input CRC: + * 1. (Cycle 1) Sends the RREG command + Input CRC. + * 2. (Cycle 2) Sends NULL+CRC to retrieve the register data. + * 3. (Cycle 3) Sends NULL+CRC to retrieve STATUS and check for CRC_ERR. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_read_reg_unlocked(struct ads131m_priv *priv, u8 reg, u16 *val) +{ + struct device *dev = &priv->spi->dev; + u16 command; + int ret; + + lockdep_assert_held(&priv->lock); + + command = ADS131M_CMD_RREG(reg, 0); /* n=0 for 1 register */ + + /* + * Cycle 1: Send RREG Command + Input CRC + * Ignore the RX buffer (it's from the previous command) + */ + ret = ads131m_tx_frame_unlocked(priv, command); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on RREG (cycle 1)\n"); + return ret; + } + + /* Cycle 2: Send NULL Command to get the register data */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) { + dev_err_ratelimited(dev, "SPI error on RREG data (cycle 2)\n"); + return ret; + } + + /* + * Per datasheet, for a single reg read, the response is the data. + * It's in the first 2 bytes of the RX buffer (MSB-aligned 16-bit). + */ + *val = get_unaligned_be16(&priv->rx_buffer[0]); + + /* + * Cycle 3: Check STATUS for Input CRC error. + * The RREG command does not execute if CRC is bad, but we read + * STATUS anyway to clear the flag in case it was set. + */ + return ads131m_check_status_crc_err(priv); +} + +/** + * ads131m_rmw_reg - Reads, modifies, and writes a single register. + * @priv: Device private data structure. + * @reg: The 8-bit register address. + * @clear: Bitmask of bits to clear. + * @set: Bitmask of bits to set. + * + * This function performs an atomic read-modify-write operation on a register. + * It reads the register, applies the clear and set masks, and writes + * the new value back if it has changed. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_rmw_reg(struct ads131m_priv *priv, u8 reg, u16 clear, u16 set) +{ + u16 old_val, new_val; + int ret; + + guard(mutex)(&priv->lock); + + ret = ads131m_read_reg_unlocked(priv, reg, &old_val); + if (ret < 0) + return ret; + + new_val = (old_val & ~clear) | set; + if (new_val == old_val) + return 0; + + return ads131m_write_reg_unlocked(priv, reg, new_val); +} + +/** + * ads131m_verify_output_crc - Verifies the CRC of the received SPI frame. + * @priv: Device private data structure. + * + * This function calculates the CRC-16-CCITT (Poly 0x1021, Seed 0xFFFF) over + * the received response and channel data, and compares it to the CRC word + * received at the end of the SPI frame. + * + * Return: 0 on success, -EIO on CRC mismatch. + */ +static int ads131m_verify_output_crc(struct ads131m_priv *priv) +{ + struct iio_dev *indio_dev = priv->indio_dev; + struct device *dev = &priv->spi->dev; + u16 calculated_crc, received_crc; + size_t data_len; + + lockdep_assert_held(&priv->lock); + + /* + * Frame: [Response][Chan 0]...[Chan N-1][CRC Word] + * Data for CRC: [Response][Chan 0]...[Chan N-1] + * Data length = (N_channels + 1) * 3 bytes (at 24-bit word size) + */ + data_len = ADS131M_FRAME_BYTES(indio_dev->num_channels) - 3; + calculated_crc = crc_itu_t(0xffff, priv->rx_buffer, data_len); + + /* + * The received 16-bit CRC is MSB-aligned in the last 24-bit word. + * We extract it from the first 2 bytes (BE) of that word. + */ + received_crc = get_unaligned_be16(&priv->rx_buffer[data_len]); + if (calculated_crc != received_crc) { + dev_err_ratelimited(dev, "Output CRC error. Got %04x, expected %04x\n", + received_crc, calculated_crc); + return -EIO; + } + + return 0; +} + +/** + * ads131m_adc_read - Reads channel data, checks input and output CRCs. + * @priv: Device private data structure. + * @channel: The channel number to read. + * @val: Pointer to store the raw 24-bit value. + * + * This function sends a NULL command (with Input CRC) to retrieve data. + * It checks the received STATUS word for any Input CRC errors from the + * previous command, and then verifies the Output CRC of the current + * data frame. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_adc_read(struct ads131m_priv *priv, u8 channel, s32 *val) +{ + struct device *dev = &priv->spi->dev; + u16 status; + int ret; + u8 *buf; + + guard(mutex)(&priv->lock); + + /* Send NULL command + Input CRC, and receive data frame */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) + return ret; + + /* + * Check STATUS for Input CRC error from the previous command frame. + * Note: the STATUS word belongs to the frame before this NULL command. + */ + status = get_unaligned_be16(&priv->rx_buffer[0]); + if (status & ADS131M_STATUS_CRC_ERR) { + dev_err_ratelimited(dev, + "Previous input CRC error reported in STATUS (0x%04x)\n", + status); + } + + ret = ads131m_verify_output_crc(priv); + if (ret < 0) + return ret; + + buf = &priv->rx_buffer[ADS131M_CHANNEL_INDEX(channel)]; + *val = sign_extend32(get_unaligned_be24(buf), ADS131M_CODE_BITS); + + return 0; +} + +static int ads131m_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *channel, + int *val, int *val2, long mask) +{ + struct ads131m_priv *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads131m_adc_read(priv, channel->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = priv->scale_val; + *val2 = priv->scale_val2; + + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } +} + +#define ADS131M_VOLTAGE_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .channel = (num), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + } + +static const struct iio_chan_spec ads131m02_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), +}; + +static const struct iio_chan_spec ads131m03_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), +}; + +static const struct iio_chan_spec ads131m04_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), +}; + +static const struct iio_chan_spec ads131m06_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), + ADS131M_VOLTAGE_CHANNEL(4), + ADS131M_VOLTAGE_CHANNEL(5), +}; + +static const struct iio_chan_spec ads131m08_channels[] = { + ADS131M_VOLTAGE_CHANNEL(0), + ADS131M_VOLTAGE_CHANNEL(1), + ADS131M_VOLTAGE_CHANNEL(2), + ADS131M_VOLTAGE_CHANNEL(3), + ADS131M_VOLTAGE_CHANNEL(4), + ADS131M_VOLTAGE_CHANNEL(5), + ADS131M_VOLTAGE_CHANNEL(6), + ADS131M_VOLTAGE_CHANNEL(7), +}; + +static const struct ads131m_configuration ads131m02_config = { + .channels = ads131m02_channels, + .num_channels = ARRAY_SIZE(ads131m02_channels), + .reset_ack = 0xff22, + .name = "ads131m02", +}; + +static const struct ads131m_configuration ads131m03_config = { + .channels = ads131m03_channels, + .num_channels = ARRAY_SIZE(ads131m03_channels), + .reset_ack = 0xff23, + .name = "ads131m03", +}; + +static const struct ads131m_configuration ads131m04_config = { + .channels = ads131m04_channels, + .num_channels = ARRAY_SIZE(ads131m04_channels), + .reset_ack = 0xff24, + .name = "ads131m04", +}; + +static const struct ads131m_configuration ads131m06_config = { + .channels = ads131m06_channels, + .num_channels = ARRAY_SIZE(ads131m06_channels), + .reset_ack = 0xff26, + .supports_extref = true, + .supports_xtal = true, + .name = "ads131m06", +}; + +static const struct ads131m_configuration ads131m08_config = { + .channels = ads131m08_channels, + .num_channels = ARRAY_SIZE(ads131m08_channels), + .reset_ack = 0xff28, + .supports_extref = true, + .supports_xtal = true, + .name = "ads131m08", +}; + +static const struct iio_info ads131m_info = { + .read_raw = ads131m_read_raw, +}; + +/* + * Prepares the reusable SPI message structure for a full-duplex transfer. + * The ADS131M requires sending a command frame while simultaneously + * receiving the response/data frame from the previous command cycle. + * + * This message is optimized for the primary data acquisition workflow: + * sending a single-word command (like NULL) and receiving a full data + * frame (Response + N*Channels + CRC). + * + * This message is sized for a full data frame and is reused for all + * command/data cycles. The driver does not implement variable-length SPI + * messages. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_prepare_message(struct ads131m_priv *priv) +{ + struct iio_dev *indio_dev = priv->indio_dev; + struct device *dev = &priv->spi->dev; + int ret; + + priv->xfer.tx_buf = priv->tx_buffer; + priv->xfer.rx_buf = priv->rx_buffer; + priv->xfer.len = ADS131M_FRAME_BYTES(indio_dev->num_channels); + spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); + + ret = devm_spi_optimize_message(dev, priv->spi, &priv->msg); + if (ret) + return dev_err_probe(dev, ret, "failed to optimize SPI message\n"); + + return 0; +} + +/** + * ads131m_hw_reset - Pulses the optional hardware reset. + * @priv: Device private data structure. + * @rstc: Reset control for the /RESET line. + * + * Pulses the /RESET line to perform a hardware reset and waits the + * required t_REGACQ time for the device to be ready. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_reset(struct ads131m_priv *priv, + struct reset_control *rstc) +{ + struct device *dev = &priv->spi->dev; + int ret; + + /* + * Manually pulse the reset line using the framework. + * The reset-gpio provider does not implement the .reset op, + * so we must use .assert and .deassert. + */ + ret = reset_control_assert(rstc); + if (ret) + return dev_err_probe(dev, ret, "Failed to assert reset\n"); + + /* Datasheet: Hold /RESET low for > 2 f_CLKIN cycles. 1us is ample. */ + fsleep(1); + + ret = reset_control_deassert(rstc); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to deassert reset\n"); + + /* Wait t_REGACQ (5us) for registers to be accessible */ + fsleep(ADS131M_RESET_DELAY_US); + + return 0; +} + +/** + * ads131m_sw_reset - Issues a software RESET and verifies ACK. + * @priv: Device private data structure. + * + * This function sends a RESET command (with Input CRC), waits t_REGACQ, + * reads back the RESET ACK, and then sends a final NULL to check for + * any input CRC errors. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_sw_reset(struct ads131m_priv *priv) +{ + u16 expected_ack = priv->config->reset_ack; + struct device *dev = &priv->spi->dev; + u16 response; + int ret; + + guard(mutex)(&priv->lock); + + ret = ads131m_tx_frame_unlocked(priv, ADS131M_CMD_RESET); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to send RESET command\n"); + + /* Wait t_REGACQ (5us) for device to be ready after reset */ + fsleep(ADS131M_RESET_DELAY_US); + + /* Cycle 2: Send NULL + CRC to retrieve the response to the RESET */ + ret = ads131m_rx_frame_unlocked(priv); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read RESET ACK\n"); + + response = get_unaligned_be16(&priv->rx_buffer[0]); + + /* Check against the device-specific ACK value */ + if (response != expected_ack) + return dev_err_probe(dev, -EIO, + "RESET ACK mismatch, got 0x%04x, expected 0x%04x\n", + response, expected_ack); + + /* Cycle 3: Check STATUS for Input CRC error on the RESET command. */ + return ads131m_check_status_crc_err(priv); +} + +/** + * ads131m_reset - Resets the device using hardware or software. + * @priv: Device private data structure. + * @rstc: Optional reset control, or NULL for software reset. + * + * This function performs a hardware reset if supported (rstc provided), + * otherwise it issues a software RESET command via SPI. + * + * Note: The software reset path also validates the device's reset + * acknowledgment against the expected ID for the compatible string. + * The hardware reset path bypasses this ID check. + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_reset(struct ads131m_priv *priv, struct reset_control *rstc) +{ + if (rstc) + return ads131m_hw_reset(priv, rstc); + + return ads131m_sw_reset(priv); +} + +static int ads131m_power_init(struct ads131m_priv *priv) +{ + static const char * const supply_ids[] = { "avdd", "dvdd" }; + struct device *dev = &priv->spi->dev; + int vref_uV; + int ret; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supply_ids), supply_ids); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to enable regulators\n"); + + /* Default to Internal 1.2V reference: 1200mV / 2^23 */ + priv->scale_val = ADS131M_VREF_INTERNAL_mV; + priv->scale_val2 = BIT(ADS131M_CODE_BITS); + + if (!priv->config->supports_extref) + return 0; + + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "failed to get refin supply\n"); + + if (ret == 0) + return dev_err_probe(dev, -EINVAL, "refin supply reports 0V\n"); + + if (ret == -ENODEV) + return 0; + + vref_uV = ret; + + /* + * External reference found: Scale(mV) = (vref_uV * 0.96) / 1000 + * The denominator is 100 * 2^23 because of the 0.96 factor (96/100). + */ + priv->scale_val = div_s64((s64)vref_uV * ADS131M_EXTREF_SCALE_NUM, 1000); + priv->scale_val2 = ADS131M_EXTREF_SCALE_DEN * BIT(ADS131M_CODE_BITS); + priv->use_external_ref = true; + + return 0; +} + +/** + * ads131m_hw_init - Initialize the ADC hardware. + * @priv: Device private data structure. + * @rstc: Optional reset control, or NULL for software reset. + * @is_xtal: True if 'clock-names' is "xtal", false if "clkin". + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_hw_init(struct ads131m_priv *priv, + struct reset_control *rstc, bool is_xtal) +{ + struct device *dev = &priv->spi->dev; + u16 mode_clear, mode_set; + int ret; + + ret = ads131m_reset(priv, rstc); + if (ret < 0) + return ret; + + /* + * Configure CLOCK register (0x03) based on DT properties. + * This register only needs configuration for 32-pin (M06/M08) + * variants, as the configurable bits (XTAL_DIS, EXTREF_EN) + * are reserved on 20-pin (M02/M03/M04) variants. + */ + if (priv->config->supports_xtal || priv->config->supports_extref) { + u16 clk_set = 0; + + if (priv->config->supports_xtal && !is_xtal) + clk_set |= ADS131M_CLOCK_XTAL_DIS; + + if (priv->config->supports_extref && priv->use_external_ref) + clk_set |= ADS131M_CLOCK_EXTREF_EN; + + ret = ads131m_rmw_reg(priv, ADS131M_REG_CLOCK, + ADS131M_CLOCK_EXTREF_EN | ADS131M_CLOCK_XTAL_DIS, + clk_set); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to configure CLOCK register\n"); + } + + /* + * The RESET command sets all registers to default, which means: + * 1. The RESET bit (Bit 10) in MODE is set to '1'. + * 2. The CRC_TYPE bit (Bit 11) in MODE is '0' (CCITT). + * 3. The RX_CRC_EN bit (Bit 12) in MODE is '0' (Disabled). + * + * We must: + * 1. Clear the RESET bit. + * 2. Enable Input CRC (RX_CRC_EN). + * 3. Explicitly clear the ANSI CRC bit (for certainty). + */ + mode_clear = ADS131M_MODE_CRC_TYPE_ANSI | ADS131M_MODE_RESET_FLAG; + mode_set = ADS131M_MODE_RX_CRC_EN; + + ret = ads131m_rmw_reg(priv, ADS131M_REG_MODE, mode_clear, mode_set); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to configure MODE register\n"); + + return 0; +} + +/** + * ads131m_parse_clock - enable clock and detect "xtal" selection + * @priv: Device private data structure. + * @is_xtal: result flag (true if "xtal", false if default "clkin") + * + * Return: 0 on success, or a negative error code. + */ +static int ads131m_parse_clock(struct ads131m_priv *priv, bool *is_xtal) +{ + struct device *dev = &priv->spi->dev; + struct clk *clk; + int ret; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR_OR_NULL(clk)) { + if (IS_ERR(clk)) + ret = PTR_ERR(clk); + else + ret = -ENODEV; + + return dev_err_probe(dev, ret, "clk get enabled failed\n"); + } + + ret = device_property_match_string(dev, "clock-names", "xtal"); + if (ret > 0) + return dev_err_probe(dev, -EINVAL, + "'xtal' must be the only or first clock name"); + + if (ret < 0 && ret != -ENODATA) + return dev_err_probe(dev, ret, + "failed to read 'clock-names' property"); + + if (ret == 0 && !priv->config->supports_xtal) + return dev_err_probe(dev, -EINVAL, + "'xtal' clock not supported on this device"); + + *is_xtal = !ret; + + return 0; +} + +static int ads131m_probe(struct spi_device *spi) +{ + const struct ads131m_configuration *config; + struct device *dev = &spi->dev; + struct reset_control *rstc; + struct iio_dev *indio_dev; + struct ads131m_priv *priv; + bool is_xtal; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->indio_dev = indio_dev; + priv->spi = spi; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ads131m_info; + + config = spi_get_device_match_data(spi); + + priv->config = config; + indio_dev->name = config->name; + indio_dev->channels = config->channels; + indio_dev->num_channels = config->num_channels; + + rstc = devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rstc)) + return dev_err_probe(dev, PTR_ERR(rstc), + "Failed to get reset controller\n"); + + ret = devm_mutex_init(dev, &priv->lock); + if (ret < 0) + return ret; + + ret = ads131m_prepare_message(priv); + if (ret < 0) + return ret; + + ret = ads131m_power_init(priv); + if (ret < 0) + return ret; + + /* Power must be applied and stable before the clock is enabled. */ + ret = ads131m_parse_clock(priv, &is_xtal); + if (ret < 0) + return ret; + + ret = ads131m_hw_init(priv, rstc, is_xtal); + if (ret < 0) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ads131m_of_match[] = { + { .compatible = "ti,ads131m02", .data = &ads131m02_config }, + { .compatible = "ti,ads131m03", .data = &ads131m03_config }, + { .compatible = "ti,ads131m04", .data = &ads131m04_config }, + { .compatible = "ti,ads131m06", .data = &ads131m06_config }, + { .compatible = "ti,ads131m08", .data = &ads131m08_config }, + { } +}; +MODULE_DEVICE_TABLE(of, ads131m_of_match); + +static const struct spi_device_id ads131m_id[] = { + { "ads131m02", (kernel_ulong_t)&ads131m02_config }, + { "ads131m03", (kernel_ulong_t)&ads131m03_config }, + { "ads131m04", (kernel_ulong_t)&ads131m04_config }, + { "ads131m06", (kernel_ulong_t)&ads131m06_config }, + { "ads131m08", (kernel_ulong_t)&ads131m08_config }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads131m_id); + +static struct spi_driver ads131m_driver = { + .driver = { + .name = "ads131m02", + .of_match_table = ads131m_of_match, + }, + .probe = ads131m_probe, + .id_table = ads131m_id, +}; +module_spi_driver(ads131m_driver); + +MODULE_AUTHOR("David Jander <david@protonic.nl>"); +MODULE_DESCRIPTION("Texas Instruments ADS131M02 ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/amplifiers/Kconfig b/drivers/iio/amplifiers/Kconfig index 55eb16b32f6c..a8a604863eed 100644 --- a/drivers/iio/amplifiers/Kconfig +++ b/drivers/iio/amplifiers/Kconfig @@ -36,6 +36,18 @@ config ADA4250 To compile this driver as a module, choose M here: the module will be called ada4250. +config ADL8113 + tristate "Analog Devices ADL8113 Low Noise Amplifier" + depends on GPIOLIB + help + Say yes here to build support for Analog Devices ADL8113 Low Noise + Amplifier with integrated bypass switches. The device supports four + operation modes controlled by GPIO pins: internal amplifier, + internal bypass, and two external bypass modes. + + To compile this driver as a module, choose M here: the + module will be called adl8113. + config HMC425 tristate "Analog Devices HMC425A and similar GPIO Gain Amplifiers" depends on GPIOLIB diff --git a/drivers/iio/amplifiers/Makefile b/drivers/iio/amplifiers/Makefile index 2126331129cf..0a76443be1aa 100644 --- a/drivers/iio/amplifiers/Makefile +++ b/drivers/iio/amplifiers/Makefile @@ -6,4 +6,5 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AD8366) += ad8366.o obj-$(CONFIG_ADA4250) += ada4250.o +obj-$(CONFIG_ADL8113) += adl8113.o obj-$(CONFIG_HMC425) += hmc425a.o diff --git a/drivers/iio/amplifiers/adl8113.c b/drivers/iio/amplifiers/adl8113.c new file mode 100644 index 000000000000..b8a431b6616b --- /dev/null +++ b/drivers/iio/amplifiers/adl8113.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ADL8113 Low Noise Amplifier with integrated bypass switches + * + * Copyright 2025 Analog Devices Inc. + */ + +#include <linux/array_size.h> +#include <linux/bitmap.h> +#include <linux/device/driver.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/iio.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> + +enum adl8113_signal_path { + ADL8113_INTERNAL_AMP, + ADL8113_INTERNAL_BYPASS, + ADL8113_EXTERNAL_A, + ADL8113_EXTERNAL_B, +}; + +struct adl8113_gain_config { + enum adl8113_signal_path path; + int gain_db; +}; + +struct adl8113_state { + struct gpio_descs *gpios; + struct adl8113_gain_config *gain_configs; + unsigned int num_gain_configs; + enum adl8113_signal_path current_path; +}; + +static const char * const adl8113_supply_names[] = { + "vdd1", + "vss2", + "vdd2", +}; + +static int adl8113_set_path(struct adl8113_state *st, + enum adl8113_signal_path path) +{ + DECLARE_BITMAP(values, 2); + int ret; + + /* + * Determine GPIO values based on signal path. + * Va: bit 0, Vb: bit 1. + */ + switch (path) { + case ADL8113_INTERNAL_AMP: + bitmap_write(values, 0x00, 0, 2); + break; + case ADL8113_INTERNAL_BYPASS: + bitmap_write(values, 0x03, 0, 2); + break; + case ADL8113_EXTERNAL_A: + bitmap_write(values, 0x02, 0, 2); + break; + case ADL8113_EXTERNAL_B: + bitmap_write(values, 0x01, 0, 2); + break; + default: + return -EINVAL; + } + + ret = gpiod_set_array_value_cansleep(st->gpios->ndescs, st->gpios->desc, + st->gpios->info, values); + if (ret) + return ret; + + st->current_path = path; + return 0; +} + +static int adl8113_find_gain_config(struct adl8113_state *st, int gain_db) +{ + unsigned int i; + + for (i = 0; i < st->num_gain_configs; i++) { + if (st->gain_configs[i].gain_db == gain_db) + return i; + } + return -EINVAL; +} + +static const struct iio_chan_spec adl8113_channels[] = { + { + .type = IIO_VOLTAGE, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_HARDWAREGAIN), + }, +}; + +static int adl8113_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adl8113_state *st = iio_priv(indio_dev); + unsigned int i; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + /* Find current gain configuration */ + for (i = 0; i < st->num_gain_configs; i++) { + if (st->gain_configs[i].path == st->current_path) { + *val = st->gain_configs[i].gain_db; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO_DB; + } + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static int adl8113_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct adl8113_state *st = iio_priv(indio_dev); + int config_idx; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + if (val2 != 0) + return -EINVAL; + + config_idx = adl8113_find_gain_config(st, val); + if (config_idx < 0) + return config_idx; + + return adl8113_set_path(st, st->gain_configs[config_idx].path); + default: + return -EINVAL; + } +} + +static const struct iio_info adl8113_info = { + .read_raw = adl8113_read_raw, + .write_raw = adl8113_write_raw, +}; + +static int adl8113_init_gain_configs(struct device *dev, struct adl8113_state *st) +{ + int external_a_gain, external_b_gain; + unsigned int i; + + /* + * Allocate for all 4 possible paths: + * - Internal amp and bypass (always present) + * - External bypass A and B (optional if configured) + */ + st->gain_configs = devm_kcalloc(dev, 4, sizeof(*st->gain_configs), + GFP_KERNEL); + if (!st->gain_configs) + return -ENOMEM; + + /* Start filling the gain configurations with data */ + i = 0; + + /* Always include internal amplifier (14dB) */ + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_INTERNAL_AMP, + .gain_db = 14, + }; + + /* Always include internal bypass (-2dB insertion loss) */ + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_INTERNAL_BYPASS, + .gain_db = -2, + }; + + /* Add external bypass A if configured */ + if (!device_property_read_u32(dev, "adi,external-bypass-a-gain-db", + &external_a_gain)) { + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_EXTERNAL_A, + .gain_db = external_a_gain, + }; + } + + /* Add external bypass B if configured */ + if (!device_property_read_u32(dev, "adi,external-bypass-b-gain-db", + &external_b_gain)) { + st->gain_configs[i++] = (struct adl8113_gain_config) { + .path = ADL8113_EXTERNAL_B, + .gain_db = external_b_gain, + }; + } + + st->num_gain_configs = i; + + return 0; +} + +static int adl8113_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct adl8113_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->gpios = devm_gpiod_get_array(dev, "ctrl", GPIOD_OUT_LOW); + if (IS_ERR(st->gpios)) + return dev_err_probe(dev, PTR_ERR(st->gpios), + "failed to get control GPIOs\n"); + + if (st->gpios->ndescs != 2) + return dev_err_probe(dev, -EINVAL, + "expected 2 control GPIOs, got %u\n", + st->gpios->ndescs); + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(adl8113_supply_names), + adl8113_supply_names); + if (ret) + return dev_err_probe(dev, ret, + "failed to get and enable supplies\n"); + + /* Initialize gain configurations from devicetree */ + ret = adl8113_init_gain_configs(dev, st); + if (ret) + return ret; + + /* Initialize to internal amplifier path (14dB) */ + ret = adl8113_set_path(st, ADL8113_INTERNAL_AMP); + if (ret) + return ret; + + indio_dev->info = &adl8113_info; + indio_dev->name = "adl8113"; + indio_dev->channels = adl8113_channels; + indio_dev->num_channels = ARRAY_SIZE(adl8113_channels); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id adl8113_of_match[] = { + { .compatible = "adi,adl8113" }, + { } +}; +MODULE_DEVICE_TABLE(of, adl8113_of_match); + +static struct platform_driver adl8113_driver = { + .driver = { + .name = "adl8113", + .of_match_table = adl8113_of_match, + }, + .probe = adl8113_probe, +}; +module_platform_driver(adl8113_driver); + +MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com>"); +MODULE_DESCRIPTION("Analog Devices ADL8113 Low Noise Amplifier"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c index 7a7a9d37339b..1c94b334f987 100644 --- a/drivers/iio/buffer/industrialio-buffer-dma.c +++ b/drivers/iio/buffer/industrialio-buffer-dma.c @@ -6,6 +6,7 @@ #include <linux/atomic.h> #include <linux/cleanup.h> +#include <linux/lockdep.h> #include <linux/slab.h> #include <linux/kernel.h> #include <linux/module.h> @@ -135,9 +136,8 @@ static void iio_dma_buffer_cleanup_worker(struct work_struct *work) struct iio_dma_buffer_block *block, *_block; LIST_HEAD(block_list); - spin_lock_irq(&iio_dma_buffer_dead_blocks_lock); - list_splice_tail_init(&iio_dma_buffer_dead_blocks, &block_list); - spin_unlock_irq(&iio_dma_buffer_dead_blocks_lock); + scoped_guard(spinlock_irq, &iio_dma_buffer_dead_blocks_lock) + list_splice_tail_init(&iio_dma_buffer_dead_blocks, &block_list); list_for_each_entry_safe(block, _block, &block_list, head) iio_buffer_block_release(&block->kref); @@ -147,13 +147,11 @@ static DECLARE_WORK(iio_dma_buffer_cleanup_work, iio_dma_buffer_cleanup_worker); static void iio_buffer_block_release_atomic(struct kref *kref) { struct iio_dma_buffer_block *block; - unsigned long flags; block = container_of(kref, struct iio_dma_buffer_block, kref); - spin_lock_irqsave(&iio_dma_buffer_dead_blocks_lock, flags); - list_add_tail(&block->head, &iio_dma_buffer_dead_blocks); - spin_unlock_irqrestore(&iio_dma_buffer_dead_blocks_lock, flags); + scoped_guard(spinlock_irqsave, &iio_dma_buffer_dead_blocks_lock) + list_add_tail(&block->head, &iio_dma_buffer_dead_blocks); schedule_work(&iio_dma_buffer_cleanup_work); } @@ -171,22 +169,20 @@ static struct iio_dma_buffer_queue *iio_buffer_to_queue(struct iio_buffer *buf) return container_of(buf, struct iio_dma_buffer_queue, buffer); } -static struct iio_dma_buffer_block *iio_dma_buffer_alloc_block( - struct iio_dma_buffer_queue *queue, size_t size, bool fileio) +static struct iio_dma_buffer_block * +iio_dma_buffer_alloc_block(struct iio_dma_buffer_queue *queue, size_t size, + bool fileio) { - struct iio_dma_buffer_block *block; - - block = kzalloc(sizeof(*block), GFP_KERNEL); + struct iio_dma_buffer_block *block __free(kfree) = + kzalloc(sizeof(*block), GFP_KERNEL); if (!block) return NULL; if (fileio) { block->vaddr = dma_alloc_coherent(queue->dev, PAGE_ALIGN(size), &block->phys_addr, GFP_KERNEL); - if (!block->vaddr) { - kfree(block); + if (!block->vaddr) return NULL; - } } block->fileio = fileio; @@ -201,7 +197,7 @@ static struct iio_dma_buffer_block *iio_dma_buffer_alloc_block( if (!fileio) atomic_inc(&queue->num_dmabufs); - return block; + return_ptr(block); } static void _iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) @@ -232,14 +228,12 @@ static void iio_dma_buffer_queue_wake(struct iio_dma_buffer_queue *queue) void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) { struct iio_dma_buffer_queue *queue = block->queue; - unsigned long flags; bool cookie; cookie = dma_fence_begin_signalling(); - spin_lock_irqsave(&queue->list_lock, flags); - _iio_dma_buffer_block_done(block); - spin_unlock_irqrestore(&queue->list_lock, flags); + scoped_guard(spinlock_irqsave, &queue->list_lock) + _iio_dma_buffer_block_done(block); if (!block->fileio) iio_buffer_signal_dmabuf_done(block->fence, 0); @@ -261,25 +255,25 @@ EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_block_done, "IIO_DMA_BUFFER"); * hand the blocks back to the queue. */ void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue, - struct list_head *list) + struct list_head *list) { struct iio_dma_buffer_block *block, *_block; - unsigned long flags; bool cookie; cookie = dma_fence_begin_signalling(); - spin_lock_irqsave(&queue->list_lock, flags); - list_for_each_entry_safe(block, _block, list, head) { - list_del(&block->head); - block->bytes_used = 0; - _iio_dma_buffer_block_done(block); + scoped_guard(spinlock_irqsave, &queue->list_lock) { + list_for_each_entry_safe(block, _block, list, head) { + list_del(&block->head); + block->bytes_used = 0; + _iio_dma_buffer_block_done(block); - if (!block->fileio) - iio_buffer_signal_dmabuf_done(block->fence, -EINTR); - iio_buffer_block_put_atomic(block); + if (!block->fileio) + iio_buffer_signal_dmabuf_done(block->fence, + -EINTR); + iio_buffer_block_put_atomic(block); + } } - spin_unlock_irqrestore(&queue->list_lock, flags); if (queue->fileio.enabled) queue->fileio.enabled = false; @@ -328,7 +322,6 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) struct iio_dma_buffer_block *block; bool try_reuse = false; size_t size; - int ret = 0; int i; /* @@ -339,13 +332,13 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) size = DIV_ROUND_UP(queue->buffer.bytes_per_datum * queue->buffer.length, 2); - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); queue->fileio.enabled = iio_dma_buffer_can_use_fileio(queue); /* If DMABUFs were created, disable fileio interface */ if (!queue->fileio.enabled) - goto out_unlock; + return 0; /* Allocations are page aligned */ if (PAGE_ALIGN(queue->fileio.block_size) == PAGE_ALIGN(size)) @@ -354,21 +347,21 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) queue->fileio.block_size = size; queue->fileio.active_block = NULL; - spin_lock_irq(&queue->list_lock); - for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { - block = queue->fileio.blocks[i]; + scoped_guard(spinlock_irq, &queue->list_lock) { + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + block = queue->fileio.blocks[i]; - /* If we can't re-use it free it */ - if (block && (!iio_dma_block_reusable(block) || !try_reuse)) - block->state = IIO_BLOCK_STATE_DEAD; - } + /* If we can't re-use it free it */ + if (block && (!iio_dma_block_reusable(block) || !try_reuse)) + block->state = IIO_BLOCK_STATE_DEAD; + } - /* - * At this point all blocks are either owned by the core or marked as - * dead. This means we can reset the lists without having to fear - * corrution. - */ - spin_unlock_irq(&queue->list_lock); + /* + * At this point all blocks are either owned by the core or + * marked as dead. This means we can reset the lists without + * having to fear corruption. + */ + } INIT_LIST_HEAD(&queue->incoming); @@ -388,10 +381,9 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) if (!block) { block = iio_dma_buffer_alloc_block(queue, size, true); - if (!block) { - ret = -ENOMEM; - goto out_unlock; - } + if (!block) + return -ENOMEM; + queue->fileio.blocks[i] = block; } @@ -415,10 +407,7 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer) } } -out_unlock: - mutex_unlock(&queue->lock); - - return ret; + return 0; } EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_request_update, "IIO_DMA_BUFFER"); @@ -426,13 +415,13 @@ static void iio_dma_buffer_fileio_free(struct iio_dma_buffer_queue *queue) { unsigned int i; - spin_lock_irq(&queue->list_lock); - for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { - if (!queue->fileio.blocks[i]) - continue; - queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD; + scoped_guard(spinlock_irq, &queue->list_lock) { + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + if (!queue->fileio.blocks[i]) + continue; + queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD; + } } - spin_unlock_irq(&queue->list_lock); INIT_LIST_HEAD(&queue->incoming); @@ -446,7 +435,7 @@ static void iio_dma_buffer_fileio_free(struct iio_dma_buffer_queue *queue) } static void iio_dma_buffer_submit_block(struct iio_dma_buffer_queue *queue, - struct iio_dma_buffer_block *block) + struct iio_dma_buffer_block *block) { int ret; @@ -490,19 +479,17 @@ static void iio_dma_buffer_submit_block(struct iio_dma_buffer_queue *queue, * * This will allocate the DMA buffers and start the DMA transfers. */ -int iio_dma_buffer_enable(struct iio_buffer *buffer, - struct iio_dev *indio_dev) +int iio_dma_buffer_enable(struct iio_buffer *buffer, struct iio_dev *indio_dev) { struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); struct iio_dma_buffer_block *block, *_block; - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); queue->active = true; list_for_each_entry_safe(block, _block, &queue->incoming, head) { list_del(&block->head); iio_dma_buffer_submit_block(queue, block); } - mutex_unlock(&queue->lock); return 0; } @@ -516,24 +503,22 @@ EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_enable, "IIO_DMA_BUFFER"); * Needs to be called when the device that the buffer is attached to stops * sampling. Typically should be the iio_buffer_access_ops disable callback. */ -int iio_dma_buffer_disable(struct iio_buffer *buffer, - struct iio_dev *indio_dev) +int iio_dma_buffer_disable(struct iio_buffer *buffer, struct iio_dev *indio_dev) { struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); queue->active = false; if (queue->ops && queue->ops->abort) queue->ops->abort(queue); - mutex_unlock(&queue->lock); return 0; } EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_disable, "IIO_DMA_BUFFER"); static void iio_dma_buffer_enqueue(struct iio_dma_buffer_queue *queue, - struct iio_dma_buffer_block *block) + struct iio_dma_buffer_block *block) { if (block->state == IIO_BLOCK_STATE_DEAD) { iio_buffer_block_put(block); @@ -545,25 +530,22 @@ static void iio_dma_buffer_enqueue(struct iio_dma_buffer_queue *queue, } } -static struct iio_dma_buffer_block *iio_dma_buffer_dequeue( - struct iio_dma_buffer_queue *queue) +static struct iio_dma_buffer_block * +iio_dma_buffer_dequeue(struct iio_dma_buffer_queue *queue) { struct iio_dma_buffer_block *block; unsigned int idx; - spin_lock_irq(&queue->list_lock); + guard(spinlock_irq)(&queue->list_lock); idx = queue->fileio.next_dequeue; block = queue->fileio.blocks[idx]; - if (block->state == IIO_BLOCK_STATE_DONE) { - idx = (idx + 1) % ARRAY_SIZE(queue->fileio.blocks); - queue->fileio.next_dequeue = idx; - } else { - block = NULL; - } + if (block->state != IIO_BLOCK_STATE_DONE) + return NULL; - spin_unlock_irq(&queue->list_lock); + idx = (idx + 1) % ARRAY_SIZE(queue->fileio.blocks); + queue->fileio.next_dequeue = idx; return block; } @@ -579,14 +561,13 @@ static int iio_dma_buffer_io(struct iio_buffer *buffer, size_t n, if (n < buffer->bytes_per_datum) return -EINVAL; - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); if (!queue->fileio.active_block) { block = iio_dma_buffer_dequeue(queue); - if (block == NULL) { - ret = 0; - goto out_unlock; - } + if (!block) + return 0; + queue->fileio.pos = 0; queue->fileio.active_block = block; } else { @@ -602,10 +583,8 @@ static int iio_dma_buffer_io(struct iio_buffer *buffer, size_t n, ret = copy_from_user(addr, user_buffer, n); else ret = copy_to_user(user_buffer, addr, n); - if (ret) { - ret = -EFAULT; - goto out_unlock; - } + if (ret) + return -EFAULT; queue->fileio.pos += n; @@ -614,12 +593,7 @@ static int iio_dma_buffer_io(struct iio_buffer *buffer, size_t n, iio_dma_buffer_enqueue(queue, block); } - ret = n; - -out_unlock: - mutex_unlock(&queue->lock); - - return ret; + return n; } /** @@ -677,23 +651,19 @@ size_t iio_dma_buffer_usage(struct iio_buffer *buf) * but won't increase since all blocks are in use. */ - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); if (queue->fileio.active_block) data_available += queue->fileio.active_block->size; - spin_lock_irq(&queue->list_lock); + guard(spinlock_irq)(&queue->list_lock); for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { block = queue->fileio.blocks[i]; - if (block != queue->fileio.active_block - && block->state == IIO_BLOCK_STATE_DONE) + if (block != queue->fileio.active_block && block->state == IIO_BLOCK_STATE_DONE) data_available += block->size; } - spin_unlock_irq(&queue->list_lock); - mutex_unlock(&queue->lock); - return data_available; } EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_usage, "IIO_DMA_BUFFER"); @@ -764,7 +734,7 @@ int iio_dma_buffer_enqueue_dmabuf(struct iio_buffer *buffer, bool cookie; int ret; - WARN_ON(!mutex_is_locked(&queue->lock)); + lockdep_assert_held(&queue->lock); cookie = dma_fence_begin_signalling(); @@ -854,8 +824,8 @@ EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_set_length, "IIO_DMA_BUFFER"); * should refer to the device that will perform the DMA to ensure that * allocations are done from a memory region that can be accessed by the device. */ -int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, - struct device *dev, const struct iio_dma_buffer_ops *ops) +void iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, struct device *dev, + const struct iio_dma_buffer_ops *ops) { iio_buffer_init(&queue->buffer); queue->buffer.length = PAGE_SIZE; @@ -867,8 +837,6 @@ int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, mutex_init(&queue->lock); spin_lock_init(&queue->list_lock); - - return 0; } EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_init, "IIO_DMA_BUFFER"); @@ -881,12 +849,10 @@ EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_init, "IIO_DMA_BUFFER"); */ void iio_dma_buffer_exit(struct iio_dma_buffer_queue *queue) { - mutex_lock(&queue->lock); + guard(mutex)(&queue->lock); iio_dma_buffer_fileio_free(queue); queue->ops = NULL; - - mutex_unlock(&queue->lock); } EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_exit, "IIO_DMA_BUFFER"); diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c index 27dd56334345..0d3454f4adb0 100644 --- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -6,6 +6,7 @@ #include <linux/slab.h> #include <linux/kernel.h> +#include <linux/cleanup.h> #include <linux/dmaengine.h> #include <linux/dma-mapping.h> #include <linux/spinlock.h> @@ -39,27 +40,24 @@ struct dmaengine_buffer { size_t max_size; }; -static struct dmaengine_buffer *iio_buffer_to_dmaengine_buffer( - struct iio_buffer *buffer) +static struct dmaengine_buffer *iio_buffer_to_dmaengine_buffer(struct iio_buffer *buffer) { return container_of(buffer, struct dmaengine_buffer, queue.buffer); } static void iio_dmaengine_buffer_block_done(void *data, - const struct dmaengine_result *result) + const struct dmaengine_result *result) { struct iio_dma_buffer_block *block = data; - unsigned long flags; - spin_lock_irqsave(&block->queue->list_lock, flags); - list_del(&block->head); - spin_unlock_irqrestore(&block->queue->list_lock, flags); + scoped_guard(spinlock_irqsave, &block->queue->list_lock) + list_del(&block->head); block->bytes_used -= result->residue; iio_dma_buffer_block_done(block); } static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, - struct iio_dma_buffer_block *block) + struct iio_dma_buffer_block *block) { struct dmaengine_buffer *dmaengine_buffer = iio_buffer_to_dmaengine_buffer(&queue->buffer); @@ -131,9 +129,8 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, if (dma_submit_error(cookie)) return dma_submit_error(cookie); - spin_lock_irq(&dmaengine_buffer->queue.list_lock); - list_add_tail(&block->head, &dmaengine_buffer->active); - spin_unlock_irq(&dmaengine_buffer->queue.list_lock); + scoped_guard(spinlock_irq, &dmaengine_buffer->queue.list_lock) + list_add_tail(&block->head, &dmaengine_buffer->active); dma_async_issue_pending(dmaengine_buffer->chan); @@ -189,7 +186,7 @@ static const struct iio_dma_buffer_ops iio_dmaengine_default_ops = { }; static ssize_t iio_dmaengine_buffer_get_length_align(struct device *dev, - struct device_attribute *attr, char *buf) + struct device_attribute *attr, char *buf) { struct iio_buffer *buffer = to_iio_dev_attr(attr)->buffer; struct dmaengine_buffer *dmaengine_buffer = @@ -248,7 +245,7 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct dma_chan *chan) dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev); iio_dma_buffer_init(&dmaengine_buffer->queue, chan->device->dev, - &iio_dmaengine_default_ops); + &iio_dmaengine_default_ops); dmaengine_buffer->queue.buffer.attrs = iio_dmaengine_buffer_attrs; dmaengine_buffer->queue.buffer.access = &iio_dmaengine_buffer_ops; diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c index 86bde4a91bf7..bbc96c4c6283 100644 --- a/drivers/iio/chemical/ens160_core.c +++ b/drivers/iio/chemical/ens160_core.c @@ -316,12 +316,9 @@ static int ens160_setup_trigger(struct iio_dev *indio_dev, int irq) indio_dev->trig = iio_trigger_get(trig); - ret = devm_request_threaded_irq(dev, irq, - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_ONESHOT, - indio_dev->name, - indio_dev->trig); + ret = devm_request_irq(dev, irq, iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, indio_dev->name, + indio_dev->trig); if (ret) return dev_err_probe(dev, ret, "failed to request irq\n"); diff --git a/drivers/iio/chemical/scd4x.c b/drivers/iio/chemical/scd4x.c index 0fd839176e26..23a326fb62a7 100644 --- a/drivers/iio/chemical/scd4x.c +++ b/drivers/iio/chemical/scd4x.c @@ -59,6 +59,8 @@ enum scd4x_channel_idx { SCD4X_CO2, SCD4X_TEMP, SCD4X_HR, + /* kernel timestamp, at the end of buffer */ + SCD4X_TS, }; struct scd4x_state { @@ -615,6 +617,7 @@ static const struct iio_chan_spec scd4x_channels[] = { .endianness = IIO_CPU, }, }, + IIO_CHAN_SOFT_TIMESTAMP(SCD4X_TS), }; static int scd4x_suspend(struct device *dev) diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c index 9ac80e4b7d75..5133755c2ea6 100644 --- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c @@ -188,11 +188,8 @@ int cros_ec_sensors_push_data(struct iio_dev *indio_dev, /* * Ignore samples if the buffer is not set: it is needed if the ODR is * set but the buffer is not enabled yet. - * - * Note: iio_device_claim_buffer_mode() returns -EBUSY if the buffer - * is not enabled. */ - if (iio_device_claim_buffer_mode(indio_dev) < 0) + if (!iio_device_try_claim_buffer_mode(indio_dev)) return 0; out = (s16 *)st->samples; @@ -444,14 +441,14 @@ static ssize_t cros_ec_sensors_calibrate(struct iio_dev *indio_dev, ret = kstrtobool(buf, &calibrate); if (ret < 0) return ret; - if (!calibrate) - return -EINVAL; mutex_lock(&st->cmd_lock); st->param.cmd = MOTIONSENSE_CMD_PERFORM_CALIB; + st->param.perform_calib.enable = calibrate; ret = cros_ec_motion_send_host_cmd(st, 0); if (ret != 0) { - dev_warn(&indio_dev->dev, "Unable to calibrate sensor\n"); + dev_warn(&indio_dev->dev, "Unable to calibrate sensor: %d\n", + ret); } else { /* Save values */ for (i = CROS_EC_SENSOR_X; i < CROS_EC_SENSOR_MAX_AXIS; i++) diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 7cd3caec1262..db9f5c711b3d 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -482,6 +482,19 @@ config MAX517 This driver can also be built as a module. If so, the module will be called max517. +config MAX22007 + tristate "Analog Devices MAX22007 DAC Driver" + depends on SPI + select REGMAP_SPI + select CRC8 + help + Say Y here if you want to build a driver for Analog Devices MAX22007. + + MAX22007 is a quad-channel, 12-bit, voltage-output digital to + analog converter (DAC) with SPI interface. + + If compiled as a module, it will be called max22007. + config MAX5522 tristate "Maxim MAX5522 DAC driver" depends on SPI_MASTER @@ -524,6 +537,26 @@ config MCP4728 To compile this driver as a module, choose M here: the module will be called mcp4728. +config MCP47FEB02 + tristate "MCP47F(E/V)B01/02/04/08/11/12/14/18/21/22/24/28 DAC driver" + depends on I2C + help + Say yes here if you want to build the driver for the Microchip: + - 8-bit DAC: + MCP47FEB01, MCP47FEB02, MCP47FEB04, MCP47FEB08, + MCP47FVB01, MCP47FVB02, MCP47FVB04, MCP47FVB08 + - 10-bit DAC: + MCP47FEB11, MCP47FEB12, MCP47FEB14, MCP47FEB18, + MCP47FVB11, MCP47FVB12, MCP47FVB14, MCP47FVB18 + - 12-bit DAC: + MCP47FEB21, MCP47FEB22, MCP47FEB24, MCP47FEB28, + MCP47FVB21, MCP47FVB22, MCP47FVB24, MCP47FVB28 + having 1 to 8 channels, 8/10/12-bit digital-to-analog converter + (DAC) with I2C interface. + + To compile this driver as a module, choose M here: the module + will be called mcp47feb02. + config MCP4821 tristate "MCP4801/02/11/12/21/22 DAC driver" depends on SPI diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index e6ac4c67e337..2a80bbf4e80a 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -48,10 +48,12 @@ obj-$(CONFIG_LTC2664) += ltc2664.o obj-$(CONFIG_LTC2688) += ltc2688.o obj-$(CONFIG_M62332) += m62332.o obj-$(CONFIG_MAX517) += max517.o +obj-$(CONFIG_MAX22007) += max22007.o obj-$(CONFIG_MAX5522) += max5522.o obj-$(CONFIG_MAX5821) += max5821.o obj-$(CONFIG_MCP4725) += mcp4725.o obj-$(CONFIG_MCP4728) += mcp4728.o +obj-$(CONFIG_MCP47FEB02) += mcp47feb02.o obj-$(CONFIG_MCP4821) += mcp4821.o obj-$(CONFIG_MCP4922) += mcp4922.o obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o diff --git a/drivers/iio/dac/adi-axi-dac.c b/drivers/iio/dac/adi-axi-dac.c index 0d525272a8a8..9cc895bbe51a 100644 --- a/drivers/iio/dac/adi-axi-dac.c +++ b/drivers/iio/dac/adi-axi-dac.c @@ -885,34 +885,35 @@ static const struct regmap_config axi_dac_regmap_config = { static int axi_dac_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct axi_dac_state *st; void __iomem *base; unsigned int ver; struct clk *clk; int ret; - st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL); + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); if (!st) return -ENOMEM; - st->info = device_get_match_data(&pdev->dev); + st->info = device_get_match_data(dev); if (!st->info) return -ENODEV; - clk = devm_clk_get_enabled(&pdev->dev, "s_axi_aclk"); + clk = devm_clk_get_enabled(dev, "s_axi_aclk"); if (IS_ERR(clk)) { /* Backward compat., old fdt versions without clock-names. */ - clk = devm_clk_get_enabled(&pdev->dev, NULL); + clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(clk), - "failed to get clock\n"); + return dev_err_probe(dev, PTR_ERR(clk), + "failed to get clock\n"); } if (st->info->has_dac_clk) { struct clk *dac_clk; - dac_clk = devm_clk_get_enabled(&pdev->dev, "dac_clk"); + dac_clk = devm_clk_get_enabled(dev, "dac_clk"); if (IS_ERR(dac_clk)) - return dev_err_probe(&pdev->dev, PTR_ERR(dac_clk), + return dev_err_probe(dev, PTR_ERR(dac_clk), "failed to get dac_clk clock\n"); /* We only care about the streaming mode rate */ @@ -923,11 +924,10 @@ static int axi_dac_probe(struct platform_device *pdev) if (IS_ERR(base)) return PTR_ERR(base); - st->dev = &pdev->dev; - st->regmap = devm_regmap_init_mmio(&pdev->dev, base, - &axi_dac_regmap_config); + st->dev = dev; + st->regmap = devm_regmap_init_mmio(dev, base, &axi_dac_regmap_config); if (IS_ERR(st->regmap)) - return dev_err_probe(&pdev->dev, PTR_ERR(st->regmap), + return dev_err_probe(dev, PTR_ERR(st->regmap), "failed to init register map\n"); /* @@ -942,18 +942,15 @@ static int axi_dac_probe(struct platform_device *pdev) if (ret) return ret; - if (ADI_AXI_PCORE_VER_MAJOR(ver) != - ADI_AXI_PCORE_VER_MAJOR(st->info->version)) { - dev_err(&pdev->dev, - "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", - ADI_AXI_PCORE_VER_MAJOR(st->info->version), - ADI_AXI_PCORE_VER_MINOR(st->info->version), - ADI_AXI_PCORE_VER_PATCH(st->info->version), - ADI_AXI_PCORE_VER_MAJOR(ver), - ADI_AXI_PCORE_VER_MINOR(ver), - ADI_AXI_PCORE_VER_PATCH(ver)); - return -ENODEV; - } + if (ADI_AXI_PCORE_VER_MAJOR(ver) != ADI_AXI_PCORE_VER_MAJOR(st->info->version)) + return dev_err_probe(dev, -ENODEV, + "Major version mismatch. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n", + ADI_AXI_PCORE_VER_MAJOR(st->info->version), + ADI_AXI_PCORE_VER_MINOR(st->info->version), + ADI_AXI_PCORE_VER_PATCH(st->info->version), + ADI_AXI_PCORE_VER_MAJOR(ver), + ADI_AXI_PCORE_VER_MINOR(ver), + ADI_AXI_PCORE_VER_PATCH(ver)); /* Let's get the core read only configuration */ ret = regmap_read(st->regmap, AXI_DAC_CONFIG_REG, &st->reg_config); @@ -975,34 +972,33 @@ static int axi_dac_probe(struct platform_device *pdev) mutex_init(&st->lock); - ret = devm_iio_backend_register(&pdev->dev, st->info->backend_info, st); + ret = devm_iio_backend_register(dev, st->info->backend_info, st); if (ret) - return dev_err_probe(&pdev->dev, ret, + return dev_err_probe(dev, ret, "failed to register iio backend\n"); - device_for_each_child_node_scoped(&pdev->dev, child) { + device_for_each_child_node_scoped(dev, child) { int val; if (!st->info->has_child_nodes) - return dev_err_probe(&pdev->dev, -EINVAL, + return dev_err_probe(dev, -EINVAL, "invalid fdt axi-dac compatible."); /* Processing only reg 0 node */ ret = fwnode_property_read_u32(child, "reg", &val); if (ret) - return dev_err_probe(&pdev->dev, ret, - "invalid reg property."); + return dev_err_probe(dev, ret, "invalid reg property."); if (val != 0) - return dev_err_probe(&pdev->dev, -EINVAL, - "invalid node address."); + return dev_err_probe(dev, -EINVAL, + "invalid node address."); ret = axi_dac_create_platform_device(st, child); if (ret) - return dev_err_probe(&pdev->dev, -EINVAL, - "cannot create device."); + return dev_err_probe(dev, -EINVAL, + "cannot create device."); } - dev_info(&pdev->dev, "AXI DAC IP core (%d.%.2d.%c) probed\n", + dev_info(dev, "AXI DAC IP core (%d.%.2d.%c) probed\n", ADI_AXI_PCORE_VER_MAJOR(ver), ADI_AXI_PCORE_VER_MINOR(ver), ADI_AXI_PCORE_VER_PATCH(ver)); diff --git a/drivers/iio/dac/ds4424.c b/drivers/iio/dac/ds4424.c index a8198ba4f98a..6dda8918975a 100644 --- a/drivers/iio/dac/ds4424.c +++ b/drivers/iio/dac/ds4424.c @@ -14,7 +14,6 @@ #include <linux/iio/iio.h> #include <linux/iio/driver.h> #include <linux/iio/machine.h> -#include <linux/iio/consumer.h> #define DS4422_MAX_DAC_CHANNELS 2 #define DS4424_MAX_DAC_CHANNELS 4 diff --git a/drivers/iio/dac/max22007.c b/drivers/iio/dac/max22007.c new file mode 100644 index 000000000000..182ac7155a89 --- /dev/null +++ b/drivers/iio/dac/max22007.c @@ -0,0 +1,491 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * max22007.c - MAX22007 DAC driver + * + * Driver for Analog Devices MAX22007 Digital to Analog Converter. + * + * Copyright (c) 2026 Analog Devices Inc. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/crc8.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/device/devres.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/gpio/consumer.h> +#include <linux/iio/iio.h> +#include <linux/kstrtox.h> +#include <linux/minmax.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/types.h> + +#include <dt-bindings/iio/addac/adi,ad74413r.h> +struct device; + +#define MAX22007_NUM_CHANNELS 4 +#define MAX22007_REV_ID_REG 0x00 +#define MAX22007_STAT_INTR_REG 0x01 +#define MAX22007_INTERRUPT_EN_REG 0x02 +#define MAX22007_CONFIG_REG 0x03 +#define MAX22007_CONTROL_REG 0x04 +#define MAX22007_CHANNEL_MODE_REG 0x05 +#define MAX22007_SOFT_RESET_REG 0x06 +#define MAX22007_DAC_CHANNEL_REG(ch) (0x07 + (ch)) +#define MAX22007_GPIO_CTRL_REG 0x0B +#define MAX22007_GPIO_DATA_REG 0x0C +#define MAX22007_GPI_EDGE_INT_CTRL_REG 0x0D +#define MAX22007_GPI_INT_STATUS_REG 0x0E + +/* Channel mask definitions */ +#define MAX22007_CH_MODE_CH_MASK(ch) BIT(12 + (ch)) +#define MAX22007_CH_PWRON_CH_MASK(ch) BIT(8 + (ch)) +#define MAX22007_DAC_LATCH_MODE_MASK(ch) BIT(12 + (ch)) +#define MAX22007_LDAC_UPDATE_MASK(ch) BIT(12 + (ch)) +#define MAX22007_SW_RST_MASK BIT(8) +#define MAX22007_SW_CLR_MASK BIT(12) +#define MAX22007_SOFT_RESET_BITS_MASK (MAX22007_SW_RST_MASK | \ + MAX22007_SW_CLR_MASK) +#define MAX22007_DAC_DATA_MASK GENMASK(15, 4) +#define MAX22007_DAC_MAX_RAW GENMASK(11, 0) +#define MAX22007_CRC8_POLYNOMIAL 0x8C +#define MAX22007_CRC_EN_MASK BIT(0) +#define MAX22007_RW_MASK BIT(0) +#define MAX22007_CRC_OVERHEAD 1 +#define MAX22007_NUM_SUPPLIES 3 +#define MAX22007_REF_MV 2500 + +/* Field value preparation macros with masking */ +#define MAX22007_CH_PWR_VAL(ch, val) (((val) & 0x1) << (8 + (ch))) +#define MAX22007_CH_MODE_VAL(ch, val) (((val) & 0x1) << (12 + (ch))) +#define MAX22007_DAC_LATCH_MODE_VAL(ch, val) (((val) & 0x1) << (12 + (ch))) + +static u8 max22007_crc8_table[CRC8_TABLE_SIZE]; + +static const char * const max22007_supply_names[MAX22007_NUM_SUPPLIES] = { + "vdd", + "hvdd", + "hvss", +}; + +struct max22007_state { + struct spi_device *spi; + struct regmap *regmap; + struct iio_chan_spec *iio_chans; + u8 tx_buf[4] __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[4]; +}; + +static int max22007_spi_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct max22007_state *st = context; + u8 calculated_crc, received_crc; + u8 rx_buf[4]; + u8 reg_byte; + int ret; + + if (reg_size != 1) + return -EINVAL; + + if (val_size == 0 || val_size > 3) + return -EINVAL; + + memcpy(®_byte, reg, 1); + + ret = spi_write_then_read(st->spi, ®_byte, 1, rx_buf, + val_size + MAX22007_CRC_OVERHEAD); + if (ret) { + dev_err(&st->spi->dev, "SPI transfer failed: %d\n", ret); + return ret; + } + + calculated_crc = crc8(max22007_crc8_table, ®_byte, 1, 0x00); + calculated_crc = crc8(max22007_crc8_table, rx_buf, 2, calculated_crc); + received_crc = rx_buf[val_size]; + + if (calculated_crc != received_crc) { + dev_err(&st->spi->dev, "CRC mismatch on read register %02x\n", reg_byte); + return -EIO; + } + + memcpy(val, rx_buf, val_size); + + return 0; +} + +static int max22007_spi_write(void *context, const void *data, size_t count) +{ + struct max22007_state *st = context; + struct spi_transfer xfer = { + .tx_buf = st->tx_buf, + .rx_buf = st->rx_buf, + }; + + if (count + MAX22007_CRC_OVERHEAD > sizeof(st->tx_buf)) + return -EINVAL; + + memset(st->tx_buf, 0, sizeof(st->tx_buf)); + + xfer.len = count + MAX22007_CRC_OVERHEAD; + + memcpy(st->tx_buf, data, count); + st->tx_buf[count] = crc8(max22007_crc8_table, st->tx_buf, + sizeof(st->tx_buf) - 1, 0x00); + + return spi_sync_transfer(st->spi, &xfer, 1); +} + +static bool max22007_reg_readable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX22007_REV_ID_REG: + case MAX22007_STAT_INTR_REG: + case MAX22007_CONFIG_REG: + case MAX22007_CONTROL_REG: + case MAX22007_CHANNEL_MODE_REG: + case MAX22007_SOFT_RESET_REG: + case MAX22007_GPIO_CTRL_REG: + case MAX22007_GPIO_DATA_REG: + case MAX22007_GPI_EDGE_INT_CTRL_REG: + case MAX22007_GPI_INT_STATUS_REG: + return true; + case MAX22007_DAC_CHANNEL_REG(0) ... MAX22007_DAC_CHANNEL_REG(MAX22007_NUM_CHANNELS - 1): + return true; + default: + return false; + } +} + +static bool max22007_reg_writable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX22007_CONFIG_REG: + case MAX22007_CONTROL_REG: + case MAX22007_CHANNEL_MODE_REG: + case MAX22007_SOFT_RESET_REG: + case MAX22007_GPIO_CTRL_REG: + case MAX22007_GPIO_DATA_REG: + case MAX22007_GPI_EDGE_INT_CTRL_REG: + return true; + case MAX22007_DAC_CHANNEL_REG(0) ... MAX22007_DAC_CHANNEL_REG(MAX22007_NUM_CHANNELS - 1): + return true; + default: + return false; + } +} + +static const struct regmap_bus max22007_regmap_bus = { + .read = max22007_spi_read, + .write = max22007_spi_write, + .read_flag_mask = MAX22007_RW_MASK, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static const struct regmap_config max22007_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .reg_shift = -1, + .readable_reg = max22007_reg_readable, + .writeable_reg = max22007_reg_writable, + .max_register = 0x0E, +}; + +static int max22007_write_channel_data(struct max22007_state *st, + unsigned int channel, int data) +{ + unsigned int reg_val; + + if (data < 0 || data > MAX22007_DAC_MAX_RAW) + return -EINVAL; + + reg_val = FIELD_PREP(MAX22007_DAC_DATA_MASK, data); + + return regmap_write(st->regmap, MAX22007_DAC_CHANNEL_REG(channel), reg_val); +} + +static int max22007_read_channel_data(struct max22007_state *st, + unsigned int channel, int *data) +{ + unsigned int reg_val; + int ret; + + ret = regmap_read(st->regmap, MAX22007_DAC_CHANNEL_REG(channel), ®_val); + if (ret) + return ret; + + *data = FIELD_GET(MAX22007_DAC_DATA_MASK, reg_val); + + return 0; +} + +static int max22007_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max22007_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = max22007_read_channel_data(st, chan->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLTAGE) + *val = 5 * MAX22007_REF_MV; /* 5 * Vref in mV */ + else + *val = 25; /* Vref / (2 * Rsense) = MAX22007_REF_MV / 100 */ + *val2 = 12; /* 12-bit DAC resolution */ + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static int max22007_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max22007_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return max22007_write_channel_data(st, chan->channel, val); + default: + return -EINVAL; + } +} + +static const struct iio_info max22007_info = { + .read_raw = max22007_read_raw, + .write_raw = max22007_write_raw, +}; + +static ssize_t max22007_read_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct max22007_state *st = iio_priv(indio_dev); + unsigned int reg_val; + bool powerdown; + int ret; + + ret = regmap_read(st->regmap, MAX22007_CHANNEL_MODE_REG, ®_val); + if (ret) + return ret; + + powerdown = !(reg_val & MAX22007_CH_PWRON_CH_MASK(chan->channel)); + + return sysfs_emit(buf, "%d\n", powerdown); +} + +static ssize_t max22007_write_dac_powerdown(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) +{ + struct max22007_state *st = iio_priv(indio_dev); + bool powerdown; + int ret; + + ret = kstrtobool(buf, &powerdown); + if (ret) + return ret; + + ret = regmap_update_bits(st->regmap, MAX22007_CHANNEL_MODE_REG, + MAX22007_CH_PWRON_CH_MASK(chan->channel), + MAX22007_CH_PWR_VAL(chan->channel, powerdown ? 0 : 1)); + if (ret) + return ret; + + return len; +} + +static const struct iio_chan_spec_ext_info max22007_ext_info[] = { + { + .name = "powerdown", + .read = max22007_read_dac_powerdown, + .write = max22007_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + { } +}; + +static int max22007_parse_channel_cfg(struct max22007_state *st, u8 *num_channels) +{ + struct device *dev = &st->spi->dev; + int ret, num_chan; + int i = 0; + u32 reg; + + num_chan = device_get_child_node_count(dev); + if (!num_chan) + return dev_err_probe(dev, -ENODEV, "no channels configured\n"); + + st->iio_chans = devm_kcalloc(dev, num_chan, sizeof(*st->iio_chans), GFP_KERNEL); + if (!st->iio_chans) + return -ENOMEM; + + device_for_each_child_node_scoped(dev, child) { + u32 ch_func; + enum iio_chan_type chan_type; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, + "failed to read reg property of %pfwP\n", child); + + if (reg >= MAX22007_NUM_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "reg out of range in %pfwP\n", child); + + ret = fwnode_property_read_u32(child, "adi,ch-func", &ch_func); + if (ret) + return dev_err_probe(dev, ret, + "missing adi,ch-func property for %pfwP\n", child); + + switch (ch_func) { + case CH_FUNC_VOLTAGE_OUTPUT: + chan_type = IIO_VOLTAGE; + break; + case CH_FUNC_CURRENT_OUTPUT: + chan_type = IIO_CURRENT; + break; + default: + return dev_err_probe(dev, -EINVAL, + "invalid adi,ch-func %u for %pfwP\n", + ch_func, child); + } + + st->iio_chans[i++] = (struct iio_chan_spec) { + .output = 1, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .ext_info = max22007_ext_info, + .channel = reg, + .type = chan_type, + }; + + ret = regmap_update_bits(st->regmap, MAX22007_CHANNEL_MODE_REG, + MAX22007_CH_MODE_CH_MASK(reg), + MAX22007_CH_MODE_VAL(reg, ch_func - 1)); + if (ret) + return ret; + + /* Set DAC to transparent mode (immediate update) */ + ret = regmap_update_bits(st->regmap, MAX22007_CONFIG_REG, + MAX22007_DAC_LATCH_MODE_MASK(reg), + MAX22007_DAC_LATCH_MODE_VAL(reg, 1)); + if (ret) + return ret; + } + + *num_channels = num_chan; + + return 0; +} + +static int max22007_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct gpio_desc *reset_gpio; + struct max22007_state *st; + struct iio_dev *indio_dev; + u8 num_channels; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + + crc8_populate_lsb(max22007_crc8_table, MAX22007_CRC8_POLYNOMIAL); + + st->regmap = devm_regmap_init(dev, &max22007_regmap_bus, st, + &max22007_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + ret = devm_regulator_bulk_get_enable(dev, MAX22007_NUM_SUPPLIES, + max22007_supply_names); + if (ret) + return dev_err_probe(dev, ret, "Failed to get and enable regulators\n"); + + reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(reset_gpio)) + return dev_err_probe(dev, PTR_ERR(reset_gpio), + "Failed to get reset GPIO\n"); + + if (reset_gpio) { + gpiod_set_value_cansleep(reset_gpio, 1); + usleep_range(1000, 5000); + gpiod_set_value_cansleep(reset_gpio, 0); + usleep_range(1000, 5000); + } else { + ret = regmap_write(st->regmap, MAX22007_SOFT_RESET_REG, + MAX22007_SOFT_RESET_BITS_MASK); + if (ret) + return ret; + } + + ret = regmap_set_bits(st->regmap, MAX22007_CONFIG_REG, + MAX22007_CRC_EN_MASK); + if (ret) + return ret; + + ret = max22007_parse_channel_cfg(st, &num_channels); + if (ret) + return ret; + + indio_dev->info = &max22007_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->iio_chans; + indio_dev->num_channels = num_channels; + indio_dev->name = "max22007"; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id max22007_id[] = { + { "max22007" }, + { } +}; +MODULE_DEVICE_TABLE(spi, max22007_id); + +static const struct of_device_id max22007_of_match[] = { + { .compatible = "adi,max22007" }, + { } +}; +MODULE_DEVICE_TABLE(of, max22007_of_match); + +static struct spi_driver max22007_driver = { + .driver = { + .name = "max22007", + .of_match_table = max22007_of_match, + }, + .probe = max22007_probe, + .id_table = max22007_id, +}; +module_spi_driver(max22007_driver); + +MODULE_AUTHOR("Janani Sunil <janani.sunil@analog.com>"); +MODULE_DESCRIPTION("Analog Devices MAX22007 DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/dac/mcp47feb02.c b/drivers/iio/dac/mcp47feb02.c new file mode 100644 index 000000000000..b218f0c3a0bd --- /dev/null +++ b/drivers/iio/dac/mcp47feb02.c @@ -0,0 +1,1250 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface + * + * Copyright (C) 2025 Microchip Technology Inc. and its subsidiaries + * + * Author: Ariana Lazar <ariana.lazar@microchip.com> + * + * Datasheet links: + * [MCP47FEBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005375A.pdf + * [MCP47FVBxx] https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005405A.pdf + * [MCP47FxBx4/8] https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP47FXBX48-Data-Sheet-DS200006368A.pdf + */ +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/kstrtox.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/time64.h> +#include <linux/types.h> +#include <linux/units.h> + +/* Register addresses must be left shifted with 3 positions in order to append command mask */ +#define MCP47FEB02_DAC0_REG_ADDR 0x00 +#define MCP47FEB02_VREF_REG_ADDR 0x40 +#define MCP47FEB02_POWER_DOWN_REG_ADDR 0x48 +#define MCP47FEB02_DAC_CTRL_MASK GENMASK(1, 0) + +#define MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR 0x50 +#define MCP47FEB02_GAIN_BIT_MASK BIT(0) +#define MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK BIT(6) +#define MCP47FEB02_GAIN_BITS_MASK GENMASK(15, 8) + +#define MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR 0x58 + +#define MCP47FEB02_NV_DAC0_REG_ADDR 0x80 +#define MCP47FEB02_NV_VREF_REG_ADDR 0xC0 +#define MCP47FEB02_NV_POWER_DOWN_REG_ADDR 0xC8 +#define MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR 0xD0 +#define MCP47FEB02_NV_I2C_SLAVE_ADDR_MASK GENMASK(7, 0) + +/* Voltage reference, Power-Down control register and DAC Wiperlock status register fields */ +#define DAC_CTRL_MASK(ch) (GENMASK(1, 0) << (2 * (ch))) +#define DAC_CTRL_VAL(ch, val) ((val) << (2 * (ch))) + +/* Gain Control and I2C Slave Address Reguster fields */ +#define DAC_GAIN_MASK(ch) (BIT(0) << (8 + (ch))) +#define DAC_GAIN_VAL(ch, val) ((val) << (8 + (ch))) + +#define REG_ADDR(reg) ((reg) << 3) +#define NV_REG_ADDR(reg) ((NV_DAC_ADDR_OFFSET + (reg)) << 3) +#define READFLAG_MASK GENMASK(2, 1) + +#define MCP47FEB02_MAX_CH 8 +#define MCP47FEB02_MAX_SCALES_CH 3 +#define MCP47FEB02_DAC_WIPER_UNLOCKED 0 +#define MCP47FEB02_NORMAL_OPERATION 0 +#define MCP47FEB02_INTERNAL_BAND_GAP_mV 2440 +#define NV_DAC_ADDR_OFFSET 0x10 + +enum mcp47feb02_vref_mode { + MCP47FEB02_VREF_VDD = 0, + MCP47FEB02_INTERNAL_BAND_GAP = 1, + MCP47FEB02_EXTERNAL_VREF_UNBUFFERED = 2, + MCP47FEB02_EXTERNAL_VREF_BUFFERED = 3, +}; + +enum mcp47feb02_scale { + MCP47FEB02_SCALE_VDD = 0, + MCP47FEB02_SCALE_GAIN_X1 = 1, + MCP47FEB02_SCALE_GAIN_X2 = 2, +}; + +enum mcp47feb02_gain_bit_mode { + MCP47FEB02_GAIN_BIT_X1 = 0, + MCP47FEB02_GAIN_BIT_X2 = 1, +}; + +static const char * const mcp47feb02_powerdown_modes[] = { + "1kohm_to_gnd", + "100kohm_to_gnd", + "open_circuit", +}; + +/** + * struct mcp47feb02_features - chip specific data + * @name: device name + * @phys_channels: number of hardware channels + * @resolution: DAC resolution + * @have_ext_vref1: does the hardware have an the second external voltage reference? + * @have_eeprom: does the hardware have an internal eeprom? + */ +struct mcp47feb02_features { + const char *name; + unsigned int phys_channels; + unsigned int resolution; + bool have_ext_vref1; + bool have_eeprom; +}; + +static const struct mcp47feb02_features mcp47feb01_chip_features = { + .name = "mcp47feb01", + .phys_channels = 1, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb02_chip_features = { + .name = "mcp47feb02", + .phys_channels = 2, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb04_chip_features = { + .name = "mcp47feb04", + .phys_channels = 4, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb08_chip_features = { + .name = "mcp47feb08", + .phys_channels = 8, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb11_chip_features = { + .name = "mcp47feb11", + .phys_channels = 1, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb12_chip_features = { + .name = "mcp47feb12", + .phys_channels = 2, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb14_chip_features = { + .name = "mcp47feb14", + .phys_channels = 4, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb18_chip_features = { + .name = "mcp47feb18", + .phys_channels = 8, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb21_chip_features = { + .name = "mcp47feb21", + .phys_channels = 1, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb22_chip_features = { + .name = "mcp47feb22", + .phys_channels = 2, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb24_chip_features = { + .name = "mcp47feb24", + .phys_channels = 4, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47feb28_chip_features = { + .name = "mcp47feb28", + .phys_channels = 8, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = true, +}; + +static const struct mcp47feb02_features mcp47fvb01_chip_features = { + .name = "mcp47fvb01", + .phys_channels = 1, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb02_chip_features = { + .name = "mcp47fvb02", + .phys_channels = 2, + .resolution = 8, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb04_chip_features = { + .name = "mcp47fvb04", + .phys_channels = 4, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb08_chip_features = { + .name = "mcp47fvb08", + .phys_channels = 8, + .resolution = 8, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb11_chip_features = { + .name = "mcp47fvb11", + .phys_channels = 1, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb12_chip_features = { + .name = "mcp47fvb12", + .phys_channels = 2, + .resolution = 10, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb14_chip_features = { + .name = "mcp47fvb14", + .phys_channels = 4, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb18_chip_features = { + .name = "mcp47fvb18", + .phys_channels = 8, + .resolution = 10, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb21_chip_features = { + .name = "mcp47fvb21", + .phys_channels = 1, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb22_chip_features = { + .name = "mcp47fvb22", + .phys_channels = 2, + .resolution = 12, + .have_ext_vref1 = false, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb24_chip_features = { + .name = "mcp47fvb24", + .phys_channels = 4, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +static const struct mcp47feb02_features mcp47fvb28_chip_features = { + .name = "mcp47fvb28", + .phys_channels = 8, + .resolution = 12, + .have_ext_vref1 = true, + .have_eeprom = false, +}; + +/** + * struct mcp47feb02_channel_data - channel configuration + * @ref_mode: chosen voltage for reference + * @use_2x_gain: output driver gain control + * @powerdown: is false if the channel is in normal operation mode + * @powerdown_mode: selected power-down mode + * @dac_data: dac value + */ +struct mcp47feb02_channel_data { + u8 ref_mode; + bool use_2x_gain; + bool powerdown; + u8 powerdown_mode; + u16 dac_data; +}; + +/** + * struct mcp47feb02_data - chip configuration + * @chdata: options configured for each channel on the device + * @lock: prevents concurrent reads/writes to driver's state members + * @chip_features: pointer to features struct + * @scale_1: scales set on channels that are based on Vref1 + * @scale: scales set on channels that are based on Vref/Vref0 + * @active_channels_mask: enabled channels + * @regmap: regmap for directly accessing device register + * @labels: table with channels labels + * @phys_channels: physical channels on the device + * @vref1_buffered: Vref1 buffer is enabled + * @vref_buffered: Vref/Vref0 buffer is enabled + * @use_vref1: vref1-supply is defined + * @use_vref: vref-supply is defined + */ +struct mcp47feb02_data { + struct mcp47feb02_channel_data chdata[MCP47FEB02_MAX_CH]; + struct mutex lock; /* prevents concurrent reads/writes to driver's state members */ + const struct mcp47feb02_features *chip_features; + int scale_1[2 * MCP47FEB02_MAX_SCALES_CH]; + int scale[2 * MCP47FEB02_MAX_SCALES_CH]; + unsigned long active_channels_mask; + struct regmap *regmap; + const char *labels[MCP47FEB02_MAX_CH]; + u16 phys_channels; + bool vref1_buffered; + bool vref_buffered; + bool use_vref1; + bool use_vref; +}; + +static const struct regmap_range mcp47feb02_readable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), +}; + +static const struct regmap_range mcp47feb02_writable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), +}; + +static const struct regmap_range mcp47feb02_volatile_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_NV_DAC0_REG_ADDR, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR), +}; + +static const struct regmap_access_table mcp47feb02_readable_table = { + .yes_ranges = mcp47feb02_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47feb02_readable_ranges), +}; + +static const struct regmap_access_table mcp47feb02_writable_table = { + .yes_ranges = mcp47feb02_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47feb02_writable_ranges), +}; + +static const struct regmap_access_table mcp47feb02_volatile_table = { + .yes_ranges = mcp47feb02_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47feb02_volatile_ranges), +}; + +static const struct regmap_config mcp47feb02_regmap_config = { + .name = "mcp47feb02_regmap", + .reg_bits = 8, + .val_bits = 16, + .rd_table = &mcp47feb02_readable_table, + .wr_table = &mcp47feb02_writable_table, + .volatile_table = &mcp47feb02_volatile_table, + .max_register = MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR, + .read_flag_mask = READFLAG_MASK, + .cache_type = REGCACHE_MAPLE, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +/* For devices that doesn't have nonvolatile memory */ +static const struct regmap_range mcp47fvb02_readable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), +}; + +static const struct regmap_range mcp47fvb02_writable_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), +}; + +static const struct regmap_range mcp47fvb02_volatile_ranges[] = { + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), + regmap_reg_range(MCP47FEB02_DAC0_REG_ADDR, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR), +}; + +static const struct regmap_access_table mcp47fvb02_readable_table = { + .yes_ranges = mcp47fvb02_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_readable_ranges), +}; + +static const struct regmap_access_table mcp47fvb02_writable_table = { + .yes_ranges = mcp47fvb02_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_writable_ranges), +}; + +static const struct regmap_access_table mcp47fvb02_volatile_table = { + .yes_ranges = mcp47fvb02_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(mcp47fvb02_volatile_ranges), +}; + +static const struct regmap_config mcp47fvb02_regmap_config = { + .name = "mcp47fvb02_regmap", + .reg_bits = 8, + .val_bits = 16, + .rd_table = &mcp47fvb02_readable_table, + .wr_table = &mcp47fvb02_writable_table, + .volatile_table = &mcp47fvb02_volatile_table, + .max_register = MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR, + .read_flag_mask = READFLAG_MASK, + .cache_type = REGCACHE_MAPLE, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int mcp47feb02_write_to_eeprom(struct mcp47feb02_data *data, unsigned int reg, + unsigned int val) +{ + int eewa_val, ret; + + /* + * Wait until the currently occurring EEPROM Write Cycle is completed. + * Only serial commands to the volatile memory are allowed. + */ + guard(mutex)(&data->lock); + + ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, + eewa_val, + !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), + USEC_PER_MSEC, USEC_PER_MSEC * 5); + if (ret) + return ret; + + return regmap_write(data->regmap, reg, val); +} + +static ssize_t store_eeprom_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct mcp47feb02_data *data = iio_priv(dev_to_iio_dev(dev)); + unsigned int i, val, val1, eewa_val; + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + if (!state) + return 0; + + /* + * Verify DAC Wiper and DAC Configuration are unlocked. If both are disabled, + * writing to EEPROM is available. + */ + ret = regmap_read(data->regmap, MCP47FEB02_WIPERLOCK_STATUS_REG_ADDR, &val); + if (ret) + return ret; + + if (val) { + dev_err(dev, "DAC Wiper and DAC Configuration not are unlocked.\n"); + return -EINVAL; + } + + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { + ret = mcp47feb02_write_to_eeprom(data, NV_REG_ADDR(i), + data->chdata[i].dac_data); + if (ret) + return ret; + } + + ret = regmap_read(data->regmap, MCP47FEB02_VREF_REG_ADDR, &val); + if (ret) + return ret; + + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_VREF_REG_ADDR, val); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, &val); + if (ret) + return ret; + + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_POWER_DOWN_REG_ADDR, val); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, eewa_val, + !(eewa_val & MCP47FEB02_GAIN_BIT_STATUS_EEWA_MASK), + USEC_PER_MSEC, USEC_PER_MSEC * 5); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR, &val); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, &val1); + if (ret) + return ret; + + ret = mcp47feb02_write_to_eeprom(data, MCP47FEB02_NV_GAIN_CTRL_I2C_SLAVE_REG_ADDR, + (val1 & MCP47FEB02_GAIN_BITS_MASK) | + (val & MCP47FEB02_NV_I2C_SLAVE_ADDR_MASK)); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR_WO(store_eeprom, 0); + +static struct attribute *mcp47feb02_attributes[] = { + &iio_dev_attr_store_eeprom.dev_attr.attr, + NULL +}; + +static const struct attribute_group mcp47feb02_attribute_group = { + .attrs = mcp47feb02_attributes, +}; + +static int mcp47feb02_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mcp47feb02_data *data = iio_priv(indio_dev); + int ret; + u8 ch; + + guard(mutex)(&data->lock); + + for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { + u8 pd_mode; + + data->chdata[ch].powerdown = true; + pd_mode = data->chdata[ch].powerdown_mode + 1; + ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode)); + if (ret) + return ret; + + ret = regmap_write(data->regmap, REG_ADDR(ch), data->chdata[ch].dac_data); + if (ret) + return ret; + } + + return 0; +} + +static int mcp47feb02_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mcp47feb02_data *data = iio_priv(indio_dev); + u8 ch; + + guard(mutex)(&data->lock); + + for_each_set_bit(ch, &data->active_channels_mask, data->phys_channels) { + u8 pd_mode; + int ret; + + data->chdata[ch].powerdown = false; + pd_mode = data->chdata[ch].powerdown_mode + 1; + + ret = regmap_write(data->regmap, REG_ADDR(ch), data->chdata[ch].dac_data); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, pd_mode)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, + DAC_GAIN_MASK(ch), + DAC_GAIN_VAL(ch, data->chdata[ch].use_2x_gain)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, + DAC_CTRL_MASK(ch), + DAC_CTRL_VAL(ch, MCP47FEB02_NORMAL_OPERATION)); + if (ret) + return ret; + } + + return 0; +} + +static int mcp47feb02_get_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + return data->chdata[chan->address].powerdown_mode; +} + +static int mcp47feb02_set_powerdown_mode(struct iio_dev *indio_dev, const struct iio_chan_spec *ch, + unsigned int mode) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + data->chdata[ch->address].powerdown_mode = mode; + + return 0; +} + +static ssize_t mcp47feb02_read_powerdown(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *ch, char *buf) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + /* Print if channel is in a power-down mode or not */ + return sysfs_emit(buf, "%d\n", data->chdata[ch->address].powerdown); +} + +static ssize_t mcp47feb02_write_powerdown(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *ch, const char *buf, + size_t len) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + u32 reg = ch->address; + u8 tmp_pd_mode; + bool state; + int ret; + + guard(mutex)(&data->lock); + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + /* + * Set the channel to the specified power-down mode. Exiting power-down mode + * requires writing normal operation mode (0) to the channel-specific register bits. + */ + tmp_pd_mode = state ? (data->chdata[reg].powerdown_mode + 1) : MCP47FEB02_NORMAL_OPERATION; + ret = regmap_update_bits(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, + DAC_CTRL_MASK(reg), DAC_CTRL_VAL(reg, tmp_pd_mode)); + if (ret) + return ret; + + data->chdata[reg].powerdown = state; + + return len; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mcp47feb02_pm_ops, mcp47feb02_suspend, mcp47feb02_resume); + +static const struct iio_enum mcp47febxx_powerdown_mode_enum = { + .items = mcp47feb02_powerdown_modes, + .num_items = ARRAY_SIZE(mcp47feb02_powerdown_modes), + .get = mcp47feb02_get_powerdown_mode, + .set = mcp47feb02_set_powerdown_mode, +}; + +static const struct iio_chan_spec_ext_info mcp47feb02_ext_info[] = { + { + .name = "powerdown", + .read = mcp47feb02_read_powerdown, + .write = mcp47feb02_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp47febxx_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &mcp47febxx_powerdown_mode_enum), + { } +}; + +static const struct iio_chan_spec mcp47febxx_ch_template = { + .type = IIO_VOLTAGE, + .output = 1, + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE), + .ext_info = mcp47feb02_ext_info, +}; + +static void mcp47feb02_init_scale(struct mcp47feb02_data *data, enum mcp47feb02_scale scale, + int vref_mV, int scale_avail[]) +{ + u32 value_micro, value_int; + u64 tmp; + + /* vref_mV should not be negative */ + tmp = (u64)vref_mV * MICRO >> data->chip_features->resolution; + value_int = div_u64_rem(tmp, MICRO, &value_micro); + scale_avail[scale * 2] = value_int; + scale_avail[scale * 2 + 1] = value_micro; +} + +static int mcp47feb02_init_scales_avail(struct mcp47feb02_data *data, int vdd_mV, + int vref_mV, int vref1_mV) +{ + struct device *dev = regmap_get_device(data->regmap); + int tmp_vref; + + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_VDD, vdd_mV, data->scale); + + if (data->use_vref) + tmp_vref = vref_mV; + else + tmp_vref = MCP47FEB02_INTERNAL_BAND_GAP_mV; + + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X1, tmp_vref, data->scale); + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X2, tmp_vref * 2, data->scale); + + if (data->phys_channels >= 4) { + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_VDD, vdd_mV, data->scale_1); + + if (data->use_vref1 && vref1_mV <= 0) + return dev_err_probe(dev, vref1_mV, "Invalid voltage for Vref1\n"); + + if (data->use_vref1) + tmp_vref = vref1_mV; + else + tmp_vref = MCP47FEB02_INTERNAL_BAND_GAP_mV; + + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X1, + tmp_vref, data->scale_1); + mcp47feb02_init_scale(data, MCP47FEB02_SCALE_GAIN_X2, + tmp_vref * 2, data->scale_1); + } + + return 0; +} + +static int mcp47feb02_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + const int **vals, int *type, int *length, long info) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + switch (ch->type) { + case IIO_VOLTAGE: + if (data->phys_channels >= 4 && (ch->address % 2)) + *vals = data->scale_1; + else + *vals = data->scale; + + *length = 2 * MCP47FEB02_MAX_SCALES_CH; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static void mcp47feb02_get_scale(int ch, struct mcp47feb02_data *data, int *val, int *val2) +{ + enum mcp47feb02_scale current_scale; + + if (data->chdata[ch].ref_mode == MCP47FEB02_VREF_VDD) + current_scale = MCP47FEB02_SCALE_VDD; + else if (data->chdata[ch].use_2x_gain) + current_scale = MCP47FEB02_SCALE_GAIN_X2; + else + current_scale = MCP47FEB02_SCALE_GAIN_X1; + + if (data->phys_channels >= 4 && (ch % 2)) { + *val = data->scale_1[current_scale * 2]; + *val2 = data->scale_1[current_scale * 2 + 1]; + } else { + *val = data->scale[current_scale * 2]; + *val2 = data->scale[current_scale * 2 + 1]; + } +} + +static int mcp47feb02_check_scale(struct mcp47feb02_data *data, int val, int val2, int scale[]) +{ + unsigned int i; + + for (i = 0; i < MCP47FEB02_MAX_SCALES_CH; i++) { + if (scale[i * 2] == val && scale[i * 2 + 1] == val2) + return i; + } + + return -EINVAL; +} + +static int mcp47feb02_ch_scale(struct mcp47feb02_data *data, int ch, int scale) +{ + int tmp_val, ret; + + if (scale == MCP47FEB02_SCALE_VDD) { + tmp_val = MCP47FEB02_VREF_VDD; + } else if (data->phys_channels >= 4 && (ch % 2)) { + if (data->use_vref1) { + if (data->vref1_buffered) + tmp_val = MCP47FEB02_EXTERNAL_VREF_BUFFERED; + else + tmp_val = MCP47FEB02_EXTERNAL_VREF_UNBUFFERED; + } else { + tmp_val = MCP47FEB02_INTERNAL_BAND_GAP; + } + } else if (data->use_vref) { + if (data->vref_buffered) + tmp_val = MCP47FEB02_EXTERNAL_VREF_BUFFERED; + else + tmp_val = MCP47FEB02_EXTERNAL_VREF_UNBUFFERED; + } else { + tmp_val = MCP47FEB02_INTERNAL_BAND_GAP; + } + + ret = regmap_update_bits(data->regmap, MCP47FEB02_VREF_REG_ADDR, + DAC_CTRL_MASK(ch), DAC_CTRL_VAL(ch, tmp_val)); + if (ret) + return ret; + + data->chdata[ch].ref_mode = tmp_val; + + return 0; +} + +/* + * Setting the scale in order to choose between VDD and (Vref or Band Gap) from the user + * space. The VREF pin is either an input or an output, therefore the user cannot + * simultaneously connect an external voltage reference to the pin and select the + * internal Band Gap. + * When the DAC’s voltage reference is configured as the VREF pin, the pin is an input. + * When the DAC’s voltage reference is configured as the internal Band Gap, + * the VREF pin is an output. + * If Vref/Vref1 voltage is not available, then the internal Band Gap will be used + * to calculate the values for the scale. + */ +static int mcp47feb02_set_scale(struct mcp47feb02_data *data, int ch, int scale) +{ + int tmp_val, ret; + + ret = mcp47feb02_ch_scale(data, ch, scale); + if (ret) + return ret; + + if (scale == MCP47FEB02_SCALE_GAIN_X2) + tmp_val = MCP47FEB02_GAIN_BIT_X2; + else + tmp_val = MCP47FEB02_GAIN_BIT_X1; + + ret = regmap_update_bits(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, + DAC_GAIN_MASK(ch), DAC_GAIN_VAL(ch, tmp_val)); + if (ret) + return ret; + + data->chdata[ch].use_2x_gain = tmp_val; + + return 0; +} + +static int mcp47feb02_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + int *val, int *val2, long mask) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->regmap, REG_ADDR(ch->address), val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + mcp47feb02_get_scale(ch->address, data, val, val2); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mcp47feb02_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + int val, int val2, long mask) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + int *tmp_scale, ret; + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_write(data->regmap, REG_ADDR(ch->address), val); + if (ret) + return ret; + + data->chdata[ch->address].dac_data = val; + return 0; + case IIO_CHAN_INFO_SCALE: + if (data->phys_channels >= 4 && (ch->address % 2)) + tmp_scale = data->scale_1; + else + tmp_scale = data->scale; + + ret = mcp47feb02_check_scale(data, val, val2, tmp_scale); + if (ret < 0) + return ret; + + return mcp47feb02_set_scale(data, ch->address, ret); + default: + return -EINVAL; + } +} + +static int mcp47feb02_read_label(struct iio_dev *indio_dev, struct iio_chan_spec const *ch, + char *label) +{ + struct mcp47feb02_data *data = iio_priv(indio_dev); + + return sysfs_emit(label, "%s\n", data->labels[ch->address]); +} + +static const struct iio_info mcp47feb02_info = { + .read_raw = mcp47feb02_read_raw, + .write_raw = mcp47feb02_write_raw, + .read_label = mcp47feb02_read_label, + .read_avail = &mcp47feb02_read_avail, + .attrs = &mcp47feb02_attribute_group, +}; + +static const struct iio_info mcp47fvb02_info = { + .read_raw = mcp47feb02_read_raw, + .write_raw = mcp47feb02_write_raw, + .read_label = mcp47feb02_read_label, + .read_avail = &mcp47feb02_read_avail, +}; + +static int mcp47feb02_parse_fw(struct iio_dev *indio_dev, + const struct mcp47feb02_features *chip_features) +{ + struct iio_chan_spec chanspec = mcp47febxx_ch_template; + struct mcp47feb02_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); + struct iio_chan_spec *channels; + u32 num_channels; + u8 chan_idx = 0; + + guard(mutex)(&data->lock); + + num_channels = device_get_child_node_count(dev); + if (num_channels > chip_features->phys_channels) + return dev_err_probe(dev, -EINVAL, "More channels than the chip supports\n"); + + if (!num_channels) + return dev_err_probe(dev, -EINVAL, "No channel specified in the devicetree.\n"); + + channels = devm_kcalloc(dev, num_channels, sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + device_for_each_child_node_scoped(dev, child) { + u32 reg = 0; + int ret; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return dev_err_probe(dev, ret, "Invalid channel number\n"); + + if (reg >= chip_features->phys_channels) + return dev_err_probe(dev, -EINVAL, + "The index of the channels does not match the chip\n"); + + set_bit(reg, &data->active_channels_mask); + + ret = fwnode_property_read_string(child, "label", &data->labels[reg]); + if (ret) + return dev_err_probe(dev, ret, "%pfw: invalid label\n", + fwnode_get_name(child)); + + chanspec.address = reg; + chanspec.channel = reg; + channels[chan_idx] = chanspec; + chan_idx++; + } + + indio_dev->num_channels = num_channels; + indio_dev->channels = channels; + indio_dev->modes = INDIO_DIRECT_MODE; + data->phys_channels = chip_features->phys_channels; + + data->vref_buffered = device_property_read_bool(dev, "microchip,vref-buffered"); + + if (chip_features->have_ext_vref1) + data->vref1_buffered = device_property_read_bool(dev, "microchip,vref1-buffered"); + + return 0; +} + +static int mcp47feb02_init_ctrl_regs(struct mcp47feb02_data *data) +{ + unsigned int i, vref_ch, gain_ch, pd_ch; + int ret; + + ret = regmap_read(data->regmap, MCP47FEB02_VREF_REG_ADDR, &vref_ch); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_GAIN_CTRL_STATUS_REG_ADDR, &gain_ch); + if (ret) + return ret; + + ret = regmap_read(data->regmap, MCP47FEB02_POWER_DOWN_REG_ADDR, &pd_ch); + if (ret) + return ret; + + gain_ch = gain_ch & MCP47FEB02_GAIN_BITS_MASK; + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { + struct device *dev = regmap_get_device(data->regmap); + unsigned int pd_tmp; + + data->chdata[i].ref_mode = (vref_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; + data->chdata[i].use_2x_gain = (gain_ch >> i) & MCP47FEB02_GAIN_BIT_MASK; + + /* + * Inform the user that the current voltage reference read from the volatile + * register of the chip is different from the one specified in the device tree. + * Considering that the user cannot have an external voltage reference connected + * to the pin and select the internal Band Gap at the same time, in order to avoid + * miscofiguring the reference voltage, the volatile register will not be written. + * In order to overwrite the setting from volatile register with the one from the + * device tree, the user needs to write the chosen scale. + */ + switch (data->chdata[i].ref_mode) { + case MCP47FEB02_INTERNAL_BAND_GAP: + if (data->phys_channels >= 4 && (i % 2) && data->use_vref1) { + dev_dbg(dev, "ch[%u]: was configured to use internal band gap", i); + dev_dbg(dev, "ch[%u]: reference voltage set to VREF1", i); + break; + } + if ((data->phys_channels < 4 || (data->phys_channels >= 4 && !(i % 2))) && + data->use_vref) { + dev_dbg(dev, "ch[%u]: was configured to use internal band gap", i); + dev_dbg(dev, "ch[%u]: reference voltage set to VREF", i); + break; + } + break; + case MCP47FEB02_EXTERNAL_VREF_UNBUFFERED: + case MCP47FEB02_EXTERNAL_VREF_BUFFERED: + if (data->phys_channels >= 4 && (i % 2) && !data->use_vref1) { + dev_dbg(dev, "ch[%u]: was configured to use VREF1", i); + dev_dbg(dev, + "ch[%u]: reference voltage set to internal band gap", i); + break; + } + if ((data->phys_channels < 4 || (data->phys_channels >= 4 && !(i % 2))) && + !data->use_vref) { + dev_dbg(dev, "ch[%u]: was configured to use VREF", i); + dev_dbg(dev, + "ch[%u]: reference voltage set to internal band gap", i); + break; + } + break; + } + + pd_tmp = (pd_ch >> (2 * i)) & MCP47FEB02_DAC_CTRL_MASK; + data->chdata[i].powerdown_mode = pd_tmp ? (pd_tmp - 1) : pd_tmp; + data->chdata[i].powerdown = !!(data->chdata[i].powerdown_mode); + } + + return 0; +} + +static int mcp47feb02_init_ch_scales(struct mcp47feb02_data *data, int vdd_mV, + int vref_mV, int vref1_mV) +{ + unsigned int i; + + for_each_set_bit(i, &data->active_channels_mask, data->phys_channels) { + struct device *dev = regmap_get_device(data->regmap); + int ret; + + ret = mcp47feb02_init_scales_avail(data, vdd_mV, vref_mV, vref1_mV); + if (ret) + return dev_err_probe(dev, ret, "failed to init scales for ch %u\n", i); + } + + return 0; +} + +static int mcp47feb02_probe(struct i2c_client *client) +{ + const struct mcp47feb02_features *chip_features; + struct device *dev = &client->dev; + struct mcp47feb02_data *data; + struct iio_dev *indio_dev; + int vref1_mV = 0; + int vref_mV = 0; + int vdd_mV; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + chip_features = i2c_get_match_data(client); + if (!chip_features) + return -EINVAL; + + data->chip_features = chip_features; + + if (chip_features->have_eeprom) { + data->regmap = devm_regmap_init_i2c(client, &mcp47feb02_regmap_config); + indio_dev->info = &mcp47feb02_info; + } else { + data->regmap = devm_regmap_init_i2c(client, &mcp47fvb02_regmap_config); + indio_dev->info = &mcp47fvb02_info; + } + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), "Error initializing i2c regmap\n"); + + indio_dev->name = chip_features->name; + + ret = mcp47feb02_parse_fw(indio_dev, chip_features); + if (ret) + return dev_err_probe(dev, ret, "Error parsing firmware data\n"); + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + ret = devm_regulator_get_enable_read_voltage(dev, "vdd"); + if (ret < 0) + return ret; + + vdd_mV = ret / MILLI; + + ret = devm_regulator_get_enable_read_voltage(dev, "vref"); + if (ret > 0) { + vref_mV = ret / MILLI; + data->use_vref = true; + } else { + dev_dbg(dev, "using internal band gap as voltage reference.\n"); + dev_dbg(dev, "Vref is unavailable.\n"); + } + + if (chip_features->have_ext_vref1) { + ret = devm_regulator_get_enable_read_voltage(dev, "vref1"); + if (ret > 0) { + vref1_mV = ret / MILLI; + data->use_vref1 = true; + } else { + dev_dbg(dev, "using internal band gap as voltage reference 1.\n"); + dev_dbg(dev, "Vref1 is unavailable.\n"); + } + } + + ret = mcp47feb02_init_ctrl_regs(data); + if (ret) + return dev_err_probe(dev, ret, "Error initialising vref register\n"); + + ret = mcp47feb02_init_ch_scales(data, vdd_mV, vref_mV, vref1_mV); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id mcp47feb02_id[] = { + { "mcp47feb01", (kernel_ulong_t)&mcp47feb01_chip_features }, + { "mcp47feb02", (kernel_ulong_t)&mcp47feb02_chip_features }, + { "mcp47feb04", (kernel_ulong_t)&mcp47feb04_chip_features }, + { "mcp47feb08", (kernel_ulong_t)&mcp47feb08_chip_features }, + { "mcp47feb11", (kernel_ulong_t)&mcp47feb11_chip_features }, + { "mcp47feb12", (kernel_ulong_t)&mcp47feb12_chip_features }, + { "mcp47feb14", (kernel_ulong_t)&mcp47feb14_chip_features }, + { "mcp47feb18", (kernel_ulong_t)&mcp47feb18_chip_features }, + { "mcp47feb21", (kernel_ulong_t)&mcp47feb21_chip_features }, + { "mcp47feb22", (kernel_ulong_t)&mcp47feb22_chip_features }, + { "mcp47feb24", (kernel_ulong_t)&mcp47feb24_chip_features }, + { "mcp47feb28", (kernel_ulong_t)&mcp47feb28_chip_features }, + { "mcp47fvb01", (kernel_ulong_t)&mcp47fvb01_chip_features }, + { "mcp47fvb02", (kernel_ulong_t)&mcp47fvb02_chip_features }, + { "mcp47fvb04", (kernel_ulong_t)&mcp47fvb04_chip_features }, + { "mcp47fvb08", (kernel_ulong_t)&mcp47fvb08_chip_features }, + { "mcp47fvb11", (kernel_ulong_t)&mcp47fvb11_chip_features }, + { "mcp47fvb12", (kernel_ulong_t)&mcp47fvb12_chip_features }, + { "mcp47fvb14", (kernel_ulong_t)&mcp47fvb14_chip_features }, + { "mcp47fvb18", (kernel_ulong_t)&mcp47fvb18_chip_features }, + { "mcp47fvb21", (kernel_ulong_t)&mcp47fvb21_chip_features }, + { "mcp47fvb22", (kernel_ulong_t)&mcp47fvb22_chip_features }, + { "mcp47fvb24", (kernel_ulong_t)&mcp47fvb24_chip_features }, + { "mcp47fvb28", (kernel_ulong_t)&mcp47fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mcp47feb02_id); + +static const struct of_device_id mcp47feb02_of_match[] = { + { .compatible = "microchip,mcp47feb01", .data = &mcp47feb01_chip_features }, + { .compatible = "microchip,mcp47feb02", .data = &mcp47feb02_chip_features }, + { .compatible = "microchip,mcp47feb04", .data = &mcp47feb04_chip_features }, + { .compatible = "microchip,mcp47feb08", .data = &mcp47feb08_chip_features }, + { .compatible = "microchip,mcp47feb11", .data = &mcp47feb11_chip_features }, + { .compatible = "microchip,mcp47feb12", .data = &mcp47feb12_chip_features }, + { .compatible = "microchip,mcp47feb14", .data = &mcp47feb14_chip_features }, + { .compatible = "microchip,mcp47feb18", .data = &mcp47feb18_chip_features }, + { .compatible = "microchip,mcp47feb21", .data = &mcp47feb21_chip_features }, + { .compatible = "microchip,mcp47feb22", .data = &mcp47feb22_chip_features }, + { .compatible = "microchip,mcp47feb24", .data = &mcp47feb24_chip_features }, + { .compatible = "microchip,mcp47feb28", .data = &mcp47feb28_chip_features }, + { .compatible = "microchip,mcp47fvb01", .data = &mcp47fvb01_chip_features }, + { .compatible = "microchip,mcp47fvb02", .data = &mcp47fvb02_chip_features }, + { .compatible = "microchip,mcp47fvb04", .data = &mcp47fvb04_chip_features }, + { .compatible = "microchip,mcp47fvb08", .data = &mcp47fvb08_chip_features }, + { .compatible = "microchip,mcp47fvb11", .data = &mcp47fvb11_chip_features }, + { .compatible = "microchip,mcp47fvb12", .data = &mcp47fvb12_chip_features }, + { .compatible = "microchip,mcp47fvb14", .data = &mcp47fvb14_chip_features }, + { .compatible = "microchip,mcp47fvb18", .data = &mcp47fvb18_chip_features }, + { .compatible = "microchip,mcp47fvb21", .data = &mcp47fvb21_chip_features }, + { .compatible = "microchip,mcp47fvb22", .data = &mcp47fvb22_chip_features }, + { .compatible = "microchip,mcp47fvb24", .data = &mcp47fvb24_chip_features }, + { .compatible = "microchip,mcp47fvb28", .data = &mcp47fvb28_chip_features }, + { } +}; +MODULE_DEVICE_TABLE(of, mcp47feb02_of_match); + +static struct i2c_driver mcp47feb02_driver = { + .driver = { + .name = "mcp47feb02", + .of_match_table = mcp47feb02_of_match, + .pm = pm_sleep_ptr(&mcp47feb02_pm_ops), + }, + .probe = mcp47feb02_probe, + .id_table = mcp47feb02_id, +}; +module_i2c_driver(mcp47feb02_driver); + +MODULE_AUTHOR("Ariana Lazar <ariana.lazar@microchip.com>"); +MODULE_DESCRIPTION("IIO driver for MCP47FEB02 Multi-Channel DAC with I2C interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c index 08833b7035e4..fa686f785fa4 100644 --- a/drivers/iio/frequency/adf4377.c +++ b/drivers/iio/frequency/adf4377.c @@ -8,7 +8,9 @@ #include <linux/bitfield.h> #include <linux/bits.h> #include <linux/clk.h> +#include <linux/clk-provider.h> #include <linux/clkdev.h> +#include <linux/container_of.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/gpio/consumer.h> @@ -435,9 +437,14 @@ struct adf4377_state { struct gpio_desc *gpio_ce; struct gpio_desc *gpio_enclk1; struct gpio_desc *gpio_enclk2; + struct clk *clk; + struct clk *clkout; + struct clk_hw hw; u8 buf[2] __aligned(IIO_DMA_MINALIGN); }; +#define to_adf4377_state(h) container_of(h, struct adf4377_state, hw) + static const char * const adf4377_muxout_modes[] = { [ADF4377_MUXOUT_HIGH_Z] = "high_z", [ADF4377_MUXOUT_LKDET] = "lock_detect", @@ -929,6 +936,110 @@ static int adf4377_freq_change(struct notifier_block *nb, unsigned long action, return NOTIFY_OK; } +static unsigned long adf4377_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct adf4377_state *st = to_adf4377_state(hw); + u64 freq; + int ret; + + ret = adf4377_get_freq(st, &freq); + if (ret) + return 0; + + return freq; +} + +static int adf4377_clk_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + return adf4377_set_freq(st, rate); +} + +static int adf4377_clk_prepare(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + return regmap_update_bits(st->regmap, 0x1a, ADF4377_001A_PD_CLKOUT1_MSK | + ADF4377_001A_PD_CLKOUT2_MSK, + FIELD_PREP(ADF4377_001A_PD_CLKOUT1_MSK, 0) | + FIELD_PREP(ADF4377_001A_PD_CLKOUT2_MSK, 0)); +} + +static void adf4377_clk_unprepare(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + + regmap_update_bits(st->regmap, 0x1a, ADF4377_001A_PD_CLKOUT1_MSK | + ADF4377_001A_PD_CLKOUT2_MSK, + FIELD_PREP(ADF4377_001A_PD_CLKOUT1_MSK, 1) | + FIELD_PREP(ADF4377_001A_PD_CLKOUT2_MSK, 1)); +} + +static int adf4377_clk_is_prepared(struct clk_hw *hw) +{ + struct adf4377_state *st = to_adf4377_state(hw); + unsigned int readval; + int ret; + + ret = regmap_read(st->regmap, 0x1a, &readval); + if (ret) + return ret; + + return !(readval & (ADF4377_001A_PD_CLKOUT1_MSK | ADF4377_001A_PD_CLKOUT2_MSK)); +} + +static const struct clk_ops adf4377_clk_ops = { + .recalc_rate = adf4377_clk_recalc_rate, + .set_rate = adf4377_clk_set_rate, + .prepare = adf4377_clk_prepare, + .unprepare = adf4377_clk_unprepare, + .is_prepared = adf4377_clk_is_prepared, +}; + +static int adf4377_clk_register(struct adf4377_state *st) +{ + struct spi_device *spi = st->spi; + struct device *dev = &spi->dev; + struct clk_init_data init; + struct clk_parent_data parent_data; + int ret; + + if (!device_property_present(dev, "#clock-cells")) + return 0; + + ret = device_property_read_string(dev, "clock-output-names", &init.name); + if (ret) { + init.name = devm_kasprintf(dev, GFP_KERNEL, "%pfw-clk", + dev_fwnode(dev)); + if (!init.name) + return -ENOMEM; + } + + parent_data.fw_name = "ref_in"; + + init.ops = &adf4377_clk_ops; + init.parent_data = &parent_data; + init.num_parents = 1; + init.flags = CLK_SET_RATE_PARENT; + + st->hw.init = &init; + ret = devm_clk_hw_register(dev, &st->hw); + if (ret) + return ret; + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &st->hw); + if (ret) + return ret; + + st->clkout = st->hw.clk; + + return 0; +} + static const struct adf4377_chip_info adf4377_chip_info = { .name = "adf4377", .has_gpio_enclk2 = true, @@ -958,8 +1069,6 @@ static int adf4377_probe(struct spi_device *spi) indio_dev->info = &adf4377_info; indio_dev->name = "adf4377"; - indio_dev->channels = adf4377_channels; - indio_dev->num_channels = ARRAY_SIZE(adf4377_channels); st->regmap = regmap; st->spi = spi; @@ -979,6 +1088,15 @@ static int adf4377_probe(struct spi_device *spi) if (ret) return ret; + ret = adf4377_clk_register(st); + if (ret) + return ret; + + if (!st->clkout) { + indio_dev->channels = adf4377_channels; + indio_dev->num_channels = ARRAY_SIZE(adf4377_channels); + } + return devm_iio_device_register(&spi->dev, indio_dev); } diff --git a/drivers/iio/gyro/adxrs290.c b/drivers/iio/gyro/adxrs290.c index 8fcb41f45baa..3efe385ebedc 100644 --- a/drivers/iio/gyro/adxrs290.c +++ b/drivers/iio/gyro/adxrs290.c @@ -597,7 +597,7 @@ static int adxrs290_probe_trigger(struct iio_dev *indio_dev) ret = devm_request_irq(&st->spi->dev, st->spi->irq, &iio_trigger_generic_data_rdy_poll, - IRQF_ONESHOT, "adxrs290_irq", st->dready_trig); + IRQF_NO_THREAD, "adxrs290_irq", st->dready_trig); if (ret < 0) return dev_err_probe(&st->spi->dev, ret, "request irq %d failed\n", st->spi->irq); diff --git a/drivers/iio/gyro/itg3200_buffer.c b/drivers/iio/gyro/itg3200_buffer.c index a624400a239c..cf97adfa9727 100644 --- a/drivers/iio/gyro/itg3200_buffer.c +++ b/drivers/iio/gyro/itg3200_buffer.c @@ -118,11 +118,9 @@ int itg3200_probe_trigger(struct iio_dev *indio_dev) if (!st->trig) return -ENOMEM; - ret = request_irq(st->i2c->irq, - &iio_trigger_generic_data_rdy_poll, - IRQF_TRIGGER_RISING, - "itg3200_data_rdy", - st->trig); + ret = request_irq(st->i2c->irq, &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "itg3200_data_rdy", st->trig); if (ret) goto error_free_trig; diff --git a/drivers/iio/gyro/itg3200_core.c b/drivers/iio/gyro/itg3200_core.c index cd8a2dae56cd..bfe95ec1abda 100644 --- a/drivers/iio/gyro/itg3200_core.c +++ b/drivers/iio/gyro/itg3200_core.c @@ -93,6 +93,8 @@ static int itg3200_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_RAW: reg = (u8)chan->address; ret = itg3200_read_reg_s16(indio_dev, reg, val); + if (ret) + return ret; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = 0; diff --git a/drivers/iio/gyro/mpu3050-core.c b/drivers/iio/gyro/mpu3050-core.c index 67ae7d1012bc..ee2fcd20545d 100644 --- a/drivers/iio/gyro/mpu3050-core.c +++ b/drivers/iio/gyro/mpu3050-core.c @@ -1162,10 +1162,8 @@ int mpu3050_common_probe(struct device *dev, mpu3050->regs[1].supply = mpu3050_reg_vlogic; ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(mpu3050->regs), mpu3050->regs); - if (ret) { - dev_err(dev, "Cannot get regulators\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "Cannot get regulators\n"); ret = mpu3050_power_up(mpu3050); if (ret) diff --git a/drivers/iio/health/afe4403.c b/drivers/iio/health/afe4403.c index 0e5a512e3bb8..d358f4d5e5da 100644 --- a/drivers/iio/health/afe4403.c +++ b/drivers/iio/health/afe4403.c @@ -540,11 +540,10 @@ static int afe4403_probe(struct spi_device *spi) return ret; } - ret = devm_request_threaded_irq(dev, afe->irq, - iio_trigger_generic_data_rdy_poll, - NULL, IRQF_ONESHOT, - AFE4403_DRIVER_NAME, - afe->trig); + ret = devm_request_irq(dev, afe->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, AFE4403_DRIVER_NAME, + afe->trig); if (ret) { dev_err(dev, "Unable to request IRQ\n"); return ret; diff --git a/drivers/iio/health/afe4404.c b/drivers/iio/health/afe4404.c index 768d794e574b..032da52a96d0 100644 --- a/drivers/iio/health/afe4404.c +++ b/drivers/iio/health/afe4404.c @@ -547,11 +547,10 @@ static int afe4404_probe(struct i2c_client *client) return ret; } - ret = devm_request_threaded_irq(dev, afe->irq, - iio_trigger_generic_data_rdy_poll, - NULL, IRQF_ONESHOT, - AFE4404_DRIVER_NAME, - afe->trig); + ret = devm_request_irq(dev, afe->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, AFE4404_DRIVER_NAME, + afe->trig); if (ret) { dev_err(dev, "Unable to request IRQ\n"); return ret; diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c index 3d441013893c..7dfdb5eb305e 100644 --- a/drivers/iio/health/max30100.c +++ b/drivers/iio/health/max30100.c @@ -417,13 +417,7 @@ static int max30100_read_raw(struct iio_dev *indio_dev, * Temperature reading can only be acquired while engine * is running */ - if (iio_device_claim_buffer_mode(indio_dev)) { - /* - * Replacing -EBUSY or other error code - * returned by iio_device_claim_buffer_mode() - * because user space may rely on the current - * one. - */ + if (!iio_device_try_claim_buffer_mode(indio_dev)) { ret = -EAGAIN; } else { ret = max30100_get_temp(data, val); diff --git a/drivers/iio/health/max30102.c b/drivers/iio/health/max30102.c index a48c0881a4c7..47da44efd68b 100644 --- a/drivers/iio/health/max30102.c +++ b/drivers/iio/health/max30102.c @@ -467,44 +467,29 @@ static int max30102_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long mask) { struct max30102_data *data = iio_priv(indio_dev); - int ret = -EINVAL; + int ret; switch (mask) { - case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_RAW: { /* * Temperature reading can only be acquired when not in * shutdown; leave shutdown briefly when buffer not running */ -any_mode_retry: - if (iio_device_claim_buffer_mode(indio_dev)) { - /* - * This one is a *bit* hacky. If we cannot claim buffer - * mode, then try direct mode so that we make sure - * things cannot concurrently change. And we just keep - * trying until we get one of the modes... - */ - if (!iio_device_claim_direct(indio_dev)) - goto any_mode_retry; - - ret = max30102_get_temp(data, val, true); - iio_device_release_direct(indio_dev); - } else { - ret = max30102_get_temp(data, val, false); - iio_device_release_buffer_mode(indio_dev); - } + IIO_DEV_GUARD_CURRENT_MODE(indio_dev); + + ret = max30102_get_temp(data, val, !iio_buffer_enabled(indio_dev)); if (ret) return ret; - ret = IIO_VAL_INT; - break; + return IIO_VAL_INT; + } case IIO_CHAN_INFO_SCALE: *val = 1000; /* 62.5 */ *val2 = 16; - ret = IIO_VAL_FRACTIONAL; - break; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; } - - return ret; } static const struct iio_info max30102_info = { diff --git a/drivers/iio/imu/bmi270/bmi270_i2c.c b/drivers/iio/imu/bmi270/bmi270_i2c.c index b909a421ad01..b92da4e0776f 100644 --- a/drivers/iio/imu/bmi270/bmi270_i2c.c +++ b/drivers/iio/imu/bmi270/bmi270_i2c.c @@ -37,6 +37,7 @@ static const struct i2c_device_id bmi270_i2c_id[] = { { "bmi270", (kernel_ulong_t)&bmi270_chip_info }, { } }; +MODULE_DEVICE_TABLE(i2c, bmi270_i2c_id); static const struct acpi_device_id bmi270_acpi_match[] = { /* GPD Win Mini, Aya Neo AIR Pro, OXP Mini Pro, etc. */ @@ -45,12 +46,14 @@ static const struct acpi_device_id bmi270_acpi_match[] = { { "BMI0260", (kernel_ulong_t)&bmi260_chip_info }, { } }; +MODULE_DEVICE_TABLE(acpi, bmi270_acpi_match); static const struct of_device_id bmi270_of_match[] = { { .compatible = "bosch,bmi260", .data = &bmi260_chip_info }, { .compatible = "bosch,bmi270", .data = &bmi270_chip_info }, { } }; +MODULE_DEVICE_TABLE(of, bmi270_of_match); static struct i2c_driver bmi270_i2c_driver = { .driver = { diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c index 30f6a9595eea..727b03d541a5 100644 --- a/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c +++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_temp.c @@ -59,10 +59,7 @@ int inv_icm42600_temp_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: - if (!iio_device_claim_direct(indio_dev)) - return -EBUSY; ret = inv_icm42600_temp_read(st, &temp); - iio_device_release_direct(indio_dev); if (ret) return ret; *val = temp; diff --git a/drivers/iio/imu/smi330/smi330_core.c b/drivers/iio/imu/smi330/smi330_core.c index 7564f12543e0..7ec5ae7f521a 100644 --- a/drivers/iio/imu/smi330/smi330_core.c +++ b/drivers/iio/imu/smi330/smi330_core.c @@ -67,10 +67,6 @@ #define SMI330_CHIP_ID 0x42 #define SMI330_SOFT_RESET_DELAY 2000 -/* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */ -#define smi330_field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1)) -#define smi330_field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask)) - #define SMI330_ACCEL_CHANNEL(_axis) { \ .type = IIO_ACCEL, \ .modified = 1, \ @@ -361,7 +357,7 @@ static int smi330_get_sensor_config(struct smi330_data *data, if (ret) return ret; - reg_val = smi330_field_get(attr->mask, reg_val); + reg_val = field_get(attr->mask, reg_val); if (attr->type == IIO_VAL_INT) { for (i = 0; i < attr->len; i++) { @@ -410,7 +406,7 @@ static int smi330_set_sensor_config(struct smi330_data *data, if (ret) return ret; - reg_val = smi330_field_prep(attr->mask, reg_val); + reg_val = field_prep(attr->mask, reg_val); ret = regmap_update_bits(data->regmap, reg, attr->mask, reg_val); if (ret) return ret; @@ -475,7 +471,6 @@ static int smi330_read_avail(struct iio_dev *indio_dev, *vals = smi330_average_attr.vals; *length = smi330_average_attr.len; *type = smi330_average_attr.type; - *type = IIO_VAL_INT; return IIO_AVAIL_LIST; case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: *vals = smi330_bandwidth_attr.vals; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index 6405a5367d76..07b1773c87bd 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -79,10 +79,11 @@ enum st_lsm6dsx_hw_id { #define ST_LSM6DSX_MAX_TAGGED_WORD_LEN ((32 / ST_LSM6DSX_TAGGED_SAMPLE_SIZE) \ * ST_LSM6DSX_TAGGED_SAMPLE_SIZE) #define ST_LSM6DSX_SHIFT_VAL(val, mask) (((val) << __ffs(mask)) & (mask)) +#define st_lsm6dsx_field_get(mask, reg) ((reg & mask) >> __ffs(mask)) -#define ST_LSM6DSX_CHANNEL_ACC(chan_type, addr, mod, scan_idx) \ +#define ST_LSM6DSX_CHANNEL_ACC(addr, mod, scan_idx, events) \ { \ - .type = chan_type, \ + .type = IIO_ACCEL, \ .address = addr, \ .modified = 1, \ .channel2 = mod, \ @@ -96,9 +97,9 @@ enum st_lsm6dsx_hw_id { .storagebits = 16, \ .endianness = IIO_LE, \ }, \ - .event_spec = &st_lsm6dsx_event, \ + .event_spec = events, \ + .num_event_specs = ARRAY_SIZE(events), \ .ext_info = st_lsm6dsx_ext_info, \ - .num_event_specs = 1, \ } #define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx) \ @@ -260,14 +261,31 @@ struct st_lsm6dsx_shub_settings { u8 pause; }; +enum st_lsm6dsx_event_id { + ST_LSM6DSX_EVENT_WAKEUP, + ST_LSM6DSX_EVENT_TAP, + ST_LSM6DSX_EVENT_MAX +}; + +struct st_lsm6dsx_event_src { + struct st_lsm6dsx_reg value; + struct st_lsm6dsx_reg x_value; + struct st_lsm6dsx_reg y_value; + struct st_lsm6dsx_reg z_value; + u8 enable_mask; + u8 enable_axis_reg; + u8 enable_x_mask; + u8 enable_y_mask; + u8 enable_z_mask; + struct st_lsm6dsx_reg status; + u8 status_x_mask; + u8 status_y_mask; + u8 status_z_mask; +}; + struct st_lsm6dsx_event_settings { struct st_lsm6dsx_reg enable_reg; - struct st_lsm6dsx_reg wakeup_reg; - u8 wakeup_src_reg; - u8 wakeup_src_status_mask; - u8 wakeup_src_z_mask; - u8 wakeup_src_y_mask; - u8 wakeup_src_x_mask; + struct st_lsm6dsx_event_src sources[ST_LSM6DSX_EVENT_MAX]; }; enum st_lsm6dsx_sensor_id { @@ -353,8 +371,8 @@ struct st_lsm6dsx_settings { struct { struct st_lsm6dsx_reg irq1; struct st_lsm6dsx_reg irq2; - struct st_lsm6dsx_reg irq1_func; - struct st_lsm6dsx_reg irq2_func; + u8 irq1_func; + u8 irq2_func; struct st_lsm6dsx_reg lir; struct st_lsm6dsx_reg clear_on_read; struct st_lsm6dsx_reg hla; @@ -430,7 +448,6 @@ struct st_lsm6dsx_sensor { * @sip: Total number of samples (acc/gyro/ts) in a given pattern. * @buff: Device read buffer. * @irq_routing: pointer to interrupt routing configuration. - * @event_threshold: wakeup event threshold. * @enable_event: enabled event bitmask. * @iio_devs: Pointers to acc/gyro iio_dev instances. * @settings: Pointer to the specific sensor settings in use. @@ -453,9 +470,8 @@ struct st_lsm6dsx_hw { u8 ts_sip; u8 sip; - const struct st_lsm6dsx_reg *irq_routing; - u8 event_threshold; - u8 enable_event; + u8 irq_routing; + u8 enable_event[ST_LSM6DSX_EVENT_MAX]; u8 *buff; @@ -471,13 +487,6 @@ struct st_lsm6dsx_hw { } scan[ST_LSM6DSX_ID_MAX]; }; -static __maybe_unused const struct iio_event_spec st_lsm6dsx_event = { - .type = IIO_EV_TYPE_THRESH, - .dir = IIO_EV_DIR_EITHER, - .mask_separate = BIT(IIO_EV_INFO_VALUE) | - BIT(IIO_EV_INFO_ENABLE) -}; - static __maybe_unused const unsigned long st_lsm6dsx_available_scan_masks[] = { 0x7, 0x0, }; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index dc78227952a7..450cb5b47346 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -94,10 +94,41 @@ #define ST_LSM6DSX_REG_WHOAMI_ADDR 0x0f +static const struct iio_event_spec st_lsm6dsx_ev_motion[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_event_spec st_lsm6dsx_ev_motion_tap[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_SINGLETAP, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = { - ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x28, IIO_MOD_X, 0), - ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x2a, IIO_MOD_Y, 1), - ST_LSM6DSX_CHANNEL_ACC(IIO_ACCEL, 0x2c, IIO_MOD_Z, 2), + ST_LSM6DSX_CHANNEL_ACC(0x28, IIO_MOD_X, 0, st_lsm6dsx_ev_motion), + ST_LSM6DSX_CHANNEL_ACC(0x2a, IIO_MOD_Y, 1, st_lsm6dsx_ev_motion), + ST_LSM6DSX_CHANNEL_ACC(0x2c, IIO_MOD_Z, 2, st_lsm6dsx_ev_motion), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec st_lsm6dsx_acc_tap_channels[] = { + ST_LSM6DSX_CHANNEL_ACC(0x28, IIO_MOD_X, 0, st_lsm6dsx_ev_motion_tap), + ST_LSM6DSX_CHANNEL_ACC(0x2a, IIO_MOD_Y, 1, st_lsm6dsx_ev_motion_tap), + ST_LSM6DSX_CHANNEL_ACC(0x2c, IIO_MOD_Z, 2, st_lsm6dsx_ev_motion_tap), IIO_CHAN_SOFT_TIMESTAMP(3), }; @@ -326,14 +357,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -386,15 +411,22 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, }, .event_settings = { - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -492,14 +524,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -552,15 +578,22 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, }, .event_settings = { - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -688,14 +721,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -789,15 +816,22 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -937,14 +971,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x56, .mask = BIT(6), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -1028,15 +1056,22 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5b, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1152,14 +1187,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x56, .mask = BIT(6), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x12, .mask = BIT(5), @@ -1211,15 +1240,22 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x58, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5B, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x1b, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, }, - .wakeup_src_reg = 0x1b, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1248,8 +1284,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { }, .channels = { [ST_LSM6DSX_ID_ACC] = { - .chan = st_lsm6dsx_acc_channels, - .len = ARRAY_SIZE(st_lsm6dsx_acc_channels), + .chan = st_lsm6dsx_acc_tap_channels, + .len = ARRAY_SIZE(st_lsm6dsx_acc_tap_channels), }, [ST_LSM6DSX_ID_GYRO] = { .chan = st_lsm6dsx_gyro_channels, @@ -1329,14 +1365,8 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x56, .mask = BIT(0), }, - .irq1_func = { - .addr = 0x5e, - .mask = BIT(5), - }, - .irq2_func = { - .addr = 0x5f, - .mask = BIT(5), - }, + .irq1_func = 0x5e, + .irq2_func = 0x5f, .hla = { .addr = 0x03, .mask = BIT(4), @@ -1419,15 +1449,48 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = { .addr = 0x50, .mask = BIT(7), }, - .wakeup_reg = { - .addr = 0x5b, - .mask = GENMASK(5, 0), + .sources = { + [ST_LSM6DSX_EVENT_WAKEUP] = { + .value = { + .addr = 0x5b, + .mask = GENMASK(5, 0), + }, + .enable_mask = BIT(5), + .status = { + .addr = 0x45, + .mask = BIT(3), + }, + .status_z_mask = BIT(0), + .status_y_mask = BIT(1), + .status_x_mask = BIT(2), + }, + [ST_LSM6DSX_EVENT_TAP] = { + .x_value = { + .addr = 0x57, + .mask = GENMASK(4, 0), + }, + .y_value = { + .addr = 0x58, + .mask = GENMASK(4, 0), + }, + .z_value = { + .addr = 0x59, + .mask = GENMASK(4, 0), + }, + .enable_mask = BIT(6), + .enable_axis_reg = 0x56, + .enable_x_mask = BIT(3), + .enable_y_mask = BIT(2), + .enable_z_mask = BIT(1), + .status = { + .addr = 0x46, + .mask = BIT(5), + }, + .status_x_mask = BIT(2), + .status_y_mask = BIT(1), + .status_z_mask = BIT(0), + }, }, - .wakeup_src_reg = 0x45, - .wakeup_src_status_mask = BIT(3), - .wakeup_src_z_mask = BIT(0), - .wakeup_src_y_mask = BIT(1), - .wakeup_src_x_mask = BIT(2), }, }, { @@ -1754,20 +1817,25 @@ __st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, } static int -st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor, bool enable) +st_lsm6dsx_check_events(struct st_lsm6dsx_sensor *sensor) { struct st_lsm6dsx_hw *hw = sensor->hw; + int event; - if (sensor->id == ST_LSM6DSX_ID_GYRO || enable) + if (sensor->id != ST_LSM6DSX_ID_ACC) return 0; - return hw->enable_event; + for (event = 0; event < ST_LSM6DSX_EVENT_MAX; event++) { + if (hw->enable_event[event]) + return true; + } + return false; } int st_lsm6dsx_sensor_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable) { - if (st_lsm6dsx_check_events(sensor, enable)) + if (st_lsm6dsx_check_events(sensor)) return 0; return __st_lsm6dsx_sensor_set_enable(sensor, enable); @@ -1795,11 +1863,9 @@ static int st_lsm6dsx_read_oneshot(struct st_lsm6dsx_sensor *sensor, if (err < 0) return err; - if (!hw->enable_event) { - err = st_lsm6dsx_sensor_set_enable(sensor, false); - if (err < 0) - return err; - } + err = st_lsm6dsx_sensor_set_enable(sensor, false); + if (err < 0) + return err; *val = (s16)le16_to_cpu(data); @@ -1876,28 +1942,106 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev, return err; } -static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, bool state) +static int st_lsm6dsx_event_setup(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id event, int axis, + bool state) { - const struct st_lsm6dsx_reg *reg; + const struct st_lsm6dsx_event_src *src; unsigned int data; int err; + u8 old_enable, new_enable; - if (!hw->settings->irq_config.irq1_func.addr) + if (!hw->irq_routing) return -ENOTSUPP; - reg = &hw->settings->event_settings.enable_reg; - if (reg->addr) { - data = ST_LSM6DSX_SHIFT_VAL(state, reg->mask); - err = st_lsm6dsx_update_bits_locked(hw, reg->addr, - reg->mask, data); - if (err < 0) - return err; + /* Enable/disable event interrupt */ + src = &hw->settings->event_settings.sources[event]; + if (src->enable_axis_reg) { + u8 enable_mask; + + switch (axis) { + case IIO_MOD_X: + enable_mask = src->enable_x_mask; + break; + case IIO_MOD_Y: + enable_mask = src->enable_y_mask; + break; + case IIO_MOD_Z: + enable_mask = src->enable_z_mask; + break; + default: + enable_mask = 0; + } + if (enable_mask) { + data = ST_LSM6DSX_SHIFT_VAL(state, enable_mask); + err = st_lsm6dsx_update_bits_locked(hw, + src->enable_axis_reg, + enable_mask, data); + if (err < 0) + return err; + } + } + + /* + * If the set of axes for which the event source is enabled does not + * change from empty to non-empty or vice versa, there is nothing else + * to do. + */ + old_enable = hw->enable_event[event]; + new_enable = state ? (old_enable | BIT(axis)) : + (old_enable & ~BIT(axis)); + if (!old_enable == !new_enable) + return 0; + + data = ST_LSM6DSX_SHIFT_VAL(state, src->enable_mask); + return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing, + src->enable_mask, data); +} + +static enum st_lsm6dsx_event_id +st_lsm6dsx_get_event_id(enum iio_event_type type) +{ + switch (type) { + case IIO_EV_TYPE_THRESH: + return ST_LSM6DSX_EVENT_WAKEUP; + case IIO_EV_TYPE_GESTURE: + return ST_LSM6DSX_EVENT_TAP; + default: + return ST_LSM6DSX_EVENT_MAX; + } +} + +static const struct st_lsm6dsx_reg * +st_lsm6dsx_get_event_reg(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id event, + const struct iio_chan_spec *chan) +{ + const struct st_lsm6dsx_event_src *src; + const struct st_lsm6dsx_reg *reg; + + src = &hw->settings->event_settings.sources[event]; + switch (chan->channel2) { + case IIO_MOD_X: + reg = &src->x_value; + break; + case IIO_MOD_Y: + reg = &src->y_value; + break; + case IIO_MOD_Z: + reg = &src->z_value; + break; + default: + return NULL; } + if (reg->addr) + return reg; - /* Enable wakeup interrupt */ - data = ST_LSM6DSX_SHIFT_VAL(state, hw->irq_routing->mask); - return st_lsm6dsx_update_bits_locked(hw, hw->irq_routing->addr, - hw->irq_routing->mask, data); + /* + * The sensor does not support configuring this event source on a per + * axis basis: return the register to configure the event source for all + * axes. + */ + return &src->value; } static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, @@ -1907,14 +2051,26 @@ static int st_lsm6dsx_read_event(struct iio_dev *iio_dev, enum iio_event_info info, int *val, int *val2) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *reg; + u8 data; + int err; + + if (event == ST_LSM6DSX_EVENT_MAX) + return -EINVAL; - if (type != IIO_EV_TYPE_THRESH) + reg = st_lsm6dsx_get_event_reg(hw, event, chan); + if (!reg) return -EINVAL; + err = st_lsm6dsx_read_locked(hw, reg->addr, &data, sizeof(data)); + if (err < 0) + return err; + *val2 = 0; - *val = hw->event_threshold; + *val = st_lsm6dsx_field_get(reg->mask, data); return IIO_VAL_INT; } @@ -1927,27 +2083,29 @@ st_lsm6dsx_write_event(struct iio_dev *iio_dev, enum iio_event_info info, int val, int val2) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; const struct st_lsm6dsx_reg *reg; unsigned int data; int err; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; if (val < 0 || val > 31) return -EINVAL; - reg = &hw->settings->event_settings.wakeup_reg; + reg = st_lsm6dsx_get_event_reg(hw, event, chan); + if (!reg) + return -EINVAL; + data = ST_LSM6DSX_SHIFT_VAL(val, reg->mask); err = st_lsm6dsx_update_bits_locked(hw, reg->addr, reg->mask, data); if (err < 0) return -EINVAL; - hw->event_threshold = val; - return 0; } @@ -1957,13 +2115,56 @@ st_lsm6dsx_read_event_config(struct iio_dev *iio_dev, enum iio_event_type type, enum iio_event_direction dir) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; - return !!(hw->enable_event & BIT(chan->channel2)); + return !!(hw->enable_event[event] & BIT(chan->channel2)); +} + +/** + * st_lsm6dsx_check_other_events - Check for enabled sensor events. + * @hw: Sensor hardware instance. + * @curr: Current event type. + * + * Return: whether any events other than @curr are enabled. + */ +static bool st_lsm6dsx_check_other_events(struct st_lsm6dsx_hw *hw, + enum st_lsm6dsx_event_id curr) +{ + enum st_lsm6dsx_event_id other; + + for (other = 0; other < ST_LSM6DSX_EVENT_MAX; other++) { + if (other != curr && hw->enable_event[other]) + return true; + } + + return false; +} + +static int st_lsm6dsx_events_enable(struct st_lsm6dsx_sensor *sensor, + bool state) +{ + struct st_lsm6dsx_hw *hw = sensor->hw; + const struct st_lsm6dsx_reg *reg; + + reg = &hw->settings->event_settings.enable_reg; + if (reg->addr) { + int err; + + err = regmap_update_bits(hw->regmap, reg->addr, reg->mask, + ST_LSM6DSX_SHIFT_VAL(state, reg->mask)); + if (err) + return err; + } + + if (state || !(hw->fifo_mask & BIT(sensor->id))) + return __st_lsm6dsx_sensor_set_enable(sensor, state); + + return 0; } static int @@ -1972,45 +2173,38 @@ st_lsm6dsx_write_event_config(struct iio_dev *iio_dev, enum iio_event_type type, enum iio_event_direction dir, bool state) { + enum st_lsm6dsx_event_id event = st_lsm6dsx_get_event_id(type); struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); struct st_lsm6dsx_hw *hw = sensor->hw; u8 enable_event; int err; - if (type != IIO_EV_TYPE_THRESH) + if (event == ST_LSM6DSX_EVENT_MAX) return -EINVAL; - if (state) { - enable_event = hw->enable_event | BIT(chan->channel2); - - /* do not enable events if they are already enabled */ - if (hw->enable_event) - goto out; - } else { - enable_event = hw->enable_event & ~BIT(chan->channel2); - - /* only turn off sensor if no events is enabled */ - if (enable_event) - goto out; - } + if (state) + enable_event = hw->enable_event[event] | BIT(chan->channel2); + else + enable_event = hw->enable_event[event] & ~BIT(chan->channel2); /* stop here if no changes have been made */ - if (hw->enable_event == enable_event) + if (hw->enable_event[event] == enable_event) return 0; - err = st_lsm6dsx_event_setup(hw, state); + err = st_lsm6dsx_event_setup(hw, event, chan->channel2, state); if (err < 0) return err; mutex_lock(&hw->conf_lock); - if (enable_event || !(hw->fifo_mask & BIT(sensor->id))) - err = __st_lsm6dsx_sensor_set_enable(sensor, state); + if (enable_event) + err = st_lsm6dsx_events_enable(sensor, true); + else if (!st_lsm6dsx_check_other_events(hw, event)) + err = st_lsm6dsx_events_enable(sensor, false); mutex_unlock(&hw->conf_lock); if (err < 0) return err; -out: - hw->enable_event = enable_event; + hw->enable_event[event] = enable_event; return 0; } @@ -2151,11 +2345,11 @@ st_lsm6dsx_get_drdy_reg(struct st_lsm6dsx_hw *hw, switch (drdy_pin) { case 1: - hw->irq_routing = &hw->settings->irq_config.irq1_func; + hw->irq_routing = hw->settings->irq_config.irq1_func; *drdy_reg = &hw->settings->irq_config.irq1; break; case 2: - hw->irq_routing = &hw->settings->irq_config.irq2_func; + hw->irq_routing = hw->settings->irq_config.irq2_func; *drdy_reg = &hw->settings->irq_config.irq2; break; default: @@ -2414,53 +2608,70 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw, } static bool -st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) +st_lsm6dsx_report_events(struct st_lsm6dsx_hw *hw, enum st_lsm6dsx_event_id id, + enum iio_event_type type, enum iio_event_direction dir) { const struct st_lsm6dsx_event_settings *event_settings; + const struct st_lsm6dsx_event_src *src; int err, data; s64 timestamp; - if (!hw->enable_event) + if (!hw->enable_event[id]) return false; event_settings = &hw->settings->event_settings; - err = st_lsm6dsx_read_locked(hw, event_settings->wakeup_src_reg, + src = &event_settings->sources[id]; + err = st_lsm6dsx_read_locked(hw, src->status.addr, &data, sizeof(data)); if (err < 0) return false; timestamp = iio_get_time_ns(hw->iio_devs[ST_LSM6DSX_ID_ACC]); - if ((data & hw->settings->event_settings.wakeup_src_z_mask) && - (hw->enable_event & BIT(IIO_MOD_Z))) + if ((data & src->status_z_mask) && + (hw->enable_event[id] & BIT(IIO_MOD_Z))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_Z, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_EITHER), + type, + dir), timestamp); - if ((data & hw->settings->event_settings.wakeup_src_y_mask) && - (hw->enable_event & BIT(IIO_MOD_Y))) + if ((data & src->status_y_mask) && + (hw->enable_event[id] & BIT(IIO_MOD_Y))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_Y, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_EITHER), + type, + dir), timestamp); - if ((data & hw->settings->event_settings.wakeup_src_x_mask) && - (hw->enable_event & BIT(IIO_MOD_X))) + if ((data & src->status_x_mask) && + (hw->enable_event[id] & BIT(IIO_MOD_X))) iio_push_event(hw->iio_devs[ST_LSM6DSX_ID_ACC], IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, - IIO_EV_TYPE_THRESH, - IIO_EV_DIR_EITHER), + type, + dir), timestamp); - return data & event_settings->wakeup_src_status_mask; + return data & src->status.mask; +} + +static bool st_lsm6dsx_report_motion_event(struct st_lsm6dsx_hw *hw) +{ + bool events_found; + + events_found = st_lsm6dsx_report_events(hw, ST_LSM6DSX_EVENT_WAKEUP, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER); + events_found |= st_lsm6dsx_report_events(hw, ST_LSM6DSX_EVENT_TAP, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_SINGLETAP); + + return events_found; } static irqreturn_t st_lsm6dsx_handler_thread(int irq, void *private) @@ -2749,7 +2960,7 @@ static int st_lsm6dsx_suspend(struct device *dev) continue; if (device_may_wakeup(dev) && - sensor->id == ST_LSM6DSX_ID_ACC && hw->enable_event) { + st_lsm6dsx_check_events(sensor)) { /* Enable wake from IRQ */ enable_irq_wake(hw->irq); continue; @@ -2780,7 +2991,7 @@ static int st_lsm6dsx_resume(struct device *dev) sensor = iio_priv(hw->iio_devs[i]); if (device_may_wakeup(dev) && - sensor->id == ST_LSM6DSX_ID_ACC && hw->enable_event) + st_lsm6dsx_check_events(sensor)) disable_irq_wake(hw->irq); if (!(hw->suspend_mask & BIT(sensor->id))) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 117ffad4f376..819bfb9c289e 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -2174,88 +2174,34 @@ int __devm_iio_device_register(struct device *dev, struct iio_dev *indio_dev, EXPORT_SYMBOL_GPL(__devm_iio_device_register); /** - * __iio_device_claim_direct - Keep device in direct mode - * @indio_dev: the iio_dev associated with the device + * __iio_dev_mode_lock() - Locks the current IIO device mode + * @indio_dev: the iio_dev associated with the device * - * If the device is in direct mode it is guaranteed to stay - * that way until __iio_device_release_direct() is called. + * If the device is either in direct or buffer mode, it's guaranteed to stay + * that way until __iio_dev_mode_unlock() is called. * - * Use with __iio_device_release_direct(). + * This function is not meant to be used directly by drivers to protect internal + * state; a driver should have it's own mechanisms for that matter. * - * Drivers should only call iio_device_claim_direct(). - * - * Returns: true on success, false on failure. - */ -bool __iio_device_claim_direct(struct iio_dev *indio_dev) -{ - struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); - - mutex_lock(&iio_dev_opaque->mlock); - - if (iio_buffer_enabled(indio_dev)) { - mutex_unlock(&iio_dev_opaque->mlock); - return false; - } - return true; -} -EXPORT_SYMBOL_GPL(__iio_device_claim_direct); - -/** - * __iio_device_release_direct - releases claim on direct mode - * @indio_dev: the iio_dev associated with the device - * - * Release the claim. Device is no longer guaranteed to stay - * in direct mode. - * - * Drivers should only call iio_device_release_direct(). - * - * Use with __iio_device_claim_direct() + * There are very few cases where a driver actually needs to lock the current + * mode unconditionally. It's recommended to use iio_device_claim_direct() or + * iio_device_try_claim_buffer_mode() pairs or related helpers instead. */ -void __iio_device_release_direct(struct iio_dev *indio_dev) +void __iio_dev_mode_lock(struct iio_dev *indio_dev) { - mutex_unlock(&to_iio_dev_opaque(indio_dev)->mlock); + mutex_lock(&to_iio_dev_opaque(indio_dev)->mlock); } -EXPORT_SYMBOL_GPL(__iio_device_release_direct); +EXPORT_SYMBOL_GPL(__iio_dev_mode_lock); /** - * iio_device_claim_buffer_mode - Keep device in buffer mode - * @indio_dev: the iio_dev associated with the device - * - * If the device is in buffer mode it is guaranteed to stay - * that way until iio_device_release_buffer_mode() is called. - * - * Use with iio_device_release_buffer_mode(). - * - * Returns: 0 on success, -EBUSY on failure. - */ -int iio_device_claim_buffer_mode(struct iio_dev *indio_dev) -{ - struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); - - mutex_lock(&iio_dev_opaque->mlock); - - if (iio_buffer_enabled(indio_dev)) - return 0; - - mutex_unlock(&iio_dev_opaque->mlock); - return -EBUSY; -} -EXPORT_SYMBOL_GPL(iio_device_claim_buffer_mode); - -/** - * iio_device_release_buffer_mode - releases claim on buffer mode - * @indio_dev: the iio_dev associated with the device - * - * Release the claim. Device is no longer guaranteed to stay - * in buffer mode. - * - * Use with iio_device_claim_buffer_mode(). + * __iio_dev_mode_unlock() - Unlocks the current IIO device mode + * @indio_dev: the iio_dev associated with the device */ -void iio_device_release_buffer_mode(struct iio_dev *indio_dev) +void __iio_dev_mode_unlock(struct iio_dev *indio_dev) { mutex_unlock(&to_iio_dev_opaque(indio_dev)->mlock); } -EXPORT_SYMBOL_GPL(iio_device_release_buffer_mode); +EXPORT_SYMBOL_GPL(__iio_dev_mode_unlock); /** * iio_device_get_current_mode() - helper function providing read-only access to diff --git a/drivers/iio/industrialio-sw-device.c b/drivers/iio/industrialio-sw-device.c index cdaf30a3f233..3582524de0b8 100644 --- a/drivers/iio/industrialio-sw-device.c +++ b/drivers/iio/industrialio-sw-device.c @@ -148,7 +148,7 @@ static void device_drop_group(struct config_group *group, config_item_put(item); } -static struct configfs_group_operations device_ops = { +static const struct configfs_group_operations device_ops = { .make_group = &device_make_group, .drop_item = &device_drop_group, }; diff --git a/drivers/iio/industrialio-sw-trigger.c b/drivers/iio/industrialio-sw-trigger.c index d86a3305d9e8..334b6b10a784 100644 --- a/drivers/iio/industrialio-sw-trigger.c +++ b/drivers/iio/industrialio-sw-trigger.c @@ -152,7 +152,7 @@ static void trigger_drop_group(struct config_group *group, config_item_put(item); } -static struct configfs_group_operations trigger_ops = { +static const struct configfs_group_operations trigger_ops = { .make_group = &trigger_make_group, .drop_item = &trigger_drop_group, }; diff --git a/drivers/iio/light/isl29018.c b/drivers/iio/light/isl29018.c index 1b4c18423048..b6ab726d1dae 100644 --- a/drivers/iio/light/isl29018.c +++ b/drivers/iio/light/isl29018.c @@ -273,9 +273,9 @@ static ssize_t in_illuminance_scale_available_show mutex_lock(&chip->lock); for (i = 0; i < ARRAY_SIZE(isl29018_scales[chip->int_time]); ++i) - len += sprintf(buf + len, "%d.%06d ", - isl29018_scales[chip->int_time][i].scale, - isl29018_scales[chip->int_time][i].uscale); + len += sysfs_emit_at(buf, len, "%d.%06d ", + isl29018_scales[chip->int_time][i].scale, + isl29018_scales[chip->int_time][i].uscale); mutex_unlock(&chip->lock); buf[len - 1] = '\n'; @@ -293,8 +293,8 @@ static ssize_t in_illuminance_integration_time_available_show int len = 0; for (i = 0; i < ARRAY_SIZE(isl29018_int_utimes[chip->type]); ++i) - len += sprintf(buf + len, "0.%06d ", - isl29018_int_utimes[chip->type][i]); + len += sysfs_emit_at(buf, len, "0.%06d ", + isl29018_int_utimes[chip->type][i]); buf[len - 1] = '\n'; @@ -330,7 +330,7 @@ static ssize_t proximity_on_chip_ambient_infrared_suppression_show * Return the "proximity scheme" i.e. if the chip does on chip * infrared suppression (1 means perform on chip suppression) */ - return sprintf(buf, "%d\n", chip->prox_scheme); + return sysfs_emit(buf, "%d\n", chip->prox_scheme); } static ssize_t proximity_on_chip_ambient_infrared_suppression_store diff --git a/drivers/iio/light/opt4060.c b/drivers/iio/light/opt4060.c index 981c704e7df5..d6e915ab355d 100644 --- a/drivers/iio/light/opt4060.c +++ b/drivers/iio/light/opt4060.c @@ -302,41 +302,23 @@ static int opt4060_set_driver_state(struct iio_dev *indio_dev, bool continuous_irq) { struct opt4060_chip *chip = iio_priv(indio_dev); - int ret = 0; -any_mode_retry: - if (iio_device_claim_buffer_mode(indio_dev)) { - /* - * This one is a *bit* hacky. If we cannot claim buffer mode, - * then try direct mode so that we make sure things cannot - * concurrently change. And we just keep trying until we get one - * of the modes... - */ - if (!iio_device_claim_direct(indio_dev)) - goto any_mode_retry; - /* - * This path means that we managed to claim direct mode. In - * this case the buffer isn't enabled and it's okay to leave - * continuous mode for sampling and/or irq. - */ - ret = opt4060_set_state_common(chip, continuous_sampling, - continuous_irq); - iio_device_release_direct(indio_dev); - return ret; - } else { - /* - * This path means that we managed to claim buffer mode. In - * this case the buffer is enabled and irq and sampling must go - * to or remain continuous, but only if the trigger is from this - * device. - */ - if (!iio_trigger_validate_own_device(indio_dev->trig, indio_dev)) - ret = opt4060_set_state_common(chip, true, true); - else - ret = opt4060_set_state_common(chip, continuous_sampling, - continuous_irq); - iio_device_release_buffer_mode(indio_dev); - } - return ret; + + IIO_DEV_GUARD_CURRENT_MODE(indio_dev); + + /* + * If we manage to claim buffer mode and we are using our own trigger, + * IRQ and sampling must go to or remain continuous. + */ + if (iio_buffer_enabled(indio_dev) && + iio_trigger_validate_own_device(indio_dev->trig, indio_dev)) + return opt4060_set_state_common(chip, true, true); + + /* + * This path means that we managed to claim direct mode. In this case + * the buffer isn't enabled and it's okay to leave continuous mode for + * sampling and/or irq. + */ + return opt4060_set_state_common(chip, continuous_sampling, continuous_irq); } /* diff --git a/drivers/iio/light/si1145.c b/drivers/iio/light/si1145.c index f8eb251eca8d..ef0abc4499b7 100644 --- a/drivers/iio/light/si1145.c +++ b/drivers/iio/light/si1145.c @@ -1248,7 +1248,7 @@ static int si1145_probe_trigger(struct iio_dev *indio_dev) ret = devm_request_irq(&client->dev, client->irq, iio_trigger_generic_data_rdy_poll, - IRQF_TRIGGER_FALLING, + IRQF_TRIGGER_FALLING | IRQF_NO_THREAD, "si1145_irq", trig); if (ret < 0) { diff --git a/drivers/iio/light/vcnl4000.c b/drivers/iio/light/vcnl4000.c index 4dbb2294a843..a36c23813679 100644 --- a/drivers/iio/light/vcnl4000.c +++ b/drivers/iio/light/vcnl4000.c @@ -1078,20 +1078,17 @@ static int vcnl4010_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: - case IIO_CHAN_INFO_SCALE: - if (!iio_device_claim_direct(indio_dev)) + case IIO_CHAN_INFO_SCALE: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; /* Protect against event capture. */ - if (vcnl4010_is_in_periodic_mode(data)) { - ret = -EBUSY; - } else { - ret = vcnl4000_read_raw(indio_dev, chan, val, val2, - mask); - } + if (vcnl4010_is_in_periodic_mode(data)) + return -EBUSY; - iio_device_release_direct(indio_dev); - return ret; + return vcnl4000_read_raw(indio_dev, chan, val, val2, mask); + } case IIO_CHAN_INFO_SAMP_FREQ: switch (chan->type) { case IIO_PROXIMITY: @@ -1148,36 +1145,27 @@ static int vcnl4010_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { - int ret; struct vcnl4000_data *data = iio_priv(indio_dev); - if (!iio_device_claim_direct(indio_dev)) + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; /* Protect against event capture. */ - if (vcnl4010_is_in_periodic_mode(data)) { - ret = -EBUSY; - goto end; - } + if (vcnl4010_is_in_periodic_mode(data)) + return -EBUSY; switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: switch (chan->type) { case IIO_PROXIMITY: - ret = vcnl4010_write_proxy_samp_freq(data, val, val2); - goto end; + return vcnl4010_write_proxy_samp_freq(data, val, val2); default: - ret = -EINVAL; - goto end; + return -EINVAL; } default: - ret = -EINVAL; - goto end; + return -EINVAL; } - -end: - iio_device_release_direct(indio_dev); - return ret; } static int vcnl4010_read_event(struct iio_dev *indio_dev, @@ -1438,14 +1426,13 @@ static int vcnl4010_config_threshold_disable(struct vcnl4000_data *data) static int vcnl4010_config_threshold(struct iio_dev *indio_dev, bool state) { struct vcnl4000_data *data = iio_priv(indio_dev); - int ret; if (state) { - if (!iio_device_claim_direct(indio_dev)) + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; - ret = vcnl4010_config_threshold_enable(data); - iio_device_release_direct(indio_dev); - return ret; + + return vcnl4010_config_threshold_enable(data); } else { return vcnl4010_config_threshold_disable(data); } diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index 81b812a29044..9345fb6d5317 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -139,6 +139,19 @@ config MMC35240 To compile this driver as a module, choose M here: the module will be called mmc35240. +config MMC5633 + tristate "MEMSIC MMC5633 3-axis magnetic sensor" + select REGMAP_I2C + select REGMAP_I3C if I3C + depends on I2C + depends on I3C || !I3C + help + Say yes here to build support for the MEMSIC MMC5633 3-axis + magnetic sensor. + + To compile this driver as a module, choose M here: the module + will be called mmc5633 + config IIO_ST_MAGN_3AXIS tristate "STMicroelectronics magnetometers 3-Axis Driver" depends on (I2C || SPI_MASTER) && SYSFS diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile index dfe970fcacb8..5bd227f8c120 100644 --- a/drivers/iio/magnetometer/Makefile +++ b/drivers/iio/magnetometer/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_BMC150_MAGN_SPI) += bmc150_magn_spi.o obj-$(CONFIG_MAG3110) += mag3110.o obj-$(CONFIG_HID_SENSOR_MAGNETOMETER_3D) += hid-sensor-magn-3d.o obj-$(CONFIG_MMC35240) += mmc35240.o +obj-$(CONFIG_MMC5633) += mmc5633.o obj-$(CONFIG_IIO_ST_MAGN_3AXIS) += st_magn.o st_magn-y := st_magn_core.o diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index 3fd0171e5d69..d30315ad85de 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -581,7 +581,7 @@ static int ak8975_setup_irq(struct ak8975_data *data) irq = gpiod_to_irq(data->eoc_gpiod); rc = devm_request_irq(&client->dev, irq, ak8975_irq_handler, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, + IRQF_TRIGGER_RISING, dev_name(&client->dev), data); if (rc < 0) { dev_err(&client->dev, "irq %d request failed: %d\n", irq, rc); diff --git a/drivers/iio/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c index 6a73f6e2f1f0..a022e1805dff 100644 --- a/drivers/iio/magnetometer/bmc150_magn.c +++ b/drivers/iio/magnetometer/bmc150_magn.c @@ -906,12 +906,9 @@ int bmc150_magn_probe(struct device *dev, struct regmap *regmap, goto err_poweroff; } - ret = request_threaded_irq(irq, - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - "bmc150_magn_event", - data->dready_trig); + ret = request_irq(irq, iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + "bmc150_magn_event", data->dready_trig); if (ret < 0) { dev_err(dev, "request irq %d failed\n", irq); goto err_trigger_unregister; diff --git a/drivers/iio/magnetometer/mmc5633.c b/drivers/iio/magnetometer/mmc5633.c new file mode 100644 index 000000000000..9d8e27486963 --- /dev/null +++ b/drivers/iio/magnetometer/mmc5633.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MMC5633 - MEMSIC 3-axis Magnetic Sensor + * + * Copyright (c) 2015, Intel Corporation. + * Copyright (c) 2025, NXP + * + * IIO driver for MMC5633, base on mmc35240.c + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dev_printk.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/i3c/device.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/init.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/regmap.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/unaligned.h> + +#define MMC5633_REG_XOUT0 0x00 +#define MMC5633_REG_XOUT1 0x01 +#define MMC5633_REG_YOUT0 0x02 +#define MMC5633_REG_YOUT1 0x03 +#define MMC5633_REG_ZOUT0 0x04 +#define MMC5633_REG_ZOUT1 0x05 +#define MMC5633_REG_XOUT2 0x06 +#define MMC5633_REG_YOUT2 0x07 +#define MMC5633_REG_ZOUT2 0x08 +#define MMC5633_REG_TOUT 0x09 + +#define MMC5633_REG_STATUS1 0x18 +#define MMC5633_REG_STATUS0 0x19 +#define MMC5633_REG_CTRL0 0x1b +#define MMC5633_REG_CTRL1 0x1c +#define MMC5633_REG_CTRL2 0x1d + +#define MMC5633_REG_ID 0x39 + +#define MMC5633_STATUS1_MEAS_T_DONE_BIT BIT(7) +#define MMC5633_STATUS1_MEAS_M_DONE_BIT BIT(6) + +#define MMC5633_CTRL0_CMM_FREQ_EN BIT(7) +#define MMC5633_CTRL0_AUTO_ST_EN BIT(6) +#define MMC5633_CTRL0_AUTO_SR_EN BIT(5) +#define MMC5633_CTRL0_RESET BIT(4) +#define MMC5633_CTRL0_SET BIT(3) +#define MMC5633_CTRL0_MEAS_T BIT(1) +#define MMC5633_CTRL0_MEAS_M BIT(0) + +#define MMC5633_CTRL1_BW_MASK GENMASK(1, 0) + +#define MMC5633_WAIT_SET_RESET_US (1 * USEC_PER_MSEC) + +#define MMC5633_HDR_CTRL0_MEAS_M 0x01 +#define MMC5633_HDR_CTRL0_MEAS_T 0x03 +#define MMC5633_HDR_CTRL0_SET 0x05 +#define MMC5633_HDR_CTRL0_RESET 0x07 + +enum mmc5633_axis { + MMC5633_AXIS_X, + MMC5633_AXIS_Y, + MMC5633_AXIS_Z, + MMC5633_TEMPERATURE, +}; + +struct mmc5633_data { + struct regmap *regmap; + struct i3c_device *i3cdev; + struct mutex mutex; /* protect to finish one whole measurement */ +}; + +static int mmc5633_samp_freq[][2] = { + { 1, 200000 }, + { 2, 0 }, + { 3, 500000 }, + { 6, 600000 }, +}; + +#define MMC5633_CHANNEL(_axis) { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_ ## _axis, \ + .address = MMC5633_AXIS_ ## _axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mmc5633_channels[] = { + MMC5633_CHANNEL(X), + MMC5633_CHANNEL(Y), + MMC5633_CHANNEL(Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .address = MMC5633_TEMPERATURE, + }, +}; + +static int mmc5633_get_samp_freq_index(struct mmc5633_data *data, + int val, int val2) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mmc5633_samp_freq); i++) + if (mmc5633_samp_freq[i][0] == val && + mmc5633_samp_freq[i][1] == val2) + return i; + return -EINVAL; +} + +static int mmc5633_init(struct mmc5633_data *data) +{ + unsigned int reg_id; + int ret; + + ret = regmap_read(data->regmap, MMC5633_REG_ID, ®_id); + if (ret) + return dev_err_probe(regmap_get_device(data->regmap), ret, + "Error reading product id\n"); + + /* + * Make sure we restore sensor characteristics, by doing + * a SET/RESET sequence, the axis polarity being naturally + * aligned after RESET. + */ + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, MMC5633_CTRL0_SET); + if (ret) + return ret; + + /* + * Minimum time interval between SET or RESET to other operations is + * 1ms according to Operating Timing Diagram in datasheet. + */ + fsleep(MMC5633_WAIT_SET_RESET_US); + + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, MMC5633_CTRL0_RESET); + if (ret) + return ret; + + /* set default sampling frequency */ + return regmap_update_bits(data->regmap, MMC5633_REG_CTRL1, + MMC5633_CTRL1_BW_MASK, + FIELD_PREP(MMC5633_CTRL1_BW_MASK, 0)); +} + +static int mmc5633_take_measurement(struct mmc5633_data *data, int address) +{ + unsigned int reg_status, val; + int ret; + + val = (address == MMC5633_TEMPERATURE) ? MMC5633_CTRL0_MEAS_T : MMC5633_CTRL0_MEAS_M; + ret = regmap_write(data->regmap, MMC5633_REG_CTRL0, val); + if (ret < 0) + return ret; + + val = (address == MMC5633_TEMPERATURE) ? + MMC5633_STATUS1_MEAS_T_DONE_BIT : MMC5633_STATUS1_MEAS_M_DONE_BIT; + ret = regmap_read_poll_timeout(data->regmap, MMC5633_REG_STATUS1, reg_status, + reg_status & val, + 10 * USEC_PER_MSEC, + 100 * 10 * USEC_PER_MSEC); + if (ret) { + dev_err(regmap_get_device(data->regmap), "data not ready\n"); + return ret; + } + + return 0; +} + +static bool mmc5633_is_support_hdr(struct mmc5633_data *data) +{ + if (!data->i3cdev) + return false; + + return i3c_device_get_supported_xfer_mode(data->i3cdev) & BIT(I3C_HDR_DDR); +} + +static int mmc5633_read_measurement(struct mmc5633_data *data, int address, void *buf, size_t sz) +{ + struct device *dev = regmap_get_device(data->regmap); + u8 data_cmd[2], status[2]; + unsigned int val, ready; + int ret; + + if (mmc5633_is_support_hdr(data)) { + struct i3c_xfer xfers_wr_cmd[] = { + { + .cmd = 0x3b, + .len = 2, + .data.out = data_cmd, + } + }; + struct i3c_xfer xfers_rd_sta_cmd[] = { + { + .cmd = 0x23 | BIT(7), /* RDSTA CMD */ + .len = 2, + .data.in = status, + }, + }; + struct i3c_xfer xfers_rd_data_cmd[] = { + { + .cmd = 0x22 | BIT(7), /* RDLONG CMD */ + .len = sz, + .data.in = buf, + }, + }; + + data_cmd[0] = 0; + data_cmd[1] = (address == MMC5633_TEMPERATURE) ? + MMC5633_HDR_CTRL0_MEAS_T : MMC5633_HDR_CTRL0_MEAS_M; + + ret = i3c_device_do_xfers(data->i3cdev, xfers_wr_cmd, + ARRAY_SIZE(xfers_wr_cmd), I3C_HDR_DDR); + if (ret < 0) + return ret; + + ready = (address == MMC5633_TEMPERATURE) ? + MMC5633_STATUS1_MEAS_T_DONE_BIT : MMC5633_STATUS1_MEAS_M_DONE_BIT; + ret = read_poll_timeout(i3c_device_do_xfers, val, + val || (status[0] & ready), + 10 * USEC_PER_MSEC, + 100 * 10 * USEC_PER_MSEC, 0, + data->i3cdev, xfers_rd_sta_cmd, + ARRAY_SIZE(xfers_rd_sta_cmd), I3C_HDR_DDR); + if (ret) { + dev_err(dev, "data not ready\n"); + return ret; + } + if (val) { + dev_err(dev, "i3c transfer error\n"); + return val; + } + return i3c_device_do_xfers(data->i3cdev, xfers_rd_data_cmd, + ARRAY_SIZE(xfers_rd_data_cmd), I3C_HDR_DDR); + } + + /* Fallback to use SDR/I2C mode */ + ret = mmc5633_take_measurement(data, address); + if (ret < 0) + return ret; + + if (address == MMC5633_TEMPERATURE) + /* + * Put tempeature to last byte of buff to align HDR case. + * I3C will early terminate data read if previous data is not + * available. + */ + return regmap_bulk_read(data->regmap, MMC5633_REG_TOUT, buf + sz - 1, 1); + + return regmap_bulk_read(data->regmap, MMC5633_REG_XOUT0, buf, sz); +} + +/* X,Y,Z 3 channels, each channel has 3 byte and TEMP */ +#define MMC5633_ALL_SIZE (3 * 3 + 1) + +static int mmc5633_get_raw(struct mmc5633_data *data, int index, unsigned char *buf, int *val) +{ + if (index == MMC5633_TEMPERATURE) { + *val = buf[MMC5633_ALL_SIZE - 1]; + return 0; + } + /* + * X[19..12] X[11..4] Y[19..12] Y[11..4] Z[19..12] Z[11..4] X[3..0] Y[3..0] Z[3..0] + */ + *val = get_unaligned_be16(buf + 2 * index) << 4; + *val |= buf[index + 6] >> 4; + + return 0; +} + +static int mmc5633_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mmc5633_data *data = iio_priv(indio_dev); + char buf[MMC5633_ALL_SIZE]; + unsigned int reg, i; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + scoped_guard(mutex, &data->mutex) { + ret = mmc5633_read_measurement(data, chan->address, buf, MMC5633_ALL_SIZE); + if (ret < 0) + return ret; + } + + ret = mmc5633_get_raw(data, chan->address, buf, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_MAGN) { + *val = 0; + *val2 = 62500; + } else { + *val = 0; + *val2 = 800000000; /* 0.8C */ + } + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = -75; + return IIO_VAL_INT; + } + return -EINVAL; + case IIO_CHAN_INFO_SAMP_FREQ: + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, MMC5633_REG_CTRL1, ®); + if (ret < 0) + return ret; + } + + i = FIELD_GET(MMC5633_CTRL1_BW_MASK, reg); + if (i >= ARRAY_SIZE(mmc5633_samp_freq)) + return -EINVAL; + + *val = mmc5633_samp_freq[i][0]; + *val2 = mmc5633_samp_freq[i][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int mmc5633_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct mmc5633_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: { + ret = mmc5633_get_samp_freq_index(data, val, val2); + if (ret < 0) + return ret; + + guard(mutex)(&data->mutex); + + return regmap_update_bits(data->regmap, MMC5633_REG_CTRL1, + MMC5633_CTRL1_BW_MASK, + FIELD_PREP(MMC5633_CTRL1_BW_MASK, ret)); + } + default: + return -EINVAL; + } +} + +static int mmc5633_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = (const int *)mmc5633_samp_freq; + *length = ARRAY_SIZE(mmc5633_samp_freq) * 2; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info mmc5633_info = { + .read_raw = mmc5633_read_raw, + .write_raw = mmc5633_write_raw, + .read_avail = mmc5633_read_avail, +}; + +static bool mmc5633_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_CTRL0: + case MMC5633_REG_CTRL1: + return true; + default: + return false; + } +} + +static bool mmc5633_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_XOUT0: + case MMC5633_REG_XOUT1: + case MMC5633_REG_YOUT0: + case MMC5633_REG_YOUT1: + case MMC5633_REG_ZOUT0: + case MMC5633_REG_ZOUT1: + case MMC5633_REG_XOUT2: + case MMC5633_REG_YOUT2: + case MMC5633_REG_ZOUT2: + case MMC5633_REG_TOUT: + case MMC5633_REG_STATUS1: + case MMC5633_REG_ID: + return true; + default: + return false; + } +} + +static bool mmc5633_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MMC5633_REG_CTRL0: + case MMC5633_REG_CTRL1: + return false; + default: + return true; + } +} + +static const struct reg_default mmc5633_reg_defaults[] = { + { MMC5633_REG_CTRL0, 0x00 }, + { MMC5633_REG_CTRL1, 0x00 }, +}; + +static const struct regmap_config mmc5633_regmap_config = { + .name = "mmc5633_regmap", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = MMC5633_REG_ID, + .cache_type = REGCACHE_MAPLE, + + .writeable_reg = mmc5633_is_writeable_reg, + .readable_reg = mmc5633_is_readable_reg, + .volatile_reg = mmc5633_is_volatile_reg, + + .reg_defaults = mmc5633_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(mmc5633_reg_defaults), +}; + +static int mmc5633_common_probe(struct regmap *regmap, char *name, + struct i3c_device *i3cdev) +{ + struct device *dev = regmap_get_device(regmap); + struct mmc5633_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + + data->regmap = regmap; + data->i3cdev = i3cdev; + + ret = devm_mutex_init(dev, &data->mutex); + if (ret) + return ret; + + indio_dev->info = &mmc5633_info; + indio_dev->name = name; + indio_dev->channels = mmc5633_channels; + indio_dev->num_channels = ARRAY_SIZE(mmc5633_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = mmc5633_init(data); + if (ret < 0) + return dev_err_probe(dev, ret, "mmc5633 chip init failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int mmc5633_suspend(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + + regcache_cache_only(regmap, true); + + return 0; +} + +static int mmc5633_resume(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + int ret; + + regcache_mark_dirty(regmap); + ret = regcache_sync_region(regmap, MMC5633_REG_CTRL0, MMC5633_REG_CTRL1); + if (ret) + dev_err(dev, "Failed to restore control registers\n"); + + regcache_cache_only(regmap, false); + + return 0; +} + +static int mmc5633_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &mmc5633_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n"); + + return mmc5633_common_probe(regmap, client->name, NULL); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mmc5633_pm_ops, mmc5633_suspend, mmc5633_resume); + +static const struct of_device_id mmc5633_of_match[] = { + { .compatible = "memsic,mmc5603" }, + { .compatible = "memsic,mmc5633" }, + { } +}; +MODULE_DEVICE_TABLE(of, mmc5633_of_match); + +static const struct i2c_device_id mmc5633_i2c_id[] = { + { "mmc5603" }, + { "mmc5633" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mmc5633_i2c_id); + +static struct i2c_driver mmc5633_i2c_driver = { + .driver = { + .name = "mmc5633_i2c", + .of_match_table = mmc5633_of_match, + .pm = pm_sleep_ptr(&mmc5633_pm_ops), + }, + .probe = mmc5633_i2c_probe, + .id_table = mmc5633_i2c_id, +}; + +static const struct i3c_device_id mmc5633_i3c_ids[] = { + I3C_DEVICE(0x0251, 0x0000, NULL), + { } +}; +MODULE_DEVICE_TABLE(i3c, mmc5633_i3c_ids); + +static int mmc5633_i3c_probe(struct i3c_device *i3cdev) +{ + struct device *dev = i3cdev_to_dev(i3cdev); + struct regmap *regmap; + char *name; + + name = devm_kasprintf(dev, GFP_KERNEL, "mmc5633_%s", dev_name(dev)); + if (!name) + return -ENOMEM; + + regmap = devm_regmap_init_i3c(i3cdev, &mmc5633_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to register i3c regmap\n"); + + return mmc5633_common_probe(regmap, name, i3cdev); +} + +static struct i3c_driver mmc5633_i3c_driver = { + .driver = { + .name = "mmc5633_i3c", + }, + .probe = mmc5633_i3c_probe, + .id_table = mmc5633_i3c_ids, +}; +module_i3c_i2c_driver(mmc5633_i3c_driver, &mmc5633_i2c_driver) + +MODULE_AUTHOR("Frank Li <Frank.li@nxp.com>"); +MODULE_DESCRIPTION("MEMSIC MMC5633 magnetic sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index 2fe9dc90cceb..838a8340c4c0 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -16,6 +16,35 @@ config ABP060MG To compile this driver as a module, choose M here: the module will be called abp060mg. +config ABP2030PA + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config ABP2030PA_I2C + tristate "Honeywell ABP2 pressure sensor series I2C driver" + depends on I2C + select ABP2030PA + help + Say Y here to build I2C bus support for the Honeywell ABP2 + series pressure and temperature digital sensor. + + To compile this driver as a module, choose M here: the module + will be called abp2030pa_i2c and you will also get abp2030pa + for the core module. + +config ABP2030PA_SPI + tristate "Honeywell ABP2 pressure sensor series SPI driver" + depends on SPI_MASTER + select ABP2030PA + help + Say Y here to build SPI bus support for the Honeywell ABP2 + series pressure and temperature digital sensor. + + To compile this driver as a module, choose M here: the module + will be called abp2030pa_spi and you will also get abp2030pa + for the core module. + config ROHM_BM1390 tristate "ROHM BM1390GLV-Z pressure sensor driver" depends on I2C @@ -187,29 +216,33 @@ config MPL3115 will be called mpl3115. config MPRLS0025PA - tristate "Honeywell MPRLS0025PA (MicroPressure sensors series)" - depends on (I2C || SPI_MASTER) - select MPRLS0025PA_I2C if I2C - select MPRLS0025PA_SPI if SPI_MASTER + tristate select IIO_BUFFER select IIO_TRIGGERED_BUFFER - help - Say Y here to build support for the Honeywell MicroPressure pressure - sensor series. There are many different types with different pressure - range. These ranges can be set up in the device tree. - - To compile this driver as a module, choose M here: the module will be - called mprls0025pa. config MPRLS0025PA_I2C - tristate - depends on MPRLS0025PA + tristate "Honeywell MPR pressure sensor series I2C driver" depends on I2C + select MPRLS0025PA + help + Say Y here to build I2C bus support for the Honeywell MicroPressure + series sensor. + + To compile this driver as a module, choose M here: the module + will be called mprls0025pa_i2c and you will also get mprls0025pa + for the core module. config MPRLS0025PA_SPI - tristate - depends on MPRLS0025PA + tristate "Honeywell MPR pressure sensor series SPI driver" depends on SPI_MASTER + select MPRLS0025PA + help + Say Y here to build SPI bus support for the Honeywell MicroPressure + series sensor. + + To compile this driver as a module, choose M here: the module + will be called mprls0025pa_spi and you will also get mprls0025pa + for the core module. config MS5611 tristate "Measurement Specialties MS5611 pressure sensor driver" diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index a21443e992b9..bc0d11a20acc 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile @@ -5,6 +5,9 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_ABP060MG) += abp060mg.o +obj-$(CONFIG_ABP2030PA) += abp2030pa.o +obj-$(CONFIG_ABP2030PA_I2C) += abp2030pa_i2c.o +obj-$(CONFIG_ABP2030PA_SPI) += abp2030pa_spi.o obj-$(CONFIG_ADP810) += adp810.o obj-$(CONFIG_ROHM_BM1390) += rohm-bm1390.o obj-$(CONFIG_BMP280) += bmp280.o diff --git a/drivers/iio/pressure/abp2030pa.c b/drivers/iio/pressure/abp2030pa.c new file mode 100644 index 000000000000..4ca056a73cef --- /dev/null +++ b/drivers/iio/pressure/abp2030pa.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro> + * + * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/basic-abp2-series/documents/sps-siot-abp2-series-datasheet-32350268-en.pdf + */ + +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/string.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/unaligned.h> +#include <linux/units.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include "abp2030pa.h" + +/* Status byte flags */ +#define ABP2_ST_POWER BIT(6) /* 1 if device is powered */ +#define ABP2_ST_BUSY BIT(5) /* 1 if device is busy */ + +#define ABP2_CMD_NOP 0xf0 +#define ABP2_CMD_SYNC 0xaa +#define ABP2_PKT_SYNC_LEN 3 +#define ABP2_PKT_NOP_LEN ABP2_MEASUREMENT_RD_SIZE + +struct abp2_func_spec { + u32 output_min; + u32 output_max; +}; + +/* transfer function A: 10% to 90% of 2^24 */ +static const struct abp2_func_spec abp2_func_spec[] = { + [ABP2_FUNCTION_A] = { .output_min = 1677722, .output_max = 15099494 }, +}; + +enum abp2_variants { + ABP2001BA, ABP21_6BA, ABP22_5BA, ABP2004BA, ABP2006BA, ABP2008BA, + ABP2010BA, ABP2012BA, ABP2001BD, ABP21_6BD, ABP22_5BD, ABP2004BD, + ABP2001BG, ABP21_6BG, ABP22_5BG, ABP2004BG, ABP2006BG, ABP2008BG, + ABP2010BG, ABP2012BG, ABP2001GG, ABP21_2GG, ABP2100KA, ABP2160KA, + ABP2250KA, ABP2001KD, ABP21_6KD, ABP22_5KD, ABP2004KD, ABP2006KD, + ABP2010KD, ABP2016KD, ABP2025KD, ABP2040KD, ABP2060KD, ABP2100KD, + ABP2160KD, ABP2250KD, ABP2400KD, ABP2001KG, ABP21_6KG, ABP22_5KG, + ABP2004KG, ABP2006KG, ABP2010KG, ABP2016KG, ABP2025KG, ABP2040KG, + ABP2060KG, ABP2100KG, ABP2160KG, ABP2250KG, ABP2400KG, ABP2600KG, + ABP2800KG, ABP2250LD, ABP2600LD, ABP2600LG, ABP22_5MD, ABP2006MD, + ABP2010MD, ABP2016MD, ABP2025MD, ABP2040MD, ABP2060MD, ABP2100MD, + ABP2160MD, ABP2250MD, ABP2400MD, ABP2600MD, ABP2006MG, ABP2010MG, + ABP2016MG, ABP2025MG, ABP2040MG, ABP2060MG, ABP2100MG, ABP2160MG, + ABP2250MG, ABP2400MG, ABP2600MG, ABP2001ND, ABP2002ND, ABP2004ND, + ABP2005ND, ABP2010ND, ABP2020ND, ABP2030ND, ABP2002NG, ABP2004NG, + ABP2005NG, ABP2010NG, ABP2020NG, ABP2030NG, ABP2015PA, ABP2030PA, + ABP2060PA, ABP2100PA, ABP2150PA, ABP2175PA, ABP2001PD, ABP2005PD, + ABP2015PD, ABP2030PD, ABP2060PD, ABP2001PG, ABP2005PG, ABP2015PG, + ABP2030PG, ABP2060PG, ABP2100PG, ABP2150PG, ABP2175PG, +}; + +static const char * const abp2_triplet_variants[] = { + [ABP2001BA] = "001BA", [ABP21_6BA] = "1.6BA", [ABP22_5BA] = "2.5BA", + [ABP2004BA] = "004BA", [ABP2006BA] = "006BA", [ABP2008BA] = "008BA", + [ABP2010BA] = "010BA", [ABP2012BA] = "012BA", [ABP2001BD] = "001BD", + [ABP21_6BD] = "1.6BD", [ABP22_5BD] = "2.5BD", [ABP2004BD] = "004BD", + [ABP2001BG] = "001BG", [ABP21_6BG] = "1.6BG", [ABP22_5BG] = "2.5BG", + [ABP2004BG] = "004BG", [ABP2006BG] = "006BG", [ABP2008BG] = "008BG", + [ABP2010BG] = "010BG", [ABP2012BG] = "012BG", [ABP2001GG] = "001GG", + [ABP21_2GG] = "1.2GG", [ABP2100KA] = "100KA", [ABP2160KA] = "160KA", + [ABP2250KA] = "250KA", [ABP2001KD] = "001KD", [ABP21_6KD] = "1.6KD", + [ABP22_5KD] = "2.5KD", [ABP2004KD] = "004KD", [ABP2006KD] = "006KD", + [ABP2010KD] = "010KD", [ABP2016KD] = "016KD", [ABP2025KD] = "025KD", + [ABP2040KD] = "040KD", [ABP2060KD] = "060KD", [ABP2100KD] = "100KD", + [ABP2160KD] = "160KD", [ABP2250KD] = "250KD", [ABP2400KD] = "400KD", + [ABP2001KG] = "001KG", [ABP21_6KG] = "1.6KG", [ABP22_5KG] = "2.5KG", + [ABP2004KG] = "004KG", [ABP2006KG] = "006KG", [ABP2010KG] = "010KG", + [ABP2016KG] = "016KG", [ABP2025KG] = "025KG", [ABP2040KG] = "040KG", + [ABP2060KG] = "060KG", [ABP2100KG] = "100KG", [ABP2160KG] = "160KG", + [ABP2250KG] = "250KG", [ABP2400KG] = "400KG", [ABP2600KG] = "600KG", + [ABP2800KG] = "800KG", [ABP2250LD] = "250LD", [ABP2600LD] = "600LD", + [ABP2600LG] = "600LG", [ABP22_5MD] = "2.5MD", [ABP2006MD] = "006MD", + [ABP2010MD] = "010MD", [ABP2016MD] = "016MD", [ABP2025MD] = "025MD", + [ABP2040MD] = "040MD", [ABP2060MD] = "060MD", [ABP2100MD] = "100MD", + [ABP2160MD] = "160MD", [ABP2250MD] = "250MD", [ABP2400MD] = "400MD", + [ABP2600MD] = "600MD", [ABP2006MG] = "006MG", [ABP2010MG] = "010MG", + [ABP2016MG] = "016MG", [ABP2025MG] = "025MG", [ABP2040MG] = "040MG", + [ABP2060MG] = "060MG", [ABP2100MG] = "100MG", [ABP2160MG] = "160MG", + [ABP2250MG] = "250MG", [ABP2400MG] = "400MG", [ABP2600MG] = "600MG", + [ABP2001ND] = "001ND", [ABP2002ND] = "002ND", [ABP2004ND] = "004ND", + [ABP2005ND] = "005ND", [ABP2010ND] = "010ND", [ABP2020ND] = "020ND", + [ABP2030ND] = "030ND", [ABP2002NG] = "002NG", [ABP2004NG] = "004NG", + [ABP2005NG] = "005NG", [ABP2010NG] = "010NG", [ABP2020NG] = "020NG", + [ABP2030NG] = "030NG", [ABP2015PA] = "015PA", [ABP2030PA] = "030PA", + [ABP2060PA] = "060PA", [ABP2100PA] = "100PA", [ABP2150PA] = "150PA", + [ABP2175PA] = "175PA", [ABP2001PD] = "001PD", [ABP2005PD] = "005PD", + [ABP2015PD] = "015PD", [ABP2030PD] = "030PD", [ABP2060PD] = "060PD", + [ABP2001PG] = "001PG", [ABP2005PG] = "005PG", [ABP2015PG] = "015PG", + [ABP2030PG] = "030PG", [ABP2060PG] = "060PG", [ABP2100PG] = "100PG", + [ABP2150PG] = "150PG", [ABP2175PG] = "175PG", +}; + +/** + * struct abp2_range_config - list of pressure ranges based on nomenclature + * @pmin: lowest pressure that can be measured + * @pmax: highest pressure that can be measured + */ +struct abp2_range_config { + s32 pmin; + s32 pmax; +}; + +/* All min max limits have been converted to pascals */ +static const struct abp2_range_config abp2_range_config[] = { + [ABP2001BA] = { .pmin = 0, .pmax = 100000 }, + [ABP21_6BA] = { .pmin = 0, .pmax = 160000 }, + [ABP22_5BA] = { .pmin = 0, .pmax = 250000 }, + [ABP2004BA] = { .pmin = 0, .pmax = 400000 }, + [ABP2006BA] = { .pmin = 0, .pmax = 600000 }, + [ABP2008BA] = { .pmin = 0, .pmax = 800000 }, + [ABP2010BA] = { .pmin = 0, .pmax = 1000000 }, + [ABP2012BA] = { .pmin = 0, .pmax = 1200000 }, + [ABP2001BD] = { .pmin = -100000, .pmax = 100000 }, + [ABP21_6BD] = { .pmin = -160000, .pmax = 160000 }, + [ABP22_5BD] = { .pmin = -250000, .pmax = 250000 }, + [ABP2004BD] = { .pmin = -400000, .pmax = 400000 }, + [ABP2001BG] = { .pmin = 0, .pmax = 100000 }, + [ABP21_6BG] = { .pmin = 0, .pmax = 160000 }, + [ABP22_5BG] = { .pmin = 0, .pmax = 250000 }, + [ABP2004BG] = { .pmin = 0, .pmax = 400000 }, + [ABP2006BG] = { .pmin = 0, .pmax = 600000 }, + [ABP2008BG] = { .pmin = 0, .pmax = 800000 }, + [ABP2010BG] = { .pmin = 0, .pmax = 1000000 }, + [ABP2012BG] = { .pmin = 0, .pmax = 1200000 }, + [ABP2001GG] = { .pmin = 0, .pmax = 1000000 }, + [ABP21_2GG] = { .pmin = 0, .pmax = 1200000 }, + [ABP2100KA] = { .pmin = 0, .pmax = 100000 }, + [ABP2160KA] = { .pmin = 0, .pmax = 160000 }, + [ABP2250KA] = { .pmin = 0, .pmax = 250000 }, + [ABP2001KD] = { .pmin = -1000, .pmax = 1000 }, + [ABP21_6KD] = { .pmin = -1600, .pmax = 1600 }, + [ABP22_5KD] = { .pmin = -2500, .pmax = 2500 }, + [ABP2004KD] = { .pmin = -4000, .pmax = 4000 }, + [ABP2006KD] = { .pmin = -6000, .pmax = 6000 }, + [ABP2010KD] = { .pmin = -10000, .pmax = 10000 }, + [ABP2016KD] = { .pmin = -16000, .pmax = 16000 }, + [ABP2025KD] = { .pmin = -25000, .pmax = 25000 }, + [ABP2040KD] = { .pmin = -40000, .pmax = 40000 }, + [ABP2060KD] = { .pmin = -60000, .pmax = 60000 }, + [ABP2100KD] = { .pmin = -100000, .pmax = 100000 }, + [ABP2160KD] = { .pmin = -160000, .pmax = 160000 }, + [ABP2250KD] = { .pmin = -250000, .pmax = 250000 }, + [ABP2400KD] = { .pmin = -400000, .pmax = 400000 }, + [ABP2001KG] = { .pmin = 0, .pmax = 1000 }, + [ABP21_6KG] = { .pmin = 0, .pmax = 1600 }, + [ABP22_5KG] = { .pmin = 0, .pmax = 2500 }, + [ABP2004KG] = { .pmin = 0, .pmax = 4000 }, + [ABP2006KG] = { .pmin = 0, .pmax = 6000 }, + [ABP2010KG] = { .pmin = 0, .pmax = 10000 }, + [ABP2016KG] = { .pmin = 0, .pmax = 16000 }, + [ABP2025KG] = { .pmin = 0, .pmax = 25000 }, + [ABP2040KG] = { .pmin = 0, .pmax = 40000 }, + [ABP2060KG] = { .pmin = 0, .pmax = 60000 }, + [ABP2100KG] = { .pmin = 0, .pmax = 100000 }, + [ABP2160KG] = { .pmin = 0, .pmax = 160000 }, + [ABP2250KG] = { .pmin = 0, .pmax = 250000 }, + [ABP2400KG] = { .pmin = 0, .pmax = 400000 }, + [ABP2600KG] = { .pmin = 0, .pmax = 600000 }, + [ABP2800KG] = { .pmin = 0, .pmax = 800000 }, + [ABP2250LD] = { .pmin = -250, .pmax = 250 }, + [ABP2600LD] = { .pmin = -600, .pmax = 600 }, + [ABP2600LG] = { .pmin = 0, .pmax = 600 }, + [ABP22_5MD] = { .pmin = -250, .pmax = 250 }, + [ABP2006MD] = { .pmin = -600, .pmax = 600 }, + [ABP2010MD] = { .pmin = -1000, .pmax = 1000 }, + [ABP2016MD] = { .pmin = -1600, .pmax = 1600 }, + [ABP2025MD] = { .pmin = -2500, .pmax = 2500 }, + [ABP2040MD] = { .pmin = -4000, .pmax = 4000 }, + [ABP2060MD] = { .pmin = -6000, .pmax = 6000 }, + [ABP2100MD] = { .pmin = -10000, .pmax = 10000 }, + [ABP2160MD] = { .pmin = -16000, .pmax = 16000 }, + [ABP2250MD] = { .pmin = -25000, .pmax = 25000 }, + [ABP2400MD] = { .pmin = -40000, .pmax = 40000 }, + [ABP2600MD] = { .pmin = -60000, .pmax = 60000 }, + [ABP2006MG] = { .pmin = 0, .pmax = 600 }, + [ABP2010MG] = { .pmin = 0, .pmax = 1000 }, + [ABP2016MG] = { .pmin = 0, .pmax = 1600 }, + [ABP2025MG] = { .pmin = 0, .pmax = 2500 }, + [ABP2040MG] = { .pmin = 0, .pmax = 4000 }, + [ABP2060MG] = { .pmin = 0, .pmax = 6000 }, + [ABP2100MG] = { .pmin = 0, .pmax = 10000 }, + [ABP2160MG] = { .pmin = 0, .pmax = 16000 }, + [ABP2250MG] = { .pmin = 0, .pmax = 25000 }, + [ABP2400MG] = { .pmin = 0, .pmax = 40000 }, + [ABP2600MG] = { .pmin = 0, .pmax = 60000 }, + [ABP2001ND] = { .pmin = -249, .pmax = 249 }, + [ABP2002ND] = { .pmin = -498, .pmax = 498 }, + [ABP2004ND] = { .pmin = -996, .pmax = 996 }, + [ABP2005ND] = { .pmin = -1245, .pmax = 1245 }, + [ABP2010ND] = { .pmin = -2491, .pmax = 2491 }, + [ABP2020ND] = { .pmin = -4982, .pmax = 4982 }, + [ABP2030ND] = { .pmin = -7473, .pmax = 7473 }, + [ABP2002NG] = { .pmin = 0, .pmax = 498 }, + [ABP2004NG] = { .pmin = 0, .pmax = 996 }, + [ABP2005NG] = { .pmin = 0, .pmax = 1245 }, + [ABP2010NG] = { .pmin = 0, .pmax = 2491 }, + [ABP2020NG] = { .pmin = 0, .pmax = 4982 }, + [ABP2030NG] = { .pmin = 0, .pmax = 7473 }, + [ABP2015PA] = { .pmin = 0, .pmax = 103421 }, + [ABP2030PA] = { .pmin = 0, .pmax = 206843 }, + [ABP2060PA] = { .pmin = 0, .pmax = 413685 }, + [ABP2100PA] = { .pmin = 0, .pmax = 689476 }, + [ABP2150PA] = { .pmin = 0, .pmax = 1034214 }, + [ABP2175PA] = { .pmin = 0, .pmax = 1206583 }, + [ABP2001PD] = { .pmin = -6895, .pmax = 6895 }, + [ABP2005PD] = { .pmin = -34474, .pmax = 34474 }, + [ABP2015PD] = { .pmin = -103421, .pmax = 103421 }, + [ABP2030PD] = { .pmin = -206843, .pmax = 206843 }, + [ABP2060PD] = { .pmin = -413685, .pmax = 413685 }, + [ABP2001PG] = { .pmin = 0, .pmax = 6895 }, + [ABP2005PG] = { .pmin = 0, .pmax = 34474 }, + [ABP2015PG] = { .pmin = 0, .pmax = 103421 }, + [ABP2030PG] = { .pmin = 0, .pmax = 206843 }, + [ABP2060PG] = { .pmin = 0, .pmax = 413685 }, + [ABP2100PG] = { .pmin = 0, .pmax = 689476 }, + [ABP2150PG] = { .pmin = 0, .pmax = 1034214 }, + [ABP2175PG] = { .pmin = 0, .pmax = 1206583 }, +}; + +static_assert(ARRAY_SIZE(abp2_triplet_variants) == ARRAY_SIZE(abp2_range_config)); + +static int abp2_get_measurement(struct abp2_data *data) +{ + struct device *dev = data->dev; + int ret; + + reinit_completion(&data->completion); + + ret = data->ops->write(data, ABP2_CMD_SYNC, ABP2_PKT_SYNC_LEN); + if (ret < 0) + return ret; + + if (data->irq > 0) { + ret = wait_for_completion_timeout(&data->completion, HZ); + if (!ret) { + dev_err(dev, "timeout waiting for EOC interrupt\n"); + return -ETIMEDOUT; + } + } else { + fsleep(5 * USEC_PER_MSEC); + } + + memset(data->rx_buf, 0, sizeof(data->rx_buf)); + ret = data->ops->read(data, ABP2_CMD_NOP, ABP2_PKT_NOP_LEN); + if (ret < 0) + return ret; + + /* + * Status byte flags + * bit7 SANITY_CHK - must always be 0 + * bit6 ABP2_ST_POWER - 1 if device is powered + * bit5 ABP2_ST_BUSY - 1 if device has no new conversion ready + * bit4 SANITY_CHK - must always be 0 + * bit3 SANITY_CHK - must always be 0 + * bit2 MEMORY_ERR - 1 if integrity test has failed + * bit1 SANITY_CHK - must always be 0 + * bit0 MATH_ERR - 1 during internal math saturation error + */ + + if (data->rx_buf[0] == (ABP2_ST_POWER | ABP2_ST_BUSY)) + return -EBUSY; + + /* + * The ABP2 sensor series seem to have a noticeable latch-up sensitivity. + * A partial latch-up condition manifests as either + * - output of invalid status bytes + * - zeroed out conversions (despite a normal status byte) + * - the MOSI line being pulled low randomly in sync with the SCLK + * signal (visible during the ABP2_CMD_NOP command). + * https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1588325/am3358-spi-tx-data-corruption + */ + + if (data->rx_buf[0] != ABP2_ST_POWER) { + dev_err(data->dev, + "unexpected status byte 0x%02x\n", data->rx_buf[0]); + return -EIO; + } + + return 0; +} + +static irqreturn_t abp2_eoc_handler(int irq, void *private) +{ + struct abp2_data *data = private; + + complete(&data->completion); + + return IRQ_HANDLED; +} + +static irqreturn_t abp2_trigger_handler(int irq, void *private) +{ + int ret; + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct abp2_data *data = iio_priv(indio_dev); + + ret = abp2_get_measurement(data); + if (ret < 0) + goto out_notify_done; + + data->scan.chan[0] = get_unaligned_be24(&data->rx_buf[1]); + data->scan.chan[1] = get_unaligned_be24(&data->rx_buf[4]); + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + +out_notify_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/* + * IIO ABI expects + * value = (conv + offset) * scale + * + * temp[C] = conv * a + b + * where a = 200/16777215; b = -50 + * + * temp[C] = (conv + (b/a)) * a * (1000) + * => + * scale = a * 1000 = .0000119209296 * 1000 = .01192092966562 + * offset = b/a = -50 * 16777215 / 200 = -4194303.75 + * + * pressure = (conv - Omin) * Q + Pmin = + * ((conv - Omin) + Pmin/Q) * Q + * => + * scale = Q = (Pmax - Pmin) / (Omax - Omin) + * offset = Pmin/Q - Omin = Pmin * (Omax - Omin) / (Pmax - Pmin) - Omin + */ +static int abp2_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *channel, int *val, + int *val2, long mask) +{ + struct abp2_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = abp2_get_measurement(data); + if (ret < 0) + return ret; + + switch (channel->type) { + case IIO_PRESSURE: + *val = get_unaligned_be24(&data->rx_buf[1]); + return IIO_VAL_INT; + case IIO_TEMP: + *val = get_unaligned_be24(&data->rx_buf[4]); + return IIO_VAL_INT; + default: + return -EINVAL; + } + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (channel->type) { + case IIO_TEMP: + *val = 0; + *val2 = 11920929; + return IIO_VAL_INT_PLUS_NANO; + case IIO_PRESSURE: + *val = data->p_scale; + *val2 = data->p_scale_dec; + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_OFFSET: + switch (channel->type) { + case IIO_TEMP: + *val = -4194304; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = data->p_offset; + return IIO_VAL_INT; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static const struct iio_info abp2_info = { + .read_raw = &abp2_read_raw, +}; + +static const unsigned long abp2_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec abp2_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 24, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq) +{ + int ret; + struct abp2_data *data; + struct iio_dev *indio_dev; + const char *triplet; + s32 tmp; + s64 odelta, pdelta; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->dev = dev; + data->ops = ops; + data->irq = irq; + + init_completion(&data->completion); + + indio_dev->name = "abp2030pa"; + indio_dev->info = &abp2_info; + indio_dev->channels = abp2_channels; + indio_dev->num_channels = ARRAY_SIZE(abp2_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = abp2_scan_masks; + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "can't get and enable vdd supply\n"); + + ret = device_property_read_string(dev, "honeywell,pressure-triplet", + &triplet); + if (ret) { + ret = device_property_read_u32(dev, "honeywell,pmin-pascal", + &data->pmin); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmin-pascal could not be read\n"); + + ret = device_property_read_u32(dev, "honeywell,pmax-pascal", + &data->pmax); + if (ret) + return dev_err_probe(dev, ret, + "honeywell,pmax-pascal could not be read\n"); + } else { + ret = device_property_match_property_string(dev, + "honeywell,pressure-triplet", + abp2_triplet_variants, + ARRAY_SIZE(abp2_triplet_variants)); + if (ret < 0) + return dev_err_probe(dev, -EINVAL, "honeywell,pressure-triplet is invalid\n"); + + data->pmin = abp2_range_config[ret].pmin; + data->pmax = abp2_range_config[ret].pmax; + } + + if (data->pmin >= data->pmax) + return dev_err_probe(dev, -EINVAL, "pressure limits are invalid\n"); + + data->outmin = abp2_func_spec[data->function].output_min; + data->outmax = abp2_func_spec[data->function].output_max; + + odelta = data->outmax - data->outmin; + pdelta = data->pmax - data->pmin; + + data->p_scale = div_s64_rem(div_s64(pdelta * NANO, odelta), NANO, &tmp); + data->p_scale_dec = tmp; + + data->p_offset = div_s64(odelta * data->pmin, pdelta) - data->outmin; + + if (data->irq > 0) { + ret = devm_request_irq(dev, irq, abp2_eoc_handler, IRQF_ONESHOT, + dev_name(dev), data); + if (ret) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + abp2_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "unable to register iio device\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(abp2_common_probe, "IIO_HONEYWELL_ABP2030PA"); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>"); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/abp2030pa.h b/drivers/iio/pressure/abp2030pa.h new file mode 100644 index 000000000000..57e5ed784686 --- /dev/null +++ b/drivers/iio/pressure/abp2030pa.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro> + */ + +#ifndef _ABP2030PA_H +#define _ABP2030PA_H + +#include <linux/completion.h> +#include <linux/types.h> + +#include <linux/iio/iio.h> + +#define ABP2_MEASUREMENT_RD_SIZE 7 + +struct device; + +struct abp2_data; +struct abp2_ops; + +enum abp2_func_id { + ABP2_FUNCTION_A, +}; + +/** + * struct abp2_data + * @dev: current device structure + * @ops: pointers for bus specific read and write functions + * @pmin: minimal pressure in pascal + * @pmax: maximal pressure in pascal + * @outmin: minimum raw pressure in counts (based on transfer function) + * @outmax: maximum raw pressure in counts (based on transfer function) + * @function: transfer function + * @p_scale: pressure scale + * @p_scale_dec: pressure scale, decimal number + * @p_offset: pressure offset + * @irq: end of conversion - applies only to the i2c sensor + * @completion: handshake from irq to read + * @scan: channel values for buffered mode + * @tx_buf: transmit buffer used during the SPI communication + * @rx_buf: raw data provided by sensor + */ +struct abp2_data { + struct device *dev; + const struct abp2_ops *ops; + s32 pmin; + s32 pmax; + u32 outmin; + u32 outmax; + enum abp2_func_id function; + int p_scale; + int p_scale_dec; + int p_offset; + int irq; + struct completion completion; + struct { + u32 chan[2]; + aligned_s64 timestamp; + } scan; + u8 rx_buf[ABP2_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[ABP2_MEASUREMENT_RD_SIZE]; +}; + +struct abp2_ops { + int (*read)(struct abp2_data *data, u8 cmd, u8 nbytes); + int (*write)(struct abp2_data *data, u8 cmd, u8 nbytes); +}; + +int abp2_common_probe(struct device *dev, const struct abp2_ops *ops, int irq); + +#endif diff --git a/drivers/iio/pressure/abp2030pa_i2c.c b/drivers/iio/pressure/abp2030pa_i2c.c new file mode 100644 index 000000000000..9f1c1c8a9afb --- /dev/null +++ b/drivers/iio/pressure/abp2030pa_i2c.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro> + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/types.h> + +#include "abp2030pa.h" + +static int abp2_i2c_read(struct abp2_data *data, u8 unused, u8 nbytes) +{ + struct i2c_client *client = to_i2c_client(data->dev); + int ret; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + ret = i2c_master_recv(client, data->rx_buf, nbytes); + if (ret < 0) + return ret; + if (ret != nbytes) + return -EIO; + + return 0; +} + +static int abp2_i2c_write(struct abp2_data *data, u8 cmd, u8 nbytes) +{ + struct i2c_client *client = to_i2c_client(data->dev); + int ret; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + ret = i2c_master_send(client, data->tx_buf, nbytes); + if (ret < 0) + return ret; + if (ret != nbytes) + return -EIO; + + return 0; +} + +static const struct abp2_ops abp2_i2c_ops = { + .read = abp2_i2c_read, + .write = abp2_i2c_write, +}; + +static int abp2_i2c_probe(struct i2c_client *client) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + return abp2_common_probe(&client->dev, &abp2_i2c_ops, client->irq); +} + +static const struct of_device_id abp2_i2c_match[] = { + { .compatible = "honeywell,abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, abp2_i2c_match); + +static const struct i2c_device_id abp2_i2c_id[] = { + { "abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, abp2_i2c_id); + +static struct i2c_driver abp2_i2c_driver = { + .driver = { + .name = "abp2030pa", + .of_match_table = abp2_i2c_match, + }, + .probe = abp2_i2c_probe, + .id_table = abp2_i2c_id, +}; +module_i2c_driver(abp2_i2c_driver); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>"); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_ABP2030PA"); diff --git a/drivers/iio/pressure/abp2030pa_spi.c b/drivers/iio/pressure/abp2030pa_spi.c new file mode 100644 index 000000000000..eaea9a3ebf11 --- /dev/null +++ b/drivers/iio/pressure/abp2030pa_spi.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Honeywell ABP2 series pressure sensor driver + * + * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro> + */ + +#include <linux/errno.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/types.h> + +#include "abp2030pa.h" + +static int abp2_spi_xfer(struct abp2_data *data, u8 cmd, u8 nbytes) +{ + struct spi_device *spi = to_spi_device(data->dev); + struct spi_transfer xfer = { }; + + if (nbytes > ABP2_MEASUREMENT_RD_SIZE) + return -EOVERFLOW; + + data->tx_buf[0] = cmd; + xfer.tx_buf = data->tx_buf; + xfer.rx_buf = data->rx_buf; + xfer.len = nbytes; + + return spi_sync_transfer(spi, &xfer, 1); +} + +static const struct abp2_ops abp2_spi_ops = { + .read = abp2_spi_xfer, + .write = abp2_spi_xfer, +}; + +static int abp2_spi_probe(struct spi_device *spi) +{ + return abp2_common_probe(&spi->dev, &abp2_spi_ops, spi->irq); +} + +static const struct of_device_id abp2_spi_match[] = { + { .compatible = "honeywell,abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(of, abp2_spi_match); + +static const struct spi_device_id abp2_spi_id[] = { + { "abp2030pa" }, + { } +}; +MODULE_DEVICE_TABLE(spi, abp2_spi_id); + +static struct spi_driver abp2_spi_driver = { + .driver = { + .name = "abp2030pa", + .of_match_table = abp2_spi_match, + }, + .probe = abp2_spi_probe, + .id_table = abp2_spi_id, +}; +module_spi_driver(abp2_spi_driver); + +MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>"); +MODULE_DESCRIPTION("Honeywell ABP2 pressure sensor spi driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_HONEYWELL_ABP2030PA"); diff --git a/drivers/iio/pressure/dlhl60d.c b/drivers/iio/pressure/dlhl60d.c index 8bad7162fec6..46feb27fe632 100644 --- a/drivers/iio/pressure/dlhl60d.c +++ b/drivers/iio/pressure/dlhl60d.c @@ -306,10 +306,9 @@ static int dlh_probe(struct i2c_client *client) indio_dev->num_channels = ARRAY_SIZE(dlh_channels); if (client->irq > 0) { - ret = devm_request_threaded_irq(&client->dev, client->irq, - dlh_interrupt, NULL, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - st->info->name, indio_dev); + ret = devm_request_irq(&client->dev, client->irq, dlh_interrupt, + IRQF_TRIGGER_RISING | IRQF_NO_THREAD, + st->info->name, indio_dev); if (ret) { dev_err(&client->dev, "failed to allocate threaded irq"); return ret; diff --git a/drivers/iio/pressure/mprls0025pa.c b/drivers/iio/pressure/mprls0025pa.c index 2336f2760eae..e8c495f336ff 100644 --- a/drivers/iio/pressure/mprls0025pa.c +++ b/drivers/iio/pressure/mprls0025pa.c @@ -3,6 +3,7 @@ * MPRLS0025PA - Honeywell MicroPressure pressure sensor series driver * * Copyright (c) Andreas Klinger <ak@it-klinger.de> + * Copyright (c) 2023-2025 Petre Rodan <petre.rodan@subdimension.ro> * * Data sheet: * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf @@ -12,15 +13,24 @@ #include <linux/array_size.h> #include <linux/bitfield.h> #include <linux/bits.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> #include <linux/math64.h> #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/property.h> +#include <linux/string.h> +#include <linux/time.h> #include <linux/units.h> #include <linux/gpio/consumer.h> #include <linux/iio/buffer.h> +#include <linux/iio/iio.h> #include <linux/iio/trigger_consumer.h> #include <linux/iio/triggered_buffer.h> @@ -33,10 +43,6 @@ /* bits in status byte */ #define MPR_ST_POWER BIT(6) /* device is powered */ #define MPR_ST_BUSY BIT(5) /* device is busy */ -#define MPR_ST_MEMORY BIT(2) /* integrity test passed */ -#define MPR_ST_MATH BIT(0) /* internal math saturation */ - -#define MPR_ST_ERR_FLAG (MPR_ST_BUSY | MPR_ST_MEMORY | MPR_ST_MATH) /* * support _RAW sysfs interface: @@ -59,7 +65,7 @@ * * Values given to the userspace in sysfs interface: * * raw - press_cnt - * * offset - (-1 * outputmin) - pmin / scale + * * offset - (-1 * outputmin) + pmin / scale * note: With all sensors from the datasheet pmin = 0 * which reduces the offset to (-1 * outputmin) */ @@ -160,8 +166,8 @@ static const struct iio_chan_spec mpr_channels[] = { BIT(IIO_CHAN_INFO_OFFSET), .scan_index = 0, .scan_type = { - .sign = 's', - .realbits = 32, + .sign = 'u', + .realbits = 24, .storagebits = 32, .endianness = IIO_CPU, }, @@ -190,15 +196,15 @@ static void mpr_reset(struct mpr_data *data) * * Context: The function can sleep and data->lock should be held when calling it * Return: - * * 0 - OK, the pressure value could be read - * * -ETIMEDOUT - Timeout while waiting for the EOC interrupt or busy flag is - * still set after nloops attempts of reading + * * 0 - OK, the pressure value could be read + * * -EBUSY - Sensor does not have a new conversion ready + * * -ETIMEDOUT - Timeout while waiting for the EOC interrupt + * * -EIO - Invalid status byte received from sensor */ static int mpr_read_pressure(struct mpr_data *data, s32 *press) { struct device *dev = data->dev; - int ret, i; - int nloops = 10; + int ret; reinit_completion(&data->completion); @@ -215,44 +221,38 @@ static int mpr_read_pressure(struct mpr_data *data, s32 *press) return -ETIMEDOUT; } } else { - /* wait until status indicates data is ready */ - for (i = 0; i < nloops; i++) { - /* - * datasheet only says to wait at least 5 ms for the - * data but leave the maximum response time open - * --> let's try it nloops (10) times which seems to be - * quite long - */ - usleep_range(5000, 10000); - ret = data->ops->read(data, MPR_CMD_NOP, 1); - if (ret < 0) { - dev_err(dev, - "error while reading, status: %d\n", - ret); - return ret; - } - if (!(data->buffer[0] & MPR_ST_ERR_FLAG)) - break; - } - if (i == nloops) { - dev_err(dev, "timeout while reading\n"); - return -ETIMEDOUT; - } + fsleep(5 * USEC_PER_MSEC); } + memset(data->rx_buf, 0, sizeof(data->rx_buf)); ret = data->ops->read(data, MPR_CMD_NOP, MPR_PKT_NOP_LEN); if (ret < 0) return ret; - if (data->buffer[0] & MPR_ST_ERR_FLAG) { + /* + * Status byte flags + * bit7 SANITY_CHK - must always be 0 + * bit6 MPR_ST_POWER - 1 if device is powered + * bit5 MPR_ST_BUSY - 1 if device has no new conversion ready + * bit4 SANITY_CHK - must always be 0 + * bit3 SANITY_CHK - must always be 0 + * bit2 MEMORY_ERR - 1 if integrity test has failed + * bit1 SANITY_CHK - must always be 0 + * bit0 MATH_ERR - 1 during internal math saturation error + */ + + if (data->rx_buf[0] == (MPR_ST_POWER | MPR_ST_BUSY)) + return -EBUSY; + + if (data->rx_buf[0] != MPR_ST_POWER) { dev_err(data->dev, - "unexpected status byte %02x\n", data->buffer[0]); - return -ETIMEDOUT; + "unexpected status byte 0x%02x\n", data->rx_buf[0]); + return -EIO; } - *press = get_unaligned_be24(&data->buffer[1]); + *press = get_unaligned_be24(&data->rx_buf[1]); - dev_dbg(dev, "received: %*ph cnt: %d\n", ret, data->buffer, *press); + dev_dbg(dev, "received: %*ph cnt: %d\n", ret, data->rx_buf, *press); return 0; } @@ -313,8 +313,7 @@ static int mpr_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT_PLUS_NANO; case IIO_CHAN_INFO_OFFSET: *val = data->offset; - *val2 = data->offset2; - return IIO_VAL_INT_PLUS_NANO; + return IIO_VAL_INT; default: return -EINVAL; } @@ -330,8 +329,9 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) struct mpr_data *data; struct iio_dev *indio_dev; const char *triplet; - s64 scale, offset; + s64 odelta, pdelta; u32 func; + s32 tmp; indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) @@ -356,10 +356,6 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) return dev_err_probe(dev, ret, "can't get and enable vdd supply\n"); - ret = data->ops->init(data->dev); - if (ret) - return ret; - ret = device_property_read_u32(dev, "honeywell,transfer-function", &func); if (ret) @@ -405,26 +401,19 @@ int mpr_common_probe(struct device *dev, const struct mpr_ops *ops, int irq) data->outmin = mpr_func_spec[data->function].output_min; data->outmax = mpr_func_spec[data->function].output_max; - /* use 64 bit calculation for preserving a reasonable precision */ - scale = div_s64(((s64)(data->pmax - data->pmin)) * NANO, - data->outmax - data->outmin); - data->scale = div_s64_rem(scale, NANO, &data->scale2); - /* - * multiply with NANO before dividing by scale and later divide by NANO - * again. - */ - offset = ((-1LL) * (s64)data->outmin) * NANO - - div_s64(div_s64((s64)data->pmin * NANO, scale), NANO); - data->offset = div_s64_rem(offset, NANO, &data->offset2); + odelta = data->outmax - data->outmin; + pdelta = data->pmax - data->pmin; + + data->scale = div_s64_rem(div_s64(pdelta * NANO, odelta), NANO, &tmp); + data->scale2 = tmp; + + data->offset = div_s64(odelta * data->pmin, pdelta) - data->outmin; if (data->irq > 0) { - ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, - IRQF_TRIGGER_RISING, - dev_name(dev), - data); + ret = devm_request_irq(dev, data->irq, mpr_eoc_handler, 0, + dev_name(dev), data); if (ret) - return dev_err_probe(dev, ret, - "request irq %d failed\n", data->irq); + return ret; } data->gpiod_reset = devm_gpiod_get_optional(dev, "reset", diff --git a/drivers/iio/pressure/mprls0025pa.h b/drivers/iio/pressure/mprls0025pa.h index d62a018eaff3..9f43273e635f 100644 --- a/drivers/iio/pressure/mprls0025pa.h +++ b/drivers/iio/pressure/mprls0025pa.h @@ -12,10 +12,7 @@ #define _MPRLS0025PA_H #include <linux/completion.h> -#include <linux/delay.h> -#include <linux/device.h> #include <linux/mutex.h> -#include <linux/stddef.h> #include <linux/types.h> #include <linux/iio/iio.h> @@ -28,9 +25,6 @@ struct device; -struct iio_chan_spec; -struct iio_dev; - struct mpr_data; struct mpr_ops; @@ -53,7 +47,6 @@ enum mpr_func_id { * @scale: pressure scale * @scale2: pressure scale, decimal number * @offset: pressure offset - * @offset2: pressure offset, decimal number * @gpiod_reset: reset * @irq: end of conversion irq. used to distinguish between irq mode and * reading in a loop until data is ready @@ -61,7 +54,8 @@ enum mpr_func_id { * @chan: channel values for buffered mode * @chan.pres: pressure value * @chan.ts: timestamp - * @buffer: raw conversion data + * @rx_buf: raw conversion data + * @tx_buf: output buffer */ struct mpr_data { struct device *dev; @@ -75,7 +69,6 @@ struct mpr_data { int scale; int scale2; int offset; - int offset2; struct gpio_desc *gpiod_reset; int irq; struct completion completion; @@ -83,11 +76,11 @@ struct mpr_data { s32 pres; aligned_s64 ts; } chan; - u8 buffer[MPR_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 rx_buf[MPR_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); + u8 tx_buf[MPR_MEASUREMENT_RD_SIZE]; }; struct mpr_ops { - int (*init)(struct device *dev); int (*read)(struct mpr_data *data, const u8 cmd, const u8 cnt); int (*write)(struct mpr_data *data, const u8 cmd, const u8 cnt); }; diff --git a/drivers/iio/pressure/mprls0025pa_i2c.c b/drivers/iio/pressure/mprls0025pa_i2c.c index 79811fd4a02b..0fe8cfe0d7e7 100644 --- a/drivers/iio/pressure/mprls0025pa_i2c.c +++ b/drivers/iio/pressure/mprls0025pa_i2c.c @@ -17,11 +17,6 @@ #include "mprls0025pa.h" -static int mpr_i2c_init(struct device *unused) -{ - return 0; -} - static int mpr_i2c_read(struct mpr_data *data, const u8 unused, const u8 cnt) { int ret; @@ -30,8 +25,7 @@ static int mpr_i2c_read(struct mpr_data *data, const u8 unused, const u8 cnt) if (cnt > MPR_MEASUREMENT_RD_SIZE) return -EOVERFLOW; - memset(data->buffer, 0, MPR_MEASUREMENT_RD_SIZE); - ret = i2c_master_recv(client, data->buffer, cnt); + ret = i2c_master_recv(client, data->rx_buf, cnt); if (ret < 0) return ret; else if (ret != cnt) @@ -44,9 +38,9 @@ static int mpr_i2c_write(struct mpr_data *data, const u8 cmd, const u8 unused) { int ret; struct i2c_client *client = to_i2c_client(data->dev); - u8 wdata[MPR_PKT_SYNC_LEN] = { cmd }; - ret = i2c_master_send(client, wdata, MPR_PKT_SYNC_LEN); + data->tx_buf[0] = cmd; + ret = i2c_master_send(client, data->tx_buf, MPR_PKT_SYNC_LEN); if (ret < 0) return ret; else if (ret != MPR_PKT_SYNC_LEN) @@ -56,7 +50,6 @@ static int mpr_i2c_write(struct mpr_data *data, const u8 cmd, const u8 unused) } static const struct mpr_ops mpr_i2c_ops = { - .init = mpr_i2c_init, .read = mpr_i2c_read, .write = mpr_i2c_write, }; diff --git a/drivers/iio/pressure/mprls0025pa_spi.c b/drivers/iio/pressure/mprls0025pa_spi.c index d04102f8a4a0..8c8c726f703f 100644 --- a/drivers/iio/pressure/mprls0025pa_spi.c +++ b/drivers/iio/pressure/mprls0025pa_spi.c @@ -8,6 +8,7 @@ * https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/micropressure-mpr-series/documents/sps-siot-mpr-series-datasheet-32332628-ciid-172626.pdf */ +#include <linux/array_size.h> #include <linux/device.h> #include <linux/errno.h> #include <linux/mod_devicetable.h> @@ -18,43 +19,31 @@ #include "mprls0025pa.h" -struct mpr_spi_buf { - u8 tx[MPR_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN); -}; - -static int mpr_spi_init(struct device *dev) -{ - struct spi_device *spi = to_spi_device(dev); - struct mpr_spi_buf *buf; - - buf = devm_kzalloc(dev, sizeof(*buf), GFP_KERNEL); - if (!buf) - return -ENOMEM; - - spi_set_drvdata(spi, buf); - - return 0; -} - static int mpr_spi_xfer(struct mpr_data *data, const u8 cmd, const u8 pkt_len) { struct spi_device *spi = to_spi_device(data->dev); - struct mpr_spi_buf *buf = spi_get_drvdata(spi); - struct spi_transfer xfer; + struct spi_transfer xfers[2] = { }; if (pkt_len > MPR_MEASUREMENT_RD_SIZE) return -EOVERFLOW; - buf->tx[0] = cmd; - xfer.tx_buf = buf->tx; - xfer.rx_buf = data->buffer; - xfer.len = pkt_len; + data->tx_buf[0] = cmd; + + /* + * Dummy transfer with no data, just cause a 2.5us+ delay between the CS assert + * and the first clock edge as per the datasheet tHDSS timing requirement. + */ + xfers[0].delay.value = 2500; + xfers[0].delay.unit = SPI_DELAY_UNIT_NSECS; + + xfers[1].tx_buf = data->tx_buf; + xfers[1].rx_buf = data->rx_buf; + xfers[1].len = pkt_len; - return spi_sync_transfer(spi, &xfer, 1); + return spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); } static const struct mpr_ops mpr_spi_ops = { - .init = mpr_spi_init, .read = mpr_spi_xfer, .write = mpr_spi_xfer, }; diff --git a/drivers/iio/proximity/rfd77402.c b/drivers/iio/proximity/rfd77402.c index aff60a3c1a6f..6afdbfca3e5a 100644 --- a/drivers/iio/proximity/rfd77402.c +++ b/drivers/iio/proximity/rfd77402.c @@ -6,19 +6,28 @@ * * 7-bit I2C slave address 0x4c * - * TODO: interrupt * https://media.digikey.com/pdf/Data%20Sheets/RF%20Digital%20PDFs/RFD77402.pdf */ -#include <linux/module.h> -#include <linux/i2c.h> +#include <linux/bits.h> +#include <linux/completion.h> #include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/errno.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/types.h> #include <linux/iio/iio.h> #define RFD77402_DRV_NAME "rfd77402" #define RFD77402_ICSR 0x00 /* Interrupt Control Status Register */ +#define RFD77402_ICSR_CLR_CFG BIT(0) +#define RFD77402_ICSR_CLR_TYPE BIT(1) #define RFD77402_ICSR_INT_MODE BIT(2) #define RFD77402_ICSR_INT_POL BIT(3) #define RFD77402_ICSR_RESULT BIT(4) @@ -26,6 +35,12 @@ #define RFD77402_ICSR_H2M_MSG BIT(6) #define RFD77402_ICSR_RESET BIT(7) +#define RFD77402_IER 0x02 +#define RFD77402_IER_RESULT BIT(0) +#define RFD77402_IER_M2H_MSG BIT(1) +#define RFD77402_IER_H2M_MSG BIT(2) +#define RFD77402_IER_RESET BIT(3) + #define RFD77402_CMD_R 0x04 #define RFD77402_CMD_SINGLE 0x01 #define RFD77402_CMD_STANDBY 0x10 @@ -76,10 +91,18 @@ static const struct { {RFD77402_HFCFG_3, 0x45d4}, }; +/** + * struct rfd77402_data - device-specific data for the RFD77402 sensor + * @client: I2C client handle + * @lock: mutex to serialize sensor reads + * @completion: completion used for interrupt-driven measurements + * @irq_en: indicates whether interrupt mode is enabled + */ struct rfd77402_data { struct i2c_client *client; - /* Serialize reads from the sensor */ struct mutex lock; + struct completion completion; + bool irq_en; }; static const struct iio_chan_spec rfd77402_channels[] = { @@ -90,6 +113,41 @@ static const struct iio_chan_spec rfd77402_channels[] = { }, }; +static irqreturn_t rfd77402_interrupt_handler(int irq, void *pdata) +{ + struct rfd77402_data *data = pdata; + int ret; + + ret = i2c_smbus_read_byte_data(data->client, RFD77402_ICSR); + if (ret < 0) + return IRQ_NONE; + + /* Check if the interrupt is from our device */ + if (!(ret & RFD77402_ICSR_RESULT)) + return IRQ_NONE; + + /* Signal completion of measurement */ + complete(&data->completion); + return IRQ_HANDLED; +} + +static int rfd77402_wait_for_irq(struct rfd77402_data *data) +{ + int ret; + + /* + * According to RFD77402 Datasheet v1.8, + * Section 3.1.1 "Single Measure" (Figure: Single Measure Flow Chart), + * the suggested timeout for single measure is 100 ms. + */ + ret = wait_for_completion_timeout(&data->completion, + msecs_to_jiffies(100)); + if (ret == 0) + return -ETIMEDOUT; + + return 0; +} + static int rfd77402_set_state(struct i2c_client *client, u8 state, u16 check) { int ret; @@ -110,10 +168,36 @@ static int rfd77402_set_state(struct i2c_client *client, u8 state, u16 check) return 0; } -static int rfd77402_measure(struct i2c_client *client) +static int rfd77402_wait_for_result(struct rfd77402_data *data) +{ + struct i2c_client *client = data->client; + int val, ret; + + if (data->irq_en) { + reinit_completion(&data->completion); + return rfd77402_wait_for_irq(data); + } + + /* + * As per RFD77402 datasheet section '3.1.1 Single Measure', the + * suggested timeout value for single measure is 100ms. + */ + ret = read_poll_timeout(i2c_smbus_read_byte_data, val, + (val < 0) || (val & RFD77402_ICSR_RESULT), + 10 * USEC_PER_MSEC, + 10 * 10 * USEC_PER_MSEC, + false, + client, RFD77402_ICSR); + if (val < 0) + return val; + + return ret; +} + +static int rfd77402_measure(struct rfd77402_data *data) { + struct i2c_client *client = data->client; int ret; - int tries = 10; ret = rfd77402_set_state(client, RFD77402_CMD_MCPU_ON, RFD77402_STATUS_MCPU_ON); @@ -126,19 +210,9 @@ static int rfd77402_measure(struct i2c_client *client) if (ret < 0) goto err; - while (tries-- > 0) { - ret = i2c_smbus_read_byte_data(client, RFD77402_ICSR); - if (ret < 0) - goto err; - if (ret & RFD77402_ICSR_RESULT) - break; - msleep(20); - } - - if (tries < 0) { - ret = -ETIMEDOUT; + ret = rfd77402_wait_for_result(data); + if (ret < 0) goto err; - } ret = i2c_smbus_read_word_data(client, RFD77402_RESULT_R); if (ret < 0) @@ -168,7 +242,7 @@ static int rfd77402_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: mutex_lock(&data->lock); - ret = rfd77402_measure(data->client); + ret = rfd77402_measure(data); mutex_unlock(&data->lock); if (ret < 0) return ret; @@ -188,8 +262,20 @@ static const struct iio_info rfd77402_info = { .read_raw = rfd77402_read_raw, }; -static int rfd77402_init(struct i2c_client *client) +static int rfd77402_config_irq(struct i2c_client *client, u8 csr, u8 ier) { + int ret; + + ret = i2c_smbus_write_byte_data(client, RFD77402_ICSR, csr); + if (ret) + return ret; + + return i2c_smbus_write_byte_data(client, RFD77402_IER, ier); +} + +static int rfd77402_init(struct rfd77402_data *data) +{ + struct i2c_client *client = data->client; int ret, i; ret = rfd77402_set_state(client, RFD77402_CMD_STANDBY, @@ -197,10 +283,26 @@ static int rfd77402_init(struct i2c_client *client) if (ret < 0) return ret; - /* configure INT pad as push-pull, active low */ - ret = i2c_smbus_write_byte_data(client, RFD77402_ICSR, - RFD77402_ICSR_INT_MODE); - if (ret < 0) + if (data->irq_en) { + /* + * Enable interrupt mode: + * - Configure ICSR for auto-clear on read and + * push-pull output + * - Enable "result ready" interrupt in IER + */ + ret = rfd77402_config_irq(client, + RFD77402_ICSR_CLR_CFG | + RFD77402_ICSR_INT_MODE, + RFD77402_IER_RESULT); + } else { + /* + * Disable all interrupts: + * - Clear ICSR configuration + * - Disable all interrupts in IER + */ + ret = rfd77402_config_irq(client, 0, 0); + } + if (ret) return ret; /* I2C configuration */ @@ -275,7 +377,26 @@ static int rfd77402_probe(struct i2c_client *client) data = iio_priv(indio_dev); data->client = client; - mutex_init(&data->lock); + + ret = devm_mutex_init(&client->dev, &data->lock); + if (ret) + return ret; + + init_completion(&data->completion); + + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, rfd77402_interrupt_handler, + IRQF_ONESHOT, + "rfd77402", data); + if (ret) + return ret; + + data->irq_en = true; + dev_dbg(&client->dev, "Using interrupt mode\n"); + } else { + dev_dbg(&client->dev, "Using polling mode\n"); + } indio_dev->info = &rfd77402_info; indio_dev->channels = rfd77402_channels; @@ -283,7 +404,7 @@ static int rfd77402_probe(struct i2c_client *client) indio_dev->name = RFD77402_DRV_NAME; indio_dev->modes = INDIO_DIRECT_MODE; - ret = rfd77402_init(client); + ret = rfd77402_init(data); if (ret < 0) return ret; @@ -301,7 +422,10 @@ static int rfd77402_suspend(struct device *dev) static int rfd77402_resume(struct device *dev) { - return rfd77402_init(to_i2c_client(dev)); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct rfd77402_data *data = iio_priv(indio_dev); + + return rfd77402_init(data); } static DEFINE_SIMPLE_DEV_PM_OPS(rfd77402_pm_ops, rfd77402_suspend, @@ -313,10 +437,17 @@ static const struct i2c_device_id rfd77402_id[] = { }; MODULE_DEVICE_TABLE(i2c, rfd77402_id); +static const struct of_device_id rfd77402_of_match[] = { + { .compatible = "rfdigital,rfd77402" }, + { } +}; +MODULE_DEVICE_TABLE(of, rfd77402_of_match); + static struct i2c_driver rfd77402_driver = { .driver = { .name = RFD77402_DRV_NAME, .pm = pm_sleep_ptr(&rfd77402_pm_ops), + .of_match_table = rfd77402_of_match, }, .probe = rfd77402_probe, .id_table = rfd77402_id, diff --git a/drivers/iio/temperature/tmp006.c b/drivers/iio/temperature/tmp006.c index 10bd3f221929..d8d8c8936d17 100644 --- a/drivers/iio/temperature/tmp006.c +++ b/drivers/iio/temperature/tmp006.c @@ -356,12 +356,10 @@ static int tmp006_probe(struct i2c_client *client) indio_dev->trig = iio_trigger_get(data->drdy_trig); - ret = devm_request_threaded_irq(&client->dev, client->irq, - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_ONESHOT, - "tmp006_irq", - data->drdy_trig); + ret = devm_request_irq(&client->dev, client->irq, + iio_trigger_generic_data_rdy_poll, + IRQF_NO_THREAD, "tmp006_irq", + data->drdy_trig); if (ret < 0) return ret; } diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig index 6e65e929791c..4fc17dd0dcd7 100644 --- a/drivers/iio/test/Kconfig +++ b/drivers/iio/test/Kconfig @@ -8,7 +8,6 @@ config IIO_GTS_KUNIT_TEST tristate "Test IIO gain-time-scale helpers" if !KUNIT_ALL_TESTS depends on KUNIT select IIO_GTS_HELPER - select TEST_KUNIT_DEVICE_HELPERS default KUNIT_ALL_TESTS help build unit tests for the IIO light sensor gain-time-scale helpers. diff --git a/drivers/interconnect/Kconfig b/drivers/interconnect/Kconfig index f2e49bd97d31..882dcb0b4a5b 100644 --- a/drivers/interconnect/Kconfig +++ b/drivers/interconnect/Kconfig @@ -22,4 +22,18 @@ config INTERCONNECT_CLK help Support for wrapping clocks into the interconnect nodes. +config INTERCONNECT_KUNIT_TEST + tristate "KUnit tests for Interconnect framework" + depends on KUNIT + default KUNIT_ALL_TESTS + help + This builds the KUnit test suite for the generic system interconnect + framework. + + The tests cover the core functionality of the interconnect subsystem, + including provider/consumer APIs, topology management, and bandwidth + aggregation logic. + + If unsure, say N. + endif diff --git a/drivers/interconnect/Makefile b/drivers/interconnect/Makefile index b0a9a6753b9d..dc4c7b657c9d 100644 --- a/drivers/interconnect/Makefile +++ b/drivers/interconnect/Makefile @@ -10,3 +10,5 @@ obj-$(CONFIG_INTERCONNECT_QCOM) += qcom/ obj-$(CONFIG_INTERCONNECT_SAMSUNG) += samsung/ obj-$(CONFIG_INTERCONNECT_CLK) += icc-clk.o + +obj-$(CONFIG_INTERCONNECT_KUNIT_TEST) += icc-kunit.o diff --git a/drivers/interconnect/icc-kunit.c b/drivers/interconnect/icc-kunit.c new file mode 100644 index 000000000000..bad2b583737b --- /dev/null +++ b/drivers/interconnect/icc-kunit.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the Interconnect framework. + * + * Copyright (c) 2025 Kuan-Wei Chiu <visitorckw@gmail.com> + * + * This suite verifies the behavior of the interconnect core, including + * topology construction, bandwidth aggregation, and path lifecycle. + */ + +#include <kunit/platform_device.h> +#include <kunit/test.h> +#include <linux/interconnect-provider.h> +#include <linux/interconnect.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include "internal.h" + +enum { + NODE_CPU, + NODE_GPU, + NODE_BUS, + NODE_DDR, + NODE_MAX +}; + +struct test_node_data { + int id; + const char *name; + int num_links; + int links[2]; +}; + +/* + * Static Topology: + * CPU -\ + * -> BUS -> DDR + * GPU -/ + */ +static const struct test_node_data test_topology[] = { + { NODE_CPU, "cpu", 1, { NODE_BUS } }, + { NODE_GPU, "gpu", 1, { NODE_BUS } }, + { NODE_BUS, "bus", 1, { NODE_DDR } }, + { NODE_DDR, "ddr", 0, { } }, +}; + +struct icc_test_priv { + struct icc_provider provider; + struct platform_device *pdev; + struct icc_node *nodes[NODE_MAX]; +}; + +static struct icc_node *get_node(struct icc_test_priv *priv, int id) +{ + int idx = id - NODE_CPU; + + if (idx < 0 || idx >= ARRAY_SIZE(test_topology)) + return NULL; + return priv->nodes[idx]; +} + +static int icc_test_set(struct icc_node *src, struct icc_node *dst) +{ + return 0; +} + +static int icc_test_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, + u32 peak_bw, u32 *agg_avg, u32 *agg_peak) +{ + return icc_std_aggregate(node, tag, avg_bw, peak_bw, agg_avg, agg_peak); +} + +static struct icc_node *icc_test_xlate(const struct of_phandle_args *spec, void *data) +{ + return NULL; +} + +static int icc_test_get_bw(struct icc_node *node, u32 *avg, u32 *peak) +{ + *avg = 0; + *peak = 0; + + return 0; +} + +static int icc_test_init(struct kunit *test) +{ + struct icc_test_priv *priv; + struct icc_node *node; + int i, j, ret; + + priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv); + test->priv = priv; + + priv->pdev = kunit_platform_device_alloc(test, "icc-test-dev", -1); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->pdev); + KUNIT_ASSERT_EQ(test, kunit_platform_device_add(test, priv->pdev), 0); + + priv->provider.set = icc_test_set; + priv->provider.aggregate = icc_test_aggregate; + priv->provider.xlate = icc_test_xlate; + priv->provider.get_bw = icc_test_get_bw; + priv->provider.dev = &priv->pdev->dev; + priv->provider.data = priv; + INIT_LIST_HEAD(&priv->provider.nodes); + + ret = icc_provider_register(&priv->provider); + KUNIT_ASSERT_EQ(test, ret, 0); + + for (i = 0; i < ARRAY_SIZE(test_topology); i++) { + const struct test_node_data *data = &test_topology[i]; + + node = icc_node_create(data->id); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node); + + node->name = data->name; + icc_node_add(node, &priv->provider); + priv->nodes[i] = node; + } + + for (i = 0; i < ARRAY_SIZE(test_topology); i++) { + const struct test_node_data *data = &test_topology[i]; + struct icc_node *src = get_node(priv, data->id); + + for (j = 0; j < data->num_links; j++) { + ret = icc_link_create(src, data->links[j]); + KUNIT_ASSERT_EQ_MSG(test, ret, 0, "Failed to link %s->%d", + src->name, data->links[j]); + } + } + + icc_sync_state(&priv->pdev->dev); + + return 0; +} + +static void icc_test_exit(struct kunit *test) +{ + struct icc_test_priv *priv = test->priv; + + icc_nodes_remove(&priv->provider); + icc_provider_deregister(&priv->provider); +} + +/* + * Helper to construct a mock path. + * + * Because we are bypassing icc_get(), we must manually link the requests + * to the nodes' req_list so that icc_std_aggregate() can discover them. + */ +static struct icc_path *icc_test_create_path(struct kunit *test, + struct icc_node **nodes, int num) +{ + struct icc_path *path; + int i; + + path = kunit_kzalloc(test, struct_size(path, reqs, num), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, path); + + path->num_nodes = num; + for (i = 0; i < num; i++) { + path->reqs[i].node = nodes[i]; + hlist_add_head(&path->reqs[i].req_node, &nodes[i]->req_list); + } + path->name = "mock-path"; + + return path; +} + +static void icc_test_destroy_path(struct kunit *test, struct icc_path *path) +{ + int i; + + for (i = 0; i < path->num_nodes; i++) + hlist_del(&path->reqs[i].req_node); + + kunit_kfree(test, path); +} + +static void icc_test_topology_integrity(struct kunit *test) +{ + struct icc_test_priv *priv = test->priv; + struct icc_node *cpu = get_node(priv, NODE_CPU); + struct icc_node *bus = get_node(priv, NODE_BUS); + + KUNIT_EXPECT_EQ(test, cpu->num_links, 1); + KUNIT_EXPECT_PTR_EQ(test, cpu->links[0], bus); + KUNIT_EXPECT_PTR_EQ(test, cpu->provider, &priv->provider); +} + +static void icc_test_set_bw(struct kunit *test) +{ + struct icc_test_priv *priv = test->priv; + struct icc_path *path; + struct icc_node *path_nodes[3]; + int ret; + + /* Path: CPU -> BUS -> DDR */ + path_nodes[0] = get_node(priv, NODE_CPU); + path_nodes[1] = get_node(priv, NODE_BUS); + path_nodes[2] = get_node(priv, NODE_DDR); + + path = icc_test_create_path(test, path_nodes, 3); + + ret = icc_enable(path); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = icc_set_bw(path, 1000, 2000); + KUNIT_EXPECT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, path_nodes[0]->avg_bw, 1000); + KUNIT_EXPECT_EQ(test, path_nodes[0]->peak_bw, 2000); + KUNIT_EXPECT_EQ(test, path_nodes[1]->avg_bw, 1000); + KUNIT_EXPECT_EQ(test, path_nodes[1]->peak_bw, 2000); + + icc_set_tag(path, 0xABC); + KUNIT_EXPECT_EQ(test, path->reqs[0].tag, 0xABC); + + icc_disable(path); + KUNIT_EXPECT_EQ(test, path_nodes[0]->avg_bw, 0); + + icc_test_destroy_path(test, path); +} + +static void icc_test_aggregation(struct kunit *test) +{ + struct icc_test_priv *priv = test->priv; + struct icc_path *path_cpu, *path_gpu; + struct icc_node *nodes_cpu[3], *nodes_gpu[2]; + struct icc_node *bus = get_node(priv, NODE_BUS); + int ret; + + nodes_cpu[0] = get_node(priv, NODE_CPU); + nodes_cpu[1] = bus; + nodes_cpu[2] = get_node(priv, NODE_DDR); + path_cpu = icc_test_create_path(test, nodes_cpu, 3); + + nodes_gpu[0] = get_node(priv, NODE_GPU); + nodes_gpu[1] = bus; + path_gpu = icc_test_create_path(test, nodes_gpu, 2); + + icc_enable(path_cpu); + icc_enable(path_gpu); + + ret = icc_set_bw(path_cpu, 1000, 1000); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, bus->avg_bw, 1000); + + ret = icc_set_bw(path_gpu, 2000, 2000); + KUNIT_EXPECT_EQ(test, ret, 0); + + /* Bus aggregates: CPU(1000) + GPU(2000) */ + KUNIT_EXPECT_EQ(test, bus->avg_bw, 3000); + /* Peak aggregates: max(CPU, GPU) */ + KUNIT_EXPECT_EQ(test, bus->peak_bw, 2000); + + icc_test_destroy_path(test, path_cpu); + icc_test_destroy_path(test, path_gpu); +} + +static void icc_test_bulk_ops(struct kunit *test) +{ + struct icc_test_priv *priv = test->priv; + struct icc_node *nodes_cpu[3], *nodes_gpu[2]; + struct icc_bulk_data bulk[2]; + int ret; + + nodes_cpu[0] = get_node(priv, NODE_CPU); + nodes_cpu[1] = get_node(priv, NODE_BUS); + nodes_cpu[2] = get_node(priv, NODE_DDR); + + nodes_gpu[0] = get_node(priv, NODE_GPU); + nodes_gpu[1] = get_node(priv, NODE_BUS); + + bulk[0].path = icc_test_create_path(test, nodes_cpu, 3); + bulk[0].avg_bw = 500; + bulk[0].peak_bw = 500; + + bulk[1].path = icc_test_create_path(test, nodes_gpu, 2); + bulk[1].avg_bw = 600; + bulk[1].peak_bw = 600; + + ret = icc_bulk_set_bw(2, bulk); + KUNIT_EXPECT_EQ(test, ret, 0); + /* Paths disabled, bandwidth should be 0 */ + KUNIT_EXPECT_EQ(test, get_node(priv, NODE_BUS)->avg_bw, 0); + + ret = icc_bulk_enable(2, bulk); + KUNIT_EXPECT_EQ(test, ret, 0); + /* Paths enabled, aggregation applies */ + KUNIT_EXPECT_EQ(test, get_node(priv, NODE_BUS)->avg_bw, 1100); + + icc_bulk_disable(2, bulk); + KUNIT_EXPECT_EQ(test, get_node(priv, NODE_BUS)->avg_bw, 0); + + icc_test_destroy_path(test, bulk[0].path); + icc_test_destroy_path(test, bulk[1].path); +} + +static struct kunit_case icc_test_cases[] = { + KUNIT_CASE(icc_test_topology_integrity), + KUNIT_CASE(icc_test_set_bw), + KUNIT_CASE(icc_test_aggregation), + KUNIT_CASE(icc_test_bulk_ops), + {} +}; + +static struct kunit_suite icc_test_suite = { + .name = "interconnect", + .init = icc_test_init, + .exit = icc_test_exit, + .test_cases = icc_test_cases, +}; + +kunit_test_suite(icc_test_suite); + +MODULE_AUTHOR("Kuan-Wei Chiu <visitorckw@gmail.com>"); +MODULE_DESCRIPTION("KUnit tests for the Interconnect framework"); +MODULE_LICENSE("GPL"); diff --git a/drivers/interconnect/mediatek/Kconfig b/drivers/interconnect/mediatek/Kconfig index 985c849efac3..9fd3f2170443 100644 --- a/drivers/interconnect/mediatek/Kconfig +++ b/drivers/interconnect/mediatek/Kconfig @@ -27,3 +27,10 @@ config INTERCONNECT_MTK_MT8195 help This is a driver for the MediaTek bus interconnect on MT8195-based platforms. + +config INTERCONNECT_MTK_MT8196 + tristate "MediaTek MT8196 interconnect driver" + depends on INTERCONNECT_MTK_DVFSRC_EMI + help + This is a driver for the MediaTek bus interconnect on MT8196-based + platforms. diff --git a/drivers/interconnect/mediatek/Makefile b/drivers/interconnect/mediatek/Makefile index 8e2283a9a5b5..6bd656668f5d 100644 --- a/drivers/interconnect/mediatek/Makefile +++ b/drivers/interconnect/mediatek/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_INTERCONNECT_MTK_DVFSRC_EMI) += icc-emi.o obj-$(CONFIG_INTERCONNECT_MTK_MT8183) += mt8183.o obj-$(CONFIG_INTERCONNECT_MTK_MT8195) += mt8195.o +obj-$(CONFIG_INTERCONNECT_MTK_MT8195) += mt8196.o diff --git a/drivers/interconnect/mediatek/icc-emi.c b/drivers/interconnect/mediatek/icc-emi.c index 7da740b5fa8d..dfa3a9cd9399 100644 --- a/drivers/interconnect/mediatek/icc-emi.c +++ b/drivers/interconnect/mediatek/icc-emi.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_platform.h> +#include <linux/overflow.h> #include <linux/platform_device.h> #include <linux/soc/mediatek/dvfsrc.h> @@ -22,7 +23,9 @@ static int mtk_emi_icc_aggregate(struct icc_node *node, u32 tag, u32 avg_bw, { struct mtk_icc_node *in = node->data; - *agg_avg += avg_bw; + if (check_add_overflow(*agg_avg, avg_bw, agg_avg)) + *agg_avg = U32_MAX; + *agg_peak = max_t(u32, *agg_peak, peak_bw); in->sum_avg = *agg_avg; @@ -40,7 +43,7 @@ static int mtk_emi_icc_set(struct icc_node *src, struct icc_node *dst) if (unlikely(!src->provider)) return -EINVAL; - dev = src->provider->dev; + dev = src->provider->dev->parent; switch (node->ep) { case 0: @@ -97,7 +100,7 @@ int mtk_emi_icc_probe(struct platform_device *pdev) if (!data) return -ENOMEM; - provider->dev = pdev->dev.parent; + provider->dev = dev; provider->set = mtk_emi_icc_set; provider->aggregate = mtk_emi_icc_aggregate; provider->xlate = of_icc_xlate_onecell; diff --git a/drivers/interconnect/mediatek/mt8196.c b/drivers/interconnect/mediatek/mt8196.c new file mode 100644 index 000000000000..e9af32065be1 --- /dev/null +++ b/drivers/interconnect/mediatek/mt8196.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2025 Collabora Ltd. + * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> + */ + +#include <linux/device.h> +#include <linux/interconnect.h> +#include <linux/interconnect-provider.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <dt-bindings/interconnect/mediatek,mt8196.h> + +#include "icc-emi.h" + +static struct mtk_icc_node ddr_emi = { + .name = "ddr-emi", + .id = SLAVE_DDR_EMI, + .ep = 1, +}; + +static struct mtk_icc_node mcusys = { + .name = "mcusys", + .id = MASTER_MCUSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mcu_port0 = { + .name = "mcu-port0", + .id = MASTER_MCU_0, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mcu_port1 = { + .name = "mcu-port1", + .id = MASTER_MCU_1, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mcu_port2 = { + .name = "mcu-port2", + .id = MASTER_MCU_2, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mcu_port3 = { + .name = "mcu-port3", + .id = MASTER_MCU_3, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mcu_port4 = { + .name = "mcu-port4", + .id = MASTER_MCU_4, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node gpu = { + .name = "gpu", + .id = MASTER_GPUSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mmsys = { + .name = "mmsys", + .id = MASTER_MMSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mm_vpu = { + .name = "mm-vpu", + .id = MASTER_MM_VPU, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_disp = { + .name = "mm-disp", + .id = MASTER_MM_DISP, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_vdec = { + .name = "mm-vdec", + .id = MASTER_MM_VDEC, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_venc = { + .name = "mm-venc", + .id = MASTER_MM_VENC, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_cam = { + .name = "mm-cam", + .id = MASTER_MM_CAM, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_img = { + .name = "mm-img", + .id = MASTER_MM_IMG, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node mm_mdp = { + .name = "mm-mdp", + .id = MASTER_MM_MDP, + .ep = 0, + .num_links = 1, + .links = { MASTER_MMSYS } +}; + +static struct mtk_icc_node vpusys = { + .name = "vpusys", + .id = MASTER_VPUSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node vpu_port0 = { + .name = "vpu-port0", + .id = MASTER_VPU_0, + .ep = 0, + .num_links = 1, + .links = { MASTER_VPUSYS } +}; + +static struct mtk_icc_node vpu_port1 = { + .name = "vpu-port1", + .id = MASTER_VPU_1, + .ep = 0, + .num_links = 1, + .links = { MASTER_VPUSYS } +}; + +static struct mtk_icc_node mdlasys = { + .name = "mdlasys", + .id = MASTER_MDLASYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node mdla_port0 = { + .name = "mdla-port0", + .id = MASTER_MDLA_0, + .ep = 0, + .num_links = 1, + .links = { MASTER_MDLASYS } +}; + +static struct mtk_icc_node ufs = { + .name = "ufs", + .id = MASTER_UFS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node pcie = { + .name = "pcie", + .id = MASTER_PCIE, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node usb = { + .name = "usb", + .id = MASTER_USB, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node wifi = { + .name = "wifi", + .id = MASTER_WIFI, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node bt = { + .name = "bt", + .id = MASTER_BT, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node netsys = { + .name = "netsys", + .id = MASTER_NETSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node dbgif = { + .name = "dbgif", + .id = MASTER_DBGIF, + .ep = 0, + .num_links = 1, + .links = { SLAVE_DDR_EMI } +}; + +static struct mtk_icc_node hrt_ddr_emi = { + .name = "hrt-ddr-emi", + .id = SLAVE_HRT_DDR_EMI, + .ep = 2, +}; + +static struct mtk_icc_node hrt_mmsys = { + .name = "hrt-mmsys", + .id = MASTER_HRT_MMSYS, + .ep = 0, + .num_links = 1, + .links = { SLAVE_HRT_DDR_EMI } +}; + +static struct mtk_icc_node hrt_mm_disp = { + .name = "hrt-mm-disp", + .id = MASTER_HRT_MM_DISP, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_mm_vdec = { + .name = "hrt-mm-vdec", + .id = MASTER_HRT_MM_VDEC, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_mm_venc = { + .name = "hrt-mm-venc", + .id = MASTER_HRT_MM_VENC, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_mm_cam = { + .name = "hrt-mm-cam", + .id = MASTER_HRT_MM_CAM, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_mm_img = { + .name = "hrt-mm-img", + .id = MASTER_HRT_MM_IMG, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_mm_mdp = { + .name = "hrt-mm-mdp", + .id = MASTER_HRT_MM_MDP, + .ep = 0, + .num_links = 1, + .links = { MASTER_HRT_MMSYS } +}; + +static struct mtk_icc_node hrt_adsp = { + .name = "hrt-adsp", + .id = MASTER_HRT_ADSP, + .ep = 0, + .num_links = 1, + .links = { SLAVE_HRT_DDR_EMI } +}; + +static struct mtk_icc_node hrt_dbgif = { + .name = "hrt-dbgif", + .id = MASTER_HRT_DBGIF, + .ep = 0, + .num_links = 1, + .links = { SLAVE_HRT_DDR_EMI } +}; + +static struct mtk_icc_node *mt8196_emi_icc_nodes[] = { + [SLAVE_DDR_EMI] = &ddr_emi, + [MASTER_MCUSYS] = &mcusys, + [MASTER_MCU_0] = &mcu_port0, + [MASTER_MCU_1] = &mcu_port1, + [MASTER_MCU_2] = &mcu_port2, + [MASTER_MCU_3] = &mcu_port3, + [MASTER_MCU_4] = &mcu_port4, + [MASTER_GPUSYS] = &gpu, + [MASTER_MMSYS] = &mmsys, + [MASTER_MM_VPU] = &mm_vpu, + [MASTER_MM_DISP] = &mm_disp, + [MASTER_MM_VDEC] = &mm_vdec, + [MASTER_MM_VENC] = &mm_venc, + [MASTER_MM_CAM] = &mm_cam, + [MASTER_MM_IMG] = &mm_img, + [MASTER_MM_MDP] = &mm_mdp, + [MASTER_VPUSYS] = &vpusys, + [MASTER_VPU_0] = &vpu_port0, + [MASTER_VPU_1] = &vpu_port1, + [MASTER_MDLASYS] = &mdlasys, + [MASTER_MDLA_0] = &mdla_port0, + [MASTER_UFS] = &ufs, + [MASTER_PCIE] = &pcie, + [MASTER_USB] = &usb, + [MASTER_WIFI] = &wifi, + [MASTER_BT] = &bt, + [MASTER_NETSYS] = &netsys, + [MASTER_DBGIF] = &dbgif, + [SLAVE_HRT_DDR_EMI] = &hrt_ddr_emi, + [MASTER_HRT_MMSYS] = &hrt_mmsys, + [MASTER_HRT_MM_DISP] = &hrt_mm_disp, + [MASTER_HRT_MM_VDEC] = &hrt_mm_vdec, + [MASTER_HRT_MM_VENC] = &hrt_mm_venc, + [MASTER_HRT_MM_CAM] = &hrt_mm_cam, + [MASTER_HRT_MM_IMG] = &hrt_mm_img, + [MASTER_HRT_MM_MDP] = &hrt_mm_mdp, + [MASTER_HRT_ADSP] = &hrt_adsp, + [MASTER_HRT_DBGIF] = &hrt_dbgif +}; + +static struct mtk_icc_desc mt8196_emi_icc = { + .nodes = mt8196_emi_icc_nodes, + .num_nodes = ARRAY_SIZE(mt8196_emi_icc_nodes), +}; + +static const struct of_device_id mtk_mt8196_emi_icc_of_match[] = { + { .compatible = "mediatek,mt8196-emi", .data = &mt8196_emi_icc }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mtk_mt8196_emi_icc_of_match); + +static struct platform_driver mtk_emi_icc_mt8196_driver = { + .driver = { + .name = "emi-icc-mt8196", + .of_match_table = mtk_mt8196_emi_icc_of_match, + .sync_state = icc_sync_state, + }, + .probe = mtk_emi_icc_probe, + .remove = mtk_emi_icc_remove, + +}; +module_platform_driver(mtk_emi_icc_mt8196_driver); + +MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>"); +MODULE_DESCRIPTION("MediaTek MT8196 EMI ICC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/interconnect/qcom/msm8974.c b/drivers/interconnect/qcom/msm8974.c index 469fc48ebfe9..3239edc37f02 100644 --- a/drivers/interconnect/qcom/msm8974.c +++ b/drivers/interconnect/qcom/msm8974.c @@ -173,9 +173,6 @@ enum { MSM8974_SNOC_SLV_QDSS_STM, }; -#define RPM_BUS_MASTER_REQ 0x73616d62 -#define RPM_BUS_SLAVE_REQ 0x766c7362 - #define to_msm8974_icc_provider(_provider) \ container_of(_provider, struct msm8974_icc_provider, provider) diff --git a/drivers/interconnect/qcom/qcs8300.c b/drivers/interconnect/qcom/qcs8300.c index 70a377bbcf29..bc403a9bf68c 100644 --- a/drivers/interconnect/qcom/qcs8300.c +++ b/drivers/interconnect/qcom/qcs8300.c @@ -629,7 +629,7 @@ static struct qcom_icc_node qxm_nsp = { .name = "qxm_nsp", .channels = 2, .buswidth = 32, - .num_links = 1, + .num_links = 2, .link_nodes = { &qns_hcp, &qns_nsp_gemnoc }, }; diff --git a/drivers/interconnect/qcom/smd-rpm.c b/drivers/interconnect/qcom/smd-rpm.c index 8316c87a2c60..dbc7ae50b02b 100644 --- a/drivers/interconnect/qcom/smd-rpm.c +++ b/drivers/interconnect/qcom/smd-rpm.c @@ -14,7 +14,6 @@ #include "icc-rpm.h" #define RPM_KEY_BW 0x00007762 -#define QCOM_RPM_SMD_KEY_RATE 0x007a484b static struct qcom_smd_rpm *icc_smd_rpm; diff --git a/drivers/mcb/mcb-core.c b/drivers/mcb/mcb-core.c index c1367223e71a..3d487d75c483 100644 --- a/drivers/mcb/mcb-core.c +++ b/drivers/mcb/mcb-core.c @@ -85,7 +85,8 @@ static void mcb_remove(struct device *dev) struct mcb_device *mdev = to_mcb_device(dev); struct module *carrier_mod; - mdrv->remove(mdev); + if (mdrv->remove) + mdrv->remove(mdev); carrier_mod = mdev->dev.parent->driver->owner; module_put(carrier_mod); @@ -176,13 +177,13 @@ static const struct device_type mcb_carrier_device_type = { * @owner: The @mcb_driver's module * @mod_name: The name of the @mcb_driver's module * - * Register a @mcb_driver at the system. Perform some sanity checks, if - * the .probe and .remove methods are provided by the driver. + * Register a @mcb_driver at the system. Perform a sanity check, if + * .probe method is provided by the driver. */ int __mcb_register_driver(struct mcb_driver *drv, struct module *owner, const char *mod_name) { - if (!drv->probe || !drv->remove) + if (!drv->probe) return -EINVAL; drv->driver.owner = owner; diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.c b/drivers/misc/bcm-vk/bcm_vk_msg.c index 1f42d1d5a630..665a3888708a 100644 --- a/drivers/misc/bcm-vk/bcm_vk_msg.c +++ b/drivers/misc/bcm-vk/bcm_vk_msg.c @@ -1010,6 +1010,9 @@ ssize_t bcm_vk_read(struct file *p_file, struct device *dev = &vk->pdev->dev; struct bcm_vk_msg_chan *chan = &vk->to_h_msg_chan; struct bcm_vk_wkent *entry = NULL, *iter; + struct vk_msg_blk tmp_msg; + u32 tmp_usr_msg_id; + u32 tmp_blks; u32 q_num; u32 rsp_length; @@ -1034,6 +1037,9 @@ ssize_t bcm_vk_read(struct file *p_file, entry = iter; } else { /* buffer not big enough */ + tmp_msg = iter->to_h_msg[0]; + tmp_usr_msg_id = iter->usr_msg_id; + tmp_blks = iter->to_h_blks; rc = -EMSGSIZE; } goto read_loop_exit; @@ -1052,14 +1058,12 @@ read_loop_exit: bcm_vk_free_wkent(dev, entry); } else if (rc == -EMSGSIZE) { - struct vk_msg_blk tmp_msg = entry->to_h_msg[0]; - /* * in this case, return just the first block, so * that app knows what size it is looking for. */ - set_msg_id(&tmp_msg, entry->usr_msg_id); - tmp_msg.size = entry->to_h_blks - 1; + set_msg_id(&tmp_msg, tmp_usr_msg_id); + tmp_msg.size = tmp_blks - 1; if (copy_to_user(buf, &tmp_msg, VK_MSGQ_BLK_SIZE) != 0) { dev_err(dev, "Error return 1st block in -EMSGSIZE\n"); rc = -EFAULT; diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index 883dfd0ed658..bc2cfb75d9bb 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -34,6 +34,7 @@ */ #define FM25_SN_LEN 8 /* serial number length */ +#define FM25_MAX_ID_LEN 9 /* ID length */ #define EE_MAXADDRLEN 3 /* 24 bit addresses, up to 2 MBytes */ struct at25_data { @@ -44,6 +45,8 @@ struct at25_data { struct nvmem_config nvmem_config; struct nvmem_device *nvmem; u8 sernum[FM25_SN_LEN]; + u8 id[FM25_MAX_ID_LEN]; + u8 id_len; }; #define AT25_WREN 0x06 /* latch the write enable */ @@ -64,8 +67,6 @@ struct at25_data { #define AT25_INSTR_BIT3 0x08 /* additional address bit in instr */ -#define FM25_ID_LEN 9 /* ID length */ - /* * Specs often allow 5ms for a page write, sometimes 20ms; * it's important to recover from write timeouts. @@ -180,11 +181,25 @@ static ssize_t sernum_show(struct device *dev, struct device_attribute *attr, ch } static DEVICE_ATTR_RO(sernum); -static struct attribute *sernum_attrs[] = { +static ssize_t jedec_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct at25_data *at25; + + at25 = dev_get_drvdata(dev); + + if (!at25->id_len) + return -EOPNOTSUPP; + + return sysfs_emit(buf, "%*phN\n", at25->id_len, at25->id); +} +static DEVICE_ATTR_RO(jedec_id); + +static struct attribute *at25_attrs[] = { &dev_attr_sernum.attr, + &dev_attr_jedec_id.attr, NULL, }; -ATTRIBUTE_GROUPS(sernum); +ATTRIBUTE_GROUPS(at25); /* * Poll Read Status Register with timeout @@ -378,7 +393,7 @@ 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]; + u8 id[FM25_MAX_ID_LEN]; u32 val; int i; @@ -388,7 +403,12 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) chip->byte_len = val; } else { /* Get ID of chip */ - fm25_aux_read(at25, id, FM25_RDID, FM25_ID_LEN); + fm25_aux_read(at25, id, FM25_RDID, FM25_MAX_ID_LEN); + + /* Store the unprocessed ID for exposing via sysfs */ + memcpy(at25->id, id, FM25_MAX_ID_LEN); + at25->id_len = FM25_MAX_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++) { @@ -398,30 +418,42 @@ static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) 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; - } - switch (id[7]) { - case 0x21 ... 0x26: - chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; - break; - case 0x2a ... 0x30: - /* CY15B102QN ... CY15B116QN */ - chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13); - break; - default: - dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]); + if (id[6] == 0xc2) { + at25->id_len = 9; + switch (id[7]) { + case 0x21 ... 0x26: + chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; + break; + case 0x2a ... 0x30: + /* CY15B102QN ... 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; + } + } else if (id[2] == 0x82 && id[3] == 0x06) { + at25->id_len = 8; + switch (id[1]) { + case 0x51 ... 0x54: + /* CY15B102QSN ... CY15B204QSN */ + chip->byte_len = BIT(((id[0] >> 3) & 0x1F) + 9); + break; + default: + dev_err(dev, "Error: unsupported product id %02x\n", id[1]); + return -ENODEV; + } + } else { + dev_err(dev, "Error: unrecognized JEDEC ID format: %*ph\n", + FM25_MAX_ID_LEN, id); 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]; - } + 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) @@ -539,7 +571,7 @@ static struct spi_mem_driver at25_driver = { .driver = { .name = "at25", .of_match_table = at25_of_match, - .dev_groups = sernum_groups, + .dev_groups = at25_groups, }, .id_table = at25_spi_ids, }, diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c index 9cae6f530679..5230e910a1d1 100644 --- a/drivers/misc/eeprom/eeprom_93xx46.c +++ b/drivers/misc/eeprom/eeprom_93xx46.c @@ -45,6 +45,7 @@ struct eeprom_93xx46_platform_data { #define OP_START 0x4 #define OP_WRITE (OP_START | 0x1) #define OP_READ (OP_START | 0x2) +/* The following addresses are offset for the 1K EEPROM variant in 16-bit mode */ #define ADDR_EWDS 0x00 #define ADDR_ERAL 0x20 #define ADDR_EWEN 0x30 @@ -191,10 +192,7 @@ static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) bits = edev->addrlen + 3; cmd_addr = OP_START << edev->addrlen; - if (edev->pdata->flags & EE_ADDR8) - cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS) << 1; - else - cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS); + cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS) << (edev->addrlen - 6); if (has_quirk_instruction_length(edev)) { cmd_addr <<= 2; @@ -328,10 +326,7 @@ static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev) bits = edev->addrlen + 3; cmd_addr = OP_START << edev->addrlen; - if (edev->pdata->flags & EE_ADDR8) - cmd_addr |= ADDR_ERAL << 1; - else - cmd_addr |= ADDR_ERAL; + cmd_addr |= ADDR_ERAL << (edev->addrlen - 6); if (has_quirk_instruction_length(edev)) { cmd_addr <<= 2; diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index ee652ef01534..4f5a79c50f58 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -22,6 +22,7 @@ #include <linux/firmware/qcom/qcom_scm.h> #include <uapi/misc/fastrpc.h> #include <linux/of_reserved_mem.h> +#include <linux/bits.h> #define ADSP_DOMAIN_ID (0) #define MDSP_DOMAIN_ID (1) @@ -33,7 +34,6 @@ #define FASTRPC_ALIGN 128 #define FASTRPC_MAX_FDLIST 16 #define FASTRPC_MAX_CRCLIST 64 -#define FASTRPC_PHYS(p) ((p) & 0xffffffff) #define FASTRPC_CTX_MAX (256) #define FASTRPC_INIT_HANDLE 1 #define FASTRPC_DSP_UTILITIES_HANDLE 2 @@ -106,7 +106,7 @@ #define miscdev_to_fdevice(d) container_of(d, struct fastrpc_device, miscdev) struct fastrpc_phy_page { - u64 addr; /* physical address */ + dma_addr_t addr; /* dma address */ u64 size; /* size of contiguous region */ }; @@ -171,7 +171,7 @@ struct fastrpc_msg { u64 ctx; /* invoke caller context */ u32 handle; /* handle to invoke */ u32 sc; /* scalars structure describing the data */ - u64 addr; /* physical address */ + dma_addr_t addr; /* dma address */ u64 size; /* size of contiguous region */ }; @@ -194,7 +194,7 @@ struct fastrpc_buf { struct dma_buf *dmabuf; struct device *dev; void *virt; - u64 phys; + dma_addr_t dma_addr; u64 size; /* Lock for dma buf attachments */ struct mutex lock; @@ -217,7 +217,7 @@ struct fastrpc_map { struct dma_buf *buf; struct sg_table *table; struct dma_buf_attachment *attach; - u64 phys; + dma_addr_t dma_addr; u64 size; void *va; u64 len; @@ -257,6 +257,12 @@ struct fastrpc_session_ctx { bool valid; }; +struct fastrpc_soc_data { + u32 sid_pos; + u32 dma_addr_bits_cdsp; + u32 dma_addr_bits_default; +}; + struct fastrpc_channel_ctx { int domain_id; int sesscount; @@ -278,6 +284,7 @@ struct fastrpc_channel_ctx { bool secure; bool unsigned_support; u64 dma_mask; + const struct fastrpc_soc_data *soc_data; }; struct fastrpc_device { @@ -305,6 +312,24 @@ struct fastrpc_user { struct mutex mutex; }; +/* Extract SMMU PA from consolidated IOVA */ +static inline dma_addr_t fastrpc_ipa_to_dma_addr(struct fastrpc_channel_ctx *cctx, dma_addr_t iova) +{ + if (!cctx->soc_data->sid_pos) + return 0; + return iova & GENMASK_ULL(cctx->soc_data->sid_pos - 1, 0); +} + +/* + * Prepare the consolidated iova to send to DSP by prepending the SID + * to smmu PA at the appropriate position + */ +static inline u64 fastrpc_sid_offset(struct fastrpc_channel_ctx *cctx, + struct fastrpc_session_ctx *sctx) +{ + return (u64)sctx->sid << cctx->soc_data->sid_pos; +} + static void fastrpc_free_map(struct kref *ref) { struct fastrpc_map *map; @@ -320,11 +345,12 @@ static void fastrpc_free_map(struct kref *ref) perm.vmid = QCOM_SCM_VMID_HLOS; perm.perm = QCOM_SCM_PERM_RWX; - err = qcom_scm_assign_mem(map->phys, map->len, + err = qcom_scm_assign_mem(map->dma_addr, map->len, &src_perms, &perm, 1); if (err) { - dev_err(map->fl->sctx->dev, "Failed to assign memory phys 0x%llx size 0x%llx err %d\n", - map->phys, map->len, err); + dev_err(map->fl->sctx->dev, + "Failed to assign memory dma_addr %pad size 0x%llx err %d\n", + &map->dma_addr, map->len, err); return; } } @@ -389,7 +415,7 @@ static int fastrpc_map_lookup(struct fastrpc_user *fl, int fd, static void fastrpc_buf_free(struct fastrpc_buf *buf) { dma_free_coherent(buf->dev, buf->size, buf->virt, - FASTRPC_PHYS(buf->phys)); + fastrpc_ipa_to_dma_addr(buf->fl->cctx, buf->dma_addr)); kfree(buf); } @@ -408,12 +434,12 @@ static int __fastrpc_buf_alloc(struct fastrpc_user *fl, struct device *dev, buf->fl = fl; buf->virt = NULL; - buf->phys = 0; + buf->dma_addr = 0; buf->size = size; buf->dev = dev; buf->raddr = 0; - buf->virt = dma_alloc_coherent(dev, buf->size, (dma_addr_t *)&buf->phys, + buf->virt = dma_alloc_coherent(dev, buf->size, &buf->dma_addr, GFP_KERNEL); if (!buf->virt) { mutex_destroy(&buf->lock); @@ -439,7 +465,7 @@ static int fastrpc_buf_alloc(struct fastrpc_user *fl, struct device *dev, buf = *obuf; if (fl->sctx && fl->sctx->sid) - buf->phys += ((u64)fl->sctx->sid << 32); + buf->dma_addr += fastrpc_sid_offset(fl->cctx, fl->sctx); return 0; } @@ -684,7 +710,8 @@ static int fastrpc_dma_buf_attach(struct dma_buf *dmabuf, return -ENOMEM; ret = dma_get_sgtable(buffer->dev, &a->sgt, buffer->virt, - FASTRPC_PHYS(buffer->phys), buffer->size); + fastrpc_ipa_to_dma_addr(buffer->fl->cctx, buffer->dma_addr), + buffer->size); if (ret < 0) { dev_err(buffer->dev, "failed to get scatterlist from DMA API\n"); kfree(a); @@ -733,7 +760,7 @@ static int fastrpc_mmap(struct dma_buf *dmabuf, dma_resv_assert_held(dmabuf->resv); return dma_mmap_coherent(buf->dev, vma, buf->virt, - FASTRPC_PHYS(buf->phys), size); + fastrpc_ipa_to_dma_addr(buf->fl->cctx, buf->dma_addr), size); } static const struct dma_buf_ops fastrpc_dma_buf_ops = { @@ -746,6 +773,11 @@ static const struct dma_buf_ops fastrpc_dma_buf_ops = { .release = fastrpc_release, }; +static dma_addr_t fastrpc_compute_dma_addr(struct fastrpc_user *fl, dma_addr_t sg_dma_addr) +{ + return sg_dma_addr + fastrpc_sid_offset(fl->cctx, fl->sctx); +} + static int fastrpc_map_attach(struct fastrpc_user *fl, int fd, u64 len, u32 attr, struct fastrpc_map **ppmap) { @@ -784,12 +816,10 @@ static int fastrpc_map_attach(struct fastrpc_user *fl, int fd, } map->table = table; - if (attr & FASTRPC_ATTR_SECUREMAP) { - map->phys = sg_phys(map->table->sgl); - } else { - map->phys = sg_dma_address(map->table->sgl); - map->phys += ((u64)fl->sctx->sid << 32); - } + if (attr & FASTRPC_ATTR_SECUREMAP) + map->dma_addr = sg_phys(map->table->sgl); + else + map->dma_addr = fastrpc_compute_dma_addr(fl, sg_dma_address(map->table->sgl)); for_each_sg(map->table->sgl, sgl, map->table->nents, sgl_index) map->size += sg_dma_len(sgl); @@ -815,10 +845,11 @@ static int fastrpc_map_attach(struct fastrpc_user *fl, int fd, dst_perms[1].vmid = fl->cctx->vmperms[0].vmid; dst_perms[1].perm = QCOM_SCM_PERM_RWX; map->attr = attr; - err = qcom_scm_assign_mem(map->phys, (u64)map->len, &src_perms, dst_perms, 2); + err = qcom_scm_assign_mem(map->dma_addr, (u64)map->len, &src_perms, dst_perms, 2); if (err) { - dev_err(sess->dev, "Failed to assign memory with phys 0x%llx size 0x%llx err %d\n", - map->phys, map->len, err); + dev_err(sess->dev, + "Failed to assign memory with dma_addr %pad size 0x%llx err %d\n", + &map->dma_addr, map->len, err); goto map_err; } } @@ -1009,7 +1040,7 @@ static int fastrpc_get_args(u32 kernel, struct fastrpc_invoke_ctx *ctx) struct vm_area_struct *vma = NULL; rpra[i].buf.pv = (u64) ctx->args[i].ptr; - pages[i].addr = ctx->maps[i]->phys; + pages[i].addr = ctx->maps[i]->dma_addr; mmap_read_lock(current->mm); vma = find_vma(current->mm, ctx->args[i].ptr); @@ -1036,7 +1067,7 @@ static int fastrpc_get_args(u32 kernel, struct fastrpc_invoke_ctx *ctx) goto bail; rpra[i].buf.pv = args - ctx->olaps[oix].offset; - pages[i].addr = ctx->buf->phys - + pages[i].addr = ctx->buf->dma_addr - ctx->olaps[oix].offset + (pkt_size - rlen); pages[i].addr = pages[i].addr & PAGE_MASK; @@ -1068,7 +1099,7 @@ static int fastrpc_get_args(u32 kernel, struct fastrpc_invoke_ctx *ctx) list[i].num = ctx->args[i].length ? 1 : 0; list[i].pgidx = i; if (ctx->maps[i]) { - pages[i].addr = ctx->maps[i]->phys; + pages[i].addr = ctx->maps[i]->dma_addr; pages[i].size = ctx->maps[i]->size; } rpra[i].dma.fd = ctx->args[i].fd; @@ -1150,7 +1181,7 @@ static int fastrpc_invoke_send(struct fastrpc_session_ctx *sctx, msg->ctx = ctx->ctxid | fl->pd; msg->handle = handle; msg->sc = ctx->sc; - msg->addr = ctx->buf ? ctx->buf->phys : 0; + msg->addr = ctx->buf ? ctx->buf->dma_addr : 0; msg->size = roundup(ctx->msg_sz, PAGE_SIZE); fastrpc_context_get(ctx); @@ -1306,13 +1337,15 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, if (fl->cctx->vmcount) { u64 src_perms = BIT(QCOM_SCM_VMID_HLOS); - err = qcom_scm_assign_mem(fl->cctx->remote_heap->phys, + err = qcom_scm_assign_mem(fl->cctx->remote_heap->dma_addr, (u64)fl->cctx->remote_heap->size, &src_perms, fl->cctx->vmperms, fl->cctx->vmcount); if (err) { - dev_err(fl->sctx->dev, "Failed to assign memory with phys 0x%llx size 0x%llx err %d\n", - fl->cctx->remote_heap->phys, fl->cctx->remote_heap->size, err); + dev_err(fl->sctx->dev, + "Failed to assign memory with dma_addr %pad size 0x%llx err %d\n", + &fl->cctx->remote_heap->dma_addr, + fl->cctx->remote_heap->size, err); goto err_map; } scm_done = true; @@ -1332,7 +1365,7 @@ static int fastrpc_init_create_static_process(struct fastrpc_user *fl, args[1].length = inbuf.namelen; args[1].fd = -1; - pages[0].addr = fl->cctx->remote_heap->phys; + pages[0].addr = fl->cctx->remote_heap->dma_addr; pages[0].size = fl->cctx->remote_heap->size; args[2].ptr = (u64)(uintptr_t) pages; @@ -1361,12 +1394,12 @@ err_invoke: dst_perms.vmid = QCOM_SCM_VMID_HLOS; dst_perms.perm = QCOM_SCM_PERM_RWX; - err = qcom_scm_assign_mem(fl->cctx->remote_heap->phys, + err = qcom_scm_assign_mem(fl->cctx->remote_heap->dma_addr, (u64)fl->cctx->remote_heap->size, &src_perms, &dst_perms, 1); if (err) - dev_err(fl->sctx->dev, "Failed to assign memory phys 0x%llx size 0x%llx err %d\n", - fl->cctx->remote_heap->phys, fl->cctx->remote_heap->size, err); + dev_err(fl->sctx->dev, "Failed to assign memory dma_addr %pad size 0x%llx err %d\n", + &fl->cctx->remote_heap->dma_addr, fl->cctx->remote_heap->size, err); } err_map: fastrpc_buf_free(fl->cctx->remote_heap); @@ -1455,7 +1488,7 @@ static int fastrpc_init_create_process(struct fastrpc_user *fl, args[2].length = inbuf.filelen; args[2].fd = init.filefd; - pages[0].addr = imem->phys; + pages[0].addr = imem->dma_addr; pages[0].size = imem->size; args[3].ptr = (u64)(uintptr_t) pages; @@ -1913,7 +1946,7 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp) args[0].ptr = (u64) (uintptr_t) &req_msg; args[0].length = sizeof(req_msg); - pages.addr = buf->phys; + pages.addr = buf->dma_addr; pages.size = buf->size; args[1].ptr = (u64) (uintptr_t) &pages; @@ -1941,11 +1974,12 @@ static int fastrpc_req_mmap(struct fastrpc_user *fl, char __user *argp) if (req.flags == ADSP_MMAP_REMOTE_HEAP_ADDR && fl->cctx->vmcount) { u64 src_perms = BIT(QCOM_SCM_VMID_HLOS); - err = qcom_scm_assign_mem(buf->phys, (u64)buf->size, + err = qcom_scm_assign_mem(buf->dma_addr, (u64)buf->size, &src_perms, fl->cctx->vmperms, fl->cctx->vmcount); if (err) { - dev_err(fl->sctx->dev, "Failed to assign memory phys 0x%llx size 0x%llx err %d", - buf->phys, buf->size, err); + dev_err(fl->sctx->dev, + "Failed to assign memory dma_addr %pad size 0x%llx err %d", + &buf->dma_addr, buf->size, err); goto err_assign; } } @@ -2059,7 +2093,7 @@ static int fastrpc_req_mem_map(struct fastrpc_user *fl, char __user *argp) args[0].ptr = (u64) (uintptr_t) &req_msg; args[0].length = sizeof(req_msg); - pages.addr = map->phys; + pages.addr = map->dma_addr; pages.size = map->len; args[1].ptr = (u64) (uintptr_t) &pages; @@ -2165,6 +2199,7 @@ static int fastrpc_cb_probe(struct platform_device *pdev) int i, sessions = 0; unsigned long flags; int rc; + u32 dma_bits; cctx = dev_get_drvdata(dev->parent); if (!cctx) @@ -2178,12 +2213,16 @@ static int fastrpc_cb_probe(struct platform_device *pdev) spin_unlock_irqrestore(&cctx->lock, flags); return -ENOSPC; } + dma_bits = cctx->soc_data->dma_addr_bits_default; sess = &cctx->session[cctx->sesscount++]; sess->used = false; sess->valid = true; sess->dev = dev; dev_set_drvdata(dev, sess); + if (cctx->domain_id == CDSP_DOMAIN_ID) + dma_bits = cctx->soc_data->dma_addr_bits_cdsp; + if (of_property_read_u32(dev->of_node, "reg", &sess->sid)) dev_info(dev, "FastRPC Session ID not specified in DT\n"); @@ -2198,9 +2237,9 @@ static int fastrpc_cb_probe(struct platform_device *pdev) } } spin_unlock_irqrestore(&cctx->lock, flags); - rc = dma_set_mask(dev, DMA_BIT_MASK(32)); + rc = dma_set_mask(dev, DMA_BIT_MASK(dma_bits)); if (rc) { - dev_err(dev, "32-bit DMA enable failed\n"); + dev_err(dev, "%u-bit DMA enable failed\n", dma_bits); return rc; } @@ -2285,6 +2324,18 @@ static int fastrpc_get_domain_id(const char *domain) return -EINVAL; } +static const struct fastrpc_soc_data kaanapali_soc_data = { + .sid_pos = 56, + .dma_addr_bits_cdsp = 34, + .dma_addr_bits_default = 32, +}; + +static const struct fastrpc_soc_data default_soc_data = { + .sid_pos = 32, + .dma_addr_bits_cdsp = 32, + .dma_addr_bits_default = 32, +}; + static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) { struct device *rdev = &rpdev->dev; @@ -2293,6 +2344,9 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) const char *domain; bool secure_dsp; unsigned int vmids[FASTRPC_MAX_VMIDS]; + const struct fastrpc_soc_data *soc_data; + + soc_data = device_get_match_data(rdev); err = of_property_read_string(rdev->of_node, "label", &domain); if (err) { @@ -2345,6 +2399,7 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) secure_dsp = !(of_property_read_bool(rdev->of_node, "qcom,non-secure-domain")); data->secure = secure_dsp; + data->soc_data = soc_data; switch (domain_id) { case ADSP_DOMAIN_ID: @@ -2482,7 +2537,8 @@ static int fastrpc_rpmsg_callback(struct rpmsg_device *rpdev, void *data, } static const struct of_device_id fastrpc_rpmsg_of_match[] = { - { .compatible = "qcom,fastrpc" }, + { .compatible = "qcom,kaanapali-fastrpc", .data = &kaanapali_soc_data }, + { .compatible = "qcom,fastrpc", .data = &default_soc_data }, { }, }; MODULE_DEVICE_TABLE(of, fastrpc_rpmsg_of_match); diff --git a/drivers/misc/kgdbts.c b/drivers/misc/kgdbts.c index 3b7a041ea351..9d3218330f0a 100644 --- a/drivers/misc/kgdbts.c +++ b/drivers/misc/kgdbts.c @@ -1067,7 +1067,7 @@ static void kgdbts_run_tests(void) configured = 0; } -static int kgdbts_option_setup(char *opt) +static int __init kgdbts_option_setup(char *opt) { if (strlen(opt) >= MAX_CONFIG_LEN) { printk(KERN_ERR "kgdbts: config string too long\n"); diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index f4eb307cd35e..5902dd1ee44b 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -2,7 +2,7 @@ # Copyright (c) 2003-2019, Intel Corporation. All rights reserved. config INTEL_MEI tristate "Intel Management Engine Interface" - depends on X86 && PCI + depends on PCI default X86_64 || MATOM help The Intel Management Engine (Intel ME) provides Manageability, @@ -49,7 +49,7 @@ config INTEL_MEI_TXE config INTEL_MEI_GSC tristate "Intel MEI GSC embedded device" depends on INTEL_MEI_ME - depends on DRM_I915 || DRM_XE + depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST help Intel auxiliary driver for GSC devices embedded in Intel graphics devices. @@ -84,7 +84,7 @@ config INTEL_MEI_VSC config INTEL_MEI_LB tristate "Intel Late Binding (LB) support on ME Interface" depends on INTEL_MEI_ME - depends on DRM_XE + depends on DRM_XE!=n || COMPILE_TEST help Enable support for Intel Late Binding (LB) via the MEI interface. diff --git a/drivers/misc/mei/gsc_proxy/Kconfig b/drivers/misc/mei/gsc_proxy/Kconfig index ac78b9d1eccd..bd8f955f548e 100644 --- a/drivers/misc/mei/gsc_proxy/Kconfig +++ b/drivers/misc/mei/gsc_proxy/Kconfig @@ -4,7 +4,7 @@ config INTEL_MEI_GSC_PROXY tristate "Intel GSC Proxy services of ME Interface" depends on INTEL_MEI_ME - depends on DRM_I915 + depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST help MEI Support for GSC Proxy Services on Intel platforms. diff --git a/drivers/misc/mei/hdcp/Kconfig b/drivers/misc/mei/hdcp/Kconfig index 631dd9651d7c..b9d5205c5b1a 100644 --- a/drivers/misc/mei/hdcp/Kconfig +++ b/drivers/misc/mei/hdcp/Kconfig @@ -4,7 +4,7 @@ config INTEL_MEI_HDCP tristate "Intel HDCP2.2 services of ME Interface" depends on INTEL_MEI_ME - depends on DRM_I915 || DRM_XE + depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST help MEI Support for HDCP2.2 Services on Intel platforms. diff --git a/drivers/misc/mei/pxp/Kconfig b/drivers/misc/mei/pxp/Kconfig index aa2dece4a927..2c5c00dc4b6f 100644 --- a/drivers/misc/mei/pxp/Kconfig +++ b/drivers/misc/mei/pxp/Kconfig @@ -4,7 +4,7 @@ config INTEL_MEI_PXP tristate "Intel PXP services of ME Interface" depends on INTEL_MEI_ME - depends on DRM_I915 || DRM_XE + depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST help MEI Support for PXP Services on Intel platforms. diff --git a/drivers/misc/ti_fpc202.c b/drivers/misc/ti_fpc202.c index 7964e46c7448..8eb2b5ac9850 100644 --- a/drivers/misc/ti_fpc202.c +++ b/drivers/misc/ti_fpc202.c @@ -309,7 +309,6 @@ static void fpc202_remove_port(struct fpc202_priv *priv, int port_id) static int fpc202_probe(struct i2c_client *client) { struct device *dev = &client->dev; - struct device_node *i2c_handle; struct fpc202_priv *priv; int ret, port_id; @@ -357,7 +356,7 @@ static int fpc202_probe(struct i2c_client *client) bitmap_zero(priv->probed_ports, FPC202_NUM_PORTS); - for_each_child_of_node(dev->of_node, i2c_handle) { + for_each_child_of_node_scoped(dev->of_node, i2c_handle) { ret = of_property_read_u32(i2c_handle, "reg", &port_id); if (ret) { if (ret == -EINVAL) diff --git a/drivers/most/core.c b/drivers/most/core.c index da319d108ea1..40d63e38fef5 100644 --- a/drivers/most/core.c +++ b/drivers/most/core.c @@ -1282,19 +1282,28 @@ int most_register_interface(struct most_interface *iface) int id; struct most_channel *c; - if (!iface || !iface->enqueue || !iface->configure || - !iface->poison_channel || (iface->num_channels > MAX_CHANNELS)) + if (!iface) return -EINVAL; + device_initialize(iface->dev); + + if (!iface->enqueue || !iface->configure || !iface->poison_channel || + (iface->num_channels > MAX_CHANNELS)) { + put_device(iface->dev); + return -EINVAL; + } + id = ida_alloc(&mdev_id, GFP_KERNEL); if (id < 0) { dev_err(iface->dev, "Failed to allocate device ID\n"); + put_device(iface->dev); return id; } iface->p = kzalloc(sizeof(*iface->p), GFP_KERNEL); if (!iface->p) { ida_free(&mdev_id, id); + put_device(iface->dev); return -ENOMEM; } @@ -1304,7 +1313,7 @@ int most_register_interface(struct most_interface *iface) iface->dev->bus = &mostbus; iface->dev->groups = interface_attr_groups; dev_set_drvdata(iface->dev, iface); - if (device_register(iface->dev)) { + if (device_add(iface->dev)) { dev_err(iface->dev, "Failed to register interface device\n"); kfree(iface->p); put_device(iface->dev); diff --git a/drivers/mux/mmio.c b/drivers/mux/mmio.c index 3409af1ffb80..0611ef28bb69 100644 --- a/drivers/mux/mmio.c +++ b/drivers/mux/mmio.c @@ -72,7 +72,7 @@ static int mux_mmio_probe(struct platform_device *pdev) if (IS_ERR(base)) regmap = ERR_PTR(-ENODEV); else - regmap = regmap_init_mmio(dev, base, &mux_mmio_regmap_cfg); + regmap = devm_regmap_init_mmio(dev, base, &mux_mmio_regmap_cfg); /* Fallback to checking the parent node on "real" errors. */ if (IS_ERR(regmap) && regmap != ERR_PTR(-EPROBE_DEFER)) { regmap = dev_get_regmap(dev->parent, NULL); diff --git a/drivers/net/wireless/ath/ath11k/mhi.c b/drivers/net/wireless/ath/ath11k/mhi.c index acd76e9392d3..d2c44f7f9b62 100644 --- a/drivers/net/wireless/ath/ath11k/mhi.c +++ b/drivers/net/wireless/ath/ath11k/mhi.c @@ -34,7 +34,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qca6390[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, }, { .num = 21, @@ -48,7 +47,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qca6390[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, }, }; @@ -99,7 +97,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qcn9074[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, }, { .num = 21, @@ -113,7 +110,6 @@ static const struct mhi_channel_config ath11k_mhi_channels_qcn9074[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, }, }; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/mhi.c b/drivers/net/wireless/ath/ath12k/wifi7/mhi.c index b8d972659314..988affafcfd1 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/mhi.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/mhi.c @@ -20,7 +20,6 @@ static const struct mhi_channel_config ath12k_wifi7_mhi_channels_qcn9274[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, }, { .num = 21, @@ -34,7 +33,6 @@ static const struct mhi_channel_config ath12k_wifi7_mhi_channels_qcn9274[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, }, }; @@ -85,7 +83,6 @@ static const struct mhi_channel_config ath12k_wifi7_mhi_channels_wcn7850[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = false, }, { .num = 21, @@ -99,7 +96,6 @@ static const struct mhi_channel_config ath12k_wifi7_mhi_channels_wcn7850[] = { .lpm_notify = false, .offload_channel = false, .doorbell_mode_switch = false, - .auto_queue = true, }, }; diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index bf47a982cf62..74ddbd0f79b0 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -30,7 +30,7 @@ source "drivers/nvmem/layouts/Kconfig" config NVMEM_AN8855_EFUSE tristate "Airoha AN8855 eFuse support" - depends on MFD_AIROHA_AN8855 || COMPILE_TEST + depends on 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 diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 387c88c55259..c6180cf1dd91 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -789,11 +789,10 @@ static int nvmem_validate_keepouts(struct nvmem_device *nvmem) static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np) { struct device *dev = &nvmem->dev; - struct device_node *child; const __be32 *addr; int len, ret; - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { struct nvmem_cell_info info = {0}; addr = of_get_property(child, "reg", &len); @@ -801,7 +800,6 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod continue; if (len < 2 * sizeof(u32)) { dev_err(dev, "nvmem: invalid reg on %pOF\n", child); - of_node_put(child); return -EINVAL; } @@ -817,7 +815,6 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod info.nbits < 1 || info.bit_offset + info.nbits > BITS_PER_BYTE * info.bytes) { dev_err(dev, "nvmem: invalid bits on %pOF\n", child); - of_node_put(child); return -EINVAL; } } @@ -830,7 +827,7 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod ret = nvmem_add_one_cell(nvmem, &info); kfree(info.name); if (ret) { - of_node_put(child); + of_node_put(info.np); return ret; } } diff --git a/drivers/pps/generators/Kconfig b/drivers/pps/generators/Kconfig index b3f340ed3163..4ef02b3f2576 100644 --- a/drivers/pps/generators/Kconfig +++ b/drivers/pps/generators/Kconfig @@ -23,14 +23,6 @@ config PPS_GENERATOR_DUMMY This driver can also be built as a module. If so, the module will be called pps_gen-dummy. -config PPS_GENERATOR_PARPORT - tristate "Parallel port PPS signal generator" - depends on PARPORT && BROKEN - help - If you say yes here you get support for a PPS signal generator which - utilizes STROBE pin of a parallel port to send PPS signals. It uses - parport abstraction layer and hrtimers to precisely control the signal. - config PPS_GENERATOR_TIO tristate "TIO PPS signal generator" depends on X86 && CPU_SUP_INTEL diff --git a/drivers/pps/generators/Makefile b/drivers/pps/generators/Makefile index e109920e8a2d..5d38774b4a56 100644 --- a/drivers/pps/generators/Makefile +++ b/drivers/pps/generators/Makefile @@ -7,7 +7,6 @@ pps_gen_core-y := pps_gen.o sysfs.o obj-$(CONFIG_PPS_GENERATOR) := pps_gen_core.o obj-$(CONFIG_PPS_GENERATOR_DUMMY) += pps_gen-dummy.o -obj-$(CONFIG_PPS_GENERATOR_PARPORT) += pps_gen_parport.o obj-$(CONFIG_PPS_GENERATOR_TIO) += pps_gen_tio.o ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG diff --git a/drivers/pps/generators/pps_gen_parport.c b/drivers/pps/generators/pps_gen_parport.c deleted file mode 100644 index 05bbf8d30ef1..000000000000 --- a/drivers/pps/generators/pps_gen_parport.c +++ /dev/null @@ -1,238 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * pps_gen_parport.c -- kernel parallel port PPS signal generator - * - * Copyright (C) 2009 Alexander Gordeev <lasaine@lvk.cs.msu.su> - */ - - -/* - * TODO: - * fix issues when realtime clock is adjusted in a leap - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/time.h> -#include <linux/hrtimer.h> -#include <linux/parport.h> - -#define SIGNAL 0 -#define NO_SIGNAL PARPORT_CONTROL_STROBE - -/* module parameters */ - -#define SEND_DELAY_MAX 100000 - -static unsigned int send_delay = 30000; -MODULE_PARM_DESC(delay, - "Delay between setting and dropping the signal (ns)"); -module_param_named(delay, send_delay, uint, 0); - - -#define SAFETY_INTERVAL 3000 /* set the hrtimer earlier for safety (ns) */ - -/* internal per port structure */ -struct pps_generator_pp { - struct pardevice *pardev; /* parport device */ - struct hrtimer timer; - long port_write_time; /* calibrated port write time (ns) */ -}; - -static struct pps_generator_pp device = { - .pardev = NULL, -}; - -static int attached; - -/* calibrated time between a hrtimer event and the reaction */ -static long hrtimer_error = SAFETY_INTERVAL; - -/* the kernel hrtimer event */ -static enum hrtimer_restart hrtimer_event(struct hrtimer *timer) -{ - struct timespec64 expire_time, ts1, ts2, ts3, dts; - struct pps_generator_pp *dev; - struct parport *port; - long lim, delta; - unsigned long flags; - - /* We have to disable interrupts here. The idea is to prevent - * other interrupts on the same processor to introduce random - * lags while polling the clock. ktime_get_real_ts64() takes <1us on - * most machines while other interrupt handlers can take much - * more potentially. - * - * NB: approx time with blocked interrupts = - * send_delay + 3 * SAFETY_INTERVAL - */ - local_irq_save(flags); - - /* first of all we get the time stamp... */ - ktime_get_real_ts64(&ts1); - expire_time = ktime_to_timespec64(hrtimer_get_softexpires(timer)); - dev = container_of(timer, struct pps_generator_pp, timer); - lim = NSEC_PER_SEC - send_delay - dev->port_write_time; - - /* check if we are late */ - if (expire_time.tv_sec != ts1.tv_sec || ts1.tv_nsec > lim) { - local_irq_restore(flags); - pr_err("we are late this time %ptSp\n", &ts1); - goto done; - } - - /* busy loop until the time is right for an assert edge */ - do { - ktime_get_real_ts64(&ts2); - } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim); - - /* set the signal */ - port = dev->pardev->port; - port->ops->write_control(port, SIGNAL); - - /* busy loop until the time is right for a clear edge */ - lim = NSEC_PER_SEC - dev->port_write_time; - do { - ktime_get_real_ts64(&ts2); - } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim); - - /* unset the signal */ - port->ops->write_control(port, NO_SIGNAL); - - ktime_get_real_ts64(&ts3); - - local_irq_restore(flags); - - /* update calibrated port write time */ - dts = timespec64_sub(ts3, ts2); - dev->port_write_time = - (dev->port_write_time + timespec64_to_ns(&dts)) >> 1; - -done: - /* update calibrated hrtimer error */ - dts = timespec64_sub(ts1, expire_time); - delta = timespec64_to_ns(&dts); - /* If the new error value is bigger then the old, use the new - * value, if not then slowly move towards the new value. This - * way it should be safe in bad conditions and efficient in - * good conditions. - */ - if (delta >= hrtimer_error) - hrtimer_error = delta; - else - hrtimer_error = (3 * hrtimer_error + delta) >> 2; - - /* update the hrtimer expire time */ - hrtimer_set_expires(timer, - ktime_set(expire_time.tv_sec + 1, - NSEC_PER_SEC - (send_delay + - dev->port_write_time + SAFETY_INTERVAL + - 2 * hrtimer_error))); - - return HRTIMER_RESTART; -} - -/* calibrate port write time */ -#define PORT_NTESTS_SHIFT 5 -static void calibrate_port(struct pps_generator_pp *dev) -{ - struct parport *port = dev->pardev->port; - int i; - long acc = 0; - - for (i = 0; i < (1 << PORT_NTESTS_SHIFT); i++) { - struct timespec64 a, b; - unsigned long irq_flags; - - local_irq_save(irq_flags); - ktime_get_real_ts64(&a); - port->ops->write_control(port, NO_SIGNAL); - ktime_get_real_ts64(&b); - local_irq_restore(irq_flags); - - b = timespec64_sub(b, a); - acc += timespec64_to_ns(&b); - } - - dev->port_write_time = acc >> PORT_NTESTS_SHIFT; - pr_info("port write takes %ldns\n", dev->port_write_time); -} - -static inline ktime_t next_intr_time(struct pps_generator_pp *dev) -{ - struct timespec64 ts; - - ktime_get_real_ts64(&ts); - - return ktime_set(ts.tv_sec + - ((ts.tv_nsec > 990 * NSEC_PER_MSEC) ? 1 : 0), - NSEC_PER_SEC - (send_delay + - dev->port_write_time + 3 * SAFETY_INTERVAL)); -} - -static void parport_attach(struct parport *port) -{ - struct pardev_cb pps_cb; - - if (send_delay > SEND_DELAY_MAX) { - pr_err("delay value should be not greater then %d\n", SEND_DELAY_MAX); - return; - } - - if (attached) { - /* we already have a port */ - return; - } - - memset(&pps_cb, 0, sizeof(pps_cb)); - pps_cb.private = &device; - pps_cb.flags = PARPORT_FLAG_EXCL; - device.pardev = parport_register_dev_model(port, KBUILD_MODNAME, - &pps_cb, 0); - if (!device.pardev) { - pr_err("couldn't register with %s\n", port->name); - return; - } - - if (parport_claim_or_block(device.pardev) < 0) { - pr_err("couldn't claim %s\n", port->name); - goto err_unregister_dev; - } - - pr_info("attached to %s\n", port->name); - attached = 1; - - calibrate_port(&device); - - hrtimer_setup(&device.timer, hrtimer_event, CLOCK_REALTIME, HRTIMER_MODE_ABS); - hrtimer_start(&device.timer, next_intr_time(&device), HRTIMER_MODE_ABS); - - return; - -err_unregister_dev: - parport_unregister_device(device.pardev); -} - -static void parport_detach(struct parport *port) -{ - if (port->cad != device.pardev) - return; /* not our port */ - - hrtimer_cancel(&device.timer); - parport_release(device.pardev); - parport_unregister_device(device.pardev); -} - -static struct parport_driver pps_gen_parport_driver = { - .name = KBUILD_MODNAME, - .match_port = parport_attach, - .detach = parport_detach, -}; -module_parport_driver(pps_gen_parport_driver); - -MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>"); -MODULE_DESCRIPTION("parallel port PPS signal generator"); -MODULE_LICENSE("GPL"); diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index ba3d80d12605..7338b38697d0 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1514,26 +1514,22 @@ static int of_qcom_slim_ngd_register(struct device *parent, const struct ngd_reg_offset_data *data; struct qcom_slim_ngd *ngd; const struct of_device_id *match; - struct device_node *node; u32 id; int ret; match = of_match_node(qcom_slim_ngd_dt_match, parent->of_node); data = match->data; - for_each_available_child_of_node(parent->of_node, node) { + for_each_available_child_of_node_scoped(parent->of_node, node) { if (of_property_read_u32(node, "reg", &id)) continue; ngd = kzalloc(sizeof(*ngd), GFP_KERNEL); - if (!ngd) { - of_node_put(node); + if (!ngd) return -ENOMEM; - } ngd->pdev = platform_device_alloc(QCOM_SLIM_NGD_DRV_NAME, id); if (!ngd->pdev) { kfree(ngd); - of_node_put(node); return -ENOMEM; } ngd->id = id; @@ -1546,7 +1542,6 @@ static int of_qcom_slim_ngd_register(struct device *parent, if (ret) { platform_device_put(ngd->pdev); kfree(ngd); - of_node_put(node); return ret; } ngd->pdev->dev.of_node = node; @@ -1556,7 +1551,6 @@ static int of_qcom_slim_ngd_register(struct device *parent, if (ret) { platform_device_put(ngd->pdev); kfree(ngd); - of_node_put(node); return ret; } ngd->base = ctrl->base + ngd->id * data->offset + diff --git a/drivers/spi/spi-fsi.c b/drivers/spi/spi-fsi.c index 68276d195917..f6a75f0184c4 100644 --- a/drivers/spi/spi-fsi.c +++ b/drivers/spi/spi-fsi.c @@ -528,12 +528,12 @@ static size_t fsi_spi_max_transfer_size(struct spi_device *spi) return SPI_FSI_MAX_RX_SIZE; } -static int fsi_spi_probe(struct device *dev) +static int fsi_spi_probe(struct fsi_device *fsi) { int rc; int num_controllers_registered = 0; struct fsi2spi *bridge; - struct fsi_device *fsi = to_fsi_dev(dev); + struct device *dev = &fsi->dev; rc = fsi_spi_check_mux(fsi, dev); if (rc) @@ -590,10 +590,9 @@ MODULE_DEVICE_TABLE(fsi, fsi_spi_ids); static struct fsi_driver fsi_spi_driver = { .id_table = fsi_spi_ids, + .probe = fsi_spi_probe, .drv = { .name = "spi-fsi", - .bus = &fsi_bus_type, - .probe = fsi_spi_probe, }, }; module_fsi_driver(fsi_spi_driver); diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index 697b3e8bb023..87e3ee9d4f2a 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -149,6 +149,7 @@ static int apple_spmi_probe(struct platform_device *pdev) } static const struct of_device_id apple_spmi_match_table[] = { + { .compatible = "apple,t8103-spmi", }, { .compatible = "apple,spmi", }, {} }; diff --git a/drivers/spmi/spmi-mtk-pmif.c b/drivers/spmi/spmi-mtk-pmif.c index 160d36f7d238..1048420b5afb 100644 --- a/drivers/spmi/spmi-mtk-pmif.c +++ b/drivers/spmi/spmi-mtk-pmif.c @@ -1,14 +1,21 @@ // SPDX-License-Identifier: GPL-2.0 // // Copyright (c) 2021 MediaTek Inc. +// Copyright (c) 2025 Collabora Ltd +// AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> #include <linux/clk.h> +#include <linux/interrupt.h> #include <linux/iopoll.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_irq.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/spmi.h> +#include <linux/irqchip/chained_irq.h> #define SWINF_IDLE 0x00 #define SWINF_WFVLDCLR 0x06 @@ -20,11 +27,13 @@ #define PMIF_CMD_EXT_REG 2 #define PMIF_CMD_EXT_REG_LONG 3 -#define PMIF_DELAY_US 10 +#define PMIF_DELAY_US 2 #define PMIF_TIMEOUT_US (10 * 1000) #define PMIF_CHAN_OFFSET 0x5 +#define PMIF_RCS_IRQ_MASK GENMASK(7, 0) +#define PMIF_MAX_BUSES 2 #define PMIF_MAX_CLKS 3 #define SPMI_OP_ST_BUSY 1 @@ -41,16 +50,28 @@ struct pmif_data { const u32 *regs; const u32 *spmimst_regs; u32 soc_chan; + u8 spmi_ver; + u32 num_spmi_buses; }; -struct pmif { +struct pmif_bus { void __iomem *base; void __iomem *spmimst_base; - struct ch_reg chan; + struct spmi_controller *ctrl; + struct irq_domain *dom; + int irq; struct clk_bulk_data clks[PMIF_MAX_CLKS]; size_t nclks; + u8 irq_min_sid; + u8 irq_max_sid; + u16 irq_en; + raw_spinlock_t lock; +}; + +struct pmif { + struct pmif_bus bus[PMIF_MAX_BUSES]; + struct ch_reg chan; const struct pmif_data *data; - raw_spinlock_t lock; }; static const char * const pmif_clock_names[] = { @@ -262,33 +283,46 @@ static const u32 mt8195_spmi_regs[] = { [SPMI_MST_DBG] = 0x00FC, }; -static u32 pmif_readl(struct pmif *arb, enum pmif_regs reg) +static inline struct pmif *to_mtk_pmif(struct spmi_controller *ctrl) +{ + return dev_get_drvdata(ctrl->dev.parent); +} + +static u32 pmif_readl(struct pmif *arb, struct pmif_bus *pbus, enum pmif_regs reg) { - return readl(arb->base + arb->data->regs[reg]); + return readl(pbus->base + arb->data->regs[reg]); } -static void pmif_writel(struct pmif *arb, u32 val, enum pmif_regs reg) +static void pmif_writel(struct pmif *arb, struct pmif_bus *pbus, + u32 val, enum pmif_regs reg) { - writel(val, arb->base + arb->data->regs[reg]); + writel(val, pbus->base + arb->data->regs[reg]); } -static void mtk_spmi_writel(struct pmif *arb, u32 val, enum spmi_regs reg) +static u32 mtk_spmi_readl(struct pmif *arb, struct pmif_bus *pbus, enum spmi_regs reg) { - writel(val, arb->spmimst_base + arb->data->spmimst_regs[reg]); + return readl(pbus->spmimst_base + arb->data->spmimst_regs[reg]); } -static bool pmif_is_fsm_vldclr(struct pmif *arb) +static void mtk_spmi_writel(struct pmif *arb, struct pmif_bus *pbus, + u32 val, enum spmi_regs reg) +{ + writel(val, pbus->spmimst_base + arb->data->spmimst_regs[reg]); +} + +static bool pmif_is_fsm_vldclr(struct pmif *arb, struct pmif_bus *pbus) { u32 reg_rdata; - reg_rdata = pmif_readl(arb, arb->chan.ch_sta); + reg_rdata = pmif_readl(arb, pbus, arb->chan.ch_sta); return GET_SWINF(reg_rdata) == SWINF_WFVLDCLR; } static int pmif_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) { - struct pmif *arb = spmi_controller_get_drvdata(ctrl); + struct pmif_bus *pbus = spmi_controller_get_drvdata(ctrl); + struct pmif *arb = to_mtk_pmif(ctrl); u32 rdata, cmd; int ret; @@ -298,8 +332,8 @@ static int pmif_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) cmd = opc - SPMI_CMD_RESET; - mtk_spmi_writel(arb, (cmd << 0x4) | sid, SPMI_OP_ST_CTRL); - ret = readl_poll_timeout_atomic(arb->spmimst_base + arb->data->spmimst_regs[SPMI_OP_ST_STA], + mtk_spmi_writel(arb, pbus, (cmd << 0x4) | sid, SPMI_OP_ST_CTRL); + ret = readl_poll_timeout_atomic(pbus->spmimst_base + arb->data->spmimst_regs[SPMI_OP_ST_STA], rdata, (rdata & SPMI_OP_ST_BUSY) == SPMI_OP_ST_BUSY, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) @@ -311,7 +345,8 @@ static int pmif_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 addr, u8 *buf, size_t len) { - struct pmif *arb = spmi_controller_get_drvdata(ctrl); + struct pmif_bus *pbus = spmi_controller_get_drvdata(ctrl); + struct pmif *arb = to_mtk_pmif(ctrl); struct ch_reg *inf_reg; int ret; u32 data, cmd; @@ -325,7 +360,6 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, if (len > 4) { dev_err(&ctrl->dev, "pmif supports 1..4 bytes per trans, but:%zu requested", len); - return -EINVAL; } @@ -336,41 +370,43 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, else return -EINVAL; - raw_spin_lock_irqsave(&arb->lock, flags); + raw_spin_lock_irqsave(&pbus->lock, flags); /* Wait for Software Interface FSM state to be IDLE. */ inf_reg = &arb->chan; - ret = readl_poll_timeout_atomic(arb->base + arb->data->regs[inf_reg->ch_sta], + ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], data, GET_SWINF(data) == SWINF_IDLE, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { /* set channel ready if the data has transferred */ - if (pmif_is_fsm_vldclr(arb)) - pmif_writel(arb, 1, inf_reg->ch_rdy); - raw_spin_unlock_irqrestore(&arb->lock, flags); + if (pmif_is_fsm_vldclr(arb, pbus)) + pmif_writel(arb, pbus, 1, inf_reg->ch_rdy); + raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_IDLE\n"); return ret; } /* Send the command. */ cmd = (opc << 30) | (sid << 24) | ((len - 1) << 16) | addr; - pmif_writel(arb, cmd, inf_reg->ch_send); - raw_spin_unlock_irqrestore(&arb->lock, flags); + pmif_writel(arb, pbus, cmd, inf_reg->ch_send); /* * Wait for Software Interface FSM state to be WFVLDCLR, * read the data and clear the valid flag. */ - ret = readl_poll_timeout_atomic(arb->base + arb->data->regs[inf_reg->ch_sta], + ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], data, GET_SWINF(data) == SWINF_WFVLDCLR, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { + raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_WFVLDCLR\n"); return ret; } - data = pmif_readl(arb, inf_reg->rdata); + data = pmif_readl(arb, pbus, inf_reg->rdata); + pmif_writel(arb, pbus, 1, inf_reg->ch_rdy); + raw_spin_unlock_irqrestore(&pbus->lock, flags); + memcpy(buf, &data, len); - pmif_writel(arb, 1, inf_reg->ch_rdy); return 0; } @@ -378,7 +414,8 @@ static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, static int pmif_spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 addr, const u8 *buf, size_t len) { - struct pmif *arb = spmi_controller_get_drvdata(ctrl); + struct pmif_bus *pbus = spmi_controller_get_drvdata(ctrl); + struct pmif *arb = to_mtk_pmif(ctrl); struct ch_reg *inf_reg; int ret; u32 data, wdata, cmd; @@ -409,31 +446,184 @@ static int pmif_spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, /* Set the write data. */ memcpy(&wdata, buf, len); - raw_spin_lock_irqsave(&arb->lock, flags); + raw_spin_lock_irqsave(&pbus->lock, flags); /* Wait for Software Interface FSM state to be IDLE. */ inf_reg = &arb->chan; - ret = readl_poll_timeout_atomic(arb->base + arb->data->regs[inf_reg->ch_sta], + ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], data, GET_SWINF(data) == SWINF_IDLE, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { /* set channel ready if the data has transferred */ - if (pmif_is_fsm_vldclr(arb)) - pmif_writel(arb, 1, inf_reg->ch_rdy); - raw_spin_unlock_irqrestore(&arb->lock, flags); + if (pmif_is_fsm_vldclr(arb, pbus)) + pmif_writel(arb, pbus, 1, inf_reg->ch_rdy); + raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_IDLE\n"); return ret; } - pmif_writel(arb, wdata, inf_reg->wdata); + pmif_writel(arb, pbus, wdata, inf_reg->wdata); /* Send the command. */ cmd = (opc << 30) | BIT(29) | (sid << 24) | ((len - 1) << 16) | addr; - pmif_writel(arb, cmd, inf_reg->ch_send); - raw_spin_unlock_irqrestore(&arb->lock, flags); + pmif_writel(arb, pbus, cmd, inf_reg->ch_send); + raw_spin_unlock_irqrestore(&pbus->lock, flags); return 0; } +static void mtk_spmi_handle_chained_irq(struct irq_desc *desc) +{ + struct pmif_bus *pbus = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + struct pmif *arb = to_mtk_pmif(pbus->ctrl); + u8 regidx_min, regidx_max; + bool irq_handled = false; + unsigned int i; + + regidx_min = pbus->irq_min_sid / 4; + regidx_min += SPMI_SLV_3_0_EINT; + + regidx_max = pbus->irq_max_sid / 4; + regidx_max += SPMI_SLV_3_0_EINT; + + chained_irq_enter(chip, desc); + + for (i = regidx_min; i <= regidx_max; i++) { + u32 val = mtk_spmi_readl(arb, pbus, i); + + while (val) { + u8 bit = __ffs(val); + u8 bank = bit / 7; + u8 sid = ((i - SPMI_SLV_3_0_EINT) * 4) + bank; + + val &= ~(PMIF_RCS_IRQ_MASK << (8 * bank)); + + /* Check if IRQs for this SID are enabled */ + if (!(pbus->irq_en & BIT(sid))) + continue; + + generic_handle_domain_irq_safe(pbus->dom, sid); + irq_handled = true; + } + } + + if (!irq_handled) + handle_bad_irq(desc); + + chained_irq_exit(chip, desc); +} + +static void mtk_spmi_rcs_irq_eoi(struct irq_data *d) +{ + struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); + struct pmif *arb = to_mtk_pmif(pbus->ctrl); + irq_hw_number_t irq = irqd_to_hwirq(d); + unsigned int reg, shift; + + /* There are four interrupts (8 bits each) per register */ + reg = SPMI_SLV_3_0_EINT + d->hwirq / 4; + shift = (irq % 4) * 8; + + mtk_spmi_writel(arb, pbus, PMIF_RCS_IRQ_MASK << shift, reg); +} + +static void mtk_spmi_rcs_irq_enable(struct irq_data *d) +{ + struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); + irq_hw_number_t irq = irqd_to_hwirq(d); + + pbus->irq_en |= BIT(irq); +} + +static void mtk_spmi_rcs_irq_disable(struct irq_data *d) +{ + struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); + irq_hw_number_t irq = irqd_to_hwirq(d); + + pbus->irq_en &= ~BIT(irq); +} + +static int mtk_spmi_rcs_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); + + return irq_set_irq_wake(pbus->irq, on); +} + +static const struct irq_chip mtk_spmi_rcs_irq_chip = { + .name = "spmi_rcs", + .irq_eoi = mtk_spmi_rcs_irq_eoi, + .irq_enable = mtk_spmi_rcs_irq_enable, + .irq_disable = mtk_spmi_rcs_irq_disable, + .irq_set_wake = mtk_spmi_rcs_irq_set_wake, +}; + +static int mtk_spmi_rcs_irq_translate(struct irq_domain *d, struct irq_fwspec *fwspec, + unsigned long *out_hwirq, unsigned int *out_type) +{ + struct pmif_bus *pbus = d->host_data; + struct device *dev = &pbus->ctrl->dev; + u32 *intspec = fwspec->param; + + if (intspec[0] > SPMI_MAX_SLAVE_ID) + return -EINVAL; + + /* + * The IRQ number in intspec[1] is ignored on purpose here! + * + * The controller only has knowledge of which SID raised an interrupt + * and the type of irq, but doesn't know about any device irq number, + * hence that must be read from the SPMI device's registers. + */ + *out_hwirq = intspec[0]; + *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; + + if (pbus->irq_min_sid > intspec[0]) + pbus->irq_min_sid = intspec[0]; + + if (pbus->irq_max_sid < intspec[0]) + pbus->irq_max_sid = intspec[0]; + + dev_dbg(dev, "Found SPMI IRQ %u (map: 0x%lx)\n", intspec[0], *out_hwirq); + + return 0; +} + +static struct lock_class_key mtk_spmi_rcs_irqlock_class, mtk_spmi_rcs_irqreq_class; + +static int mtk_spmi_rcs_irq_alloc(struct irq_domain *d, unsigned int virq, + unsigned int nr_irqs, void *data) +{ + struct pmif_bus *pbus = d->host_data; + struct device *dev = &pbus->ctrl->dev; + struct irq_fwspec *fwspec = data; + irq_hw_number_t hwirq; + unsigned int irqtype; + int i, ret; + + ret = mtk_spmi_rcs_irq_translate(d, fwspec, &hwirq, &irqtype); + if (ret) + return ret; + + for (i = 0; i < nr_irqs; i++) { + dev_dbg(dev, "Mapping IRQ%u (hwirq %lu) with type %u\n", + virq, hwirq, irqtype); + + irq_set_lockdep_class(virq, &mtk_spmi_rcs_irqlock_class, + &mtk_spmi_rcs_irqreq_class); + irq_domain_set_info(d, virq, hwirq, &mtk_spmi_rcs_irq_chip, + pbus, handle_level_irq, NULL, NULL); + } + + return 0; +} + +static const struct irq_domain_ops mtk_spmi_rcs_irq_domain_ops = { + .alloc = mtk_spmi_rcs_irq_alloc, + .free = irq_domain_free_irqs_common, + .translate = mtk_spmi_rcs_irq_translate, +}; + static const struct pmif_data mt6873_pmif_arb = { .regs = mt6873_regs, .spmimst_regs = mt6873_spmi_regs, @@ -446,84 +636,202 @@ static const struct pmif_data mt8195_pmif_arb = { .soc_chan = 2, }; -static int mtk_spmi_probe(struct platform_device *pdev) +static const struct pmif_data mt8196_pmif_arb = { + .regs = mt8195_regs, + .spmimst_regs = mt8195_spmi_regs, + .soc_chan = 2, + .spmi_ver = 2, + .num_spmi_buses = 2, +}; + +static int mtk_spmi_irq_init(struct device_node *node, + const struct pmif_data *pdata, + struct pmif_bus *pbus) +{ + struct pmif *arb = to_mtk_pmif(pbus->ctrl); + unsigned int i; + + /* No interrupts required for SPMI 1.x controller */ + if (pdata->spmi_ver < 2) { + pbus->dom = NULL; + return 0; + } + + pbus->irq = of_irq_get_byname(node, "rcs"); + if (pbus->irq <= 0) + return pbus->irq ? : -ENXIO; + + pbus->dom = irq_domain_create_tree(of_fwnode_handle(node), + &mtk_spmi_rcs_irq_domain_ops, pbus); + if (!pbus->dom) + return -ENOMEM; + + /* Clear possible unhandled interrupts coming from bootloader SPMI init */ + for (i = SPMI_SLV_3_0_EINT; i <= SPMI_SLV_F_C_EINT; i++) + mtk_spmi_writel(arb, pbus, GENMASK(31, 0), i); + + return 0; +} + +static void mtk_spmi_irq_remove(struct pmif_bus *pbus) +{ + if (!pbus->dom) + return; + + irq_set_chained_handler_and_data(pbus->irq, NULL, NULL); + irq_domain_remove(pbus->dom); +} + +static int mtk_spmi_bus_probe(struct platform_device *pdev, + struct device_node *node, + const struct pmif_data *pdata, + struct pmif_bus *pbus) { - struct pmif *arb; struct spmi_controller *ctrl; - int err, i; - u32 chan_offset; + int err, idx, bus_id, i; - ctrl = devm_spmi_controller_alloc(&pdev->dev, sizeof(*arb)); + if (pdata->num_spmi_buses > 1) + bus_id = of_alias_get_id(node, "spmi"); + else + bus_id = 0; + + if (bus_id < 0) + return dev_err_probe(&pdev->dev, bus_id, + "Cannot find SPMI Bus alias ID\n"); + + ctrl = devm_spmi_controller_alloc(&pdev->dev, sizeof(*pbus)); if (IS_ERR(ctrl)) return PTR_ERR(ctrl); - arb = spmi_controller_get_drvdata(ctrl); - arb->data = device_get_match_data(&pdev->dev); - if (!arb->data) { - dev_err(&pdev->dev, "Cannot get drv_data\n"); + pbus = spmi_controller_get_drvdata(ctrl); + pbus->ctrl = ctrl; + + idx = of_property_match_string(node, "reg-names", "pmif"); + if (idx < 0) return -EINVAL; - } - arb->base = devm_platform_ioremap_resource_byname(pdev, "pmif"); - if (IS_ERR(arb->base)) - return PTR_ERR(arb->base); + pbus->base = devm_of_iomap(&pdev->dev, node, idx, NULL); + if (IS_ERR(pbus->base)) + return PTR_ERR(pbus->base); - arb->spmimst_base = devm_platform_ioremap_resource_byname(pdev, "spmimst"); - if (IS_ERR(arb->spmimst_base)) - return PTR_ERR(arb->spmimst_base); + idx = of_property_match_string(node, "reg-names", "spmimst"); + if (idx < 0) + return -EINVAL; - arb->nclks = ARRAY_SIZE(pmif_clock_names); - for (i = 0; i < arb->nclks; i++) - arb->clks[i].id = pmif_clock_names[i]; + pbus->spmimst_base = devm_of_iomap(&pdev->dev, node, idx, NULL); + if (IS_ERR(pbus->spmimst_base)) + return PTR_ERR(pbus->spmimst_base); + + pbus->nclks = ARRAY_SIZE(pmif_clock_names); + for (i = 0; i < pbus->nclks; i++) { + pbus->clks[i].id = pmif_clock_names[i]; + pbus->clks[i].clk = of_clk_get_by_name(node, pbus->clks[i].id); + if (IS_ERR(pbus->clks[i].clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pbus->clks[i].clk), + "Failed to get clocks\n"); + } - err = clk_bulk_get(&pdev->dev, arb->nclks, arb->clks); + err = clk_bulk_prepare_enable(pbus->nclks, pbus->clks); if (err) { - dev_err(&pdev->dev, "Failed to get clocks: %d\n", err); - return err; + dev_err_probe(&pdev->dev, err, "Failed to enable clocks\n"); + goto err_put_clks; } - err = clk_bulk_prepare_enable(arb->nclks, arb->clks); + err = mtk_spmi_irq_init(node, pdata, pbus); if (err) { - dev_err(&pdev->dev, "Failed to enable clocks: %d\n", err); - goto err_put_clks; + dev_err_probe(&pdev->dev, err, "Cannot initialize SPMI IRQs\n"); + goto err_disable_clks; } ctrl->cmd = pmif_arb_cmd; ctrl->read_cmd = pmif_spmi_read_cmd; ctrl->write_cmd = pmif_spmi_write_cmd; + ctrl->dev.of_node = node; + dev_set_name(&ctrl->dev, "spmi-%d", bus_id); - chan_offset = PMIF_CHAN_OFFSET * arb->data->soc_chan; - arb->chan.ch_sta = PMIF_SWINF_0_STA + chan_offset; - arb->chan.wdata = PMIF_SWINF_0_WDATA_31_0 + chan_offset; - arb->chan.rdata = PMIF_SWINF_0_RDATA_31_0 + chan_offset; - arb->chan.ch_send = PMIF_SWINF_0_ACC + chan_offset; - arb->chan.ch_rdy = PMIF_SWINF_0_VLD_CLR + chan_offset; - - raw_spin_lock_init(&arb->lock); - - platform_set_drvdata(pdev, ctrl); + raw_spin_lock_init(&pbus->lock); err = spmi_controller_add(ctrl); if (err) - goto err_domain_remove; + goto err_remove_irq; + + if (pbus->dom) + irq_set_chained_handler_and_data(pbus->irq, mtk_spmi_handle_chained_irq, pbus); return 0; -err_domain_remove: - clk_bulk_disable_unprepare(arb->nclks, arb->clks); +err_remove_irq: + mtk_spmi_irq_remove(pbus); +err_disable_clks: + clk_bulk_disable_unprepare(pbus->nclks, pbus->clks); err_put_clks: - clk_bulk_put(arb->nclks, arb->clks); + clk_bulk_put(pbus->nclks, pbus->clks); return err; } +static int mtk_spmi_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct pmif *arb; + u32 chan_offset; + u8 cur_bus = 0; + int ret; + + arb = devm_kzalloc(&pdev->dev, sizeof(*arb), GFP_KERNEL); + if (!arb) + return -ENOMEM; + + arb->data = device_get_match_data(&pdev->dev); + if (!arb->data) { + dev_err(&pdev->dev, "Cannot get drv_data\n"); + return -EINVAL; + } + + platform_set_drvdata(pdev, arb); + + if (!arb->data->num_spmi_buses) { + ret = mtk_spmi_bus_probe(pdev, node, arb->data, &arb->bus[cur_bus]); + if (ret) + return ret; + } else { + for_each_available_child_of_node_scoped(node, child) { + if (!of_node_name_eq(child, "spmi")) + continue; + + ret = mtk_spmi_bus_probe(pdev, child, arb->data, + &arb->bus[cur_bus]); + if (ret) + return ret; + cur_bus++; + } + } + + chan_offset = PMIF_CHAN_OFFSET * arb->data->soc_chan; + arb->chan.ch_sta = PMIF_SWINF_0_STA + chan_offset; + arb->chan.wdata = PMIF_SWINF_0_WDATA_31_0 + chan_offset; + arb->chan.rdata = PMIF_SWINF_0_RDATA_31_0 + chan_offset; + arb->chan.ch_send = PMIF_SWINF_0_ACC + chan_offset; + arb->chan.ch_rdy = PMIF_SWINF_0_VLD_CLR + chan_offset; + + return 0; +} + static void mtk_spmi_remove(struct platform_device *pdev) { - struct spmi_controller *ctrl = platform_get_drvdata(pdev); - struct pmif *arb = spmi_controller_get_drvdata(ctrl); + struct pmif *arb = platform_get_drvdata(pdev); + int i; - spmi_controller_remove(ctrl); - clk_bulk_disable_unprepare(arb->nclks, arb->clks); - clk_bulk_put(arb->nclks, arb->clks); + for (i = 0; i < PMIF_MAX_BUSES; i++) { + struct pmif_bus *pbus = &arb->bus[i]; + + if (!pbus->ctrl) + continue; + + mtk_spmi_irq_remove(pbus); + spmi_controller_remove(pbus->ctrl); + clk_bulk_disable_unprepare(pbus->nclks, pbus->clks); + clk_bulk_put(pbus->nclks, pbus->clks); + } } static const struct of_device_id mtk_spmi_match_table[] = { @@ -534,6 +842,9 @@ static const struct of_device_id mtk_spmi_match_table[] = { .compatible = "mediatek,mt8195-spmi", .data = &mt8195_pmif_arb, }, { + .compatible = "mediatek,mt8196-spmi", + .data = &mt8196_pmif_arb, + }, { /* sentinel */ }, }; @@ -549,6 +860,7 @@ static struct platform_driver mtk_spmi_driver = { }; module_platform_driver(mtk_spmi_driver); +MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>"); MODULE_AUTHOR("Hsin-Hsiung Wang <hsin-hsiung.wang@mediatek.com>"); MODULE_DESCRIPTION("MediaTek SPMI Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c index 91581974ef84..69f8d456324a 100644 --- a/drivers/spmi/spmi-pmic-arb.c +++ b/drivers/spmi/spmi-pmic-arb.c @@ -2,6 +2,8 @@ /* * Copyright (c) 2012-2015, 2017, 2021, The Linux Foundation. All rights reserved. */ + +#include <linux/bitfield.h> #include <linux/bitmap.h> #include <linux/delay.h> #include <linux/err.h> @@ -25,12 +27,12 @@ #define PMIC_ARB_VERSION_V3_MIN 0x30000000 #define PMIC_ARB_VERSION_V5_MIN 0x50000000 #define PMIC_ARB_VERSION_V7_MIN 0x70000000 +#define PMIC_ARB_VERSION_V8_MIN 0x80000000 #define PMIC_ARB_INT_EN 0x0004 #define PMIC_ARB_FEATURES 0x0004 #define PMIC_ARB_FEATURES_PERIPH_MASK GENMASK(10, 0) - -#define PMIC_ARB_FEATURES1 0x0008 +#define PMIC_ARB_FEATURES_V8_PERIPH_MASK GENMASK(12, 0) /* PMIC Arbiter channel registers offsets */ #define PMIC_ARB_CMD 0x00 @@ -50,9 +52,11 @@ #define SPMI_MAPPING_BIT_IS_1_RESULT(X) (((X) >> 0) & 0xFF) #define SPMI_MAPPING_TABLE_TREE_DEPTH 16 /* Maximum of 16-bits */ -#define PMIC_ARB_MAX_PPID BIT(12) /* PPID is 12bit */ +#define PMIC_ARB_MAX_PPID BIT(13) #define PMIC_ARB_APID_VALID BIT(15) -#define PMIC_ARB_CHAN_IS_IRQ_OWNER(reg) ((reg) & BIT(24)) +#define PMIC_ARB_CHAN_IS_IRQ_OWNER_MASK BIT(24) +#define PMIC_ARB_V8_CHAN_IS_IRQ_OWNER_MASK BIT(31) + #define INVALID_EE 0xFF /* Ownership Table */ @@ -96,30 +100,37 @@ enum pmic_arb_channel { PMIC_ARB_CHANNEL_OBS, }; -#define PMIC_ARB_MAX_BUSES 2 +#define PMIC_ARB_MAX_BUSES 4 /* Maximum number of support PMIC peripherals */ #define PMIC_ARB_MAX_PERIPHS 512 #define PMIC_ARB_MAX_PERIPHS_V7 1024 +#define PMIC_ARB_MAX_PERIPHS_V8 8192 #define PMIC_ARB_TIMEOUT_US 1000 #define PMIC_ARB_MAX_TRANS_BYTES (8) #define PMIC_ARB_APID_MASK 0xFF -#define PMIC_ARB_PPID_MASK 0xFFF +#define PMIC_ARB_PPID_MASK GENMASK(11, 0) +#define PMIC_ARB_V8_PPID_MASK GENMASK(12, 0) /* interrupt enable bit */ #define SPMI_PIC_ACC_ENABLE_BIT BIT(0) +#define HWIRQ_SID_MASK GENMASK(28, 24) +#define HWIRQ_PID_MASK GENMASK(23, 16) +#define HWIRQ_IRQID_MASK GENMASK(15, 13) +#define HWIRQ_APID_MASK GENMASK(12, 0) + #define spec_to_hwirq(slave_id, periph_id, irq_id, apid) \ - ((((slave_id) & 0xF) << 28) | \ - (((periph_id) & 0xFF) << 20) | \ - (((irq_id) & 0x7) << 16) | \ - (((apid) & 0x3FF) << 0)) + (FIELD_PREP(HWIRQ_SID_MASK, (slave_id)) | \ + FIELD_PREP(HWIRQ_PID_MASK, (periph_id)) | \ + FIELD_PREP(HWIRQ_IRQID_MASK, (irq_id)) | \ + FIELD_PREP(HWIRQ_APID_MASK, (apid))) -#define hwirq_to_sid(hwirq) (((hwirq) >> 28) & 0xF) -#define hwirq_to_per(hwirq) (((hwirq) >> 20) & 0xFF) -#define hwirq_to_irq(hwirq) (((hwirq) >> 16) & 0x7) -#define hwirq_to_apid(hwirq) (((hwirq) >> 0) & 0x3FF) +#define hwirq_to_sid(hwirq) FIELD_GET(HWIRQ_SID_MASK, (hwirq)) +#define hwirq_to_per(hwirq) FIELD_GET(HWIRQ_PID_MASK, (hwirq)) +#define hwirq_to_irq(hwirq) FIELD_GET(HWIRQ_IRQID_MASK, (hwirq)) +#define hwirq_to_apid(hwirq) FIELD_GET(HWIRQ_APID_MASK, (hwirq)) struct pmic_arb_ver_ops; @@ -138,11 +149,12 @@ struct spmi_pmic_arb; * @domain: irq domain object for PMIC IRQ domain * @intr: address of the SPMI interrupt control registers. * @cnfg: address of the PMIC Arbiter configuration registers. + * @apid_owner: on v8: address of APID owner mapping table registers * @spmic: spmi controller registered for this bus * @lock: lock to synchronize accesses. - * @base_apid: on v7: minimum APID associated with the particular SPMI - * bus instance - * @apid_count: on v5 and v7: number of APIDs associated with the + * @base_apid: on v7 and v8: minimum APID associated with the + * particular SPMI bus instance + * @apid_count: on v5, v7 and v8: number of APIDs associated with the * particular SPMI bus instance * @mapping_table: in-memory copy of PPID -> APID mapping table. * @mapping_table_valid:bitmap containing valid-only periphs @@ -159,6 +171,7 @@ struct spmi_pmic_arb_bus { struct irq_domain *domain; void __iomem *intr; void __iomem *cnfg; + void __iomem *apid_owner; struct spmi_controller *spmic; raw_spinlock_t lock; u16 base_apid; @@ -181,6 +194,7 @@ struct spmi_pmic_arb_bus { * @wr_base: on v1 "core", on v2 "chnls" register base off DT. * @core: core register base for v2 and above only (see above) * @core_size: core register base size + * @apid_map: on v8, APID mapping table register base * @channel: execution environment channel to use for accesses. * @ee: the current Execution Environment * @ver_ops: version dependent operations. @@ -193,6 +207,7 @@ struct spmi_pmic_arb { void __iomem *wr_base; void __iomem *core; resource_size_t core_size; + void __iomem *apid_map; u8 channel; u8 ee; const struct pmic_arb_ver_ops *ver_ops; @@ -206,6 +221,7 @@ struct spmi_pmic_arb { * * @ver_str: version string. * @get_core_resources: initializes the core, observer and channels + * @get_bus_resources: requests per-SPMI bus register resources * @init_apid: finds the apid base and count * @ppid_to_apid: finds the apid for a given ppid. * @non_data_cmd: on v1 issues an spmi non-data command. @@ -227,6 +243,9 @@ struct spmi_pmic_arb { struct pmic_arb_ver_ops { const char *ver_str; int (*get_core_resources)(struct platform_device *pdev, void __iomem *core); + int (*get_bus_resources)(struct platform_device *pdev, + struct device_node *node, + struct spmi_pmic_arb_bus *bus); int (*init_apid)(struct spmi_pmic_arb_bus *bus, int index); int (*ppid_to_apid)(struct spmi_pmic_arb_bus *bus, u16 ppid); /* spmi commands (read_cmd, write_cmd, cmd) functionality */ @@ -656,7 +675,7 @@ static int periph_interrupt(struct spmi_pmic_arb_bus *bus, u16 apid) unsigned int irq; u32 status, id; int handled = 0; - u8 sid = (bus->apid_data[apid].ppid >> 8) & 0xF; + u8 sid = (bus->apid_data[apid].ppid >> 8) & 0x1F; u8 per = bus->apid_data[apid].ppid & 0xFF; status = readl_relaxed(pmic_arb->ver_ops->irq_status(bus, apid)); @@ -686,7 +705,7 @@ static void pmic_arb_chained_irq(struct irq_desc *desc) int last = bus->max_apid; /* * acc_offset will be non-zero for the secondary SPMI bus instance on - * v7 controllers. + * v7 and v8 controllers. */ int acc_offset = bus->base_apid >> 5; u8 ee = pmic_arb->ee; @@ -913,7 +932,8 @@ static int qpnpint_irq_domain_translate(struct irq_domain *d, return -EINVAL; if (fwspec->param_count != 4) return -EINVAL; - if (intspec[0] > 0xF || intspec[1] > 0xFF || intspec[2] > 0x7) + if (intspec[0] > FIELD_MAX(HWIRQ_SID_MASK) || intspec[1] > FIELD_MAX(HWIRQ_PID_MASK) || + intspec[2] > FIELD_MAX(HWIRQ_IRQID_MASK)) return -EINVAL; ppid = intspec[0] << 8 | intspec[1]; @@ -1160,7 +1180,9 @@ static int pmic_arb_ppid_to_apid_v2(struct spmi_pmic_arb_bus *bus, u16 ppid) return apid_valid & ~PMIC_ARB_APID_VALID; } -static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) +static int _pmic_arb_read_apid_map(struct spmi_pmic_arb_bus *bus, + void __iomem *ppid_base, unsigned long ppid_mask, + u8 ppid_shift, unsigned long irq_owner_mask) { struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; struct apid_data *apidd; @@ -1171,7 +1193,7 @@ static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) /* * In order to allow multiple EEs to write to a single PPID in arbiter - * version 5 and 7, there is more than one APID mapped to each PPID. + * version 5,7 and 8, there can be more than one APID mapped to each PPID. * The owner field for each of these mappings specifies the EE which is * allowed to write to the APID. The owner of the last (highest) APID * which has the IRQ owner bit set for a given PPID will receive @@ -1183,19 +1205,30 @@ static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) * APID = N to N+M-1 are assigned to the secondary bus * where N = number of APIDs supported by the primary bus and * M = number of APIDs supported by the secondary bus + * + * In arbiter version 8, the APID numbering space is divided between + * the SPMI buses according to this mapping: + * APID = 0 to N-1 --> bus 0 + * APID = N to N+M-1 --> bus 1 + * APID = N+M to N+M+P-1 --> bus 2 + * APID = N+M+P to N+M+P+Q-1 --> bus 3 + * where N = number of APIDs supported by bus 0 + * M = number of APIDs supported by bus 1 + * P = number of APIDs supported by bus 2 + * Q = number of APIDs supported by bus 3 */ + apidd = &bus->apid_data[bus->base_apid]; apid_max = bus->base_apid + bus->apid_count; for (i = bus->base_apid; i < apid_max; i++, apidd++) { offset = pmic_arb->ver_ops->apid_map_offset(i); if (offset >= pmic_arb->core_size) break; - - regval = readl_relaxed(pmic_arb->core + offset); + regval = readl_relaxed(ppid_base + offset); if (!regval) continue; - ppid = (regval >> 8) & PMIC_ARB_PPID_MASK; - is_irq_ee = PMIC_ARB_CHAN_IS_IRQ_OWNER(regval); + ppid = (regval >> ppid_shift) & ppid_mask; + is_irq_ee = regval & irq_owner_mask; regval = readl_relaxed(pmic_arb->ver_ops->apid_owner(bus, i)); apidd->write_ee = SPMI_OWNERSHIP_PERIPH2OWNER(regval); @@ -1237,6 +1270,12 @@ static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) return 0; } +static int pmic_arb_read_apid_map_v5(struct spmi_pmic_arb_bus *bus) +{ + return _pmic_arb_read_apid_map(bus, bus->pmic_arb->core, PMIC_ARB_PPID_MASK, + 8, PMIC_ARB_CHAN_IS_IRQ_OWNER_MASK); +} + static int pmic_arb_ppid_to_apid_v5(struct spmi_pmic_arb_bus *bus, u16 ppid) { if (!(bus->ppid_to_apid[ppid] & PMIC_ARB_APID_VALID)) @@ -1345,37 +1384,46 @@ static int pmic_arb_get_core_resources_v7(struct platform_device *pdev, return pmic_arb_get_obsrvr_chnls_v2(pdev); } -/* - * Only v7 supports 2 buses. Each bus will get a different apid count, read - * from different registers. - */ -static int pmic_arb_init_apid_v7(struct spmi_pmic_arb_bus *bus, int index) +static int _pmic_arb_init_apid_v7(struct spmi_pmic_arb_bus *bus, int index, + int max_buses, unsigned long periph_mask) { struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; - int ret; + int i; - if (index == 0) { - bus->base_apid = 0; - bus->apid_count = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES) & - PMIC_ARB_FEATURES_PERIPH_MASK; - } else if (index == 1) { - bus->base_apid = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES) & - PMIC_ARB_FEATURES_PERIPH_MASK; - bus->apid_count = readl_relaxed(pmic_arb->core + PMIC_ARB_FEATURES1) & - PMIC_ARB_FEATURES_PERIPH_MASK; - } else { - dev_err(&bus->spmic->dev, "Unsupported buses count %d detected\n", - bus->id); + if (index < 0 || index >= max_buses) { + dev_err(&bus->spmic->dev, "Unsupported bus index %d detected\n", + index); return -EINVAL; } - if (bus->base_apid + bus->apid_count > pmic_arb->max_periphs) { - dev_err(&bus->spmic->dev, "Unsupported APID count %d detected\n", + bus->base_apid = 0; + bus->apid_count = 0; + for (i = 0; i <= index; i++) { + bus->base_apid += bus->apid_count; + bus->apid_count = readl_relaxed(pmic_arb->core + + PMIC_ARB_FEATURES + i * 4) & + periph_mask; + } + + if (bus->apid_count == 0) { + dev_err(&bus->spmic->dev, "Bus %d not implemented\n", index); + return -EINVAL; + } else if (bus->base_apid + bus->apid_count > pmic_arb->max_periphs) { + dev_err(&bus->spmic->dev, "Unsupported max APID %d detected\n", bus->base_apid + bus->apid_count); return -EINVAL; } - ret = pmic_arb_init_apid_min_max(bus); + return pmic_arb_init_apid_min_max(bus); +} + +/* + * Arbiter v7 supports 2 buses. Each bus will get a different apid count, read + * from different registers. + */ +static int pmic_arb_init_apid_v7(struct spmi_pmic_arb_bus *bus, int index) +{ + int ret = _pmic_arb_init_apid_v7(bus, index, 2, PMIC_ARB_FEATURES_PERIPH_MASK); if (ret) return ret; @@ -1424,6 +1472,102 @@ static int pmic_arb_offset_v7(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, return offset; } +static int pmic_arb_get_core_resources_v8(struct platform_device *pdev, + void __iomem *core) +{ + struct spmi_pmic_arb *pmic_arb = platform_get_drvdata(pdev); + + pmic_arb->apid_map = devm_platform_ioremap_resource_byname(pdev, "chnl_map"); + if (IS_ERR(pmic_arb->apid_map)) + return PTR_ERR(pmic_arb->apid_map); + + pmic_arb->core = core; + + pmic_arb->max_periphs = PMIC_ARB_MAX_PERIPHS_V8; + + return pmic_arb_get_obsrvr_chnls_v2(pdev); +} + +static int pmic_arb_get_bus_resources_v8(struct platform_device *pdev, + struct device_node *node, + struct spmi_pmic_arb_bus *bus) +{ + int index; + + index = of_property_match_string(node, "reg-names", "chnl_owner"); + if (index < 0) { + dev_err(&pdev->dev, "chnl_owner reg region missing\n"); + return -EINVAL; + } + + bus->apid_owner = devm_of_iomap(&pdev->dev, node, index, NULL); + + return PTR_ERR_OR_ZERO(bus->apid_owner); +} + +static int pmic_arb_read_apid_map_v8(struct spmi_pmic_arb_bus *bus) +{ + return _pmic_arb_read_apid_map(bus, bus->pmic_arb->apid_map, + PMIC_ARB_V8_PPID_MASK, 0, + PMIC_ARB_V8_CHAN_IS_IRQ_OWNER_MASK); +} + +/* + * Arbiter v8 supports up to 4 buses. Each bus will get a different apid count, read + * from different registers. + */ +static int pmic_arb_init_apid_v8(struct spmi_pmic_arb_bus *bus, int index) +{ + int ret = _pmic_arb_init_apid_v7(bus, index, 4, + PMIC_ARB_FEATURES_V8_PERIPH_MASK); + if (ret) + return ret; + + ret = pmic_arb_read_apid_map_v8(bus); + if (ret) { + dev_err(&bus->spmic->dev, "could not read APID->PPID mapping table, rc= %d\n", + ret); + return ret; + } + + return 0; +} + +/* + * v8 offset per ee and per apid for observer channels and per apid for + * read/write channels. + */ +static int pmic_arb_offset_v8(struct spmi_pmic_arb_bus *bus, u8 sid, u16 addr, + enum pmic_arb_channel ch_type) +{ + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + u16 apid; + int rc; + u32 offset = 0; + u16 ppid = (sid << 8) | (addr >> 8); + + rc = pmic_arb->ver_ops->ppid_to_apid(bus, ppid); + if (rc < 0) + return rc; + + apid = rc; + switch (ch_type) { + case PMIC_ARB_CHANNEL_OBS: + offset = 0x40000 * pmic_arb->ee + 0x20 * apid; + break; + case PMIC_ARB_CHANNEL_RW: + if (bus->apid_data[apid].write_ee != pmic_arb->ee) { + dev_err(&bus->spmic->dev, "disallowed SPMI write to sid=%u, addr=0x%04X\n", + sid, addr); + return -EPERM; + } + offset = 0x200 * apid; + break; + } + + return offset; +} + static u32 pmic_arb_fmt_cmd_v1(u8 opc, u8 sid, u16 addr, u8 bc) { return (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7); @@ -1491,6 +1635,14 @@ pmic_arb_acc_enable_v7(struct spmi_pmic_arb_bus *bus, u16 n) } static void __iomem * +pmic_arb_acc_enable_v8(struct spmi_pmic_arb_bus *bus, u16 n) +{ + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + + return pmic_arb->wr_base + 0x100 + 0x200 * n; +} + +static void __iomem * pmic_arb_irq_status_v1(struct spmi_pmic_arb_bus *bus, u16 n) { return bus->intr + 0x600 + 0x4 * n; @@ -1517,6 +1669,14 @@ pmic_arb_irq_status_v7(struct spmi_pmic_arb_bus *bus, u16 n) } static void __iomem * +pmic_arb_irq_status_v8(struct spmi_pmic_arb_bus *bus, u16 n) +{ + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + + return pmic_arb->wr_base + 0x104 + 0x200 * n; +} + +static void __iomem * pmic_arb_irq_clear_v1(struct spmi_pmic_arb_bus *bus, u16 n) { return bus->intr + 0xA00 + 0x4 * n; @@ -1542,6 +1702,14 @@ pmic_arb_irq_clear_v7(struct spmi_pmic_arb_bus *bus, u16 n) return pmic_arb->wr_base + 0x108 + 0x1000 * n; } +static void __iomem * +pmic_arb_irq_clear_v8(struct spmi_pmic_arb_bus *bus, u16 n) +{ + struct spmi_pmic_arb *pmic_arb = bus->pmic_arb; + + return pmic_arb->wr_base + 0x108 + 0x200 * n; +} + static u32 pmic_arb_apid_map_offset_v2(u16 n) { return 0x800 + 0x4 * n; @@ -1557,6 +1725,12 @@ static u32 pmic_arb_apid_map_offset_v7(u16 n) return 0x2000 + 0x4 * n; } +static u32 pmic_arb_apid_map_offset_v8(u16 n) +{ + /* For v8, offset is from "chnl_map" base register, not "core". */ + return 0x4 * n; +} + static void __iomem * pmic_arb_apid_owner_v2(struct spmi_pmic_arb_bus *bus, u16 n) { @@ -1564,7 +1738,7 @@ pmic_arb_apid_owner_v2(struct spmi_pmic_arb_bus *bus, u16 n) } /* - * For arbiter version 7, APID ownership table registers have independent + * For arbiter version 7 and 8, APID ownership table registers have independent * numbering space for each SPMI bus instance, so each is indexed starting from * 0. */ @@ -1574,6 +1748,12 @@ pmic_arb_apid_owner_v7(struct spmi_pmic_arb_bus *bus, u16 n) return bus->cnfg + 0x4 * (n - bus->base_apid); } +static void __iomem * +pmic_arb_apid_owner_v8(struct spmi_pmic_arb_bus *bus, u16 n) +{ + return bus->apid_owner + 0x4 * (n - bus->base_apid); +} + static const struct pmic_arb_ver_ops pmic_arb_v1 = { .ver_str = "v1", .get_core_resources = pmic_arb_get_core_resources_v1, @@ -1654,6 +1834,23 @@ static const struct pmic_arb_ver_ops pmic_arb_v7 = { .apid_owner = pmic_arb_apid_owner_v7, }; +static const struct pmic_arb_ver_ops pmic_arb_v8 = { + .ver_str = "v8", + .get_core_resources = pmic_arb_get_core_resources_v8, + .get_bus_resources = pmic_arb_get_bus_resources_v8, + .init_apid = pmic_arb_init_apid_v8, + .ppid_to_apid = pmic_arb_ppid_to_apid_v5, + .non_data_cmd = pmic_arb_non_data_cmd_v2, + .offset = pmic_arb_offset_v8, + .fmt_cmd = pmic_arb_fmt_cmd_v2, + .owner_acc_status = pmic_arb_owner_acc_status_v7, + .acc_enable = pmic_arb_acc_enable_v8, + .irq_status = pmic_arb_irq_status_v8, + .irq_clear = pmic_arb_irq_clear_v8, + .apid_map_offset = pmic_arb_apid_map_offset_v8, + .apid_owner = pmic_arb_apid_owner_v8, +}; + static const struct irq_domain_ops pmic_arb_irq_domain_ops = { .activate = qpnpint_irq_domain_activate, .alloc = qpnpint_irq_domain_alloc, @@ -1731,6 +1928,12 @@ static int spmi_pmic_arb_bus_init(struct platform_device *pdev, bus->spmic = ctrl; bus->id = bus_index; + if (pmic_arb->ver_ops->get_bus_resources) { + ret = pmic_arb->ver_ops->get_bus_resources(pdev, node, bus); + if (ret) + return ret; + } + ret = pmic_arb->ver_ops->init_apid(bus, bus_index); if (ret) return ret; @@ -1825,8 +2028,10 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev) pmic_arb->ver_ops = &pmic_arb_v3; else if (hw_ver < PMIC_ARB_VERSION_V7_MIN) pmic_arb->ver_ops = &pmic_arb_v5; - else + else if (hw_ver < PMIC_ARB_VERSION_V8_MIN) pmic_arb->ver_ops = &pmic_arb_v7; + else + pmic_arb->ver_ops = &pmic_arb_v8; err = pmic_arb->ver_ops->get_core_resources(pdev, core); if (err) @@ -1875,6 +2080,7 @@ static void spmi_pmic_arb_remove(struct platform_device *pdev) static const struct of_device_id spmi_pmic_arb_match_table[] = { { .compatible = "qcom,spmi-pmic-arb", }, { .compatible = "qcom,x1e80100-spmi-pmic-arb", }, + { .compatible = "qcom,glymur-spmi-pmic-arb", }, {}, }; MODULE_DEVICE_TABLE(of, spmi_pmic_arb_match_table); diff --git a/drivers/staging/iio/addac/adt7316-i2c.c b/drivers/staging/iio/addac/adt7316-i2c.c index f45968ef94ea..3bdaee925dee 100644 --- a/drivers/staging/iio/addac/adt7316-i2c.c +++ b/drivers/staging/iio/addac/adt7316-i2c.c @@ -136,7 +136,7 @@ static struct i2c_driver adt7316_driver = { .driver = { .name = "adt7316", .of_match_table = adt7316_of_match, - .pm = ADT7316_PM_OPS, + .pm = pm_sleep_ptr(&adt7316_pm_ops), }, .probe = adt7316_i2c_probe, .id_table = adt7316_i2c_id, diff --git a/drivers/staging/iio/addac/adt7316-spi.c b/drivers/staging/iio/addac/adt7316-spi.c index af513e003da7..f91325d11394 100644 --- a/drivers/staging/iio/addac/adt7316-spi.c +++ b/drivers/staging/iio/addac/adt7316-spi.c @@ -142,7 +142,7 @@ static struct spi_driver adt7316_driver = { .driver = { .name = "adt7316", .of_match_table = adt7316_of_spi_match, - .pm = ADT7316_PM_OPS, + .pm = pm_sleep_ptr(&adt7316_pm_ops), }, .probe = adt7316_spi_probe, .id_table = adt7316_spi_id, diff --git a/drivers/staging/iio/addac/adt7316.c b/drivers/staging/iio/addac/adt7316.c index 8a9a8262c2be..59fb3bd26bc1 100644 --- a/drivers/staging/iio/addac/adt7316.c +++ b/drivers/staging/iio/addac/adt7316.c @@ -2082,7 +2082,6 @@ static const struct attribute_group adt7516_event_attribute_group = { .name = "events", }; -#ifdef CONFIG_PM_SLEEP static int adt7316_disable(struct device *dev) { struct iio_dev *dev_info = dev_get_drvdata(dev); @@ -2098,9 +2097,8 @@ static int adt7316_enable(struct device *dev) return _adt7316_store_enabled(chip, 1); } -EXPORT_SYMBOL_GPL(adt7316_pm_ops); -SIMPLE_DEV_PM_OPS(adt7316_pm_ops, adt7316_disable, adt7316_enable); -#endif + +EXPORT_GPL_SIMPLE_DEV_PM_OPS(adt7316_pm_ops, adt7316_disable, adt7316_enable); static const struct iio_info adt7316_info = { .attrs = &adt7316_attribute_group, diff --git a/drivers/staging/iio/addac/adt7316.h b/drivers/staging/iio/addac/adt7316.h index 8c2a92ae7157..f208f0d3583a 100644 --- a/drivers/staging/iio/addac/adt7316.h +++ b/drivers/staging/iio/addac/adt7316.h @@ -22,12 +22,8 @@ struct adt7316_bus { int (*multi_write)(void *client, u8 first_reg, u8 count, u8 *data); }; -#ifdef CONFIG_PM_SLEEP extern const struct dev_pm_ops adt7316_pm_ops; -#define ADT7316_PM_OPS (&adt7316_pm_ops) -#else -#define ADT7316_PM_OPS NULL -#endif + int adt7316_probe(struct device *dev, struct adt7316_bus *bus, const char *name); diff --git a/drivers/staging/iio/frequency/ad9832.c b/drivers/staging/iio/frequency/ad9832.c index 49388da5a684..b87ea1781b27 100644 --- a/drivers/staging/iio/frequency/ad9832.c +++ b/drivers/staging/iio/frequency/ad9832.c @@ -23,12 +23,9 @@ #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> -#include "ad9832.h" - #include "dds.h" /* Registers */ - #define AD9832_FREQ0LL 0x0 #define AD9832_FREQ0HL 0x1 #define AD9832_FREQ0LM 0x2 @@ -45,14 +42,12 @@ #define AD9832_PHASE2H 0xD #define AD9832_PHASE3L 0xE #define AD9832_PHASE3H 0xF - #define AD9832_PHASE_SYM 0x10 #define AD9832_FREQ_SYM 0x11 #define AD9832_PINCTRL_EN 0x12 #define AD9832_OUTPUT_EN 0x13 /* Command Control Bits */ - #define AD9832_CMD_PHA8BITSW 0x1 #define AD9832_CMD_PHA16BITSW 0x0 #define AD9832_CMD_FRE8BITSW 0x3 @@ -92,7 +87,6 @@ * @phase_data: tuning word spi transmit buffer * @freq_data: tuning word spi transmit buffer */ - struct ad9832_state { struct spi_device *spi; struct clk *mclk; @@ -299,16 +293,10 @@ static const struct iio_info ad9832_info = { static int ad9832_probe(struct spi_device *spi) { - struct ad9832_platform_data *pdata = dev_get_platdata(&spi->dev); struct iio_dev *indio_dev; struct ad9832_state *st; int ret; - if (!pdata) { - dev_dbg(&spi->dev, "no platform data?\n"); - return -ENODEV; - } - indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); if (!indio_dev) return -ENOMEM; @@ -335,7 +323,6 @@ static int ad9832_probe(struct spi_device *spi) indio_dev->modes = INDIO_DIRECT_MODE; /* Setup default messages */ - st->xfer.tx_buf = &st->data; st->xfer.len = 2; @@ -379,30 +366,6 @@ static int ad9832_probe(struct spi_device *spi) return ret; } - ret = ad9832_write_frequency(st, AD9832_FREQ0HM, pdata->freq0); - if (ret) - return ret; - - ret = ad9832_write_frequency(st, AD9832_FREQ1HM, pdata->freq1); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE0H, pdata->phase0); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE1H, pdata->phase1); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE2H, pdata->phase2); - if (ret) - return ret; - - ret = ad9832_write_phase(st, AD9832_PHASE3H, pdata->phase3); - if (ret) - return ret; - return devm_iio_device_register(&spi->dev, indio_dev); } diff --git a/drivers/staging/iio/frequency/ad9832.h b/drivers/staging/iio/frequency/ad9832.h deleted file mode 100644 index d0d840edb8d2..000000000000 --- a/drivers/staging/iio/frequency/ad9832.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * AD9832 SPI DDS driver - * - * Copyright 2011 Analog Devices Inc. - */ -#ifndef IIO_DDS_AD9832_H_ -#define IIO_DDS_AD9832_H_ - -/* - * TODO: struct ad9832_platform_data needs to go into include/linux/iio - */ - -/** - * struct ad9832_platform_data - platform specific information - * @freq0: power up freq0 tuning word in Hz - * @freq1: power up freq1 tuning word in Hz - * @phase0: power up phase0 value [0..4095] correlates with 0..2PI - * @phase1: power up phase1 value [0..4095] correlates with 0..2PI - * @phase2: power up phase2 value [0..4095] correlates with 0..2PI - * @phase3: power up phase3 value [0..4095] correlates with 0..2PI - */ - -struct ad9832_platform_data { - unsigned long freq0; - unsigned long freq1; - unsigned short phase0; - unsigned short phase1; - unsigned short phase2; - unsigned short phase3; -}; - -#endif /* IIO_DDS_AD9832_H_ */ diff --git a/include/dt-bindings/interconnect/mediatek,mt8196.h b/include/dt-bindings/interconnect/mediatek,mt8196.h new file mode 100644 index 000000000000..de700fa73223 --- /dev/null +++ b/include/dt-bindings/interconnect/mediatek,mt8196.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) 2025 Collabora Ltd. + * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> + */ + +#ifndef __DT_BINDINGS_INTERCONNECT_MEDIATEK_MT8196_H +#define __DT_BINDINGS_INTERCONNECT_MEDIATEK_MT8196_H + +#define SLAVE_DDR_EMI 0 +#define MASTER_MCUSYS 1 +#define MASTER_MCU_0 2 +#define MASTER_MCU_1 3 +#define MASTER_MCU_2 4 +#define MASTER_MCU_3 5 +#define MASTER_MCU_4 6 +#define MASTER_GPUSYS 7 +#define MASTER_MMSYS 8 +#define MASTER_MM_VPU 9 +#define MASTER_MM_DISP 10 +#define MASTER_MM_VDEC 11 +#define MASTER_MM_VENC 12 +#define MASTER_MM_CAM 13 +#define MASTER_MM_IMG 14 +#define MASTER_MM_MDP 15 +#define MASTER_VPUSYS 16 +#define MASTER_VPU_0 17 +#define MASTER_VPU_1 18 +#define MASTER_MDLASYS 19 +#define MASTER_MDLA_0 20 +#define MASTER_UFS 21 +#define MASTER_PCIE 22 +#define MASTER_USB 23 +#define MASTER_WIFI 24 +#define MASTER_BT 25 +#define MASTER_NETSYS 26 +#define MASTER_DBGIF 27 +#define SLAVE_HRT_DDR_EMI 28 +#define MASTER_HRT_MMSYS 29 +#define MASTER_HRT_MM_DISP 30 +#define MASTER_HRT_MM_VDEC 31 +#define MASTER_HRT_MM_VENC 32 +#define MASTER_HRT_MM_CAM 33 +#define MASTER_HRT_MM_IMG 34 +#define MASTER_HRT_MM_MDP 35 +#define MASTER_HRT_ADSP 36 +#define MASTER_HRT_DBGIF 37 +#endif diff --git a/include/linux/comedi/comedi_8254.h b/include/linux/comedi/comedi_8254.h index d527f04400df..91f5861061ec 100644 --- a/include/linux/comedi/comedi_8254.h +++ b/include/linux/comedi/comedi_8254.h @@ -83,11 +83,11 @@ typedef unsigned int comedi_8254_iocb_fn(struct comedi_8254 *i8254, int dir, * @divisor: divisor for single counter * @divisor1: divisor loaded into first cascaded counter * @divisor2: divisor loaded into second cascaded counter - * #next_div: next divisor for single counter + * @next_div: next divisor for single counter * @next_div1: next divisor to use for first cascaded counter * @next_div2: next divisor to use for second cascaded counter - * @clock_src; current clock source for each counter (driver specific) - * @gate_src; current gate source for each counter (driver specific) + * @clock_src: current clock source for each counter (driver specific) + * @gate_src: current gate source for each counter (driver specific) * @busy: flags used to indicate that a counter is "busy" * @insn_config: driver specific (*insn_config) callback */ diff --git a/include/linux/coresight-pmu.h b/include/linux/coresight-pmu.h index 89b0ac0014b0..2e179abe472a 100644 --- a/include/linux/coresight-pmu.h +++ b/include/linux/coresight-pmu.h @@ -22,30 +22,6 @@ #define CORESIGHT_LEGACY_CPU_TRACE_ID(cpu) (0x10 + (cpu * 2)) /* - * Below are the definition of bit offsets for perf option, and works as - * arbitrary values for all ETM versions. - * - * Most of them are orignally from ETMv3.5/PTM's ETMCR config, therefore, - * ETMv3.5/PTM doesn't define ETMCR config bits with prefix "ETM3_" and - * directly use below macros as config bits. - */ -#define ETM_OPT_BRANCH_BROADCAST 8 -#define ETM_OPT_CYCACC 12 -#define ETM_OPT_CTXTID 14 -#define ETM_OPT_CTXTID2 15 -#define ETM_OPT_TS 28 -#define ETM_OPT_RETSTK 29 - -/* ETMv4 CONFIGR programming bits for the ETM OPTs */ -#define ETM4_CFG_BIT_BB 3 -#define ETM4_CFG_BIT_CYCACC 4 -#define ETM4_CFG_BIT_CTXTID 6 -#define ETM4_CFG_BIT_VMID 7 -#define ETM4_CFG_BIT_TS 11 -#define ETM4_CFG_BIT_RETSTK 12 -#define ETM4_CFG_BIT_VMID_OPT 15 - -/* * Interpretation of the PERF_RECORD_AUX_OUTPUT_HW_ID payload. * Used to associate a CPU with the CoreSight Trace ID. * [07:00] - Trace ID - uses 8 bits to make value easy to read in file. diff --git a/include/linux/eeprom_93cx6.h b/include/linux/eeprom_93cx6.h index 3a485cc0e0fa..194a4a49ff1d 100644 --- a/include/linux/eeprom_93cx6.h +++ b/include/linux/eeprom_93cx6.h @@ -31,10 +31,10 @@ * struct eeprom_93cx6 - control structure for setting the commands * for reading the eeprom data. * @data: private pointer for the driver. - * @register_read(struct eeprom_93cx6 *eeprom): handler to - * read the eeprom register, this function should set all reg_* fields. - * @register_write(struct eeprom_93cx6 *eeprom): handler to - * write to the eeprom register by using all reg_* fields. + * @register_read: handler to read the eeprom register; + * this function should set all reg_* fields. + * @register_write: handler to write to the eeprom register by using + * all reg_* fields. * @width: eeprom width, should be one of the PCI_EEPROM_WIDTH_* defines * @quirks: eeprom or controller quirks * @drive_data: Set if we're driving the data line. diff --git a/include/linux/fsi.h b/include/linux/fsi.h index adea1b432f2d..9c67c43f9e6c 100644 --- a/include/linux/fsi.h +++ b/include/linux/fsi.h @@ -19,6 +19,16 @@ struct fsi_device { uint32_t size; }; +static inline void *fsi_get_drvdata(struct fsi_device *fsi_dev) +{ + return dev_get_drvdata(&fsi_dev->dev); +} + +static inline void fsi_set_drvdata(struct fsi_device *fsi_dev, void *data) +{ + dev_set_drvdata(&fsi_dev->dev, data); +} + extern int fsi_device_read(struct fsi_device *dev, uint32_t addr, void *val, size_t size); extern int fsi_device_write(struct fsi_device *dev, uint32_t addr, @@ -39,6 +49,8 @@ struct fsi_device_id { .engine_type = (t), .version = (v), struct fsi_driver { + int (*probe)(struct fsi_device *fsidev); + void (*remove)(struct fsi_device *fsidev); struct device_driver drv; const struct fsi_device_id *id_table; }; @@ -68,7 +80,6 @@ extern int fsi_slave_read(struct fsi_slave *slave, uint32_t addr, extern int fsi_slave_write(struct fsi_slave *slave, uint32_t addr, const void *val, size_t size); -extern const struct bus_type fsi_bus_type; extern const struct device_type fsi_cdev_type; enum fsi_dev_type { diff --git a/include/linux/iio/buffer-dma.h b/include/linux/iio/buffer-dma.h index 4f33e6a39797..ef8687a88b73 100644 --- a/include/linux/iio/buffer-dma.h +++ b/include/linux/iio/buffer-dma.h @@ -119,7 +119,12 @@ struct iio_dma_buffer_queue { struct device *dev; const struct iio_dma_buffer_ops *ops; + /* + * A mutex to protect accessing, configuring (eg: enqueuing DMA blocks) + * and do file IO on struct iio_dma_buffer_queue objects. + */ struct mutex lock; + /* A spin lock to protect adding/removing blocks to the queue list */ spinlock_t list_lock; struct list_head incoming; @@ -136,20 +141,19 @@ struct iio_dma_buffer_queue { */ struct iio_dma_buffer_ops { int (*submit)(struct iio_dma_buffer_queue *queue, - struct iio_dma_buffer_block *block); + struct iio_dma_buffer_block *block); void (*abort)(struct iio_dma_buffer_queue *queue); }; void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block); void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue, - struct list_head *list); + struct list_head *list); -int iio_dma_buffer_enable(struct iio_buffer *buffer, - struct iio_dev *indio_dev); +int iio_dma_buffer_enable(struct iio_buffer *buffer, struct iio_dev *indio_dev); int iio_dma_buffer_disable(struct iio_buffer *buffer, - struct iio_dev *indio_dev); + struct iio_dev *indio_dev); int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, - char __user *user_buffer); + char __user *user_buffer); int iio_dma_buffer_write(struct iio_buffer *buffer, size_t n, const char __user *user_buffer); size_t iio_dma_buffer_usage(struct iio_buffer *buffer); @@ -157,8 +161,8 @@ int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd); int iio_dma_buffer_set_length(struct iio_buffer *buffer, unsigned int length); int iio_dma_buffer_request_update(struct iio_buffer *buffer); -int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, - struct device *dma_dev, const struct iio_dma_buffer_ops *ops); +void iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, struct device *dev, + const struct iio_dma_buffer_ops *ops); void iio_dma_buffer_exit(struct iio_dma_buffer_queue *queue); void iio_dma_buffer_release(struct iio_dma_buffer_queue *queue); diff --git a/include/linux/iio/buffer_impl.h b/include/linux/iio/buffer_impl.h index c0b0e0992a85..9442c165561a 100644 --- a/include/linux/iio/buffer_impl.h +++ b/include/linux/iio/buffer_impl.h @@ -113,10 +113,10 @@ struct iio_buffer { /** @flags: File ops flags including busy flag. */ unsigned long flags; - /** @bytes_per_datum: Size of individual datum including timestamp. */ + /** @bytes_per_datum: Size of individual datum including timestamp. */ size_t bytes_per_datum; - /* @direction: Direction of the data stream (in/out). */ + /** @direction: Direction of the data stream (in/out). */ enum iio_buffer_direction direction; /** @@ -178,7 +178,9 @@ struct iio_buffer { * @insert_buffer: buffer to insert * @remove_buffer: buffer_to_remove * - * Note this will tear down the all buffering and build it up again + * Note this will tear down all the buffering and build it up again + * + * Returns: 0 on success or -errno on error */ int iio_update_buffers(struct iio_dev *indio_dev, struct iio_buffer *insert_buffer, diff --git a/include/linux/iio/frequency/ad9523.h b/include/linux/iio/frequency/ad9523.h index ff22a0ac15f5..236437a226b2 100644 --- a/include/linux/iio/frequency/ad9523.h +++ b/include/linux/iio/frequency/ad9523.h @@ -45,7 +45,7 @@ enum ref_sel_mode { * @output_dis: Disables, powers down the entire channel. * @driver_mode: Output driver mode (logic level family). * @divider_phase: Divider initial phase after a SYNC. Range 0..63 - LSB = 1/2 of a period of the divider input clock. + * LSB = 1/2 of a period of the divider input clock. * @channel_divider: 10-bit channel divider. * @extended_name: Optional descriptive channel name. */ diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index 872ebdf0dd77..a9ecff191bd9 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -10,6 +10,7 @@ #include <linux/align.h> #include <linux/device.h> #include <linux/cdev.h> +#include <linux/cleanup.h> #include <linux/compiler_types.h> #include <linux/minmax.h> #include <linux/slab.h> @@ -661,34 +662,148 @@ void iio_device_unregister(struct iio_dev *indio_dev); int __devm_iio_device_register(struct device *dev, struct iio_dev *indio_dev, struct module *this_mod); int iio_push_event(struct iio_dev *indio_dev, u64 ev_code, s64 timestamp); -bool __iio_device_claim_direct(struct iio_dev *indio_dev); -void __iio_device_release_direct(struct iio_dev *indio_dev); + +void __iio_dev_mode_lock(struct iio_dev *indio_dev) __acquires(indio_dev); +void __iio_dev_mode_unlock(struct iio_dev *indio_dev) __releases(indio_dev); /* * Helper functions that allow claim and release of direct mode * in a fashion that doesn't generate many false positives from sparse. * Note this must remain static inline in the header so that sparse - * can see the __acquire() marking. Revisit when sparse supports - * __cond_acquires() + * can see the __acquires() and __releases() annotations. + */ + +/** + * iio_device_claim_direct() - Keep device in direct mode + * @indio_dev: the iio_dev associated with the device + * + * If the device is in direct mode it is guaranteed to stay + * that way until iio_device_release_direct() is called. + * + * Use with iio_device_release_direct(). + * + * Returns: true on success, false on failure. */ static inline bool iio_device_claim_direct(struct iio_dev *indio_dev) { - if (!__iio_device_claim_direct(indio_dev)) - return false; + __iio_dev_mode_lock(indio_dev); - __acquire(iio_dev); + if (iio_buffer_enabled(indio_dev)) { + __iio_dev_mode_unlock(indio_dev); + return false; + } return true; } -static inline void iio_device_release_direct(struct iio_dev *indio_dev) +/** + * iio_device_release_direct() - Releases claim on direct mode + * @indio_dev: the iio_dev associated with the device + * + * Release the claim. Device is no longer guaranteed to stay + * in direct mode. + * + * Use with iio_device_claim_direct(). + */ +#define iio_device_release_direct(indio_dev) __iio_dev_mode_unlock(indio_dev) + +/** + * iio_device_try_claim_buffer_mode() - Keep device in buffer mode + * @indio_dev: the iio_dev associated with the device + * + * If the device is in buffer mode it is guaranteed to stay + * that way until iio_device_release_buffer_mode() is called. + * + * Use with iio_device_release_buffer_mode(). + * + * Returns: true on success, false on failure. + */ +static inline bool iio_device_try_claim_buffer_mode(struct iio_dev *indio_dev) { - __iio_device_release_direct(indio_dev); - __release(indio_dev); + __iio_dev_mode_lock(indio_dev); + + if (!iio_buffer_enabled(indio_dev)) { + __iio_dev_mode_unlock(indio_dev); + return false; + } + + return true; } -int iio_device_claim_buffer_mode(struct iio_dev *indio_dev); -void iio_device_release_buffer_mode(struct iio_dev *indio_dev); +/** + * iio_device_release_buffer_mode() - releases claim on buffer mode + * @indio_dev: the iio_dev associated with the device + * + * Release the claim. Device is no longer guaranteed to stay + * in buffer mode. + * + * Use with iio_device_try_claim_buffer_mode(). + */ +#define iio_device_release_buffer_mode(indio_dev) __iio_dev_mode_unlock(indio_dev) + +/* + * These classes are not meant to be used directly by drivers (hence the + * __priv__ prefix). Instead, documented wrapper macros are provided below to + * enforce the use of ACQUIRE() or guard() semantics and avoid the problematic + * scoped guard variants. + */ +DEFINE_GUARD(__priv__iio_dev_mode_lock, struct iio_dev *, + __iio_dev_mode_lock(_T), __iio_dev_mode_unlock(_T)); +DEFINE_GUARD_COND(__priv__iio_dev_mode_lock, _try_direct, + iio_device_claim_direct(_T)); + +/** + * IIO_DEV_ACQUIRE_DIRECT_MODE() - Tries to acquire the direct mode lock with + * automatic release + * @dev: IIO device instance + * @claim: Variable identifier to store acquire result + * + * Tries to acquire the direct mode lock with cleanup ACQUIRE() semantics and + * automatically releases it at the end of the scope. It most be always paired + * with IIO_DEV_ACQUIRE_ERR(), for example (notice the scope braces):: + * + * switch() { + * case IIO_CHAN_INFO_RAW: { + * IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + * if (IIO_DEV_ACQUIRE_FAILED(claim)) + * return -EBUSY; + * + * ... + * } + * case IIO_CHAN_INFO_SCALE: + * ... + * ... + * } + * + * Context: Can sleep + */ +#define IIO_DEV_ACQUIRE_DIRECT_MODE(dev, claim) \ + ACQUIRE(__priv__iio_dev_mode_lock_try_direct, claim)(dev) + +/** + * IIO_DEV_ACQUIRE_FAILED() - ACQUIRE_ERR() wrapper + * @claim: The claim variable passed to IIO_DEV_ACQUIRE_*_MODE() + * + * Return: true if failed to acquire the mode, otherwise false. + */ +#define IIO_DEV_ACQUIRE_FAILED(claim) \ + ACQUIRE_ERR(__priv__iio_dev_mode_lock_try_direct, &(claim)) + +/** + * IIO_DEV_GUARD_CURRENT_MODE() - Acquires the mode lock with automatic release + * @dev: IIO device instance + * + * Acquires the mode lock with cleanup guard() semantics. It is usually paired + * with iio_buffer_enabled(). + * + * This should *not* be used to protect internal driver state and it's use in + * general is *strongly* discouraged. Use any of the IIO_DEV_ACQUIRE_*_MODE() + * variants. + * + * Context: Can sleep + */ +#define IIO_DEV_GUARD_CURRENT_MODE(dev) \ + guard(__priv__iio_dev_mode_lock)(dev) extern const struct bus_type iio_bus_type; diff --git a/include/linux/mhi.h b/include/linux/mhi.h index dd372b0123a6..88ccb3e14f48 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -215,7 +215,6 @@ enum mhi_db_brst_mode { * @lpm_notify: The channel master requires low power mode notifications * @offload_channel: The client manages the channel completely * @doorbell_mode_switch: Channel switches to doorbell mode on M0 transition - * @auto_queue: Framework will automatically queue buffers for DL traffic * @wake-capable: Channel capable of waking up the system */ struct mhi_channel_config { @@ -232,7 +231,6 @@ struct mhi_channel_config { bool lpm_notify; bool offload_channel; bool doorbell_mode_switch; - bool auto_queue; bool wake_capable; }; @@ -744,18 +742,6 @@ void mhi_device_put(struct mhi_device *mhi_dev); int mhi_prepare_for_transfer(struct mhi_device *mhi_dev); /** - * mhi_prepare_for_transfer_autoqueue - Setup UL and DL channels with auto queue - * buffers for DL traffic - * @mhi_dev: Device associated with the channels - * - * Allocate and initialize the channel context and also issue the START channel - * command to both channels. Channels can be started only if both host and - * device execution environments match and channels are in a DISABLED state. - * The MHI core will automatically allocate and queue buffers for the DL traffic. - */ -int mhi_prepare_for_transfer_autoqueue(struct mhi_device *mhi_dev); - -/** * mhi_unprepare_from_transfer - Reset UL and DL channels for data transfer. * Issue the RESET channel command and let the * device clean-up the context so no incoming diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h index 7d0aa718499c..fa9000f68523 100644 --- a/include/linux/miscdevice.h +++ b/include/linux/miscdevice.h @@ -52,7 +52,6 @@ #define PXA3XX_GCU_MINOR 197 #define TUN_MINOR 200 #define CUSE_MINOR 203 -#define MWAVE_MINOR 219 /* ACP/Mwave Modem */ #define MPT_MINOR 220 #define MPT2SAS_MINOR 221 #define MPT3SAS_MINOR 222 diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 8080a6fc6c8c..749d985e9da2 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -2622,14 +2622,20 @@ struct ec_params_motion_sense { /* * Used for MOTIONSENSE_CMD_INFO, MOTIONSENSE_CMD_DATA - * and MOTIONSENSE_CMD_PERFORM_CALIB. */ struct __ec_todo_unpacked { uint8_t sensor_num; - } info, info_3, data, fifo_flush, perform_calib, - list_activities; + } info, info_3, data, fifo_flush, list_activities; /* + * Used for MOTIONSENSE_CMD_PERFORM_CALIB: + * Allow entering/exiting the calibration mode. + */ + struct __ec_todo_unpacked { + uint8_t sensor_num; + uint8_t enable; + } perform_calib; + /* * Used for MOTIONSENSE_CMD_EC_RATE, MOTIONSENSE_CMD_SENSOR_ODR * and MOTIONSENSE_CMD_SENSOR_RANGE. */ diff --git a/include/linux/units.h b/include/linux/units.h index 0c296a004e89..80d57c50b9e3 100644 --- a/include/linux/units.h +++ b/include/linux/units.h @@ -21,6 +21,25 @@ #define PICO 1000000000000ULL #define FEMTO 1000000000000000ULL +/* + * Percentage and related scaling units + * + * These macros define scaling factors used to convert between ratio and + * percentage-based representations with different decimal resolutions. + * They are used for precise fractional calculations in engineering, finance, + * and measurement applications. + * + * Examples: + * 1% = 0.01 = 1 / PERCENT + * 0.1% = 0.001 = 1 / PERMILLE + * 0.01% = 0.0001 = 1 / PERMYRIAD (1 basis point) + * 0.001% = 0.00001 = 1 / PERCENTMILLE + */ +#define PERCENT 100 +#define PERMILLE 1000 +#define PERMYRIAD 10000 +#define PERCENTMILLE 100000 + #define NANOHZ_PER_HZ 1000000000UL #define MICROHZ_PER_HZ 1000000UL #define MILLIHZ_PER_HZ 1000UL diff --git a/include/uapi/linux/android/binder.h b/include/uapi/linux/android/binder.h index 03ee4c7010d7..701cad36de43 100644 --- a/include/uapi/linux/android/binder.h +++ b/include/uapi/linux/android/binder.h @@ -278,7 +278,7 @@ enum { * NOTE: Two special error codes you should check for when calling * in to the driver are: * - * EINTR -- The operation has been interupted. This should be + * EINTR -- The operation has been interrupted. This should be * handled by retrying the ioctl() until a different error code * is returned. * diff --git a/net/qrtr/mhi.c b/net/qrtr/mhi.c index 69f53625a049..80e341d2f8a4 100644 --- a/net/qrtr/mhi.c +++ b/net/qrtr/mhi.c @@ -24,13 +24,25 @@ static void qcom_mhi_qrtr_dl_callback(struct mhi_device *mhi_dev, struct qrtr_mhi_dev *qdev = dev_get_drvdata(&mhi_dev->dev); int rc; - if (!qdev || mhi_res->transaction_status) + if (!qdev || (mhi_res->transaction_status && mhi_res->transaction_status != -ENOTCONN)) return; + /* Channel got reset. So just free the buffer */ + if (mhi_res->transaction_status == -ENOTCONN) { + devm_kfree(&mhi_dev->dev, mhi_res->buf_addr); + return; + } + rc = qrtr_endpoint_post(&qdev->ep, mhi_res->buf_addr, mhi_res->bytes_xferd); if (rc == -EINVAL) dev_err(qdev->dev, "invalid ipcrouter packet\n"); + + /* Done with the buffer, now recycle it for future use */ + rc = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, mhi_res->buf_addr, + mhi_dev->mhi_cntrl->buffer_len, MHI_EOT); + if (rc) + dev_err(&mhi_dev->dev, "Failed to recycle the buffer: %d\n", rc); } /* From QRTR to MHI */ @@ -72,6 +84,29 @@ free_skb: return rc; } +static int qcom_mhi_qrtr_queue_dl_buffers(struct mhi_device *mhi_dev) +{ + u32 free_desc; + void *buf; + int ret; + + free_desc = mhi_get_free_desc_count(mhi_dev, DMA_FROM_DEVICE); + while (free_desc--) { + buf = devm_kmalloc(&mhi_dev->dev, mhi_dev->mhi_cntrl->buffer_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, buf, mhi_dev->mhi_cntrl->buffer_len, + MHI_EOT); + if (ret) { + dev_err(&mhi_dev->dev, "Failed to queue buffer: %d\n", ret); + return ret; + } + } + + return 0; +} + static int qcom_mhi_qrtr_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id) { @@ -87,20 +122,30 @@ static int qcom_mhi_qrtr_probe(struct mhi_device *mhi_dev, qdev->ep.xmit = qcom_mhi_qrtr_send; dev_set_drvdata(&mhi_dev->dev, qdev); - rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO); - if (rc) - return rc; /* start channels */ - rc = mhi_prepare_for_transfer_autoqueue(mhi_dev); - if (rc) { - qrtr_endpoint_unregister(&qdev->ep); + rc = mhi_prepare_for_transfer(mhi_dev); + if (rc) return rc; - } + + rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO); + if (rc) + goto err_unprepare; + + rc = qcom_mhi_qrtr_queue_dl_buffers(mhi_dev); + if (rc) + goto err_unregister; dev_dbg(qdev->dev, "Qualcomm MHI QRTR driver probed\n"); return 0; + +err_unregister: + qrtr_endpoint_unregister(&qdev->ep); +err_unprepare: + mhi_unprepare_from_transfer(mhi_dev); + + return rc; } static void qcom_mhi_qrtr_remove(struct mhi_device *mhi_dev) @@ -151,11 +196,13 @@ static int __maybe_unused qcom_mhi_qrtr_pm_resume_early(struct device *dev) if (state == MHI_STATE_M3) return 0; - rc = mhi_prepare_for_transfer_autoqueue(mhi_dev); - if (rc) + rc = mhi_prepare_for_transfer(mhi_dev); + if (rc) { dev_err(dev, "failed to prepare for autoqueue transfer %d\n", rc); + return rc; + } - return rc; + return qcom_mhi_qrtr_queue_dl_buffers(mhi_dev); } static const struct dev_pm_ops qcom_mhi_qrtr_pm_ops = { diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index 0c57cf9b4004..19f57c5b2fa2 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -67,3 +67,16 @@ mod bindings_helper { } pub use bindings_raw::*; + +pub const compat_ptr_ioctl: Option< + unsafe extern "C" fn(*mut file, ffi::c_uint, ffi::c_ulong) -> ffi::c_long, +> = { + #[cfg(CONFIG_COMPAT)] + { + Some(bindings_raw::compat_ptr_ioctl) + } + #[cfg(not(CONFIG_COMPAT))] + { + None + } +}; diff --git a/rust/helpers/binder.c b/rust/helpers/binder.c index 224d38a92f1d..a2327f1b3c94 100644 --- a/rust/helpers/binder.c +++ b/rust/helpers/binder.c @@ -7,20 +7,21 @@ #include <linux/list_lru.h> #include <linux/task_work.h> -unsigned long rust_helper_list_lru_count(struct list_lru *lru) +__rust_helper 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) +__rust_helper 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) +__rust_helper void rust_helper_init_task_work(struct callback_head *twork, + task_work_func_t func) { init_task_work(twork, func); } diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs index d698cddcb4a5..c3c2052c9206 100644 --- a/rust/kernel/miscdevice.rs +++ b/rust/kernel/miscdevice.rs @@ -20,7 +20,7 @@ use crate::{ seq_file::SeqFile, types::{ForeignOwnable, Opaque}, }; -use core::{marker::PhantomData, mem::MaybeUninit, pin::Pin}; +use core::{marker::PhantomData, pin::Pin}; /// Options for creating a misc device. #[derive(Copy, Clone)] @@ -32,8 +32,7 @@ pub struct MiscDeviceOptions { impl MiscDeviceOptions { /// Create a raw `struct miscdev` ready for registration. pub const fn into_raw<T: MiscDevice>(self) -> bindings::miscdevice { - // SAFETY: All zeros is valid for this C type. - let mut result: bindings::miscdevice = unsafe { MaybeUninit::zeroed().assume_init() }; + let mut result: bindings::miscdevice = pin_init::zeroed(); result.minor = bindings::MISC_DYNAMIC_MINOR as ffi::c_int; result.name = crate::str::as_char_ptr_in_const_context(self.name); result.fops = MiscdeviceVTable::<T>::build(); @@ -411,7 +410,7 @@ impl<T: MiscDevice> MiscdeviceVTable<T> { compat_ioctl: if T::HAS_COMPAT_IOCTL { Some(Self::compat_ioctl) } else if T::HAS_IOCTL { - Some(bindings::compat_ptr_ioctl) + bindings::compat_ptr_ioctl } else { None }, @@ -420,8 +419,7 @@ impl<T: MiscDevice> MiscdeviceVTable<T> { } else { None }, - // SAFETY: All zeros is a valid value for `bindings::file_operations`. - ..unsafe { MaybeUninit::zeroed().assume_init() } + ..pin_init::zeroed() }; const fn build() -> &'static bindings::file_operations { diff --git a/rust/kernel/sync/arc.rs b/rust/kernel/sync/arc.rs index 289f77abf415..921e19333b89 100644 --- a/rust/kernel/sync/arc.rs +++ b/rust/kernel/sync/arc.rs @@ -240,6 +240,9 @@ impl<T> Arc<T> { // `Arc` object. Ok(unsafe { Self::from_inner(inner) }) } + + /// The offset that the value is stored at. + pub const DATA_OFFSET: usize = core::mem::offset_of!(ArcInner<T>, data); } impl<T: ?Sized> Arc<T> { diff --git a/samples/rust/rust_misc_device.rs b/samples/rust/rust_misc_device.rs index 3ef8da543abf..87a1fe63533a 100644 --- a/samples/rust/rust_misc_device.rs +++ b/samples/rust/rust_misc_device.rs @@ -96,7 +96,6 @@ //! ``` use kernel::{ - c_str, device::Device, fs::{File, Kiocb}, ioctl::{_IO, _IOC_SIZE, _IOR, _IOW}, @@ -131,7 +130,7 @@ impl kernel::InPlaceModule for RustMiscDeviceModule { pr_info!("Initialising Rust Misc Device Sample\n"); let options = MiscDeviceOptions { - name: c_str!("rust-misc-device"), + name: c"rust-misc-device", }; try_pin_init!(Self { |
