From detlev.casanova at collabora.com Fri May 1 11:07:08 2026 From: detlev.casanova at collabora.com (Detlev Casanova) Date: Fri, 1 May 2026 14:07:08 -0400 Subject: [PATCH 00/11] v4l2: Add tracing for stateless codecs In-Reply-To: <4c216c195d237e71188dfbdd54bbc1e234eff434.camel@collabora.com> References: <20260212162328.192217-1-detlev.casanova@collabora.com> <4c216c195d237e71188dfbdd54bbc1e234eff434.camel@collabora.com> Message-ID: <23206161-3d25-432f-9461-4f5454d98d08@collabora.com> Hi Nicolas, On 4/28/26 15:52, Nicolas Dufresne wrote: > Le jeudi 12 f?vrier 2026 ? 11:23 -0500, Detlev Casanova a ?crit?: >> Hi ! >> >> This patchset aims to improve codec event tracing in v4l2. >> >> The traces added in visl by Daniel Almeida are moved to the global trace >> events and slightly reworked to be printed in a more consistent format. >> >> To each trace are also added a tgid and fd fields, helping userspace track >> different decoding sessions (contexts) based on the given file descriptor >> used by the given process id. >> >> Also for better tracking, stream on and stream off events are added as >> well as HW run and HW done events to track decoder core usage. >> >> Finally, add a show_fdinfo callback on video device files, allowing drivers >> to expose usage information. >> Currently only used for frame buffer memory usage. >> >> The main focus is to be able to generate perfetto traces to show VPU usage, >> a perfetto producer using this can be found at [1]. >> >> [1]: >> https://gitlab.collabora.com/detlev/hantro-perf/-/tree/hantro-improved-info > I would recommend renaming this repo, as its not really evolving toward > something truly hantro specific anymore, same goes for the code below appart > from the run/done traces and the fdinfo that has to be opted in. Yes, hantro was the first focus, but I've also been integrating rkvdec into this. > > There is effectively a bit of an overlap with the v4l2-tracer, but there is also > advantages having it in the kernel, as you source all the event, for the entire > system, in one place, so I'm fine with that. Considering the possible large > overhead of the full trace, I'd like to see the ability to filter what we want > to trace, with some level of granularity. Tracing is by default disable and and has a really small performance footprint when disabled. On the userspace side, each trace events can be enabled separately and even filtered (although I'm not sure filtering is good for performance, has it has to match each event with a regex). > Maybe we only need decode_params for > specific use case to be debugged, and don't care about large scaling list/matrix > ? I would also like to see some Documentation on the tracing, so that its usage > is not only explained in a tool. Agreed. > To Hans, there is nice tools idea in there, the perfetto producer is simply C++, > and the v4l2top utility is Rust. Would you see these as tools v4l2-utils ? For > the rust part, we can either leave the build independent, and cargo would be > used to build and run, or we can implement a meson wrapper around cargo. But I > don't believe its a good idea to use native rustc support in meson for that one > because of the large number of third party crate needed. > > I like the direction, hope the feedback suite you well. > > Nicolas > >> Detlev Casanova (11): >> ? media: Move visl traces to v4l2-core >> ? media: Reformat v4l2-requests trace event printk >> ? media: Add tgid and fd fields in v4l2_fh struct >> ? media: Add tgid and fd to the v4l2-requests trace fields >> ? media: Add missing types to v4l2_ctrl_ptr >> ? media: Trace the stateless controls when set in v4l2-ctrls-core.c >> ? media: Add stream on/off traces and run them in the ioctl >> ? media: Add HW run/done trace events >> ? media: hantro: Add v4l2_hw run/done traces >> ? media: v4l2: Add callback for show_fdinfo >> ? media: hantro: Add fdinfo callback >> >> ?drivers/media/platform/verisilicon/hantro.h?? |??? 2 + >> ?.../media/platform/verisilicon/hantro_drv.c?? |?? 25 + >> ?.../media/platform/verisilicon/hantro_v4l2.c? |?? 10 +- >> ?.../verisilicon/rockchip_vpu981_regs.h??????? |??? 1 + >> ?.../platform/verisilicon/rockchip_vpu_hw.c??? |??? 4 + >> ?drivers/media/test-drivers/visl/Makefile????? |??? 2 +- >> ?drivers/media/test-drivers/visl/visl-dec.c??? |?? 76 - >> ?.../media/test-drivers/visl/visl-trace-av1.h? |? 314 --- >> ?.../media/test-drivers/visl/visl-trace-fwht.h |?? 66 - >> ?.../media/test-drivers/visl/visl-trace-h264.h |? 349 ---- >> ?.../media/test-drivers/visl/visl-trace-hevc.h |? 464 ----- >> ?.../test-drivers/visl/visl-trace-mpeg2.h????? |?? 99 - >> ?.../test-drivers/visl/visl-trace-points.c???? |?? 11 - >> ?.../media/test-drivers/visl/visl-trace-vp8.h? |? 156 -- >> ?.../media/test-drivers/visl/visl-trace-vp9.h? |? 292 --- >> ?drivers/media/v4l2-core/v4l2-ctrls-api.c????? |?? 10 + >> ?drivers/media/v4l2-core/v4l2-ctrls-core.c???? |? 114 + >> ?drivers/media/v4l2-core/v4l2-dev.c??????????? |?? 10 + >> ?drivers/media/v4l2-core/v4l2-fh.c???????????? |??? 1 + >> ?drivers/media/v4l2-core/v4l2-ioctl.c????????? |?? 37 +- >> ?drivers/media/v4l2-core/v4l2-trace.c????????? |?? 48 + >> ?include/media/v4l2-ctrls.h??????????????????? |?? 19 + >> ?include/media/v4l2-dev.h????????????????????? |??? 1 + >> ?include/media/v4l2-fh.h?????????????????????? |??? 4 + >> ?include/trace/events/v4l2.h?????????????????? |?? 58 + >> ?include/trace/events/v4l2_requests.h????????? | 1836 +++++++++++++++++ >> ?26 files changed, 2178 insertions(+), 1831 deletions(-) >> ?delete mode 100644 drivers/media/test-drivers/visl/visl-trace-av1.h >> ?delete mode 100644 drivers/media/test-drivers/visl/visl-trace-fwht.h >> ?delete mode 100644 drivers/media/test-drivers/visl/visl-trace-h264.h >> ?delete mode 100644 drivers/media/test-drivers/visl/visl-trace-hevc.h >> ?delete mode 100644 drivers/media/test-drivers/visl/visl-trace-mpeg2.h >> ?delete mode 100644 drivers/media/test-drivers/visl/visl-trace-points.c >> ?delete mode 100644 drivers/media/test-drivers/visl/visl-trace-vp8.h >> ?delete mode 100644 drivers/media/test-drivers/visl/visl-trace-vp9.h >> ?create mode 100644 include/trace/events/v4l2_requests.h From niklas.soderlund+renesas at ragnatech.se Fri May 1 12:03:39 2026 From: niklas.soderlund+renesas at ragnatech.se (=?UTF-8?q?Niklas=20S=C3=B6derlund?=) Date: Fri, 1 May 2026 21:03:39 +0200 Subject: [PATCH] media: uapi: rkisp: Correct name version enum Message-ID: <20260501190339.3449193-1-niklas.soderlund+renesas@ragnatech.se> The name of the enum to hold the mapping of parameter buffer versions have a typo in the name, correct it. While this is a uAPI header the impact should be minimal as the enum is only used as a collection for the one version number supported. Fixes: e9d05e9d5db1 ("media: uapi: rkisp1-config: Add extensible params format") Signed-off-by: Niklas S?derlund --- include/uapi/linux/rkisp1-config.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/uapi/linux/rkisp1-config.h b/include/uapi/linux/rkisp1-config.h index b2d2a71f7baf..7638b2220600 100644 --- a/include/uapi/linux/rkisp1-config.h +++ b/include/uapi/linux/rkisp1-config.h @@ -1535,11 +1535,11 @@ struct rkisp1_ext_params_wdr_config { sizeof(struct rkisp1_ext_params_wdr_config)) /** - * enum rksip1_ext_param_buffer_version - RkISP1 extensible parameters version + * enum rkisp1_ext_param_buffer_version - RkISP1 extensible parameters version * * @RKISP1_EXT_PARAM_BUFFER_V1: First version of RkISP1 extensible parameters */ -enum rksip1_ext_param_buffer_version { +enum rkisp1_ext_param_buffer_version { RKISP1_EXT_PARAM_BUFFER_V1 = V4L2_ISP_PARAMS_VERSION_V1, }; @@ -1601,7 +1601,7 @@ enum rksip1_ext_param_buffer_version { * +---------------------------------------------------------------------+ * * @version: The RkISP1 extensible parameters buffer version, see - * :c:type:`rksip1_ext_param_buffer_version` + * :c:type:`rkisp1_ext_param_buffer_version` * @data_size: The RkISP1 configuration data effective size, excluding this * header * @data: The RkISP1 extensible configuration data blocks -- 2.54.0 From rostedt at goodmis.org Fri May 1 12:07:44 2026 From: rostedt at goodmis.org (Steven Rostedt) Date: Fri, 1 May 2026 15:07:44 -0400 Subject: [PATCH 01/11] media: Move visl traces to v4l2-core In-Reply-To: <20260212162328.192217-2-detlev.casanova@collabora.com> References: <20260212162328.192217-1-detlev.casanova@collabora.com> <20260212162328.192217-2-detlev.casanova@collabora.com> Message-ID: <20260501150744.065081ac@gandalf.local.home> Sorry, I didn't look at these patches at the time they were posted, but when I saw you reply recently with: "On the userspace side, each trace events can be enabled separately and even filtered (although I'm not sure filtering is good for performance, has it has to match each event with a regex)." I had to see what you meant by regex, because no tracer should be using regex for filtering. On Thu, 12 Feb 2026 11:23:18 -0500 Detlev Casanova wrote: > + > +DECLARE_EVENT_CLASS(v4l2_ctrl_mpeg2_seq_tmpl, > + TP_PROTO(const struct v4l2_ctrl_mpeg2_sequence *s), > + TP_ARGS(s), > + TP_STRUCT__entry(__field_struct(struct v4l2_ctrl_mpeg2_sequence, s)), > + TP_fast_assign(__entry->s = *s;), What the heck! You are copying an entire structure onto the ring buffer to print just a portion of it? This is really a waste of ring buffer, and also prevents you from doing any real filtering. You do realize that you can filter on fields (if they are defined as normal fields). For example, # cd /sys/kernel/tracing # echo 'height > 20 && width > 30' > events/visl_fwht_controls/v4l2_ctrl_fwht_params/filter # echo 1 > events/visl_fwht_controls/v4l2_ctrl_fwht_params/enable And that will only trace that event where the height field is greater than 20 and the width field is greater than 30. It's converted into a fast array to do the filtering. No regex involved. Also, there's a library to read the raw events from the ring buffers where you can do filtering on the read side too: https://trace-cmd.org/Documentation/libtracefs/ https://trace-cmd.org/Documentation/libtraceevent/ -- Steve > + TP_printk("\nhorizontal_size %u\nvertical_size %u\nvbv_buffer_size %u\n" > + "profile_and_level_indication %u\nchroma_format %u\nflags %s\n", > + __entry->s.horizontal_size, > + __entry->s.vertical_size, > + __entry->s.vbv_buffer_size, > + __entry->s.profile_and_level_indication, > + __entry->s.chroma_format, > + __print_flags(__entry->s.flags, "|", > + {V4L2_MPEG2_SEQ_FLAG_PROGRESSIVE, "PROGRESSIVE"}) > + ) > +); > + From macroalpha82 at gmail.com Fri May 1 15:11:39 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:39 -0500 Subject: [PATCH V4 00/10] Add Invensense ICM42607 Message-ID: <20260501221152.194251-1-macroalpha82@gmail.com> From: Chris Morgan Add support for the ICM42607 IMU. This sensor shares the same functionality but a different register layout with the existing ICM42600. This driver should work with the ICM42607 and ICM42607P over both I2C and SPI, however only the ICM42607P over I2C could be tested. Changes Since V1: - Instead of creating a new driver, merged with the existing inv_icm42600 driver. This necessitated adding some code to the existing driver to permit using a different register layout for the same functionality. - Split changes up a bit more to decrease the size of the individual patches. Note that patch 0004 is still pretty hefty; if I need to split further I may need to create some temporary stub functions. - Used guard() and PM_RUNTIME_ACQUIRE_AUTOSUSPEND() on the new functions per Jonathan's recommendations. Changes Since V2: - Went back to using a new driver on advice from Invensense engineer. - Further split changes up into smaller chunks of functionality. Note still that the largest patch is approximately 900 lines, and that while the driver compiles cleanly at each commit it is not able to drive the hardware until the commit that adds the Interrupt (as it also adds the Makefile). - Change the error to a warning when the devicetree binding does not match the hardware ID. - Dropped the ack on the devicetree bindings, as I am creating a new file (for a new driver) instead of modifying the existing one. Changes Since V3: - Numerous small fixes (too many to list here). Thank you to everyone who provided feedback. - Split power management additions into an additional commit to break things up further. - Consolidated devicetree documentation in existing invensense,icm42600.yaml file. - Removed most of the FIELD_PREP from header file to c files to make code easier to read. - Changed scale values to 2D arrays for Gyro and Accelerometer. - Removed IIO_CHAN_INFO_CALIBBIAS attribute. Chris Morgan (10): dt-bindings: iio: imu: icm42600: Add icm42607 binding iio: imu: inv_icm42607: Add inv_icm42607 Core Driver iio: imu: inv_icm42607: Add I2C and SPI For icm42607 iio: imu: inv_icm42607: Add PM support for icm42607 iio: imu: inv_icm42607: Add Buffer support for icm42607 iio: imu: inv_icm42607: Add Temp Support in icm42607 iio: imu: inv_icm42607: Add Accelerometer for icm42607 iio: imu: inv_icm42607: Add Wake on Movement for icm42607 iio: imu: inv_icm42607: Add Gyroscope to icm42607 arm64: dts: rockchip: Add icm42607p IMU for RG-DS .../bindings/iio/imu/invensense,icm42600.yaml | 4 + .../dts/rockchip/rk3568-anbernic-rg-ds.dts | 20 +- drivers/iio/imu/Kconfig | 1 + drivers/iio/imu/Makefile | 1 + drivers/iio/imu/inv_icm42607/Kconfig | 30 + drivers/iio/imu/inv_icm42607/Makefile | 14 + drivers/iio/imu/inv_icm42607/inv_icm42607.h | 438 ++++++++ .../iio/imu/inv_icm42607/inv_icm42607_accel.c | 989 ++++++++++++++++++ .../imu/inv_icm42607/inv_icm42607_buffer.c | 559 ++++++++++ .../imu/inv_icm42607/inv_icm42607_buffer.h | 101 ++ .../iio/imu/inv_icm42607/inv_icm42607_core.c | 809 ++++++++++++++ .../iio/imu/inv_icm42607/inv_icm42607_gyro.c | 536 ++++++++++ .../iio/imu/inv_icm42607/inv_icm42607_i2c.c | 91 ++ .../iio/imu/inv_icm42607/inv_icm42607_spi.c | 97 ++ .../iio/imu/inv_icm42607/inv_icm42607_temp.c | 81 ++ .../iio/imu/inv_icm42607/inv_icm42607_temp.h | 30 + 16 files changed, 3800 insertions(+), 1 deletion(-) create mode 100644 drivers/iio/imu/inv_icm42607/Kconfig create mode 100644 drivers/iio/imu/inv_icm42607/Makefile create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607.h create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.h create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_core.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_gyro.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h -- 2.43.0 From macroalpha82 at gmail.com Fri May 1 15:11:40 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:40 -0500 Subject: [PATCH V4 01/10] dt-bindings: iio: imu: icm42600: Add icm42607 binding In-Reply-To: <20260501221152.194251-1-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> Message-ID: <20260501221152.194251-2-macroalpha82@gmail.com> From: Chris Morgan Add devicetree binding for the Invensense ICM42607 and Invensense ICM42607P inertial measurement unit. This unit is a combined accelerometer, gyroscope, and thermometer available via I2C or SPI. This device is functionally very similar to the icm42600 series with a very different register layout. Additionally, add mount-matrix attribute to schema. Signed-off-by: Chris Morgan --- .../devicetree/bindings/iio/imu/invensense,icm42600.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml index 119e28a833fd..b69c6bbb6f6b 100644 --- a/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml +++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml @@ -30,6 +30,8 @@ properties: - invensense,icm42600 - invensense,icm42602 - invensense,icm42605 + - invensense,icm42607 + - invensense,icm42607p - invensense,icm42622 - invensense,icm42631 - invensense,icm42686 @@ -53,6 +55,8 @@ properties: drive-open-drain: type: boolean + mount-matrix: true + vdd-supply: description: Regulator that provides power to the sensor -- 2.43.0 From macroalpha82 at gmail.com Fri May 1 15:11:41 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:41 -0500 Subject: [PATCH V4 02/10] iio: imu: inv_icm42607: Add inv_icm42607 Core Driver In-Reply-To: <20260501221152.194251-1-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> Message-ID: <20260501221152.194251-3-macroalpha82@gmail.com> From: Chris Morgan Add the core component of a new inv_icm42607 driver. This includes a few setup functions and the full register definition in the header file. Signed-off-by: Chris Morgan --- drivers/iio/imu/inv_icm42607/inv_icm42607.h | 400 ++++++++++++++++++ .../iio/imu/inv_icm42607/inv_icm42607_core.c | 303 +++++++++++++ 2 files changed, 703 insertions(+) create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607.h create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_core.c diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h new file mode 100644 index 000000000000..f6b3cad8064a --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h @@ -0,0 +1,400 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#ifndef INV_ICM42607_H_ +#define INV_ICM42607_H_ + +#include +#include +#include +#include +#include +#include +#include + +enum inv_icm42607_chip { + INV_CHIP_INVALID, + INV_CHIP_ICM42607P, + INV_CHIP_ICM42607, + INV_CHIP_NB +}; + +/* serial bus slew rates */ +enum inv_icm42607_slew_rate { + INV_ICM42607_SLEW_RATE_20_60NS, + INV_ICM42607_SLEW_RATE_12_36NS, + INV_ICM42607_SLEW_RATE_6_18NS, + INV_ICM42607_SLEW_RATE_4_12NS, + INV_ICM42607_SLEW_RATE_2_6NS, + INV_ICM42607_SLEW_RATE_INF_2NS +}; + +enum inv_icm42607_sensor_mode { + INV_ICM42607_SENSOR_MODE_OFF, + INV_ICM42607_SENSOR_MODE_STANDBY, + INV_ICM42607_SENSOR_MODE_LOW_POWER, + INV_ICM42607_SENSOR_MODE_LOW_NOISE, + INV_ICM42607_SENSOR_MODE_NB +}; + +/* gyroscope fullscale values */ +enum inv_icm42607_gyro_fs { + INV_ICM42607_GYRO_FS_2000DPS, + INV_ICM42607_GYRO_FS_1000DPS, + INV_ICM42607_GYRO_FS_500DPS, + INV_ICM42607_GYRO_FS_250DPS, + INV_ICM42607_GYRO_FS_NB +}; + +/* accelerometer fullscale values */ +enum inv_icm42607_accel_fs { + INV_ICM42607_ACCEL_FS_16G, + INV_ICM42607_ACCEL_FS_8G, + INV_ICM42607_ACCEL_FS_4G, + INV_ICM42607_ACCEL_FS_2G, + INV_ICM42607_ACCEL_FS_NB +}; + +/* ODR values */ +enum inv_icm42607_odr { + INV_ICM42607_ODR_1600HZ = 5, + INV_ICM42607_ODR_800HZ, + INV_ICM42607_ODR_400HZ, + INV_ICM42607_ODR_200HZ, + INV_ICM42607_ODR_100HZ, + INV_ICM42607_ODR_50HZ, + INV_ICM42607_ODR_25HZ, + INV_ICM42607_ODR_12_5HZ, + INV_ICM42607_ODR_6_25HZ_LP, + INV_ICM42607_ODR_3_125HZ_LP, + INV_ICM42607_ODR_1_5625HZ_LP, + INV_ICM42607_ODR_NB +}; + +enum inv_icm42607_filter_bw { + /* Low-Noise mode sensor data filter (bandwidth) */ + INV_ICM42607_FILTER_BYPASS, + INV_ICM42607_FILTER_BW_180HZ, + INV_ICM42607_FILTER_BW_121HZ, + INV_ICM42607_FILTER_BW_73HZ, + INV_ICM42607_FILTER_BW_53HZ, + INV_ICM42607_FILTER_BW_34HZ, + INV_ICM42607_FILTER_BW_25HZ, + INV_ICM42607_FILTER_BW_16HZ +}; + +enum inv_icm42607_filter_avg { + /* Low-Power mode sensor data filter (averaging) */ + INV_ICM42607_FILTER_AVG_2X = 0, + INV_ICM42607_FILTER_AVG_4X, + INV_ICM42607_FILTER_AVG_8X, + INV_ICM42607_FILTER_AVG_16X, + INV_ICM42607_FILTER_AVG_32X, + INV_ICM42607_FILTER_AVG_64X + /* values 7 and 8 also correspond to 64x. */ +}; + +struct inv_icm42607_sensor_conf { + int mode; + int fs; + int odr; + int filter; +}; +#define INV_ICM42607_SENSOR_CONF_INIT {-1, -1, -1, -1} + +struct inv_icm42607_conf { + struct inv_icm42607_sensor_conf gyro; + struct inv_icm42607_sensor_conf accel; + bool temp_en; +}; + +struct inv_icm42607_suspended { + enum inv_icm42607_sensor_mode gyro; + enum inv_icm42607_sensor_mode accel; + bool temp; +}; + +struct inv_icm42607_apex { + unsigned int on; + struct { + u64 value; + bool enable; + } wom; +}; + +/** + * struct inv_icm42607_state - driver state variables + * @lock: lock for serializing multiple registers access. + * @chip: chip identifier. + * @name: chip name. + * @map: regmap pointer. + * @vddio_supply: I/O voltage regulator for the chip. + * @irq: chip irq, required to enable/disable and set wakeup + * @orientation: sensor chip orientation relative to main hardware. + * @conf: chip sensors configurations. + * @suspended: suspended sensors configuration. + * @indio_gyro: gyroscope IIO device. + * @indio_accel: accelerometer IIO device. + * @timestamp: interrupt timestamps. + * @apex: APEX (Advanced Pedometer and Event detection) management + * @buffer: data transfer buffer aligned for DMA. + */ +struct inv_icm42607_state { + struct mutex lock; + enum inv_icm42607_chip chip; + const char *name; + struct regmap *map; + struct regulator *vddio_supply; + int irq; + struct iio_mount_matrix orientation; + struct inv_icm42607_conf conf; + struct inv_icm42607_suspended suspended; + struct iio_dev *indio_gyro; + struct iio_dev *indio_accel; + struct { + s64 gyro; + s64 accel; + } timestamp; + struct inv_icm42607_apex apex; + __be16 buffer[3] __aligned(IIO_DMA_MINALIGN); +}; + +/** + * struct inv_icm42607_sensor_state - sensor state variables + * @power_mode: sensor requested power mode (for common frequencies) + * @filter: sensor filter. + * @ts: timestamp module states. + */ +struct inv_icm42607_sensor_state { + enum inv_icm42607_sensor_mode power_mode; + int filter; + struct inv_sensors_timestamp ts; +}; + +/* Virtual register addresses: @bank on MSB (4 upper bits), @address on LSB */ + +/* Register Map for User Bank 0 */ +#define INV_ICM42607_REG_DEVICE_CONFIG 0x01 +#define INV_ICM42607_DEVICE_CONFIG_SPI_AP_4WIRE BIT(2) +#define INV_ICM42607_DEVICE_CONFIG_SPI_MODE BIT(0) + +#define INV_ICM42607_REG_SIGNAL_PATH_RESET 0x02 +#define INV_ICM42607_SIGNAL_PATH_RESET_SOFT_RESET BIT(4) +#define INV_ICM42607_SIGNAL_PATH_RESET_FIFO_FLUSH BIT(2) + +#define INV_ICM42607_REG_DRIVE_CONFIG1 0x03 +#define INV_ICM42607_DRIVE_CONFIG1_I3C_DDR_MASK GENMASK(5, 3) +#define INV_ICM42607_DRIVE_CONFIG1_I3C_SDR_MASK GENMASK(2, 0) + +#define INV_ICM42607_REG_DRIVE_CONFIG2 0x04 +#define INV_ICM42607_DRIVE_CONFIG2_I2C_MASK GENMASK(5, 3) +#define INV_ICM42607_DRIVE_CONFIG2_ALL_MASK GENMASK(2, 0) + +#define INV_ICM42607_REG_DRIVE_CONFIG3 0x05 +#define INV_ICM42607_DRIVE_CONFIG3_SPI_MASK GENMASK(2, 0) + +#define INV_ICM42607_REG_INT_CONFIG 0x06 +#define INV_ICM42607_INT_CONFIG_INT2_LATCHED BIT(5) +#define INV_ICM42607_INT_CONFIG_INT2_PUSH_PULL BIT(4) +#define INV_ICM42607_INT_CONFIG_INT2_ACTIVE_HIGH BIT(3) +#define INV_ICM42607_INT_CONFIG_INT2_ACTIVE_LOW 0x00 +#define INV_ICM42607_INT_CONFIG_INT1_LATCHED BIT(2) +#define INV_ICM42607_INT_CONFIG_INT1_PUSH_PULL BIT(1) +#define INV_ICM42607_INT_CONFIG_INT1_ACTIVE_HIGH BIT(0) +#define INV_ICM42607_INT_CONFIG_INT1_ACTIVE_LOW 0x00 + +/* all sensor data are 16 bits (2 registers wide) in big-endian */ +#define INV_ICM42607_REG_TEMP_DATA1 0x09 +#define INV_ICM42607_REG_TEMP_DATA0 0x0A +#define INV_ICM42607_REG_ACCEL_DATA_X1 0x0B +#define INV_ICM42607_REG_ACCEL_DATA_X0 0x0C +#define INV_ICM42607_REG_ACCEL_DATA_Y1 0x0D +#define INV_ICM42607_REG_ACCEL_DATA_Y0 0x0E +#define INV_ICM42607_REG_ACCEL_DATA_Z1 0x0F +#define INV_ICM42607_REG_ACCEL_DATA_Z0 0x10 +#define INV_ICM42607_REG_GYRO_DATA_X1 0x11 +#define INV_ICM42607_REG_GYRO_DATA_X0 0x12 +#define INV_ICM42607_REG_GYRO_DATA_Y1 0x13 +#define INV_ICM42607_REG_GYRO_DATA_Y0 0x14 +#define INV_ICM42607_REG_GYRO_DATA_Z1 0x15 +#define INV_ICM42607_REG_GYRO_DATA_Z0 0x16 +#define INV_ICM42607_DATA_INVALID -32768 + +#define INV_ICM42607_REG_TMST_FSYNCH 0x17 +#define INV_ICM42607_REG_TMST_FSYNCL 0x18 + +/* APEX Data Registers */ +#define INV_ICM42607_REG_APEX_DATA0 0x31 +#define INV_ICM42607_REG_APEX_DATA1 0x32 +#define INV_ICM42607_REG_APEX_DATA2 0x33 +#define INV_ICM42607_REG_APEX_DATA3 0x34 +#define INV_ICM42607_REG_APEX_DATA4 0x1D +#define INV_ICM42607_REG_APEX_DATA5 0x1E + +#define INV_ICM42607_REG_PWR_MGMT0 0x1F +#define INV_ICM42607_PWR_MGMT0_ACCEL_LP_CLK_SEL BIT(7) +#define INV_ICM42607_PWR_MGMT0_IDLE BIT(4) +#define INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK GENMASK(3, 2) +#define INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK GENMASK(1, 0) + +#define INV_ICM42607_REG_GYRO_CONFIG0 0x20 +#define INV_ICM42607_GYRO_CONFIG0_FS_SEL_MASK GENMASK(6, 5) +#define INV_ICM42607_GYRO_CONFIG0_ODR_MASK GENMASK(3, 0) + +#define INV_ICM42607_REG_ACCEL_CONFIG0 0x21 +#define INV_ICM42607_ACCEL_CONFIG0_FS_SEL_MASK GENMASK(6, 5) +#define INV_ICM42607_ACCEL_CONFIG0_ODR_MASK GENMASK(3, 0) + +#define INV_ICM42607_REG_TEMP_CONFIG0 0x22 +#define INV_ICM42607_TEMP_CONFIG0_FILTER_MASK GENMASK(6, 4) + +#define INV_ICM42607_REG_GYRO_CONFIG1 0x23 +#define INV_ICM42607_GYRO_CONFIG1_FILTER_MASK GENMASK(2, 0) + +#define INV_ICM42607_REG_ACCEL_CONFIG1 0x24 +#define INV_ICM42607_ACCEL_CONFIG1_AVG_MASK GENMASK(6, 4) +#define INV_ICM42607_ACCEL_CONFIG1_FILTER_MASK GENMASK(2, 0) + +#define INV_ICM42607_REG_APEX_CONFIG0 0x25 +#define INV_ICM42607_APEX_CONFIG0_DMP_POWER_SAVE_EN BIT(3) +#define INV_ICM42607_APEX_CONFIG0_DMP_INIT_EN BIT(2) +#define INV_ICM42607_APEX_CONFIG0_DMP_MEM_RESET_EN BIT(0) + +#define INV_ICM42607_REG_APEX_CONFIG1 0x26 +#define INV_ICM42607_APEX_CONFIG1_SMD_ENABLE BIT(6) +#define INV_ICM42607_APEX_CONFIG1_FF_ENABLE BIT(5) +#define INV_ICM42607_APEX_CONFIG1_TILT_ENABLE BIT(4) +#define INV_ICM42607_APEX_CONFIG1_PED_ENABLE BIT(3) +#define INV_ICM42607_APEX_CONFIG1_DMP_ODR_MASK GENMASK(1, 0) + +#define INV_ICM42607_REG_WOM_CONFIG 0x27 +#define INV_ICM42607_WOM_CONFIG_INT_DUR_MASK GENMASK(4, 3) +#define INV_ICM42607_WOM_CONFIG_INT_MODE BIT(2) +#define INV_ICM42607_WOM_CONFIG_MODE BIT(1) +#define INV_ICM42607_WOM_CONFIG_EN BIT(0) + +#define INV_ICM42607_REG_FIFO_CONFIG1 0x28 +#define INV_ICM42607_FIFO_CONFIG1_MODE BIT(1) +#define INV_ICM42607_FIFO_CONFIG1_BYPASS BIT(0) + +#define INV_ICM42607_REG_FIFO_CONFIG2 0x29 +#define INV_ICM42607_REG_FIFO_CONFIG3 0x2A +#define INV_ICM42607_FIFO_WATERMARK_VAL(_wm) \ + cpu_to_le16((_wm) & GENMASK(11, 0)) +/* FIFO is 2048 bytes, let 12 samples for reading latency */ +#define INV_ICM42607_FIFO_WATERMARK_MAX (2048 - 12 * 16) + +#define INV_ICM42607_REG_INT_SOURCE0 0x2B +#define INV_ICM42607_INT_SOURCE0_ST_INT1_EN BIT(7) +#define INV_ICM42607_INT_SOURCE0_FSYNC_INT1_EN BIT(6) +#define INV_ICM42607_INT_SOURCE0_PLL_RDY_INT1_EN BIT(5) +#define INV_ICM42607_INT_SOURCE0_RESET_DONE_INT1_EN BIT(4) +#define INV_ICM42607_INT_SOURCE0_DRDY_INT1_EN BIT(3) +#define INV_ICM42607_INT_SOURCE0_FIFO_THS_INT1_EN BIT(2) +#define INV_ICM42607_INT_SOURCE0_FIFO_FULL_INT1_EN BIT(1) +#define INV_ICM42607_INT_SOURCE0_AGC_RDY_INT1_EN BIT(0) + +#define INV_ICM42607_REG_INT_SOURCE1 0x2C +#define INV_ICM42607_INT_SOURCE1_I3C_ERROR_INT1_EN BIT(6) +#define INV_ICM42607_INT_SOURCE1_SMD_INT1_EN BIT(3) +#define INV_ICM42607_INT_SOURCE1_WOM_INT1_EN GENMASK(2, 0) + +#define INV_ICM42607_REG_INT_SOURCE3 0x2D +#define INV_ICM42607_INT_SOURCE3_ST_INT2_EN BIT(7) +#define INV_ICM42607_INT_SOURCE3_FSYNC_INT2_EN BIT(6) +#define INV_ICM42607_INT_SOURCE3_PLL_RDY_INT2_EN BIT(5) +#define INV_ICM42607_INT_SOURCE3_RESET_DONE_INT2_EN BIT(4) +#define INV_ICM42607_INT_SOURCE3_DRDY_INT2_EN BIT(3) +#define INV_ICM42607_INT_SOURCE3_FIFO_THS_INT2_EN BIT(2) +#define INV_ICM42607_INT_SOURCE3_FIFO_FULL_INT2_EN BIT(1) +#define INV_ICM42607_INT_SOURCE3_AGC_RDY_INT2_EN BIT(0) + +#define INV_ICM42607_REG_INT_SOURCE4 0x2E +#define INV_ICM42607_INT_SOURCE4_I3C_ERROR_INT2_EN BIT(6) +#define INV_ICM42607_INT_SOURCE4_SMD_INT2_EN BIT(3) +#define INV_ICM42607_INT_SOURCE4_WOM_Z_INT2_EN BIT(2) +#define INV_ICM42607_INT_SOURCE4_WOM_Y_INT2_EN BIT(1) +#define INV_ICM42607_INT_SOURCE4_WOM_X_INT2_EN BIT(0) + +#define INV_ICM42607_REG_FIFO_LOST_PKT0 0x2F +#define INV_ICM42607_REG_FIFO_LOST_PKT1 0x30 + +#define INV_ICM42607_REG_INTF_CONFIG0 0x35 +#define INV_ICM42607_INTF_CONFIG0_FIFO_COUNT_FORMAT BIT(6) +#define INV_ICM42607_INTF_CONFIG0_FIFO_COUNT_ENDIAN BIT(5) +#define INV_ICM42607_INTF_CONFIG0_SENSOR_DATA_ENDIAN BIT(4) +#define INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK GENMASK(1, 0) +#define INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_SPI_DIS \ + FIELD_PREP(INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK, 2) +#define INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_I2C_DIS \ + FIELD_PREP(INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK, 3) + +#define INV_ICM42607_REG_INTF_CONFIG1 0x36 +#define INV_ICM42607_INTF_CONFIG1_I3C_SDR_EN BIT(3) +#define INV_ICM42607_INTF_CONFIG1_I3C_DDR_EN BIT(2) +#define INV_ICM42607_INTF_CONFIG1_CLKSEL_MASK GENMASK(1, 0) +#define INV_ICM42607_INTF_CONFIG1_CLKSEL_INT 0 +#define INV_ICM42607_INTF_CONFIG1_CLKSEL_PLL 1 +#define INV_ICM42607_INTF_CONFIG1_CLKSEL_OFF 2 + +#define INV_ICM42607_REG_INT_STATUS_DRDY 0x39 +#define INV_ICM42607_INT_STATUS_DRDY_DATA_RDY BIT(0) + +#define INV_ICM42607_REG_INT_STATUS 0x3A +#define INV_ICM42607_INT_STATUS_ST BIT(7) +#define INV_ICM42607_INT_STATUS_FSYNC BIT(6) +#define INV_ICM42607_INT_STATUS_PLL_RDY BIT(5) +#define INV_ICM42607_INT_STATUS_RESET_DONE BIT(4) +#define INV_ICM42607_INT_STATUS_FIFO_THS BIT(2) +#define INV_ICM42607_INT_STATUS_FIFO_FULL BIT(1) +#define INV_ICM42607_INT_STATUS_AGC_RDY BIT(0) + +#define INV_ICM42607_REG_INT_STATUS2 0x3B +#define INV_ICM42607_INT_STATUS2_SMD BIT(3) +#define INV_ICM42607_INT_STATUS2_WOM_INT GENMASK(2, 0) + +#define INV_ICM42607_REG_INT_STATUS3 0x3C +#define INV_ICM42607_INT_STATUS3_STEP_DET BIT(5) +#define INV_ICM42607_INT_STATUS3_STEP_CNT_OVF BIT(4) +#define INV_ICM42607_INT_STATUS3_TILT_DET BIT(3) +#define INV_ICM42607_INT_STATUS3_FF_DET BIT(2) + +/* + * FIFO access registers + * FIFO count is 16 bits (2 registers) big-endian + * FIFO data is a continuous read register to read FIFO content + */ +#define INV_ICM42607_REG_FIFO_COUNTH 0x3D +#define INV_ICM42607_REG_FIFO_COUNTL 0x3E +#define INV_ICM42607_REG_FIFO_DATA 0x3F + +#define INV_ICM42607_REG_ACCEL_WOM_X_THR 0x4b +#define INV_ICM42607_REG_ACCEL_WOM_Y_THR 0x4c +#define INV_ICM42607_REG_ACCEL_WOM_Z_THR 0x4d + +#define INV_ICM42607_REG_WHOAMI 0x75 +#define INV_ICM42607P_WHOAMI 0x60 +#define INV_ICM42607_WHOAMI 0x67 + +/* Sleep times required by the driver */ +#define INV_ICM42607_POWER_UP_TIME_US 100000 +#define INV_ICM42607_RESET_TIME_MS 1 +#define INV_ICM42607_ACCEL_STARTUP_TIME_MS 20 +#define INV_ICM42607_GYRO_STARTUP_TIME_MS 60 +#define INV_ICM42607_GYRO_STOP_TIME_MS 150 +#define INV_ICM42607_TEMP_STARTUP_TIME_MS 14 +#define INV_ICM42607_SUSPEND_DELAY_MS 2000 + +typedef int (*inv_icm42607_bus_setup)(struct inv_icm42607_state *); + +u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr); + +int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval); + +int inv_icm42607_core_probe(struct regmap *regmap, int chip, + inv_icm42607_bus_setup bus_setup); + +#endif diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c new file mode 100644 index 000000000000..a0bbd8a7ea4b --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inv_icm42607.h" + +struct inv_icm42607_hw { + uint8_t whoami; + const char *name; + const struct inv_icm42607_conf *conf; +}; + +/* chip initial default configuration */ +static const struct inv_icm42607_conf inv_icm42607_default_conf = { + .gyro = { + .mode = INV_ICM42607_SENSOR_MODE_OFF, + .fs = INV_ICM42607_GYRO_FS_1000DPS, + .odr = INV_ICM42607_ODR_100HZ, + .filter = INV_ICM42607_FILTER_BW_25HZ, + }, + .accel = { + .mode = INV_ICM42607_SENSOR_MODE_OFF, + .fs = INV_ICM42607_ACCEL_FS_4G, + .odr = INV_ICM42607_ODR_100HZ, + .filter = INV_ICM42607_FILTER_BW_25HZ, + }, + .temp_en = false, +}; + +static const struct inv_icm42607_hw inv_icm42607_hw[INV_CHIP_NB] = { + [INV_CHIP_ICM42607] = { + .whoami = INV_ICM42607_WHOAMI, + .name = "icm42607", + .conf = &inv_icm42607_default_conf, + }, + [INV_CHIP_ICM42607P] = { + .whoami = INV_ICM42607P_WHOAMI, + .name = "icm42607p", + .conf = &inv_icm42607_default_conf, + }, +}; + +u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr) +{ + static const u32 odr_periods[INV_ICM42607_ODR_NB] = { + /* Reserved values */ + 0, 0, 0, 0, 0, + /* 1600Hz */ + 625000, + /* 800Hz */ + 1250000, + /* 400Hz */ + 2500000, + /* 200Hz */ + 5000000, + /* 100 Hz */ + 10000000, + /* 50Hz */ + 20000000, + /* 25Hz */ + 40000000, + /* 12.5Hz */ + 80000000, + /* 6.25Hz */ + 160000000, + /* 3.125Hz */ + 320000000, + /* 1.5625Hz */ + 640000000, + }; + + odr = clamp(odr, INV_ICM42607_ODR_1600HZ, INV_ICM42607_ODR_1_5625HZ_LP); + + return odr_periods[odr]; +} + +int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + if (readval) + return regmap_read(st->map, reg, readval); + + return regmap_write(st->map, reg, writeval); +} + +static int inv_icm42607_set_conf(struct inv_icm42607_state *st, + const struct inv_icm42607_conf *conf) +{ + unsigned int val; + int ret; + + val = FIELD_PREP(INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK, + conf->gyro.mode); + val |= FIELD_PREP(INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK, + conf->accel.mode); + /* + * No temperature enable reg in datasheet, but BSP driver + * selected RC oscillator clock in LP mode when temperature + * was disabled. + */ + if (!conf->temp_en) + val |= INV_ICM42607_PWR_MGMT0_ACCEL_LP_CLK_SEL; + ret = regmap_write(st->map, INV_ICM42607_REG_PWR_MGMT0, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_GYRO_CONFIG0_FS_SEL_MASK, + conf->gyro.fs); + val |= FIELD_PREP(INV_ICM42607_GYRO_CONFIG0_ODR_MASK, + conf->gyro.odr); + ret = regmap_write(st->map, INV_ICM42607_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_ACCEL_CONFIG0_FS_SEL_MASK, conf->accel.fs); + val |= FIELD_PREP(INV_ICM42607_ACCEL_CONFIG0_ODR_MASK, conf->accel.odr); + ret = regmap_write(st->map, INV_ICM42607_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_GYRO_CONFIG1_FILTER_MASK, + conf->gyro.filter); + ret = regmap_write(st->map, INV_ICM42607_REG_GYRO_CONFIG1, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_ACCEL_CONFIG1_FILTER_MASK, + conf->accel.filter); + ret = regmap_write(st->map, INV_ICM42607_REG_ACCEL_CONFIG1, val); + if (ret) + return ret; + + st->conf = *conf; + + return 0; +} + +/** + * inv_icm42607_setup() - check and setup chip + * @st: driver internal state + * @bus_setup: callback for setting up bus specific registers + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_icm42607_setup(struct inv_icm42607_state *st, + inv_icm42607_bus_setup bus_setup) +{ + const struct inv_icm42607_hw *hw = &inv_icm42607_hw[st->chip]; + const struct device *dev = regmap_get_device(st->map); + unsigned int val; + int ret; + + ret = regmap_read(st->map, INV_ICM42607_REG_WHOAMI, &val); + if (ret) + return ret; + + if (val != hw->whoami) + dev_warn_probe(dev, -ENODEV, + "invalid whoami %#02x expected %#02x (%s)\n", + val, hw->whoami, hw->name); + + st->name = hw->name; + + ret = regmap_write(st->map, INV_ICM42607_REG_SIGNAL_PATH_RESET, + INV_ICM42607_SIGNAL_PATH_RESET_SOFT_RESET); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(st->map, INV_ICM42607_REG_INT_STATUS, + val, val & INV_ICM42607_INT_STATUS_RESET_DONE, + INV_ICM42607_RESET_TIME_MS * 100, + INV_ICM42607_RESET_TIME_MS * 1000); + + if (ret) + return dev_err_probe(dev, ret, + "reset error, reset done bit not set\n"); + + ret = bus_setup(st); + if (ret) + return ret; + + ret = regmap_set_bits(st->map, INV_ICM42607_REG_INTF_CONFIG0, + INV_ICM42607_INTF_CONFIG0_SENSOR_DATA_ENDIAN); + if (ret) + return ret; + + ret = regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG1, + INV_ICM42607_INTF_CONFIG1_CLKSEL_MASK, + INV_ICM42607_INTF_CONFIG1_CLKSEL_PLL); + if (ret) + return ret; + + return inv_icm42607_set_conf(st, hw->conf); +} + +static int inv_icm42607_enable_vddio_reg(struct inv_icm42607_state *st) +{ + int ret; + + ret = regulator_enable(st->vddio_supply); + if (ret) + return ret; + + fsleep(INV_ICM42607_POWER_UP_TIME_US); + + return 0; +} + +static void inv_icm42607_disable_vddio_reg(void *_data) +{ + struct inv_icm42607_state *st = _data; + + regulator_disable(st->vddio_supply); +} + +int inv_icm42607_core_probe(struct regmap *regmap, int chip, + inv_icm42607_bus_setup bus_setup) +{ + struct device *dev = regmap_get_device(regmap); + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct inv_icm42607_state *st; + int irq; + bool open_drain; + int ret; + + /* + * Keep bounds checking in case more chips are added, for now only + * 2 are supported. + */ + if (chip < INV_CHIP_INVALID || chip >= INV_CHIP_NB) + dev_warn_probe(dev, -ENODEV, "Invalid chip = %d\n", chip); + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq < 0) + return dev_err_probe(dev, irq, "error missing INT1 interrupt\n"); + + open_drain = device_property_read_bool(dev, "drive-open-drain"); + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + dev_set_drvdata(dev, st); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->chip = chip; + st->map = regmap; + st->irq = irq; + + ret = iio_read_mount_matrix(dev, &st->orientation); + if (ret) + return dev_err_probe(dev, ret, + "failed to retrieve mounting matrix %d\n", ret); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get vdd regulator\n"); + + st->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(st->vddio_supply)) + return PTR_ERR(st->vddio_supply); + + ret = inv_icm42607_enable_vddio_reg(st); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, inv_icm42607_disable_vddio_reg, st); + if (ret) + return ret; + + /* Setup chip registers (includes WHOAMI check, reset check, bus setup) */ + ret = inv_icm42607_setup(st, bus_setup); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(inv_icm42607_core_probe, "IIO_ICM42607"); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-42607x device driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_INV_SENSORS_TIMESTAMP"); -- 2.43.0 From macroalpha82 at gmail.com Fri May 1 15:11:42 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:42 -0500 Subject: [PATCH V4 03/10] iio: imu: inv_icm42607: Add I2C and SPI For icm42607 In-Reply-To: <20260501221152.194251-1-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> Message-ID: <20260501221152.194251-4-macroalpha82@gmail.com> From: Chris Morgan Add I2C and SPI driver support for InvenSense ICM-42607 devices. Add necessary Kconfig and Makefile to allow building of (incomplete) driver. Signed-off-by: Chris Morgan --- drivers/iio/imu/Kconfig | 1 + drivers/iio/imu/Makefile | 1 + drivers/iio/imu/inv_icm42607/Kconfig | 30 ++++++ drivers/iio/imu/inv_icm42607/Makefile | 10 ++ drivers/iio/imu/inv_icm42607/inv_icm42607.h | 2 + .../iio/imu/inv_icm42607/inv_icm42607_core.c | 20 ++++ .../iio/imu/inv_icm42607/inv_icm42607_i2c.c | 90 +++++++++++++++++ .../iio/imu/inv_icm42607/inv_icm42607_spi.c | 96 +++++++++++++++++++ 8 files changed, 250 insertions(+) create mode 100644 drivers/iio/imu/inv_icm42607/Kconfig create mode 100644 drivers/iio/imu/inv_icm42607/Makefile create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index 7e0181c27bb6..8bab4616be20 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -109,6 +109,7 @@ config KMX61 be called kmx61. source "drivers/iio/imu/inv_icm42600/Kconfig" +source "drivers/iio/imu/inv_icm42607/Kconfig" source "drivers/iio/imu/inv_icm45600/Kconfig" source "drivers/iio/imu/inv_mpu6050/Kconfig" diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index 13fb7846e9c9..3268dc2371ae 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_FXOS8700_I2C) += fxos8700_i2c.o obj-$(CONFIG_FXOS8700_SPI) += fxos8700_spi.o obj-y += inv_icm42600/ +obj-y += inv_icm42607/ obj-y += inv_icm45600/ obj-y += inv_mpu6050/ diff --git a/drivers/iio/imu/inv_icm42607/Kconfig b/drivers/iio/imu/inv_icm42607/Kconfig new file mode 100644 index 000000000000..7ba64e6e8d80 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/Kconfig @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config INV_ICM42607 + tristate + select IIO_BUFFER + select IIO_INV_SENSORS_TIMESTAMP + +config INV_ICM42607_I2C + tristate "InvenSense ICM-42607X I2C driver" + depends on I2C + select INV_ICM42607 + select REGMAP_I2C + help + This driver supports the InvenSense ICM-42607 motion tracking + device over I2C. + + This driver can be built as a module. The module will be called + inv-icm42607-i2c. + +config INV_ICM42607_SPI + tristate "InvenSense ICM-42607X SPI driver" + depends on SPI_MASTER + select INV_ICM42607 + select REGMAP_SPI + help + This driver supports the InvenSense ICM-42607 motion tracking + device over SPI. + + This driver can be built as a module. The module will be called + inv-icm42607-spi. diff --git a/drivers/iio/imu/inv_icm42607/Makefile b/drivers/iio/imu/inv_icm42607/Makefile new file mode 100644 index 000000000000..be109102e203 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-$(CONFIG_INV_ICM42607) += inv-icm42607.o +inv-icm42607-y += inv_icm42607_core.o + +obj-$(CONFIG_INV_ICM42607_I2C) += inv-icm42607-i2c.o +inv-icm42607-i2c-y += inv_icm42607_i2c.o + +obj-$(CONFIG_INV_ICM42607_SPI) += inv-icm42607-spi.o +inv-icm42607-spi-y += inv_icm42607_spi.o diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h index f6b3cad8064a..763d731ccc60 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607.h +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h @@ -389,6 +389,8 @@ struct inv_icm42607_sensor_state { typedef int (*inv_icm42607_bus_setup)(struct inv_icm42607_state *); +extern const struct regmap_config inv_icm42607_regmap_config; + u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr); int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c index a0bbd8a7ea4b..1ac3a573863c 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -16,6 +16,26 @@ #include "inv_icm42607.h" +static const struct regmap_range_cfg inv_icm42607_regmap_ranges[] = { + { + .name = "user bank", + .range_min = 0x0000, + .range_max = 0x00FF, + .window_start = 0, + .window_len = 0x0100, + }, +}; + +const struct regmap_config inv_icm42607_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x00FF, + .ranges = inv_icm42607_regmap_ranges, + .num_ranges = ARRAY_SIZE(inv_icm42607_regmap_ranges), + .cache_type = REGCACHE_NONE, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm42607_regmap_config, "IIO_ICM42607"); + struct inv_icm42607_hw { uint8_t whoami; const char *name; diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c new file mode 100644 index 000000000000..83cdb9c87167 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "inv_icm42607.h" + +static int inv_icm42607_i2c_bus_setup(struct inv_icm42607_state *st) +{ + unsigned int val; + int ret; + + ret = regmap_clear_bits(st->map, INV_ICM42607_REG_INTF_CONFIG1, + INV_ICM42607_INTF_CONFIG1_I3C_DDR_EN | + INV_ICM42607_INTF_CONFIG1_I3C_SDR_EN); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_DRIVE_CONFIG2_I2C_MASK, + INV_ICM42607_SLEW_RATE_12_36NS); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_DRIVE_CONFIG2, + INV_ICM42607_DRIVE_CONFIG2_I2C_MASK, val); + if (ret) + return ret; + + return regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG0, + INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK, + INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_SPI_DIS); +} + +static int inv_icm42607_probe(struct i2c_client *client) +{ + const void *match; + enum inv_icm42607_chip chip; + struct regmap *regmap; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EOPNOTSUPP; + + match = i2c_get_match_data(client); + chip = (kernel_ulong_t)match; + + regmap = devm_regmap_init_i2c(client, &inv_icm42607_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return inv_icm42607_core_probe(regmap, chip, inv_icm42607_i2c_bus_setup); +} + +static const struct i2c_device_id inv_icm42607_id[] = { + { "icm42607", INV_CHIP_ICM42607 }, + { "icm42607p", INV_CHIP_ICM42607P }, + { } +}; +MODULE_DEVICE_TABLE(i2c, inv_icm42607_id); + +static const struct of_device_id inv_icm42607_of_matches[] = { + { + .compatible = "invensense,icm42607", + .data = (void *)INV_CHIP_ICM42607, + }, { + .compatible = "invensense,icm42607p", + .data = (void *)INV_CHIP_ICM42607P, + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_icm42607_of_matches); + +static struct i2c_driver inv_icm42607_driver = { + .driver = { + .name = "inv-icm42607-i2c", + .of_match_table = inv_icm42607_of_matches, + }, + .id_table = inv_icm42607_id, + .probe = inv_icm42607_probe, +}; +module_i2c_driver(inv_icm42607_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-42607x I2C driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM42607"); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c new file mode 100644 index 000000000000..7a02bbab3a63 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "inv_icm42607.h" + +static int inv_icm42607_spi_bus_setup(struct inv_icm42607_state *st) +{ + unsigned int val; + int ret; + + ret = regmap_set_bits(st->map, INV_ICM42607_REG_DEVICE_CONFIG, + INV_ICM42607_DEVICE_CONFIG_SPI_AP_4WIRE); + if (ret) + return ret; + + ret = regmap_clear_bits(st->map, INV_ICM42607_REG_INTF_CONFIG1, + INV_ICM42607_INTF_CONFIG1_I3C_DDR_EN | + INV_ICM42607_INTF_CONFIG1_I3C_SDR_EN); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM42607_DRIVE_CONFIG3_SPI_MASK, + INV_ICM42607_SLEW_RATE_INF_2NS); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_DRIVE_CONFIG3, + INV_ICM42607_DRIVE_CONFIG3_SPI_MASK, val); + if (ret) + return ret; + + return regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG0, + INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_MASK, + INV_ICM42607_INTF_CONFIG0_UI_SIFS_CFG_I2C_DIS); +} + +static int inv_icm42607_probe(struct spi_device *spi) +{ + const void *match; + enum inv_icm42607_chip chip; + struct regmap *regmap; + + match = spi_get_device_match_data(spi); + chip = (kernel_ulong_t)match; + + regmap = devm_regmap_init_spi(spi, &inv_icm42607_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(regmap), + "Failed to register spi regmap %ld\n", + PTR_ERR(regmap)); + + return inv_icm42607_core_probe(regmap, chip, + inv_icm42607_spi_bus_setup); +} + +static const struct of_device_id inv_icm42607_of_matches[] = { + { + .compatible = "invensense,icm42607", + .data = (void *)INV_CHIP_ICM42607, + }, + { + .compatible = "invensense,icm42607p", + .data = (void *)INV_CHIP_ICM42607P, + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_icm42607_of_matches); + +static const struct spi_device_id inv_icm42607_spi_id_table[] = { + { "icm42607", INV_CHIP_ICM42607 }, + { "icm42607p", INV_CHIP_ICM42607P }, + { } +}; +MODULE_DEVICE_TABLE(spi, inv_icm42607_spi_id_table); + +static struct spi_driver inv_icm42607_driver = { + .driver = { + .name = "inv-icm42607-spi", + .of_match_table = inv_icm42607_of_matches, + }, + .id_table = inv_icm42607_spi_id_table, + .probe = inv_icm42607_probe, +}; +module_spi_driver(inv_icm42607_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-42607x SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM42607"); -- 2.43.0 From macroalpha82 at gmail.com Fri May 1 15:11:43 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:43 -0500 Subject: [PATCH V4 04/10] iio: imu: inv_icm42607: Add PM support for icm42607 In-Reply-To: <20260501221152.194251-1-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> Message-ID: <20260501221152.194251-5-macroalpha82@gmail.com> From: Chris Morgan Add power management support for the ICM42607 device driver. Signed-off-by: Chris Morgan --- drivers/iio/imu/inv_icm42607/inv_icm42607.h | 2 + .../iio/imu/inv_icm42607/inv_icm42607_core.c | 177 ++++++++++++++++++ .../iio/imu/inv_icm42607/inv_icm42607_i2c.c | 1 + .../iio/imu/inv_icm42607/inv_icm42607_spi.c | 1 + 4 files changed, 181 insertions(+) diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h index 763d731ccc60..7facc114adc5 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607.h +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -390,6 +391,7 @@ struct inv_icm42607_sensor_state { typedef int (*inv_icm42607_bus_setup)(struct inv_icm42607_state *); extern const struct regmap_config inv_icm42607_regmap_config; +extern const struct dev_pm_ops inv_icm42607_pm_ops; u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c index 1ac3a573863c..af3784040b7a 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,62 @@ u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr) return odr_periods[odr]; } +static int inv_icm42607_set_pwr_mgmt0(struct inv_icm42607_state *st, + enum inv_icm42607_sensor_mode gyro, + enum inv_icm42607_sensor_mode accel, + bool temp, unsigned int *sleep_ms) +{ + enum inv_icm42607_sensor_mode oldgyro = st->conf.gyro.mode; + enum inv_icm42607_sensor_mode oldaccel = st->conf.accel.mode; + bool oldtemp = st->conf.temp_en; + unsigned int sleepval; + unsigned int val; + int ret; + + if (gyro == oldgyro && accel == oldaccel && temp == oldtemp) + return 0; + + val = FIELD_PREP(INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK, gyro); + val |= FIELD_PREP(INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK, accel); + if (!temp) + val |= INV_ICM42607_PWR_MGMT0_ACCEL_LP_CLK_SEL; + ret = regmap_write(st->map, INV_ICM42607_REG_PWR_MGMT0, val); + if (ret) + return ret; + + st->conf.gyro.mode = gyro; + st->conf.accel.mode = accel; + st->conf.temp_en = temp; + + sleepval = 0; + if (temp && !oldtemp) { + if (sleepval < INV_ICM42607_TEMP_STARTUP_TIME_MS) + sleepval = INV_ICM42607_TEMP_STARTUP_TIME_MS; + } + if (accel != oldaccel && oldaccel == INV_ICM42607_SENSOR_MODE_OFF) { + usleep_range(200, 300); + if (sleepval < INV_ICM42607_ACCEL_STARTUP_TIME_MS) + sleepval = INV_ICM42607_ACCEL_STARTUP_TIME_MS; + } + if (gyro != oldgyro) { + if (oldgyro == INV_ICM42607_SENSOR_MODE_OFF) { + usleep_range(200, 300); + if (sleepval < INV_ICM42607_GYRO_STARTUP_TIME_MS) + sleepval = INV_ICM42607_GYRO_STARTUP_TIME_MS; + } else if (gyro == INV_ICM42607_SENSOR_MODE_OFF) { + if (sleepval < INV_ICM42607_GYRO_STOP_TIME_MS) + sleepval = INV_ICM42607_GYRO_STOP_TIME_MS; + } + } + + if (sleep_ms) + *sleep_ms = sleepval; + else if (sleepval) + msleep(sleepval); + + return 0; +} + int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { @@ -313,10 +370,130 @@ int inv_icm42607_core_probe(struct regmap *regmap, int chip, if (ret) return ret; + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, INV_ICM42607_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + return 0; } EXPORT_SYMBOL_NS_GPL(inv_icm42607_core_probe, "IIO_ICM42607"); +/* + * Suspend saves sensors state and turns everything off. + * Check first if runtime suspend has not already done the job. + */ +static int inv_icm42607_suspend(struct device *dev) +{ + struct inv_icm42607_state *st = dev_get_drvdata(dev); + struct device *accel_dev; + bool wakeup; + int accel_conf; + int ret = 0; + + guard(mutex)(&st->lock); + + st->suspended.gyro = st->conf.gyro.mode; + st->suspended.accel = st->conf.accel.mode; + st->suspended.temp = st->conf.temp_en; + if (pm_runtime_suspended(dev)) + return 0; + + /* keep chip on and wake-up capable if APEX and wakeup on */ + accel_dev = &st->indio_accel->dev; + wakeup = st->apex.on && device_may_wakeup(accel_dev); + if (wakeup) { + /* keep accel on and setup irq for wakeup */ + accel_conf = st->conf.accel.mode; + enable_irq_wake(st->irq); + disable_irq(st->irq); + } else { + accel_conf = INV_ICM42607_SENSOR_MODE_OFF; + } + + ret = inv_icm42607_set_pwr_mgmt0(st, INV_ICM42607_SENSOR_MODE_OFF, + INV_ICM42607_SENSOR_MODE_OFF, false, + NULL); + if (ret) + return ret; + + if (!wakeup) + regulator_disable(st->vddio_supply); + + return 0; +} + +/* + * System resume gets the system back on and restores the sensors state. + * Manually put runtime power management in system active state. + */ +static int inv_icm42607_resume(struct device *dev) +{ + struct inv_icm42607_state *st = dev_get_drvdata(dev); + struct device *accel_dev; + bool wakeup; + int ret; + + guard(mutex)(&st->lock); + + if (pm_runtime_suspended(dev)) + return 0; + + /* check wakeup capability */ + accel_dev = &st->indio_accel->dev; + wakeup = st->apex.on && device_may_wakeup(accel_dev); + /* restore irq state */ + if (wakeup) { + enable_irq(st->irq); + disable_irq_wake(st->irq); + } else { + ret = inv_icm42607_enable_vddio_reg(st); + if (ret) + return ret; + } + + /* restore sensors state */ + ret = inv_icm42607_set_pwr_mgmt0(st, st->suspended.gyro, + st->suspended.accel, + st->suspended.temp, NULL); + return ret; +} + +static int inv_icm42607_runtime_suspend(struct device *dev) +{ + struct inv_icm42607_state *st = dev_get_drvdata(dev); + int ret = 0; + + guard(mutex)(&st->lock); + + ret = inv_icm42607_set_pwr_mgmt0(st, INV_ICM42607_SENSOR_MODE_OFF, + INV_ICM42607_SENSOR_MODE_OFF, false, + NULL); + if (ret) + return ret; + + regulator_disable(st->vddio_supply); + + return 0; +} + +static int inv_icm42607_runtime_resume(struct device *dev) +{ + struct inv_icm42607_state *st = dev_get_drvdata(dev); + + guard(mutex)(&st->lock); + + return inv_icm42607_enable_vddio_reg(st); +} + +EXPORT_NS_GPL_DEV_PM_OPS(inv_icm42607_pm_ops, IIO_ICM42607) = { + SYSTEM_SLEEP_PM_OPS(inv_icm42607_suspend, inv_icm42607_resume) + RUNTIME_PM_OPS(inv_icm42607_runtime_suspend, + inv_icm42607_runtime_resume, NULL) +}; + MODULE_AUTHOR("InvenSense, Inc."); MODULE_DESCRIPTION("InvenSense ICM-42607x device driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c index 83cdb9c87167..24cf39d52592 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_i2c.c @@ -78,6 +78,7 @@ static struct i2c_driver inv_icm42607_driver = { .driver = { .name = "inv-icm42607-i2c", .of_match_table = inv_icm42607_of_matches, + .pm = pm_ptr(&inv_icm42607_pm_ops), }, .id_table = inv_icm42607_id, .probe = inv_icm42607_probe, diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c index 7a02bbab3a63..9ee3b39b1b08 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c @@ -84,6 +84,7 @@ static struct spi_driver inv_icm42607_driver = { .driver = { .name = "inv-icm42607-spi", .of_match_table = inv_icm42607_of_matches, + .pm = &inv_icm42607_pm_ops, }, .id_table = inv_icm42607_spi_id_table, .probe = inv_icm42607_probe, -- 2.43.0 From macroalpha82 at gmail.com Fri May 1 15:11:44 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:44 -0500 Subject: [PATCH V4 05/10] iio: imu: inv_icm42607: Add Buffer support for icm42607 In-Reply-To: <20260501221152.194251-1-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> Message-ID: <20260501221152.194251-6-macroalpha82@gmail.com> From: Chris Morgan Add all FIFO parsing and reading functions to support inv_icm42607 hardware. Signed-off-by: Chris Morgan --- drivers/iio/imu/inv_icm42607/Makefile | 1 + drivers/iio/imu/inv_icm42607/inv_icm42607.h | 4 + .../imu/inv_icm42607/inv_icm42607_buffer.c | 489 ++++++++++++++++++ .../imu/inv_icm42607/inv_icm42607_buffer.h | 101 ++++ .../iio/imu/inv_icm42607/inv_icm42607_core.c | 26 + 5 files changed, 621 insertions(+) create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.h diff --git a/drivers/iio/imu/inv_icm42607/Makefile b/drivers/iio/imu/inv_icm42607/Makefile index be109102e203..3c9d08509793 100644 --- a/drivers/iio/imu/inv_icm42607/Makefile +++ b/drivers/iio/imu/inv_icm42607/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_INV_ICM42607) += inv-icm42607.o inv-icm42607-y += inv_icm42607_core.o +inv-icm42607-y += inv_icm42607_buffer.o obj-$(CONFIG_INV_ICM42607_I2C) += inv-icm42607-i2c.o inv-icm42607-i2c-y += inv_icm42607_i2c.o diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h index 7facc114adc5..fd860685d0ad 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607.h +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h @@ -15,6 +15,8 @@ #include #include +#include "inv_icm42607_buffer.h" + enum inv_icm42607_chip { INV_CHIP_INVALID, INV_CHIP_ICM42607P, @@ -140,6 +142,7 @@ struct inv_icm42607_apex { * @indio_accel: accelerometer IIO device. * @timestamp: interrupt timestamps. * @apex: APEX (Advanced Pedometer and Event detection) management + * @fifo: FIFO management structure. * @buffer: data transfer buffer aligned for DMA. */ struct inv_icm42607_state { @@ -159,6 +162,7 @@ struct inv_icm42607_state { s64 accel; } timestamp; struct inv_icm42607_apex apex; + struct inv_icm42607_fifo fifo; __be16 buffer[3] __aligned(IIO_DMA_MINALIGN); }; diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c new file mode 100644 index 000000000000..5c67f6a37520 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "inv_icm42607.h" +#include "inv_icm42607_buffer.h" + +/* FIFO header: 1 byte */ +#define INV_ICM42607_FIFO_HEADER_MSG BIT(7) +#define INV_ICM42607_FIFO_HEADER_ACCEL BIT(6) +#define INV_ICM42607_FIFO_HEADER_GYRO BIT(5) +#define INV_ICM42607_FIFO_HEADER_TMST_FSYNC GENMASK(3, 2) +#define INV_ICM42607_FIFO_HEADER_ODR_ACCEL BIT(1) +#define INV_ICM42607_FIFO_HEADER_ODR_GYRO BIT(0) + +struct inv_icm42607_fifo_1sensor_packet { + u8 header; + struct inv_icm42607_fifo_sensor_data data; + s8 temp; +} __packed; +#define INV_ICM42607_FIFO_1SENSOR_PACKET_SIZE 8 + +struct inv_icm42607_fifo_2sensors_packet { + u8 header; + struct inv_icm42607_fifo_sensor_data accel; + struct inv_icm42607_fifo_sensor_data gyro; + s8 temp; + __be16 timestamp; +} __packed; +#define INV_ICM42607_FIFO_2SENSORS_PACKET_SIZE 16 + +ssize_t inv_icm42607_fifo_decode_packet(const void *packet, const void **accel, + const void **gyro, const int8_t **temp, + const void **timestamp, unsigned int *odr) +{ + const struct inv_icm42607_fifo_1sensor_packet *pack1 = packet; + const struct inv_icm42607_fifo_2sensors_packet *pack2 = packet; + u8 header = *((const u8 *)packet); + + /* FIFO empty */ + if (header & INV_ICM42607_FIFO_HEADER_MSG) { + *accel = NULL; + *gyro = NULL; + *temp = NULL; + *timestamp = NULL; + *odr = 0; + return 0; + } + + /* handle odr flags */ + *odr = 0; + if (header & INV_ICM42607_FIFO_HEADER_ODR_GYRO) + *odr |= INV_ICM42607_SENSOR_GYRO; + if (header & INV_ICM42607_FIFO_HEADER_ODR_ACCEL) + *odr |= INV_ICM42607_SENSOR_ACCEL; + + /* accel + gyro */ + if ((header & INV_ICM42607_FIFO_HEADER_ACCEL) && + (header & INV_ICM42607_FIFO_HEADER_GYRO)) { + *accel = &pack2->accel; + *gyro = &pack2->gyro; + *temp = &pack2->temp; + *timestamp = &pack2->timestamp; + return INV_ICM42607_FIFO_2SENSORS_PACKET_SIZE; + } + + /* accel only */ + if (header & INV_ICM42607_FIFO_HEADER_ACCEL) { + *accel = &pack1->data; + *gyro = NULL; + *temp = &pack1->temp; + *timestamp = NULL; + return INV_ICM42607_FIFO_1SENSOR_PACKET_SIZE; + } + + /* gyro only */ + if (header & INV_ICM42607_FIFO_HEADER_GYRO) { + *accel = NULL; + *gyro = &pack1->data; + *temp = &pack1->temp; + *timestamp = NULL; + return INV_ICM42607_FIFO_1SENSOR_PACKET_SIZE; + } + + /* invalid packet if here */ + return -EINVAL; +} + +void inv_icm42607_buffer_update_fifo_period(struct inv_icm42607_state *st) +{ + u32 period_gyro, period_accel; + + if (st->fifo.en & INV_ICM42607_SENSOR_GYRO) + period_gyro = inv_icm42607_odr_to_period(st->conf.gyro.odr); + else + period_gyro = U32_MAX; + + if (st->fifo.en & INV_ICM42607_SENSOR_ACCEL) + period_accel = inv_icm42607_odr_to_period(st->conf.accel.odr); + else + period_accel = U32_MAX; + + st->fifo.period = min(period_gyro, period_accel); +} + +int inv_icm42607_buffer_set_fifo_en(struct inv_icm42607_state *st, + unsigned int fifo_en) +{ + unsigned int val; + int ret; + + /* update FIFO EN bits for accel and gyro */ + val = 0; + if (fifo_en & INV_ICM42607_SENSOR_GYRO) + val |= INV_ICM42607_FIFO_CONFIG1_MODE; + if (fifo_en & INV_ICM42607_SENSOR_ACCEL) + val |= INV_ICM42607_FIFO_CONFIG1_MODE; + if (fifo_en & INV_ICM42607_SENSOR_TEMP) + val |= INV_ICM42607_FIFO_CONFIG1_MODE; + + ret = regmap_write(st->map, INV_ICM42607_REG_FIFO_CONFIG1, val); + if (ret) + return ret; + + st->fifo.en = fifo_en; + inv_icm42607_buffer_update_fifo_period(st); + + return 0; +} + +static size_t inv_icm42607_get_packet_size(unsigned int fifo_en) +{ + size_t packet_size; + + if ((fifo_en & INV_ICM42607_SENSOR_GYRO) && + (fifo_en & INV_ICM42607_SENSOR_ACCEL)) + packet_size = INV_ICM42607_FIFO_2SENSORS_PACKET_SIZE; + else + packet_size = INV_ICM42607_FIFO_1SENSOR_PACKET_SIZE; + + return packet_size; +} + +static unsigned int inv_icm42607_wm_truncate(unsigned int watermark, + size_t packet_size) +{ + size_t wm_size; + + wm_size = watermark * packet_size; + if (wm_size > INV_ICM42607_FIFO_WATERMARK_MAX) + wm_size = INV_ICM42607_FIFO_WATERMARK_MAX; + + return wm_size / packet_size; +} + +/** + * inv_icm42607_buffer_update_watermark - update watermark FIFO threshold + * @st: driver internal state + * + * Returns 0 on success, a negative error code otherwise. + */ +int inv_icm42607_buffer_update_watermark(struct inv_icm42607_state *st) +{ + size_t packet_size, wm_size; + unsigned int wm_gyro, wm_accel, watermark; + u32 period_gyro, period_accel; + u32 latency_gyro, latency_accel, latency; + bool restore; + __le16 raw_wm; + int ret; + + packet_size = inv_icm42607_get_packet_size(st->fifo.en); + + /* compute sensors latency, depending on sensor watermark and odr */ + wm_gyro = inv_icm42607_wm_truncate(st->fifo.watermark.gyro, packet_size); + wm_accel = inv_icm42607_wm_truncate(st->fifo.watermark.accel, packet_size); + /* use us for odr to avoid overflow using 32 bits values */ + period_gyro = inv_icm42607_odr_to_period(st->conf.gyro.odr) / 1000UL; + period_accel = inv_icm42607_odr_to_period(st->conf.accel.odr) / 1000UL; + latency_gyro = period_gyro * wm_gyro; + latency_accel = period_accel * wm_accel; + + /* 0 value for watermark means that the sensor is turned off */ + if (wm_gyro == 0 && wm_accel == 0) + return 0; + + if (latency_gyro == 0) { + watermark = wm_accel; + st->fifo.watermark.eff_accel = wm_accel; + } else if (latency_accel == 0) { + watermark = wm_gyro; + st->fifo.watermark.eff_gyro = wm_gyro; + } else { + /* compute the smallest latency that is a multiple of both */ + if (latency_gyro <= latency_accel) + latency = latency_gyro - (latency_accel % latency_gyro); + else + latency = latency_accel - (latency_gyro % latency_accel); + /* all this works because periods are multiple of each others */ + watermark = latency / min(period_gyro, period_accel); + if (watermark < 1) + watermark = 1; + /* update effective watermark */ + st->fifo.watermark.eff_gyro = latency / period_gyro; + if (st->fifo.watermark.eff_gyro < 1) + st->fifo.watermark.eff_gyro = 1; + st->fifo.watermark.eff_accel = latency / period_accel; + if (st->fifo.watermark.eff_accel < 1) + st->fifo.watermark.eff_accel = 1; + } + + /* compute watermark value in bytes */ + wm_size = watermark * packet_size; + + /* changing FIFO watermark requires to turn off watermark interrupt */ + ret = regmap_update_bits_check(st->map, INV_ICM42607_REG_INT_SOURCE0, + INV_ICM42607_INT_SOURCE0_FIFO_THS_INT1_EN, + 0, &restore); + if (ret) + return ret; + + raw_wm = INV_ICM42607_FIFO_WATERMARK_VAL(wm_size); + memcpy(st->buffer, &raw_wm, sizeof(raw_wm)); + ret = regmap_bulk_write(st->map, INV_ICM42607_REG_FIFO_CONFIG2, + st->buffer, sizeof(raw_wm)); + if (ret) + return ret; + + /* restore watermark interrupt */ + if (restore) { + ret = regmap_update_bits(st->map, INV_ICM42607_REG_INT_SOURCE0, + INV_ICM42607_INT_SOURCE0_FIFO_THS_INT1_EN, + INV_ICM42607_INT_SOURCE0_FIFO_THS_INT1_EN); + if (ret) + return ret; + } + + return 0; +} + +static int inv_icm42607_buffer_preenable(struct iio_dev *indio_dev) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + struct inv_icm42607_sensor_state *sensor_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &sensor_st->ts; + + pm_runtime_get_sync(dev); + + guard(mutex)(&st->lock); + inv_sensors_timestamp_reset(ts); + + return 0; +} + +/* + * update_scan_mode callback is turning sensors on and setting data FIFO enable + * bits. + */ +static int inv_icm42607_buffer_postenable(struct iio_dev *indio_dev) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + /* exit if FIFO is already on */ + if (st->fifo.on) { + st->fifo.on++; + return 0; + } + + /* set FIFO threshold interrupt */ + ret = regmap_update_bits(st->map, INV_ICM42607_REG_INT_SOURCE0, + INV_ICM42607_INT_SOURCE0_FIFO_THS_INT1_EN, + INV_ICM42607_INT_SOURCE0_FIFO_THS_INT1_EN); + if (ret) + return ret; + + /* flush FIFO data */ + ret = regmap_write(st->map, INV_ICM42607_REG_SIGNAL_PATH_RESET, + INV_ICM42607_SIGNAL_PATH_RESET_FIFO_FLUSH); + if (ret) + return ret; + + /* set FIFO in streaming mode */ + ret = regmap_write(st->map, INV_ICM42607_REG_FIFO_CONFIG1, + INV_ICM42607_FIFO_CONFIG1_MODE); + if (ret) + return ret; + + /* workaround: first read of FIFO count after reset is always 0 */ + ret = regmap_bulk_read(st->map, INV_ICM42607_REG_FIFO_COUNTH, st->buffer, 2); + if (ret) + return ret; + + st->fifo.on++; + + return 0; +} + +static int inv_icm42607_buffer_predisable(struct iio_dev *indio_dev) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + guard(mutex)(&st->lock); + + if (st->fifo.on > 1) { + st->fifo.on--; + return 0; + } + + /* set FIFO in bypass mode */ + ret = regmap_write(st->map, INV_ICM42607_REG_FIFO_CONFIG1, + INV_ICM42607_FIFO_CONFIG1_BYPASS); + if (ret) + return ret; + + /* flush FIFO data */ + ret = regmap_write(st->map, INV_ICM42607_REG_SIGNAL_PATH_RESET, + INV_ICM42607_SIGNAL_PATH_RESET_FIFO_FLUSH); + if (ret) + return ret; + + /* disable FIFO threshold interrupt */ + ret = regmap_update_bits(st->map, INV_ICM42607_REG_INT_SOURCE0, + INV_ICM42607_INT_SOURCE0_FIFO_THS_INT1_EN, 0); + if (ret) + return ret; + + st->fifo.on--; + + return 0; +} + +static int inv_icm42607_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int sensor; + unsigned int *watermark; + unsigned int sleep_temp = 0; + unsigned int sleep_sensor = 0; + unsigned int sleep; + int ret; + + if (indio_dev == st->indio_gyro) { + sensor = INV_ICM42607_SENSOR_GYRO; + watermark = &st->fifo.watermark.gyro; + } else if (indio_dev == st->indio_accel) { + sensor = INV_ICM42607_SENSOR_ACCEL; + watermark = &st->fifo.watermark.accel; + } else { + return -EINVAL; + } + + mutex_lock(&st->lock); + + ret = inv_icm42607_buffer_set_fifo_en(st, st->fifo.en & ~sensor); + if (ret) + goto out_unlock; + + *watermark = 0; + ret = inv_icm42607_buffer_update_watermark(st); + if (ret) + goto out_unlock; + +out_unlock: + mutex_unlock(&st->lock); + + /* sleep maximum required time */ + sleep = max(sleep_sensor, sleep_temp); + if (sleep) + msleep(sleep); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +const struct iio_buffer_setup_ops inv_icm42607_buffer_ops = { + .preenable = inv_icm42607_buffer_preenable, + .postenable = inv_icm42607_buffer_postenable, + .predisable = inv_icm42607_buffer_predisable, + .postdisable = inv_icm42607_buffer_postdisable, +}; + +int inv_icm42607_buffer_fifo_read(struct inv_icm42607_state *st, + unsigned int max) +{ + size_t max_count; + __be16 *raw_fifo_count; + ssize_t i, size; + const void *accel, *gyro, *timestamp; + const s8 *temp; + unsigned int odr; + int ret; + + /* reset all samples counters */ + st->fifo.count = 0; + st->fifo.nb.gyro = 0; + st->fifo.nb.accel = 0; + st->fifo.nb.total = 0; + + /* compute maximum FIFO read size */ + if (max == 0) + max_count = sizeof(st->fifo.data); + else + max_count = max * inv_icm42607_get_packet_size(st->fifo.en); + + /* read FIFO count value */ + raw_fifo_count = (__be16 *)st->buffer; + ret = regmap_bulk_read(st->map, INV_ICM42607_REG_FIFO_COUNTH, + raw_fifo_count, sizeof(*raw_fifo_count)); + if (ret) + return ret; + st->fifo.count = be16_to_cpup(raw_fifo_count); + + /* check and clamp FIFO count value */ + if (st->fifo.count == 0) + return 0; + if (st->fifo.count > max_count) + st->fifo.count = max_count; + + /* read all FIFO data in internal buffer */ + ret = regmap_noinc_read(st->map, INV_ICM42607_REG_FIFO_DATA, + st->fifo.data, st->fifo.count); + if (ret) + return ret; + + /* compute number of samples for each sensor */ + for (i = 0; i < st->fifo.count; i += size) { + size = inv_icm42607_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + if (size <= 0) + break; + if (gyro != NULL && inv_icm42607_fifo_is_data_valid(gyro)) + st->fifo.nb.gyro++; + if (accel != NULL && inv_icm42607_fifo_is_data_valid(accel)) + st->fifo.nb.accel++; + st->fifo.nb.total++; + } + + return 0; +} + +int inv_icm42607_buffer_hwfifo_flush(struct inv_icm42607_state *st, + unsigned int count) +{ + s64 gyro_ts, accel_ts; + int ret; + + gyro_ts = iio_get_time_ns(st->indio_gyro); + accel_ts = iio_get_time_ns(st->indio_accel); + + ret = inv_icm42607_buffer_fifo_read(st, count); + + return ret; +} + +int inv_icm42607_buffer_init(struct inv_icm42607_state *st) +{ + int ret; + + st->fifo.watermark.eff_gyro = 1; + st->fifo.watermark.eff_accel = 1; + + /* Configure FIFO_COUNT format in bytes and big endian */ + ret = regmap_set_bits(st->map, INV_ICM42607_REG_INTF_CONFIG0, + INV_ICM42607_INTF_CONFIG0_FIFO_COUNT_ENDIAN); + if (ret) + return ret; + + /* Initialize FIFO in bypass mode */ + return regmap_write(st->map, INV_ICM42607_REG_FIFO_CONFIG1, + INV_ICM42607_FIFO_CONFIG1_BYPASS); +} diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.h b/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.h new file mode 100644 index 000000000000..2df49e795ad1 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#ifndef INV_ICM42607_BUFFER_H_ +#define INV_ICM42607_BUFFER_H_ + +#include +#include + +struct inv_icm42607_state; + +#define INV_ICM42607_SENSOR_GYRO BIT(0) +#define INV_ICM42607_SENSOR_ACCEL BIT(1) +#define INV_ICM42607_SENSOR_TEMP BIT(2) + +/** + * struct inv_icm42607_fifo - FIFO state variables + * @on: reference counter for FIFO on. + * @en: bits field of INV_ICM42607_SENSOR_* for FIFO EN bits. + * @period: FIFO internal period. + * @watermark: watermark configuration values for accel and gyro. + * @count: number of bytes in the FIFO data buffer. + * @nb: gyro, accel and total samples in the FIFO data buffer. + * @data: FIFO data buffer aligned for DMA (2kB + 32 bytes of read cache). + */ +struct inv_icm42607_fifo { + unsigned int on; + unsigned int en; + u32 period; + struct { + unsigned int gyro; + unsigned int accel; + unsigned int eff_gyro; + unsigned int eff_accel; + } watermark; + size_t count; + struct { + size_t gyro; + size_t accel; + size_t total; + } nb; + u8 data[2080] __aligned(IIO_DMA_MINALIGN); +}; + +/* FIFO data packet */ +struct inv_icm42607_fifo_sensor_data { + __be16 x; + __be16 y; + __be16 z; +}; + +#define INV_ICM42607_FIFO_DATA_INVALID -32768 + +static inline s16 inv_icm42607_fifo_get_sensor_data(__be16 d) +{ + return be16_to_cpu(d); +} + +static inline bool +inv_icm42607_fifo_is_data_valid(const struct inv_icm42607_fifo_sensor_data *s) +{ + s16 x, y, z; + + x = inv_icm42607_fifo_get_sensor_data(s->x); + y = inv_icm42607_fifo_get_sensor_data(s->y); + z = inv_icm42607_fifo_get_sensor_data(s->z); + + if (x == INV_ICM42607_FIFO_DATA_INVALID && + y == INV_ICM42607_FIFO_DATA_INVALID && + z == INV_ICM42607_FIFO_DATA_INVALID) + return false; + + return true; +} + +ssize_t inv_icm42607_fifo_decode_packet(const void *packet, const void **accel, + const void **gyro, const s8 **temp, + const void **timestamp, unsigned int *odr); + +extern const struct iio_buffer_setup_ops inv_icm42607_buffer_ops; + +int inv_icm42607_buffer_init(struct inv_icm42607_state *st); + +void inv_icm42607_buffer_update_fifo_period(struct inv_icm42607_state *st); + +int inv_icm42607_buffer_set_fifo_en(struct inv_icm42607_state *st, + unsigned int fifo_en); + +int inv_icm42607_buffer_update_watermark(struct inv_icm42607_state *st); + +int inv_icm42607_buffer_fifo_read(struct inv_icm42607_state *st, + unsigned int max); + +int inv_icm42607_buffer_fifo_parse(struct inv_icm42607_state *st); + +int inv_icm42607_buffer_hwfifo_flush(struct inv_icm42607_state *st, + unsigned int count); + +#endif diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c index af3784040b7a..7b9c05df820a 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -16,6 +16,7 @@ #include #include "inv_icm42607.h" +#include "inv_icm42607_buffer.h" static const struct regmap_range_cfg inv_icm42607_regmap_ranges[] = { { @@ -163,6 +164,7 @@ static int inv_icm42607_set_pwr_mgmt0(struct inv_icm42607_state *st, return 0; } + int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { @@ -370,6 +372,11 @@ int inv_icm42607_core_probe(struct regmap *regmap, int chip, if (ret) return ret; + /* Initialize buffer/FIFO handling */ + ret = inv_icm42607_buffer_init(st); + if (ret) + return ret; + ret = devm_pm_runtime_set_active_enabled(dev); if (ret) return ret; @@ -401,6 +408,13 @@ static int inv_icm42607_suspend(struct device *dev) if (pm_runtime_suspended(dev)) return 0; + if (st->fifo.on) { + ret = regmap_write(st->map, INV_ICM42607_REG_FIFO_CONFIG1, + INV_ICM42607_FIFO_CONFIG1_BYPASS); + if (ret) + return ret; + } + /* keep chip on and wake-up capable if APEX and wakeup on */ accel_dev = &st->indio_accel->dev; wakeup = st->apex.on && device_may_wakeup(accel_dev); @@ -432,6 +446,8 @@ static int inv_icm42607_suspend(struct device *dev) static int inv_icm42607_resume(struct device *dev) { struct inv_icm42607_state *st = dev_get_drvdata(dev); + struct inv_icm42607_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm42607_sensor_state *accel_st = iio_priv(st->indio_accel); struct device *accel_dev; bool wakeup; int ret; @@ -458,6 +474,16 @@ static int inv_icm42607_resume(struct device *dev) ret = inv_icm42607_set_pwr_mgmt0(st, st->suspended.gyro, st->suspended.accel, st->suspended.temp, NULL); + if (ret) + return ret; + + if (st->fifo.on) { + inv_sensors_timestamp_reset(&gyro_st->ts); + inv_sensors_timestamp_reset(&accel_st->ts); + ret = regmap_write(st->map, INV_ICM42607_REG_FIFO_CONFIG1, + INV_ICM42607_FIFO_CONFIG1_MODE); + } + return ret; } -- 2.43.0 From macroalpha82 at gmail.com Fri May 1 15:11:45 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:45 -0500 Subject: [PATCH V4 06/10] iio: imu: inv_icm42607: Add Temp Support in icm42607 In-Reply-To: <20260501221152.194251-1-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> Message-ID: <20260501221152.194251-7-macroalpha82@gmail.com> From: Chris Morgan Add functions for reading temperature sensor data. Signed-off-by: Chris Morgan --- drivers/iio/imu/inv_icm42607/Makefile | 1 + drivers/iio/imu/inv_icm42607/inv_icm42607.h | 3 + .../iio/imu/inv_icm42607/inv_icm42607_core.c | 17 ++++ .../iio/imu/inv_icm42607/inv_icm42607_temp.c | 81 +++++++++++++++++++ .../iio/imu/inv_icm42607/inv_icm42607_temp.h | 30 +++++++ 5 files changed, 132 insertions(+) create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h diff --git a/drivers/iio/imu/inv_icm42607/Makefile b/drivers/iio/imu/inv_icm42607/Makefile index 3c9d08509793..ccb8e007cdeb 100644 --- a/drivers/iio/imu/inv_icm42607/Makefile +++ b/drivers/iio/imu/inv_icm42607/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_INV_ICM42607) += inv-icm42607.o inv-icm42607-y += inv_icm42607_core.o inv-icm42607-y += inv_icm42607_buffer.o +inv-icm42607-y += inv_icm42607_temp.o obj-$(CONFIG_INV_ICM42607_I2C) += inv-icm42607-i2c.o inv-icm42607-i2c-y += inv_icm42607_i2c.o diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h index fd860685d0ad..05b5a246e384 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607.h +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h @@ -399,6 +399,9 @@ extern const struct dev_pm_ops inv_icm42607_pm_ops; u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr); +int inv_icm42607_set_temp_conf(struct inv_icm42607_state *st, bool enable, + unsigned int *sleep_ms); + int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c index 7b9c05df820a..77b9a633d31a 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -164,6 +164,23 @@ static int inv_icm42607_set_pwr_mgmt0(struct inv_icm42607_state *st, return 0; } +int inv_icm42607_set_temp_conf(struct inv_icm42607_state *st, bool enable, + unsigned int *sleep_ms) +{ + unsigned int val; + int ret; + + val = FIELD_PREP(INV_ICM42607_TEMP_CONFIG0_FILTER_MASK, + INV_ICM42607_FILTER_BW_34HZ); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_TEMP_CONFIG0, + INV_ICM42607_TEMP_CONFIG0_FILTER_MASK, val); + if (ret) + return ret; + + return inv_icm42607_set_pwr_mgmt0(st, st->conf.gyro.mode, + st->conf.accel.mode, enable, + sleep_ms); +} int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c new file mode 100644 index 000000000000..bcc11620c74c --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "inv_icm42607.h" +#include "inv_icm42607_temp.h" + +static int inv_icm42607_temp_read(struct inv_icm42607_state *st, s16 *temp) +{ + struct device *dev = regmap_get_device(st->map); + __be16 *raw; + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + guard(mutex)(&st->lock); + + ret = inv_icm42607_set_temp_conf(st, true, NULL); + if (ret) + return ret; + + raw = &st->buffer[0]; + ret = regmap_bulk_read(st->map, INV_ICM42607_REG_TEMP_DATA1, raw, sizeof(*raw)); + if (ret) + return ret; + + *temp = be16_to_cpup(raw); + if (*temp == INV_ICM42607_DATA_INVALID) + ret = -EINVAL; + + return ret; +} + +int inv_icm42607_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + s16 temp; + int ret; + + if (chan->type != IIO_TEMP) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42607_temp_read(st, &temp); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + *val = temp; + return IIO_VAL_INT; + /* + * T?C = (temp / 128) + 25 + * Tm?C = 1000 * ((temp * 100 / 12800) + 25) + * scale: 100000 / 12800 ~= 7.8125 + * offset: 25000 + */ + case IIO_CHAN_INFO_SCALE: + *val = 7; + *val2 = 812500; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + *val = 25000; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h b/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h new file mode 100644 index 000000000000..d0bd6c460ff2 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_temp.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#ifndef INV_ICM42607_TEMP_H_ +#define INV_ICM42607_TEMP_H_ + +#include + +#define INV_ICM42607_TEMP_CHAN(_index) \ +{ \ + .type = IIO_TEMP, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + }, \ +} + +int inv_icm42607_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask); + +#endif -- 2.43.0 From macroalpha82 at gmail.com Fri May 1 15:11:46 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:46 -0500 Subject: [PATCH V4 07/10] iio: imu: inv_icm42607: Add Accelerometer for icm42607 In-Reply-To: <20260501221152.194251-1-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> Message-ID: <20260501221152.194251-8-macroalpha82@gmail.com> From: Chris Morgan Add icm42607 accelerometer sensor for icm42607. Signed-off-by: Chris Morgan --- drivers/iio/imu/inv_icm42607/Makefile | 1 + drivers/iio/imu/inv_icm42607/inv_icm42607.h | 16 + .../iio/imu/inv_icm42607/inv_icm42607_accel.c | 686 ++++++++++++++++++ .../imu/inv_icm42607/inv_icm42607_buffer.c | 49 +- .../iio/imu/inv_icm42607/inv_icm42607_core.c | 61 ++ 5 files changed, 812 insertions(+), 1 deletion(-) create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c diff --git a/drivers/iio/imu/inv_icm42607/Makefile b/drivers/iio/imu/inv_icm42607/Makefile index ccb8e007cdeb..e908d77c4219 100644 --- a/drivers/iio/imu/inv_icm42607/Makefile +++ b/drivers/iio/imu/inv_icm42607/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_INV_ICM42607) += inv-icm42607.o inv-icm42607-y += inv_icm42607_core.o +inv-icm42607-y += inv_icm42607_accel.o inv-icm42607-y += inv_icm42607_buffer.o inv-icm42607-y += inv_icm42607_temp.o diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h index 05b5a246e384..5768e63d1dd2 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607.h +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h @@ -397,8 +397,16 @@ typedef int (*inv_icm42607_bus_setup)(struct inv_icm42607_state *); extern const struct regmap_config inv_icm42607_regmap_config; extern const struct dev_pm_ops inv_icm42607_pm_ops; +const struct iio_mount_matrix * +inv_icm42607_get_mount_matrix(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan); + u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr); +int inv_icm42607_set_accel_conf(struct inv_icm42607_state *st, + struct inv_icm42607_sensor_conf *conf, + unsigned int *sleep_ms); + int inv_icm42607_set_temp_conf(struct inv_icm42607_state *st, bool enable, unsigned int *sleep_ms); @@ -408,4 +416,12 @@ int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, int inv_icm42607_core_probe(struct regmap *regmap, int chip, inv_icm42607_bus_setup bus_setup); +struct iio_dev *inv_icm42607_accel_init(struct inv_icm42607_state *st); + +int inv_icm42607_accel_parse_fifo(struct iio_dev *indio_dev); + +void inv_icm42607_accel_handle_events(struct iio_dev *indio_dev, + unsigned int status2, unsigned int status3, + s64 timestamp); + #endif diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c new file mode 100644 index 000000000000..d998d8c94eb9 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "inv_icm42607.h" +#include "inv_icm42607_temp.h" +#include "inv_icm42607_buffer.h" + +#define INV_ICM42607_ACCEL_CHAN(_modifier, _index, _ext_info) \ +{ \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = _ext_info, \ +} + +#define INV_ICM42607_ACCEL_EVENT_CHAN(_modifier, _events, _events_nb) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .event_spec = _events, \ + .num_event_specs = _events_nb, \ + .scan_index = -1, \ + } + +enum inv_icm42607_accel_scan { + INV_ICM42607_ACCEL_SCAN_X, + INV_ICM42607_ACCEL_SCAN_Y, + INV_ICM42607_ACCEL_SCAN_Z, + INV_ICM42607_ACCEL_SCAN_TEMP, + INV_ICM42607_ACCEL_SCAN_TIMESTAMP, +}; + +static const char * const inv_icm42607_accel_power_mode_items[] = { + "low-noise", + "low-power", +}; + +static const int inv_icm42607_accel_power_mode_values[] = { + INV_ICM42607_SENSOR_MODE_LOW_NOISE, + INV_ICM42607_SENSOR_MODE_LOW_POWER, +}; + +static const int inv_icm42607_accel_filter_values[] = { + INV_ICM42607_FILTER_BW_25HZ, + INV_ICM42607_FILTER_AVG_16X, +}; + +static int inv_icm42607_accel_power_mode_set(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int idx) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_state *accel_st = iio_priv(indio_dev); + int power_mode, filter; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + if (idx >= ARRAY_SIZE(inv_icm42607_accel_power_mode_values)) + return -EINVAL; + + power_mode = inv_icm42607_accel_power_mode_values[idx]; + filter = inv_icm42607_accel_filter_values[idx]; + + guard(mutex)(&st->lock); + + /* cannot change if accel sensor is on */ + if (st->conf.accel.mode != INV_ICM42607_SENSOR_MODE_OFF) + return -EBUSY; + + /* prevent change if power mode is not supported by the ODR */ + switch (power_mode) { + case INV_ICM42607_SENSOR_MODE_LOW_NOISE: + if (st->conf.accel.odr >= INV_ICM42607_ODR_6_25HZ_LP) + return -EPERM; + break; + case INV_ICM42607_SENSOR_MODE_LOW_POWER: + default: + if (st->conf.accel.odr <= INV_ICM42607_ODR_800HZ) + return -EPERM; + break; + } + + accel_st->power_mode = power_mode; + accel_st->filter = filter; + + return 0; +} + +static int inv_icm42607_accel_power_mode_get(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_state *accel_st = iio_priv(indio_dev); + unsigned int idx; + int power_mode; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + guard(mutex)(&st->lock); + + /* if sensor is on, returns actual power mode and not configured one */ + switch (st->conf.accel.mode) { + case INV_ICM42607_SENSOR_MODE_LOW_POWER: + case INV_ICM42607_SENSOR_MODE_LOW_NOISE: + power_mode = st->conf.accel.mode; + break; + default: + power_mode = accel_st->power_mode; + break; + } + + for (idx = 0; idx < ARRAY_SIZE(inv_icm42607_accel_power_mode_values); ++idx) { + if (power_mode == inv_icm42607_accel_power_mode_values[idx]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm42607_accel_power_mode_values)) + return -EINVAL; + + return idx; +} + +static const struct iio_enum inv_icm42607_accel_power_mode_enum = { + .items = inv_icm42607_accel_power_mode_items, + .num_items = ARRAY_SIZE(inv_icm42607_accel_power_mode_items), + .set = inv_icm42607_accel_power_mode_set, + .get = inv_icm42607_accel_power_mode_get, +}; + +static const struct iio_chan_spec_ext_info inv_icm42607_accel_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm42607_get_mount_matrix), + IIO_ENUM_AVAILABLE("power_mode", IIO_SHARED_BY_TYPE, + &inv_icm42607_accel_power_mode_enum), + IIO_ENUM("power_mode", IIO_SHARED_BY_TYPE, + &inv_icm42607_accel_power_mode_enum), + { } +}; + +static const struct iio_chan_spec inv_icm42607_accel_channels[] = { + INV_ICM42607_ACCEL_CHAN(IIO_MOD_X, INV_ICM42607_ACCEL_SCAN_X, + inv_icm42607_accel_ext_infos), + INV_ICM42607_ACCEL_CHAN(IIO_MOD_Y, INV_ICM42607_ACCEL_SCAN_Y, + inv_icm42607_accel_ext_infos), + INV_ICM42607_ACCEL_CHAN(IIO_MOD_Z, INV_ICM42607_ACCEL_SCAN_Z, + inv_icm42607_accel_ext_infos), + INV_ICM42607_TEMP_CHAN(INV_ICM42607_ACCEL_SCAN_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(INV_ICM42607_ACCEL_SCAN_TIMESTAMP), +}; + +static const struct iio_event_spec inv_icm42607_motion_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), + }, +}; + +/* + * IIO buffer data: size must be a power of 2 and timestamp aligned + * 16 bytes: 6 bytes acceleration, 2 bytes temperature, 8 bytes timestamp + */ +struct inv_icm42607_accel_buffer { + struct inv_icm42607_fifo_sensor_data accel; + s16 temp; + aligned_s64 timestamp; +}; + +#define INV_ICM42607_SCAN_MASK_ACCEL_3AXIS \ + (BIT(INV_ICM42607_ACCEL_SCAN_X) | \ + BIT(INV_ICM42607_ACCEL_SCAN_Y) | \ + BIT(INV_ICM42607_ACCEL_SCAN_Z)) + +#define INV_ICM42607_SCAN_MASK_TEMP BIT(INV_ICM42607_ACCEL_SCAN_TEMP) + +static const unsigned long inv_icm42607_accel_scan_masks[] = { + INV_ICM42607_SCAN_MASK_ACCEL_3AXIS, + INV_ICM42607_SCAN_MASK_TEMP, + INV_ICM42607_SCAN_MASK_ACCEL_3AXIS | INV_ICM42607_SCAN_MASK_TEMP, + 0, +}; + +/* enable accelerometer sensor and FIFO write */ +static int inv_icm42607_accel_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + unsigned int fifo_en = 0; + unsigned int sleep_temp = 0; + unsigned int sleep_accel = 0; + unsigned int sleep; + int ret; + + mutex_lock(&st->lock); + + if (*scan_mask & INV_ICM42607_SCAN_MASK_TEMP) { + /* enable temp sensor */ + ret = inv_icm42607_set_temp_conf(st, true, &sleep_temp); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42607_SENSOR_TEMP; + } + + if (*scan_mask & INV_ICM42607_SCAN_MASK_ACCEL_3AXIS) { + /* enable accel sensor */ + conf.mode = accel_st->power_mode; + conf.filter = accel_st->filter; + ret = inv_icm42607_set_accel_conf(st, &conf, &sleep_accel); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42607_SENSOR_ACCEL; + } + + /* update data FIFO write */ + ret = inv_icm42607_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + +out_unlock: + mutex_unlock(&st->lock); + /* + * Choose the highest enable-delay time of the two sensors being + * enabled, and sleep for that amount of time. + */ + sleep = max(sleep_accel, sleep_temp); + msleep(sleep); + + return ret; +} + +static int inv_icm42607_accel_read_sensor(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + s16 *val) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_state *accel_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + unsigned int reg; + __be16 *data; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42607_REG_ACCEL_DATA_X1; + break; + case IIO_MOD_Y: + reg = INV_ICM42607_REG_ACCEL_DATA_Y1; + break; + case IIO_MOD_Z: + reg = INV_ICM42607_REG_ACCEL_DATA_Z1; + break; + default: + return -EINVAL; + } + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + guard(mutex)(&st->lock); + + /* enable accel sensor */ + conf.mode = accel_st->power_mode; + conf.filter = accel_st->filter; + ret = inv_icm42607_set_accel_conf(st, &conf, NULL); + if (ret) + return ret; + + /* read accel register data */ + data = (__be16 *)&st->buffer[0]; + ret = regmap_bulk_read(st->map, reg, data, sizeof(*data)); + if (ret) + return ret; + + *val = be16_to_cpup(data); + if (*val == INV_ICM42607_DATA_INVALID) + ret = -EINVAL; + + return ret; +} + +static const int inv_icm42607_accel_scale_nano[][2] = { + [INV_ICM42607_ACCEL_FS_16G] = { 0, 4788403 }, + [INV_ICM42607_ACCEL_FS_8G] = { 0, 2394202 }, + [INV_ICM42607_ACCEL_FS_4G] = { 0, 1197101 }, + [INV_ICM42607_ACCEL_FS_2G] = { 0, 598550 } +}; + +static int inv_icm42607_accel_read_scale(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + unsigned int idx; + + idx = st->conf.accel.fs; + + *val = inv_icm42607_accel_scale_nano[idx][0]; + *val2 = inv_icm42607_accel_scale_nano[idx][1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm42607_accel_write_scale(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + int ret; + size_t scales_len = ARRAY_SIZE(inv_icm42607_accel_scale_nano); + + for (idx = 0; idx < scales_len; idx++) { + if (val == inv_icm42607_accel_scale_nano[idx][0] && + val2 == inv_icm42607_accel_scale_nano[idx][1]) + break; + } + if (idx >= scales_len) + return -EINVAL; + + conf.fs = idx; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + guard(mutex)(&st->lock); + + ret = inv_icm42607_set_accel_conf(st, &conf, NULL); + + return ret; +} + +/* IIO format int + micro */ +static const int inv_icm42607_accel_odr[] = { + /* 1.5625Hz */ + 1, 562500, + /* 3.125Hz */ + 3, 125000, + /* 6.25Hz */ + 6, 250000, + /* 12.5Hz */ + 12, 500000, + /* 25Hz */ + 25, 0, + /* 50Hz */ + 50, 0, + /* 100Hz */ + 100, 0, + /* 200Hz */ + 200, 0, + /* 400Hz */ + 400, 0, + /* 800Hz */ + 800, 0, + /* 1600Hz */ + 1600, 0, +}; + +static const int inv_icm42607_accel_odr_conv[] = { + INV_ICM42607_ODR_1_5625HZ_LP, + INV_ICM42607_ODR_3_125HZ_LP, + INV_ICM42607_ODR_6_25HZ_LP, + INV_ICM42607_ODR_12_5HZ, + INV_ICM42607_ODR_25HZ, + INV_ICM42607_ODR_50HZ, + INV_ICM42607_ODR_100HZ, + INV_ICM42607_ODR_200HZ, + INV_ICM42607_ODR_400HZ, + INV_ICM42607_ODR_800HZ, + INV_ICM42607_ODR_1600HZ, +}; + +static int inv_icm42607_accel_read_odr(struct inv_icm42607_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + odr = st->conf.accel.odr; + + for (i = 0; i < ARRAY_SIZE(inv_icm42607_accel_odr_conv); ++i) { + if (inv_icm42607_accel_odr_conv[i] == odr) + break; + } + if (i >= ARRAY_SIZE(inv_icm42607_accel_odr_conv)) + return -EINVAL; + + *val = inv_icm42607_accel_odr[2 * i]; + *val2 = inv_icm42607_accel_odr[2 * i + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm42607_accel_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm42607_accel_odr); idx += 2) { + if (val == inv_icm42607_accel_odr[idx] && + val2 == inv_icm42607_accel_odr[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm42607_accel_odr)) + return -EINVAL; + + conf.odr = inv_icm42607_accel_odr_conv[idx / 2]; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + guard(mutex)(&st->lock); + + ret = inv_sensors_timestamp_update_odr(ts, inv_icm42607_odr_to_period(conf.odr), + iio_buffer_enabled(indio_dev)); + if (ret) + return ret; + + ret = inv_icm42607_set_accel_conf(st, &conf, NULL); + if (ret) + return ret; + + inv_icm42607_buffer_update_fifo_period(st); + inv_icm42607_buffer_update_watermark(st); + + return ret; +} + +static int inv_icm42607_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + s16 data; + int ret; + + switch (chan->type) { + case IIO_ACCEL: + break; + case IIO_TEMP: + return inv_icm42607_temp_read_raw(indio_dev, chan, val, val2, mask); + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42607_accel_read_sensor(indio_dev, chan, &data); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm42607_accel_read_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42607_accel_read_odr(st, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42607_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42607_accel_write_scale(indio_dev, val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42607_accel_write_odr(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42607_accel_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int inv_icm42607_accel_hwfifo_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + st->fifo.watermark.accel = val; + return inv_icm42607_buffer_update_watermark(st); +} + +static int inv_icm42607_accel_hwfifo_flush(struct iio_dev *indio_dev, + unsigned int count) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (count == 0) + return 0; + + guard(mutex)(&st->lock); + + ret = inv_icm42607_buffer_hwfifo_flush(st, count); + if (ret) + return ret; + + return st->fifo.nb.accel; +} + +static const struct iio_info inv_icm42607_accel_info = { + .read_raw = inv_icm42607_accel_read_raw, + .write_raw = inv_icm42607_accel_write_raw, + .write_raw_get_fmt = inv_icm42607_accel_write_raw_get_fmt, + .debugfs_reg_access = inv_icm42607_debugfs_reg, + .update_scan_mode = inv_icm42607_accel_update_scan_mode, + .hwfifo_set_watermark = inv_icm42607_accel_hwfifo_set_watermark, + .hwfifo_flush_to_buffer = inv_icm42607_accel_hwfifo_flush, +}; + +struct iio_dev *inv_icm42607_accel_init(struct inv_icm42607_state *st) +{ + struct device *dev = regmap_get_device(st->map); + const char *name; + struct inv_icm42607_sensor_state *accel_st; + struct inv_sensors_timestamp_chip ts_chip; + struct iio_dev *indio_dev; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-accel", st->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*accel_st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + accel_st = iio_priv(indio_dev); + + accel_st->power_mode = INV_ICM42607_SENSOR_MODE_LOW_POWER; + accel_st->filter = INV_ICM42607_FILTER_AVG_16X; + + /* + * clock period is 32kHz (31250ns) + * jitter is +/- 2% (20 per mille) + */ + ts_chip.clock_period = 31250; + ts_chip.jitter = 20; + ts_chip.init_period = inv_icm42607_odr_to_period(st->conf.accel.odr); + inv_sensors_timestamp_init(&accel_st->ts, &ts_chip); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; + indio_dev->info = &inv_icm42607_accel_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->channels = inv_icm42607_accel_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm42607_accel_channels); + indio_dev->available_scan_masks = inv_icm42607_accel_scan_masks; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &inv_icm42607_buffer_ops); + if (ret) + return ERR_PTR(ret); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + /* accel events are wakeup capable */ + ret = devm_device_init_wakeup(&indio_dev->dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} + +int inv_icm42607_accel_parse_fifo(struct iio_dev *indio_dev) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; + ssize_t i, size; + unsigned int no; + const void *accel, *gyro, *timestamp; + const int8_t *temp; + unsigned int odr; + int64_t ts_val; + struct inv_icm42607_accel_buffer buffer = { }; + + /* parse all fifo packets */ + for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) { + size = inv_icm42607_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + /* quit if error or FIFO is empty */ + if (size <= 0) + return size; + + /* skip packet if no accel data or data is invalid */ + if (accel == NULL || !inv_icm42607_fifo_is_data_valid(accel)) + continue; + + /* update odr */ + if (odr & INV_ICM42607_SENSOR_ACCEL) { + inv_sensors_timestamp_apply_odr(ts, st->fifo.period, + st->fifo.nb.total, no); + } + + memcpy(&buffer.accel, accel, sizeof(buffer.accel)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; + ts_val = inv_sensors_timestamp_pop(ts); + iio_push_to_buffers_with_ts(indio_dev, &buffer, + sizeof(buffer), ts_val); + } + + return 0; +} diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c index 5c67f6a37520..34698b429c2e 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c @@ -351,6 +351,7 @@ static int inv_icm42607_buffer_postdisable(struct iio_dev *indio_dev) struct device *dev = regmap_get_device(st->map); unsigned int sensor; unsigned int *watermark; + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; unsigned int sleep_temp = 0; unsigned int sleep_sensor = 0; unsigned int sleep; @@ -377,6 +378,15 @@ static int inv_icm42607_buffer_postdisable(struct iio_dev *indio_dev) if (ret) goto out_unlock; + conf.mode = INV_ICM42607_SENSOR_MODE_OFF; + ret = inv_icm42607_set_accel_conf(st, &conf, &sleep_sensor); + if (ret) + goto out_unlock; + + /* if FIFO is off, turn temperature off */ + if (!st->fifo.on) + ret = inv_icm42607_set_temp_conf(st, false, &sleep_temp); + out_unlock: mutex_unlock(&st->lock); @@ -456,9 +466,33 @@ int inv_icm42607_buffer_fifo_read(struct inv_icm42607_state *st, return 0; } +int inv_icm42607_buffer_fifo_parse(struct inv_icm42607_state *st) +{ + struct inv_icm42607_sensor_state *accel_st = iio_priv(st->indio_accel); + struct inv_sensors_timestamp *ts; + int ret; + + if (st->fifo.nb.total == 0) + return 0; + + /* handle accelerometer timestamp and FIFO data parsing */ + if (st->fifo.nb.accel > 0) { + ts = &accel_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_accel, + st->timestamp.accel); + ret = inv_icm42607_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; + } + + return 0; +} + int inv_icm42607_buffer_hwfifo_flush(struct inv_icm42607_state *st, unsigned int count) { + struct inv_icm42607_sensor_state *accel_st = iio_priv(st->indio_accel); + struct inv_sensors_timestamp *ts; s64 gyro_ts, accel_ts; int ret; @@ -466,8 +500,21 @@ int inv_icm42607_buffer_hwfifo_flush(struct inv_icm42607_state *st, accel_ts = iio_get_time_ns(st->indio_accel); ret = inv_icm42607_buffer_fifo_read(st, count); + if (ret) + return ret; - return ret; + if (st->fifo.nb.total == 0) + return 0; + + if (st->fifo.nb.accel > 0) { + ts = &accel_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts); + ret = inv_icm42607_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; + } + + return 0; } int inv_icm42607_buffer_init(struct inv_icm42607_state *st) diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c index 77b9a633d31a..5c2010d8256f 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -74,6 +74,15 @@ static const struct inv_icm42607_hw inv_icm42607_hw[INV_CHIP_NB] = { }, }; +const struct iio_mount_matrix * +inv_icm42607_get_mount_matrix(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + + return &st->orientation; +} + u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr) { static const u32 odr_periods[INV_ICM42607_ODR_NB] = { @@ -164,6 +173,53 @@ static int inv_icm42607_set_pwr_mgmt0(struct inv_icm42607_state *st, return 0; } +int inv_icm42607_set_accel_conf(struct inv_icm42607_state *st, + struct inv_icm42607_sensor_conf *conf, + unsigned int *sleep_ms) +{ + struct inv_icm42607_sensor_conf *oldconf = &st->conf.accel; + unsigned int val; + int ret; + + if (conf->mode < 0) + conf->mode = oldconf->mode; + if (conf->fs < 0) + conf->fs = oldconf->fs; + if (conf->odr < 0) + conf->odr = oldconf->odr; + if (conf->filter < 0) + conf->filter = oldconf->filter; + + if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { + val = FIELD_PREP(INV_ICM42607_ACCEL_CONFIG0_FS_SEL_MASK, conf->fs); + val |= FIELD_PREP(INV_ICM42607_ACCEL_CONFIG0_ODR_MASK, conf->odr); + ret = regmap_write(st->map, INV_ICM42607_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + if (conf->filter != oldconf->filter) { + if (conf->mode == INV_ICM42607_SENSOR_MODE_LOW_POWER) { + val = FIELD_PREP(INV_ICM42607_ACCEL_CONFIG1_AVG_MASK, conf->filter); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_ACCEL_CONFIG1, + INV_ICM42607_ACCEL_CONFIG1_AVG_MASK, val); + } else { + val = FIELD_PREP(INV_ICM42607_ACCEL_CONFIG1_FILTER_MASK, + conf->filter); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_ACCEL_CONFIG1, + INV_ICM42607_ACCEL_CONFIG1_FILTER_MASK, val); + } + if (ret) + return ret; + oldconf->filter = conf->filter; + } + + return inv_icm42607_set_pwr_mgmt0(st, st->conf.gyro.mode, conf->mode, + st->conf.temp_en, sleep_ms); +} + int inv_icm42607_set_temp_conf(struct inv_icm42607_state *st, bool enable, unsigned int *sleep_ms) { @@ -394,6 +450,11 @@ int inv_icm42607_core_probe(struct regmap *regmap, int chip, if (ret) return ret; + /* Initialize IIO device for Accel */ + st->indio_accel = inv_icm42607_accel_init(st); + if (IS_ERR(st->indio_accel)) + return PTR_ERR(st->indio_accel); + ret = devm_pm_runtime_set_active_enabled(dev); if (ret) return ret; -- 2.43.0 From macroalpha82 at gmail.com Fri May 1 15:11:47 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:47 -0500 Subject: [PATCH V4 08/10] iio: imu: inv_icm42607: Add Wake on Movement for icm42607 In-Reply-To: <20260501221152.194251-1-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> Message-ID: <20260501221152.194251-9-macroalpha82@gmail.com> From: Chris Morgan Add support for wake on movement for the icm42607 driver. Signed-off-by: Chris Morgan --- drivers/iio/imu/inv_icm42607/inv_icm42607.h | 3 + .../iio/imu/inv_icm42607/inv_icm42607_accel.c | 303 ++++++++++++++++++ .../iio/imu/inv_icm42607/inv_icm42607_core.c | 159 ++++++++- 3 files changed, 464 insertions(+), 1 deletion(-) diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h index 5768e63d1dd2..a0123e062820 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607.h +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h @@ -410,6 +410,9 @@ int inv_icm42607_set_accel_conf(struct inv_icm42607_state *st, int inv_icm42607_set_temp_conf(struct inv_icm42607_state *st, bool enable, unsigned int *sleep_ms); +int inv_icm42607_enable_wom(struct inv_icm42607_state *st); +int inv_icm42607_disable_wom(struct inv_icm42607_state *st); + int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c index d998d8c94eb9..d056c74dcbfe 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_accel.c @@ -173,6 +173,16 @@ static const struct iio_chan_spec_ext_info inv_icm42607_accel_ext_infos[] = { { } }; +/* WoM event: rising ROC */ +static const struct iio_event_spec inv_icm42607_wom_events[] = { + { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + static const struct iio_chan_spec inv_icm42607_accel_channels[] = { INV_ICM42607_ACCEL_CHAN(IIO_MOD_X, INV_ICM42607_ACCEL_SCAN_X, inv_icm42607_accel_ext_infos), @@ -182,6 +192,8 @@ static const struct iio_chan_spec inv_icm42607_accel_channels[] = { inv_icm42607_accel_ext_infos), INV_ICM42607_TEMP_CHAN(INV_ICM42607_ACCEL_SCAN_TEMP), IIO_CHAN_SOFT_TIMESTAMP(INV_ICM42607_ACCEL_SCAN_TIMESTAMP), + INV_ICM42607_ACCEL_EVENT_CHAN(IIO_MOD_X_OR_Y_OR_Z, inv_icm42607_wom_events, + ARRAY_SIZE(inv_icm42607_wom_events)), }; static const struct iio_event_spec inv_icm42607_motion_events[] = { @@ -319,6 +331,180 @@ static int inv_icm42607_accel_read_sensor(struct iio_dev *indio_dev, return ret; } +static unsigned int inv_icm42607_accel_convert_roc_to_wom(u64 roc, + int accel_hz, int accel_uhz) +{ + /* 1000/256mg per LSB converted in ?m/s? */ + const unsigned int convert = (9807U * (MICRO / MILLI)) / 256U; + u64 value; + u64 freq_uhz; + + /* return 0 only if roc is 0 */ + if (roc == 0) + return 0; + + freq_uhz = (u64)accel_hz * MICRO + (u64)accel_uhz; + value = div64_u64(roc * MICRO, freq_uhz * (u64)convert); + + /* limit value to 8 bits and prevent 0 */ + return clamp(value, 1, 255); +} + +static u64 inv_icm42607_accel_convert_wom_to_roc(unsigned int threshold, + int accel_hz, int accel_uhz) +{ + /* 1000/256mg per LSB converted in ?m/s? */ + const unsigned int convert = (9807U * (MICRO / MILLI)) / 256U; + u64 value; + u64 freq_uhz; + + value = threshold * convert; + freq_uhz = (u64)accel_hz * MICRO + (u64)accel_uhz; + + /* compute the differential by multiplying by the frequency */ + return div_u64(value * freq_uhz, MICRO); +} + +static int inv_icm42607_accel_set_wom_threshold(struct inv_icm42607_state *st, + u64 value, + int accel_hz, int accel_uhz) +{ + unsigned int threshold; + int ret; + + /* convert roc to wom threshold and convert back to handle clipping */ + threshold = inv_icm42607_accel_convert_roc_to_wom(value, accel_hz, accel_uhz); + value = inv_icm42607_accel_convert_wom_to_roc(threshold, accel_hz, accel_uhz); + + dev_dbg(regmap_get_device(st->map), "wom_threshold: 0x%x\n", threshold); + + /* set accel WoM threshold for the 3 axes */ + st->buffer[0] = threshold; + st->buffer[1] = threshold; + st->buffer[2] = threshold; + ret = regmap_bulk_write(st->map, INV_ICM42607_REG_ACCEL_WOM_X_THR, st->buffer, 3); + if (ret) + return ret; + + st->apex.wom.value = value; + + return 0; +} + +static int _inv_icm42607_accel_enable_wom(struct iio_dev *indio_dev) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + unsigned int sleep_ms = 0; + int ret; + + scoped_guard(mutex, &st->lock) { + /* turn on accel sensor */ + conf.mode = accel_st->power_mode; + conf.filter = accel_st->filter; + ret = inv_icm42607_set_accel_conf(st, &conf, &sleep_ms); + if (ret) + return ret; + } + + if (sleep_ms) + msleep(sleep_ms); + + scoped_guard(mutex, &st->lock) { + ret = inv_icm42607_enable_wom(st); + if (ret) + return ret; + st->apex.on++; + st->apex.wom.enable = true; + } + + return 0; +} + +static int inv_icm42607_accel_enable_wom(struct iio_dev *indio_dev) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct device *pdev = regmap_get_device(st->map); + int ret; + + ret = pm_runtime_resume_and_get(pdev); + if (ret) + return ret; + + ret = _inv_icm42607_accel_enable_wom(indio_dev); + if (ret) { + pm_runtime_mark_last_busy(pdev); + pm_runtime_put_autosuspend(pdev); + return ret; + } + + return 0; +} + +static int _inv_icm42607_accel_disable_wom(struct iio_dev *indio_dev) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + unsigned int sleep_ms = 0; + int ret; + + scoped_guard(mutex, &st->lock) { + /* + * Consider that turning off WoM is always working to avoid + * blocking the chip in on mode and prevent going back to sleep. + * If there is an error, the chip will anyway go back to sleep + * and the feature will not work anymore. + */ + st->apex.wom.enable = false; + st->apex.on--; + ret = inv_icm42607_disable_wom(st); + if (ret) + return ret; + /* turn off accel sensor if not used */ + if (!st->apex.on && !iio_buffer_enabled(indio_dev)) { + conf.mode = INV_ICM42607_SENSOR_MODE_OFF; + ret = inv_icm42607_set_accel_conf(st, &conf, &sleep_ms); + if (ret) + return ret; + } + } + + if (sleep_ms) + msleep(sleep_ms); + + return 0; +} + +static int inv_icm42607_accel_disable_wom(struct iio_dev *indio_dev) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct device *pdev = regmap_get_device(st->map); + int ret; + + ret = _inv_icm42607_accel_disable_wom(indio_dev); + + pm_runtime_mark_last_busy(pdev); + pm_runtime_put_autosuspend(pdev); + + return ret; +} + +void inv_icm42607_accel_handle_events(struct iio_dev *indio_dev, + unsigned int status2, unsigned int status3, + s64 timestamp) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + u64 ev_code; + + /* handle WoM event */ + if (st->apex.wom.enable && (status2 & INV_ICM42607_INT_STATUS2_WOM_INT)) { + ev_code = IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_ROC, IIO_EV_DIR_RISING); + iio_push_event(indio_dev, ev_code, timestamp); + } +} + static const int inv_icm42607_accel_scale_nano[][2] = { [INV_ICM42607_ACCEL_FS_16G] = { 0, 4788403 }, [INV_ICM42607_ACCEL_FS_8G] = { 0, 2394202 }, @@ -464,6 +650,9 @@ static int inv_icm42607_accel_write_odr(struct iio_dev *indio_dev, return ret; ret = inv_icm42607_set_accel_conf(st, &conf, NULL); + if (ret) + return ret; + ret = inv_icm42607_accel_set_wom_threshold(st, st->apex.wom.value, val, val2); if (ret) return ret; @@ -578,6 +767,116 @@ static int inv_icm42607_accel_hwfifo_flush(struct iio_dev *indio_dev, return st->fifo.nb.accel; } +static int inv_icm42607_accel_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 inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + + /* handle only WoM (roc rising) event */ + if (type != IIO_EV_TYPE_ROC || dir != IIO_EV_DIR_RISING) + return -EINVAL; + + guard(mutex)(&st->lock); + + return st->apex.wom.enable ? 1 : 0; +} + +static int inv_icm42607_accel_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + + /* handle only WoM (roc rising) event */ + if (type != IIO_EV_TYPE_ROC || dir != IIO_EV_DIR_RISING) + return -EINVAL; + + scoped_guard(mutex, &st->lock) { + if (st->apex.wom.enable == state) + return 0; + } + + if (state) + return inv_icm42607_accel_enable_wom(indio_dev); + + return inv_icm42607_accel_disable_wom(indio_dev); +} + +static int inv_icm42607_accel_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + u32 rem; + + /* handle only WoM (roc rising) event value */ + if (type != IIO_EV_TYPE_ROC || dir != IIO_EV_DIR_RISING) + return -EINVAL; + + guard(mutex)(&st->lock); + + /* return value in micro */ + *val = div_u64_rem(st->apex.wom.value, MICRO, &rem); + *val2 = rem; + return IIO_VAL_INT_PLUS_MICRO; +} + +static int _inv_icm42607_accel_wom_value(struct inv_icm42607_state *st, + int val, int val2) +{ + u64 value; + unsigned int accel_hz, accel_uhz; + int ret; + + guard(mutex)(&st->lock); + + ret = inv_icm42607_accel_read_odr(st, &accel_hz, &accel_uhz); + if (ret < 0) + return ret; + + value = (u64)val * MICRO + (u64)val2; + + return inv_icm42607_accel_set_wom_threshold(st, value, + accel_hz, accel_uhz); +} + +static int inv_icm42607_accel_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + int ret; + + /* handle only WoM (roc rising) event value */ + if (type != IIO_EV_TYPE_ROC || dir != IIO_EV_DIR_RISING) + return -EINVAL; + + if (val < 0 || val2 < 0) + return -EINVAL; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = _inv_icm42607_accel_wom_value(st, val, val2); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + static const struct iio_info inv_icm42607_accel_info = { .read_raw = inv_icm42607_accel_read_raw, .write_raw = inv_icm42607_accel_write_raw, @@ -586,6 +885,10 @@ static const struct iio_info inv_icm42607_accel_info = { .update_scan_mode = inv_icm42607_accel_update_scan_mode, .hwfifo_set_watermark = inv_icm42607_accel_hwfifo_set_watermark, .hwfifo_flush_to_buffer = inv_icm42607_accel_hwfifo_flush, + .read_event_config = inv_icm42607_accel_read_event_config, + .write_event_config = inv_icm42607_accel_write_event_config, + .read_event_value = inv_icm42607_accel_read_event_value, + .write_event_value = inv_icm42607_accel_write_event_value, }; struct iio_dev *inv_icm42607_accel_init(struct inv_icm42607_state *st) diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c index 5c2010d8256f..480fe1741a04 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -238,6 +238,39 @@ int inv_icm42607_set_temp_conf(struct inv_icm42607_state *st, bool enable, sleep_ms); } +int inv_icm42607_enable_wom(struct inv_icm42607_state *st) +{ + int ret; + + /* enable WoM hardware */ + ret = regmap_write(st->map, INV_ICM42607_REG_WOM_CONFIG, + FIELD_PREP(INV_ICM42607_WOM_CONFIG_INT_DUR_MASK, 1) | + INV_ICM42607_WOM_CONFIG_MODE | + INV_ICM42607_WOM_CONFIG_EN); + if (ret) + return ret; + + /* enable WoM interrupt */ + return regmap_set_bits(st->map, INV_ICM42607_REG_INT_SOURCE1, + INV_ICM42607_INT_SOURCE1_WOM_INT1_EN); +} + +int inv_icm42607_disable_wom(struct inv_icm42607_state *st) +{ + int ret; + + /* disable WoM interrupt */ + ret = regmap_clear_bits(st->map, INV_ICM42607_REG_INT_SOURCE1, + INV_ICM42607_INT_SOURCE1_WOM_INT1_EN); + if (ret) + return ret; + + /* disable WoM hardware */ + return regmap_clear_bits(st->map, INV_ICM42607_REG_WOM_CONFIG, + INV_ICM42607_WOM_CONFIG_EN); +} + + int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval) { @@ -361,6 +394,109 @@ static int inv_icm42607_setup(struct inv_icm42607_state *st, return inv_icm42607_set_conf(st, hw->conf); } +static irqreturn_t inv_icm42607_irq_timestamp(int irq, void *_data) +{ + struct inv_icm42607_state *st = _data; + + st->timestamp.gyro = iio_get_time_ns(st->indio_gyro); + st->timestamp.accel = iio_get_time_ns(st->indio_accel); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t inv_icm42607_irq_handler(int irq, void *_data) +{ + struct inv_icm42607_state *st = _data; + struct device *dev = regmap_get_device(st->map); + unsigned int status; + int ret; + + mutex_lock(&st->lock); + + if (st->apex.on) { + unsigned int status2, status3; + + /* read INT_STATUS2 and INT_STATUS3 in 1 operation */ + ret = regmap_bulk_read(st->map, INV_ICM42607_REG_INT_STATUS2, st->buffer, 2); + if (ret) + goto out_unlock; + status2 = st->buffer[0]; + status3 = st->buffer[1]; + inv_icm42607_accel_handle_events(st->indio_accel, status2, status3, + st->timestamp.accel); + } + + ret = regmap_read(st->map, INV_ICM42607_REG_INT_STATUS, &status); + if (ret) + goto out_unlock; + + if (status & INV_ICM42607_INT_STATUS_FIFO_FULL) + dev_warn(dev, "FIFO full data lost!\n"); + + if (status & INV_ICM42607_INT_STATUS_FIFO_THS) { + ret = inv_icm42607_buffer_fifo_read(st, 0); + if (ret) { + dev_err(dev, "FIFO read error %d\n", ret); + goto out_unlock; + } + ret = inv_icm42607_buffer_fifo_parse(st); + if (ret) + dev_err(dev, "FIFO parsing error %d\n", ret); + } + +out_unlock: + mutex_unlock(&st->lock); + return IRQ_HANDLED; +} + +/** + * inv_icm42607_irq_init() - initialize int pin and interrupt handler + * @st: driver internal state + * @irq: irq number + * @irq_type: irq trigger type + * @open_drain: true if irq is open drain, false for push-pull + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_icm42607_irq_init(struct inv_icm42607_state *st, int irq, + int irq_type, bool open_drain) +{ + struct device *dev = regmap_get_device(st->map); + unsigned int val = 0; + int ret; + + switch (irq_type) { + case IRQF_TRIGGER_RISING: + case IRQF_TRIGGER_HIGH: + val = INV_ICM42607_INT_CONFIG_INT1_ACTIVE_HIGH; + break; + default: + val = INV_ICM42607_INT_CONFIG_INT1_ACTIVE_LOW; + break; + } + + switch (irq_type) { + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_HIGH: + val |= INV_ICM42607_INT_CONFIG_INT1_LATCHED; + break; + default: + break; + } + + if (!open_drain) + val |= INV_ICM42607_INT_CONFIG_INT1_PUSH_PULL; + + ret = regmap_write(st->map, INV_ICM42607_REG_INT_CONFIG, val); + if (ret) + return ret; + + irq_type |= IRQF_ONESHOT; + return devm_request_threaded_irq(dev, irq, inv_icm42607_irq_timestamp, + inv_icm42607_irq_handler, irq_type, + st->name, st); +} + static int inv_icm42607_enable_vddio_reg(struct inv_icm42607_state *st) { int ret; @@ -387,7 +523,7 @@ int inv_icm42607_core_probe(struct regmap *regmap, int chip, struct device *dev = regmap_get_device(regmap); struct fwnode_handle *fwnode = dev_fwnode(dev); struct inv_icm42607_state *st; - int irq; + int irq, irq_type; bool open_drain; int ret; @@ -402,6 +538,9 @@ int inv_icm42607_core_probe(struct regmap *regmap, int chip, if (irq < 0) return dev_err_probe(dev, irq, "error missing INT1 interrupt\n"); + irq_type = irq_get_trigger_type(irq); + if (!irq_type) + irq_type = IRQF_TRIGGER_FALLING; open_drain = device_property_read_bool(dev, "drive-open-drain"); st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); @@ -455,6 +594,11 @@ int inv_icm42607_core_probe(struct regmap *regmap, int chip, if (IS_ERR(st->indio_accel)) return PTR_ERR(st->indio_accel); + /* Initialize interrupt handling */ + ret = inv_icm42607_irq_init(st, irq, irq_type, open_drain); + if (ret) + return ret; + ret = devm_pm_runtime_set_active_enabled(dev); if (ret) return ret; @@ -502,6 +646,12 @@ static int inv_icm42607_suspend(struct device *dev) enable_irq_wake(st->irq); disable_irq(st->irq); } else { + /* disable APEX features and accel if wakeup disabled */ + if (st->apex.wom.enable) { + ret = inv_icm42607_disable_wom(st); + if (ret) + return ret; + } accel_conf = INV_ICM42607_SENSOR_MODE_OFF; } @@ -555,6 +705,13 @@ static int inv_icm42607_resume(struct device *dev) if (ret) return ret; + /* restore APEX features if disabled */ + if (!wakeup && st->apex.wom.enable) { + ret = inv_icm42607_enable_wom(st); + if (ret) + return ret; + } + if (st->fifo.on) { inv_sensors_timestamp_reset(&gyro_st->ts); inv_sensors_timestamp_reset(&accel_st->ts); -- 2.43.0 From macroalpha82 at gmail.com Fri May 1 15:11:49 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:49 -0500 Subject: [PATCH V4 10/10] arm64: dts: rockchip: Add icm42607p IMU for RG-DS In-Reply-To: <20260501221152.194251-1-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> Message-ID: <20260501221152.194251-11-macroalpha82@gmail.com> From: Chris Morgan Add the Invensense ICM42607P IMU for the Anbernic RG-DS. Mount-matrix was tested with iio-sensor-proxy and reports correct orientation. Signed-off-by: Chris Morgan --- .../dts/rockchip/rk3568-anbernic-rg-ds.dts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/rockchip/rk3568-anbernic-rg-ds.dts b/arch/arm64/boot/dts/rockchip/rk3568-anbernic-rg-ds.dts index 8d906ab02c5f..875ca884deca 100644 --- a/arch/arm64/boot/dts/rockchip/rk3568-anbernic-rg-ds.dts +++ b/arch/arm64/boot/dts/rockchip/rk3568-anbernic-rg-ds.dts @@ -871,7 +871,18 @@ aw87391_pa_r: audio-codec at 5b { sound-name-prefix = "Right Amp"; }; - /* invensense,icm42607p at 0x68 */ + icm42607p: imu at 68 { + compatible = "invensense,icm42607p"; + reg = <0x68>; + interrupt-names = "INT1"; + interrupt-parent = <&gpio0>; + interrupts = ; + mount-matrix = "-1", "0", "0", + "0", "1", "0", + "0", "0", "-1"; + pinctrl-0 = <&accel_irq>; + pinctrl-names = "default"; + }; }; &i2c3 { @@ -932,6 +943,13 @@ &i2s1_8ch { }; &pinctrl { + accel { + accel_irq: accel-irq { + rockchip,pins = + <0 RK_PD6 RK_FUNC_GPIO &pcfg_pull_up>; + }; + }; + gpio-keys { vol_keys_l: vol-keys_l { rockchip,pins = -- 2.43.0 From macroalpha82 at gmail.com Fri May 1 15:11:48 2026 From: macroalpha82 at gmail.com (Chris Morgan) Date: Fri, 1 May 2026 17:11:48 -0500 Subject: [PATCH V4 09/10] iio: imu: inv_icm42607: Add Gyroscope to icm42607 In-Reply-To: <20260501221152.194251-1-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> Message-ID: <20260501221152.194251-10-macroalpha82@gmail.com> From: Chris Morgan Add gyroscope functions to the icm42607 driver. Signed-off-by: Chris Morgan --- drivers/iio/imu/inv_icm42607/Makefile | 1 + drivers/iio/imu/inv_icm42607/inv_icm42607.h | 8 + .../imu/inv_icm42607/inv_icm42607_buffer.c | 25 +- .../iio/imu/inv_icm42607/inv_icm42607_core.c | 48 ++ .../iio/imu/inv_icm42607/inv_icm42607_gyro.c | 536 ++++++++++++++++++ 5 files changed, 617 insertions(+), 1 deletion(-) create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_gyro.c diff --git a/drivers/iio/imu/inv_icm42607/Makefile b/drivers/iio/imu/inv_icm42607/Makefile index e908d77c4219..fc66e580fe99 100644 --- a/drivers/iio/imu/inv_icm42607/Makefile +++ b/drivers/iio/imu/inv_icm42607/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_INV_ICM42607) += inv-icm42607.o inv-icm42607-y += inv_icm42607_core.o +inv-icm42607-y += inv_icm42607_gyro.o inv-icm42607-y += inv_icm42607_accel.o inv-icm42607-y += inv_icm42607_buffer.o inv-icm42607-y += inv_icm42607_temp.o diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h index a0123e062820..02715dded326 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607.h +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h @@ -407,6 +407,10 @@ int inv_icm42607_set_accel_conf(struct inv_icm42607_state *st, struct inv_icm42607_sensor_conf *conf, unsigned int *sleep_ms); +int inv_icm42607_set_gyro_conf(struct inv_icm42607_state *st, + struct inv_icm42607_sensor_conf *conf, + unsigned int *sleep_ms); + int inv_icm42607_set_temp_conf(struct inv_icm42607_state *st, bool enable, unsigned int *sleep_ms); @@ -419,6 +423,10 @@ int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, int inv_icm42607_core_probe(struct regmap *regmap, int chip, inv_icm42607_bus_setup bus_setup); +struct iio_dev *inv_icm42607_gyro_init(struct inv_icm42607_state *st); + +int inv_icm42607_gyro_parse_fifo(struct iio_dev *indio_dev); + struct iio_dev *inv_icm42607_accel_init(struct inv_icm42607_state *st); int inv_icm42607_accel_parse_fifo(struct iio_dev *indio_dev); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c index 34698b429c2e..1017439fa65c 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_buffer.c @@ -379,7 +379,10 @@ static int inv_icm42607_buffer_postdisable(struct iio_dev *indio_dev) goto out_unlock; conf.mode = INV_ICM42607_SENSOR_MODE_OFF; - ret = inv_icm42607_set_accel_conf(st, &conf, &sleep_sensor); + if (sensor == INV_ICM42607_SENSOR_GYRO) + ret = inv_icm42607_set_gyro_conf(st, &conf, &sleep_sensor); + else + ret = inv_icm42607_set_accel_conf(st, &conf, &sleep_sensor); if (ret) goto out_unlock; @@ -468,6 +471,7 @@ int inv_icm42607_buffer_fifo_read(struct inv_icm42607_state *st, int inv_icm42607_buffer_fifo_parse(struct inv_icm42607_state *st) { + struct inv_icm42607_sensor_state *gyro_st = iio_priv(st->indio_gyro); struct inv_icm42607_sensor_state *accel_st = iio_priv(st->indio_accel); struct inv_sensors_timestamp *ts; int ret; @@ -475,6 +479,16 @@ int inv_icm42607_buffer_fifo_parse(struct inv_icm42607_state *st) if (st->fifo.nb.total == 0) return 0; + /* handle gyroscope timestamp and FIFO data parsing */ + if (st->fifo.nb.gyro > 0) { + ts = &gyro_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_gyro, + st->timestamp.gyro); + ret = inv_icm42607_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + /* handle accelerometer timestamp and FIFO data parsing */ if (st->fifo.nb.accel > 0) { ts = &accel_st->ts; @@ -491,6 +505,7 @@ int inv_icm42607_buffer_fifo_parse(struct inv_icm42607_state *st) int inv_icm42607_buffer_hwfifo_flush(struct inv_icm42607_state *st, unsigned int count) { + struct inv_icm42607_sensor_state *gyro_st = iio_priv(st->indio_gyro); struct inv_icm42607_sensor_state *accel_st = iio_priv(st->indio_accel); struct inv_sensors_timestamp *ts; s64 gyro_ts, accel_ts; @@ -506,6 +521,14 @@ int inv_icm42607_buffer_hwfifo_flush(struct inv_icm42607_state *st, if (st->fifo.nb.total == 0) return 0; + if (st->fifo.nb.gyro > 0) { + ts = &gyro_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts); + ret = inv_icm42607_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + if (st->fifo.nb.accel > 0) { ts = &accel_st->ts; inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts); diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c index 480fe1741a04..eb6513c378a8 100644 --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c @@ -220,6 +220,49 @@ int inv_icm42607_set_accel_conf(struct inv_icm42607_state *st, st->conf.temp_en, sleep_ms); } +int inv_icm42607_set_gyro_conf(struct inv_icm42607_state *st, + struct inv_icm42607_sensor_conf *conf, + unsigned int *sleep_ms) +{ + struct inv_icm42607_sensor_conf *oldconf = &st->conf.gyro; + unsigned int val; + int ret; + + if (conf->mode < 0) + conf->mode = oldconf->mode; + if (conf->fs < 0) + conf->fs = oldconf->fs; + if (conf->odr < 0) + conf->odr = oldconf->odr; + if (conf->filter < 0) + conf->filter = oldconf->filter; + + if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { + val = FIELD_PREP(INV_ICM42607_GYRO_CONFIG0_FS_SEL_MASK, + conf->fs); + val |= FIELD_PREP(INV_ICM42607_GYRO_CONFIG0_ODR_MASK, + conf->odr); + ret = regmap_write(st->map, INV_ICM42607_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + if (conf->filter != oldconf->filter) { + val = FIELD_PREP(INV_ICM42607_GYRO_CONFIG1_FILTER_MASK, + conf->filter); + ret = regmap_update_bits(st->map, INV_ICM42607_REG_GYRO_CONFIG1, + INV_ICM42607_GYRO_CONFIG1_FILTER_MASK, val); + if (ret) + return ret; + oldconf->filter = conf->filter; + } + + return inv_icm42607_set_pwr_mgmt0(st, conf->mode, st->conf.accel.mode, + st->conf.temp_en, sleep_ms); +} + int inv_icm42607_set_temp_conf(struct inv_icm42607_state *st, bool enable, unsigned int *sleep_ms) { @@ -589,6 +632,11 @@ int inv_icm42607_core_probe(struct regmap *regmap, int chip, if (ret) return ret; + /* Initialize IIO device for Gyro */ + st->indio_gyro = inv_icm42607_gyro_init(st); + if (IS_ERR(st->indio_gyro)) + return PTR_ERR(st->indio_gyro); + /* Initialize IIO device for Accel */ st->indio_accel = inv_icm42607_accel_init(st); if (IS_ERR(st->indio_accel)) diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_gyro.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_gyro.c new file mode 100644 index 000000000000..4da012bc3947 --- /dev/null +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_gyro.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 InvenSense, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "inv_icm42607.h" +#include "inv_icm42607_temp.h" +#include "inv_icm42607_buffer.h" + +#define INV_ICM42607_GYRO_CHAN(_modifier, _index, _ext_info) \ +{ \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = _ext_info, \ +} + +enum inv_icm42607_gyro_scan { + INV_ICM42607_GYRO_SCAN_X, + INV_ICM42607_GYRO_SCAN_Y, + INV_ICM42607_GYRO_SCAN_Z, + INV_ICM42607_GYRO_SCAN_TEMP, + INV_ICM42607_GYRO_SCAN_TIMESTAMP, +}; + +static const struct iio_chan_spec_ext_info inv_icm42607_gyro_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm42607_get_mount_matrix), + { }, +}; + +static const struct iio_chan_spec inv_icm42607_gyro_channels[] = { + INV_ICM42607_GYRO_CHAN(IIO_MOD_X, INV_ICM42607_GYRO_SCAN_X, + inv_icm42607_gyro_ext_infos), + INV_ICM42607_GYRO_CHAN(IIO_MOD_Y, INV_ICM42607_GYRO_SCAN_Y, + inv_icm42607_gyro_ext_infos), + INV_ICM42607_GYRO_CHAN(IIO_MOD_Z, INV_ICM42607_GYRO_SCAN_Z, + inv_icm42607_gyro_ext_infos), + INV_ICM42607_TEMP_CHAN(INV_ICM42607_GYRO_SCAN_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(INV_ICM42607_GYRO_SCAN_TIMESTAMP), +}; + +/* + * IIO buffer data: size must be a power of 2 and timestamp aligned + * 16 bytes: 6 bytes angular velocity, 2 bytes temperature, 8 bytes timestamp + */ +struct inv_icm42607_gyro_buffer { + struct inv_icm42607_fifo_sensor_data gyro; + s16 temp; + aligned_s64 timestamp; +}; + +#define INV_ICM42607_SCAN_MASK_GYRO_3AXIS \ + (BIT(INV_ICM42607_GYRO_SCAN_X) | \ + BIT(INV_ICM42607_GYRO_SCAN_Y) | \ + BIT(INV_ICM42607_GYRO_SCAN_Z)) + +#define INV_ICM42607_SCAN_MASK_TEMP BIT(INV_ICM42607_GYRO_SCAN_TEMP) + +static const unsigned long inv_icm42607_gyro_scan_masks[] = { + INV_ICM42607_SCAN_MASK_GYRO_3AXIS, + INV_ICM42607_SCAN_MASK_TEMP, + INV_ICM42607_SCAN_MASK_GYRO_3AXIS | INV_ICM42607_SCAN_MASK_TEMP, + 0, +}; + +/* enable gyroscope sensor and FIFO write */ +static int inv_icm42607_gyro_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + unsigned int fifo_en = 0; + unsigned int sleep_gyro = 0; + unsigned int sleep_temp = 0; + unsigned int sleep; + int ret; + + mutex_lock(&st->lock); + + if (*scan_mask & INV_ICM42607_SCAN_MASK_TEMP) { + /* enable temp sensor */ + ret = inv_icm42607_set_temp_conf(st, true, &sleep_temp); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42607_SENSOR_TEMP; + } + + if (*scan_mask & INV_ICM42607_SCAN_MASK_GYRO_3AXIS) { + /* enable gyro sensor */ + conf.mode = INV_ICM42607_SENSOR_MODE_LOW_NOISE; + ret = inv_icm42607_set_gyro_conf(st, &conf, &sleep_gyro); + if (ret) + goto out_unlock; + fifo_en |= INV_ICM42607_SENSOR_GYRO; + } + + /* update data FIFO write */ + ret = inv_icm42607_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + if (ret) + goto out_unlock; + +out_unlock: + mutex_unlock(&st->lock); + /* sleep maximum required time */ + sleep = max(sleep_gyro, sleep_temp); + if (sleep) + msleep(sleep); + return ret; +} + +static int inv_icm42607_gyro_read_sensor(struct inv_icm42607_state *st, + struct iio_chan_spec const *chan, + s16 *val) +{ + struct device *dev = regmap_get_device(st->map); + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + unsigned int reg; + __be16 *data; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM42607_REG_GYRO_DATA_X1; + break; + case IIO_MOD_Y: + reg = INV_ICM42607_REG_GYRO_DATA_Y1; + break; + case IIO_MOD_Z: + reg = INV_ICM42607_REG_GYRO_DATA_Z1; + break; + default: + return -EINVAL; + } + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + guard(mutex)(&st->lock); + + /* enable gyro sensor */ + conf.mode = INV_ICM42607_SENSOR_MODE_LOW_NOISE; + ret = inv_icm42607_set_gyro_conf(st, &conf, NULL); + if (ret) + return ret; + + /* read gyro register data */ + data = (__be16 *)&st->buffer[0]; + ret = regmap_bulk_read(st->map, reg, data, sizeof(*data)); + if (ret) + return ret; + + *val = (s16)be16_to_cpup(data); + if (*val == INV_ICM42607_DATA_INVALID) + ret = -EINVAL; + + return ret; +} + +static const int inv_icm42607_gyro_scale_nano[][2] = { + [INV_ICM42607_GYRO_FS_2000DPS] = { 0, 1065264 }, + [INV_ICM42607_GYRO_FS_1000DPS] = { 0, 532632 }, + [INV_ICM42607_GYRO_FS_500DPS] = { 0, 266316 }, + [INV_ICM42607_GYRO_FS_250DPS] = { 0, 133158 }, +}; + +static int inv_icm42607_gyro_read_scale(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + unsigned int idx; + + idx = st->conf.gyro.fs; + + *val = inv_icm42607_gyro_scale_nano[idx][0]; + *val2 = inv_icm42607_gyro_scale_nano[idx][1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm42607_gyro_write_scale(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + int ret; + size_t scales_len = ARRAY_SIZE(inv_icm42607_gyro_scale_nano); + + for (idx = 0; idx < scales_len; idx++) { + if (val == inv_icm42607_gyro_scale_nano[idx][0] && + val2 == inv_icm42607_gyro_scale_nano[idx][1]) + break; + } + if (idx >= scales_len) + return -EINVAL; + + conf.fs = idx; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + guard(mutex)(&st->lock); + + ret = inv_icm42607_set_gyro_conf(st, &conf, NULL); + + return ret; +} + +/* IIO format int + micro */ +static const int inv_icm42607_gyro_odr[] = { + /* 12.5Hz */ + 12, 500000, + /* 25Hz */ + 25, 0, + /* 50Hz */ + 50, 0, + /* 100Hz */ + 100, 0, + /* 200Hz */ + 200, 0, + /* 400Hz */ + 400, 0, + /* 800Hz */ + 800, 0, + /* 1600Hz */ + 1600, 0, +}; + +static const int inv_icm42607_gyro_odr_conv[] = { + INV_ICM42607_ODR_12_5HZ, + INV_ICM42607_ODR_25HZ, + INV_ICM42607_ODR_50HZ, + INV_ICM42607_ODR_100HZ, + INV_ICM42607_ODR_200HZ, + INV_ICM42607_ODR_400HZ, + INV_ICM42607_ODR_800HZ, + INV_ICM42607_ODR_1600HZ, +}; + +static int inv_icm42607_gyro_read_odr(struct inv_icm42607_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + odr = st->conf.gyro.odr; + + for (i = 0; i < ARRAY_SIZE(inv_icm42607_gyro_odr_conv); ++i) { + if (inv_icm42607_gyro_odr_conv[i] == odr) + break; + } + if (i >= ARRAY_SIZE(inv_icm42607_gyro_odr_conv)) + return -EINVAL; + + *val = inv_icm42607_gyro_odr[2 * i]; + *val2 = inv_icm42607_gyro_odr[2 * i + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm42607_gyro_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm42607_sensor_conf conf = INV_ICM42607_SENSOR_CONF_INIT; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm42607_gyro_odr); idx += 2) { + if (val == inv_icm42607_gyro_odr[idx] && + val2 == inv_icm42607_gyro_odr[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm42607_gyro_odr)) + return -EINVAL; + + conf.odr = inv_icm42607_gyro_odr_conv[idx / 2]; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + guard(mutex)(&st->lock); + + ret = inv_sensors_timestamp_update_odr(ts, inv_icm42607_odr_to_period(conf.odr), + iio_buffer_enabled(indio_dev)); + if (ret) + return ret; + + ret = inv_icm42607_set_gyro_conf(st, &conf, NULL); + if (ret) + return ret; + inv_icm42607_buffer_update_fifo_period(st); + inv_icm42607_buffer_update_watermark(st); + + return ret; +} + +static int inv_icm42607_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + s16 data; + int ret; + + switch (chan->type) { + case IIO_ANGL_VEL: + break; + case IIO_TEMP: + return inv_icm42607_temp_read_raw(indio_dev, chan, val, val2, mask); + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42607_gyro_read_sensor(st, chan, &data); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + *val = data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm42607_gyro_read_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42607_gyro_read_odr(st, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42607_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm42607_gyro_write_scale(indio_dev, val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm42607_gyro_write_odr(indio_dev, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm42607_gyro_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int inv_icm42607_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + st->fifo.watermark.gyro = val; + return inv_icm42607_buffer_update_watermark(st); +} + +static int inv_icm42607_gyro_hwfifo_flush(struct iio_dev *indio_dev, + unsigned int count) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (count == 0) + return 0; + + guard(mutex)(&st->lock); + + ret = inv_icm42607_buffer_hwfifo_flush(st, count); + if (ret) + return ret; + + return st->fifo.nb.gyro; +} + +static const struct iio_info inv_icm42607_gyro_info = { + .read_raw = inv_icm42607_gyro_read_raw, + .write_raw = inv_icm42607_gyro_write_raw, + .write_raw_get_fmt = inv_icm42607_gyro_write_raw_get_fmt, + .debugfs_reg_access = inv_icm42607_debugfs_reg, + .update_scan_mode = inv_icm42607_gyro_update_scan_mode, + .hwfifo_set_watermark = inv_icm42607_gyro_hwfifo_set_watermark, + .hwfifo_flush_to_buffer = inv_icm42607_gyro_hwfifo_flush, +}; + +struct iio_dev *inv_icm42607_gyro_init(struct inv_icm42607_state *st) +{ + struct device *dev = regmap_get_device(st->map); + const char *name; + struct inv_icm42607_sensor_state *gyro_st; + struct inv_sensors_timestamp_chip ts_chip; + struct iio_dev *indio_dev; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-gyro", st->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gyro_st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + gyro_st = iio_priv(indio_dev); + + /* + * clock period is 32kHz (31250ns) + * jitter is +/- 2% (20 per mille) + */ + ts_chip.clock_period = 31250; + ts_chip.jitter = 20; + ts_chip.init_period = inv_icm42607_odr_to_period(st->conf.accel.odr); + inv_sensors_timestamp_init(&gyro_st->ts, &ts_chip); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; + indio_dev->info = &inv_icm42607_gyro_info; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->channels = inv_icm42607_gyro_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm42607_gyro_channels); + indio_dev->available_scan_masks = inv_icm42607_gyro_scan_masks; + indio_dev->setup_ops = &inv_icm42607_buffer_ops; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &inv_icm42607_buffer_ops); + if (ret) + return ERR_PTR(ret); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} + +int inv_icm42607_gyro_parse_fifo(struct iio_dev *indio_dev) +{ + struct inv_icm42607_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm42607_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; + ssize_t i, size; + unsigned int no; + const void *accel, *gyro, *timestamp; + const s8 *temp; + unsigned int odr; + s64 ts_val; + struct inv_icm42607_gyro_buffer buffer = { }; + + /* parse all fifo packets */ + for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) { + size = inv_icm42607_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + /* quit if error or FIFO is empty */ + if (size <= 0) + return size; + + /* skip packet if no gyro data or data is invalid */ + if (gyro == NULL || !inv_icm42607_fifo_is_data_valid(gyro)) + continue; + + /* update odr */ + if (odr & INV_ICM42607_SENSOR_GYRO) { + inv_sensors_timestamp_apply_odr(ts, st->fifo.period, + st->fifo.nb.total, no); + } + + memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; + ts_val = inv_sensors_timestamp_pop(ts); + iio_push_to_buffers_with_ts(indio_dev, &buffer, + sizeof(buffer), ts_val); + } + + return 0; +} -- 2.43.0 From diederik at cknow-tech.com Sat May 2 03:29:40 2026 From: diederik at cknow-tech.com (Diederik de Haas) Date: Sat, 02 May 2026 12:29:40 +0200 Subject: [PATCH v2 2/7] arm64: dts: rockchip: Add HYM8563 RTC for Khadas Edge 2L In-Reply-To: <20260429063712.2150938-3-gray.huang@wesion.com> References: <20260429063712.2150938-1-gray.huang@wesion.com> <20260429063712.2150938-3-gray.huang@wesion.com> Message-ID: On Wed Apr 29, 2026 at 8:37 AM CEST, Gray Huang wrote: > The Khadas Edge 2L uses an on-board HYM8563 RTC connected to > I2C2. Enable it and expose its 32.768kHz clock output so later > board-level patches can reference it as the LPO clock source for > the AP6275P wireless module. > > Mark the RTC as a wakeup source as well. > > Signed-off-by: Gray Huang > --- > .../boot/dts/rockchip/rk3576-khadas-edge-2l.dts | 13 +++++++++++-- > 1 file changed, 11 insertions(+), 2 deletions(-) > > diff --git a/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts b/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts > index 5781deae00d9..c85b219fe409 100644 > --- a/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts > +++ b/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts > @@ -46,8 +46,6 @@ vcc_2v0_pldo_s3: regulator-vcc-2v0-pldo-s3 { > regulator-max-microvolt = <2000000>; > vin-supply = <&vcc_sys>; > }; > - > - > }; You're removing unneeded empty lines (good), but you introduced them in patch 1 of this series ... thus fix patch 1? That also means you're no longer adding an unrelated change in this patch. Cheers, Diederik > &cpu_l0 { > @@ -431,6 +429,17 @@ regulator-state-mem { > }; > }; > > +&i2c2 { > + status = "okay"; > + > + hym8563: hym8563 at 51 { > + compatible = "haoyu,hym8563"; > + reg = <0x51>; > + #clock-cells = <0>; > + clock-output-names = "hym8563"; > + wakeup-source; > + }; > +}; > > &sdhci { > bus-width = <8>; From honyuenkwun at gmail.com Sat May 2 10:19:58 2026 From: honyuenkwun at gmail.com (Jimmy Hon) Date: Sat, 2 May 2026 12:19:58 -0500 Subject: [PATCH v2 5/7] arm64: dts: rockchip: Add HDMI and VOP support for Khadas Edge 2L In-Reply-To: <20260429063712.2150938-6-gray.huang@wesion.com> References: <20260429063712.2150938-1-gray.huang@wesion.com> <20260429063712.2150938-6-gray.huang@wesion.com> Message-ID: On Wed, Apr 29, 2026 at 1:38?AM Gray Huang wrote: > > Enable the Video Output Processor (VOP) and the HDMI TX controller > to support HDMI display output on the Khadas Edge 2L. Also, enable > the associated HDMI PHY. > > Signed-off-by: Gray Huang < snip > > + > +&hdmi_sound { > + status = "okay"; > +}; To have hdmi sound working, you'll also need to enable sai6. Similar to the update for rock-4d e6066edc9413191479b05596ba06c40908f44e22 Jimmy From laurent.pinchart at ideasonboard.com Sat May 2 10:39:37 2026 From: laurent.pinchart at ideasonboard.com (Laurent Pinchart) Date: Sat, 2 May 2026 20:39:37 +0300 Subject: [PATCH] media: uapi: rkisp: Correct name version enum In-Reply-To: <20260501190339.3449193-1-niklas.soderlund+renesas@ragnatech.se> References: <20260501190339.3449193-1-niklas.soderlund+renesas@ragnatech.se> Message-ID: <20260502173937.GD488660@killaraus.ideasonboard.com> Hi Niklas, Thank you for the patch. On Fri, May 01, 2026 at 09:03:39PM +0200, Niklas S?derlund wrote: > The name of the enum to hold the mapping of parameter buffer versions > have a typo in the name, correct it. While this is a uAPI header the > impact should be minimal as the enum is only used as a collection for > the one version number supported. > > Fixes: e9d05e9d5db1 ("media: uapi: rkisp1-config: Add extensible params format") > Signed-off-by: Niklas S?derlund Reviewed-by: Laurent Pinchart > --- > include/uapi/linux/rkisp1-config.h | 6 +++--- > 1 file changed, 3 insertions(+), 3 deletions(-) > > diff --git a/include/uapi/linux/rkisp1-config.h b/include/uapi/linux/rkisp1-config.h > index b2d2a71f7baf..7638b2220600 100644 > --- a/include/uapi/linux/rkisp1-config.h > +++ b/include/uapi/linux/rkisp1-config.h > @@ -1535,11 +1535,11 @@ struct rkisp1_ext_params_wdr_config { > sizeof(struct rkisp1_ext_params_wdr_config)) > > /** > - * enum rksip1_ext_param_buffer_version - RkISP1 extensible parameters version > + * enum rkisp1_ext_param_buffer_version - RkISP1 extensible parameters version > * > * @RKISP1_EXT_PARAM_BUFFER_V1: First version of RkISP1 extensible parameters > */ > -enum rksip1_ext_param_buffer_version { > +enum rkisp1_ext_param_buffer_version { > RKISP1_EXT_PARAM_BUFFER_V1 = V4L2_ISP_PARAMS_VERSION_V1, > }; > > @@ -1601,7 +1601,7 @@ enum rksip1_ext_param_buffer_version { > * +---------------------------------------------------------------------+ > * > * @version: The RkISP1 extensible parameters buffer version, see > - * :c:type:`rksip1_ext_param_buffer_version` > + * :c:type:`rkisp1_ext_param_buffer_version` > * @data_size: The RkISP1 configuration data effective size, excluding this > * header > * @data: The RkISP1 extensible configuration data blocks -- Regards, Laurent Pinchart From wbg at kernel.org Sun May 3 03:46:23 2026 From: wbg at kernel.org (William Breathitt Gray) Date: Sun, 3 May 2026 19:46:23 +0900 Subject: [PATCH v5 4/6] counter: Add rockchip-pwm-capture driver In-Reply-To: <20260420-rk3576-pwm-v5-4-ae7cfbbe5427@collabora.com> References: <20260420-rk3576-pwm-v5-4-ae7cfbbe5427@collabora.com> Message-ID: <20260503104624.459765-1-wbg@kernel.org> On Mon, Apr 20, 2026 at 03:52:41PM +0200, Nicolas Frattaroli wrote: > Among many other things, Rockchip's new PWMv4 IP in the RK3576 supports > PWM capture functionality. > > Add a basic driver for this that works to expose HPC/LPC counts and > state change events to userspace through the counter framework. It's > quite basic, but works well enough to demonstrate the device function > exclusion stuff that mfpwm does, in order to eventually support all the > functions of this device in drivers within their appropriate subsystems, > without them interfering with each other. > > Signed-off-by: Nicolas Frattaroli Hi Nicolas, Forgive me if I asked this before, but I'm having trouble finding it online: do you have a link to a publicly available RK357 technical reference manual? I think that will help me better understand how this PWMv4 IP works. Regardless, some comments inline below from my current understanding. > +static struct counter_signal rkpwmc_signals[] = { > + { > + .id = 0, > + .name = "PWM Clock" > + }, > +}; If the capture mode is used to measure the duty cycle of the PWM input, then we actually have two Signals to define here: "PWM Clock" and "PWM Input". I imagine the capture mode waveforms look something like this: __ __ __ __ __ __ __ clk __| |__| |__| |__| |__| |__| |__| |__ __________________ LPC: 2 edges ____ pwm_in _________| HPC: 3 edges |____________| So the level of the pwm_in signal is counted each rising edge of clk; the number of clk edges while pwm_in is high is the HPC count, whereas the number of clk edges while pwm_in is low is the LPC count. In the Generic Counter paradigm, this would look like the following: Signals Synapses Counts ======= ======== ====== +-----------+ _______________________ | | <---- "Rising Edge" ---+---> / \ |"PWM Clock"| | |"High Polarity Capture"| | | +---|---> \ / +-----------+ | | ----------------------- | | +-----------+ | | _______________________ | | | +---> / \ |"PWM Input"| | | "Low Polarity Capture | | | <---- "None" ------+-------> \ / +-----------+ ----------------------- The key idea is that the clock and PWM input Signals are associated to both Counts (HPC and LPC) through their respective Synapses. However, while the clock Synapse ("Rising Edge") indicates the clock Signal state triggers updates in the Counts, the PWM input Synapse ("None") indicates the PWM input Signal state does not trigger but is simply evaluated to determine the new Count value. > +static const enum counter_synapse_action rkpwmc_hpc_lpc_actions[] = { > + COUNTER_SYNAPSE_ACTION_BOTH_EDGES, > + COUNTER_SYNAPSE_ACTION_NONE, > +}; To simplify my above example, I assumed that the HPC and LPC counts are only updated on rising edges of the clock Signal. If the Count values actually update on both edges, then you don't need to make a change here. Otherwise, change the "both edges" enum constant to "rising edge" or "falling edge" as required. > +static struct counter_synapse rkpwmc_pwm_synapses[] = { > + { > + .actions_list = rkpwmc_hpc_lpc_actions, > + .num_actions = ARRAY_SIZE(rkpwmc_hpc_lpc_actions), > + .signal = &rkpwmc_signals[0] > + }, > +}; Add a Synapse here for the "PWM Input" Signal. You should also implement an action_read() callback. Check the value of synapse->signal->id to determine which Signal is associated to the Synapse and set the respective action; i.e. for the PWM clock set COUNTER_SYNAPSE_ACTION_BOTH_EDGES, while for the PWM input set COUNTER_SYNAPSE_ACTION_NONE. > +static const enum counter_function rkpwmc_functions[] = { > + COUNTER_FUNCTION_INCREASE, > +}; I wonder if we need a new enum counter_function constant to express what's happening in this driver. In theory, the COUNTER_FUNCTION_INCREASE represent a Count whose value only increases, but what we're describing here is more of a duty cycle sample, right? I haven't made up my mind on this, so for now you can stick with COUNTER_FUNCTION_INCREASE. I'll reconsider it in the next revision. > +static int rkpwmc_enable_write(struct counter_device *counter, > + struct counter_count *count, > + u8 enable) > +{ > + struct rockchip_pwm_capture *pc = counter_priv(counter); > + int ret; > + > + ret = mfpwm_acquire(pc->pwmf); > + if (ret) > + return ret; > + > + if (!!enable != rkpwmc_is_enabled(pc->pwmf)) { The Counter subsystem gurantees enable is a boolean value so there's no need for the double negation here in the conditional. Also, instead of checking if the enable value is different from rkpwm_is_enabled(), check if it is the same and exit early. That will avoid the large conditional block as what you have inside can now move outside after the conditional check. > + if (enable) { > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, > + PWMV4_EN(false)); > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_CTRL, > + PWMV4_CTRL_CAP_FLAGS); > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN, > + PWMV4_INT_LPC_W(true) | > + PWMV4_INT_HPC_W(true)); > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, > + PWMV4_EN(true) | PWMV4_CLK_EN(true)); > + > + ret = clk_enable(pc->pwmf->core); > + if (ret) > + goto err_release; > + > + ret = clk_rate_exclusive_get(pc->pwmf->core); > + if (ret) > + goto err_disable_pwm_clk; > + > + ret = mfpwm_acquire(pc->pwmf); > + if (ret) > + goto err_unprotect_pwm_clk; > + } else { > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN, > + PWMV4_INT_LPC_W(false) | > + PWMV4_INT_HPC_W(false)); > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, > + PWMV4_EN(false) | PWMV4_CLK_EN(false)); > + clk_rate_exclusive_put(pc->pwmf->core); > + clk_disable(pc->pwmf->core); > + mfpwm_release(pc->pwmf); > + } > + } > + > + mfpwm_release(pc->pwmf); The call to mfpwm_release() in the else block is redundant because it is called immediately again here. William Breathitt Gray From wbg at kernel.org Sun May 3 04:06:43 2026 From: wbg at kernel.org (William Breathitt Gray) Date: Sun, 3 May 2026 20:06:43 +0900 Subject: [PATCH v5 4/6] counter: Add rockchip-pwm-capture driver In-Reply-To: References: Message-ID: <20260503110644.462016-1-wbg@kernel.org> On Mon, Apr 27, 2026 at 07:35:32PM +0200, Nicolas Frattaroli wrote: > On Sunday, 26 April 2026 12:55:20 Central European Summer Time Damon Ding wrote: > > BTW: Is there any user-space test tool similar to libpwm for the > > counter subsystem? > > I've tried looking for this as well, and couldn't find anything. If > it exists then adding a mention of it to generic-counter.rst would > be in order I think. Fabrice Gasnier added a Counter chrdev userspace test application to watch Counter events; it's at tools/counter/counter_watch_events.c if you are interested in trying it out. I do need to update generic-counter.rst to add a few new things we've picked up over the years, so it is on my general to-do list. William Breathitt Gray From krzk at kernel.org Sun May 3 05:18:18 2026 From: krzk at kernel.org (Krzysztof Kozlowski) Date: Sun, 3 May 2026 14:18:18 +0200 Subject: [PATCH V4 01/10] dt-bindings: iio: imu: icm42600: Add icm42607 binding In-Reply-To: <20260501221152.194251-2-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> <20260501221152.194251-2-macroalpha82@gmail.com> Message-ID: <20260503-convivial-aquatic-yak-d2dbeb@quoll> On Fri, May 01, 2026 at 05:11:40PM -0500, Chris Morgan wrote: > From: Chris Morgan > > Add devicetree binding for the Invensense ICM42607 and Invensense > ICM42607P inertial measurement unit. This unit is a combined > accelerometer, gyroscope, and thermometer available via I2C or SPI. > > This device is functionally very similar to the icm42600 series with a > very different register layout. Additionally, add mount-matrix > attribute to schema. Why adding it? Is it something new? Is it applicable to other variants? If not, why it is allowed for them? Do not say WHAT you did, say why you did it. Best regards, Krzysztof From vkoul at kernel.org Sun May 3 09:50:12 2026 From: vkoul at kernel.org (Vinod Koul) Date: Sun, 3 May 2026 22:20:12 +0530 Subject: [PATCH v4 00/16] phy: rockchip: usbdp: Fixes, DP 1-lane support and cleanups In-Reply-To: <20260428-rockchip-usbdp-cleanup-v4-0-7775671ece22@collabora.com> References: <20260428-rockchip-usbdp-cleanup-v4-0-7775671ece22@collabora.com> Message-ID: Hi Sebastian, On 28-04-26, 18:13, Sebastian Reichel wrote: > This series overhauls the Rockchip USBDP driver; apart from a > a bunch of cleanups and small improvements the main goal is to > get the driver ready for proper USB-C DP AltMode support. > > Once this series has landed, it unblocks enabling proper USB-C > DP AltMode on the RK3588 and RK3576 platforms incl. runtime PM > for the Synopsys DesignWare DisplayPort controller. > > Apart from this series, further changes are required on the > DRM side. There are no compile-time dependencies between the > DRM side and the PHY side, but the PHY side must be applied > to avoid SErrors once runtime PM is added to the DisplayPort > controller driver. Thus it would be really good to land this > series in the next merge window. Looks like sasiko has flagged 8 high warning, can you please check them -- ~Vinod From jonas at kwiboo.se Sun May 3 10:29:36 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 3 May 2026 17:29:36 +0000 Subject: [PATCH] phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops Message-ID: <20260503172936.194003-1-jonas@kwiboo.se> The commit 10ed34d6eaaf ("phy: Add HDMI configuration options") introduced a way for HDMI PHYs to be configured through the generic phy_configure() function. This driver currently derives the TMDS character rate from the pixel clock and the PHY bus width setting. However, no in-tree consumer of this PHY has ever called phy_set_bus_width() to change the TMDS rate. Change the TMDS character rate handling to depend on the configure() ops before any PHY consumer needs to configure a TMDS character rate that is different from the pixel clock rate. Signed-off-by: Jonas Karlman --- A near future drm/rockchip: dw_hdmi: series plans to include a call to phy_configure() to configure this HDMI PHYs TMDS character rate. --- drivers/phy/rockchip/phy-rockchip-inno-hdmi.c | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c index 1483907413fa..7f0563d4d482 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-hdmi.c @@ -245,6 +245,7 @@ struct inno_hdmi_phy { struct clk *phyclk; unsigned long pixclock; unsigned long tmdsclock; + struct phy_configure_opts_hdmi hdmi_cfg; }; struct pre_pll_config { @@ -554,19 +555,10 @@ static inline void inno_update_bits(struct inno_hdmi_phy *inno, u8 reg, static unsigned long inno_hdmi_phy_get_tmdsclk(struct inno_hdmi_phy *inno, unsigned long rate) { - int bus_width = phy_get_bus_width(inno->phy); - - switch (bus_width) { - case 4: - case 5: - case 6: - case 10: - case 12: - case 16: - return (u64)rate * bus_width / 8; - default: - return rate; - } + if (inno->hdmi_cfg.tmds_char_rate) + return inno->hdmi_cfg.tmds_char_rate; + + return rate; } static irqreturn_t inno_hdmi_phy_rk3328_hardirq(int irq, void *dev_id) @@ -602,6 +594,16 @@ static irqreturn_t inno_hdmi_phy_rk3328_irq(int irq, void *dev_id) return IRQ_HANDLED; } +static int inno_hdmi_phy_configure(struct phy *phy, + union phy_configure_opts *opts) +{ + struct inno_hdmi_phy *inno = phy_get_drvdata(phy); + + inno->hdmi_cfg = opts->hdmi; + + return 0; +} + static int inno_hdmi_phy_power_on(struct phy *phy) { struct inno_hdmi_phy *inno = phy_get_drvdata(phy); @@ -668,6 +670,7 @@ static int inno_hdmi_phy_power_off(struct phy *phy) static const struct phy_ops inno_hdmi_phy_ops = { .owner = THIS_MODULE, + .configure = inno_hdmi_phy_configure, .power_on = inno_hdmi_phy_power_on, .power_off = inno_hdmi_phy_power_off, }; @@ -1392,7 +1395,6 @@ static int inno_hdmi_phy_probe(struct platform_device *pdev) } phy_set_drvdata(inno->phy, inno); - phy_set_bus_width(inno->phy, 8); if (inno->plat_data->ops->init) { ret = inno->plat_data->ops->init(inno); -- 2.54.0 From heiko at sntech.de Sun May 3 12:12:33 2026 From: heiko at sntech.de (Heiko Stuebner) Date: Sun, 03 May 2026 21:12:33 +0200 Subject: [PATCH v4 00/16] phy: rockchip: usbdp: Fixes, DP 1-lane support and cleanups In-Reply-To: References: <20260428-rockchip-usbdp-cleanup-v4-0-7775671ece22@collabora.com> Message-ID: <6960660.0VBMTVartN@phil> Hi Vinod, Am Sonntag, 3. Mai 2026, 18:50:12 Mitteleurop?ische Sommerzeit schrieb Vinod Koul: > Hi Sebastian, > > On 28-04-26, 18:13, Sebastian Reichel wrote: > > This series overhauls the Rockchip USBDP driver; apart from a > > a bunch of cleanups and small improvements the main goal is to > > get the driver ready for proper USB-C DP AltMode support. > > > > Once this series has landed, it unblocks enabling proper USB-C > > DP AltMode on the RK3588 and RK3576 platforms incl. runtime PM > > for the Synopsys DesignWare DisplayPort controller. > > > > Apart from this series, further changes are required on the > > DRM side. There are no compile-time dependencies between the > > DRM side and the PHY side, but the PHY side must be applied > > to avoid SErrors once runtime PM is added to the DisplayPort > > controller driver. Thus it would be really good to land this > > series in the next merge window. > > Looks like sasiko has flagged 8 high warning, can you please check them (1) as mentioned in a different patch thread, review should be happening on the lists. Tools regularly dump old results, so anyone looking at the mailinglist thread might now be able to follow along with what happened in 2 months or so. Also other robot tools can reply on the lists just fine. (2) if 1. is so hugely impossible, please at least provide some reference to what you mean. Not everybody knows all the hype-tools-of-the-week and simply searching for that mysterious "sasiko" [0] did not provide any meaningful results for me, despite some amazon or youtube links for some non-kernel uses of that term ;-) So a link should be present at least please. Thanks Heiko [0] https://duckduckgo.com/?q=%22sasiko%22 From macromorgan at hotmail.com Sun May 3 13:51:30 2026 From: macromorgan at hotmail.com (Chris Morgan) Date: Sun, 3 May 2026 15:51:30 -0500 Subject: [PATCH V4 01/10] dt-bindings: iio: imu: icm42600: Add icm42607 binding In-Reply-To: <20260503-convivial-aquatic-yak-d2dbeb@quoll> References: <20260501221152.194251-1-macroalpha82@gmail.com> <20260501221152.194251-2-macroalpha82@gmail.com> <20260503-convivial-aquatic-yak-d2dbeb@quoll> Message-ID: On Sun, May 03, 2026 at 02:18:18PM +0200, Krzysztof Kozlowski wrote: > On Fri, May 01, 2026 at 05:11:40PM -0500, Chris Morgan wrote: > > From: Chris Morgan > > > > Add devicetree binding for the Invensense ICM42607 and Invensense > > ICM42607P inertial measurement unit. This unit is a combined > > accelerometer, gyroscope, and thermometer available via I2C or SPI. > > > > This device is functionally very similar to the icm42600 series with a > > very different register layout. Additionally, add mount-matrix > > attribute to schema. > > Why adding it? Is it something new? Is it applicable to other variants? > If not, why it is allowed for them? It's not new, technically this is a bug/oversight from the very first iteration of the invensense icm42600 driver. The driver requests a mount matrix using iio_read_mount_matrix and then returns an error from the probe function if it can't read one [1]. So it's very much required (and for the next version I'll upgrade it to a required element). I'm adding it because a cursory grep suggests that my use case (for the Anbernic RG-DS and in the near future the Anbernic RG Vita Pro) are the first devices to use this driver in mainline in a device tree based system. Thank you, Chris [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c?h=v7.1-rc1#n746 > > Do not say WHAT you did, say why you did it. > > Best regards, > Krzysztof > From hverkuil+cisco at kernel.org Mon May 4 00:20:59 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Mon, 4 May 2026 09:20:59 +0200 Subject: [PATCH v3] media: verisilicon: Create AV1 helper library In-Reply-To: <20260415073801.58369-1-benjamin.gaignard@collabora.com> References: <20260415073801.58369-1-benjamin.gaignard@collabora.com> Message-ID: <8f29d271-da7d-4456-9427-2fb66136747a@kernel.org> Hi Benjamin, I have a few comments about this: On 15/04/2026 09:38, Benjamin Gaignard wrote: > Regroup all none hardware related AV1 functions into a helper library. > The goal is to avoid code duplication for futur AV1 codecs. futur -> future > > Tested on rock 5b board Fluster score remains the same 204/241. > > Signed-off-by: Benjamin Gaignard > Reviewed-by: Nicolas Dufresne > --- > changes in version 3: > - Remove useless wrapper functions. > > drivers/media/platform/verisilicon/Makefile | 7 +- > .../media/platform/verisilicon/hantro_av1.c | 780 +++++++++++++++ > .../media/platform/verisilicon/hantro_av1.h | 62 ++ > ...entropymode.c => hantro_av1_entropymode.c} | 18 +- > ...entropymode.h => hantro_av1_entropymode.h} | 18 +- > ...av1_filmgrain.c => hantro_av1_filmgrain.c} | 82 +- > .../verisilicon/hantro_av1_filmgrain.h | 44 + > .../media/platform/verisilicon/hantro_hw.h | 7 +- > .../verisilicon/rockchip_av1_filmgrain.h | 36 - > .../verisilicon/rockchip_vpu981_hw_av1_dec.c | 935 ++---------------- > .../platform/verisilicon/rockchip_vpu_hw.c | 7 +- > 11 files changed, 1041 insertions(+), 955 deletions(-) > create mode 100644 drivers/media/platform/verisilicon/hantro_av1.c > create mode 100644 drivers/media/platform/verisilicon/hantro_av1.h > rename drivers/media/platform/verisilicon/{rockchip_av1_entropymode.c => hantro_av1_entropymode.c} (99%) > rename drivers/media/platform/verisilicon/{rockchip_av1_entropymode.h => hantro_av1_entropymode.h} (95%) > rename drivers/media/platform/verisilicon/{rockchip_av1_filmgrain.c => hantro_av1_filmgrain.c} (92%) > create mode 100644 drivers/media/platform/verisilicon/hantro_av1_filmgrain.h > delete mode 100644 drivers/media/platform/verisilicon/rockchip_av1_filmgrain.h > > diff --git a/drivers/media/platform/verisilicon/hantro_av1.c b/drivers/media/platform/verisilicon/hantro_av1.c > new file mode 100644 > index 000000000000..5a51ac877c9c > --- /dev/null > +++ b/drivers/media/platform/verisilicon/hantro_av1.c > @@ -0,0 +1,780 @@ > + > +int hantro_av1_tile_log2(int target) > +{ > + int k; > + > + /* > + * returns the smallest value for k such that 1 << k is greater > + * than or equal to target > + */ > + for (k = 0; (1 << k) < target; k++); Checkpatch gives: ERROR: trailing statements should be on next line #637: FILE: drivers/media/platform/verisilicon/hantro_av1.c:568: + for (k = 0; (1 << k) < target; k++); Just move the ';' to the next line. > + > + return k; > +} > + > diff --git a/drivers/media/platform/verisilicon/hantro_av1_filmgrain.h b/drivers/media/platform/verisilicon/hantro_av1_filmgrain.h > new file mode 100644 > index 000000000000..5593e84114d0 > --- /dev/null > +++ b/drivers/media/platform/verisilicon/hantro_av1_filmgrain.h > @@ -0,0 +1,44 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > + > +#ifndef _HANTRO_AV1_FILMGRAIN_H_ > +#define _HANTRO_AV1_FILMGRAIN_H_ > + > +#include > + > +struct hantro_av1_film_grain { > + u8 scaling_lut_y[256]; > + u8 scaling_lut_cb[256]; > + u8 scaling_lut_cr[256]; > + s16 cropped_luma_grain_block[4096]; > + s16 cropped_chroma_grain_block[1024 * 2]; > +}; This struct is not used in hantro_av1_filmgrain.c/h. Does this belong here? > + > +void hantro_av1_generate_luma_grain_block(s32 (*luma_grain_block)[73][82], > + s32 bitdepth, > + u8 num_y_points, > + s32 grain_scale_shift, > + s32 ar_coeff_lag, > + s32 (*ar_coeffs_y)[24], > + s32 ar_coeff_shift, > + s32 grain_min, > + s32 grain_max, > + u16 random_seed); > + > +void hantro_av1_generate_chroma_grain_block(s32 (*luma_grain_block)[73][82], > + s32 (*cb_grain_block)[38][44], > + s32 (*cr_grain_block)[38][44], > + s32 bitdepth, > + u8 num_y_points, > + u8 num_cb_points, > + u8 num_cr_points, > + s32 grain_scale_shift, > + s32 ar_coeff_lag, > + s32 (*ar_coeffs_cb)[25], > + s32 (*ar_coeffs_cr)[25], > + s32 ar_coeff_shift, > + s32 grain_min, > + s32 grain_max, > + u8 chroma_scaling_from_luma, > + u16 random_seed); > + I get a lot of checkpatch warnings of this type: WARNING: function definition argument 's32' should also have an identifier name #1205: FILE: drivers/media/platform/verisilicon/hantro_av1_filmgrain.h:16: +void hantro_av1_generate_luma_grain_block(s32 (*luma_grain_block)[73][82], Looking at how it is used in rockchip_vpu981_av1_dec_set_fgs() I think this can be done a lot easier if you add a new struct here containing those arrays. E.g.: struct hantro_av1_coeffs_grain_block { s32 ar_coeffs_y[24]; s32 ar_coeffs_cb[25]; s32 ar_coeffs_cr[25]; s32 luma_grain_block[73][82]; s32 cb_grain_block[38][44]; s32 cr_grain_block[38][44]; }; Then in rockchip_vpu981_av1_dec_set_fgs you can just kzalloc that struct and pass it to these filmgrain functions. Also consider using #defines for the array sizes. If you decide not to use a struct, then you can at least change simply use 's32 luma_grain_block[73][82]' as arguments to these helper functions. There is no need to complicate the function prototypes. But I think creating a struct is a cleaner approach. Regards, Hans From vladimir.oltean at nxp.com Mon May 4 00:57:42 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Mon, 4 May 2026 10:57:42 +0300 Subject: [PATCH v7 phy-next 08/27] PCI: Remove device links to PHY In-Reply-To: <20260430110652.558622-9-vladimir.oltean@nxp.com> <20260430110652.558622-9-vladimir.oltean@nxp.com> References: <20260430110652.558622-1-vladimir.oltean@nxp.com> <20260430110652.558622-1-vladimir.oltean@nxp.com> <20260430110652.558622-9-vladimir.oltean@nxp.com> <20260430110652.558622-9-vladimir.oltean@nxp.com> Message-ID: <20260504075742.ixt7phu4cpp433q5@skbuf> Sashiko says: > commit e4f495a9118b40f8a77e8dc097821d2c846ed1fc > Author: Vladimir Oltean > > PCI: Remove device links to PHY > This commit removes device links between the PCIe controllers and PHY > providers across several drivers, since the PHY core already creates consumer > device links. > > > diff --git a/drivers/pci/controller/cadence/pcie-cadence-plat.c b/drivers/pci/controller/cadence/pcie-cadence-plat.c > > index b067a3296dd37..fc39c01b79648 100644 > > --- a/drivers/pci/controller/cadence/pcie-cadence-plat.c > > +++ b/drivers/pci/controller/cadence/pcie-cadence-plat.c > [ ... ] > > @@ -122,9 +121,6 @@ static int cdns_plat_pcie_probe(struct platform_device *pdev) > > pm_runtime_put_sync(dev); > > pm_runtime_disable(dev); > > cdns_pcie_disable_phy(cdns_plat_pcie->pcie); > > - phy_count = cdns_plat_pcie->pcie->phy_count; > > - while (phy_count--) > > - device_link_del(cdns_plat_pcie->pcie->link[phy_count]); > > > > return 0; > > This isn't a bug introduced by your patch, but I noticed the error path in > cdns_plat_pcie_probe() returns 0. > Does this cause the driver core to consider the probe successful even when > host or endpoint setup steps fail? It looks like the device would be left > in an uninitialized and disabled state while still being bound to the driver. > Should this return ret instead? > The condition does not appear intentional. Commit bd22885aa188 ("PCI: cadence: Refactor driver to use as a core library") changed cdns_pcie_host_probe() which properly returned ret on error with cdns_plat_pcie_probe() which doesn't. I will add a preparatory patch which replaces "return 0" with "return ret" in cdns_plat_pcie_probe(), blaming that commit. > > } > > diff --git a/drivers/pci/controller/dwc/pci-dra7xx.c b/drivers/pci/controller/dwc/pci-dra7xx.c > > index cd904659c3216..9c1aef4d0bd1c 100644 > > --- a/drivers/pci/controller/dwc/pci-dra7xx.c > > +++ b/drivers/pci/controller/dwc/pci-dra7xx.c > [ ... ] > > @@ -852,10 +840,6 @@ static int dra7xx_pcie_probe(struct platform_device *pdev) > > pm_runtime_disable(dev); > > dra7xx_pcie_disable_phy(dra7xx); > > > > -err_link: > > - while (--i >= 0) > > - device_link_del(link[i]); > > - > > return ret; > > } > > This isn't a bug introduced by your changes, but looking closely at the error > path right above this hunk, there is a call to pm_runtime_put(dev) immediately > followed by pm_runtime_disable(dev): > err_gpio: > err_get_sync: > pm_runtime_put(dev); > pm_runtime_disable(dev); > Does this sequence create a power management race condition? The pending > asynchronous idle/suspend work can be abruptly cancelled by > pm_runtime_disable(), leaving the hardware in an incorrect or unpredictable > power state. > Should this use pm_runtime_put_sync() instead? > > > diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c > > index fe4836925c4e3..cab9fad212add 100644 > > --- a/drivers/pci/controller/dwc/pci-keystone.c > > +++ b/drivers/pci/controller/dwc/pci-keystone.c > [ ... ] > > @@ -1337,25 +1321,18 @@ static int ks_pcie_probe(struct platform_device *pdev) > > pm_runtime_disable(dev); > > ks_pcie_disable_phy(ks_pcie); > > > > -err_link: > > - while (--i >= 0 && link[i]) > > - device_link_del(link[i]); > > - > > +err: > > return ret; > > } > > > > static void ks_pcie_remove(struct platform_device *pdev) > > { > > struct keystone_pcie *ks_pcie = platform_get_drvdata(pdev); > > - struct device_link **link = ks_pcie->link; > > - int num_lanes = ks_pcie->num_lanes; > > struct device *dev = &pdev->dev; > > > > pm_runtime_put(dev); > > pm_runtime_disable(dev); > > Similar to the question above, is it possible this asynchronous put causes > a race when immediately followed by pm_runtime_disable() during removal? > > > ks_pcie_disable_phy(ks_pcie); > > - while (num_lanes--) > > - device_link_del(link[num_lanes]); > > } It seems plausible that the pm_runtime_put() -> pm_runtime_disable() pattern is ineffective, i.e. one of two things can happen: either pm_runtime_put() runs to completion by chance, or pm_runtime_disable() cancels it. However I am not very familiar with the runtime PM API and its effects, and unless a maintainer tells me to, I would prefer leaving these code paths alone. From nicolas.frattaroli at collabora.com Mon May 4 01:25:27 2026 From: nicolas.frattaroli at collabora.com (Nicolas Frattaroli) Date: Mon, 04 May 2026 10:25:27 +0200 Subject: [PATCH v5 4/6] counter: Add rockchip-pwm-capture driver In-Reply-To: <20260503104624.459765-1-wbg@kernel.org> References: <20260420-rk3576-pwm-v5-4-ae7cfbbe5427@collabora.com> <20260503104624.459765-1-wbg@kernel.org> Message-ID: <4Sq7M_BvSOqHZS-Mve-Dmg@collabora.com> On Sunday, 3 May 2026 12:46:23 Central European Summer Time William Breathitt Gray wrote: > On Mon, Apr 20, 2026 at 03:52:41PM +0200, Nicolas Frattaroli wrote: > > Among many other things, Rockchip's new PWMv4 IP in the RK3576 supports > > PWM capture functionality. > > > > Add a basic driver for this that works to expose HPC/LPC counts and > > state change events to userspace through the counter framework. It's > > quite basic, but works well enough to demonstrate the device function > > exclusion stuff that mfpwm does, in order to eventually support all the > > functions of this device in drivers within their appropriate subsystems, > > without them interfering with each other. > > > > Signed-off-by: Nicolas Frattaroli > > Hi Nicolas, > > Forgive me if I asked this before, but I'm having trouble finding it > online: do you have a link to a publicly available RK357 technical > reference manual? I think that will help me better understand how this > PWMv4 IP works. Hi! The RK3576 TRM isn't public, but the same hardware is used in the RK3506, which does have its TRM online from Rockchip themselves: https://opensource.rock-chips.com/images/3/36/Rockchip_RK3506_TRM_Part_1_V1.2-20250811.pdf See chapter 31 of this. Right now, this counter driver implements what's briefly described in Chapter 31.3.1 "Capture Mode". > > Regardless, some comments inline below from my current understanding. > > > +static struct counter_signal rkpwmc_signals[] = { > > + { > > + .id = 0, > > + .name = "PWM Clock" > > + }, > > +}; > > If the capture mode is used to measure the duty cycle of the PWM input, > then we actually have two Signals to define here: "PWM Clock" and "PWM > Input". > > I imagine the capture mode waveforms look something like this: > > __ __ __ __ __ __ __ > clk __| |__| |__| |__| |__| |__| |__| |__ > > __________________ LPC: 2 edges ____ > pwm_in _________| HPC: 3 edges |____________| > > So the level of the pwm_in signal is counted each rising edge of clk; > the number of clk edges while pwm_in is high is the HPC count, whereas > the number of clk edges while pwm_in is low is the LPC count. Correct. In fact, your diagram is very close to what's in the aforementioned chapter. > > In the Generic Counter paradigm, this would look like the following: > > Signals Synapses Counts > ======= ======== ====== > +-----------+ _______________________ > | | <---- "Rising Edge" ---+---> / \ > |"PWM Clock"| | |"High Polarity Capture"| > | | +---|---> \ / > +-----------+ | | ----------------------- > | | > +-----------+ | | _______________________ > | | | +---> / \ > |"PWM Input"| | | "Low Polarity Capture | > | | <---- "None" ------+-------> \ / > +-----------+ ----------------------- > > The key idea is that the clock and PWM input Signals are associated to > both Counts (HPC and LPC) through their respective Synapses. However, > while the clock Synapse ("Rising Edge") indicates the clock Signal > state triggers updates in the Counts, the PWM input Synapse ("None") > indicates the PWM input Signal state does not trigger but is simply > evaluated to determine the new Count value. > > > +static const enum counter_synapse_action rkpwmc_hpc_lpc_actions[] = { > > + COUNTER_SYNAPSE_ACTION_BOTH_EDGES, > > + COUNTER_SYNAPSE_ACTION_NONE, > > +}; > > To simplify my above example, I assumed that the HPC and LPC counts are > only updated on rising edges of the clock Signal. If the Count values > actually update on both edges, then you don't need to make a change > here. Otherwise, change the "both edges" enum constant to "rising edge" > or "falling edge" as required. I checked the TRM and couldn't see any word on whether the count is updated on the clock's rising edge only, but judging by Damon Ding's review, I assume that is the case. I'll modify it to reflect that. > > +static struct counter_synapse rkpwmc_pwm_synapses[] = { > > + { > > + .actions_list = rkpwmc_hpc_lpc_actions, > > + .num_actions = ARRAY_SIZE(rkpwmc_hpc_lpc_actions), > > + .signal = &rkpwmc_signals[0] > > + }, > > +}; > > Add a Synapse here for the "PWM Input" Signal. > > You should also implement an action_read() callback. Check the value of > synapse->signal->id to determine which Signal is associated to the > Synapse and set the respective action; i.e. for the PWM clock set > COUNTER_SYNAPSE_ACTION_BOTH_EDGES, while for the PWM input set > COUNTER_SYNAPSE_ACTION_NONE. Will do! > > > +static const enum counter_function rkpwmc_functions[] = { > > + COUNTER_FUNCTION_INCREASE, > > +}; > > I wonder if we need a new enum counter_function constant to express > what's happening in this driver. In theory, the > COUNTER_FUNCTION_INCREASE represent a Count whose value only increases, > but what we're describing here is more of a duty cycle sample, right? Yeah, that's a great point. I think we can say it "increases", in that either HPC or LPC are increased by the pwm_clk signal based on the value of the pwm_in signal by the hardware. But of course, the two counts are limited by the full period of the PWM waveform. So reading a counter will give the reader a snapshot view of the high cycles vs. low cycles of the last observed PWM waveform period, which is both a duty cycle sample and a period length sample, as the counts are in clock cycles, so if we add the counts together we get the PWM signal period in clock cycles. This is also why you saw me convert counts to nanoseconds in a previous revision; it felt like I should decouple it from the clock, but I think the can of worms that this opens isn't worth it. > > I haven't made up my mind on this, so for now you can stick with > COUNTER_FUNCTION_INCREASE. I'll reconsider it in the next revision. > > > +static int rkpwmc_enable_write(struct counter_device *counter, > > + struct counter_count *count, > > + u8 enable) > > +{ > > + struct rockchip_pwm_capture *pc = counter_priv(counter); > > + int ret; > > + > > + ret = mfpwm_acquire(pc->pwmf); > > + if (ret) > > + return ret; > > + > > + if (!!enable != rkpwmc_is_enabled(pc->pwmf)) { > > The Counter subsystem gurantees enable is a boolean value so there's no > need for the double negation here in the conditional. > > Also, instead of checking if the enable value is different from > rkpwm_is_enabled(), check if it is the same and exit early. That will > avoid the large conditional block as what you have inside can now move > outside after the conditional check. Good call, will do. > > > + if (enable) { > > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, > > + PWMV4_EN(false)); > > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_CTRL, > > + PWMV4_CTRL_CAP_FLAGS); > > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN, > > + PWMV4_INT_LPC_W(true) | > > + PWMV4_INT_HPC_W(true)); > > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, > > + PWMV4_EN(true) | PWMV4_CLK_EN(true)); > > + > > + ret = clk_enable(pc->pwmf->core); > > + if (ret) > > + goto err_release; > > + > > + ret = clk_rate_exclusive_get(pc->pwmf->core); > > + if (ret) > > + goto err_disable_pwm_clk; > > + > > + ret = mfpwm_acquire(pc->pwmf); > > + if (ret) > > + goto err_unprotect_pwm_clk; > > + } else { > > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN, > > + PWMV4_INT_LPC_W(false) | > > + PWMV4_INT_HPC_W(false)); > > + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, > > + PWMV4_EN(false) | PWMV4_CLK_EN(false)); > > + clk_rate_exclusive_put(pc->pwmf->core); > > + clk_disable(pc->pwmf->core); > > + mfpwm_release(pc->pwmf); > > + } > > + } > > + > > + mfpwm_release(pc->pwmf); > > The call to mfpwm_release() in the else block is redundant because it is > called immediately again here. It's actually intentional, and not redundant. The if (enable) branch does an mfpwm_acquire() so that as long as the counter functionality is enabled, the use count remains > 0. This way, the PWM output driver can't tell the mfpwm driver that it'd like to have exclusive use of the PWM device now, as the counter driver is claiming it. To balance this, the else block here needs to release it. The release outside the else block is to balance the acquire earlier in the function, the pair of which is there to ensure mfpwm has powered on the hardware and associated clocks for the register reads and writes. Admittedly, I could get rid of the additional mfpwm_acquire in the if (enable) branch by letting the function entry acquire "leak" on purpose, by getting rid of the function end mfpwm_release. But this feels less obvious to me than the current way. I'll definitely add comments though to indicate what's going on, and maybe even define a cleanup.h guard() class for the function level acquire-release. Thanks for the review, and especially the explanations. Kind regards, Nicolas Frattaroli > > William Breathitt Gray > From benjamin.gaignard at collabora.com Mon May 4 03:25:54 2026 From: benjamin.gaignard at collabora.com (Benjamin Gaignard) Date: Mon, 4 May 2026 12:25:54 +0200 Subject: [PATCH v3] media: verisilicon: Create AV1 helper library In-Reply-To: <8f29d271-da7d-4456-9427-2fb66136747a@kernel.org> References: <20260415073801.58369-1-benjamin.gaignard@collabora.com> <8f29d271-da7d-4456-9427-2fb66136747a@kernel.org> Message-ID: Le 04/05/2026 ? 09:20, Hans Verkuil a ?crit?: > Hi Benjamin, > > I have a few comments about this: > > On 15/04/2026 09:38, Benjamin Gaignard wrote: >> Regroup all none hardware related AV1 functions into a helper library. >> The goal is to avoid code duplication for futur AV1 codecs. > futur -> future > >> Tested on rock 5b board Fluster score remains the same 204/241. >> >> Signed-off-by: Benjamin Gaignard >> Reviewed-by: Nicolas Dufresne >> --- >> changes in version 3: >> - Remove useless wrapper functions. >> >> drivers/media/platform/verisilicon/Makefile | 7 +- >> .../media/platform/verisilicon/hantro_av1.c | 780 +++++++++++++++ >> .../media/platform/verisilicon/hantro_av1.h | 62 ++ >> ...entropymode.c => hantro_av1_entropymode.c} | 18 +- >> ...entropymode.h => hantro_av1_entropymode.h} | 18 +- >> ...av1_filmgrain.c => hantro_av1_filmgrain.c} | 82 +- >> .../verisilicon/hantro_av1_filmgrain.h | 44 + >> .../media/platform/verisilicon/hantro_hw.h | 7 +- >> .../verisilicon/rockchip_av1_filmgrain.h | 36 - >> .../verisilicon/rockchip_vpu981_hw_av1_dec.c | 935 ++---------------- >> .../platform/verisilicon/rockchip_vpu_hw.c | 7 +- >> 11 files changed, 1041 insertions(+), 955 deletions(-) >> create mode 100644 drivers/media/platform/verisilicon/hantro_av1.c >> create mode 100644 drivers/media/platform/verisilicon/hantro_av1.h >> rename drivers/media/platform/verisilicon/{rockchip_av1_entropymode.c => hantro_av1_entropymode.c} (99%) >> rename drivers/media/platform/verisilicon/{rockchip_av1_entropymode.h => hantro_av1_entropymode.h} (95%) >> rename drivers/media/platform/verisilicon/{rockchip_av1_filmgrain.c => hantro_av1_filmgrain.c} (92%) >> create mode 100644 drivers/media/platform/verisilicon/hantro_av1_filmgrain.h >> delete mode 100644 drivers/media/platform/verisilicon/rockchip_av1_filmgrain.h >> > > >> diff --git a/drivers/media/platform/verisilicon/hantro_av1.c b/drivers/media/platform/verisilicon/hantro_av1.c >> new file mode 100644 >> index 000000000000..5a51ac877c9c >> --- /dev/null >> +++ b/drivers/media/platform/verisilicon/hantro_av1.c >> @@ -0,0 +1,780 @@ > > >> + >> +int hantro_av1_tile_log2(int target) >> +{ >> + int k; >> + >> + /* >> + * returns the smallest value for k such that 1 << k is greater >> + * than or equal to target >> + */ >> + for (k = 0; (1 << k) < target; k++); > Checkpatch gives: > > ERROR: trailing statements should be on next line > #637: FILE: drivers/media/platform/verisilicon/hantro_av1.c:568: > + for (k = 0; (1 << k) < target; k++); > > Just move the ';' to the next line. OK I will do that. > >> + >> + return k; >> +} >> + > > >> diff --git a/drivers/media/platform/verisilicon/hantro_av1_filmgrain.h b/drivers/media/platform/verisilicon/hantro_av1_filmgrain.h >> new file mode 100644 >> index 000000000000..5593e84114d0 >> --- /dev/null >> +++ b/drivers/media/platform/verisilicon/hantro_av1_filmgrain.h >> @@ -0,0 +1,44 @@ >> +/* SPDX-License-Identifier: GPL-2.0-only */ >> + >> +#ifndef _HANTRO_AV1_FILMGRAIN_H_ >> +#define _HANTRO_AV1_FILMGRAIN_H_ >> + >> +#include >> + >> +struct hantro_av1_film_grain { >> + u8 scaling_lut_y[256]; >> + u8 scaling_lut_cb[256]; >> + u8 scaling_lut_cr[256]; >> + s16 cropped_luma_grain_block[4096]; >> + s16 cropped_chroma_grain_block[1024 * 2]; >> +}; > This struct is not used in hantro_av1_filmgrain.c/h. > > Does this belong here? The same structure can used for future Verisilicon hardware block so I would prefer to keep it here. > >> + >> +void hantro_av1_generate_luma_grain_block(s32 (*luma_grain_block)[73][82], >> + s32 bitdepth, >> + u8 num_y_points, >> + s32 grain_scale_shift, >> + s32 ar_coeff_lag, >> + s32 (*ar_coeffs_y)[24], >> + s32 ar_coeff_shift, >> + s32 grain_min, >> + s32 grain_max, >> + u16 random_seed); >> + >> +void hantro_av1_generate_chroma_grain_block(s32 (*luma_grain_block)[73][82], >> + s32 (*cb_grain_block)[38][44], >> + s32 (*cr_grain_block)[38][44], >> + s32 bitdepth, >> + u8 num_y_points, >> + u8 num_cb_points, >> + u8 num_cr_points, >> + s32 grain_scale_shift, >> + s32 ar_coeff_lag, >> + s32 (*ar_coeffs_cb)[25], >> + s32 (*ar_coeffs_cr)[25], >> + s32 ar_coeff_shift, >> + s32 grain_min, >> + s32 grain_max, >> + u8 chroma_scaling_from_luma, >> + u16 random_seed); >> + > I get a lot of checkpatch warnings of this type: > > WARNING: function definition argument 's32' should also have an identifier name > #1205: FILE: drivers/media/platform/verisilicon/hantro_av1_filmgrain.h:16: > +void hantro_av1_generate_luma_grain_block(s32 (*luma_grain_block)[73][82], > Looking at how it is used in rockchip_vpu981_av1_dec_set_fgs() I think this > can be done a lot easier if you add a new struct here containing those > arrays. E.g.: > > struct hantro_av1_coeffs_grain_block { > s32 ar_coeffs_y[24]; > s32 ar_coeffs_cb[25]; > s32 ar_coeffs_cr[25]; > s32 luma_grain_block[73][82]; > s32 cb_grain_block[38][44]; > s32 cr_grain_block[38][44]; > }; > > Then in rockchip_vpu981_av1_dec_set_fgs you can just kzalloc that struct > and pass it to these filmgrain functions. I will do that. > > Also consider using #defines for the array sizes. Chapter "7.18.3.3. Generate grain process" of AV1 spec doesn't define/name these values so keep them like this make the code more easy to review with the spec. https://aomediacodec.github.io/av1-spec/#generate-grain-process That said I'm always for naming values so good suggestion are welcome. Thanks, Benjamin > > If you decide not to use a struct, then you can at least change simply use > 's32 luma_grain_block[73][82]' as arguments to these helper functions. There > is no need to complicate the function prototypes. > > But I think creating a struct is a cleaner approach. > > Regards, > > Hans > From vladimir.oltean at nxp.com Mon May 4 04:48:28 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Mon, 4 May 2026 14:48:28 +0300 Subject: [PATCH v7 phy-next 09/27] scsi: ufs: exynos: stop poking into struct phy guts In-Reply-To: <20260430110652.558622-10-vladimir.oltean@nxp.com> References: <20260430110652.558622-1-vladimir.oltean@nxp.com> <20260430110652.558622-10-vladimir.oltean@nxp.com> Message-ID: <20260504114828.n7r7kujaegusdkbw@skbuf> Sashiko says: > commit 76ded6c0290bdb192c2ae1af9a2e6a47fc39c9a1 > Author: Vladimir Oltean > > scsi: ufs: exynos: stop poking into struct phy guts > This commit modifies the Exynos UFS driver to stop directly accessing the > generic PHY's power_count. Instead, it tracks the PHY power state internally > using a new phy_powered_on flag to properly handle link retries and > suspend/resume operations. > > > diff --git a/drivers/ufs/host/ufs-exynos.c b/drivers/ufs/host/ufs-exynos.c > > --- a/drivers/ufs/host/ufs-exynos.c > > +++ b/drivers/ufs/host/ufs-exynos.c > [ ... ] > > @@ -1513,6 +1516,9 @@ static void exynos_ufs_exit(struct ufs_hba *hba) > > { > > struct exynos_ufs *ufs = ufshcd_get_variant(hba); > > > > + if (!ufs->phy_powered_on) > > + return; > > + > > phy_power_off(ufs->phy); > > phy_exit(ufs->phy); > > } > > Does this introduce a regression where initialization resources are leaked > if the driver is unbound while suspended? > Looking at exynos_ufs_suspend(), the PHY is powered off and phy_powered_on > is set to false, but phy_exit() is intentionally not called so the PHY > remains initialized: > exynos_ufs_suspend() > if (!ufshcd_is_link_active(hba) && ufs->phy_powered_on) { > phy_power_off(ufs->phy); > ufs->phy_powered_on = false; > } > If an unbind or fatal error occurs while in this suspended state, > exynos_ufs_exit() will see phy_powered_on as false and return immediately. > The initialization reference and any resources allocated by phy_init() > would never be released since phy_exit() is completely skipped. > [ ... ] I think I've been looking at this issue all wrong, not considering why the UFS HCD driver calls phy_exit() -> phy_init() at runtime and what can be done about that. I have replaced this patch with the one attached, which I will be sending for v8. -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-scsi-ufs-exynos-use-dedicated-API-for-updating-PHY-b.patch Type: text/x-diff Size: 9439 bytes Desc: not available URL: From Frank.li at nxp.com Mon May 4 09:10:20 2026 From: Frank.li at nxp.com (Frank Li) Date: Mon, 4 May 2026 12:10:20 -0400 Subject: [PATCH v5 1/2] media: dt-bindings: rockchip,rk3568-mipi-csi2: add rk3588 compatible In-Reply-To: <20260305-rk3588-csi2rx-v5-1-3b7061d043ea@collabora.com> References: <20260305-rk3588-csi2rx-v5-0-3b7061d043ea@collabora.com> <20260305-rk3588-csi2rx-v5-1-3b7061d043ea@collabora.com> Message-ID: On Tue, Apr 28, 2026 at 09:27:23AM +0200, Michael Riesch via B4 Relay wrote: > From: Michael Riesch > > The RK3588 MIPI CSI-2 receivers are compatible to the ones found in the > RK3568. Introduce a list of compatible variants and add the RK3588 variant > to it. > > Acked-by: Rob Herring (Arm) > Signed-off-by: Michael Riesch > --- Reviewed-by: Frank Li > .../devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml | 11 ++++++++--- > 1 file changed, 8 insertions(+), 3 deletions(-) > > diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml > index 4ac4a3b6f406..fbcf28e9e1da 100644 > --- a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml > +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml > @@ -16,9 +16,14 @@ description: > > properties: > compatible: > - enum: > - - fsl,imx93-mipi-csi2 > - - rockchip,rk3568-mipi-csi2 > + oneOf: > + - enum: > + - fsl,imx93-mipi-csi2 > + - rockchip,rk3568-mipi-csi2 > + - items: > + - enum: > + - rockchip,rk3588-mipi-csi2 > + - const: rockchip,rk3568-mipi-csi2 > > reg: > maxItems: 1 > > -- > 2.39.5 > > From jic23 at kernel.org Mon May 4 09:51:28 2026 From: jic23 at kernel.org (Jonathan Cameron) Date: Mon, 4 May 2026 17:51:28 +0100 Subject: [PATCH V4 01/10] dt-bindings: iio: imu: icm42600: Add icm42607 binding In-Reply-To: References: <20260501221152.194251-1-macroalpha82@gmail.com> <20260501221152.194251-2-macroalpha82@gmail.com> <20260503-convivial-aquatic-yak-d2dbeb@quoll> Message-ID: <20260504175128.0bfdefe0@jic23-huawei> On Sun, 3 May 2026 15:51:30 -0500 Chris Morgan wrote: > On Sun, May 03, 2026 at 02:18:18PM +0200, Krzysztof Kozlowski wrote: > > On Fri, May 01, 2026 at 05:11:40PM -0500, Chris Morgan wrote: > > > From: Chris Morgan > > > > > > Add devicetree binding for the Invensense ICM42607 and Invensense > > > ICM42607P inertial measurement unit. This unit is a combined > > > accelerometer, gyroscope, and thermometer available via I2C or SPI. > > > > > > This device is functionally very similar to the icm42600 series with a > > > very different register layout. Additionally, add mount-matrix > > > attribute to schema. > > > > Why adding it? Is it something new? Is it applicable to other variants? > > If not, why it is allowed for them? > > It's not new, technically this is a bug/oversight from the very first > iteration of the invensense icm42600 driver. The driver requests a > mount matrix using iio_read_mount_matrix and then returns an error from Unless it's broken (always possible) iio_read_mount_matrix() is supposed to return an identity matrix if there isn't any info in the binding. /* Matrix was not declared at all: fallback to identity. */ return iio_setup_mount_idmatrix(dev, matrix); So not required by the linux driver at least. Probably not something we should require in general. Separate patch to introduce it to the binding as optional and say something about what it is for to justify it's inclusion. That patch goes before this one. > the probe function if it can't read one [1]. So it's very much required > (and for the next version I'll upgrade it to a required element). I'm > adding it because a cursory grep suggests that my use case (for the > Anbernic RG-DS and in the near future the Anbernic RG Vita Pro) are the > first devices to use this driver in mainline in a device tree based > system. > > Thank you, > Chris > > [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c?h=v7.1-rc1#n746 > > > > > Do not say WHAT you did, say why you did it. > > > > Best regards, > > Krzysztof > > From macromorgan at hotmail.com Mon May 4 10:17:50 2026 From: macromorgan at hotmail.com (Chris Morgan) Date: Mon, 4 May 2026 12:17:50 -0500 Subject: [PATCH V4 01/10] dt-bindings: iio: imu: icm42600: Add icm42607 binding In-Reply-To: <20260504175128.0bfdefe0@jic23-huawei> References: <20260501221152.194251-1-macroalpha82@gmail.com> <20260501221152.194251-2-macroalpha82@gmail.com> <20260503-convivial-aquatic-yak-d2dbeb@quoll> <20260504175128.0bfdefe0@jic23-huawei> Message-ID: On Mon, May 04, 2026 at 05:51:28PM +0100, Jonathan Cameron wrote: > On Sun, 3 May 2026 15:51:30 -0500 > Chris Morgan wrote: > > > On Sun, May 03, 2026 at 02:18:18PM +0200, Krzysztof Kozlowski wrote: > > > On Fri, May 01, 2026 at 05:11:40PM -0500, Chris Morgan wrote: > > > > From: Chris Morgan > > > > > > > > Add devicetree binding for the Invensense ICM42607 and Invensense > > > > ICM42607P inertial measurement unit. This unit is a combined > > > > accelerometer, gyroscope, and thermometer available via I2C or SPI. > > > > > > > > This device is functionally very similar to the icm42600 series with a > > > > very different register layout. Additionally, add mount-matrix > > > > attribute to schema. > > > > > > Why adding it? Is it something new? Is it applicable to other variants? > > > If not, why it is allowed for them? > > > > It's not new, technically this is a bug/oversight from the very first > > iteration of the invensense icm42600 driver. The driver requests a > > mount matrix using iio_read_mount_matrix and then returns an error from > > Unless it's broken (always possible) iio_read_mount_matrix() is supposed > to return an identity matrix if there isn't any info in the binding. > > /* Matrix was not declared at all: fallback to identity. */ > return iio_setup_mount_idmatrix(dev, matrix); > > So not required by the linux driver at least. Probably not something > we should require in general. > > Separate patch to introduce it to the binding as optional and say something > about what it is for to justify it's inclusion. That patch goes before > this one. Okay, that makes more sense. I saw the return condition but didn't realize that an empty matrix didn't result in an error. So for the next version I'll add an additionall commit with this as a new attribute, noting that it has always been supported with this driver but never specified in the binding documentation. Thank you, Chris > > > the probe function if it can't read one [1]. So it's very much required > > (and for the next version I'll upgrade it to a required element). I'm > > adding it because a cursory grep suggests that my use case (for the > > Anbernic RG-DS and in the near future the Anbernic RG Vita Pro) are the > > first devices to use this driver in mainline in a device tree based > > system. > > > > Thank you, > > Chris > > > > [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/iio/imu/inv_icm42600/inv_icm42600_core.c?h=v7.1-rc1#n746 > > > > > > > > Do not say WHAT you did, say why you did it. > > > > > > Best regards, > > > Krzysztof > > > > From jic23 at kernel.org Mon May 4 11:10:37 2026 From: jic23 at kernel.org (Jonathan Cameron) Date: Mon, 4 May 2026 19:10:37 +0100 Subject: [PATCH V4 02/10] iio: imu: inv_icm42607: Add inv_icm42607 Core Driver In-Reply-To: <20260501221152.194251-3-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> <20260501221152.194251-3-macroalpha82@gmail.com> Message-ID: <20260504191037.13b234fe@jic23-huawei> On Fri, 1 May 2026 17:11:41 -0500 Chris Morgan wrote: > From: Chris Morgan > > Add the core component of a new inv_icm42607 driver. This includes > a few setup functions and the full register definition in the > header file. > > Signed-off-by: Chris Morgan Hi Chris. A few comments inline. Biggest one is probably to shuffle when you introduce structures and elements in other structures so they only turn up when they are used in later patches. You can do that with enums etc as well. I don't mind the register defines all in the first patch but the other stuff would ideally come later. Thanks Jonathan > --- > drivers/iio/imu/inv_icm42607/inv_icm42607.h | 400 ++++++++++++++++++ > .../iio/imu/inv_icm42607/inv_icm42607_core.c | 303 +++++++++++++ > 2 files changed, 703 insertions(+) > create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607.h > create mode 100644 drivers/iio/imu/inv_icm42607/inv_icm42607_core.c > > diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h > new file mode 100644 > index 000000000000..f6b3cad8064a > --- /dev/null > +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h > + > +struct inv_icm42607_apex { > + unsigned int on; > + struct { > + u64 value; > + bool enable; > + } wom; > +}; These structures that are only used in later patches, should only be brought in as part of those patches. > + > +/** > + * struct inv_icm42607_state - driver state variables > + * @lock: lock for serializing multiple registers access. > + * @chip: chip identifier. > + * @name: chip name. > + * @map: regmap pointer. > + * @vddio_supply: I/O voltage regulator for the chip. > + * @irq: chip irq, required to enable/disable and set wakeup > + * @orientation: sensor chip orientation relative to main hardware. > + * @conf: chip sensors configurations. > + * @suspended: suspended sensors configuration. > + * @indio_gyro: gyroscope IIO device. > + * @indio_accel: accelerometer IIO device. > + * @timestamp: interrupt timestamps. > + * @apex: APEX (Advanced Pedometer and Event detection) management > + * @buffer: data transfer buffer aligned for DMA. > + */ > +struct inv_icm42607_state { > + struct mutex lock; > + enum inv_icm42607_chip chip; > + const char *name; > + struct regmap *map; > + struct regulator *vddio_supply; > + int irq; > + struct iio_mount_matrix orientation; > + struct inv_icm42607_conf conf; > + struct inv_icm42607_suspended suspended; > + struct iio_dev *indio_gyro; > + struct iio_dev *indio_accel; Even these should only be added when you add something that uses them. > + struct { > + s64 gyro; > + s64 accel; > + } timestamp; > + struct inv_icm42607_apex apex; > + __be16 buffer[3] __aligned(IIO_DMA_MINALIGN); > +}; > diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c > new file mode 100644 > index 000000000000..a0bbd8a7ea4b > --- /dev/null > +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c > +/** > + * inv_icm42607_setup() - check and setup chip > + * @st: driver internal state > + * @bus_setup: callback for setting up bus specific registers > + * > + * Returns 0 on success, a negative error code otherwise. > + */ > +static int inv_icm42607_setup(struct inv_icm42607_state *st, > + inv_icm42607_bus_setup bus_setup) > +{ > + const struct inv_icm42607_hw *hw = &inv_icm42607_hw[st->chip]; > + const struct device *dev = regmap_get_device(st->map); > + unsigned int val; > + int ret; > + > + ret = regmap_read(st->map, INV_ICM42607_REG_WHOAMI, &val); > + if (ret) > + return ret; > + > + if (val != hw->whoami) > + dev_warn_probe(dev, -ENODEV, > + "invalid whoami %#02x expected %#02x (%s)\n", > + val, hw->whoami, hw->name); > + > + st->name = hw->name; > + > + ret = regmap_write(st->map, INV_ICM42607_REG_SIGNAL_PATH_RESET, > + INV_ICM42607_SIGNAL_PATH_RESET_SOFT_RESET); > + if (ret) > + return ret; > + > + ret = regmap_read_poll_timeout(st->map, INV_ICM42607_REG_INT_STATUS, > + val, val & INV_ICM42607_INT_STATUS_RESET_DONE, > + INV_ICM42607_RESET_TIME_MS * 100, > + INV_ICM42607_RESET_TIME_MS * 1000); > + No blank line here. Keep the error check tightly coupled with the source of errors. > + if (ret) > + return dev_err_probe(dev, ret, > + "reset error, reset done bit not set\n"); > + > + ret = bus_setup(st); > + if (ret) > + return ret; > + > + ret = regmap_set_bits(st->map, INV_ICM42607_REG_INTF_CONFIG0, > + INV_ICM42607_INTF_CONFIG0_SENSOR_DATA_ENDIAN); > + if (ret) > + return ret; > + > + ret = regmap_update_bits(st->map, INV_ICM42607_REG_INTF_CONFIG1, > + INV_ICM42607_INTF_CONFIG1_CLKSEL_MASK, > + INV_ICM42607_INTF_CONFIG1_CLKSEL_PLL); > + if (ret) > + return ret; > + > + return inv_icm42607_set_conf(st, hw->conf); > +} > + > +int inv_icm42607_core_probe(struct regmap *regmap, int chip, > + inv_icm42607_bus_setup bus_setup) > +{ > + struct device *dev = regmap_get_device(regmap); > + struct fwnode_handle *fwnode = dev_fwnode(dev); > + struct inv_icm42607_state *st; > + int irq; > + bool open_drain; > + int ret; > + > + /* > + * Keep bounds checking in case more chips are added, for now only > + * 2 are supported. > + */ > + if (chip < INV_CHIP_INVALID || chip >= INV_CHIP_NB) Is INV_CHIP_INVALID valid in any sense? If it is add a comment to the enum. If it's not <= INV_CHIP_INVALID though I would be tempted to just make chip unsigned and not worry about the bottom - if anyone passes a negative wrap around will make it fail the other check. I'm not immediately spotting any way we can end up with an invalid chip id. > + dev_warn_probe(dev, -ENODEV, "Invalid chip = %d\n", chip); We allow for fallback compatibles and hence WHOAMI reg mismatches but not for bugs and I can't see how this is any thing other than a bug? If it's for legacy probe paths we still want to know what it is. > + From jic23 at kernel.org Mon May 4 11:15:37 2026 From: jic23 at kernel.org (Jonathan Cameron) Date: Mon, 4 May 2026 19:15:37 +0100 Subject: [PATCH V4 03/10] iio: imu: inv_icm42607: Add I2C and SPI For icm42607 In-Reply-To: <20260501221152.194251-4-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> <20260501221152.194251-4-macroalpha82@gmail.com> Message-ID: <20260504191537.17fb8d05@jic23-huawei> On Fri, 1 May 2026 17:11:42 -0500 Chris Morgan wrote: > From: Chris Morgan > > Add I2C and SPI driver support for InvenSense ICM-42607 devices. > Add necessary Kconfig and Makefile to allow building of (incomplete) > driver. > > Signed-off-by: Chris Morgan > diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c > new file mode 100644 > index 000000000000..7a02bbab3a63 > --- /dev/null > +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_spi.c > +static const struct of_device_id inv_icm42607_of_matches[] = { > + { > + .compatible = "invensense,icm42607", > + .data = (void *)INV_CHIP_ICM42607, > + }, > + { > + .compatible = "invensense,icm42607p", > + .data = (void *)INV_CHIP_ICM42607P, > + }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, inv_icm42607_of_matches); > + > +static const struct spi_device_id inv_icm42607_spi_id_table[] = { > + { "icm42607", INV_CHIP_ICM42607 }, > + { "icm42607p", INV_CHIP_ICM42607P }, Very strong preference in new code to not use enums for the data. That explains why you need the magic invalid in previous patch. Can we instead just use pointers to the per chip data relevant to each one? That usually means some externs in the header. We used to do things how you have it here, but the enums always end up proving a pain as drivers get more complex. They also encourage part specific code when it should almost always be part specific constant data. Currently I'm not sure what is different between these parts. If nothing yet from point of view of what the driver supports then don't bother providing data 'yet'. Jonathan > + { } > +}; From cristian.ciocaltea at collabora.com Mon May 4 11:23:59 2026 From: cristian.ciocaltea at collabora.com (Cristian Ciocaltea) Date: Mon, 04 May 2026 21:23:59 +0300 Subject: [PATCH 1/5] drm/rockchip: vop2: Fix wrong wait target in layer cfg done check In-Reply-To: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> References: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> Message-ID: <20260504-vop2-layer-cfg-tmout-v1-1-730226a7331e@collabora.com> rk3568_vop2_setup_layer_mixer() waits for the previous Video Port (VP) layer configuration to take effect before writing a new one to the shared RK3568_OVL_LAYER_SEL shadow register. However, it passes vop2->old_layer_sel to rk3568_vop2_wait_for_layer_cfg_done() as the expected value, which at that point already contains the new VP layer. This causes the wait to poll for a value that has not been written to the shadow register yet, resulting in spurious timeouts when two non-blocking atomic commits race: rockchip-drm display-subsystem: [drm] *ERROR* wait layer cfg done timeout [...] Pass the local old_layer_sel instead, which still holds the value captured from vop2->old_layer_sel before it was overwritten, i.e. the previous VP target that the hardware is expected to latch. Fixes: 3e89a8c68354 ("drm/rockchip: vop2: Fix the update of LAYER/PORT select registers when there are multi display output on rk3588/rk3568") Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 02a788a4dfdd..edca0fb16e08 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2307,7 +2307,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) * Changes of other VPs' overlays have not taken effect */ if (cfg_done) - rk3568_vop2_wait_for_layer_cfg_done(vop2, vop2->old_layer_sel); + rk3568_vop2_wait_for_layer_cfg_done(vop2, old_layer_sel); } vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel); -- 2.53.0 From cristian.ciocaltea at collabora.com Mon May 4 11:24:01 2026 From: cristian.ciocaltea at collabora.com (Cristian Ciocaltea) Date: Mon, 04 May 2026 21:24:01 +0300 Subject: [PATCH 3/5] drm/rockchip: vop2: Delay old_{layer|port}_sel updates in setup_layer_mixer() In-Reply-To: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> References: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> Message-ID: <20260504-vop2-layer-cfg-tmout-v1-3-730226a7331e@collabora.com> The old_layer_sel and old_port_sel local variables were introduced to hold the previous VP configuration for comparisons and wait targets, working around the premature update of vop2->old_layer_sel and vop2->old_port_sel earlier in the function. Remove these superfluous locals and instead defer the assignments of vop2->old_layer_sel and vop2->old_port_sel to just before the corresponding shadow register writes, where the transition from old to new logically belongs. No functional change intended. Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 5206f01ec787..1f5e8c2acecd 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2136,9 +2136,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) struct drm_plane *plane; u32 layer_sel = 0; u32 port_sel; - u32 old_layer_sel = 0; u32 atv_layer_sel = 0; - u32 old_port_sel = 0; u8 layer_id; u8 old_layer_id; u8 layer_sel_id; @@ -2161,8 +2159,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) else ovl_ctrl &= ~RK3568_OVL_CTRL__YUV_MODE(vp->id); - old_port_sel = vop2->old_port_sel; - port_sel = old_port_sel; + port_sel = vop2->old_port_sel; port_sel &= RK3568_OVL_PORT_SEL__SEL_PORT; if (vp0->nlayers) @@ -2188,8 +2185,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) port_sel |= FIELD_PREP(RK3588_OVL_PORT_SET__PORT3_MUX, 7); atv_layer_sel = vop2_readl(vop2, RK3568_OVL_LAYER_SEL); - old_layer_sel = vop2->old_layer_sel; - layer_sel = old_layer_sel; + layer_sel = vop2->old_layer_sel; ofs = 0; for (i = 0; i < vp->id; i++) @@ -2273,8 +2269,6 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) old_win->data->layer_sel_id[vp->id]); } - vop2->old_layer_sel = layer_sel; - vop2->old_port_sel = port_sel; /* * As the RK3568_OVL_LAYER_SEL and RK3568_OVL_PORT_SEL are shared by all Video Ports, * and the configuration take effect by one Video Port's vsync. @@ -2290,7 +2284,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) * of the new VP. */ - if (layer_sel != old_layer_sel && atv_layer_sel != old_layer_sel) { + if (layer_sel != vop2->old_layer_sel && atv_layer_sel != vop2->old_layer_sel) { cfg_done = vop2_readl(vop2, RK3568_REG_CFG_DONE); cfg_done &= (BIT(vop2->data->nr_vps) - 1); cfg_done &= ~BIT(vp->id); @@ -2298,20 +2292,23 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) * Changes of other VPs' overlays have not taken effect */ if (cfg_done) - rk3568_vop2_wait_for_layer_cfg_done(vop2, old_layer_sel); + rk3568_vop2_wait_for_layer_cfg_done(vop2, vop2->old_layer_sel); } - if (layer_sel != old_layer_sel || port_sel != old_port_sel) + if (layer_sel != vop2->old_layer_sel || port_sel != vop2->old_port_sel) ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id); vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl); - if (port_sel != old_port_sel) { + if (port_sel != vop2->old_port_sel) { + vop2->old_port_sel = port_sel; vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel); vop2_cfg_done(vp); rk3568_vop2_wait_for_port_mux_done(vop2); } + vop2->old_layer_sel = layer_sel; vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel); + mutex_unlock(&vop2->ovl_lock); } -- 2.53.0 From cristian.ciocaltea at collabora.com Mon May 4 11:23:58 2026 From: cristian.ciocaltea at collabora.com (Cristian Ciocaltea) Date: Mon, 04 May 2026 21:23:58 +0300 Subject: [PATCH 0/5] drm/rockchip: vop2: Fix layer cfg done timeout on multi-output setups Message-ID: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> On RK3588/RK3568 boards with multiple active display outputs, start/stop transitions may trigger a timeout during overlay layer configuration: rockchip-drm display-subsystem: [drm] *ERROR* wait layer cfg done timeout The shared OVL_LAYER_SEL and OVL_PORT_SEL shadow registers are committed to the active configuration at the vsync of whichever Video Port is selected by LAYERSEL_REGDONE_SEL. When two Video Ports race through atomic commits, rk3568_vop2_setup_layer_mixer() has two issues that cause the wait to poll for a value the hardware might not be able to produce. Patch 1 fixes passing the wrong target to the wait function, since the expected value was already overwritten with the current VP's new layer_sel before reaching the wait. Patch 2 moves the wait before the LAYERSEL_REGDONE_SEL switch, so the previous VP's vsync can still latch the pending configuration. Patches 3 through 5 contain only minor follow-up cleanup. Signed-off-by: Cristian Ciocaltea --- Cristian Ciocaltea (5): drm/rockchip: vop2: Fix wrong wait target in layer cfg done check drm/rockchip: vop2: Wait for layer cfg done before switching LAYERSEL_REGDONE_SEL drm/rockchip: vop2: Delay old_{layer|port}_sel updates in setup_layer_mixer() drm/rockchip: vop2: Drop redundant zero-init in setup_layer_mixer() drm/rockchip: vop2: Use vop2->old_layer_sel directly in wait_for_layer_cfg_done() drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 46 +++++++++++++--------------- 1 file changed, 22 insertions(+), 24 deletions(-) --- base-commit: d4c14903bf5e28e740516c4fbb7db01e0dedf3af change-id: 20260504-vop2-layer-cfg-tmout-73617a0a103c From cristian.ciocaltea at collabora.com Mon May 4 11:24:00 2026 From: cristian.ciocaltea at collabora.com (Cristian Ciocaltea) Date: Mon, 04 May 2026 21:24:00 +0300 Subject: [PATCH 2/5] drm/rockchip: vop2: Wait for layer cfg done before switching LAYERSEL_REGDONE_SEL In-Reply-To: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> References: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> Message-ID: <20260504-vop2-layer-cfg-tmout-v1-2-730226a7331e@collabora.com> LAYERSEL_REGDONE_SEL mask of RK3568_OVL_CTRL register controls which Video Port (VP) vsync latches the shared RK3568_OVL_{LAYER|PORT}_SEL shadow registers into the active configuration. rk3568_vop2_setup_layer_mixer() overwrites LAYERSEL_REGDONE_SEL to the current VP ID before waiting for the previous VP layer configuration to take effect. As a consequence, the previous VP vsync can no longer trigger the latch, so the wait polls a value that might never appear. Move the layer cfg done wait before the RK3568_OVL_CTRL write so the previous VP vsync can still commit the pending configuration. Fixes: 3e89a8c68354 ("drm/rockchip: vop2: Fix the update of LAYER/PORT select registers when there are multi display output on rk3588/rk3568") Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index edca0fb16e08..5206f01ec787 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2289,15 +2289,6 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) * lead to the configuration of the previous VP being take effect along with the VSYNC * of the new VP. */ - if (layer_sel != old_layer_sel || port_sel != old_port_sel) - ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id); - vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl); - - if (port_sel != old_port_sel) { - vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel); - vop2_cfg_done(vp); - rk3568_vop2_wait_for_port_mux_done(vop2); - } if (layer_sel != old_layer_sel && atv_layer_sel != old_layer_sel) { cfg_done = vop2_readl(vop2, RK3568_REG_CFG_DONE); @@ -2310,6 +2301,16 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) rk3568_vop2_wait_for_layer_cfg_done(vop2, old_layer_sel); } + if (layer_sel != old_layer_sel || port_sel != old_port_sel) + ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id); + vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl); + + if (port_sel != old_port_sel) { + vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel); + vop2_cfg_done(vp); + rk3568_vop2_wait_for_port_mux_done(vop2); + } + vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel); mutex_unlock(&vop2->ovl_lock); } -- 2.53.0 From cristian.ciocaltea at collabora.com Mon May 4 11:24:02 2026 From: cristian.ciocaltea at collabora.com (Cristian Ciocaltea) Date: Mon, 04 May 2026 21:24:02 +0300 Subject: [PATCH 4/5] drm/rockchip: vop2: Drop redundant zero-init in setup_layer_mixer() In-Reply-To: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> References: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> Message-ID: <20260504-vop2-layer-cfg-tmout-v1-4-730226a7331e@collabora.com> The layer_sel and atv_layer_sel local variables in rk3568_vop2_setup_layer_mixer() are unconditionally assigned from vop2->old_layer_sel and the RK3568_OVL_LAYER_SEL register read, respectively, before any use. Remove the superfluous zero-initializers. Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 1f5e8c2acecd..0849bd922ffb 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2134,9 +2134,9 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) { struct vop2 *vop2 = vp->vop2; struct drm_plane *plane; - u32 layer_sel = 0; + u32 layer_sel; u32 port_sel; - u32 atv_layer_sel = 0; + u32 atv_layer_sel; u8 layer_id; u8 old_layer_id; u8 layer_sel_id; -- 2.53.0 From cristian.ciocaltea at collabora.com Mon May 4 11:24:03 2026 From: cristian.ciocaltea at collabora.com (Cristian Ciocaltea) Date: Mon, 04 May 2026 21:24:03 +0300 Subject: [PATCH 5/5] drm/rockchip: vop2: Use vop2->old_layer_sel directly in wait_for_layer_cfg_done() In-Reply-To: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> References: <20260504-vop2-layer-cfg-tmout-v1-0-730226a7331e@collabora.com> Message-ID: <20260504-vop2-layer-cfg-tmout-v1-5-730226a7331e@collabora.com> After the old_layer_sel local was removed, the only caller of rk3568_vop2_wait_for_layer_cfg_done() already passes vop2->old_layer_sel as the expected value. Drop the redundant parameter and read the member directly inside the function. Signed-off-by: Cristian Ciocaltea --- drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c index 0849bd922ffb..1d8473a6dfd1 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -2115,7 +2115,7 @@ static u32 rk3568_vop2_read_layer_cfg(struct vop2 *vop2) return vop2_readl(vop2, RK3568_OVL_LAYER_SEL); } -static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2, u32 cfg) +static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2) { u32 atv_layer_cfg; int ret; @@ -2124,10 +2124,10 @@ static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2, u32 cfg) * Spin until the previous layer configuration is done. */ ret = readx_poll_timeout_atomic(rk3568_vop2_read_layer_cfg, vop2, atv_layer_cfg, - atv_layer_cfg == cfg, 10, 50 * 1000); + atv_layer_cfg == vop2->old_layer_sel, 10, 50 * 1000); if (ret) drm_err_ratelimited(vop2->drm, "wait layer cfg done timeout: 0x%x--0x%x\n", - atv_layer_cfg, cfg); + atv_layer_cfg, vop2->old_layer_sel); } static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) @@ -2292,7 +2292,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp) * Changes of other VPs' overlays have not taken effect */ if (cfg_done) - rk3568_vop2_wait_for_layer_cfg_done(vop2, vop2->old_layer_sel); + rk3568_vop2_wait_for_layer_cfg_done(vop2); } if (layer_sel != vop2->old_layer_sel || port_sel != vop2->old_port_sel) -- 2.53.0 From jic23 at kernel.org Mon May 4 11:26:29 2026 From: jic23 at kernel.org (Jonathan Cameron) Date: Mon, 4 May 2026 19:26:29 +0100 Subject: [PATCH V4 04/10] iio: imu: inv_icm42607: Add PM support for icm42607 In-Reply-To: <20260501221152.194251-5-macroalpha82@gmail.com> References: <20260501221152.194251-1-macroalpha82@gmail.com> <20260501221152.194251-5-macroalpha82@gmail.com> Message-ID: <20260504192629.64e2f1d9@jic23-huawei> On Fri, 1 May 2026 17:11:43 -0500 Chris Morgan wrote: > From: Chris Morgan > > Add power management support for the ICM42607 device driver. > > Signed-off-by: Chris Morgan I think this suffers from usual problem we get when runtime_pm handles a regulator that we are also registering to be turned off via devm. That devm path needs to check if device is already runtime suspended. Be careful about paths in probe() where we might check runtime suspend state that isn't set yet. https://sashiko.dev/#/patchset/20260501221152.194251-1-macroalpha82%40gmail.com Has quite a bit to say no the series in general. It may not always be right but some of the stuff in this patch is. > --- > drivers/iio/imu/inv_icm42607/inv_icm42607.h | 2 + > .../iio/imu/inv_icm42607/inv_icm42607_core.c | 177 ++++++++++++++++++ > .../iio/imu/inv_icm42607/inv_icm42607_i2c.c | 1 + > .../iio/imu/inv_icm42607/inv_icm42607_spi.c | 1 + > 4 files changed, 181 insertions(+) > > diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607.h b/drivers/iio/imu/inv_icm42607/inv_icm42607.h > index 763d731ccc60..7facc114adc5 100644 > --- a/drivers/iio/imu/inv_icm42607/inv_icm42607.h > +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607.h > @@ -11,6 +11,7 @@ > #include > #include > #include > +#include > #include > #include > > @@ -390,6 +391,7 @@ struct inv_icm42607_sensor_state { > typedef int (*inv_icm42607_bus_setup)(struct inv_icm42607_state *); > > extern const struct regmap_config inv_icm42607_regmap_config; > +extern const struct dev_pm_ops inv_icm42607_pm_ops; > > u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr); > > diff --git a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c > index 1ac3a573863c..af3784040b7a 100644 > --- a/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c > +++ b/drivers/iio/imu/inv_icm42607/inv_icm42607_core.c > @@ -9,6 +9,7 @@ > #include > #include > #include > +#include > #include > #include > #include > @@ -106,6 +107,62 @@ u32 inv_icm42607_odr_to_period(enum inv_icm42607_odr odr) > return odr_periods[odr]; > } > > +static int inv_icm42607_set_pwr_mgmt0(struct inv_icm42607_state *st, > + enum inv_icm42607_sensor_mode gyro, > + enum inv_icm42607_sensor_mode accel, > + bool temp, unsigned int *sleep_ms) > +{ > + enum inv_icm42607_sensor_mode oldgyro = st->conf.gyro.mode; > + enum inv_icm42607_sensor_mode oldaccel = st->conf.accel.mode; > + bool oldtemp = st->conf.temp_en; > + unsigned int sleepval; > + unsigned int val; > + int ret; > + > + if (gyro == oldgyro && accel == oldaccel && temp == oldtemp) > + return 0; > + > + val = FIELD_PREP(INV_ICM42607_PWR_MGMT0_GYRO_MODE_MASK, gyro); > + val |= FIELD_PREP(INV_ICM42607_PWR_MGMT0_ACCEL_MODE_MASK, accel); > + if (!temp) > + val |= INV_ICM42607_PWR_MGMT0_ACCEL_LP_CLK_SEL; > + ret = regmap_write(st->map, INV_ICM42607_REG_PWR_MGMT0, val); > + if (ret) > + return ret; > + > + st->conf.gyro.mode = gyro; > + st->conf.accel.mode = accel; > + st->conf.temp_en = temp; > + > + sleepval = 0; > + if (temp && !oldtemp) { > + if (sleepval < INV_ICM42607_TEMP_STARTUP_TIME_MS) > + sleepval = INV_ICM42607_TEMP_STARTUP_TIME_MS; > + } > + if (accel != oldaccel && oldaccel == INV_ICM42607_SENSOR_MODE_OFF) { > + usleep_range(200, 300); > + if (sleepval < INV_ICM42607_ACCEL_STARTUP_TIME_MS) > + sleepval = INV_ICM42607_ACCEL_STARTUP_TIME_MS; > + } > + if (gyro != oldgyro) { > + if (oldgyro == INV_ICM42607_SENSOR_MODE_OFF) { > + usleep_range(200, 300); > + if (sleepval < INV_ICM42607_GYRO_STARTUP_TIME_MS) > + sleepval = INV_ICM42607_GYRO_STARTUP_TIME_MS; > + } else if (gyro == INV_ICM42607_SENSOR_MODE_OFF) { > + if (sleepval < INV_ICM42607_GYRO_STOP_TIME_MS) > + sleepval = INV_ICM42607_GYRO_STOP_TIME_MS; > + } > + } > + > + if (sleep_ms) > + *sleep_ms = sleepval; > + else if (sleepval) > + msleep(sleepval); > + > + return 0; > +} > + > int inv_icm42607_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, > unsigned int writeval, unsigned int *readval) > { > @@ -313,10 +370,130 @@ int inv_icm42607_core_probe(struct regmap *regmap, int chip, > if (ret) > return ret; > > + ret = devm_pm_runtime_set_active_enabled(dev); This will interact with the status setup you need as mentioned above for devm code to work right. > + if (ret) > + return ret; > + > + pm_runtime_set_autosuspend_delay(dev, INV_ICM42607_SUSPEND_DELAY_MS); > + pm_runtime_use_autosuspend(dev); > + > return 0; > } > EXPORT_SYMBOL_NS_GPL(inv_icm42607_core_probe, "IIO_ICM42607"); > > +/* > + * Suspend saves sensors state and turns everything off. > + * Check first if runtime suspend has not already done the job. > + */ > +static int inv_icm42607_suspend(struct device *dev) > +{ > + struct inv_icm42607_state *st = dev_get_drvdata(dev); > + struct device *accel_dev; > + bool wakeup; > + int accel_conf; > + int ret = 0; Always set before use so no need to init here. > + > + guard(mutex)(&st->lock); > + > + st->suspended.gyro = st->conf.gyro.mode; > + st->suspended.accel = st->conf.accel.mode; > + st->suspended.temp = st->conf.temp_en; > + if (pm_runtime_suspended(dev)) > + return 0; > + > + /* keep chip on and wake-up capable if APEX and wakeup on */ > + accel_dev = &st->indio_accel->dev; Never set at this point in series. (Sashiko raised this) > + wakeup = st->apex.on && device_may_wakeup(accel_dev); > + if (wakeup) { > + /* keep accel on and setup irq for wakeup */ > + accel_conf = st->conf.accel.mode; > + enable_irq_wake(st->irq); > + disable_irq(st->irq); > + } else { > + accel_conf = INV_ICM42607_SENSOR_MODE_OFF; > + } > + > + ret = inv_icm42607_set_pwr_mgmt0(st, INV_ICM42607_SENSOR_MODE_OFF, > + INV_ICM42607_SENSOR_MODE_OFF, false, > + NULL); > + if (ret) > + return ret; > + > + if (!wakeup) > + regulator_disable(st->vddio_supply); > + > + return 0; > +} From jonas at kwiboo.se Mon May 4 12:10:41 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:41 +0000 Subject: [PATCH v4 04/17] drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-5-jonas@kwiboo.se> Use the passed mode instead of mixing use of passed mode and the stored previous_mode in dw_hdmi_setup(). The passed mode is currenly always the previous_mode. Also fix a small typo and add a variable to help shorten a code line. Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v4: No change v3: Collect r-b tag v2: Update commit message, s/type/typo/ --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 4ecad0d2eeaa..f028a4b71aad 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2258,6 +2258,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, const struct drm_connector *connector, const struct drm_display_mode *mode) { + const struct drm_display_info *display = &connector->display_info; int ret; hdmi_disable_overflow_interrupts(hdmi); @@ -2303,12 +2304,10 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, hdmi->hdmi_data.video_mode.mdataenablepolarity = true; /* HDMI Initialization Step B.1 */ - hdmi_av_composer(hdmi, &connector->display_info, mode); + hdmi_av_composer(hdmi, display, mode); - /* HDMI Initializateion Step B.2 */ - ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, - &connector->display_info, - &hdmi->previous_mode); + /* HDMI Initialization Step B.2 */ + ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, display, mode); if (ret) return ret; hdmi->phy.enabled = true; -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:38 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:38 +0000 Subject: [PATCH v4 01/17] drm: bridge: dw_hdmi: Disable scrambler feature when not supported In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-2-jonas@kwiboo.se> The scrambler feature can be left enabled when hotplugging from a sink and mode that require scrambling to a sink that does not support SCDC or scrambling. Typically a blank screen or 'no signal' message can be observed after using a HDMI 2.0 4K at 60Hz mode and then hotplugging to a sink that only support HDMI 1.4. Fix this by disabling the scrambler feature when SCDC is not supported. Fixes: 264fce6cc2c1 ("drm/bridge: dw-hdmi: Add SCDC and TMDS Scrambling support") Reported-by: Christopher Obbard Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v4: No change v3: Collect r-b tag v2: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index f4a1ebb79716..248454c45d6b 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2135,6 +2135,8 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, HDMI_MC_SWRSTZ); drm_scdc_set_scrambling(hdmi->curr_conn, 0); } + } else if (hdmi->version >= 0x200a) { + hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL); } /* Set up horizontal active pixel width */ -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:37 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:37 +0000 Subject: [PATCH v4 00/17] drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup Message-ID: <20260504191059.275928-1-jonas@kwiboo.se> This is a revival of an old dw-hdmi series and is the first series part of a new effort to upstream old LibreELEC HDMI 2.0 patches for Rockchip RK33xx devices. This series ensure poweron/poweroff and CEC phys addr invalidation is happening under drm mode_config mutex lock, and also ensure EDID is updated after a HPD low voltage pulse by changing to debounce hotplug processing. These changes have mainly been tested on Rockchip RK3328, RK3399 and RK3568 devices. Rockchip uses the dw-hdmi connector, so this could use some more testing with drivers that use the bridge connector. Testing with a Rock Pi 4 (RK3399) using a Reaspberry Pi Monitor with Linux kms client console using drm.debug=0xe should log something like following: Power cycle monitor using the power button: [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 [CONNECTOR:68:HDMI-A-1] Same epoch counter 10 Cable unplugged: [CONNECTOR:68:HDMI-A-1] EDID changed, epoch counter 11 [CONNECTOR:68:HDMI-A-1] status updated from connected to disconnected [CONNECTOR:68:HDMI-A-1] Changed epoch counter 10 => 12 [CONNECTOR:68:HDMI-A-1] generating connector hotplug event [CONNECTOR:68:HDMI-A-1] Sent hotplug event Cable connected: [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 [CONNECTOR:68:HDMI-A-1] status updated from disconnected to connected [CONNECTOR:68:HDMI-A-1] Changed epoch counter 12 => 13 [CONNECTOR:68:HDMI-A-1] generating connector hotplug event [CONNECTOR:68:HDMI-A-1] Sent hotplug event This series has evolved into an initial part of a larger multi series effort to: - drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup - drm/bridge: dw-hdmi: Improve input/output bus format handling - drm/bridge: dw-hdmi: Convert to a HDMI bridge and use of bridge connector - drm/bridge: dw-hdmi: Add and use tmds_char_rate_valid() plat data ops - drm/meson: hdmi: Misc cleanup and use CEC notifier helpers - phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops - drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format - drm/rockchip: dw_hdmi: Enable YCbCr and Deep Color modes Link to snapshot: https://github.com/Kwiboo/linux-rockchip/commits/next-20260430-rk-hdmi-v2/ Changes in v4: - Change to use generic CEC notifier helpers - Disable/mask hpd_work until enable_hpd()/hpd_enable() - Read connector status directly from HW regs in hpd_work - Continued rework of HDP and RXSENSE interrupt handling - Collect r-b tags - Rebased on next-20260430 Link to v3: https://lore.kernel.org/r/ Changes in v3: - Rework EDID refresh handling to closer match bridge connector - Use delayed work to debounce HPD processing - Update commit messages - Collect r-b tags - Rebased on next-20260401 Link to v2: https://lore.kernel.org/r/20240908132823.3308029-1-jonas at kwiboo.se/ Changes in v2: - Add patch to disable scrambler feature when not supported - Add patch to only notify connected status on HPD interrupt - Update commit messages - Collect r-b tags - Rebased on next-20240906 Link to v1: https://lore.kernel.org/r/20240611155108.1436502-1-jonas at kwiboo.se/ Jonas Karlman (17): drm: bridge: dw_hdmi: Disable scrambler feature when not supported drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode drm: bridge: dw_hdmi: Fold poweron and setup functions drm: bridge: dw_hdmi: Remove previous_mode and mode_set drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect drm: bridge: dw_hdmi: Remove cec_notifier_mutex drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio drm: bridge: dw_hdmi: Use generic CEC notifier helpers drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 1 - drivers/gpu/drm/bridge/synopsys/Kconfig | 1 + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 436 ++++++-------------- drivers/gpu/drm/meson/meson_dw_hdmi.c | 3 - drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 2 - drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 - include/drm/bridge/dw_hdmi.h | 6 - 7 files changed, 134 insertions(+), 317 deletions(-) -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:39 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:39 +0000 Subject: [PATCH v4 02/17] drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-3-jonas@kwiboo.se> drm_helper_hpd_irq_event() and drm_bridge_hpd_notify() may incorrectly be called with a connected status when HPD is high and RX sense is changed. This typically happens when the HDMI cable is unplugged, shortly before the HPD is changed to low. The original intent of commit da09daf88108 ("drm: bridge: dw_hdmi: only trigger hotplug event on link change") was to signal hotplug event at correct interrupt states. Based on the commit message the intent was to trigger hotplug event: - when HPD goes high (plugin) - when both HPD and RX sense has gone low (plugout) However, following interrupt state changes can typically be observed when the HDMI cable is unplugged: - RX interrupt: HPD=high RX=low -> triggers a connected event - HPD interrupt: HPD=low RX=low -> triggers a disconnected event Fix this by only notify connected status on the HPD interrupt when HPD is going high, not on the RX sense interrupt when RX sense is changed. After this a connected event should be triggered when HPD=high at HPD interrupt, and a disconnected event should be triggered when both HPD=low and RX=low at either HPD or RX interrupt. Fixes: da09daf88108 ("drm: bridge: dw_hdmi: only trigger hotplug event on link change") Reviewed-by: Nicolas Frattaroli Signed-off-by: Jonas Karlman --- v4: Collect r-b tag v3: Update commit message v2: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 248454c45d6b..0647ec6632d8 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3157,7 +3157,8 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) mutex_unlock(&hdmi->cec_notifier_mutex); } - if (phy_stat & HDMI_PHY_HPD) + if ((intr_stat & HDMI_IH_PHY_STAT0_HPD) && + (phy_stat & HDMI_PHY_HPD)) status = connector_status_connected; if (!(phy_stat & (HDMI_PHY_HPD | HDMI_PHY_RX_SENSE))) -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:40 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:40 +0000 Subject: [PATCH v4 03/17] drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-4-jonas@kwiboo.se> Change to only call poweron/poweroff from atomic_enable/atomic_disable funcs instead of trying to be clever by keeping a bridge_is_on state and poweron/off in the hotplug irq handler. The bridge is already enabled/disabled depending on connection state with the call to drm_helper_hpd_irq_event() in hotplug irq handler. A benefit of this is that drm mode_config mutex is always held at poweron/off, something that may reduce the need for the dw-hdmi mutex. Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v4: No change v3: Collect r-b tag v2: Update commit message --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 33 ++--------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 0647ec6632d8..4ecad0d2eeaa 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -171,7 +171,6 @@ struct dw_hdmi { enum drm_connector_force force; /* mutex-protected force state */ struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */ bool disabled; /* DRM has disabled our bridge */ - bool bridge_is_on; /* indicates the bridge is on */ bool rxsense; /* rxsense state */ u8 phy_mask; /* desired phy int mask settings */ u8 mc_clkdis; /* clock disable register */ @@ -2400,8 +2399,6 @@ static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) static void dw_hdmi_poweron(struct dw_hdmi *hdmi) { - hdmi->bridge_is_on = true; - /* * The curr_conn field is guaranteed to be valid here, as this function * is only be called when !hdmi->disabled. @@ -2415,30 +2412,6 @@ static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) hdmi->phy.ops->disable(hdmi, hdmi->phy.data); hdmi->phy.enabled = false; } - - hdmi->bridge_is_on = false; -} - -static void dw_hdmi_update_power(struct dw_hdmi *hdmi) -{ - int force = hdmi->force; - - if (hdmi->disabled) { - force = DRM_FORCE_OFF; - } else if (force == DRM_FORCE_UNSPECIFIED) { - if (hdmi->rxsense) - force = DRM_FORCE_ON; - else - force = DRM_FORCE_OFF; - } - - if (force == DRM_FORCE_OFF) { - if (hdmi->bridge_is_on) - dw_hdmi_poweroff(hdmi); - } else { - if (!hdmi->bridge_is_on) - dw_hdmi_poweron(hdmi); - } } /* @@ -2563,7 +2536,6 @@ static void dw_hdmi_connector_force(struct drm_connector *connector) mutex_lock(&hdmi->mutex); hdmi->force = connector->force; - dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); } @@ -2988,7 +2960,7 @@ static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); hdmi->disabled = true; hdmi->curr_conn = NULL; - dw_hdmi_update_power(hdmi); + dw_hdmi_poweroff(hdmi); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, false); mutex_unlock(&hdmi->mutex); @@ -3006,7 +2978,7 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); hdmi->disabled = false; hdmi->curr_conn = connector; - dw_hdmi_update_power(hdmi); + dw_hdmi_poweron(hdmi); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, true); mutex_unlock(&hdmi->mutex); @@ -3106,7 +3078,6 @@ void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense) if (hpd) hdmi->rxsense = true; - dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); } mutex_unlock(&hdmi->mutex); -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:42 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:42 +0000 Subject: [PATCH v4 05/17] drm: bridge: dw_hdmi: Fold poweron and setup functions In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-6-jonas@kwiboo.se> Fold the poweron and setup functions into one function and use the adjusted_mode directly from the new crtc_state to remove the need of storing previous_mode. Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v4: No change v3: Collect r-b tag v2: No change --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index f028a4b71aad..7211beffd59e 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2254,9 +2254,9 @@ static void hdmi_disable_overflow_interrupts(struct dw_hdmi *hdmi) HDMI_IH_MUTE_FC_STAT2); } -static int dw_hdmi_setup(struct dw_hdmi *hdmi, - const struct drm_connector *connector, - const struct drm_display_mode *mode) +static int dw_hdmi_poweron(struct dw_hdmi *hdmi, + const struct drm_connector *connector, + const struct drm_display_mode *mode) { const struct drm_display_info *display = &connector->display_info; int ret; @@ -2396,15 +2396,6 @@ static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE); } -static void dw_hdmi_poweron(struct dw_hdmi *hdmi) -{ - /* - * The curr_conn field is guaranteed to be valid here, as this function - * is only be called when !hdmi->disabled. - */ - dw_hdmi_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode); -} - static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) { if (hdmi->phy.enabled) { @@ -2969,15 +2960,19 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_state *state) { struct dw_hdmi *hdmi = bridge->driver_private; + const struct drm_display_mode *mode; struct drm_connector *connector; + struct drm_crtc *crtc; connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); + crtc = drm_atomic_get_new_connector_state(state, connector)->crtc; + mode = &drm_atomic_get_new_crtc_state(state, crtc)->adjusted_mode; mutex_lock(&hdmi->mutex); hdmi->disabled = false; hdmi->curr_conn = connector; - dw_hdmi_poweron(hdmi); + dw_hdmi_poweron(hdmi, connector, mode); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, true); mutex_unlock(&hdmi->mutex); -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:43 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:43 +0000 Subject: [PATCH v4 06/17] drm: bridge: dw_hdmi: Remove previous_mode and mode_set In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-7-jonas@kwiboo.se> With the use of adjusted_mode directly from the crtc_state there is no longer a need to store a copy in previous_mode, remove it and the now unneeded mode_set ops. Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v4: No change v3: Collect r-b tag v2: No change --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 7211beffd59e..a287bf56bd9f 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -156,8 +156,6 @@ struct dw_hdmi { bool enabled; } phy; - struct drm_display_mode previous_mode; - struct i2c_adapter *ddc; void __iomem *regs; bool sink_is_hdmi; @@ -167,7 +165,7 @@ struct dw_hdmi { struct pinctrl_state *default_state; struct pinctrl_state *unwedge_state; - struct mutex mutex; /* for state below and previous_mode */ + struct mutex mutex; /* for state below */ enum drm_connector_force force; /* mutex-protected force state */ struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */ bool disabled; /* DRM has disabled our bridge */ @@ -2928,20 +2926,6 @@ dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge, return mode_status; } -static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge, - const struct drm_display_mode *orig_mode, - const struct drm_display_mode *mode) -{ - struct dw_hdmi *hdmi = bridge->driver_private; - - mutex_lock(&hdmi->mutex); - - /* Store the display mode for plugin/DKMS poweron events */ - drm_mode_copy(&hdmi->previous_mode, mode); - - mutex_unlock(&hdmi->mutex); -} - static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, struct drm_atomic_state *state) { @@ -3005,7 +2989,6 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { .atomic_get_input_bus_fmts = dw_hdmi_bridge_atomic_get_input_bus_fmts, .atomic_enable = dw_hdmi_bridge_atomic_enable, .atomic_disable = dw_hdmi_bridge_atomic_disable, - .mode_set = dw_hdmi_bridge_mode_set, .mode_valid = dw_hdmi_bridge_mode_valid, .detect = dw_hdmi_bridge_detect, .edid_read = dw_hdmi_bridge_edid_read, -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:44 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:44 +0000 Subject: [PATCH v4 07/17] drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-8-jonas@kwiboo.se> Wait until the connector detect ops is called to invalidate CEC phys addr instead of doing it directly from the irq handler. Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v4: No change v3: No change v2: Collect r-b tag --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index a287bf56bd9f..059f1d241fef 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2472,7 +2472,17 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force) { struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); - return dw_hdmi_detect(hdmi); + enum drm_connector_status status; + + status = dw_hdmi_detect(hdmi); + + if (status == connector_status_disconnected) { + mutex_lock(&hdmi->cec_notifier_mutex); + cec_notifier_phys_addr_invalidate(hdmi->cec_notifier); + mutex_unlock(&hdmi->cec_notifier_mutex); + } + + return status; } static int dw_hdmi_connector_get_modes(struct drm_connector *connector) @@ -3099,12 +3109,6 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) phy_stat & HDMI_PHY_HPD, phy_stat & HDMI_PHY_RX_SENSE); - if ((phy_stat & (HDMI_PHY_RX_SENSE | HDMI_PHY_HPD)) == 0) { - mutex_lock(&hdmi->cec_notifier_mutex); - cec_notifier_phys_addr_invalidate(hdmi->cec_notifier); - mutex_unlock(&hdmi->cec_notifier_mutex); - } - if ((intr_stat & HDMI_IH_PHY_STAT0_HPD) && (phy_stat & HDMI_PHY_HPD)) status = connector_status_connected; -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:45 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:45 +0000 Subject: [PATCH v4 08/17] drm: bridge: dw_hdmi: Remove cec_notifier_mutex In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-9-jonas@kwiboo.se> With CEC phys addr invalidation moved away from the irq handler there is no longer a need for cec_notifier_mutex, remove it. Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v4: No change v3: No change v2: Collect r-b tag --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 059f1d241fef..fc4f255c2a2f 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -189,7 +189,6 @@ struct dw_hdmi { void (*enable_audio)(struct dw_hdmi *hdmi); void (*disable_audio)(struct dw_hdmi *hdmi); - struct mutex cec_notifier_mutex; struct cec_notifier *cec_notifier; hdmi_codec_plugged_cb plugged_cb; @@ -2476,11 +2475,8 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force) status = dw_hdmi_detect(hdmi); - if (status == connector_status_disconnected) { - mutex_lock(&hdmi->cec_notifier_mutex); + if (status == connector_status_disconnected) cec_notifier_phys_addr_invalidate(hdmi->cec_notifier); - mutex_unlock(&hdmi->cec_notifier_mutex); - } return status; } @@ -2594,9 +2590,7 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) if (!notifier) return -ENOMEM; - mutex_lock(&hdmi->cec_notifier_mutex); hdmi->cec_notifier = notifier; - mutex_unlock(&hdmi->cec_notifier_mutex); return 0; } @@ -2910,10 +2904,8 @@ static void dw_hdmi_bridge_detach(struct drm_bridge *bridge) { struct dw_hdmi *hdmi = bridge->driver_private; - mutex_lock(&hdmi->cec_notifier_mutex); cec_notifier_conn_unregister(hdmi->cec_notifier); hdmi->cec_notifier = NULL; - mutex_unlock(&hdmi->cec_notifier_mutex); } static enum drm_mode_status @@ -3316,7 +3308,6 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, mutex_init(&hdmi->mutex); mutex_init(&hdmi->audio_mutex); - mutex_init(&hdmi->cec_notifier_mutex); spin_lock_init(&hdmi->audio_lock); ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:47 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:47 +0000 Subject: [PATCH v4 10/17] drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-11-jonas@kwiboo.se> Update connector EDID and CEC phys addr from detect and force funcs to ensure that userspace always have access to latest read EDID after a sink use a HPD low voltage pulse to indicate that EDID has changed. With EDID being updated in detect and force funcs, there should no longer be a need to re-read EDID in get_modes funcs, so drop it. This change make the dw-hdmi connector work more closely like the bridge connector does with a hdmi bridge. Reviewed-by: Nicolas Frattaroli Signed-off-by: Jonas Karlman --- v4: Move last_connector_result assign in force ops to this patch, Collect r-b tag v3: Reworked 'Update EDID during hotplug processing' patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 25 ++++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 0b56ad7316e3..47616c11fcd7 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2473,33 +2473,36 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); const struct drm_edid *drm_edid; + if (status == connector_status_disconnected) { + drm_edid_connector_update(connector, NULL); + cec_notifier_phys_addr_invalidate(hdmi->cec_notifier); + return; + } + drm_edid = dw_hdmi_edid_read(hdmi, connector); drm_edid_connector_update(connector, drm_edid); drm_edid_free(drm_edid); - cec_notifier_set_phys_addr(hdmi->cec_notifier, - connector->display_info.source_physical_address); + if (status == connector_status_connected) + cec_notifier_set_phys_addr(hdmi->cec_notifier, + connector->display_info.source_physical_address); } static enum drm_connector_status dw_hdmi_connector_detect(struct drm_connector *connector, bool force) { - struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, - connector); + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); enum drm_connector_status status; status = dw_hdmi_detect(hdmi); - if (status == connector_status_disconnected) - cec_notifier_phys_addr_invalidate(hdmi->cec_notifier); + dw_hdmi_connector_status_update(connector, status); return status; } static int dw_hdmi_connector_get_modes(struct drm_connector *connector) { - dw_hdmi_connector_status_update(connector, connector->status); - return drm_edid_connector_add_modes(connector); } @@ -2529,13 +2532,15 @@ static int dw_hdmi_connector_atomic_check(struct drm_connector *connector, static void dw_hdmi_connector_force(struct drm_connector *connector) { - struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, - connector); + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); mutex_lock(&hdmi->mutex); hdmi->force = connector->force; + hdmi->last_connector_result = connector->status; dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); + + dw_hdmi_connector_status_update(connector, connector->status); } static const struct drm_connector_funcs dw_hdmi_connector_funcs = { -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:46 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:46 +0000 Subject: [PATCH v4 09/17] drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-10-jonas@kwiboo.se> Move connector EDID update and CEC phys addr handling to a helper function as a preparation before moving EDID refresh from get_modes funcs to detect/force funcs. Reviewed-by: Nicolas Frattaroli Signed-off-by: Jonas Karlman --- v4: Collect r-b tag v3: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 30 +++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index fc4f255c2a2f..0b56ad7316e3 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2466,6 +2466,21 @@ static const struct drm_edid *dw_hdmi_edid_read(struct dw_hdmi *hdmi, * DRM Connector Operations */ +static void +dw_hdmi_connector_status_update(struct drm_connector *connector, + enum drm_connector_status status) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); + const struct drm_edid *drm_edid; + + drm_edid = dw_hdmi_edid_read(hdmi, connector); + drm_edid_connector_update(connector, drm_edid); + drm_edid_free(drm_edid); + + cec_notifier_set_phys_addr(hdmi->cec_notifier, + connector->display_info.source_physical_address); +} + static enum drm_connector_status dw_hdmi_connector_detect(struct drm_connector *connector, bool force) { @@ -2483,20 +2498,9 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force) static int dw_hdmi_connector_get_modes(struct drm_connector *connector) { - struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, - connector); - const struct drm_edid *drm_edid; - int ret; + dw_hdmi_connector_status_update(connector, connector->status); - drm_edid = dw_hdmi_edid_read(hdmi, connector); - - drm_edid_connector_update(connector, drm_edid); - cec_notifier_set_phys_addr(hdmi->cec_notifier, - connector->display_info.source_physical_address); - ret = drm_edid_connector_add_modes(connector); - drm_edid_free(drm_edid); - - return ret; + return drm_edid_connector_add_modes(connector); } static int dw_hdmi_connector_atomic_check(struct drm_connector *connector, -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:48 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:48 +0000 Subject: [PATCH v4 11/17] drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-12-jonas@kwiboo.se> drm_edid_connector_update() is being called from bridge connector funcs and from detect and force funcs for dw-hdmi connector. Change to use is_hdmi and has_audio from display_info directly instead of keeping our own state in sink_is_hdmi and sink_has_audio. Also remove the old and unused edid struct member and related define. Reviewed-by: Neil Armstrong Reviewed-by: Nicolas Frattaroli Signed-off-by: Jonas Karlman --- v4: Collect r-b tag v3: No change v2: Collect r-b tag --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 32 ++++------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 47616c11fcd7..4c712aa07a89 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -46,8 +46,6 @@ #define DDC_CI_ADDR 0x37 #define DDC_SEGMENT_ADDR 0x30 -#define HDMI_EDID_LEN 512 - /* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */ #define SCDC_MIN_SOURCE_VERSION 0x1 @@ -147,8 +145,6 @@ struct dw_hdmi { int vic; - u8 edid[HDMI_EDID_LEN]; - struct { const struct dw_hdmi_phy_ops *ops; const char *name; @@ -158,8 +154,6 @@ struct dw_hdmi { struct i2c_adapter *ddc; void __iomem *regs; - bool sink_is_hdmi; - bool sink_has_audio; struct pinctrl *pinctrl; struct pinctrl_state *default_state; @@ -2056,7 +2050,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, HDMI_FC_INVIDCONF_IN_I_P_INTERLACED : HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE; - inv_val |= hdmi->sink_is_hdmi ? + inv_val |= display->is_hdmi ? HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE : HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE; @@ -2292,7 +2286,7 @@ static int dw_hdmi_poweron(struct dw_hdmi *hdmi, if (hdmi->hdmi_data.enc_out_bus_format == MEDIA_BUS_FMT_FIXED) hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; - hdmi->hdmi_data.rgb_limited_range = hdmi->sink_is_hdmi && + hdmi->hdmi_data.rgb_limited_range = display->is_hdmi && drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED; @@ -2312,7 +2306,7 @@ static int dw_hdmi_poweron(struct dw_hdmi *hdmi, /* HDMI Initialization Step B.3 */ dw_hdmi_enable_video_path(hdmi); - if (hdmi->sink_has_audio) { + if (display->has_audio) { dev_dbg(hdmi->dev, "sink has audio support\n"); /* HDMI Initialization Step E - Configure audio */ @@ -2321,7 +2315,7 @@ static int dw_hdmi_poweron(struct dw_hdmi *hdmi, } /* not for DVI mode */ - if (hdmi->sink_is_hdmi) { + if (display->is_hdmi) { dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__); /* HDMI Initialization Step F - Configure AVI InfoFrame */ @@ -2435,29 +2429,13 @@ static const struct drm_edid *dw_hdmi_edid_read(struct dw_hdmi *hdmi, struct drm_connector *connector) { const struct drm_edid *drm_edid; - const struct edid *edid; if (!hdmi->ddc) return NULL; drm_edid = drm_edid_read_ddc(connector, hdmi->ddc); - if (!drm_edid) { + if (!drm_edid) dev_dbg(hdmi->dev, "failed to get edid\n"); - return NULL; - } - - /* - * FIXME: This should use connector->display_info.is_hdmi and - * connector->display_info.has_audio from a path that has read the EDID - * and called drm_edid_connector_update(). - */ - edid = drm_edid_raw(drm_edid); - - dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n", - edid->width_cm, edid->height_cm); - - hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); - hdmi->sink_has_audio = drm_detect_monitor_audio(edid); return drm_edid; } -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:49 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:49 +0000 Subject: [PATCH v4 12/17] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-13-jonas@kwiboo.se> The commit 8b1a8f8b2002 ("drm/display: add CEC helpers code") added generic CEC helpers to be used by HDMI drivers. Replace the open-coded CEC notifier handling with use of the generic CEC notifier helpers. Ensure DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER is also selected when DRM_DW_HDMI_CEC is enabled so that the CEC helpers is available. Signed-off-by: Jonas Karlman --- v4: New patch --- drivers/gpu/drm/bridge/synopsys/Kconfig | 1 + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 32 ++++------------------- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig index a46df7583bcf..e6723af03b43 100644 --- a/drivers/gpu/drm/bridge/synopsys/Kconfig +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig @@ -49,6 +49,7 @@ config DRM_DW_HDMI_CEC depends on DRM_DW_HDMI select CEC_CORE select CEC_NOTIFIER + select DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER help Support the CE interface which is part of the Synopsys Designware HDMI block. diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 4c712aa07a89..0aa29b92327e 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -23,12 +23,11 @@ #include #include -#include - #include #include #include +#include #include #include #include @@ -183,8 +182,6 @@ struct dw_hdmi { void (*enable_audio)(struct dw_hdmi *hdmi); void (*disable_audio)(struct dw_hdmi *hdmi); - struct cec_notifier *cec_notifier; - hdmi_codec_plugged_cb plugged_cb; struct device *codec_dev; enum drm_connector_status last_connector_result; @@ -2453,7 +2450,7 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, if (status == connector_status_disconnected) { drm_edid_connector_update(connector, NULL); - cec_notifier_phys_addr_invalidate(hdmi->cec_notifier); + drm_connector_cec_phys_addr_invalidate(connector); return; } @@ -2462,8 +2459,7 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, drm_edid_free(drm_edid); if (status == connector_status_connected) - cec_notifier_set_phys_addr(hdmi->cec_notifier, - connector->display_info.source_physical_address); + drm_connector_cec_phys_addr_set(connector); } static enum drm_connector_status @@ -2539,8 +2535,6 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) { struct drm_connector *connector = &hdmi->connector; - struct cec_connector_info conn_info; - struct cec_notifier *notifier; if (hdmi->version >= 0x200a) connector->ycbcr_420_allowed = @@ -2571,15 +2565,8 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) drm_connector_attach_encoder(connector, hdmi->bridge.encoder); - cec_fill_conn_info_from_drm(&conn_info, connector); - - notifier = cec_notifier_conn_register(hdmi->dev, NULL, &conn_info); - if (!notifier) - return -ENOMEM; - - hdmi->cec_notifier = notifier; - - return 0; + return drmm_connector_hdmi_cec_notifier_register(connector, NULL, + hdmi->dev); } /* ----------------------------------------------------------------------------- @@ -2887,14 +2874,6 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge, return dw_hdmi_connector_create(hdmi); } -static void dw_hdmi_bridge_detach(struct drm_bridge *bridge) -{ - struct dw_hdmi *hdmi = bridge->driver_private; - - cec_notifier_conn_unregister(hdmi->cec_notifier); - hdmi->cec_notifier = NULL; -} - static enum drm_mode_status dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *info, @@ -2972,7 +2951,6 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_reset = drm_atomic_helper_bridge_reset, .attach = dw_hdmi_bridge_attach, - .detach = dw_hdmi_bridge_detach, .atomic_check = dw_hdmi_bridge_atomic_check, .atomic_get_output_bus_fmts = dw_hdmi_bridge_atomic_get_output_bus_fmts, .atomic_get_input_bus_fmts = dw_hdmi_bridge_atomic_get_input_bus_fmts, -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:50 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:50 +0000 Subject: [PATCH v4 13/17] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-14-jonas@kwiboo.se> HDMI Specification Version 1.4b chapter 8.5 mentions: An HDMI Sink shall not assert high voltage level on its Hot Plug Detect pin when the E-EDID is not available for reading. A Source may use a high voltage level Hot Plug Detect signal to initiate the reading of E-EDID data. An HDMI Sink shall indicate any change to the contents of the E-EDID by driving a low voltage level pulse on the Hot Plug Detect pin. This pulse shall be at least 100 msec. Use a delayed work to debounce reacting on HPD events to better handle a HPD low voltage level pulse when a sink changes the EDID. The delayed work is only active between enable_hpd()/hpd_enable() and disable_hpd()/hpd_disable() calls from core, i.e. enabled after attach/bind/resume and disabled before detach/unbind/suspend. The 1100 msec hotplug debounce timeout was arbitrarily picked to match other drivers using same const, and testing using a Raspberry Pi Monitor seem to use a 200-300 msec pulse when going from standby to power on state. Signed-off-by: Jonas Karlman --- v4: Disable/mask delayed_work until enable_hpd()/hpd_enable(), Read connector status directly from HW regs in hpd_work v3: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 59 +++++++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 0aa29b92327e..193bdba65758 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -50,6 +50,8 @@ #define HDMI14_MAX_TMDSCLK 340000000 +#define HOTPLUG_DEBOUNCE_MS 1100 + static const u16 csc_coeff_default[3][4] = { { 0x2000, 0x0000, 0x0000, 0x0000 }, { 0x0000, 0x2000, 0x0000, 0x0000 }, @@ -185,6 +187,7 @@ struct dw_hdmi { hdmi_codec_plugged_cb plugged_cb; struct device *codec_dev; enum drm_connector_status last_connector_result; + struct delayed_work hpd_work; }; const struct dw_hdmi_plat_data *dw_hdmi_to_plat_data(struct dw_hdmi *hdmi) @@ -2517,6 +2520,20 @@ static void dw_hdmi_connector_force(struct drm_connector *connector) dw_hdmi_connector_status_update(connector, connector->status); } +static void dw_hdmi_connector_enable_hpd(struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); + + enable_delayed_work(&hdmi->hpd_work); +} + +static void dw_hdmi_connector_disable_hpd(struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); + + disable_delayed_work_sync(&hdmi->hpd_work); +} + static const struct drm_connector_funcs dw_hdmi_connector_funcs = { .fill_modes = drm_helper_probe_single_connector_modes, .detect = dw_hdmi_connector_detect, @@ -2530,6 +2547,8 @@ static const struct drm_connector_funcs dw_hdmi_connector_funcs = { static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { .get_modes = dw_hdmi_connector_get_modes, .atomic_check = dw_hdmi_connector_atomic_check, + .enable_hpd = dw_hdmi_connector_enable_hpd, + .disable_hpd = dw_hdmi_connector_disable_hpd, }; static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) @@ -2946,6 +2965,20 @@ static const struct drm_edid *dw_hdmi_bridge_edid_read(struct drm_bridge *bridge return dw_hdmi_edid_read(hdmi, connector); } +static void dw_hdmi_bridge_hpd_enable(struct drm_bridge *bridge) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + enable_delayed_work(&hdmi->hpd_work); +} + +static void dw_hdmi_bridge_hpd_disable(struct drm_bridge *bridge) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + disable_delayed_work_sync(&hdmi->hpd_work); +} + static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, @@ -2959,6 +2992,8 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { .mode_valid = dw_hdmi_bridge_mode_valid, .detect = dw_hdmi_bridge_detect, .edid_read = dw_hdmi_bridge_edid_read, + .hpd_enable = dw_hdmi_bridge_hpd_enable, + .hpd_disable = dw_hdmi_bridge_hpd_disable, }; /* ----------------------------------------------------------------------------- @@ -3079,10 +3114,8 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) status == connector_status_connected ? "plugin" : "plugout"); - if (hdmi->bridge.dev) { - drm_helper_hpd_irq_event(hdmi->bridge.dev); - drm_bridge_hpd_notify(&hdmi->bridge, status); - } + mod_delayed_work(system_percpu_wq, &hdmi->hpd_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); } hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); @@ -3092,6 +3125,19 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) return IRQ_HANDLED; } +static void dw_hdmi_hpd_work(struct work_struct *work) +{ + struct dw_hdmi *hdmi = container_of(work, struct dw_hdmi, hpd_work.work); + enum drm_connector_status status; + + if (WARN_ON(!hdmi->bridge.dev)) + return; + + drm_helper_hpd_irq_event(hdmi->bridge.dev); + status = dw_hdmi_phy_read_hpd(hdmi, hdmi->phy.data); + drm_bridge_hpd_notify(&hdmi->bridge, status); +} + static const struct dw_hdmi_phy_data dw_hdmi_phys[] = { { .type = DW_HDMI_PHY_DWC_HDMI_TX_PHY, @@ -3376,6 +3422,9 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, goto err_res; } + INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_hpd_work); + disable_delayed_work(&hdmi->hpd_work); + ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq, dw_hdmi_irq, IRQF_SHARED, dev_name(dev), hdmi); @@ -3508,6 +3557,8 @@ EXPORT_SYMBOL_GPL(dw_hdmi_probe); void dw_hdmi_remove(struct dw_hdmi *hdmi) { + disable_delayed_work_sync(&hdmi->hpd_work); + drm_bridge_remove(&hdmi->bridge); if (hdmi->audio && !IS_ERR(hdmi->audio)) -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:51 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:51 +0000 Subject: [PATCH v4 14/17] drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-15-jonas@kwiboo.se> The commit aeac23bda87f ("drm: bridge/dw_hdmi: improve HDMI enable/disable handling") added use of PHY RXSENSE indications to avoid triggering a full enable/disable of the HDMI block when a sink use a HPD low voltage level pulse to indicate changes of the EDID. HDMI Specification Version 1.4b chapter 8.5 mentions: An HDMI Sink shall indicate any change to the contents of the E-EDID by driving a low voltage level pulse on the Hot Plug Detect pin. This pulse shall be at least 100 msec. A delayed work is now used to debounce reacting on a HPD low voltage level pulse when a sink changes the EDID. Remove RXSENSE handling to simplify the HPD interrupt handling and instead depend on the debounced HPD work to refresh EDID. This also ensures the initial HPD interrupt polarity is based on current HPD status to avoid an unnecessary interrupt from being triggered immediately at probe or resume when a sink is connected. Signed-off-by: Jonas Karlman --- v4: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 140 +++------------------- 1 file changed, 16 insertions(+), 124 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 193bdba65758..e3ebb0dfdaf2 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -161,11 +161,7 @@ struct dw_hdmi { struct pinctrl_state *unwedge_state; struct mutex mutex; /* for state below */ - enum drm_connector_force force; /* mutex-protected force state */ struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */ - bool disabled; /* DRM has disabled our bridge */ - bool rxsense; /* rxsense state */ - u8 phy_mask; /* desired phy int mask settings */ u8 mc_clkdis; /* clock disable register */ spinlock_t audio_lock; @@ -196,14 +192,6 @@ const struct dw_hdmi_plat_data *dw_hdmi_to_plat_data(struct dw_hdmi *hdmi) } EXPORT_SYMBOL_GPL(dw_hdmi_to_plat_data); -#define HDMI_IH_PHY_STAT0_RX_SENSE \ - (HDMI_IH_PHY_STAT0_RX_SENSE0 | HDMI_IH_PHY_STAT0_RX_SENSE1 | \ - HDMI_IH_PHY_STAT0_RX_SENSE2 | HDMI_IH_PHY_STAT0_RX_SENSE3) - -#define HDMI_PHY_RX_SENSE \ - (HDMI_PHY_RX_SENSE0 | HDMI_PHY_RX_SENSE1 | \ - HDMI_PHY_RX_SENSE2 | HDMI_PHY_RX_SENSE3) - static inline void hdmi_writeb(struct dw_hdmi *hdmi, u8 val, int offset) { regmap_write(hdmi->regm, offset << hdmi->reg_shift, val); @@ -1702,36 +1690,25 @@ EXPORT_SYMBOL_GPL(dw_hdmi_phy_read_hpd); void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data, bool force, bool disabled, bool rxsense) { - u8 old_mask = hdmi->phy_mask; - - if (force || disabled || !rxsense) - hdmi->phy_mask |= HDMI_PHY_RX_SENSE; - else - hdmi->phy_mask &= ~HDMI_PHY_RX_SENSE; - - if (old_mask != hdmi->phy_mask) - hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0); } EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd); void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data) { /* - * Configure the PHY RX SENSE and HPD interrupts polarities and clear - * any pending interrupt. + * Configure the PHY HPD interrupt polarity based on current HPD status + * and clear any pending interrupt. */ - hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, HDMI_PHY_POL0); - hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, - HDMI_IH_PHY_STAT0); + hdmi_modb(hdmi, hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ? + 0 : HDMI_PHY_HPD, HDMI_PHY_HPD, HDMI_PHY_POL0); + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0); /* Enable cable hot plug irq. */ - hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0); + hdmi_writeb(hdmi, ~HDMI_PHY_HPD, HDMI_PHY_MASK0); /* Clear and unmute interrupts. */ - hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, - HDMI_IH_PHY_STAT0); - hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE), - HDMI_IH_MUTE_PHY_STAT0); + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0); + hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); } EXPORT_SYMBOL_GPL(dw_hdmi_phy_setup_hpd); @@ -2395,26 +2372,6 @@ static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) } } -/* - * Adjust the detection of RXSENSE according to whether we have a forced - * connection mode enabled, or whether we have been disabled. There is - * no point processing RXSENSE interrupts if we have a forced connection - * state, or DRM has us disabled. - * - * We also disable rxsense interrupts when we think we're disconnected - * to avoid floating TDMS signals giving false rxsense interrupts. - * - * Note: we still need to listen for HPD interrupts even when DRM has us - * disabled so that we can detect a connect event. - */ -static void dw_hdmi_update_phy_mask(struct dw_hdmi *hdmi) -{ - if (hdmi->phy.ops->update_hpd) - hdmi->phy.ops->update_hpd(hdmi, hdmi->phy.data, - hdmi->force, hdmi->disabled, - hdmi->rxsense); -} - static enum drm_connector_status dw_hdmi_detect(struct dw_hdmi *hdmi) { enum drm_connector_status result; @@ -2512,9 +2469,7 @@ static void dw_hdmi_connector_force(struct drm_connector *connector) struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); mutex_lock(&hdmi->mutex); - hdmi->force = connector->force; hdmi->last_connector_result = connector->status; - dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); dw_hdmi_connector_status_update(connector, connector->status); @@ -2919,10 +2874,8 @@ static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, struct dw_hdmi *hdmi = bridge->driver_private; mutex_lock(&hdmi->mutex); - hdmi->disabled = true; hdmi->curr_conn = NULL; dw_hdmi_poweroff(hdmi); - dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, false); mutex_unlock(&hdmi->mutex); } @@ -2941,10 +2894,8 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, mode = &drm_atomic_get_new_crtc_state(state, crtc)->adjusted_mode; mutex_lock(&hdmi->mutex); - hdmi->disabled = false; hdmi->curr_conn = connector; dw_hdmi_poweron(hdmi, connector, mode); - dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, true); mutex_unlock(&hdmi->mutex); } @@ -3038,78 +2989,23 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense) { - mutex_lock(&hdmi->mutex); - - if (!hdmi->force) { - /* - * If the RX sense status indicates we're disconnected, - * clear the software rxsense status. - */ - if (!rx_sense) - hdmi->rxsense = false; - - /* - * Only set the software rxsense status when both - * rxsense and hpd indicates we're connected. - * This avoids what seems to be bad behaviour in - * at least iMX6S versions of the phy. - */ - if (hpd) - hdmi->rxsense = true; - - dw_hdmi_update_phy_mask(hdmi); - } - mutex_unlock(&hdmi->mutex); } EXPORT_SYMBOL_GPL(dw_hdmi_setup_rx_sense); static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) { struct dw_hdmi *hdmi = dev_id; - u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat; - enum drm_connector_status status = connector_status_unknown; + u8 intr_stat; intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); - phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0); - phy_stat = hdmi_readb(hdmi, HDMI_PHY_STAT0); - - phy_pol_mask = 0; - if (intr_stat & HDMI_IH_PHY_STAT0_HPD) - phy_pol_mask |= HDMI_PHY_HPD; - if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE0) - phy_pol_mask |= HDMI_PHY_RX_SENSE0; - if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE1) - phy_pol_mask |= HDMI_PHY_RX_SENSE1; - if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE2) - phy_pol_mask |= HDMI_PHY_RX_SENSE2; - if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE3) - phy_pol_mask |= HDMI_PHY_RX_SENSE3; - - if (phy_pol_mask) - hdmi_modb(hdmi, ~phy_int_pol, phy_pol_mask, HDMI_PHY_POL0); + if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { + enum drm_connector_status status; - /* - * RX sense tells us whether the TDMS transmitters are detecting - * load - in other words, there's something listening on the - * other end of the link. Use this to decide whether we should - * power on the phy as HPD may be toggled by the sink to merely - * ask the source to re-read the EDID. - */ - if (intr_stat & - (HDMI_IH_PHY_STAT0_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) { - dw_hdmi_setup_rx_sense(hdmi, - phy_stat & HDMI_PHY_HPD, - phy_stat & HDMI_PHY_RX_SENSE); - - if ((intr_stat & HDMI_IH_PHY_STAT0_HPD) && - (phy_stat & HDMI_PHY_HPD)) - status = connector_status_connected; - - if (!(phy_stat & (HDMI_PHY_HPD | HDMI_PHY_RX_SENSE))) - status = connector_status_disconnected; - } + /* Set HPD interrupt polarity based on current HPD status. */ + status = dw_hdmi_phy_read_hpd(hdmi, hdmi->phy.data); + hdmi_modb(hdmi, status == connector_status_connected ? + 0 : HDMI_PHY_HPD, HDMI_PHY_HPD, HDMI_PHY_POL0); - if (status != connector_status_unknown) { dev_dbg(hdmi->dev, "EVENT=%s\n", status == connector_status_connected ? "plugin" : "plugout"); @@ -3119,8 +3015,7 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) } hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); - hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE), - HDMI_IH_MUTE_PHY_STAT0); + hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); return IRQ_HANDLED; } @@ -3311,9 +3206,6 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, hdmi->dev = dev; hdmi->sample_rate = 48000; hdmi->channels = 2; - hdmi->disabled = true; - hdmi->rxsense = true; - hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE); hdmi->mc_clkdis = 0x7f; hdmi->last_connector_result = connector_status_disconnected; -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:52 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:52 +0000 Subject: [PATCH v4 15/17] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-16-jonas@kwiboo.se> The dw_hdmi_setup_rx_sense() helper is empty and no longer needed after recent RXSENSE and HPD rework, remove it. Signed-off-by: Jonas Karlman --- v4: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 5 ----- drivers/gpu/drm/meson/meson_dw_hdmi.c | 3 --- include/drm/bridge/dw_hdmi.h | 2 -- 3 files changed, 10 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index e3ebb0dfdaf2..60b887249eba 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2987,11 +2987,6 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) return ret; } -void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense) -{ -} -EXPORT_SYMBOL_GPL(dw_hdmi_setup_rx_sense); - static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) { struct dw_hdmi *hdmi = dev_id; diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index fef1702acb14..2a8756da569b 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -524,9 +524,6 @@ static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id) if (stat & HDMITX_TOP_INTR_HPD_RISE) hpd_connected = true; - dw_hdmi_setup_rx_sense(dw_hdmi->hdmi, hpd_connected, - hpd_connected); - drm_helper_hpd_irq_event(dw_hdmi->bridge->dev); drm_bridge_hpd_notify(dw_hdmi->bridge, hpd_connected ? connector_status_connected diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index 8500dd4f99d8..a612b9fa6dbb 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -186,8 +186,6 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev, void dw_hdmi_resume(struct dw_hdmi *hdmi); -void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense); - int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn, struct device *codec_dev); void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm); -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:54 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:54 +0000 Subject: [PATCH v4 17/17] drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-18-jonas@kwiboo.se> The bottom half IRQ handler only modify delay of or queue a delayed work used for HPD handling. The mod_delayed_work() called is documented as being safe to call from any context including IRQ handler. Merge top and bottom half IRQ handlers to simplify IRQ handling now that HPD event is handled using a delayed work. Signed-off-by: Jonas Karlman --- v4: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 32 +++++++---------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index c0ed067154c7..a42c45ff0ade 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2971,24 +2971,12 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) if (hdmi->i2c) ret = dw_hdmi_i2c_irq(hdmi); - intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0) & HDMI_IH_PHY_STAT0_HPD; if (intr_stat) { - hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); - return IRQ_WAKE_THREAD; - } - - return ret; -} - -static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) -{ - struct dw_hdmi *hdmi = dev_id; - u8 intr_stat; - - intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); - if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { enum drm_connector_status status; + hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); + /* Set HPD interrupt polarity based on current HPD status. */ status = dw_hdmi_phy_read_hpd(hdmi, hdmi->phy.data); hdmi_modb(hdmi, status == connector_status_connected ? @@ -3000,12 +2988,13 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) mod_delayed_work(system_percpu_wq, &hdmi->hpd_work, msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); - } - hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); - hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); + hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); + hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); + ret = IRQ_HANDLED; + } - return IRQ_HANDLED; + return ret; } static void dw_hdmi_hpd_work(struct work_struct *work) @@ -3305,9 +3294,8 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_hpd_work); disable_delayed_work(&hdmi->hpd_work); - ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq, - dw_hdmi_irq, IRQF_SHARED, - dev_name(dev), hdmi); + ret = devm_request_irq(dev, irq, dw_hdmi_hardirq, IRQF_SHARED, + dev_name(dev), hdmi); if (ret) goto err_res; -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:53 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:53 +0000 Subject: [PATCH v4 16/17] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-17-jonas@kwiboo.se> The dw_hdmi_phy_update_hpd() helper is empty and no longer needed after recent RXSENSE and HPD rework, remove it. Signed-off-by: Jonas Karlman --- v4: New patch --- drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 1 - drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 7 ------- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 2 -- drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 -- include/drm/bridge/dw_hdmi.h | 4 ---- 5 files changed, 16 deletions(-) diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c index 8e8cfd66f23b..20d389dbfdc5 100644 --- a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c +++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c @@ -78,7 +78,6 @@ static const struct dw_hdmi_phy_ops imx8mp_hdmi_phy_ops = { .disable = imx8mp_hdmi_phy_disable, .setup_hpd = im8mp_hdmi_phy_setup_hpd, .read_hpd = dw_hdmi_phy_read_hpd, - .update_hpd = dw_hdmi_phy_update_hpd, }; static int imx8mp_dw_hdmi_bind(struct device *dev) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 60b887249eba..c0ed067154c7 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -1687,12 +1687,6 @@ enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi, } EXPORT_SYMBOL_GPL(dw_hdmi_phy_read_hpd); -void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data, - bool force, bool disabled, bool rxsense) -{ -} -EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd); - void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data) { /* @@ -1716,7 +1710,6 @@ static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = { .init = dw_hdmi_phy_init, .disable = dw_hdmi_phy_disable, .read_hpd = dw_hdmi_phy_read_hpd, - .update_hpd = dw_hdmi_phy_update_hpd, .setup_hpd = dw_hdmi_phy_setup_hpd, }; diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 0dc1eb5d2ae3..7136e713df2e 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -413,7 +413,6 @@ static const struct dw_hdmi_phy_ops rk3228_hdmi_phy_ops = { .init = dw_hdmi_rockchip_genphy_init, .disable = dw_hdmi_rockchip_genphy_disable, .read_hpd = dw_hdmi_phy_read_hpd, - .update_hpd = dw_hdmi_phy_update_hpd, .setup_hpd = dw_hdmi_rk3228_setup_hpd, }; @@ -449,7 +448,6 @@ static const struct dw_hdmi_phy_ops rk3328_hdmi_phy_ops = { .init = dw_hdmi_rockchip_genphy_init, .disable = dw_hdmi_rockchip_genphy_disable, .read_hpd = dw_hdmi_rk3328_read_hpd, - .update_hpd = dw_hdmi_phy_update_hpd, .setup_hpd = dw_hdmi_rk3328_setup_hpd, }; diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c index 4fa69c463dc4..2ac99b8ce8c4 100644 --- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c +++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c @@ -221,7 +221,6 @@ static const struct dw_hdmi_phy_ops sun8i_a83t_hdmi_phy_ops = { .init = sun8i_a83t_hdmi_phy_config, .disable = sun8i_a83t_hdmi_phy_disable, .read_hpd = dw_hdmi_phy_read_hpd, - .update_hpd = dw_hdmi_phy_update_hpd, .setup_hpd = dw_hdmi_phy_setup_hpd, }; @@ -395,7 +394,6 @@ static const struct dw_hdmi_phy_ops sun8i_h3_hdmi_phy_ops = { .init = sun8i_h3_hdmi_phy_config, .disable = sun8i_h3_hdmi_phy_disable, .read_hpd = dw_hdmi_phy_read_hpd, - .update_hpd = dw_hdmi_phy_update_hpd, .setup_hpd = dw_hdmi_phy_setup_hpd, }; diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index a612b9fa6dbb..10013b8d3adb 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -118,8 +118,6 @@ struct dw_hdmi_phy_ops { const struct drm_display_mode *mode); void (*disable)(struct dw_hdmi *hdmi, void *data); enum drm_connector_status (*read_hpd)(struct dw_hdmi *hdmi, void *data); - void (*update_hpd)(struct dw_hdmi *hdmi, void *data, - bool force, bool disabled, bool rxsense); void (*setup_hpd)(struct dw_hdmi *hdmi, void *data); }; @@ -213,8 +211,6 @@ void dw_hdmi_phy_gen2_reset(struct dw_hdmi *hdmi); enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi, void *data); -void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data, - bool force, bool disabled, bool rxsense); void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data); bool dw_hdmi_bus_fmt_is_420(struct dw_hdmi *hdmi); -- 2.54.0 From laurent.pinchart at ideasonboard.com Mon May 4 13:56:47 2026 From: laurent.pinchart at ideasonboard.com (Laurent Pinchart) Date: Mon, 4 May 2026 23:56:47 +0300 Subject: [PATCH v5 1/2] media: dt-bindings: rockchip,rk3568-mipi-csi2: add rk3588 compatible In-Reply-To: <20260305-rk3588-csi2rx-v5-1-3b7061d043ea@collabora.com> References: <20260305-rk3588-csi2rx-v5-0-3b7061d043ea@collabora.com> <20260305-rk3588-csi2rx-v5-1-3b7061d043ea@collabora.com> Message-ID: <20260504205647.GA1489398@killaraus.ideasonboard.com> Hi Michael, Thank you for the patch. On Tue, Apr 28, 2026 at 09:27:23AM +0200, Michael Riesch via B4 Relay wrote: > From: Michael Riesch > > The RK3588 MIPI CSI-2 receivers are compatible to the ones found in the > RK3568. Introduce a list of compatible variants and add the RK3588 variant > to it. > > Acked-by: Rob Herring (Arm) > Signed-off-by: Michael Riesch Reviewed-by: Laurent Pinchart > --- > .../devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml | 11 ++++++++--- > 1 file changed, 8 insertions(+), 3 deletions(-) > > diff --git a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml > index 4ac4a3b6f406..fbcf28e9e1da 100644 > --- a/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml > +++ b/Documentation/devicetree/bindings/media/rockchip,rk3568-mipi-csi2.yaml > @@ -16,9 +16,14 @@ description: > > properties: > compatible: > - enum: > - - fsl,imx93-mipi-csi2 > - - rockchip,rk3568-mipi-csi2 > + oneOf: > + - enum: > + - fsl,imx93-mipi-csi2 > + - rockchip,rk3568-mipi-csi2 > + - items: > + - enum: > + - rockchip,rk3588-mipi-csi2 > + - const: rockchip,rk3568-mipi-csi2 > > reg: > maxItems: 1 > -- Regards, Laurent Pinchart From neil.armstrong at linaro.org Mon May 4 14:21:55 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Mon, 4 May 2026 23:21:55 +0200 Subject: [PATCH v4 12/17] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260504191059.275928-13-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> <20260504191059.275928-13-jonas@kwiboo.se> Message-ID: <80fb7a31-3a85-48d5-a971-4e7632778f0c@linaro.org> On 5/4/26 21:10, Jonas Karlman wrote: > The commit 8b1a8f8b2002 ("drm/display: add CEC helpers code") added > generic CEC helpers to be used by HDMI drivers. > > Replace the open-coded CEC notifier handling with use of the generic CEC > notifier helpers. Ensure DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER is also > selected when DRM_DW_HDMI_CEC is enabled so that the CEC helpers is > available. > > Signed-off-by: Jonas Karlman > --- > v4: New patch > --- > drivers/gpu/drm/bridge/synopsys/Kconfig | 1 + > drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 32 ++++------------------- > 2 files changed, 6 insertions(+), 27 deletions(-) > > diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig > index a46df7583bcf..e6723af03b43 100644 > --- a/drivers/gpu/drm/bridge/synopsys/Kconfig > +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig > @@ -49,6 +49,7 @@ config DRM_DW_HDMI_CEC > depends on DRM_DW_HDMI > select CEC_CORE > select CEC_NOTIFIER > + select DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER > help > Support the CE interface which is part of the Synopsys > Designware HDMI block. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 4c712aa07a89..0aa29b92327e 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > @@ -23,12 +23,11 @@ > #include > #include > > -#include > - > #include > #include > > #include > +#include > #include > #include > #include > @@ -183,8 +182,6 @@ struct dw_hdmi { > void (*enable_audio)(struct dw_hdmi *hdmi); > void (*disable_audio)(struct dw_hdmi *hdmi); > > - struct cec_notifier *cec_notifier; > - > hdmi_codec_plugged_cb plugged_cb; > struct device *codec_dev; > enum drm_connector_status last_connector_result; > @@ -2453,7 +2450,7 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, > > if (status == connector_status_disconnected) { > drm_edid_connector_update(connector, NULL); > - cec_notifier_phys_addr_invalidate(hdmi->cec_notifier); > + drm_connector_cec_phys_addr_invalidate(connector); > return; > } > > @@ -2462,8 +2459,7 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, > drm_edid_free(drm_edid); > > if (status == connector_status_connected) > - cec_notifier_set_phys_addr(hdmi->cec_notifier, > - connector->display_info.source_physical_address); > + drm_connector_cec_phys_addr_set(connector); > } > > static enum drm_connector_status > @@ -2539,8 +2535,6 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = > static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) > { > struct drm_connector *connector = &hdmi->connector; > - struct cec_connector_info conn_info; > - struct cec_notifier *notifier; > > if (hdmi->version >= 0x200a) > connector->ycbcr_420_allowed = > @@ -2571,15 +2565,8 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) > > drm_connector_attach_encoder(connector, hdmi->bridge.encoder); > > - cec_fill_conn_info_from_drm(&conn_info, connector); > - > - notifier = cec_notifier_conn_register(hdmi->dev, NULL, &conn_info); > - if (!notifier) > - return -ENOMEM; > - > - hdmi->cec_notifier = notifier; > - > - return 0; > + return drmm_connector_hdmi_cec_notifier_register(connector, NULL, > + hdmi->dev); > } > > /* ----------------------------------------------------------------------------- > @@ -2887,14 +2874,6 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge, > return dw_hdmi_connector_create(hdmi); > } > > -static void dw_hdmi_bridge_detach(struct drm_bridge *bridge) > -{ > - struct dw_hdmi *hdmi = bridge->driver_private; > - > - cec_notifier_conn_unregister(hdmi->cec_notifier); > - hdmi->cec_notifier = NULL; > -} > - > static enum drm_mode_status > dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge, > const struct drm_display_info *info, > @@ -2972,7 +2951,6 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { > .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, > .atomic_reset = drm_atomic_helper_bridge_reset, > .attach = dw_hdmi_bridge_attach, > - .detach = dw_hdmi_bridge_detach, > .atomic_check = dw_hdmi_bridge_atomic_check, > .atomic_get_output_bus_fmts = dw_hdmi_bridge_atomic_get_output_bus_fmts, > .atomic_get_input_bus_fmts = dw_hdmi_bridge_atomic_get_input_bus_fmts, Reviewed-by: Neil Armstrong Thanks, Neil From dmitry.baryshkov at oss.qualcomm.com Mon May 4 17:45:01 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Tue, 5 May 2026 03:45:01 +0300 Subject: [PATCH v5 05/10] drm/bridge: dw-hdmi-qp: Add HDMI 2.0 SCDC scrambling and high TMDS clock ratio support In-Reply-To: <539f1bb9-6509-40c7-8692-bb3cdee33f91@collabora.com> References: <20260426-dw-hdmi-qp-scramb-v5-0-d778e70c317b@collabora.com> <20260426-dw-hdmi-qp-scramb-v5-5-d778e70c317b@collabora.com> <539f1bb9-6509-40c7-8692-bb3cdee33f91@collabora.com> Message-ID: On Tue, Apr 28, 2026 at 04:51:46PM +0300, Cristian Ciocaltea wrote: > Hi Dmitry, > > Thanks for taking the time to review the series! > > On 4/28/26 4:38 AM, Dmitry Baryshkov wrote: > > On Sun, Apr 26, 2026 at 03:20:17AM +0300, Cristian Ciocaltea wrote: > >> Enable HDMI 2.0 display modes (e.g. 4K at 60Hz) by adding SCDC management > >> for the high TMDS clock ratio and scrambling, required when the TMDS > >> character rate exceeds the 340 MHz HDMI 1.4b limit. > >> > >> A periodic work item monitors the sink's scrambling status to recover > >> from sink-side resets. On hotplug detect, if SCDC scrambling state is > >> out of sync with the driver, trigger a CRTC reset to re-establish the > >> link. > >> > >> Reject modes requiring TMDS rates above 600 MHz, as those fall in the > >> HDMI 2.1 FRL domain which is not supported. In no_hpd configurations, > >> further restrict to 340 MHz since SCDC requires a connected sink. > >> > >> Tested-by: Diederik de Haas > >> Tested-by: Maud Spierings > >> Signed-off-by: Cristian Ciocaltea > >> --- > >> drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 188 ++++++++++++++++++++++++--- > >> 1 file changed, 172 insertions(+), 16 deletions(-) > > > > My main issue with this patch (sorry) is that this adds yet another copy > > of SCDC-related helpers into the driver which already OP_HDMI and other > > helpers. > > > > >> + > >> + drm_atomic_helper_connector_hdmi_hotplug(connector, > >> + connector_status_connected); > > > > I don't see a forced hotplug event in the existing drivers. Why is it > > necessary? This function is being called from the detect() path. > > For some reason, without it, the connector seems to lose its state after reset: > > [ 2142.982967] dwhdmiqp-rockchip fde80000.hdmi: resetting crtc > [ 2142.994132] rockchip-drm display-subsystem: [drm] HDMI Sink doesn't support RGB, something's wrong. > [ 2143.140827] dwhdmiqp-rockchip fde80000.hdmi: dw_hdmi_qp_bridge_atomic_enable mode=DVI > > Will recheck if that's still the case after the HPD handling rework. I think so. If it doesn't help, please check that drm_bridge_connector_detect() is called for each hotplug event from the hardware. It should take care of parsing EDID (and thus updating HDMI Sink properties). -- With best wishes Dmitry From Simon at symple.nz Tue May 5 01:18:17 2026 From: Simon at symple.nz (Simon Wright) Date: Tue, 5 May 2026 08:18:17 +0000 Subject: drm/bridge: dw-hdmi-qp: seamless handoff sets wrong tmds_char_rate, breaking audio on strict HDMI 2.1 sinks Message-ID: [Resent in plain text ? first attempt was rejected by the list's HTML filter.] Hi, I have tracked down a bug in dw-hdmi-qp that silences audio on strict HDMI 2.1 sinks (tested: LG G3 OLED) when booting with HDMI seamless handoff from U-Boot. HDMI 2.0 sinks are unaffected. The root cause is a mismatch between the AVI InfoFrame colour depth and the ACR embedded CTS value. Hardware tested: Source: ArmSoM Sige5 (Rockchip RK3576) Armbian-edge kernel 7.0.2 (Linux mainline v7.0 plus Armbian board-enable patches; not a BSP kernel) Failing sink: LG G3 OLED 4K (HDMI 2.1, strict embedded-CTS mode) Passing sink: Various HDMI 2.0 TVs (self-measuring CTS, unaffected) The bug is structural and is expected to affect RK3588 under the same boot sequence. Root cause ---------- When the system boots with seamless handoff active, U-Boot has configured the PHY for 8-bit 1080p60 (TMDS = 148,500,000 Hz). The DRM connector state, negotiated from EDID, selects 10-bit deep colour (TMDS = 185,625,000 Hz). In dw_hdmi_qp_bridge_atomic_enable(): hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; This stores 185,625,000. The PHY ops->init() is called but the PHY is already running at 148,500,000 from U-Boot and is not reconfigured. The result is an inconsistency between what the sink is told over HDMI and what the wire actually carries: AVI InfoFrame: declares 10-bit deep colour at the connector-state TMDS rate (185.625 MHz) ACR embedded CTS: driver uses hdmi->tmds_char_rate = 185,625,000 Hz to compute CTS via dw_hdmi_qp_set_sample_rate(), producing CTS = 185,625 (internally consistent with the AVI declaration) Wire TMDS clock: PHY untouched from U-Boot, still running at 148,500,000 Hz (8-bit) The dmesg signature (full pr_err output, lower in this report) confirms both AVI and ACR are computed from the nominal 185.625 MHz value: "tmds=185625000 ... cts=185625". A standard HDMI 2.0 sink does not cross-check AVI against the wire TMDS in any way that matters for audio, so the mismatch is harmless. A strict HDMI 2.1 sink (LG G3) measures the wire TMDS and verifies it matches the AVI declaration; with declared 185.625 MHz vs measured 148.5 MHz, the check fails and audio is muted, permanently, until the next mode set. The N/CTS values themselves are correct CEA-861 ratios. At a wire TMDS of 148.5 MHz with N=6144, CTS=148,500 yields f_audio = 48 kHz; at a wire TMDS of 185.625 MHz with N=6144, CTS=185,625 also yields f_audio = 48 kHz. The driver picks one TMDS, computes a self-consistent N/CTS pair, and embeds it. The bug is solely that the TMDS the driver picks (connector-state nominal) differs from the TMDS on the wire (PHY actual). Dmesg diagnostic signature -------------------------- Failing (seamless handoff, DRM selects 10-bit 1080p from EDID): dwhdmiqp-rockchip 27da0000.hdmi: atomic_enable: \ tmds_candidate=185625000 is_hdmi=1 DW_HDMI_QP set_sample_rate: tmds=185625000 sr=48000 n=6144 cts=185625 [audio silent on LG G3] Working (8-bit mode forced, see workaround below): dwhdmiqp-rockchip 27da0000.hdmi: atomic_enable: \ tmds_candidate=148500000 is_hdmi=1 DW_HDMI_QP set_sample_rate: tmds=148500000 sr=48000 n=6144 cts=148500 [audio working on LG G3] The difference is entirely in which tmds_char_rate bridge_atomic_enable() receives. If the PHY and DRM state agree (both 148.5 MHz), audio works because what the AVI declares matches what the wire carries. If they disagree (DRM: 185.625 MHz, PHY: 148.5 MHz), the AVI declares 185.625 but the wire carries 148.5, and any sink that cross-checks the wire TMDS against the declared rate (e.g. LG G3 OLED) mutes audio. Confirmed workaround -------------------- In dw_hdmi_qp_bridge_tmds_char_rate_valid(), reject 185,625,000 Hz: if (rate == 185625000) return MODE_CLOCK_HIGH; This forces DRM to select 8-bit mode (148,500,000 Hz). bridge_atomic_enable() then stores the correct tmds_char_rate, the AVI InfoFrame declares 8-bit, the ACR CTS is computed correctly, and the LG G3 produces audio. Note: 185,625,000 Hz (1080p at 60 10-bit) is only selected via EDID negotiation during seamless handoff. A genuine modeset at that rate would correctly reconfigure the PHY and the AVI/ACR inconsistency would not arise. Blocking this rate in tmds_char_rate_valid() therefore has no correct-use-case cost on RK3576 with the current BSP. 4K at 60Hz (594 MHz) is unaffected and continues to work correctly, including audio. Proposed proper fix ------------------- The fundamental issue is that bridge_atomic_enable() accepts conn_state->hdmi.tmds_char_rate without verifying it against the PHY's actual output frequency. Option A (no display disruption): The HDPTX PHY PLL is already registered as a clock provider. On RK3576, rk3576.dtsi gives the hdptxphy node #clock-cells = <0>; VOP2 consumes it as "pll_hdmiphy0". If the bridge driver adds a new clk pointer (e.g. hdmi->tmds_clk) populated via devm_clk_get_optional() at probe and queries clk_get_rate() in bridge_atomic_enable(), it can detect the seamless-handoff mismatch and use the actual rate: /* hdmi->tmds_clk added in struct dw_hdmi_qp; populated at * probe via devm_clk_get_optional(dev, "tmds") or similar */ actual_rate = hdmi->tmds_clk ? clk_get_rate(hdmi->tmds_clk) : 0; if (actual_rate && actual_rate != conn_state->hdmi.tmds_char_rate) { dev_warn(hdmi->dev, "seamless handoff: PHY at %lu Hz, DRM selected %llu Hz; " "using actual PHY rate for audio\n", actual_rate, conn_state->hdmi.tmds_char_rate); hdmi->tmds_char_rate = actual_rate; } else { hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; } This corrects the ACR CTS and AVI InfoFrame without touching the PHY configuration or causing a display blank. Option B (correct but causes brief blank): If the driver detects a mismatch, call phy.ops->init() to bring the PHY into agreement with the DRM-negotiated state. All parameters become consistent at the cost of a single mode change on first enable after seamless handoff. This is arguably the more correct approach if the intent is to honour what the EDID reports. Option A is preferred for a seamless user experience. Option B is preferable if the seamless-handoff-inherited U-Boot mode is not otherwise suitable for production use. Relation to Collabora's HDMI 2.0 patch series ---------------------------------------------- The "Add HDMI 2.0 support to DW HDMI QP TX" series (v3, January 2026) addresses high-TMDS-clock-ratio and scrambling. It does not appear to address the seamless-handoff tmds_char_rate mismatch described here. If a revision of that series reads PHY state at probe time it may incidentally close this gap, but that is not a stated goal. Reviewers may wish to verify the seamless-handoff path is exercised in testing. Summary ------- Bug: bridge_atomic_enable() takes EDID-negotiated tmds_char_rate (185,625,000) while PHY runs at U-Boot rate (148,500,000); the AVI InfoFrame and the ACR CTS both reflect the connector-state nominal value, but the wire TMDS clock disagrees with what AVI declares Symptom: audio silent on any HDMI 2.1 sink that cross-checks wire TMDS against the AVI declaration; HDMI 2.0 sinks unaffected (no such cross-check) Scope: RK3576 confirmed; RK3588 structurally identical Not a bug: N/CTS table, IEC60958 channel status, ARC, ALSA path Workaround: reject 185,625,000 Hz in tmds_char_rate_valid() so DRM picks 8-bit 1080p (148,500,000 Hz) which matches the actual PHY rate Fix: compare conn_state->hdmi.tmds_char_rate against actual PHY clock at bridge_atomic_enable() and use the measured rate when they disagree (Option A) or reconfigure the PHY (Option B) I am happy to test patches on Sige5 + LG G3. Regards, Simon Symple Solutions, Dunedin, New Zealand From benjamin.gaignard at collabora.com Tue May 5 01:19:39 2026 From: benjamin.gaignard at collabora.com (Benjamin Gaignard) Date: Tue, 5 May 2026 10:19:39 +0200 Subject: [PATCH v4] media: verisilicon: Create AV1 helper library Message-ID: <20260505081939.17230-1-benjamin.gaignard@collabora.com> Regroup all none hardware related AV1 functions into a helper library. The goal is to avoid code duplication for future AV1 codecs. Tested on rock 5b board Fluster score remains the same 204/241. Signed-off-by: Benjamin Gaignard Reviewed-by: Nicolas Dufresne --- version 4: - change functions prototypes to use a structure. - rebased on v7.1-rc2 tag. drivers/media/platform/verisilicon/Makefile | 7 +- .../media/platform/verisilicon/hantro_av1.c | 782 ++++++++++++++ .../media/platform/verisilicon/hantro_av1.h | 62 ++ ...entropymode.c => hantro_av1_entropymode.c} | 18 +- ...entropymode.h => hantro_av1_entropymode.h} | 18 +- ...av1_filmgrain.c => hantro_av1_filmgrain.c} | 125 ++- .../verisilicon/hantro_av1_filmgrain.h | 48 + .../media/platform/verisilicon/hantro_hw.h | 7 +- .../verisilicon/rockchip_av1_filmgrain.h | 36 - .../verisilicon/rockchip_vpu981_hw_av1_dec.c | 977 ++---------------- .../platform/verisilicon/rockchip_vpu_hw.c | 7 +- 11 files changed, 1076 insertions(+), 1011 deletions(-) create mode 100644 drivers/media/platform/verisilicon/hantro_av1.c create mode 100644 drivers/media/platform/verisilicon/hantro_av1.h rename drivers/media/platform/verisilicon/{rockchip_av1_entropymode.c => hantro_av1_entropymode.c} (99%) rename drivers/media/platform/verisilicon/{rockchip_av1_entropymode.h => hantro_av1_entropymode.h} (95%) rename drivers/media/platform/verisilicon/{rockchip_av1_filmgrain.c => hantro_av1_filmgrain.c} (85%) create mode 100644 drivers/media/platform/verisilicon/hantro_av1_filmgrain.h delete mode 100644 drivers/media/platform/verisilicon/rockchip_av1_filmgrain.h diff --git a/drivers/media/platform/verisilicon/Makefile b/drivers/media/platform/verisilicon/Makefile index f6f019d04ff0..a1dd6c2d29be 100644 --- a/drivers/media/platform/verisilicon/Makefile +++ b/drivers/media/platform/verisilicon/Makefile @@ -19,7 +19,10 @@ hantro-vpu-y += \ hantro_hevc.o \ hantro_mpeg2.o \ hantro_vp8.o \ - hantro_vp9.o + hantro_vp9.o \ + hantro_av1.o \ + hantro_av1_filmgrain.o \ + hantro_av1_entropymode.o hantro-vpu-$(CONFIG_VIDEO_HANTRO_IMX8M) += \ imx8m_vpu_hw.o @@ -33,8 +36,6 @@ hantro-vpu-$(CONFIG_VIDEO_HANTRO_ROCKCHIP) += \ rockchip_vpu2_hw_mpeg2_dec.o \ rockchip_vpu2_hw_vp8_dec.o \ rockchip_vpu981_hw_av1_dec.o \ - rockchip_av1_filmgrain.o \ - rockchip_av1_entropymode.o \ rockchip_vpu_hw.o hantro-vpu-$(CONFIG_VIDEO_HANTRO_SUNXI) += \ diff --git a/drivers/media/platform/verisilicon/hantro_av1.c b/drivers/media/platform/verisilicon/hantro_av1.c new file mode 100644 index 000000000000..7258a749f7b5 --- /dev/null +++ b/drivers/media/platform/verisilicon/hantro_av1.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026, Collabora + * + * Author: Benjamin Gaignard + */ + +#include +#include +#include + +#include "hantro.h" +#include "hantro_av1.h" +#include "hantro_hw.h" + +#define GM_GLOBAL_MODELS_PER_FRAME 7 +#define GLOBAL_MODEL_TOTAL_SIZE (6 * 4 + 4 * 2) +#define GLOBAL_MODEL_SIZE ALIGN(GM_GLOBAL_MODELS_PER_FRAME * GLOBAL_MODEL_TOTAL_SIZE, 2048) +#define AV1_MAX_TILES 128 +#define AV1_TILE_INFO_SIZE (AV1_MAX_TILES * 16) +#define AV1_INVALID_IDX -1 +#define AV1_TILE_SIZE ALIGN(32 * 128, 4096) + +#define SUPERRES_SCALE_BITS 3 + +#define DIV_LUT_PREC_BITS 14 +#define DIV_LUT_BITS 8 +#define DIV_LUT_NUM BIT(DIV_LUT_BITS) +#define WARP_PARAM_REDUCE_BITS 6 +#define WARPEDMODEL_PREC_BITS 16 + +#define AV1_DIV_ROUND_UP_POW2(value, n) \ +({ \ + typeof(n) _n = n; \ + typeof(value) _value = value; \ + (_value + (BIT(_n) >> 1)) >> _n; \ +}) + +#define AV1_DIV_ROUND_UP_POW2_SIGNED(value, n) \ +({ \ + typeof(n) _n_ = n; \ + typeof(value) _value_ = value; \ + (((_value_) < 0) ? -AV1_DIV_ROUND_UP_POW2(-(_value_), (_n_)) \ + : AV1_DIV_ROUND_UP_POW2((_value_), (_n_))); \ +}) + +static const short div_lut[DIV_LUT_NUM + 1] = { + 16384, 16320, 16257, 16194, 16132, 16070, 16009, 15948, 15888, 15828, 15768, + 15709, 15650, 15592, 15534, 15477, 15420, 15364, 15308, 15252, 15197, 15142, + 15087, 15033, 14980, 14926, 14873, 14821, 14769, 14717, 14665, 14614, 14564, + 14513, 14463, 14413, 14364, 14315, 14266, 14218, 14170, 14122, 14075, 14028, + 13981, 13935, 13888, 13843, 13797, 13752, 13707, 13662, 13618, 13574, 13530, + 13487, 13443, 13400, 13358, 13315, 13273, 13231, 13190, 13148, 13107, 13066, + 13026, 12985, 12945, 12906, 12866, 12827, 12788, 12749, 12710, 12672, 12633, + 12596, 12558, 12520, 12483, 12446, 12409, 12373, 12336, 12300, 12264, 12228, + 12193, 12157, 12122, 12087, 12053, 12018, 11984, 11950, 11916, 11882, 11848, + 11815, 11782, 11749, 11716, 11683, 11651, 11619, 11586, 11555, 11523, 11491, + 11460, 11429, 11398, 11367, 11336, 11305, 11275, 11245, 11215, 11185, 11155, + 11125, 11096, 11067, 11038, 11009, 10980, 10951, 10923, 10894, 10866, 10838, + 10810, 10782, 10755, 10727, 10700, 10673, 10645, 10618, 10592, 10565, 10538, + 10512, 10486, 10460, 10434, 10408, 10382, 10356, 10331, 10305, 10280, 10255, + 10230, 10205, 10180, 10156, 10131, 10107, 10082, 10058, 10034, 10010, 9986, + 9963, 9939, 9916, 9892, 9869, 9846, 9823, 9800, 9777, 9754, 9732, + 9709, 9687, 9664, 9642, 9620, 9598, 9576, 9554, 9533, 9511, 9489, + 9468, 9447, 9425, 9404, 9383, 9362, 9341, 9321, 9300, 9279, 9259, + 9239, 9218, 9198, 9178, 9158, 9138, 9118, 9098, 9079, 9059, 9039, + 9020, 9001, 8981, 8962, 8943, 8924, 8905, 8886, 8867, 8849, 8830, + 8812, 8793, 8775, 8756, 8738, 8720, 8702, 8684, 8666, 8648, 8630, + 8613, 8595, 8577, 8560, 8542, 8525, 8508, 8490, 8473, 8456, 8439, + 8422, 8405, 8389, 8372, 8355, 8339, 8322, 8306, 8289, 8273, 8257, + 8240, 8224, 8208, 8192, +}; + +enum hantro_av1_tx_mode { + HANTRO_AV1_TX_MODE_ONLY_4X4 = 0, + HANTRO_AV1_TX_MODE_8X8 = 1, + HANTRO_AV1_TX_MODE_16x16 = 2, + HANTRO_AV1_TX_MODE_32x32 = 3, + HANTRO_AV1_TX_MODE_SELECT = 4, +}; + +enum hantro_av1_inter_prediction_filter_type { + HANTRO_AV1_EIGHT_TAP_SMOOTH = 0, + HANTRO_AV1_EIGHT_TAP = 1, + HANTRO_AV1_EIGHT_TAP_SHARP = 2, + HANTRO_AV1_BILINEAR = 3, + HANTRO_AV1_SWITCHABLE = 4, +}; + +int hantro_av1_get_hardware_tx_mode(enum v4l2_av1_tx_mode tx_mode) +{ + switch (tx_mode) { + case V4L2_AV1_TX_MODE_ONLY_4X4: + return HANTRO_AV1_TX_MODE_ONLY_4X4; + case V4L2_AV1_TX_MODE_LARGEST: + return HANTRO_AV1_TX_MODE_32x32; + case V4L2_AV1_TX_MODE_SELECT: + return HANTRO_AV1_TX_MODE_SELECT; + } + + return HANTRO_AV1_TX_MODE_32x32; +} + +int hantro_av1_get_hardware_mcomp_filt_type(int interpolation_filter) +{ + switch (interpolation_filter) { + case V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP: + return HANTRO_AV1_EIGHT_TAP; + case V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SMOOTH: + return HANTRO_AV1_EIGHT_TAP_SMOOTH; + case V4L2_AV1_INTERPOLATION_FILTER_EIGHTTAP_SHARP: + return HANTRO_AV1_EIGHT_TAP_SHARP; + case V4L2_AV1_INTERPOLATION_FILTER_BILINEAR: + return HANTRO_AV1_BILINEAR; + case V4L2_AV1_INTERPOLATION_FILTER_SWITCHABLE: + return HANTRO_AV1_SWITCHABLE; + } + + return HANTRO_AV1_EIGHT_TAP_SMOOTH; +} + +int hantro_av1_get_frame_index(struct hantro_ctx *ctx, int ref) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; + u64 timestamp; + int i, idx = frame->ref_frame_idx[ref]; + + if (idx >= V4L2_AV1_TOTAL_REFS_PER_FRAME || idx < 0) + return AV1_INVALID_IDX; + + timestamp = frame->reference_frame_ts[idx]; + for (i = 0; i < AV1_MAX_FRAME_BUF_COUNT; i++) { + if (!av1_dec->frame_refs[i].used) + continue; + if (av1_dec->frame_refs[i].timestamp == timestamp) + return i; + } + + return AV1_INVALID_IDX; +} + +int hantro_av1_get_order_hint(struct hantro_ctx *ctx, int ref) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + int idx = hantro_av1_get_frame_index(ctx, ref); + + if (idx != AV1_INVALID_IDX) + return av1_dec->frame_refs[idx].order_hint; + + return 0; +} + +int hantro_av1_frame_ref(struct hantro_ctx *ctx, u64 timestamp) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; + int i; + + for (i = 0; i < AV1_MAX_FRAME_BUF_COUNT; i++) { + int j; + + if (av1_dec->frame_refs[i].used) + continue; + + av1_dec->frame_refs[i].width = frame->frame_width_minus_1 + 1; + av1_dec->frame_refs[i].height = frame->frame_height_minus_1 + 1; + av1_dec->frame_refs[i].mi_cols = DIV_ROUND_UP(frame->frame_width_minus_1 + 1, 8); + av1_dec->frame_refs[i].mi_rows = DIV_ROUND_UP(frame->frame_height_minus_1 + 1, 8); + av1_dec->frame_refs[i].timestamp = timestamp; + av1_dec->frame_refs[i].frame_type = frame->frame_type; + av1_dec->frame_refs[i].order_hint = frame->order_hint; + av1_dec->frame_refs[i].vb2_ref = hantro_get_dst_buf(ctx); + + for (j = 0; j < V4L2_AV1_TOTAL_REFS_PER_FRAME; j++) + av1_dec->frame_refs[i].order_hints[j] = frame->order_hints[j]; + av1_dec->frame_refs[i].used = true; + av1_dec->current_frame_index = i; + + return i; + } + + return AV1_INVALID_IDX; +} + +static void hantro_av1_frame_unref(struct hantro_ctx *ctx, int idx) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + + if (idx >= 0) + av1_dec->frame_refs[idx].used = false; +} + +void hantro_av1_clean_refs(struct hantro_ctx *ctx) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + + int ref, idx; + + for (idx = 0; idx < AV1_MAX_FRAME_BUF_COUNT; idx++) { + u64 timestamp = av1_dec->frame_refs[idx].timestamp; + bool used = false; + + if (!av1_dec->frame_refs[idx].used) + continue; + + for (ref = 0; ref < V4L2_AV1_TOTAL_REFS_PER_FRAME; ref++) { + if (ctrls->frame->reference_frame_ts[ref] == timestamp) + used = true; + } + + if (!used) + hantro_av1_frame_unref(ctx, idx); + } +} + +size_t hantro_av1_luma_size(struct hantro_ctx *ctx) +{ + return ctx->ref_fmt.plane_fmt[0].bytesperline * ctx->ref_fmt.height; +} + +size_t hantro_av1_chroma_size(struct hantro_ctx *ctx) +{ + size_t cr_offset = hantro_av1_luma_size(ctx); + + return ALIGN((cr_offset * 3) / 2, 64); +} + +static void hantro_av1_tiles_free(struct hantro_ctx *ctx) +{ + struct hantro_dev *vpu = ctx->dev; + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + + if (av1_dec->db_data_col.cpu) + dma_free_coherent(vpu->dev, av1_dec->db_data_col.size, + av1_dec->db_data_col.cpu, + av1_dec->db_data_col.dma); + av1_dec->db_data_col.cpu = NULL; + + if (av1_dec->db_ctrl_col.cpu) + dma_free_coherent(vpu->dev, av1_dec->db_ctrl_col.size, + av1_dec->db_ctrl_col.cpu, + av1_dec->db_ctrl_col.dma); + av1_dec->db_ctrl_col.cpu = NULL; + + if (av1_dec->cdef_col.cpu) + dma_free_coherent(vpu->dev, av1_dec->cdef_col.size, + av1_dec->cdef_col.cpu, av1_dec->cdef_col.dma); + av1_dec->cdef_col.cpu = NULL; + + if (av1_dec->sr_col.cpu) + dma_free_coherent(vpu->dev, av1_dec->sr_col.size, + av1_dec->sr_col.cpu, av1_dec->sr_col.dma); + av1_dec->sr_col.cpu = NULL; + + if (av1_dec->lr_col.cpu) + dma_free_coherent(vpu->dev, av1_dec->lr_col.size, + av1_dec->lr_col.cpu, av1_dec->lr_col.dma); + av1_dec->lr_col.cpu = NULL; +} + +static int hantro_av1_tiles_reallocate(struct hantro_ctx *ctx) +{ + struct hantro_dev *vpu = ctx->dev; + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_av1_tile_info *tile_info = &ctrls->frame->tile_info; + unsigned int num_tile_cols = tile_info->tile_cols; + unsigned int height = ALIGN(ctrls->frame->frame_height_minus_1 + 1, 64); + unsigned int height_in_sb = height / 64; + unsigned int stripe_num = ((height + 8) + 63) / 64; + size_t size; + + if (av1_dec->db_data_col.size >= + ALIGN(height * 12 * ctx->bit_depth / 8, 128) * num_tile_cols) + return 0; + + hantro_av1_tiles_free(ctx); + + size = ALIGN(height * 12 * ctx->bit_depth / 8, 128) * num_tile_cols; + av1_dec->db_data_col.cpu = dma_alloc_coherent(vpu->dev, size, + &av1_dec->db_data_col.dma, + GFP_KERNEL); + if (!av1_dec->db_data_col.cpu) + goto buffer_allocation_error; + av1_dec->db_data_col.size = size; + + size = ALIGN(height * 2 * 16 / 4, 128) * num_tile_cols; + av1_dec->db_ctrl_col.cpu = dma_alloc_coherent(vpu->dev, size, + &av1_dec->db_ctrl_col.dma, + GFP_KERNEL); + if (!av1_dec->db_ctrl_col.cpu) + goto buffer_allocation_error; + av1_dec->db_ctrl_col.size = size; + + size = ALIGN(height_in_sb * 44 * ctx->bit_depth * 16 / 8, 128) * num_tile_cols; + av1_dec->cdef_col.cpu = dma_alloc_coherent(vpu->dev, size, + &av1_dec->cdef_col.dma, + GFP_KERNEL); + if (!av1_dec->cdef_col.cpu) + goto buffer_allocation_error; + av1_dec->cdef_col.size = size; + + size = ALIGN(height_in_sb * (3040 + 1280), 128) * num_tile_cols; + av1_dec->sr_col.cpu = dma_alloc_coherent(vpu->dev, size, + &av1_dec->sr_col.dma, + GFP_KERNEL); + if (!av1_dec->sr_col.cpu) + goto buffer_allocation_error; + av1_dec->sr_col.size = size; + + size = ALIGN(stripe_num * 1536 * ctx->bit_depth / 8, 128) * num_tile_cols; + av1_dec->lr_col.cpu = dma_alloc_coherent(vpu->dev, size, + &av1_dec->lr_col.dma, + GFP_KERNEL); + if (!av1_dec->lr_col.cpu) + goto buffer_allocation_error; + av1_dec->lr_col.size = size; + + av1_dec->num_tile_cols_allocated = num_tile_cols; + return 0; + +buffer_allocation_error: + hantro_av1_tiles_free(ctx); + return -ENOMEM; +} + +void hantro_av1_exit(struct hantro_ctx *ctx) +{ + struct hantro_dev *vpu = ctx->dev; + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + + if (av1_dec->global_model.cpu) + dma_free_coherent(vpu->dev, av1_dec->global_model.size, + av1_dec->global_model.cpu, + av1_dec->global_model.dma); + av1_dec->global_model.cpu = NULL; + + if (av1_dec->tile_info.cpu) + dma_free_coherent(vpu->dev, av1_dec->tile_info.size, + av1_dec->tile_info.cpu, + av1_dec->tile_info.dma); + av1_dec->tile_info.cpu = NULL; + + if (av1_dec->film_grain.cpu) + dma_free_coherent(vpu->dev, av1_dec->film_grain.size, + av1_dec->film_grain.cpu, + av1_dec->film_grain.dma); + av1_dec->film_grain.cpu = NULL; + + if (av1_dec->prob_tbl.cpu) + dma_free_coherent(vpu->dev, av1_dec->prob_tbl.size, + av1_dec->prob_tbl.cpu, av1_dec->prob_tbl.dma); + av1_dec->prob_tbl.cpu = NULL; + + if (av1_dec->prob_tbl_out.cpu) + dma_free_coherent(vpu->dev, av1_dec->prob_tbl_out.size, + av1_dec->prob_tbl_out.cpu, + av1_dec->prob_tbl_out.dma); + av1_dec->prob_tbl_out.cpu = NULL; + + if (av1_dec->tile_buf.cpu) + dma_free_coherent(vpu->dev, av1_dec->tile_buf.size, + av1_dec->tile_buf.cpu, av1_dec->tile_buf.dma); + av1_dec->tile_buf.cpu = NULL; + + hantro_av1_tiles_free(ctx); +} + +int hantro_av1_init(struct hantro_ctx *ctx) +{ + struct hantro_dev *vpu = ctx->dev; + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + + memset(av1_dec, 0, sizeof(*av1_dec)); + + av1_dec->global_model.cpu = dma_alloc_coherent(vpu->dev, GLOBAL_MODEL_SIZE, + &av1_dec->global_model.dma, + GFP_KERNEL); + if (!av1_dec->global_model.cpu) + return -ENOMEM; + av1_dec->global_model.size = GLOBAL_MODEL_SIZE; + + av1_dec->tile_info.cpu = dma_alloc_coherent(vpu->dev, AV1_TILE_INFO_SIZE, + &av1_dec->tile_info.dma, + GFP_KERNEL); + if (!av1_dec->tile_info.cpu) + return -ENOMEM; + av1_dec->tile_info.size = AV1_TILE_INFO_SIZE; + + av1_dec->film_grain.cpu = dma_alloc_coherent(vpu->dev, + ALIGN(sizeof(struct hantro_av1_film_grain), + 2048), + &av1_dec->film_grain.dma, + GFP_KERNEL); + if (!av1_dec->film_grain.cpu) + return -ENOMEM; + av1_dec->film_grain.size = ALIGN(sizeof(struct hantro_av1_film_grain), 2048); + + av1_dec->prob_tbl.cpu = dma_alloc_coherent(vpu->dev, + ALIGN(sizeof(struct av1cdfs), 2048), + &av1_dec->prob_tbl.dma, + GFP_KERNEL); + if (!av1_dec->prob_tbl.cpu) + return -ENOMEM; + av1_dec->prob_tbl.size = ALIGN(sizeof(struct av1cdfs), 2048); + + av1_dec->prob_tbl_out.cpu = dma_alloc_coherent(vpu->dev, + ALIGN(sizeof(struct av1cdfs), 2048), + &av1_dec->prob_tbl_out.dma, + GFP_KERNEL); + if (!av1_dec->prob_tbl_out.cpu) + return -ENOMEM; + av1_dec->prob_tbl_out.size = ALIGN(sizeof(struct av1cdfs), 2048); + av1_dec->cdfs = &av1_dec->default_cdfs; + av1_dec->cdfs_ndvc = &av1_dec->default_cdfs_ndvc; + + hantro_av1_set_default_cdfs(av1_dec->cdfs, av1_dec->cdfs_ndvc); + + av1_dec->tile_buf.cpu = dma_alloc_coherent(vpu->dev, + AV1_TILE_SIZE, + &av1_dec->tile_buf.dma, + GFP_KERNEL); + if (!av1_dec->tile_buf.cpu) + return -ENOMEM; + av1_dec->tile_buf.size = AV1_TILE_SIZE; + + return 0; +} + +int hantro_av1_prepare_run(struct hantro_ctx *ctx) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + + ctrls->sequence = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_SEQUENCE); + if (WARN_ON(!ctrls->sequence)) + return -EINVAL; + + ctrls->tile_group_entry = + hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY); + if (WARN_ON(!ctrls->tile_group_entry)) + return -EINVAL; + + ctrls->frame = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_FRAME); + if (WARN_ON(!ctrls->frame)) + return -EINVAL; + + ctrls->film_grain = + hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_FILM_GRAIN); + + return hantro_av1_tiles_reallocate(ctx); +} + +static int hantro_av1_get_msb(u32 n) +{ + if (n == 0) + return 0; + return 31 ^ __builtin_clz(n); +} + +static short hantro_av1_resolve_divisor_32(u32 d, short *shift) +{ + int f; + u64 e; + + *shift = hantro_av1_get_msb(d); + /* e is obtained from D after resetting the most significant 1 bit. */ + e = d - ((u32)1 << *shift); + /* Get the most significant DIV_LUT_BITS (8) bits of e into f */ + if (*shift > DIV_LUT_BITS) + f = AV1_DIV_ROUND_UP_POW2(e, *shift - DIV_LUT_BITS); + else + f = e << (DIV_LUT_BITS - *shift); + if (f > DIV_LUT_NUM) + return -1; + *shift += DIV_LUT_PREC_BITS; + /* Use f as lookup into the precomputed table of multipliers */ + return div_lut[f]; +} + +static void hantro_av1_get_shear_params(const u32 *params, s64 *alpha, + s64 *beta, s64 *gamma, s64 *delta) +{ + const int *mat = params; + short shift; + short y; + long long gv, dv; + + if (mat[2] <= 0) + return; + + *alpha = clamp_val(mat[2] - (1 << WARPEDMODEL_PREC_BITS), S16_MIN, S16_MAX); + *beta = clamp_val(mat[3], S16_MIN, S16_MAX); + + y = hantro_av1_resolve_divisor_32(abs(mat[2]), &shift) * (mat[2] < 0 ? -1 : 1); + + gv = ((long long)mat[4] * (1 << WARPEDMODEL_PREC_BITS)) * y; + + *gamma = clamp_val((int)AV1_DIV_ROUND_UP_POW2_SIGNED(gv, shift), S16_MIN, S16_MAX); + + dv = ((long long)mat[3] * mat[4]) * y; + *delta = clamp_val(mat[5] - + (int)AV1_DIV_ROUND_UP_POW2_SIGNED(dv, shift) - (1 << WARPEDMODEL_PREC_BITS), + S16_MIN, S16_MAX); + + *alpha = AV1_DIV_ROUND_UP_POW2_SIGNED(*alpha, WARP_PARAM_REDUCE_BITS) + * (1 << WARP_PARAM_REDUCE_BITS); + *beta = AV1_DIV_ROUND_UP_POW2_SIGNED(*beta, WARP_PARAM_REDUCE_BITS) + * (1 << WARP_PARAM_REDUCE_BITS); + *gamma = AV1_DIV_ROUND_UP_POW2_SIGNED(*gamma, WARP_PARAM_REDUCE_BITS) + * (1 << WARP_PARAM_REDUCE_BITS); + *delta = AV1_DIV_ROUND_UP_POW2_SIGNED(*delta, WARP_PARAM_REDUCE_BITS) + * (1 << WARP_PARAM_REDUCE_BITS); +} + +void hantro_av1_set_global_model(struct hantro_ctx *ctx) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; + const struct v4l2_av1_global_motion *gm = &frame->global_motion; + u8 *dst = av1_dec->global_model.cpu; + int ref_frame, i; + + memset(dst, 0, GLOBAL_MODEL_SIZE); + for (ref_frame = 0; ref_frame < V4L2_AV1_REFS_PER_FRAME; ++ref_frame) { + s64 alpha = 0, beta = 0, gamma = 0, delta = 0; + + for (i = 0; i < 6; ++i) { + if (i == 2) + *(s32 *)dst = + gm->params[V4L2_AV1_REF_LAST_FRAME + ref_frame][3]; + else if (i == 3) + *(s32 *)dst = + gm->params[V4L2_AV1_REF_LAST_FRAME + ref_frame][2]; + else + *(s32 *)dst = + gm->params[V4L2_AV1_REF_LAST_FRAME + ref_frame][i]; + dst += 4; + } + + if (gm->type[V4L2_AV1_REF_LAST_FRAME + ref_frame] <= V4L2_AV1_WARP_MODEL_AFFINE) + hantro_av1_get_shear_params(&gm->params[V4L2_AV1_REF_LAST_FRAME + ref_frame][0], + &alpha, &beta, &gamma, &delta); + + *(s16 *)dst = alpha; + dst += 2; + *(s16 *)dst = beta; + dst += 2; + *(s16 *)dst = gamma; + dst += 2; + *(s16 *)dst = delta; + dst += 2; + } +} + +int hantro_av1_tile_log2(int target) +{ + int k; + + /* + * returns the smallest value for k such that 1 << k is greater + * than or equal to target + */ + for (k = 0; (1 << k) < target; k++) + ; + + return k; +} + +int hantro_av1_get_dist(struct hantro_ctx *ctx, int a, int b) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + int bits = ctrls->sequence->order_hint_bits - 1; + int diff, m; + + if (!ctrls->sequence->order_hint_bits) + return 0; + + diff = a - b; + m = 1 << bits; + diff = (diff & (m - 1)) - (diff & m); + + return diff; +} + +void hantro_av1_set_frame_sign_bias(struct hantro_ctx *ctx) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; + const struct v4l2_ctrl_av1_sequence *sequence = ctrls->sequence; + int i; + + if (!sequence->order_hint_bits || IS_INTRA(frame->frame_type)) { + for (i = 0; i < V4L2_AV1_TOTAL_REFS_PER_FRAME; i++) + av1_dec->ref_frame_sign_bias[i] = 0; + + return; + } + // Identify the nearest forward and backward references. + for (i = 0; i < V4L2_AV1_TOTAL_REFS_PER_FRAME - 1; i++) { + if (hantro_av1_get_frame_index(ctx, i) >= 0) { + int rel_off = + hantro_av1_get_dist(ctx, + hantro_av1_get_order_hint(ctx, i), + frame->order_hint); + av1_dec->ref_frame_sign_bias[i + 1] = (rel_off <= 0) ? 0 : 1; + } + } +} + +void hantro_av1_init_scaling_function(const u8 *values, const u8 *scaling, + u8 num_points, u8 *scaling_lut) +{ + int i, point; + + if (num_points == 0) { + memset(scaling_lut, 0, 256); + return; + } + + for (point = 0; point < num_points - 1; point++) { + int x; + s32 delta_y = scaling[point + 1] - scaling[point]; + s32 delta_x = values[point + 1] - values[point]; + s64 delta = + delta_x ? delta_y * ((65536 + (delta_x >> 1)) / + delta_x) : 0; + + for (x = 0; x < delta_x; x++) { + scaling_lut[values[point] + x] = + scaling[point] + + (s32)((x * delta + 32768) >> 16); + } + } + + for (i = values[num_points - 1]; i < 256; i++) + scaling_lut[i] = scaling[num_points - 1]; +} + +void hantro_av1_set_tile_info(struct hantro_ctx *ctx) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_av1_tile_info *tile_info = &ctrls->frame->tile_info; + const struct v4l2_ctrl_av1_tile_group_entry *group_entry = + ctrls->tile_group_entry; + u8 *dst = av1_dec->tile_info.cpu; + int tile0, tile1; + + memset(dst, 0, av1_dec->tile_info.size); + + for (tile0 = 0; tile0 < tile_info->tile_cols; tile0++) { + for (tile1 = 0; tile1 < tile_info->tile_rows; tile1++) { + int tile_id = tile1 * tile_info->tile_cols + tile0; + u32 start, end; + u32 y0 = + tile_info->height_in_sbs_minus_1[tile1] + 1; + u32 x0 = tile_info->width_in_sbs_minus_1[tile0] + 1; + + /* tile size in SB units (width,height) */ + *dst++ = x0; + *dst++ = 0; + *dst++ = 0; + *dst++ = 0; + *dst++ = y0; + *dst++ = 0; + *dst++ = 0; + *dst++ = 0; + + /* tile start position */ + start = group_entry[tile_id].tile_offset - group_entry[0].tile_offset; + *dst++ = start & 255; + *dst++ = (start >> 8) & 255; + *dst++ = (start >> 16) & 255; + *dst++ = (start >> 24) & 255; + + /* number of bytes in tile data */ + end = start + group_entry[tile_id].tile_size; + *dst++ = end & 255; + *dst++ = (end >> 8) & 255; + *dst++ = (end >> 16) & 255; + *dst++ = (end >> 24) & 255; + } + } +} + +bool hantro_av1_is_lossless(struct hantro_ctx *ctx) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; + const struct v4l2_av1_segmentation *segmentation = &frame->segmentation; + const struct v4l2_av1_quantization *quantization = &frame->quantization; + int i; + + for (i = 0; i < V4L2_AV1_MAX_SEGMENTS; i++) { + int qindex = quantization->base_q_idx; + + if (segmentation->feature_enabled[i] & + V4L2_AV1_SEGMENT_FEATURE_ENABLED(V4L2_AV1_SEG_LVL_ALT_Q)) { + qindex += segmentation->feature_data[i][V4L2_AV1_SEG_LVL_ALT_Q]; + } + qindex = clamp(qindex, 0, 255); + + if (qindex || + quantization->delta_q_y_dc || + quantization->delta_q_u_dc || + quantization->delta_q_u_ac || + quantization->delta_q_v_dc || + quantization->delta_q_v_ac) + return false; + } + + return true; +} + +void hantro_av1_update_prob(struct hantro_ctx *ctx) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; + bool frame_is_intra = IS_INTRA(frame->frame_type); + struct av1cdfs *out_cdfs = (struct av1cdfs *)av1_dec->prob_tbl_out.cpu; + int i; + + if (frame->flags & V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF) + return; + + for (i = 0; i < NUM_REF_FRAMES; i++) { + if (frame->refresh_frame_flags & BIT(i)) { + struct mvcdfs stored_mv_cdf; + + hantro_av1_get_cdfs(ctx, i); + stored_mv_cdf = av1_dec->cdfs->mv_cdf; + *av1_dec->cdfs = *out_cdfs; + if (frame_is_intra) { + av1_dec->cdfs->mv_cdf = stored_mv_cdf; + *av1_dec->cdfs_ndvc = out_cdfs->mv_cdf; + } + hantro_av1_store_cdfs(ctx, frame->refresh_frame_flags); + break; + } + } +} + +void hantro_av1_set_prob(struct hantro_ctx *ctx) +{ + struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; + struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; + const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; + const struct v4l2_av1_quantization *quantization = &frame->quantization; + bool error_resilient_mode = + !!(frame->flags & V4L2_AV1_FRAME_FLAG_ERROR_RESILIENT_MODE); + bool frame_is_intra = IS_INTRA(frame->frame_type); + + if (error_resilient_mode || frame_is_intra || + frame->primary_ref_frame == AV1_PRIMARY_REF_NONE) { + av1_dec->cdfs = &av1_dec->default_cdfs; + av1_dec->cdfs_ndvc = &av1_dec->default_cdfs_ndvc; + hantro_av1_default_coeff_probs(quantization->base_q_idx, + av1_dec->cdfs); + } else { + hantro_av1_get_cdfs(ctx, frame->ref_frame_idx[frame->primary_ref_frame]); + } + hantro_av1_store_cdfs(ctx, frame->refresh_frame_flags); + + memcpy(av1_dec->prob_tbl.cpu, av1_dec->cdfs, sizeof(struct av1cdfs)); + + if (frame_is_intra) { + int mv_offset = offsetof(struct av1cdfs, mv_cdf); + /* Overwrite MV context area with intrabc MV context */ + memcpy(av1_dec->prob_tbl.cpu + mv_offset, av1_dec->cdfs_ndvc, + sizeof(struct mvcdfs)); + } +} diff --git a/drivers/media/platform/verisilicon/hantro_av1.h b/drivers/media/platform/verisilicon/hantro_av1.h new file mode 100644 index 000000000000..4e2122b95cdd --- /dev/null +++ b/drivers/media/platform/verisilicon/hantro_av1.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _HANTRO_AV1_H_ +#define _HANTRO_AV1_H_ + +#define AV1_PRIMARY_REF_NONE 7 +#define AV1_REF_SCALE_SHIFT 14 +#define MAX_FRAME_DISTANCE 31 +#define AV1DEC_MAX_PIC_BUFFERS 24 + +#define SCALE_NUMERATOR 8 +#define SUPERRES_SCALE_DENOMINATOR_MIN (SCALE_NUMERATOR + 1) + +#define RS_SCALE_SUBPEL_BITS 14 +#define RS_SCALE_SUBPEL_MASK ((1 << RS_SCALE_SUBPEL_BITS) - 1) +#define RS_SUBPEL_BITS 6 +#define RS_SUBPEL_MASK ((1 << RS_SUBPEL_BITS) - 1) +#define RS_SCALE_EXTRA_BITS (RS_SCALE_SUBPEL_BITS - RS_SUBPEL_BITS) +#define RS_SCALE_EXTRA_OFF (1 << (RS_SCALE_EXTRA_BITS - 1)) + +/* + * These 3 values aren't defined enum v4l2_av1_segment_feature because + * they are not part of the specification + */ +#define V4L2_AV1_SEG_LVL_ALT_LF_Y_H 2 +#define V4L2_AV1_SEG_LVL_ALT_LF_U 3 +#define V4L2_AV1_SEG_LVL_ALT_LF_V 4 + +#define IS_INTRA(type) ((type == V4L2_AV1_KEY_FRAME) || (type == V4L2_AV1_INTRA_ONLY_FRAME)) + +#define LST_BUF_IDX (V4L2_AV1_REF_LAST_FRAME - V4L2_AV1_REF_LAST_FRAME) +#define LST2_BUF_IDX (V4L2_AV1_REF_LAST2_FRAME - V4L2_AV1_REF_LAST_FRAME) +#define LST3_BUF_IDX (V4L2_AV1_REF_LAST3_FRAME - V4L2_AV1_REF_LAST_FRAME) +#define GLD_BUF_IDX (V4L2_AV1_REF_GOLDEN_FRAME - V4L2_AV1_REF_LAST_FRAME) +#define BWD_BUF_IDX (V4L2_AV1_REF_BWDREF_FRAME - V4L2_AV1_REF_LAST_FRAME) +#define ALT2_BUF_IDX (V4L2_AV1_REF_ALTREF2_FRAME - V4L2_AV1_REF_LAST_FRAME) +#define ALT_BUF_IDX (V4L2_AV1_REF_ALTREF_FRAME - V4L2_AV1_REF_LAST_FRAME) + +int hantro_av1_get_frame_index(struct hantro_ctx *ctx, int ref); +int hantro_av1_get_order_hint(struct hantro_ctx *ctx, int ref); +int hantro_av1_frame_ref(struct hantro_ctx *ctx, u64 timestamp); +void hantro_av1_clean_refs(struct hantro_ctx *ctx); +size_t hantro_av1_luma_size(struct hantro_ctx *ctx); +size_t hantro_av1_chroma_size(struct hantro_ctx *ctx); +void hantro_av1_exit(struct hantro_ctx *ctx); +int hantro_av1_init(struct hantro_ctx *ctx); +int hantro_av1_prepare_run(struct hantro_ctx *ctx); +void hantro_av1_set_global_model(struct hantro_ctx *ctx); +int hantro_av1_tile_log2(int target); +int hantro_av1_get_dist(struct hantro_ctx *ctx, int a, int b); +void hantro_av1_set_frame_sign_bias(struct hantro_ctx *ctx); +void hantro_av1_init_scaling_function(const u8 *values, const u8 *scaling, + u8 num_points, u8 *scaling_lut); +void hantro_av1_set_tile_info(struct hantro_ctx *ctx); +bool hantro_av1_is_lossless(struct hantro_ctx *ctx); +void hantro_av1_update_prob(struct hantro_ctx *ctx); +void hantro_av1_set_prob(struct hantro_ctx *ctx); + +int hantro_av1_get_hardware_mcomp_filt_type(int interpolation_filter); +int hantro_av1_get_hardware_tx_mode(enum v4l2_av1_tx_mode tx_mode); + +#endif diff --git a/drivers/media/platform/verisilicon/rockchip_av1_entropymode.c b/drivers/media/platform/verisilicon/hantro_av1_entropymode.c similarity index 99% rename from drivers/media/platform/verisilicon/rockchip_av1_entropymode.c rename to drivers/media/platform/verisilicon/hantro_av1_entropymode.c index b1ae72ad675e..4f7bfec73668 100644 --- a/drivers/media/platform/verisilicon/rockchip_av1_entropymode.c +++ b/drivers/media/platform/verisilicon/hantro_av1_entropymode.c @@ -11,7 +11,7 @@ */ #include "hantro.h" -#include "rockchip_av1_entropymode.h" +#include "hantro_av1_entropymode.h" #define AOM_ICDF ICDF #define AOM_CDF2(a0) AOM_ICDF(a0) @@ -4195,7 +4195,7 @@ static const u16 default_bits_cdf[][10] = { } }; -static int rockchip_av1_get_q_ctx(int q) +static int hantro_av1_get_q_ctx(int q) { if (q <= 20) return 0; @@ -4206,10 +4206,10 @@ static int rockchip_av1_get_q_ctx(int q) return 3; } -void rockchip_av1_default_coeff_probs(u32 base_qindex, void *ptr) +void hantro_av1_default_coeff_probs(u32 base_qindex, void *ptr) { struct av1cdfs *cdfs = (struct av1cdfs *)ptr; - const int index = rockchip_av1_get_q_ctx(base_qindex); + const int index = hantro_av1_get_q_ctx(base_qindex); memcpy(cdfs->txb_skip_cdf, av1_default_txb_skip_cdfs[index], sizeof(av1_default_txb_skip_cdfs[0])); @@ -4240,8 +4240,8 @@ void rockchip_av1_default_coeff_probs(u32 base_qindex, void *ptr) sizeof(av1_default_eob_multi1024_cdfs[0])); } -void rockchip_av1_set_default_cdfs(struct av1cdfs *cdfs, - struct mvcdfs *cdfs_ndvc) +void hantro_av1_set_default_cdfs(struct av1cdfs *cdfs, + struct mvcdfs *cdfs_ndvc) { memcpy(cdfs->partition_cdf, default_partition_cdf, sizeof(cdfs->partition_cdf)); @@ -4398,7 +4398,7 @@ void rockchip_av1_set_default_cdfs(struct av1cdfs *cdfs, sizeof(cdfs->compound_idx_cdf)); } -void rockchip_av1_get_cdfs(struct hantro_ctx *ctx, u32 ref_idx) +void hantro_av1_get_cdfs(struct hantro_ctx *ctx, u32 ref_idx) { struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; @@ -4406,8 +4406,8 @@ void rockchip_av1_get_cdfs(struct hantro_ctx *ctx, u32 ref_idx) av1_dec->cdfs_ndvc = &av1_dec->cdfs_last_ndvc[ref_idx]; } -void rockchip_av1_store_cdfs(struct hantro_ctx *ctx, - u32 refresh_frame_flags) +void hantro_av1_store_cdfs(struct hantro_ctx *ctx, + u32 refresh_frame_flags) { struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; int i; diff --git a/drivers/media/platform/verisilicon/rockchip_av1_entropymode.h b/drivers/media/platform/verisilicon/hantro_av1_entropymode.h similarity index 95% rename from drivers/media/platform/verisilicon/rockchip_av1_entropymode.h rename to drivers/media/platform/verisilicon/hantro_av1_entropymode.h index bbf8424c7d2c..abbc660ecce3 100644 --- a/drivers/media/platform/verisilicon/rockchip_av1_entropymode.h +++ b/drivers/media/platform/verisilicon/hantro_av1_entropymode.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -#ifndef _ROCKCHIP_AV1_ENTROPYMODE_H_ -#define _ROCKCHIP_AV1_ENTROPYMODE_H_ +#ifndef _HANTRO_AV1_ENTROPYMODE_H_ +#define _HANTRO_AV1_ENTROPYMODE_H_ #include @@ -262,11 +262,11 @@ struct av1cdfs { u16 dummy3[16]; }; -void rockchip_av1_store_cdfs(struct hantro_ctx *ctx, - u32 refresh_frame_flags); -void rockchip_av1_get_cdfs(struct hantro_ctx *ctx, u32 ref_idx); -void rockchip_av1_set_default_cdfs(struct av1cdfs *cdfs, - struct mvcdfs *cdfs_ndvc); -void rockchip_av1_default_coeff_probs(u32 base_qindex, void *ptr); +void hantro_av1_store_cdfs(struct hantro_ctx *ctx, + u32 refresh_frame_flags); +void hantro_av1_get_cdfs(struct hantro_ctx *ctx, u32 ref_idx); +void hantro_av1_set_default_cdfs(struct av1cdfs *cdfs, + struct mvcdfs *cdfs_ndvc); +void hantro_av1_default_coeff_probs(u32 base_qindex, void *ptr); -#endif /* _ROCKCHIP_AV1_ENTROPYMODE_H_ */ +#endif /* _HANTRO_AV1_ENTROPYMODE_H_ */ diff --git a/drivers/media/platform/verisilicon/rockchip_av1_filmgrain.c b/drivers/media/platform/verisilicon/hantro_av1_filmgrain.c similarity index 85% rename from drivers/media/platform/verisilicon/rockchip_av1_filmgrain.c rename to drivers/media/platform/verisilicon/hantro_av1_filmgrain.c index f64dea797eff..a1fc40e0218a 100644 --- a/drivers/media/platform/verisilicon/rockchip_av1_filmgrain.c +++ b/drivers/media/platform/verisilicon/hantro_av1_filmgrain.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only OR Apache-2.0 -#include "rockchip_av1_filmgrain.h" +#include "hantro_av1_filmgrain.h" static const s32 gaussian_sequence[2048] = { 56, 568, -180, 172, 124, -84, 172, -64, -900, 24, 820, @@ -204,8 +204,8 @@ static inline s32 round_power_of_two(const s32 val, s32 n) return (val + a) >> n; } -static void rockchip_av1_init_random_generator(u8 luma_num, u16 seed, - u16 *random_register) +static void hantro_av1_init_random_generator(u8 luma_num, u16 seed, + u16 *random_register) { u16 random_reg = seed; @@ -214,7 +214,7 @@ static void rockchip_av1_init_random_generator(u8 luma_num, u16 seed, *random_register = random_reg; } -static inline void rockchip_av1_update_random_register(u16 *random_register) +static inline void hantro_av1_update_random_register(u16 *random_register) { u16 bit; u16 random_reg = *random_register; @@ -224,21 +224,20 @@ static inline void rockchip_av1_update_random_register(u16 *random_register) *random_register = (random_reg >> 1) | (bit << 15); } -static inline s32 rockchip_av1_get_random_number(u16 random_register) +static inline s32 hantro_av1_get_random_number(u16 random_register) { return (random_register >> 5) & ((1 << 11) - 1); } -void rockchip_av1_generate_luma_grain_block(s32 (*luma_grain_block)[73][82], - s32 bitdepth, - u8 num_y_points, - s32 grain_scale_shift, - s32 ar_coeff_lag, - s32 (*ar_coeffs_y)[24], - s32 ar_coeff_shift, - s32 grain_min, - s32 grain_max, - u16 random_seed) +void hantro_av1_generate_luma_grain_block(struct hantro_av1_coeffs_grain_block *coeffs, + s32 bitdepth, + u8 num_y_points, + s32 grain_scale_shift, + s32 ar_coeff_lag, + s32 ar_coeff_shift, + s32 grain_min, + s32 grain_max, + u16 random_seed) { s32 gauss_sec_shift = 12 - bitdepth + grain_scale_shift; u16 grain_random_register = random_seed; @@ -247,15 +246,15 @@ void rockchip_av1_generate_luma_grain_block(s32 (*luma_grain_block)[73][82], for (i = 0; i < 73; i++) { for (j = 0; j < 82; j++) { if (num_y_points > 0) { - rockchip_av1_update_random_register + hantro_av1_update_random_register (&grain_random_register); - (*luma_grain_block)[i][j] = + coeffs->luma_grain_block[i][j] = round_power_of_two(gaussian_sequence - [rockchip_av1_get_random_number + [hantro_av1_get_random_number (grain_random_register)], gauss_sec_shift); } else { - (*luma_grain_block)[i][j] = 0; + coeffs->luma_grain_block[i][j] = 0; } } } @@ -272,72 +271,68 @@ void rockchip_av1_generate_luma_grain_block(s32 (*luma_grain_block)[73][82], deltacol <= ar_coeff_lag; deltacol++) { if (deltarow == 0 && deltacol == 0) break; - wsum = wsum + (*ar_coeffs_y)[pos] * - (*luma_grain_block)[i + deltarow][j + deltacol]; + wsum = wsum + coeffs->ar_coeffs_y[pos] * + coeffs->luma_grain_block[i + deltarow][j + deltacol]; ++pos; } } - (*luma_grain_block)[i][j] = - clamp((*luma_grain_block)[i][j] + + coeffs->luma_grain_block[i][j] = + clamp(coeffs->luma_grain_block[i][j] + round_power_of_two(wsum, ar_coeff_shift), grain_min, grain_max); } } // Calculate chroma grain noise once per frame -void rockchip_av1_generate_chroma_grain_block(s32 (*luma_grain_block)[73][82], - s32 (*cb_grain_block)[38][44], - s32 (*cr_grain_block)[38][44], - s32 bitdepth, - u8 num_y_points, - u8 num_cb_points, - u8 num_cr_points, - s32 grain_scale_shift, - s32 ar_coeff_lag, - s32 (*ar_coeffs_cb)[25], - s32 (*ar_coeffs_cr)[25], - s32 ar_coeff_shift, - s32 grain_min, - s32 grain_max, - u8 chroma_scaling_from_luma, - u16 random_seed) +void hantro_av1_generate_chroma_grain_block(struct hantro_av1_coeffs_grain_block *coeffs, + s32 bitdepth, + u8 num_y_points, + u8 num_cb_points, + u8 num_cr_points, + s32 grain_scale_shift, + s32 ar_coeff_lag, + s32 ar_coeff_shift, + s32 grain_min, + s32 grain_max, + u8 chroma_scaling_from_luma, + u16 random_seed) { s32 gauss_sec_shift = 12 - bitdepth + grain_scale_shift; u16 grain_random_register = 0; s32 i, j; - rockchip_av1_init_random_generator(7, random_seed, - &grain_random_register); + hantro_av1_init_random_generator(7, random_seed, + &grain_random_register); for (i = 0; i < 38; i++) { for (j = 0; j < 44; j++) { if (num_cb_points || chroma_scaling_from_luma) { - rockchip_av1_update_random_register + hantro_av1_update_random_register (&grain_random_register); - (*cb_grain_block)[i][j] = + coeffs->cb_grain_block[i][j] = round_power_of_two(gaussian_sequence - [rockchip_av1_get_random_number + [hantro_av1_get_random_number (grain_random_register)], gauss_sec_shift); } else { - (*cb_grain_block)[i][j] = 0; + coeffs->cb_grain_block[i][j] = 0; } } } - rockchip_av1_init_random_generator(11, random_seed, - &grain_random_register); + hantro_av1_init_random_generator(11, random_seed, + &grain_random_register); for (i = 0; i < 38; i++) { for (j = 0; j < 44; j++) { if (num_cr_points || chroma_scaling_from_luma) { - rockchip_av1_update_random_register + hantro_av1_update_random_register (&grain_random_register); - (*cr_grain_block)[i][j] = + coeffs->cr_grain_block[i][j] = round_power_of_two(gaussian_sequence - [rockchip_av1_get_random_number + [hantro_av1_get_random_number (grain_random_register)], gauss_sec_shift); } else { - (*cr_grain_block)[i][j] = 0; + coeffs->cr_grain_block[i][j] = 0; } } } @@ -355,12 +350,12 @@ void rockchip_av1_generate_chroma_grain_block(s32 (*luma_grain_block)[73][82], deltacol <= ar_coeff_lag; deltacol++) { if (deltarow == 0 && deltacol == 0) break; - wsum_cb = wsum_cb + (*ar_coeffs_cb)[pos] * - (*cb_grain_block)[i + deltarow][j + deltacol]; + wsum_cb = wsum_cb + coeffs->ar_coeffs_cb[pos] * + coeffs->cb_grain_block[i + deltarow][j + deltacol]; wsum_cr = wsum_cr + - (*ar_coeffs_cr)[pos] * - (*cr_grain_block)[i + deltarow][j + deltacol]; + coeffs->ar_coeffs_cr[pos] * + coeffs->cr_grain_block[i + deltarow][j + deltacol]; ++pos; } } @@ -371,28 +366,28 @@ void rockchip_av1_generate_chroma_grain_block(s32 (*luma_grain_block)[73][82], s32 luma_coord_x = (j << 1) - 3; av_luma += - (*luma_grain_block)[luma_coord_y][luma_coord_x]; + coeffs->luma_grain_block[luma_coord_y][luma_coord_x]; av_luma += - (*luma_grain_block)[luma_coord_y][luma_coord_x + 1]; + coeffs->luma_grain_block[luma_coord_y][luma_coord_x + 1]; av_luma += - (*luma_grain_block)[luma_coord_y + 1][luma_coord_x]; + coeffs->luma_grain_block[luma_coord_y + 1][luma_coord_x]; av_luma += - (*luma_grain_block)[(luma_coord_y + 1)][luma_coord_x + 1]; + coeffs->luma_grain_block[(luma_coord_y + 1)][luma_coord_x + 1]; av_luma = round_power_of_two(av_luma, 2); - wsum_cb = wsum_cb + (*ar_coeffs_cb)[pos] * av_luma; - wsum_cr = wsum_cr + (*ar_coeffs_cr)[pos] * av_luma; + wsum_cb = wsum_cb + coeffs->ar_coeffs_cb[pos] * av_luma; + wsum_cr = wsum_cr + coeffs->ar_coeffs_cr[pos] * av_luma; } if (num_cb_points || chroma_scaling_from_luma) { - (*cb_grain_block)[i][j] = - clamp((*cb_grain_block)[i][j] + + coeffs->cb_grain_block[i][j] = + clamp(coeffs->cb_grain_block[i][j] + round_power_of_two(wsum_cb, ar_coeff_shift), grain_min, grain_max); } if (num_cr_points || chroma_scaling_from_luma) { - (*cr_grain_block)[i][j] = - clamp((*cr_grain_block)[i][j] + + coeffs->cr_grain_block[i][j] = + clamp(coeffs->cr_grain_block[i][j] + round_power_of_two(wsum_cr, ar_coeff_shift), grain_min, grain_max); } diff --git a/drivers/media/platform/verisilicon/hantro_av1_filmgrain.h b/drivers/media/platform/verisilicon/hantro_av1_filmgrain.h new file mode 100644 index 000000000000..d80be7c011a0 --- /dev/null +++ b/drivers/media/platform/verisilicon/hantro_av1_filmgrain.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _HANTRO_AV1_FILMGRAIN_H_ +#define _HANTRO_AV1_FILMGRAIN_H_ + +#include + +struct hantro_av1_film_grain { + u8 scaling_lut_y[256]; + u8 scaling_lut_cb[256]; + u8 scaling_lut_cr[256]; + s16 cropped_luma_grain_block[4096]; + s16 cropped_chroma_grain_block[1024 * 2]; +}; + +struct hantro_av1_coeffs_grain_block { + s32 ar_coeffs_y[24]; + s32 ar_coeffs_cb[25]; + s32 ar_coeffs_cr[25]; + s32 luma_grain_block[73][82]; + s32 cb_grain_block[38][44]; + s32 cr_grain_block[38][44]; +}; + +void hantro_av1_generate_luma_grain_block(struct hantro_av1_coeffs_grain_block *coeffs, + s32 bitdepth, + u8 num_y_points, + s32 grain_scale_shift, + s32 ar_coeff_lag, + s32 ar_coeff_shift, + s32 grain_min, + s32 grain_max, + u16 random_seed); + +void hantro_av1_generate_chroma_grain_block(struct hantro_av1_coeffs_grain_block *coeffs, + s32 bitdepth, + u8 num_y_points, + u8 num_cb_points, + u8 num_cr_points, + s32 grain_scale_shift, + s32 ar_coeff_lag, + s32 ar_coeff_shift, + s32 grain_min, + s32 grain_max, + u8 chroma_scaling_from_luma, + u16 random_seed); + +#endif diff --git a/drivers/media/platform/verisilicon/hantro_hw.h b/drivers/media/platform/verisilicon/hantro_hw.h index 5f2011529f02..d1d39d1df5d2 100644 --- a/drivers/media/platform/verisilicon/hantro_hw.h +++ b/drivers/media/platform/verisilicon/hantro_hw.h @@ -15,8 +15,8 @@ #include #include -#include "rockchip_av1_entropymode.h" -#include "rockchip_av1_filmgrain.h" +#include "hantro_av1_entropymode.h" +#include "hantro_av1_filmgrain.h" #define DEC_8190_ALIGN_MASK 0x07U @@ -459,10 +459,7 @@ void hantro_hevc_ref_init(struct hantro_ctx *ctx); dma_addr_t hantro_hevc_get_ref_buf(struct hantro_ctx *ctx, s32 poc); int hantro_hevc_add_ref_buf(struct hantro_ctx *ctx, int poc, dma_addr_t addr); -int rockchip_vpu981_av1_dec_init(struct hantro_ctx *ctx); -void rockchip_vpu981_av1_dec_exit(struct hantro_ctx *ctx); int rockchip_vpu981_av1_dec_run(struct hantro_ctx *ctx); -void rockchip_vpu981_av1_dec_done(struct hantro_ctx *ctx); static inline unsigned short hantro_vp9_num_sbs(unsigned short dimension) { diff --git a/drivers/media/platform/verisilicon/rockchip_av1_filmgrain.h b/drivers/media/platform/verisilicon/rockchip_av1_filmgrain.h deleted file mode 100644 index 31a8b7920c31..000000000000 --- a/drivers/media/platform/verisilicon/rockchip_av1_filmgrain.h +++ /dev/null @@ -1,36 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ - -#ifndef _ROCKCHIP_AV1_FILMGRAIN_H_ -#define _ROCKCHIP_AV1_FILMGRAIN_H_ - -#include - -void rockchip_av1_generate_luma_grain_block(s32 (*luma_grain_block)[73][82], - s32 bitdepth, - u8 num_y_points, - s32 grain_scale_shift, - s32 ar_coeff_lag, - s32 (*ar_coeffs_y)[24], - s32 ar_coeff_shift, - s32 grain_min, - s32 grain_max, - u16 random_seed); - -void rockchip_av1_generate_chroma_grain_block(s32 (*luma_grain_block)[73][82], - s32 (*cb_grain_block)[38][44], - s32 (*cr_grain_block)[38][44], - s32 bitdepth, - u8 num_y_points, - u8 num_cb_points, - u8 num_cr_points, - s32 grain_scale_shift, - s32 ar_coeff_lag, - s32 (*ar_coeffs_cb)[25], - s32 (*ar_coeffs_cr)[25], - s32 ar_coeff_shift, - s32 grain_min, - s32 grain_max, - u8 chroma_scaling_from_luma, - u16 random_seed); - -#endif diff --git a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c index e4e21ad37323..5bf1f2689fbc 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu981_hw_av1_dec.c @@ -7,622 +7,35 @@ #include #include "hantro.h" +#include "hantro_av1.h" #include "hantro_v4l2.h" #include "rockchip_vpu981_regs.h" #define AV1_DEC_MODE 17 -#define GM_GLOBAL_MODELS_PER_FRAME 7 -#define GLOBAL_MODEL_TOTAL_SIZE (6 * 4 + 4 * 2) -#define GLOBAL_MODEL_SIZE ALIGN(GM_GLOBAL_MODELS_PER_FRAME * GLOBAL_MODEL_TOTAL_SIZE, 2048) -#define AV1_MAX_TILES 128 -#define AV1_TILE_INFO_SIZE (AV1_MAX_TILES * 16) -#define AV1DEC_MAX_PIC_BUFFERS 24 -#define AV1_REF_SCALE_SHIFT 14 -#define AV1_INVALID_IDX -1 -#define MAX_FRAME_DISTANCE 31 -#define AV1_PRIMARY_REF_NONE 7 -#define AV1_TILE_SIZE ALIGN(32 * 128, 4096) -/* - * These 3 values aren't defined enum v4l2_av1_segment_feature because - * they are not part of the specification - */ -#define V4L2_AV1_SEG_LVL_ALT_LF_Y_H 2 -#define V4L2_AV1_SEG_LVL_ALT_LF_U 3 -#define V4L2_AV1_SEG_LVL_ALT_LF_V 4 - -#define SUPERRES_SCALE_BITS 3 -#define SCALE_NUMERATOR 8 -#define SUPERRES_SCALE_DENOMINATOR_MIN (SCALE_NUMERATOR + 1) - -#define RS_SUBPEL_BITS 6 -#define RS_SUBPEL_MASK ((1 << RS_SUBPEL_BITS) - 1) -#define RS_SCALE_SUBPEL_BITS 14 -#define RS_SCALE_SUBPEL_MASK ((1 << RS_SCALE_SUBPEL_BITS) - 1) -#define RS_SCALE_EXTRA_BITS (RS_SCALE_SUBPEL_BITS - RS_SUBPEL_BITS) -#define RS_SCALE_EXTRA_OFF (1 << (RS_SCALE_EXTRA_BITS - 1)) - -#define IS_INTRA(type) ((type == V4L2_AV1_KEY_FRAME) || (type == V4L2_AV1_INTRA_ONLY_FRAME)) - -#define LST_BUF_IDX (V4L2_AV1_REF_LAST_FRAME - V4L2_AV1_REF_LAST_FRAME) -#define LST2_BUF_IDX (V4L2_AV1_REF_LAST2_FRAME - V4L2_AV1_REF_LAST_FRAME) -#define LST3_BUF_IDX (V4L2_AV1_REF_LAST3_FRAME - V4L2_AV1_REF_LAST_FRAME) -#define GLD_BUF_IDX (V4L2_AV1_REF_GOLDEN_FRAME - V4L2_AV1_REF_LAST_FRAME) -#define BWD_BUF_IDX (V4L2_AV1_REF_BWDREF_FRAME - V4L2_AV1_REF_LAST_FRAME) -#define ALT2_BUF_IDX (V4L2_AV1_REF_ALTREF2_FRAME - V4L2_AV1_REF_LAST_FRAME) -#define ALT_BUF_IDX (V4L2_AV1_REF_ALTREF_FRAME - V4L2_AV1_REF_LAST_FRAME) - -#define DIV_LUT_PREC_BITS 14 -#define DIV_LUT_BITS 8 -#define DIV_LUT_NUM BIT(DIV_LUT_BITS) -#define WARP_PARAM_REDUCE_BITS 6 -#define WARPEDMODEL_PREC_BITS 16 - -#define AV1_DIV_ROUND_UP_POW2(value, n) \ -({ \ - typeof(n) _n = n; \ - typeof(value) _value = value; \ - (_value + (BIT(_n) >> 1)) >> _n; \ -}) - -#define AV1_DIV_ROUND_UP_POW2_SIGNED(value, n) \ -({ \ - typeof(n) _n_ = n; \ - typeof(value) _value_ = value; \ - (((_value_) < 0) ? -AV1_DIV_ROUND_UP_POW2(-(_value_), (_n_)) \ - : AV1_DIV_ROUND_UP_POW2((_value_), (_n_))); \ -}) - -enum rockchip_av1_tx_mode { - ROCKCHIP_AV1_TX_MODE_ONLY_4X4 = 0, - ROCKCHIP_AV1_TX_MODE_8X8 = 1, - ROCKCHIP_AV1_TX_MODE_16x16 = 2, - ROCKCHIP_AV1_TX_MODE_32x32 = 3, - ROCKCHIP_AV1_TX_MODE_SELECT = 4, -}; - -struct rockchip_av1_film_grain { - u8 scaling_lut_y[256]; - u8 scaling_lut_cb[256]; - u8 scaling_lut_cr[256]; - s16 cropped_luma_grain_block[4096]; - s16 cropped_chroma_grain_block[1024 * 2]; -}; - -static const short div_lut[DIV_LUT_NUM + 1] = { - 16384, 16320, 16257, 16194, 16132, 16070, 16009, 15948, 15888, 15828, 15768, - 15709, 15650, 15592, 15534, 15477, 15420, 15364, 15308, 15252, 15197, 15142, - 15087, 15033, 14980, 14926, 14873, 14821, 14769, 14717, 14665, 14614, 14564, - 14513, 14463, 14413, 14364, 14315, 14266, 14218, 14170, 14122, 14075, 14028, - 13981, 13935, 13888, 13843, 13797, 13752, 13707, 13662, 13618, 13574, 13530, - 13487, 13443, 13400, 13358, 13315, 13273, 13231, 13190, 13148, 13107, 13066, - 13026, 12985, 12945, 12906, 12866, 12827, 12788, 12749, 12710, 12672, 12633, - 12596, 12558, 12520, 12483, 12446, 12409, 12373, 12336, 12300, 12264, 12228, - 12193, 12157, 12122, 12087, 12053, 12018, 11984, 11950, 11916, 11882, 11848, - 11815, 11782, 11749, 11716, 11683, 11651, 11619, 11586, 11555, 11523, 11491, - 11460, 11429, 11398, 11367, 11336, 11305, 11275, 11245, 11215, 11185, 11155, - 11125, 11096, 11067, 11038, 11009, 10980, 10951, 10923, 10894, 10866, 10838, - 10810, 10782, 10755, 10727, 10700, 10673, 10645, 10618, 10592, 10565, 10538, - 10512, 10486, 10460, 10434, 10408, 10382, 10356, 10331, 10305, 10280, 10255, - 10230, 10205, 10180, 10156, 10131, 10107, 10082, 10058, 10034, 10010, 9986, - 9963, 9939, 9916, 9892, 9869, 9846, 9823, 9800, 9777, 9754, 9732, - 9709, 9687, 9664, 9642, 9620, 9598, 9576, 9554, 9533, 9511, 9489, - 9468, 9447, 9425, 9404, 9383, 9362, 9341, 9321, 9300, 9279, 9259, - 9239, 9218, 9198, 9178, 9158, 9138, 9118, 9098, 9079, 9059, 9039, - 9020, 9001, 8981, 8962, 8943, 8924, 8905, 8886, 8867, 8849, 8830, - 8812, 8793, 8775, 8756, 8738, 8720, 8702, 8684, 8666, 8648, 8630, - 8613, 8595, 8577, 8560, 8542, 8525, 8508, 8490, 8473, 8456, 8439, - 8422, 8405, 8389, 8372, 8355, 8339, 8322, 8306, 8289, 8273, 8257, - 8240, 8224, 8208, 8192, -}; - -static int rockchip_vpu981_get_frame_index(struct hantro_ctx *ctx, int ref) -{ - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; - u64 timestamp; - int i, idx = frame->ref_frame_idx[ref]; - - if (idx >= V4L2_AV1_TOTAL_REFS_PER_FRAME || idx < 0) - return AV1_INVALID_IDX; - - timestamp = frame->reference_frame_ts[idx]; - for (i = 0; i < AV1_MAX_FRAME_BUF_COUNT; i++) { - if (!av1_dec->frame_refs[i].used) - continue; - if (av1_dec->frame_refs[i].timestamp == timestamp) - return i; - } - - return AV1_INVALID_IDX; -} - -static int rockchip_vpu981_get_order_hint(struct hantro_ctx *ctx, int ref) -{ - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - int idx = rockchip_vpu981_get_frame_index(ctx, ref); - - if (idx != AV1_INVALID_IDX) - return av1_dec->frame_refs[idx].order_hint; - - return 0; -} - -static int rockchip_vpu981_av1_dec_frame_ref(struct hantro_ctx *ctx, - u64 timestamp) -{ - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; - int i; - - for (i = 0; i < AV1_MAX_FRAME_BUF_COUNT; i++) { - int j; - - if (av1_dec->frame_refs[i].used) - continue; - - av1_dec->frame_refs[i].width = frame->frame_width_minus_1 + 1; - av1_dec->frame_refs[i].height = frame->frame_height_minus_1 + 1; - av1_dec->frame_refs[i].mi_cols = DIV_ROUND_UP(frame->frame_width_minus_1 + 1, 8); - av1_dec->frame_refs[i].mi_rows = DIV_ROUND_UP(frame->frame_height_minus_1 + 1, 8); - av1_dec->frame_refs[i].timestamp = timestamp; - av1_dec->frame_refs[i].frame_type = frame->frame_type; - av1_dec->frame_refs[i].order_hint = frame->order_hint; - av1_dec->frame_refs[i].vb2_ref = hantro_get_dst_buf(ctx); - - for (j = 0; j < V4L2_AV1_TOTAL_REFS_PER_FRAME; j++) - av1_dec->frame_refs[i].order_hints[j] = frame->order_hints[j]; - av1_dec->frame_refs[i].used = true; - av1_dec->current_frame_index = i; - - return i; - } - - return AV1_INVALID_IDX; -} - -static void rockchip_vpu981_av1_dec_frame_unref(struct hantro_ctx *ctx, int idx) -{ - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - - if (idx >= 0) - av1_dec->frame_refs[idx].used = false; -} - -static void rockchip_vpu981_av1_dec_clean_refs(struct hantro_ctx *ctx) -{ - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - - int ref, idx; - - for (idx = 0; idx < AV1_MAX_FRAME_BUF_COUNT; idx++) { - u64 timestamp = av1_dec->frame_refs[idx].timestamp; - bool used = false; - - if (!av1_dec->frame_refs[idx].used) - continue; - - for (ref = 0; ref < V4L2_AV1_TOTAL_REFS_PER_FRAME; ref++) { - if (ctrls->frame->reference_frame_ts[ref] == timestamp) - used = true; - } - - if (!used) - rockchip_vpu981_av1_dec_frame_unref(ctx, idx); - } -} - -static size_t rockchip_vpu981_av1_dec_luma_size(struct hantro_ctx *ctx) -{ - return ctx->dst_fmt.width * ctx->dst_fmt.height * ctx->bit_depth / 8; -} - -static size_t rockchip_vpu981_av1_dec_chroma_size(struct hantro_ctx *ctx) -{ - size_t cr_offset = rockchip_vpu981_av1_dec_luma_size(ctx); - - return ALIGN((cr_offset * 3) / 2, 64); -} - -static void rockchip_vpu981_av1_dec_tiles_free(struct hantro_ctx *ctx) -{ - struct hantro_dev *vpu = ctx->dev; - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - - if (av1_dec->db_data_col.cpu) - dma_free_coherent(vpu->dev, av1_dec->db_data_col.size, - av1_dec->db_data_col.cpu, - av1_dec->db_data_col.dma); - av1_dec->db_data_col.cpu = NULL; - - if (av1_dec->db_ctrl_col.cpu) - dma_free_coherent(vpu->dev, av1_dec->db_ctrl_col.size, - av1_dec->db_ctrl_col.cpu, - av1_dec->db_ctrl_col.dma); - av1_dec->db_ctrl_col.cpu = NULL; - - if (av1_dec->cdef_col.cpu) - dma_free_coherent(vpu->dev, av1_dec->cdef_col.size, - av1_dec->cdef_col.cpu, av1_dec->cdef_col.dma); - av1_dec->cdef_col.cpu = NULL; - - if (av1_dec->sr_col.cpu) - dma_free_coherent(vpu->dev, av1_dec->sr_col.size, - av1_dec->sr_col.cpu, av1_dec->sr_col.dma); - av1_dec->sr_col.cpu = NULL; - - if (av1_dec->lr_col.cpu) - dma_free_coherent(vpu->dev, av1_dec->lr_col.size, - av1_dec->lr_col.cpu, av1_dec->lr_col.dma); - av1_dec->lr_col.cpu = NULL; -} - -static int rockchip_vpu981_av1_dec_tiles_reallocate(struct hantro_ctx *ctx) -{ - struct hantro_dev *vpu = ctx->dev; - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - const struct v4l2_av1_tile_info *tile_info = &ctrls->frame->tile_info; - unsigned int num_tile_cols = tile_info->tile_cols; - unsigned int height = ALIGN(ctrls->frame->frame_height_minus_1 + 1, 64); - unsigned int height_in_sb = height / 64; - unsigned int stripe_num = ((height + 8) + 63) / 64; - size_t size; - - if (av1_dec->db_data_col.size >= - ALIGN(height * 12 * ctx->bit_depth / 8, 128) * num_tile_cols) - return 0; - - rockchip_vpu981_av1_dec_tiles_free(ctx); - - size = ALIGN(height * 12 * ctx->bit_depth / 8, 128) * num_tile_cols; - av1_dec->db_data_col.cpu = dma_alloc_coherent(vpu->dev, size, - &av1_dec->db_data_col.dma, - GFP_KERNEL); - if (!av1_dec->db_data_col.cpu) - goto buffer_allocation_error; - av1_dec->db_data_col.size = size; - - size = ALIGN(height * 2 * 16 / 4, 128) * num_tile_cols; - av1_dec->db_ctrl_col.cpu = dma_alloc_coherent(vpu->dev, size, - &av1_dec->db_ctrl_col.dma, - GFP_KERNEL); - if (!av1_dec->db_ctrl_col.cpu) - goto buffer_allocation_error; - av1_dec->db_ctrl_col.size = size; - - size = ALIGN(height_in_sb * 44 * ctx->bit_depth * 16 / 8, 128) * num_tile_cols; - av1_dec->cdef_col.cpu = dma_alloc_coherent(vpu->dev, size, - &av1_dec->cdef_col.dma, - GFP_KERNEL); - if (!av1_dec->cdef_col.cpu) - goto buffer_allocation_error; - av1_dec->cdef_col.size = size; - - size = ALIGN(height_in_sb * (3040 + 1280), 128) * num_tile_cols; - av1_dec->sr_col.cpu = dma_alloc_coherent(vpu->dev, size, - &av1_dec->sr_col.dma, - GFP_KERNEL); - if (!av1_dec->sr_col.cpu) - goto buffer_allocation_error; - av1_dec->sr_col.size = size; - - size = ALIGN(stripe_num * 1536 * ctx->bit_depth / 8, 128) * num_tile_cols; - av1_dec->lr_col.cpu = dma_alloc_coherent(vpu->dev, size, - &av1_dec->lr_col.dma, - GFP_KERNEL); - if (!av1_dec->lr_col.cpu) - goto buffer_allocation_error; - av1_dec->lr_col.size = size; - - av1_dec->num_tile_cols_allocated = num_tile_cols; - return 0; - -buffer_allocation_error: - rockchip_vpu981_av1_dec_tiles_free(ctx); - return -ENOMEM; -} - -void rockchip_vpu981_av1_dec_exit(struct hantro_ctx *ctx) -{ - struct hantro_dev *vpu = ctx->dev; - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - - if (av1_dec->global_model.cpu) - dma_free_coherent(vpu->dev, av1_dec->global_model.size, - av1_dec->global_model.cpu, - av1_dec->global_model.dma); - av1_dec->global_model.cpu = NULL; - - if (av1_dec->tile_info.cpu) - dma_free_coherent(vpu->dev, av1_dec->tile_info.size, - av1_dec->tile_info.cpu, - av1_dec->tile_info.dma); - av1_dec->tile_info.cpu = NULL; - - if (av1_dec->film_grain.cpu) - dma_free_coherent(vpu->dev, av1_dec->film_grain.size, - av1_dec->film_grain.cpu, - av1_dec->film_grain.dma); - av1_dec->film_grain.cpu = NULL; - - if (av1_dec->prob_tbl.cpu) - dma_free_coherent(vpu->dev, av1_dec->prob_tbl.size, - av1_dec->prob_tbl.cpu, av1_dec->prob_tbl.dma); - av1_dec->prob_tbl.cpu = NULL; - - if (av1_dec->prob_tbl_out.cpu) - dma_free_coherent(vpu->dev, av1_dec->prob_tbl_out.size, - av1_dec->prob_tbl_out.cpu, - av1_dec->prob_tbl_out.dma); - av1_dec->prob_tbl_out.cpu = NULL; - - if (av1_dec->tile_buf.cpu) - dma_free_coherent(vpu->dev, av1_dec->tile_buf.size, - av1_dec->tile_buf.cpu, av1_dec->tile_buf.dma); - av1_dec->tile_buf.cpu = NULL; - - rockchip_vpu981_av1_dec_tiles_free(ctx); -} - -int rockchip_vpu981_av1_dec_init(struct hantro_ctx *ctx) -{ - struct hantro_dev *vpu = ctx->dev; - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - - memset(av1_dec, 0, sizeof(*av1_dec)); - - av1_dec->global_model.cpu = dma_alloc_coherent(vpu->dev, GLOBAL_MODEL_SIZE, - &av1_dec->global_model.dma, - GFP_KERNEL); - if (!av1_dec->global_model.cpu) - return -ENOMEM; - av1_dec->global_model.size = GLOBAL_MODEL_SIZE; - - av1_dec->tile_info.cpu = dma_alloc_coherent(vpu->dev, AV1_TILE_INFO_SIZE, - &av1_dec->tile_info.dma, - GFP_KERNEL); - if (!av1_dec->tile_info.cpu) - return -ENOMEM; - av1_dec->tile_info.size = AV1_TILE_INFO_SIZE; - - av1_dec->film_grain.cpu = dma_alloc_coherent(vpu->dev, - ALIGN(sizeof(struct rockchip_av1_film_grain), 2048), - &av1_dec->film_grain.dma, - GFP_KERNEL); - if (!av1_dec->film_grain.cpu) - return -ENOMEM; - av1_dec->film_grain.size = ALIGN(sizeof(struct rockchip_av1_film_grain), 2048); - - av1_dec->prob_tbl.cpu = dma_alloc_coherent(vpu->dev, - ALIGN(sizeof(struct av1cdfs), 2048), - &av1_dec->prob_tbl.dma, - GFP_KERNEL); - if (!av1_dec->prob_tbl.cpu) - return -ENOMEM; - av1_dec->prob_tbl.size = ALIGN(sizeof(struct av1cdfs), 2048); - - av1_dec->prob_tbl_out.cpu = dma_alloc_coherent(vpu->dev, - ALIGN(sizeof(struct av1cdfs), 2048), - &av1_dec->prob_tbl_out.dma, - GFP_KERNEL); - if (!av1_dec->prob_tbl_out.cpu) - return -ENOMEM; - av1_dec->prob_tbl_out.size = ALIGN(sizeof(struct av1cdfs), 2048); - av1_dec->cdfs = &av1_dec->default_cdfs; - av1_dec->cdfs_ndvc = &av1_dec->default_cdfs_ndvc; - - rockchip_av1_set_default_cdfs(av1_dec->cdfs, av1_dec->cdfs_ndvc); - - av1_dec->tile_buf.cpu = dma_alloc_coherent(vpu->dev, - AV1_TILE_SIZE, - &av1_dec->tile_buf.dma, - GFP_KERNEL); - if (!av1_dec->tile_buf.cpu) - return -ENOMEM; - av1_dec->tile_buf.size = AV1_TILE_SIZE; - - return 0; -} - -static int rockchip_vpu981_av1_dec_prepare_run(struct hantro_ctx *ctx) -{ - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - - ctrls->sequence = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_SEQUENCE); - if (WARN_ON(!ctrls->sequence)) - return -EINVAL; - - ctrls->tile_group_entry = - hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY); - if (WARN_ON(!ctrls->tile_group_entry)) - return -EINVAL; - - ctrls->frame = hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_FRAME); - if (WARN_ON(!ctrls->frame)) - return -EINVAL; - - ctrls->film_grain = - hantro_get_ctrl(ctx, V4L2_CID_STATELESS_AV1_FILM_GRAIN); - - return rockchip_vpu981_av1_dec_tiles_reallocate(ctx); -} - -static inline int rockchip_vpu981_av1_dec_get_msb(u32 n) -{ - if (n == 0) - return 0; - return 31 ^ __builtin_clz(n); -} - -static short rockchip_vpu981_av1_dec_resolve_divisor_32(u32 d, short *shift) -{ - int f; - u64 e; - - *shift = rockchip_vpu981_av1_dec_get_msb(d); - /* e is obtained from D after resetting the most significant 1 bit. */ - e = d - ((u32)1 << *shift); - /* Get the most significant DIV_LUT_BITS (8) bits of e into f */ - if (*shift > DIV_LUT_BITS) - f = AV1_DIV_ROUND_UP_POW2(e, *shift - DIV_LUT_BITS); - else - f = e << (DIV_LUT_BITS - *shift); - if (f > DIV_LUT_NUM) - return -1; - *shift += DIV_LUT_PREC_BITS; - /* Use f as lookup into the precomputed table of multipliers */ - return div_lut[f]; -} - -static void -rockchip_vpu981_av1_dec_get_shear_params(const u32 *params, s64 *alpha, - s64 *beta, s64 *gamma, s64 *delta) -{ - const int *mat = params; - short shift; - short y; - long long gv, dv; - - if (mat[2] <= 0) - return; - - *alpha = clamp_val(mat[2] - (1 << WARPEDMODEL_PREC_BITS), S16_MIN, S16_MAX); - *beta = clamp_val(mat[3], S16_MIN, S16_MAX); - - y = rockchip_vpu981_av1_dec_resolve_divisor_32(abs(mat[2]), &shift) * (mat[2] < 0 ? -1 : 1); - - gv = ((long long)mat[4] * (1 << WARPEDMODEL_PREC_BITS)) * y; - - *gamma = clamp_val((int)AV1_DIV_ROUND_UP_POW2_SIGNED(gv, shift), S16_MIN, S16_MAX); - - dv = ((long long)mat[3] * mat[4]) * y; - *delta = clamp_val(mat[5] - - (int)AV1_DIV_ROUND_UP_POW2_SIGNED(dv, shift) - (1 << WARPEDMODEL_PREC_BITS), - S16_MIN, S16_MAX); - - *alpha = AV1_DIV_ROUND_UP_POW2_SIGNED(*alpha, WARP_PARAM_REDUCE_BITS) - * (1 << WARP_PARAM_REDUCE_BITS); - *beta = AV1_DIV_ROUND_UP_POW2_SIGNED(*beta, WARP_PARAM_REDUCE_BITS) - * (1 << WARP_PARAM_REDUCE_BITS); - *gamma = AV1_DIV_ROUND_UP_POW2_SIGNED(*gamma, WARP_PARAM_REDUCE_BITS) - * (1 << WARP_PARAM_REDUCE_BITS); - *delta = AV1_DIV_ROUND_UP_POW2_SIGNED(*delta, WARP_PARAM_REDUCE_BITS) - * (1 << WARP_PARAM_REDUCE_BITS); -} static void rockchip_vpu981_av1_dec_set_global_model(struct hantro_ctx *ctx) { struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; - const struct v4l2_av1_global_motion *gm = &frame->global_motion; - u8 *dst = av1_dec->global_model.cpu; struct hantro_dev *vpu = ctx->dev; - int ref_frame, i; - - memset(dst, 0, GLOBAL_MODEL_SIZE); - for (ref_frame = 0; ref_frame < V4L2_AV1_REFS_PER_FRAME; ++ref_frame) { - s64 alpha = 0, beta = 0, gamma = 0, delta = 0; - - for (i = 0; i < 6; ++i) { - if (i == 2) - *(s32 *)dst = - gm->params[V4L2_AV1_REF_LAST_FRAME + ref_frame][3]; - else if (i == 3) - *(s32 *)dst = - gm->params[V4L2_AV1_REF_LAST_FRAME + ref_frame][2]; - else - *(s32 *)dst = - gm->params[V4L2_AV1_REF_LAST_FRAME + ref_frame][i]; - dst += 4; - } - - if (gm->type[V4L2_AV1_REF_LAST_FRAME + ref_frame] <= V4L2_AV1_WARP_MODEL_AFFINE) - rockchip_vpu981_av1_dec_get_shear_params(&gm->params[V4L2_AV1_REF_LAST_FRAME + ref_frame][0], - &alpha, &beta, &gamma, &delta); - - *(s16 *)dst = alpha; - dst += 2; - *(s16 *)dst = beta; - dst += 2; - *(s16 *)dst = gamma; - dst += 2; - *(s16 *)dst = delta; - dst += 2; - } + hantro_av1_set_global_model(ctx); hantro_write_addr(vpu, AV1_GLOBAL_MODEL, av1_dec->global_model.dma); } -static int rockchip_vpu981_av1_tile_log2(int target) -{ - int k; - - /* - * returns the smallest value for k such that 1 << k is greater - * than or equal to target - */ - for (k = 0; (1 << k) < target; k++); - - return k; -} - static void rockchip_vpu981_av1_dec_set_tile_info(struct hantro_ctx *ctx) { struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; const struct v4l2_av1_tile_info *tile_info = &ctrls->frame->tile_info; - const struct v4l2_ctrl_av1_tile_group_entry *group_entry = - ctrls->tile_group_entry; int context_update_y = tile_info->context_update_tile_id / tile_info->tile_cols; int context_update_x = tile_info->context_update_tile_id % tile_info->tile_cols; int context_update_tile_id = context_update_x * tile_info->tile_rows + context_update_y; - u8 *dst = av1_dec->tile_info.cpu; struct hantro_dev *vpu = ctx->dev; - int tile0, tile1; - - memset(dst, 0, av1_dec->tile_info.size); - - for (tile0 = 0; tile0 < tile_info->tile_cols; tile0++) { - for (tile1 = 0; tile1 < tile_info->tile_rows; tile1++) { - int tile_id = tile1 * tile_info->tile_cols + tile0; - u32 start, end; - u32 y0 = - tile_info->height_in_sbs_minus_1[tile1] + 1; - u32 x0 = tile_info->width_in_sbs_minus_1[tile0] + 1; - - /* tile size in SB units (width,height) */ - *dst++ = x0; - *dst++ = 0; - *dst++ = 0; - *dst++ = 0; - *dst++ = y0; - *dst++ = 0; - *dst++ = 0; - *dst++ = 0; - - /* tile start position */ - start = group_entry[tile_id].tile_offset - group_entry[0].tile_offset; - *dst++ = start & 255; - *dst++ = (start >> 8) & 255; - *dst++ = (start >> 16) & 255; - *dst++ = (start >> 24) & 255; - - /* number of bytes in tile data */ - end = start + group_entry[tile_id].tile_size; - *dst++ = end & 255; - *dst++ = (end >> 8) & 255; - *dst++ = (end >> 16) & 255; - *dst++ = (end >> 24) & 255; - } - } + + hantro_av1_set_tile_info(ctx); hantro_reg_write(vpu, &av1_multicore_expect_context_update, !!(context_update_x == 0)); hantro_reg_write(vpu, &av1_tile_enable, @@ -631,8 +44,8 @@ static void rockchip_vpu981_av1_dec_set_tile_info(struct hantro_ctx *ctx) hantro_reg_write(vpu, &av1_num_tile_rows_8k, tile_info->tile_rows); hantro_reg_write(vpu, &av1_context_update_tile_id, context_update_tile_id); hantro_reg_write(vpu, &av1_tile_transpose, 1); - if (rockchip_vpu981_av1_tile_log2(tile_info->tile_cols) || - rockchip_vpu981_av1_tile_log2(tile_info->tile_rows)) + if (hantro_av1_tile_log2(tile_info->tile_cols) || + hantro_av1_tile_log2(tile_info->tile_rows)) hantro_reg_write(vpu, &av1_dec_tile_size_mag, tile_info->tile_size_bytes - 1); else hantro_reg_write(vpu, &av1_dec_tile_size_mag, 3); @@ -640,50 +53,6 @@ static void rockchip_vpu981_av1_dec_set_tile_info(struct hantro_ctx *ctx) hantro_write_addr(vpu, AV1_TILE_BASE, av1_dec->tile_info.dma); } -static int rockchip_vpu981_av1_dec_get_dist(struct hantro_ctx *ctx, - int a, int b) -{ - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - int bits = ctrls->sequence->order_hint_bits - 1; - int diff, m; - - if (!ctrls->sequence->order_hint_bits) - return 0; - - diff = a - b; - m = 1 << bits; - diff = (diff & (m - 1)) - (diff & m); - - return diff; -} - -static void rockchip_vpu981_av1_dec_set_frame_sign_bias(struct hantro_ctx *ctx) -{ - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; - const struct v4l2_ctrl_av1_sequence *sequence = ctrls->sequence; - int i; - - if (!sequence->order_hint_bits || IS_INTRA(frame->frame_type)) { - for (i = 0; i < V4L2_AV1_TOTAL_REFS_PER_FRAME; i++) - av1_dec->ref_frame_sign_bias[i] = 0; - - return; - } - // Identify the nearest forward and backward references. - for (i = 0; i < V4L2_AV1_TOTAL_REFS_PER_FRAME - 1; i++) { - if (rockchip_vpu981_get_frame_index(ctx, i) >= 0) { - int rel_off = - rockchip_vpu981_av1_dec_get_dist(ctx, - rockchip_vpu981_get_order_hint(ctx, i), - frame->order_hint); - av1_dec->ref_frame_sign_bias[i + 1] = (rel_off <= 0) ? 0 : 1; - } - } -} - static bool rockchip_vpu981_av1_dec_set_ref(struct hantro_ctx *ctx, int ref, int idx, int width, int height) @@ -806,12 +175,12 @@ static void rockchip_vpu981_av1_dec_set_segmentation(struct hantro_ctx *ctx) if (!!(seg->flags & V4L2_AV1_SEGMENTATION_FLAG_ENABLED) && frame->primary_ref_frame < V4L2_AV1_REFS_PER_FRAME) { - int idx = rockchip_vpu981_get_frame_index(ctx, frame->primary_ref_frame); + int idx = hantro_av1_get_frame_index(ctx, frame->primary_ref_frame); if (idx >= 0) { dma_addr_t luma_addr, mv_addr = 0; struct hantro_decoded_buffer *seg; - size_t mv_offset = rockchip_vpu981_av1_dec_chroma_size(ctx); + size_t mv_offset = hantro_av1_chroma_size(ctx); seg = vb2_to_hantro_decoded_buf(&av1_dec->frame_refs[idx].vb2_ref->vb2_buf); luma_addr = hantro_get_dec_buf_addr(ctx, &seg->base.vb.vb2_buf); @@ -1041,35 +410,6 @@ static void rockchip_vpu981_av1_dec_set_segmentation(struct hantro_ctx *ctx) segval[7][V4L2_AV1_SEG_LVL_REF_GLOBALMV]); } -static bool rockchip_vpu981_av1_dec_is_lossless(struct hantro_ctx *ctx) -{ - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; - const struct v4l2_av1_segmentation *segmentation = &frame->segmentation; - const struct v4l2_av1_quantization *quantization = &frame->quantization; - int i; - - for (i = 0; i < V4L2_AV1_MAX_SEGMENTS; i++) { - int qindex = quantization->base_q_idx; - - if (segmentation->feature_enabled[i] & - V4L2_AV1_SEGMENT_FEATURE_ENABLED(V4L2_AV1_SEG_LVL_ALT_Q)) { - qindex += segmentation->feature_data[i][V4L2_AV1_SEG_LVL_ALT_Q]; - } - qindex = clamp(qindex, 0, 255); - - if (qindex || - quantization->delta_q_y_dc || - quantization->delta_q_u_dc || - quantization->delta_q_u_ac || - quantization->delta_q_v_dc || - quantization->delta_q_v_ac) - return false; - } - return true; -} - static void rockchip_vpu981_av1_dec_set_loopfilter(struct hantro_ctx *ctx) { struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; @@ -1089,7 +429,7 @@ static void rockchip_vpu981_av1_dec_set_loopfilter(struct hantro_ctx *ctx) hantro_reg_write(vpu, &av1_filt_level3, loop_filter->level[3]); if (loop_filter->flags & V4L2_AV1_LOOP_FILTER_FLAG_DELTA_ENABLED && - !rockchip_vpu981_av1_dec_is_lossless(ctx) && + !hantro_av1_is_lossless(ctx) && !(frame->flags & V4L2_AV1_FRAME_FLAG_ALLOW_INTRABC)) { hantro_reg_write(vpu, &av1_filt_ref_adj_0, loop_filter->ref_deltas[0]); @@ -1128,121 +468,27 @@ static void rockchip_vpu981_av1_dec_set_loopfilter(struct hantro_ctx *ctx) hantro_write_addr(vpu, AV1_DB_CTRL_COL, av1_dec->db_ctrl_col.dma); } -static void rockchip_vpu981_av1_dec_update_prob(struct hantro_ctx *ctx) -{ - struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; - bool frame_is_intra = IS_INTRA(frame->frame_type); - struct av1cdfs *out_cdfs = (struct av1cdfs *)av1_dec->prob_tbl_out.cpu; - int i; - - if (frame->flags & V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF) - return; - - for (i = 0; i < NUM_REF_FRAMES; i++) { - if (frame->refresh_frame_flags & BIT(i)) { - struct mvcdfs stored_mv_cdf; - - rockchip_av1_get_cdfs(ctx, i); - stored_mv_cdf = av1_dec->cdfs->mv_cdf; - *av1_dec->cdfs = *out_cdfs; - if (frame_is_intra) { - av1_dec->cdfs->mv_cdf = stored_mv_cdf; - *av1_dec->cdfs_ndvc = out_cdfs->mv_cdf; - } - rockchip_av1_store_cdfs(ctx, - frame->refresh_frame_flags); - break; - } - } -} - -void rockchip_vpu981_av1_dec_done(struct hantro_ctx *ctx) -{ - rockchip_vpu981_av1_dec_update_prob(ctx); -} - static void rockchip_vpu981_av1_dec_set_prob(struct hantro_ctx *ctx) { struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; - struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; - const struct v4l2_ctrl_av1_frame *frame = ctrls->frame; - const struct v4l2_av1_quantization *quantization = &frame->quantization; struct hantro_dev *vpu = ctx->dev; - bool error_resilient_mode = - !!(frame->flags & V4L2_AV1_FRAME_FLAG_ERROR_RESILIENT_MODE); - bool frame_is_intra = IS_INTRA(frame->frame_type); - - if (error_resilient_mode || frame_is_intra || - frame->primary_ref_frame == AV1_PRIMARY_REF_NONE) { - av1_dec->cdfs = &av1_dec->default_cdfs; - av1_dec->cdfs_ndvc = &av1_dec->default_cdfs_ndvc; - rockchip_av1_default_coeff_probs(quantization->base_q_idx, - av1_dec->cdfs); - } else { - rockchip_av1_get_cdfs(ctx, frame->ref_frame_idx[frame->primary_ref_frame]); - } - rockchip_av1_store_cdfs(ctx, frame->refresh_frame_flags); - - memcpy(av1_dec->prob_tbl.cpu, av1_dec->cdfs, sizeof(struct av1cdfs)); - - if (frame_is_intra) { - int mv_offset = offsetof(struct av1cdfs, mv_cdf); - /* Overwrite MV context area with intrabc MV context */ - memcpy(av1_dec->prob_tbl.cpu + mv_offset, av1_dec->cdfs_ndvc, - sizeof(struct mvcdfs)); - } + hantro_av1_set_prob(ctx); hantro_write_addr(vpu, AV1_PROP_TABLE_OUT, av1_dec->prob_tbl_out.dma); hantro_write_addr(vpu, AV1_PROP_TABLE, av1_dec->prob_tbl.dma); } -static void -rockchip_vpu981_av1_dec_init_scaling_function(const u8 *values, const u8 *scaling, - u8 num_points, u8 *scaling_lut) -{ - int i, point; - - if (num_points == 0) { - memset(scaling_lut, 0, 256); - return; - } - - for (point = 0; point < num_points - 1; point++) { - int x; - s32 delta_y = scaling[point + 1] - scaling[point]; - s32 delta_x = values[point + 1] - values[point]; - s64 delta = - delta_x ? delta_y * ((65536 + (delta_x >> 1)) / - delta_x) : 0; - - for (x = 0; x < delta_x; x++) { - scaling_lut[values[point] + x] = - scaling[point] + - (s32)((x * delta + 32768) >> 16); - } - } - - for (i = values[num_points - 1]; i < 256; i++) - scaling_lut[i] = scaling[num_points - 1]; -} static void rockchip_vpu981_av1_dec_set_fgs(struct hantro_ctx *ctx) { struct hantro_av1_dec_hw_ctx *av1_dec = &ctx->av1_dec; struct hantro_av1_dec_ctrls *ctrls = &av1_dec->ctrls; const struct v4l2_ctrl_av1_film_grain *film_grain = ctrls->film_grain; - struct rockchip_av1_film_grain *fgmem = av1_dec->film_grain.cpu; + struct hantro_av1_film_grain *fgmem = av1_dec->film_grain.cpu; struct hantro_dev *vpu = ctx->dev; bool scaling_from_luma = !!(film_grain->flags & V4L2_AV1_FILM_GRAIN_FLAG_CHROMA_SCALING_FROM_LUMA); - s32 (*ar_coeffs_y)[24]; - s32 (*ar_coeffs_cb)[25]; - s32 (*ar_coeffs_cr)[25]; - s32 (*luma_grain_block)[73][82]; - s32 (*cb_grain_block)[38][44]; - s32 (*cr_grain_block)[38][44]; + struct hantro_av1_coeffs_grain_block *coeffs; s32 ar_coeff_lag, ar_coeff_shift; s32 grain_scale_shift, bitdepth; s32 grain_center, grain_min, grain_max; @@ -1269,18 +515,9 @@ static void rockchip_vpu981_av1_dec_set_fgs(struct hantro_ctx *ctx) return; } - ar_coeffs_y = kzalloc(sizeof(int32_t) * 24, GFP_KERNEL); - ar_coeffs_cb = kzalloc(sizeof(int32_t) * 25, GFP_KERNEL); - ar_coeffs_cr = kzalloc(sizeof(int32_t) * 25, GFP_KERNEL); - luma_grain_block = kzalloc(sizeof(int32_t) * 73 * 82, GFP_KERNEL); - cb_grain_block = kzalloc(sizeof(int32_t) * 38 * 44, GFP_KERNEL); - cr_grain_block = kzalloc(sizeof(int32_t) * 38 * 44, GFP_KERNEL); - - if (!ar_coeffs_y || !ar_coeffs_cb || !ar_coeffs_cr || - !luma_grain_block || !cb_grain_block || !cr_grain_block) { - pr_warn("Fail allocating memory for film grain parameters\n"); - goto alloc_fail; - } + coeffs = kzalloc(sizeof(*coeffs), GFP_KERNEL); + if (!coeffs) + return; hantro_reg_write(vpu, &av1_apply_grain, 1); @@ -1316,10 +553,10 @@ static void rockchip_vpu981_av1_dec_set_fgs(struct hantro_ctx *ctx) hantro_reg_write(vpu, &av1_chroma_scaling_from_luma, scaling_from_luma); hantro_reg_write(vpu, &av1_random_seed, film_grain->grain_seed); - rockchip_vpu981_av1_dec_init_scaling_function(film_grain->point_y_value, - film_grain->point_y_scaling, - film_grain->num_y_points, - fgmem->scaling_lut_y); + hantro_av1_init_scaling_function(film_grain->point_y_value, + film_grain->point_y_scaling, + film_grain->num_y_points, + fgmem->scaling_lut_y); if (film_grain->flags & V4L2_AV1_FILM_GRAIN_FLAG_CHROMA_SCALING_FROM_LUMA) { @@ -1328,19 +565,19 @@ static void rockchip_vpu981_av1_dec_set_fgs(struct hantro_ctx *ctx) memcpy(fgmem->scaling_lut_cr, fgmem->scaling_lut_y, sizeof(*fgmem->scaling_lut_y) * 256); } else { - rockchip_vpu981_av1_dec_init_scaling_function + hantro_av1_init_scaling_function (film_grain->point_cb_value, film_grain->point_cb_scaling, film_grain->num_cb_points, fgmem->scaling_lut_cb); - rockchip_vpu981_av1_dec_init_scaling_function + hantro_av1_init_scaling_function (film_grain->point_cr_value, film_grain->point_cr_scaling, film_grain->num_cr_points, fgmem->scaling_lut_cr); } for (i = 0; i < V4L2_AV1_AR_COEFFS_SIZE; i++) { if (i < 24) - (*ar_coeffs_y)[i] = film_grain->ar_coeffs_y_plus_128[i] - 128; - (*ar_coeffs_cb)[i] = film_grain->ar_coeffs_cb_plus_128[i] - 128; - (*ar_coeffs_cr)[i] = film_grain->ar_coeffs_cr_plus_128[i] - 128; + coeffs->ar_coeffs_y[i] = film_grain->ar_coeffs_y_plus_128[i] - 128; + coeffs->ar_coeffs_cb[i] = film_grain->ar_coeffs_cb_plus_128[i] - 128; + coeffs->ar_coeffs_cr[i] = film_grain->ar_coeffs_cr_plus_128[i] - 128; } ar_coeff_lag = film_grain->ar_coeff_lag; @@ -1351,46 +588,38 @@ static void rockchip_vpu981_av1_dec_set_fgs(struct hantro_ctx *ctx) grain_min = 0 - grain_center; grain_max = (256 << (bitdepth - 8)) - 1 - grain_center; - rockchip_av1_generate_luma_grain_block(luma_grain_block, bitdepth, - film_grain->num_y_points, grain_scale_shift, - ar_coeff_lag, ar_coeffs_y, ar_coeff_shift, - grain_min, grain_max, film_grain->grain_seed); - - rockchip_av1_generate_chroma_grain_block(luma_grain_block, cb_grain_block, - cr_grain_block, bitdepth, - film_grain->num_y_points, - film_grain->num_cb_points, - film_grain->num_cr_points, - grain_scale_shift, ar_coeff_lag, ar_coeffs_cb, - ar_coeffs_cr, ar_coeff_shift, grain_min, - grain_max, - scaling_from_luma, - film_grain->grain_seed); + hantro_av1_generate_luma_grain_block(coeffs, bitdepth, + film_grain->num_y_points, grain_scale_shift, + ar_coeff_lag, ar_coeff_shift, + grain_min, grain_max, film_grain->grain_seed); + + hantro_av1_generate_chroma_grain_block(coeffs, bitdepth, + film_grain->num_y_points, + film_grain->num_cb_points, + film_grain->num_cr_points, + grain_scale_shift, ar_coeff_lag, + ar_coeff_shift, grain_min, + grain_max, + scaling_from_luma, + film_grain->grain_seed); for (i = 0; i < 64; i++) { for (j = 0; j < 64; j++) fgmem->cropped_luma_grain_block[i * 64 + j] = - (*luma_grain_block)[i + 9][j + 9]; + coeffs->luma_grain_block[i + 9][j + 9]; } for (i = 0; i < 32; i++) { for (j = 0; j < 32; j++) { fgmem->cropped_chroma_grain_block[i * 64 + 2 * j] = - (*cb_grain_block)[i + 6][j + 6]; + coeffs->cb_grain_block[i + 6][j + 6]; fgmem->cropped_chroma_grain_block[i * 64 + 2 * j + 1] = - (*cr_grain_block)[i + 6][j + 6]; + coeffs->cr_grain_block[i + 6][j + 6]; } } + kfree(coeffs); hantro_write_addr(vpu, AV1_FILM_GRAIN, av1_dec->film_grain.dma); - -alloc_fail: - kfree(ar_coeffs_y); - kfree(ar_coeffs_cb); - kfree(ar_coeffs_cr); - kfree(luma_grain_block); - kfree(cb_grain_block); - kfree(cr_grain_block); } static void rockchip_vpu981_av1_dec_set_cdef(struct hantro_ctx *ctx) @@ -1617,12 +846,12 @@ static void rockchip_vpu981_av1_dec_set_other_frames(struct hantro_ctx *ctx) int ref_ind = 0; int rf, idx; - alt_frame_offset = rockchip_vpu981_get_order_hint(ctx, ALT_BUF_IDX); - gld_frame_offset = rockchip_vpu981_get_order_hint(ctx, GLD_BUF_IDX); - bwd_frame_offset = rockchip_vpu981_get_order_hint(ctx, BWD_BUF_IDX); - alt2_frame_offset = rockchip_vpu981_get_order_hint(ctx, ALT2_BUF_IDX); + alt_frame_offset = hantro_av1_get_order_hint(ctx, ALT_BUF_IDX); + gld_frame_offset = hantro_av1_get_order_hint(ctx, GLD_BUF_IDX); + bwd_frame_offset = hantro_av1_get_order_hint(ctx, BWD_BUF_IDX); + alt2_frame_offset = hantro_av1_get_order_hint(ctx, ALT2_BUF_IDX); - idx = rockchip_vpu981_get_frame_index(ctx, LST_BUF_IDX); + idx = hantro_av1_get_frame_index(ctx, LST_BUF_IDX); if (idx >= 0) { int alt_frame_offset_in_lst = av1_dec->frame_refs[idx].order_hints[V4L2_AV1_REF_ALTREF_FRAME]; @@ -1644,8 +873,8 @@ static void rockchip_vpu981_av1_dec_set_other_frames(struct hantro_ctx *ctx) ref_stamp--; } - idx = rockchip_vpu981_get_frame_index(ctx, BWD_BUF_IDX); - if (rockchip_vpu981_av1_dec_get_dist(ctx, bwd_frame_offset, cur_frame_offset) > 0) { + idx = hantro_av1_get_frame_index(ctx, BWD_BUF_IDX); + if (hantro_av1_get_dist(ctx, bwd_frame_offset, cur_frame_offset) > 0) { int bwd_mi_cols = av1_dec->frame_refs[idx].mi_cols; int bwd_mi_rows = av1_dec->frame_refs[idx].mi_rows; bool bwd_intra_only = @@ -1659,8 +888,8 @@ static void rockchip_vpu981_av1_dec_set_other_frames(struct hantro_ctx *ctx) } } - idx = rockchip_vpu981_get_frame_index(ctx, ALT2_BUF_IDX); - if (rockchip_vpu981_av1_dec_get_dist(ctx, alt2_frame_offset, cur_frame_offset) > 0) { + idx = hantro_av1_get_frame_index(ctx, ALT2_BUF_IDX); + if (hantro_av1_get_dist(ctx, alt2_frame_offset, cur_frame_offset) > 0) { int alt2_mi_cols = av1_dec->frame_refs[idx].mi_cols; int alt2_mi_rows = av1_dec->frame_refs[idx].mi_rows; bool alt2_intra_only = @@ -1674,8 +903,8 @@ static void rockchip_vpu981_av1_dec_set_other_frames(struct hantro_ctx *ctx) } } - idx = rockchip_vpu981_get_frame_index(ctx, ALT_BUF_IDX); - if (rockchip_vpu981_av1_dec_get_dist(ctx, alt_frame_offset, cur_frame_offset) > 0 && + idx = hantro_av1_get_frame_index(ctx, ALT_BUF_IDX); + if (hantro_av1_get_dist(ctx, alt_frame_offset, cur_frame_offset) > 0 && ref_stamp >= 0) { int alt_mi_cols = av1_dec->frame_refs[idx].mi_cols; int alt_mi_rows = av1_dec->frame_refs[idx].mi_rows; @@ -1690,7 +919,7 @@ static void rockchip_vpu981_av1_dec_set_other_frames(struct hantro_ctx *ctx) } } - idx = rockchip_vpu981_get_frame_index(ctx, LST2_BUF_IDX); + idx = hantro_av1_get_frame_index(ctx, LST2_BUF_IDX); if (idx >= 0 && ref_stamp >= 0) { int lst2_mi_cols = av1_dec->frame_refs[idx].mi_cols; int lst2_mi_rows = av1_dec->frame_refs[idx].mi_rows; @@ -1706,14 +935,14 @@ static void rockchip_vpu981_av1_dec_set_other_frames(struct hantro_ctx *ctx) } for (rf = 0; rf < V4L2_AV1_TOTAL_REFS_PER_FRAME - 1; ++rf) { - idx = rockchip_vpu981_get_frame_index(ctx, rf); + idx = hantro_av1_get_frame_index(ctx, rf); if (idx >= 0) { - int rf_order_hint = rockchip_vpu981_get_order_hint(ctx, rf); + int rf_order_hint = hantro_av1_get_order_hint(ctx, rf); cur_offset[rf] = - rockchip_vpu981_av1_dec_get_dist(ctx, cur_frame_offset, rf_order_hint); + hantro_av1_get_dist(ctx, cur_frame_offset, rf_order_hint); cur_roffset[rf] = - rockchip_vpu981_av1_dec_get_dist(ctx, rf_order_hint, cur_frame_offset); + hantro_av1_get_dist(ctx, rf_order_hint, cur_frame_offset); } else { cur_offset[rf] = 0; cur_roffset[rf] = 0; @@ -1736,32 +965,32 @@ static void rockchip_vpu981_av1_dec_set_other_frames(struct hantro_ctx *ctx) if (use_ref_frame_mvs && ref_ind > 0 && cur_offset[mf_types[0] - V4L2_AV1_REF_LAST_FRAME] <= MAX_FRAME_DISTANCE && cur_offset[mf_types[0] - V4L2_AV1_REF_LAST_FRAME] >= -MAX_FRAME_DISTANCE) { - int rf = rockchip_vpu981_get_order_hint(ctx, refs_selected[0]); - int idx = rockchip_vpu981_get_frame_index(ctx, refs_selected[0]); + int rf = hantro_av1_get_order_hint(ctx, refs_selected[0]); + int idx = hantro_av1_get_frame_index(ctx, refs_selected[0]); u32 *oh = av1_dec->frame_refs[idx].order_hints; int val; hantro_reg_write(vpu, &av1_use_temporal0_mvs, 1); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST_FRAME]); hantro_reg_write(vpu, &av1_mf1_last_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST2_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST2_FRAME]); hantro_reg_write(vpu, &av1_mf1_last2_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST3_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST3_FRAME]); hantro_reg_write(vpu, &av1_mf1_last3_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_GOLDEN_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_GOLDEN_FRAME]); hantro_reg_write(vpu, &av1_mf1_golden_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_BWDREF_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_BWDREF_FRAME]); hantro_reg_write(vpu, &av1_mf1_bwdref_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF2_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF2_FRAME]); hantro_reg_write(vpu, &av1_mf1_altref2_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF_FRAME]); hantro_reg_write(vpu, &av1_mf1_altref_offset, val); } @@ -1776,32 +1005,32 @@ static void rockchip_vpu981_av1_dec_set_other_frames(struct hantro_ctx *ctx) if (use_ref_frame_mvs && ref_ind > 1 && cur_offset[mf_types[1] - V4L2_AV1_REF_LAST_FRAME] <= MAX_FRAME_DISTANCE && cur_offset[mf_types[1] - V4L2_AV1_REF_LAST_FRAME] >= -MAX_FRAME_DISTANCE) { - int rf = rockchip_vpu981_get_order_hint(ctx, refs_selected[1]); - int idx = rockchip_vpu981_get_frame_index(ctx, refs_selected[1]); + int rf = hantro_av1_get_order_hint(ctx, refs_selected[1]); + int idx = hantro_av1_get_frame_index(ctx, refs_selected[1]); u32 *oh = av1_dec->frame_refs[idx].order_hints; int val; hantro_reg_write(vpu, &av1_use_temporal1_mvs, 1); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST_FRAME]); hantro_reg_write(vpu, &av1_mf2_last_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST2_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST2_FRAME]); hantro_reg_write(vpu, &av1_mf2_last2_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST3_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST3_FRAME]); hantro_reg_write(vpu, &av1_mf2_last3_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_GOLDEN_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_GOLDEN_FRAME]); hantro_reg_write(vpu, &av1_mf2_golden_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_BWDREF_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_BWDREF_FRAME]); hantro_reg_write(vpu, &av1_mf2_bwdref_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF2_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF2_FRAME]); hantro_reg_write(vpu, &av1_mf2_altref2_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF_FRAME]); hantro_reg_write(vpu, &av1_mf2_altref_offset, val); } @@ -1816,32 +1045,32 @@ static void rockchip_vpu981_av1_dec_set_other_frames(struct hantro_ctx *ctx) if (use_ref_frame_mvs && ref_ind > 2 && cur_offset[mf_types[2] - V4L2_AV1_REF_LAST_FRAME] <= MAX_FRAME_DISTANCE && cur_offset[mf_types[2] - V4L2_AV1_REF_LAST_FRAME] >= -MAX_FRAME_DISTANCE) { - int rf = rockchip_vpu981_get_order_hint(ctx, refs_selected[2]); - int idx = rockchip_vpu981_get_frame_index(ctx, refs_selected[2]); + int rf = hantro_av1_get_order_hint(ctx, refs_selected[2]); + int idx = hantro_av1_get_frame_index(ctx, refs_selected[2]); u32 *oh = av1_dec->frame_refs[idx].order_hints; int val; hantro_reg_write(vpu, &av1_use_temporal2_mvs, 1); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST_FRAME]); hantro_reg_write(vpu, &av1_mf3_last_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST2_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST2_FRAME]); hantro_reg_write(vpu, &av1_mf3_last2_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST3_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_LAST3_FRAME]); hantro_reg_write(vpu, &av1_mf3_last3_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_GOLDEN_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_GOLDEN_FRAME]); hantro_reg_write(vpu, &av1_mf3_golden_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_BWDREF_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_BWDREF_FRAME]); hantro_reg_write(vpu, &av1_mf3_bwdref_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF2_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF2_FRAME]); hantro_reg_write(vpu, &av1_mf3_altref2_offset, val); - val = rockchip_vpu981_av1_dec_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF_FRAME]); + val = hantro_av1_get_dist(ctx, rf, oh[V4L2_AV1_REF_ALTREF_FRAME]); hantro_reg_write(vpu, &av1_mf3_altref_offset, val); } @@ -1883,7 +1112,7 @@ static void rockchip_vpu981_av1_dec_set_reference_frames(struct hantro_ctx *ctx) if (!allow_intrabc) { for (i = 0; i < V4L2_AV1_REFS_PER_FRAME; i++) { - int idx = rockchip_vpu981_get_frame_index(ctx, i); + int idx = hantro_av1_get_frame_index(ctx, i); if (idx >= 0) ref_count[idx]++; @@ -1898,7 +1127,7 @@ static void rockchip_vpu981_av1_dec_set_reference_frames(struct hantro_ctx *ctx) } hantro_reg_write(vpu, &av1_ref_frames, ref_frames); - rockchip_vpu981_av1_dec_set_frame_sign_bias(ctx); + hantro_av1_set_frame_sign_bias(ctx); for (i = V4L2_AV1_REF_LAST_FRAME; i < V4L2_AV1_TOTAL_REFS_PER_FRAME; i++) { u32 ref = i - 1; @@ -1910,8 +1139,8 @@ static void rockchip_vpu981_av1_dec_set_reference_frames(struct hantro_ctx *ctx) width = frame->frame_width_minus_1 + 1; height = frame->frame_height_minus_1 + 1; } else { - if (rockchip_vpu981_get_frame_index(ctx, ref) > 0) - idx = rockchip_vpu981_get_frame_index(ctx, ref); + if (hantro_av1_get_frame_index(ctx, ref) > 0) + idx = hantro_av1_get_frame_index(ctx, ref); width = av1_dec->frame_refs[idx].width; height = av1_dec->frame_refs[idx].height; } @@ -1943,20 +1172,6 @@ static void rockchip_vpu981_av1_dec_set_reference_frames(struct hantro_ctx *ctx) rockchip_vpu981_av1_dec_set_other_frames(ctx); } -static int rockchip_vpu981_av1_get_hardware_tx_mode(enum v4l2_av1_tx_mode tx_mode) -{ - switch (tx_mode) { - case V4L2_AV1_TX_MODE_ONLY_4X4: - return ROCKCHIP_AV1_TX_MODE_ONLY_4X4; - case V4L2_AV1_TX_MODE_LARGEST: - return ROCKCHIP_AV1_TX_MODE_32x32; - case V4L2_AV1_TX_MODE_SELECT: - return ROCKCHIP_AV1_TX_MODE_SELECT; - } - - return ROCKCHIP_AV1_TX_MODE_32x32; -} - static void rockchip_vpu981_av1_dec_set_parameters(struct hantro_ctx *ctx) { struct hantro_dev *vpu = ctx->dev; @@ -2029,7 +1244,7 @@ static void rockchip_vpu981_av1_dec_set_parameters(struct hantro_ctx *ctx) hantro_reg_write(vpu, &av1_comp_pred_mode, (ctrls->frame->flags & V4L2_AV1_FRAME_FLAG_REFERENCE_SELECT) ? 2 : 0); - tx_mode = rockchip_vpu981_av1_get_hardware_tx_mode(ctrls->frame->tx_mode); + tx_mode = hantro_av1_get_hardware_tx_mode(ctrls->frame->tx_mode); hantro_reg_write(vpu, &av1_transform_mode, tx_mode); hantro_reg_write(vpu, &av1_max_cb_size, (ctrls->sequence->flags @@ -2061,7 +1276,7 @@ static void rockchip_vpu981_av1_dec_set_parameters(struct hantro_ctx *ctx) hantro_reg_write(vpu, &av1_qmlevel_v, 0xff); } - hantro_reg_write(vpu, &av1_lossless_e, rockchip_vpu981_av1_dec_is_lossless(ctx)); + hantro_reg_write(vpu, &av1_lossless_e, hantro_av1_is_lossless(ctx)); hantro_reg_write(vpu, &av1_quant_delta_v_dc, ctrls->frame->quantization.delta_q_v_dc); hantro_reg_write(vpu, &av1_quant_delta_v_ac, ctrls->frame->quantization.delta_q_v_ac); @@ -2109,8 +1324,8 @@ rockchip_vpu981_av1_dec_set_output_buffer(struct hantro_ctx *ctx) struct hantro_decoded_buffer *dst; struct vb2_v4l2_buffer *vb2_dst; dma_addr_t luma_addr, chroma_addr, mv_addr = 0; - size_t cr_offset = rockchip_vpu981_av1_dec_luma_size(ctx); - size_t mv_offset = rockchip_vpu981_av1_dec_chroma_size(ctx); + size_t cr_offset = hantro_av1_luma_size(ctx); + size_t mv_offset = hantro_av1_chroma_size(ctx); vb2_dst = av1_dec->frame_refs[av1_dec->current_frame_index].vb2_ref; dst = vb2_to_hantro_decoded_buf(&vb2_dst->vb2_buf); @@ -2134,7 +1349,7 @@ int rockchip_vpu981_av1_dec_run(struct hantro_ctx *ctx) hantro_start_prepare_run(ctx); - ret = rockchip_vpu981_av1_dec_prepare_run(ctx); + ret = hantro_av1_prepare_run(ctx); if (ret) goto prepare_error; @@ -2144,8 +1359,8 @@ int rockchip_vpu981_av1_dec_run(struct hantro_ctx *ctx) goto prepare_error; } - rockchip_vpu981_av1_dec_clean_refs(ctx); - rockchip_vpu981_av1_dec_frame_ref(ctx, vb2_src->vb2_buf.timestamp); + hantro_av1_clean_refs(ctx); + hantro_av1_frame_ref(ctx, vb2_src->vb2_buf.timestamp); rockchip_vpu981_av1_dec_set_parameters(ctx); rockchip_vpu981_av1_dec_set_global_model(ctx); diff --git a/drivers/media/platform/verisilicon/rockchip_vpu_hw.c b/drivers/media/platform/verisilicon/rockchip_vpu_hw.c index 02673be9878e..f50a3e38097e 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu_hw.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu_hw.c @@ -9,6 +9,7 @@ #include #include "hantro.h" +#include "hantro_av1.h" #include "hantro_jpeg.h" #include "hantro_g1_regs.h" #include "hantro_h1_regs.h" @@ -608,9 +609,9 @@ static const struct hantro_codec_ops rk3568_vepu_codec_ops[] = { static const struct hantro_codec_ops rk3588_vpu981_codec_ops[] = { [HANTRO_MODE_AV1_DEC] = { .run = rockchip_vpu981_av1_dec_run, - .init = rockchip_vpu981_av1_dec_init, - .exit = rockchip_vpu981_av1_dec_exit, - .done = rockchip_vpu981_av1_dec_done, + .init = hantro_av1_init, + .exit = hantro_av1_exit, + .done = hantro_av1_update_prob, }, }; /* -- 2.43.0 From vladimir.oltean at nxp.com Tue May 5 02:22:01 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Tue, 5 May 2026 12:22:01 +0300 Subject: [PATCH v7 phy-next 25/27] phy: include PHY provider header (2/2) In-Reply-To: <20260430110652.558622-26-vladimir.oltean@nxp.com> References: <20260430110652.558622-1-vladimir.oltean@nxp.com> <20260430110652.558622-26-vladimir.oltean@nxp.com> Message-ID: <20260505092201.uiam3gl3cctjxurw@skbuf> Sashiko says: > commit 4194b60e90a65a3b71a94359af9d2b530bae01d8 > Author: Vladimir Oltean > phy: include PHY provider header (2/2) > > This commit replaces the inclusion of the generic linux/phy/phy.h header > with the internal PHY provider header across numerous PHY drivers. It targets > drivers that act as PHY providers or chained provider/consumers to ensure they > include the appropriate provider API definitions. > [ ... ] > > > diff --git a/include/linux/phy/ulpi_phy.h b/include/linux/phy/ulpi_phy.h > > index 7054b440347ce..0f9e8430d398e 100644 > > --- a/include/linux/phy/ulpi_phy.h > > +++ b/include/linux/phy/ulpi_phy.h > > @@ -1,5 +1,5 @@ > > /* SPDX-License-Identifier: GPL-2.0 */ > > -#include > > +#include "../../drivers/phy/phy-provider.h" > > Since this header is located in include/linux/phy/, won't navigating up two > levels resolve to the include/ directory? That would make the compiler search > for include/drivers/phy/phy-provider.h, which does not exist, and could cause > build failures. The LLM review is right that the relative include path is wrong. > Also, does including a private subsystem header from a public API header in > include/linux/ cause issues for out-of-tree builds? This exposes private > structures and could cause regressions for external modules that include > since they won't have access to the kernel's drivers/ > directory. > > If the provider API is needed here, would it be better to move the functions > that depend on it out of the public header into a .c file? I have moved the file from include/linux/phy/ulpi_phy.h to drivers/phy/ulpi_phy.h for v8, and updated the relative include path to just "phy-provider.h". Frankly, it is an oversight that ulpi_phy.h was still left in the public directory. > > /** > > * Helper that registers PHY for a ULPI device and adds a lookup for binding it From vladimir.oltean at nxp.com Tue May 5 02:24:35 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Tue, 5 May 2026 12:24:35 +0300 Subject: [PATCH v7 phy-next 21/27] net: renesas: rswitch: include PHY provider header In-Reply-To: <20260430110652.558622-22-vladimir.oltean@nxp.com> References: <20260430110652.558622-1-vladimir.oltean@nxp.com> <20260430110652.558622-22-vladimir.oltean@nxp.com> Message-ID: <20260505092435.dsfsv6t4mpzluzqf@skbuf> Sashiko says: > commit 4e15b6920cd2b2dfce685835fa09c66664407579 > Author: Vladimir Oltean > net: renesas: rswitch: include PHY provider header > > This commit includes the PHY provider header in the rswitch driver as a > workaround to avoid breaking the build when struct phy becomes opaque. > It allows the driver to continue accessing internal PHY fields while > acknowledging that the driver's PHY API call sequence needs a complete > redesign. > > > diff --git a/drivers/net/ethernet/renesas/rswitch_main.c b/drivers/net/ethernet/renesas/rswitch_main.c > > index 6fe964816322a..132be5f15073c 100644 > > --- a/drivers/net/ethernet/renesas/rswitch_main.c > > +++ b/drivers/net/ethernet/renesas/rswitch_main.c > > @@ -27,6 +27,7 @@ > > #include > > #include > > > > +#include "../../../phy/phy-provider.h" /* FIXME */ > > Does preserving the ability to check the provider's global reference counts > (power_count and init_count) via this internal header leave the driver > vulnerable to data races? > > Since these fields are protected by the PHY provider's internal phy->mutex, > reading them locklessly can create a time-of-check to time-of-use > vulnerability. The count could be altered by a concurrent operation before > the mutex is acquired inside phy_power_off() or phy_exit(). > > Additionally, if the PHY is shared across multiple ports (like in QSGMII > mode), would checking the global reference count instead of tracking the > consumer's local vote cause a port to observe a non-zero count from > another port's vote? This could lead to an incorrect phy_power_off() > call, prematurely decrementing the shared refcount and powering down the > PHY while it is still in active use by other ports. > > While the commit message notes this is a known issue preserved as a > workaround until a complete redesign, could retaining these unlocked > reads cause a regression in power vote tracking in the interim? > > > #include "rswitch.h" > > #include "rswitch_l2.h" I will ignore this comment. Inaction will leave the driver in exactly the same state as before. Not great, true, but I don't see how not changing anything could cause a regression. From vladimir.oltean at nxp.com Tue May 5 02:34:51 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Tue, 5 May 2026 12:34:51 +0300 Subject: [PATCH v7 phy-next 14/27] usb: gadget: tegra-xudc: avoid direct dereference of phy->dev.of_node In-Reply-To: <20260430110652.558622-15-vladimir.oltean@nxp.com> References: <20260430110652.558622-1-vladimir.oltean@nxp.com> <20260430110652.558622-15-vladimir.oltean@nxp.com> Message-ID: <20260505093451.ll4agt6lmlixsz5g@skbuf> Sashiko says: > commit 1ca88ec2e954b3a331eb5afb8081406d9200c8f6 > Author: Vladimir Oltean > usb: gadget: tegra-xudc: avoid direct dereference of phy->dev.of_node > > This commit replaces direct dereferences of the device tree node with a > parallel lookup using the phys phandle and the padctl provider. It also > introduces a local usbphy variable to hold the output and simplify error > checks. > > > diff --git a/drivers/usb/gadget/udc/tegra-xudc.c b/drivers/usb/gadget/udc/tegra-xudc.c > > index e9d33be028664..cf4e6c87e44da 100644 > > --- a/drivers/usb/gadget/udc/tegra-xudc.c > > +++ b/drivers/usb/gadget/udc/tegra-xudc.c > [ ... ] > > @@ -3515,7 +3516,10 @@ static int tegra_xudc_phy_get(struct tegra_xudc *xudc) > > xudc->vbus_nb.notifier_call = tegra_xudc_vbus_notify; > > > > for (i = 0; i < xudc->soc->num_phys; i++) { > > + struct of_phandle_args args; > > char phy_name[] = "usb.-."; > > + struct usb_phy *usbphy; > > + int index, err; > > This declares a local err variable inside the loop, shadowing the outer > err variable from the function scope. > Later in this loop, if devm_phy_optional_get() or devm_usb_get_phy_by_node() > fails, the code assigns the error code to this inner err and uses goto > clean_up. > Since the inner err is destroyed when leaving the loop block, will the > function incorrectly return the outer err which is still 0? This is correct, I've removed the duplicated "err" variable declaration for v8. > > > > /* Get USB2 phy */ > > snprintf(phy_name, sizeof(phy_name), "usb2-%d", i); > [ ... ] > > @@ -3525,22 +3529,31 @@ static int tegra_xudc_phy_get(struct tegra_xudc *xudc) > > dev_err_probe(xudc->dev, err, > > "failed to get PHY for phy-name usb2-%d\n", i); > > goto clean_up; > > - } else if (xudc->utmi_phy[i]) { > [ ... ] > > } else if (!xudc->utmi_phy[i]) { > > /* if utmi phy is not available, ignore USB3 phy get */ > > continue; > > } > > > > + index = of_property_match_string(np, "phy-names", phy_name); > > + if (index < 0) > > + continue; > > + > > + err = of_parse_phandle_with_args(np, "phys", "#phy-cells", > > + index, &args); > > + if (err) > > + continue; > > If either of these device tree parsing steps fail, the code uses continue > to skip to the next iteration. > Does this unintentionally skip the entire USB3 PHY initialization block > that follows later in this loop for the current port? This is correct, although theoretically of_property_match_string() and of_parse_phandle_with_args() should not fail as long as devm_phy_optional_get() succeeded for that very same phy_name starting from the very same OF node. But nonetheless, if this happens due to bugs, I've instead treated the condition as fatal and printed error messages for v8. > > + > > + /* Get usb-phy, if utmi phy is available */ > > + usbphy = devm_usb_get_phy_by_node(xudc->dev, args.np, NULL); > > + of_node_put(args.np); > [ ... ] From vladimir.oltean at nxp.com Tue May 5 02:39:25 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Tue, 5 May 2026 12:39:25 +0300 Subject: [PATCH v7 phy-next 13/27] usb: host: tegra: avoid direct dereference of phy->dev.of_node In-Reply-To: <20260430110652.558622-14-vladimir.oltean@nxp.com> References: <20260430110652.558622-1-vladimir.oltean@nxp.com> <20260430110652.558622-14-vladimir.oltean@nxp.com> Message-ID: <20260505093925.c3rshtu7hcuwglbp@skbuf> Sashiko says: > commit 037a329bd1e70f26b8d861fb06f3bbc539fb2c49 > Author: Vladimir Oltean > usb: host: tegra: avoid direct dereference of phy->dev.of_node > > This commit updates the Tegra USB host driver to retrieve the PHY OF node > using the "phys" phandle rather than directly accessing phy->dev.of_node. > This addresses an upcoming change where struct phy will become an opaque > pointer. It also simplifies a debug message to log the array index. > > > diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c > > index d2214d309e96..99b0ac9763f9 100644 > > --- a/drivers/usb/host/xhci-tegra.c > > +++ b/drivers/usb/host/xhci-tegra.c > > @@ -1491,6 +1491,7 @@ static int tegra_xhci_id_notify(struct notifier_block *nb, > > > > static int tegra_xusb_init_usb_phy(struct tegra_xusb *tegra) > > { > > + struct device_node *np = dev_of_node(tegra->dev); > > unsigned int i; > > > > tegra->usbphy = devm_kcalloc(tegra->dev, tegra->num_usb_phys, > > @@ -1504,23 +1505,33 @@ static int tegra_xusb_init_usb_phy(struct tegra_xusb *tegra) > > tegra->otg_usb3_port = -EINVAL; > > > > for (i = 0; i < tegra->num_usb_phys; i++) { > > - struct phy *phy = tegra_xusb_get_phy(tegra, "usb2", i); > > + struct of_phandle_args args; > > + struct usb_phy *usbphy; > > + int index, err; > > + char prop[8]; > > > > - if (!phy) > > + snprintf(prop, sizeof(prop), "usb2-%d", i); > > + > > + /* > > + * usb-phy is optional, continue if it's not available. > > + */ > > + index = of_property_match_string(np, "phy-names", prop); > > + if (index < 0) > > continue; > > > > - tegra->usbphy[i] = devm_usb_get_phy_by_node(tegra->dev, > > - phy->dev.of_node, > > - &tegra->id_nb); > > - if (!IS_ERR(tegra->usbphy[i])) { > > - dev_dbg(tegra->dev, "usbphy-%d registered", i); > > - otg_set_host(tegra->usbphy[i]->otg, &tegra->hcd->self); > > - } else { > > - /* > > - * usb-phy is optional, continue if its not available. > > - */ > > - tegra->usbphy[i] = NULL; > > - } > > + err = of_parse_phandle_with_args(np, "phys", "#phy-cells", > > + index, &args); > > + if (err) > > + continue; > > The call to of_parse_phandle_with_args() increments the reference count > of the device tree node and populates it in args.np. The new code passes > args.np to devm_usb_get_phy_by_node(), but does not appear to call > of_node_put() to drop the reference when it is done. Does this leak the > device node reference on both the success and error paths of the loop? I've added an of_node_put(args.np) after devm_usb_get_phy_by_node() for v8. > > + > > + usbphy = devm_usb_get_phy_by_node(tegra->dev, args.np, > > + &tegra->id_nb); > > Also in tegra_xusb_init_usb_phy(), the single notifier block instance > tegra->id_nb is passed to devm_usb_get_phy_by_node() for every USB PHY > being processed. Because the struct notifier_block API uses an embedded next > pointer for its singly-linked list, does registering the exact same object > to multiple independent PHY notifier chains overwrite its next pointer and > corrupt the lists? If this is a problem, it is a pre-existing and completely unrelated problem which I don't have knowledge how to handle, so I will leave it alone. > > + if (IS_ERR(usbphy)) > > + continue; > > + > > + tegra->usbphy[i] = usbphy; > > + dev_dbg(tegra->dev, "usbphy-%d registered", i); > > + otg_set_host(tegra->usbphy[i]->otg, &tegra->hcd->self); > > } > > > > return 0; > [...] > > drivers/usb/host/xhci-tegra.c | 42 ++++++++++++++++++++++------------- > > 1 file changed, 26 insertions(+), 16 deletions(-) > > > > @@ -2168,8 +2179,7 @@ static void tegra_xhci_disable_phy_wake(struct tegra_xusb *tegra) > > continue; > > > > if (tegra_xusb_padctl_remote_wake_detected(padctl, tegra->phys[i])) > > - dev_dbg(tegra->dev, "%pOF remote wake detected\n", > > - tegra->phys[i]->dev.of_node); > > + dev_dbg(tegra->dev, "PHY %d remote wake detected\n", i); > > > > tegra_xusb_padctl_disable_phy_wake(padctl, tegra->phys[i]); > > } > > In tegra_xhci_disable_phy_wake(), the loop counter i is declared as an > unsigned int. The updated dev_dbg() message formats this variable using > the %d specifier. This isn't a bug since the values are bounded by > tegra->num_phys, but should it use %u to match the unsigned integer type? I've changed to %u printing for v8. From krzysztof.kozlowski at oss.qualcomm.com Tue May 5 02:43:17 2026 From: krzysztof.kozlowski at oss.qualcomm.com (Krzysztof Kozlowski) Date: Tue, 5 May 2026 11:43:17 +0200 Subject: [PATCH 1/2] pinctrl: bcm: Move MODULE_DEVICE_TABLE next to the table itself Message-ID: <20260505094321.75040-3-krzysztof.kozlowski@oss.qualcomm.com> By convention MODULE_DEVICE_TABLE() immediately follows the ID table it exports, because this is easier to read and verify. It also makes more sense since #ifdef for ACPI or OF could hide both of them. Most of the pin controller drivers already have this correctly placed, so adjust the other drivers. No functional impact. Signed-off-by: Krzysztof Kozlowski --- Apologies for sending it immediately after previous Qualcomm patch without grouping as patchset. Initially I fixed only Qualcomm but then figure out that poor patterns like to spread and I can also investigate other files. --- drivers/pinctrl/bcm/pinctrl-bcm4908.c | 2 +- drivers/pinctrl/bcm/pinctrl-ns.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pinctrl/bcm/pinctrl-bcm4908.c b/drivers/pinctrl/bcm/pinctrl-bcm4908.c index 12f7a253ea4d..57969cdbc635 100644 --- a/drivers/pinctrl/bcm/pinctrl-bcm4908.c +++ b/drivers/pinctrl/bcm/pinctrl-bcm4908.c @@ -466,6 +466,7 @@ static const struct of_device_id bcm4908_pinctrl_of_match_table[] = { { .compatible = "brcm,bcm4908-pinctrl", }, { } }; +MODULE_DEVICE_TABLE(of, bcm4908_pinctrl_of_match_table); static int bcm4908_pinctrl_probe(struct platform_device *pdev) { @@ -561,4 +562,3 @@ module_platform_driver(bcm4908_pinctrl_driver); MODULE_AUTHOR("Rafa? Mi?ecki"); MODULE_DESCRIPTION("Broadcom BCM4908 pinmux driver"); MODULE_LICENSE("GPL v2"); -MODULE_DEVICE_TABLE(of, bcm4908_pinctrl_of_match_table); diff --git a/drivers/pinctrl/bcm/pinctrl-ns.c b/drivers/pinctrl/bcm/pinctrl-ns.c index 03bd01b4a945..e134c9c73450 100644 --- a/drivers/pinctrl/bcm/pinctrl-ns.c +++ b/drivers/pinctrl/bcm/pinctrl-ns.c @@ -204,6 +204,7 @@ static const struct of_device_id ns_pinctrl_of_match_table[] = { { .compatible = "brcm,bcm53012-pinmux", .data = (void *)FLAG_BCM53012, }, { } }; +MODULE_DEVICE_TABLE(of, ns_pinctrl_of_match_table); static int ns_pinctrl_probe(struct platform_device *pdev) { @@ -295,4 +296,3 @@ static struct platform_driver ns_pinctrl_driver = { module_platform_driver(ns_pinctrl_driver); MODULE_AUTHOR("Rafa? Mi?ecki"); -MODULE_DEVICE_TABLE(of, ns_pinctrl_of_match_table); -- 2.51.0 From krzysztof.kozlowski at oss.qualcomm.com Tue May 5 02:43:18 2026 From: krzysztof.kozlowski at oss.qualcomm.com (Krzysztof Kozlowski) Date: Tue, 5 May 2026 11:43:18 +0200 Subject: [PATCH 2/2] pinctrl: rockchip: Move MODULE_DEVICE_TABLE next to the table itself In-Reply-To: <20260505094321.75040-3-krzysztof.kozlowski@oss.qualcomm.com> References: <20260505094321.75040-3-krzysztof.kozlowski@oss.qualcomm.com> Message-ID: <20260505094321.75040-4-krzysztof.kozlowski@oss.qualcomm.com> By convention MODULE_DEVICE_TABLE() immediately follows the ID table it exports, because this is easier to read and verify. It also makes more sense since #ifdef for ACPI or OF could hide both of them. Most of the pin controller drivers already have this correctly placed, so adjust the other drivers. No functional impact. Signed-off-by: Krzysztof Kozlowski --- drivers/pinctrl/pinctrl-rockchip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/pinctrl-rockchip.c b/drivers/pinctrl/pinctrl-rockchip.c index 2f14c7f9c95a..7e0fcd45fd26 100644 --- a/drivers/pinctrl/pinctrl-rockchip.c +++ b/drivers/pinctrl/pinctrl-rockchip.c @@ -5303,6 +5303,7 @@ static const struct of_device_id rockchip_pinctrl_dt_match[] = { .data = &rk3588_pin_ctrl }, {}, }; +MODULE_DEVICE_TABLE(of, rockchip_pinctrl_dt_match); static struct platform_driver rockchip_pinctrl_driver = { .probe = rockchip_pinctrl_probe, @@ -5329,4 +5330,3 @@ module_exit(rockchip_pinctrl_drv_unregister); MODULE_DESCRIPTION("ROCKCHIP Pin Controller Driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:pinctrl-rockchip"); -MODULE_DEVICE_TABLE(of, rockchip_pinctrl_dt_match); -- 2.51.0 From vladimir.oltean at nxp.com Tue May 5 03:04:53 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Tue, 5 May 2026 13:04:53 +0300 Subject: [PATCH v8 phy-next 01/31] PCI: cadence: Preserve all error codes in cdns_plat_pcie_probe() In-Reply-To: <20260505100523.1922388-1-vladimir.oltean@nxp.com> References: <20260505100523.1922388-1-vladimir.oltean@nxp.com> Message-ID: <20260505100523.1922388-2-vladimir.oltean@nxp.com> The blamed commit functionally changed the error path of cdns_pcie_host_probe(), now cdns_plat_pcie_probe(). When the old code path executed "goto err_get_sync", the PCIe controller probe function propagated the pm_runtime_get_sync() error code. The new code doesn't, and returns 0. Similarly for the "goto err_init" previously triggered by cdns_pcie_host_init() errors, and now triggered by cdns_pcie_host_setup() and cdns_pcie_ep_setup() errors. These are not propagated and will result in probing success, which is incorrect. Fixes: bd22885aa188 ("PCI: cadence: Refactor driver to use as a core library") Signed-off-by: Vladimir Oltean --- Cc: Bjorn Helgaas Cc: "Krzysztof Wilczy?ski" Cc: Lorenzo Pieralisi Cc: Manikandan K Pillai Cc: Manivannan Sadhasivam Cc: Rob Herring v7->v8: patch is new, issue was flagged by Sashiko https://sashiko.dev/#/patchset/20260430110652.558622-1-vladimir.oltean at nxp.com --- drivers/pci/controller/cadence/pcie-cadence-plat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/cadence/pcie-cadence-plat.c b/drivers/pci/controller/cadence/pcie-cadence-plat.c index b067a3296dd3..8b12a46b5601 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-plat.c +++ b/drivers/pci/controller/cadence/pcie-cadence-plat.c @@ -126,7 +126,7 @@ static int cdns_plat_pcie_probe(struct platform_device *pdev) while (phy_count--) device_link_del(cdns_plat_pcie->pcie->link[phy_count]); - return 0; + return ret; } static void cdns_plat_pcie_shutdown(struct platform_device *pdev) -- 2.34.1 From vladimir.oltean at nxp.com Tue May 5 03:04:54 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Tue, 5 May 2026 13:04:54 +0300 Subject: [PATCH v8 phy-next 02/31] ata: add where missing In-Reply-To: <20260505100523.1922388-1-vladimir.oltean@nxp.com> References: <20260505100523.1922388-1-vladimir.oltean@nxp.com> Message-ID: <20260505100523.1922388-3-vladimir.oltean@nxp.com> It appears that libahci.c, ahci.c as well as the ahci_brcm, ahci_ceva and ahci_qoriq drivers are using runtime PM operations without including . This header is somehow being indirectly provided by , which would like to drop it (none of the functions it exports need it). Signed-off-by: Vladimir Oltean Acked-by: Damien Le Moal --- Cc: Damien Le Moal Cc: Niklas Cassel v2->v8: none v1->v2: collect tag --- drivers/ata/ahci.c | 1 + drivers/ata/ahci_brcm.c | 1 + drivers/ata/ahci_ceva.c | 1 + drivers/ata/ahci_qoriq.c | 1 + drivers/ata/libahci.c | 1 + 5 files changed, 5 insertions(+) diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c index 1d73a53370cf..1396a53bd6df 100644 --- a/drivers/ata/ahci.c +++ b/drivers/ata/ahci.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/ata/ahci_brcm.c b/drivers/ata/ahci_brcm.c index 29be74fedcf0..48460e515722 100644 --- a/drivers/ata/ahci_brcm.c +++ b/drivers/ata/ahci_brcm.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include diff --git a/drivers/ata/ahci_ceva.c b/drivers/ata/ahci_ceva.c index 2d6a08c23d6a..3938bf378341 100644 --- a/drivers/ata/ahci_ceva.c +++ b/drivers/ata/ahci_ceva.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "ahci.h" diff --git a/drivers/ata/ahci_qoriq.c b/drivers/ata/ahci_qoriq.c index 0dec1a17e5b1..409152bfefb6 100644 --- a/drivers/ata/ahci_qoriq.c +++ b/drivers/ata/ahci_qoriq.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "ahci.h" diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index c79abdfcd7a9..e0de4703a4f2 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "ahci.h" #include "libata.h" -- 2.34.1 From vladimir.oltean at nxp.com Tue May 5 03:04:55 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Tue, 5 May 2026 13:04:55 +0300 Subject: [PATCH v8 phy-next 03/31] PCI: Add missing headers transitively included by In-Reply-To: <20260505100523.1922388-1-vladimir.oltean@nxp.com> References: <20260505100523.1922388-1-vladimir.oltean@nxp.com> Message-ID: <20260505100523.1922388-4-vladimir.oltean@nxp.com> The tegra as well as a few dwc PCI controller drivers uses PM runtime operations without including the required header. Similarly, pcie-rockchip-host, pcie-starfive as well as a few dwc PCI controllers use the regulator consumer API without including . pcie-spacemit-k1.c uses of_get_next_available_child() and of_node_put() without including . It seems these function prototypes were indirectly provided by , mostly by mistake (none of the functions it exports need it). Before the PHY header can drop the unnecessary includes, make sure the PCI controller drivers include what they use. Signed-off-by: Vladimir Oltean Acked-by: Bjorn Helgaas --- Cc: Lorenzo Pieralisi Cc: "Krzysztof Wilczy?ski" Cc: Manivannan Sadhasivam Cc: Rob Herring Cc: Bjorn Helgaas Cc: Heiko Stuebner Cc: Shawn Guo Cc: Yixun Lan Cc: Thierry Reding Cc: Jonathan Hunter Cc: Shawn Lin Cc: Kevin Xie v5->v8: none v4->v5: fix pcie-spacemit-k1 driver, previously missed due to limited build coverage v2->v4: none v1->v2: collect tag, adjust commit title --- drivers/pci/controller/dwc/pci-keystone.c | 1 + drivers/pci/controller/dwc/pcie-dw-rockchip.c | 1 + drivers/pci/controller/dwc/pcie-histb.c | 1 + drivers/pci/controller/dwc/pcie-qcom-ep.c | 1 + drivers/pci/controller/dwc/pcie-spacemit-k1.c | 3 +++ drivers/pci/controller/dwc/pcie-tegra194.c | 1 + drivers/pci/controller/pci-tegra.c | 1 + drivers/pci/controller/pcie-rockchip-host.c | 1 + drivers/pci/controller/plda/pcie-starfive.c | 1 + 9 files changed, 11 insertions(+) diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index 278d2dba1db0..fe4836925c4e 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 731d93663cca..ae27ce05247c 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pci/controller/dwc/pcie-histb.c b/drivers/pci/controller/dwc/pcie-histb.c index a52071589377..432a54c5bfce 100644 --- a/drivers/pci/controller/dwc/pcie-histb.c +++ b/drivers/pci/controller/dwc/pcie-histb.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include diff --git a/drivers/pci/controller/dwc/pcie-qcom-ep.c b/drivers/pci/controller/dwc/pcie-qcom-ep.c index 257c2bcb5f76..35a297923e7f 100644 --- a/drivers/pci/controller/dwc/pcie-qcom-ep.c +++ b/drivers/pci/controller/dwc/pcie-qcom-ep.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pci/controller/dwc/pcie-spacemit-k1.c b/drivers/pci/controller/dwc/pcie-spacemit-k1.c index be20a520255b..41316aa54106 100644 --- a/drivers/pci/controller/dwc/pcie-spacemit-k1.c +++ b/drivers/pci/controller/dwc/pcie-spacemit-k1.c @@ -13,9 +13,12 @@ #include #include #include +#include #include #include +#include #include +#include #include #include diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c index 9dcfa194050e..f1d08814a73c 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194.c +++ b/drivers/pci/controller/dwc/pcie-tegra194.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pci/controller/pci-tegra.c b/drivers/pci/controller/pci-tegra.c index 512309763d1f..a2c1662b6e81 100644 --- a/drivers/pci/controller/pci-tegra.c +++ b/drivers/pci/controller/pci-tegra.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/pci/controller/pcie-rockchip-host.c b/drivers/pci/controller/pcie-rockchip-host.c index ee1822ca01db..46adb4582fcc 100644 --- a/drivers/pci/controller/pcie-rockchip-host.c +++ b/drivers/pci/controller/pcie-rockchip-host.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "../pci.h" #include "pcie-rockchip.h" diff --git a/drivers/pci/controller/plda/pcie-starfive.c b/drivers/pci/controller/plda/pcie-starfive.c index 298036c3e7f9..22344cca167b 100644 --- a/drivers/pci/controller/plda/pcie-starfive.c +++ b/drivers/pci/controller/plda/pcie-starfive.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "../../pci.h" -- 2.34.1 From vladimir.oltean at nxp.com Tue May 5 03:04:56 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Tue, 5 May 2026 13:04:56 +0300 Subject: [PATCH v8 phy-next 04/31] usb: add missing headers transitively included by In-Reply-To: <20260505100523.1922388-1-vladimir.oltean@nxp.com> References: <20260505100523.1922388-1-vladimir.oltean@nxp.com> Message-ID: <20260505100523.1922388-5-vladimir.oltean@nxp.com> The chipidea ci_hdrc_imx driver uses regulator consumer API like regulator_enable() but does not include . The core USB HCD driver calls invalidate_kernel_vmap_range() and flush_kernel_vmap_range(), but does not include . The DWC3 gadget driver calls: - device_property_present() - device_property_count_u8() - device_property_read_u8_array() but does not include Similarly, dwc3-imx uses device_property_read_bool() without including . The dwc3-generic-plat driver uses of_device_get_match_data() but does not include . In all these cases, the necessary includes were still provided somehow, directly or indirectly, through . I found the following command to be quite helpful in figuring out the include chain: $ make KCFLAGS="-H" drivers/usb/dwc3/dwc3-imx.o Since wants to drop the unnecessary includes, fill in the required headers to avoid any breakage. Signed-off-by: Vladimir Oltean Acked-by: Thinh Nguyen # dwc3 Acked-by: Greg Kroah-Hartman --- Cc: Peter Chen Cc: Greg Kroah-Hartman Cc: Frank Li Cc: Sascha Hauer Cc: Pengutronix Kernel Team Cc: Fabio Estevam Cc: Thinh Nguyen Cc: Xu Yang v7->v8: none v6->v7: - add drivers/usb/dwc3/dwc3-imx.c to the list of patched files - collect tag from Greg, keeping it despite the new addition because the change is minor and in the same spirit as the rest (https://lore.kernel.org/linux-phy/2026033028-squint-yield-4c23 at gregkh/) v2->v6: none v1->v2: collect tag --- drivers/usb/chipidea/ci_hdrc_imx.c | 1 + drivers/usb/core/hcd.c | 1 + drivers/usb/dwc3/dwc3-generic-plat.c | 1 + drivers/usb/dwc3/dwc3-imx.c | 1 + drivers/usb/dwc3/gadget.c | 1 + 5 files changed, 5 insertions(+) diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 56d2ba824a0b..0a21d7cc5f5a 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "ci.h" #include "ci_hdrc_imx.h" diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 89221f1ce769..b3826ebcbe98 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -10,6 +10,7 @@ */ #include +#include #include #include #include diff --git a/drivers/usb/dwc3/dwc3-generic-plat.c b/drivers/usb/dwc3/dwc3-generic-plat.c index ca69ac0eb07c..2f2ae6f4704f 100644 --- a/drivers/usb/dwc3/dwc3-generic-plat.c +++ b/drivers/usb/dwc3/dwc3-generic-plat.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include diff --git a/drivers/usb/dwc3/dwc3-imx.c b/drivers/usb/dwc3/dwc3-imx.c index 973a486b544d..6e122674edaf 100644 --- a/drivers/usb/dwc3/dwc3-imx.c +++ b/drivers/usb/dwc3/dwc3-imx.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "core.h" #include "glue.h" diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 3d4ca68e584c..b5a6fd2899f1 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include -- 2.34.1 From vladimir.oltean at nxp.com Tue May 5 03:04:57 2026 From: vladimir.oltean at nxp.com (Vladimir Oltean) Date: Tue, 5 May 2026 13:04:57 +0300 Subject: [PATCH v8 phy-next 05/31] drm: add where missing In-Reply-To: <20260505100523.1922388-1-vladimir.oltean@nxp.com> References: <20260505100523.1922388-1-vladimir.oltean@nxp.com> Message-ID: <20260505100523.1922388-6-vladimir.oltean@nxp.com> Multiple DRM bridge drivers use runtime PM operations without including the proper header, instead relying on transitive inclusion by . The PHY subsystem wants to get rid of headers it provides for no reason, so modify these drivers to include what they need directly. Signed-off-by: Vladimir Oltean --- Cc: Andrzej Hajda Cc: Neil Armstrong Cc: Robert Foss Cc: Laurent Pinchart Cc: Jonas Karlman Cc: Jernej Skrabec Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: Thomas Zimmermann Cc: David Airlie Cc: Simona Vetter Cc: Inki Dae Cc: Jagan Teki Cc: Marek Szyprowski Cc: Rob Clark Cc: Dmitry Baryshkov v1->v8: none --- drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 1 + drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c | 1 + drivers/gpu/drm/bridge/nwl-dsi.c | 1 + drivers/gpu/drm/bridge/samsung-dsim.c | 1 + drivers/gpu/drm/msm/dp/dp_aux.c | 1 + drivers/gpu/drm/rockchip/cdn-dp-core.c | 1 + 6 files changed, 6 insertions(+) diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c index 8dee5f2fbde5..4ee08663e626 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c index 064c6915c896..d20c0f8ad04c 100644 --- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c index 2f7429b24fc2..9ac8796ae91e 100644 --- a/drivers/gpu/drm/bridge/nwl-dsi.c +++ b/drivers/gpu/drm/bridge/nwl-dsi.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c index c3eb437ef1b0..4244434747af 100644 --- a/drivers/gpu/drm/bridge/samsung-dsim.c +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include